Add GitHub Sync - Automated repository synchronization tool
Complete implementation of automated GitHub repository synchronization: - Webhook-based auto-sync from GitHub - Multi-repository support with branch selection - Web dashboard for management - Manual sync and rollback functionality - Comprehensive logging and monitoring Located in /gitpusher/ subdirectory as standalone application.
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
# Deny all access to source directory
|
||||
Require all denied
|
||||
|
||||
# Alternative for older Apache versions
|
||||
<IfModule !mod_authz_core.c>
|
||||
Order deny,allow
|
||||
Deny from all
|
||||
</IfModule>
|
||||
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
/**
|
||||
* ConfigManager - Handles reading and writing JSON configuration files
|
||||
*/
|
||||
class ConfigManager {
|
||||
private $dataDir;
|
||||
|
||||
public function __construct($dataDir = '/gitpusher/data') {
|
||||
$this->dataDir = $dataDir;
|
||||
$this->ensureDataDirExists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure data directory exists with proper permissions
|
||||
*/
|
||||
private function ensureDataDirExists() {
|
||||
if (!file_exists($this->dataDir)) {
|
||||
mkdir($this->dataDir, 0755, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read JSON file
|
||||
*/
|
||||
public function read($filename) {
|
||||
$filepath = $this->dataDir . '/' . $filename;
|
||||
|
||||
if (!file_exists($filepath)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$content = file_get_contents($filepath);
|
||||
$data = json_decode($content, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
error_log("JSON decode error in $filename: " . json_last_error_msg());
|
||||
return [];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write JSON file
|
||||
*/
|
||||
public function write($filename, $data) {
|
||||
$filepath = $this->dataDir . '/' . $filename;
|
||||
|
||||
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
if ($json === false) {
|
||||
error_log("JSON encode error: " . json_last_error_msg());
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = file_put_contents($filepath, $json);
|
||||
|
||||
if ($result === false) {
|
||||
error_log("Failed to write file: $filepath");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set appropriate permissions (readable only by owner)
|
||||
chmod($filepath, 0600);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all repositories from config
|
||||
*/
|
||||
public function getRepositories() {
|
||||
$config = $this->read('config.json');
|
||||
return $config['repositories'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get repository by ID
|
||||
*/
|
||||
public function getRepository($repoId) {
|
||||
$repos = $this->getRepositories();
|
||||
|
||||
foreach ($repos as $repo) {
|
||||
if ($repo['id'] === $repoId) {
|
||||
return $repo;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new repository
|
||||
*/
|
||||
public function addRepository($repoData) {
|
||||
$config = $this->read('config.json');
|
||||
|
||||
if (!isset($config['repositories'])) {
|
||||
$config['repositories'] = [];
|
||||
}
|
||||
|
||||
// Generate unique ID
|
||||
$repoData['id'] = uniqid('repo_', true);
|
||||
$repoData['created_at'] = date('Y-m-d H:i:s');
|
||||
$repoData['status'] = 'pending';
|
||||
|
||||
$config['repositories'][] = $repoData;
|
||||
|
||||
return $this->write('config.json', $config) ? $repoData['id'] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update repository
|
||||
*/
|
||||
public function updateRepository($repoId, $updates) {
|
||||
$config = $this->read('config.json');
|
||||
|
||||
if (!isset($config['repositories'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($config['repositories'] as &$repo) {
|
||||
if ($repo['id'] === $repoId) {
|
||||
$repo = array_merge($repo, $updates);
|
||||
$repo['updated_at'] = date('Y-m-d H:i:s');
|
||||
return $this->write('config.json', $config);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete repository
|
||||
*/
|
||||
public function deleteRepository($repoId) {
|
||||
$config = $this->read('config.json');
|
||||
|
||||
if (!isset($config['repositories'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$config['repositories'] = array_filter($config['repositories'], function($repo) use ($repoId) {
|
||||
return $repo['id'] !== $repoId;
|
||||
});
|
||||
|
||||
// Re-index array
|
||||
$config['repositories'] = array_values($config['repositories']);
|
||||
|
||||
return $this->write('config.json', $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get GitHub Personal Access Token
|
||||
*/
|
||||
public function getGitHubToken() {
|
||||
$secrets = $this->read('secrets.json');
|
||||
return $secrets['github_pat'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set GitHub Personal Access Token
|
||||
*/
|
||||
public function setGitHubToken($token) {
|
||||
$secrets = $this->read('secrets.json');
|
||||
$secrets['github_pat'] = $token;
|
||||
return $this->write('secrets.json', $secrets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get webhook secret for a repository
|
||||
*/
|
||||
public function getWebhookSecret($repoId) {
|
||||
$secrets = $this->read('secrets.json');
|
||||
return $secrets['webhook_secrets'][$repoId] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set webhook secret for a repository
|
||||
*/
|
||||
public function setWebhookSecret($repoId, $secret) {
|
||||
$secrets = $this->read('secrets.json');
|
||||
|
||||
if (!isset($secrets['webhook_secrets'])) {
|
||||
$secrets['webhook_secrets'] = [];
|
||||
}
|
||||
|
||||
$secrets['webhook_secrets'][$repoId] = $secret;
|
||||
|
||||
return $this->write('secrets.json', $secrets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate secure webhook secret
|
||||
*/
|
||||
public function generateWebhookSecret() {
|
||||
return bin2hex(random_bytes(32));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHandler - Handles all Git operations
|
||||
*/
|
||||
class GitHandler {
|
||||
private $logger;
|
||||
private $configManager;
|
||||
|
||||
public function __construct(Logger $logger, ConfigManager $configManager) {
|
||||
$this->logger = $logger;
|
||||
$this->configManager = $configManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute shell command and return result
|
||||
*/
|
||||
private function exec($command, $cwd = null) {
|
||||
$descriptorspec = [
|
||||
0 => ['pipe', 'r'], // stdin
|
||||
1 => ['pipe', 'w'], // stdout
|
||||
2 => ['pipe', 'w'] // stderr
|
||||
];
|
||||
|
||||
$process = proc_open($command, $descriptorspec, $pipes, $cwd);
|
||||
|
||||
if (!is_resource($process)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'output' => '',
|
||||
'error' => 'Failed to execute command',
|
||||
'exit_code' => -1
|
||||
];
|
||||
}
|
||||
|
||||
$stdout = stream_get_contents($pipes[1]);
|
||||
$stderr = stream_get_contents($pipes[2]);
|
||||
|
||||
fclose($pipes[0]);
|
||||
fclose($pipes[1]);
|
||||
fclose($pipes[2]);
|
||||
|
||||
$exitCode = proc_close($process);
|
||||
|
||||
return [
|
||||
'success' => $exitCode === 0,
|
||||
'output' => trim($stdout),
|
||||
'error' => trim($stderr),
|
||||
'exit_code' => $exitCode
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Git URL with authentication token
|
||||
*/
|
||||
private function buildGitUrl($repoUrl, $token) {
|
||||
// Convert https://github.com/user/repo.git to https://TOKEN@github.com/user/repo.git
|
||||
$parsed = parse_url($repoUrl);
|
||||
|
||||
if (!$parsed || !isset($parsed['host'])) {
|
||||
return $repoUrl;
|
||||
}
|
||||
|
||||
$scheme = $parsed['scheme'] ?? 'https';
|
||||
$host = $parsed['host'];
|
||||
$path = $parsed['path'] ?? '';
|
||||
|
||||
return "$scheme://$token@$host$path";
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone repository
|
||||
*/
|
||||
public function cloneRepository($repoId, $repoUrl, $targetPath, $branch = 'main') {
|
||||
$this->logger->info($repoId, "Starting clone of repository to $targetPath");
|
||||
|
||||
// Check if target path already exists
|
||||
if (file_exists($targetPath)) {
|
||||
$this->logger->error($repoId, "Target path already exists: $targetPath");
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Target path already exists'
|
||||
];
|
||||
}
|
||||
|
||||
// Create parent directory if it doesn't exist
|
||||
$parentDir = dirname($targetPath);
|
||||
if (!file_exists($parentDir)) {
|
||||
mkdir($parentDir, 0755, true);
|
||||
}
|
||||
|
||||
// Get GitHub token
|
||||
$token = $this->configManager->getGitHubToken();
|
||||
$gitUrl = $token ? $this->buildGitUrl($repoUrl, $token) : $repoUrl;
|
||||
|
||||
// Clone repository
|
||||
$command = sprintf(
|
||||
'git clone --branch %s %s %s 2>&1',
|
||||
escapeshellarg($branch),
|
||||
escapeshellarg($gitUrl),
|
||||
escapeshellarg($targetPath)
|
||||
);
|
||||
|
||||
$result = $this->exec($command);
|
||||
|
||||
if ($result['success']) {
|
||||
$this->logger->success($repoId, "Repository cloned successfully", [
|
||||
'path' => $targetPath,
|
||||
'branch' => $branch
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'Repository cloned successfully',
|
||||
'output' => $result['output']
|
||||
];
|
||||
} else {
|
||||
$this->logger->error($repoId, "Failed to clone repository", [
|
||||
'error' => $result['error'],
|
||||
'output' => $result['output']
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Failed to clone repository',
|
||||
'error' => $result['error']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull latest changes
|
||||
*/
|
||||
public function pull($repoId, $targetPath, $branch = 'main') {
|
||||
$this->logger->info($repoId, "Starting pull for $targetPath");
|
||||
|
||||
// Check if path exists
|
||||
if (!file_exists($targetPath)) {
|
||||
$this->logger->error($repoId, "Repository path does not exist: $targetPath");
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Repository path does not exist'
|
||||
];
|
||||
}
|
||||
|
||||
// Check if it's a git repository
|
||||
if (!file_exists("$targetPath/.git")) {
|
||||
$this->logger->error($repoId, "Not a git repository: $targetPath");
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Not a git repository'
|
||||
];
|
||||
}
|
||||
|
||||
// Get current commit before pull
|
||||
$currentCommit = $this->getCurrentCommit($targetPath);
|
||||
|
||||
// Pull changes
|
||||
$command = sprintf(
|
||||
'cd %s && git pull origin %s 2>&1',
|
||||
escapeshellarg($targetPath),
|
||||
escapeshellarg($branch)
|
||||
);
|
||||
|
||||
$result = $this->exec($command);
|
||||
|
||||
// Check for merge conflicts
|
||||
if (!$result['success'] || strpos($result['output'], 'CONFLICT') !== false) {
|
||||
$this->logger->warning($repoId, "Merge conflict detected", [
|
||||
'output' => $result['output'],
|
||||
'error' => $result['error']
|
||||
]);
|
||||
|
||||
$this->configManager->updateRepository($repoId, ['status' => 'conflict']);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Merge conflict detected',
|
||||
'conflict' => true,
|
||||
'output' => $result['output']
|
||||
];
|
||||
}
|
||||
|
||||
// Get new commit after pull
|
||||
$newCommit = $this->getCurrentCommit($targetPath);
|
||||
|
||||
// Count changed files
|
||||
$changedFiles = $this->getChangedFilesBetweenCommits($targetPath, $currentCommit, $newCommit);
|
||||
|
||||
if ($result['success']) {
|
||||
$this->logger->success($repoId, "Pull completed successfully", [
|
||||
'files_changed' => count($changedFiles),
|
||||
'old_commit' => substr($currentCommit, 0, 7),
|
||||
'new_commit' => substr($newCommit, 0, 7)
|
||||
]);
|
||||
|
||||
$this->configManager->updateRepository($repoId, [
|
||||
'status' => 'synced',
|
||||
'last_sync' => date('Y-m-d H:i:s'),
|
||||
'last_commit' => $newCommit
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'Pull completed successfully',
|
||||
'files_changed' => count($changedFiles),
|
||||
'output' => $result['output']
|
||||
];
|
||||
} else {
|
||||
$this->logger->error($repoId, "Pull failed", [
|
||||
'error' => $result['error'],
|
||||
'output' => $result['output']
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Pull failed',
|
||||
'error' => $result['error']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert to specific commit
|
||||
*/
|
||||
public function revert($repoId, $targetPath, $commitHash) {
|
||||
$this->logger->info($repoId, "Starting revert to commit $commitHash");
|
||||
|
||||
if (!file_exists($targetPath)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Repository path does not exist'
|
||||
];
|
||||
}
|
||||
|
||||
// Create revert commit
|
||||
$command = sprintf(
|
||||
'cd %s && git revert --no-edit %s 2>&1',
|
||||
escapeshellarg($targetPath),
|
||||
escapeshellarg($commitHash)
|
||||
);
|
||||
|
||||
$result = $this->exec($command);
|
||||
|
||||
if ($result['success']) {
|
||||
$this->logger->success($repoId, "Reverted to commit $commitHash", [
|
||||
'commit' => $commitHash
|
||||
]);
|
||||
|
||||
$newCommit = $this->getCurrentCommit($targetPath);
|
||||
|
||||
$this->configManager->updateRepository($repoId, [
|
||||
'last_commit' => $newCommit,
|
||||
'last_sync' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'Revert completed successfully',
|
||||
'output' => $result['output']
|
||||
];
|
||||
} else {
|
||||
$this->logger->error($repoId, "Revert failed", [
|
||||
'error' => $result['error']
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Revert failed',
|
||||
'error' => $result['error']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current commit hash
|
||||
*/
|
||||
public function getCurrentCommit($targetPath) {
|
||||
$result = $this->exec("cd " . escapeshellarg($targetPath) . " && git rev-parse HEAD 2>&1");
|
||||
return $result['success'] ? trim($result['output']) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get commit history
|
||||
*/
|
||||
public function getCommitHistory($targetPath, $limit = 20) {
|
||||
$command = sprintf(
|
||||
'cd %s && git log --pretty=format:"%%H|%%an|%%ae|%%at|%%s" -n %d 2>&1',
|
||||
escapeshellarg($targetPath),
|
||||
(int)$limit
|
||||
);
|
||||
|
||||
$result = $this->exec($command);
|
||||
|
||||
if (!$result['success']) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$commits = [];
|
||||
$lines = explode("\n", $result['output']);
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (empty($line)) continue;
|
||||
|
||||
$parts = explode('|', $line);
|
||||
if (count($parts) !== 5) continue;
|
||||
|
||||
$commits[] = [
|
||||
'hash' => $parts[0],
|
||||
'hash_short' => substr($parts[0], 0, 7),
|
||||
'author_name' => $parts[1],
|
||||
'author_email' => $parts[2],
|
||||
'timestamp' => date('Y-m-d H:i:s', (int)$parts[3]),
|
||||
'message' => $parts[4]
|
||||
];
|
||||
}
|
||||
|
||||
return $commits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get changed files between commits
|
||||
*/
|
||||
private function getChangedFilesBetweenCommits($targetPath, $oldCommit, $newCommit) {
|
||||
if ($oldCommit === $newCommit) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$command = sprintf(
|
||||
'cd %s && git diff --name-only %s %s 2>&1',
|
||||
escapeshellarg($targetPath),
|
||||
escapeshellarg($oldCommit),
|
||||
escapeshellarg($newCommit)
|
||||
);
|
||||
|
||||
$result = $this->exec($command);
|
||||
|
||||
if (!$result['success']) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_filter(explode("\n", $result['output']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get repository status
|
||||
*/
|
||||
public function getStatus($targetPath) {
|
||||
$command = sprintf(
|
||||
'cd %s && git status --porcelain 2>&1',
|
||||
escapeshellarg($targetPath)
|
||||
);
|
||||
|
||||
$result = $this->exec($command);
|
||||
|
||||
return [
|
||||
'success' => $result['success'],
|
||||
'clean' => $result['success'] && empty($result['output']),
|
||||
'output' => $result['output']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current branch
|
||||
*/
|
||||
public function getCurrentBranch($targetPath) {
|
||||
$result = $this->exec("cd " . escapeshellarg($targetPath) . " && git branch --show-current 2>&1");
|
||||
return $result['success'] ? trim($result['output']) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch available branches from remote
|
||||
*/
|
||||
public function getRemoteBranches($repoUrl) {
|
||||
$token = $this->configManager->getGitHubToken();
|
||||
$gitUrl = $token ? $this->buildGitUrl($repoUrl, $token) : $repoUrl;
|
||||
|
||||
$command = sprintf(
|
||||
'git ls-remote --heads %s 2>&1',
|
||||
escapeshellarg($gitUrl)
|
||||
);
|
||||
|
||||
$result = $this->exec($command);
|
||||
|
||||
if (!$result['success']) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$branches = [];
|
||||
$lines = explode("\n", $result['output']);
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (preg_match('/refs\/heads\/(.+)$/', $line, $matches)) {
|
||||
$branches[] = $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
return $branches;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
/**
|
||||
* Logger - Manages log entries for sync operations
|
||||
*/
|
||||
class Logger {
|
||||
private $configManager;
|
||||
private $maxLogEntries = 1000; // Keep last 1000 entries
|
||||
|
||||
public function __construct(ConfigManager $configManager) {
|
||||
$this->configManager = $configManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add log entry
|
||||
*/
|
||||
public function log($repoId, $type, $message, $details = []) {
|
||||
$logs = $this->configManager->read('log.json');
|
||||
|
||||
if (!isset($logs['entries'])) {
|
||||
$logs['entries'] = [];
|
||||
}
|
||||
|
||||
$entry = [
|
||||
'id' => uniqid('log_', true),
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
'repo_id' => $repoId,
|
||||
'type' => $type, // success, error, warning, info
|
||||
'message' => $message,
|
||||
'details' => $details
|
||||
];
|
||||
|
||||
// Add to beginning of array
|
||||
array_unshift($logs['entries'], $entry);
|
||||
|
||||
// Keep only last N entries
|
||||
if (count($logs['entries']) > $this->maxLogEntries) {
|
||||
$logs['entries'] = array_slice($logs['entries'], 0, $this->maxLogEntries);
|
||||
}
|
||||
|
||||
$this->configManager->write('log.json', $logs);
|
||||
|
||||
// Also log to PHP error log for debugging
|
||||
error_log("[GitPusher] [$type] $repoId: $message");
|
||||
|
||||
return $entry['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Log success
|
||||
*/
|
||||
public function success($repoId, $message, $details = []) {
|
||||
return $this->log($repoId, 'success', $message, $details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log error
|
||||
*/
|
||||
public function error($repoId, $message, $details = []) {
|
||||
return $this->log($repoId, 'error', $message, $details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log warning
|
||||
*/
|
||||
public function warning($repoId, $message, $details = []) {
|
||||
return $this->log($repoId, 'warning', $message, $details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log info
|
||||
*/
|
||||
public function info($repoId, $message, $details = []) {
|
||||
return $this->log($repoId, 'info', $message, $details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all log entries
|
||||
*/
|
||||
public function getAll($limit = 100, $offset = 0) {
|
||||
$logs = $this->configManager->read('log.json');
|
||||
$entries = $logs['entries'] ?? [];
|
||||
|
||||
return array_slice($entries, $offset, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logs for specific repository
|
||||
*/
|
||||
public function getByRepository($repoId, $limit = 100) {
|
||||
$logs = $this->configManager->read('log.json');
|
||||
$entries = $logs['entries'] ?? [];
|
||||
|
||||
$filtered = array_filter($entries, function($entry) use ($repoId) {
|
||||
return $entry['repo_id'] === $repoId;
|
||||
});
|
||||
|
||||
return array_slice(array_values($filtered), 0, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logs by type
|
||||
*/
|
||||
public function getByType($type, $limit = 100) {
|
||||
$logs = $this->configManager->read('log.json');
|
||||
$entries = $logs['entries'] ?? [];
|
||||
|
||||
$filtered = array_filter($entries, function($entry) use ($type) {
|
||||
return $entry['type'] === $type;
|
||||
});
|
||||
|
||||
return array_slice(array_values($filtered), 0, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all logs
|
||||
*/
|
||||
public function clear() {
|
||||
return $this->configManager->write('log.json', ['entries' => []]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear logs for specific repository
|
||||
*/
|
||||
public function clearByRepository($repoId) {
|
||||
$logs = $this->configManager->read('log.json');
|
||||
$entries = $logs['entries'] ?? [];
|
||||
|
||||
$filtered = array_filter($entries, function($entry) use ($repoId) {
|
||||
return $entry['repo_id'] !== $repoId;
|
||||
});
|
||||
|
||||
$logs['entries'] = array_values($filtered);
|
||||
|
||||
return $this->configManager->write('log.json', $logs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get statistics
|
||||
*/
|
||||
public function getStats() {
|
||||
$logs = $this->configManager->read('log.json');
|
||||
$entries = $logs['entries'] ?? [];
|
||||
|
||||
$stats = [
|
||||
'total' => count($entries),
|
||||
'success' => 0,
|
||||
'error' => 0,
|
||||
'warning' => 0,
|
||||
'info' => 0,
|
||||
'last_24h' => 0
|
||||
];
|
||||
|
||||
$yesterday = strtotime('-24 hours');
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$stats[$entry['type']]++;
|
||||
|
||||
$timestamp = strtotime($entry['timestamp']);
|
||||
if ($timestamp >= $yesterday) {
|
||||
$stats['last_24h']++;
|
||||
}
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user