mirror of
git://f0xx.org/android_cast
synced 2026-07-29 06:18:42 +03:00
sync BE
This commit is contained in:
@@ -31,6 +31,10 @@ try {
|
||||
$build = BuildRunner::rerunBuild($buildId, null, $userId);
|
||||
json_out(['ok' => true, 'build' => $build]);
|
||||
}
|
||||
if ($action === 'force_rerun') {
|
||||
$build = BuildRunner::forceRerun($buildId, $userId);
|
||||
json_out(['ok' => true, 'build' => $build]);
|
||||
}
|
||||
if ($action === 'rerun_as') {
|
||||
$override = is_array($body['params'] ?? null) ? $body['params'] : [];
|
||||
$build = BuildRunner::rerunBuild($buildId, $override, $userId);
|
||||
|
||||
@@ -2,4 +2,6 @@
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../../src/bootstrap.php';
|
||||
Auth::check();
|
||||
BuildRunner::reconcileRunningBuilds();
|
||||
BuildRunner::reconcileQueuedBuilds();
|
||||
json_out(['ok' => true, 'builds' => BuildRepository::listRecent(10)]);
|
||||
|
||||
@@ -39,27 +39,82 @@
|
||||
}
|
||||
}
|
||||
|
||||
function updateBuildRow(row, build) {
|
||||
if (!row || !build) return;
|
||||
row.setAttribute('data-build-status', build.status || '');
|
||||
var cell = row.querySelector('[data-build-status-cell="' + build.id + '"]');
|
||||
updateBuildStatusCell(cell, build.status, build.phase);
|
||||
var play = row.querySelector('.build-transport-play');
|
||||
var stop = row.querySelector('.build-transport-stop');
|
||||
var active = build.status === 'running' || build.status === 'queued';
|
||||
if (play) {
|
||||
play.setAttribute('data-build-status', build.status || '');
|
||||
}
|
||||
if (stop) {
|
||||
stop.setAttribute('data-build-status', build.status || '');
|
||||
stop.disabled = !active;
|
||||
}
|
||||
}
|
||||
|
||||
function refreshRecentBuilds() {
|
||||
var tbody = document.getElementById('builds-recent-tbody');
|
||||
if (!tbody) return;
|
||||
fetch(base + '/api/builds.php', { credentials: 'same-origin' })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (j) {
|
||||
if (!j.ok || !j.builds) return;
|
||||
j.builds.forEach(function (b) {
|
||||
var cell = tbody.querySelector('[data-build-status-cell="' + b.id + '"]');
|
||||
updateBuildStatusCell(cell, b.status, b.phase);
|
||||
});
|
||||
})
|
||||
.catch(function () {})
|
||||
.then(function () {
|
||||
setTimeout(refreshRecentBuilds, 4000);
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', base + '/api/builds.php', true);
|
||||
xhr.onload = function () {
|
||||
var j = null;
|
||||
try { j = JSON.parse(xhr.responseText); } catch (e) { j = null; }
|
||||
if (!j || !j.ok || !j.builds) return;
|
||||
j.builds.forEach(function (b) {
|
||||
var row = tbody.querySelector('tr[data-build-id="' + b.id + '"]');
|
||||
updateBuildRow(row, b);
|
||||
});
|
||||
};
|
||||
xhr.send();
|
||||
setTimeout(refreshRecentBuilds, 1000);
|
||||
}
|
||||
|
||||
function bindBuildListTransport() {
|
||||
var tbody = document.getElementById('builds-recent-tbody');
|
||||
if (!tbody) return;
|
||||
tbody.addEventListener('click', function (ev) {
|
||||
var play = ev.target.closest('.build-transport-play');
|
||||
var stop = ev.target.closest('.build-transport-stop');
|
||||
if (play) {
|
||||
var pid = Number(play.getAttribute('data-build-id'));
|
||||
var pst = play.getAttribute('data-build-status') || '';
|
||||
if (!pid) return;
|
||||
var msg = pst === 'running' || pst === 'queued'
|
||||
? 'Force re-run? This stops the current run and starts a new build with the same parameters.'
|
||||
: null;
|
||||
if (msg && !confirm(msg)) return;
|
||||
postBuildAction({ action: 'force_rerun', build_id: pid }).then(function (j) {
|
||||
if (j.build && j.build.id) {
|
||||
window.location.href = base + '/?view=build&id=' + j.build.id;
|
||||
}
|
||||
}).catch(function (err) {
|
||||
alert(err.message || 'Re-run failed');
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (stop) {
|
||||
var sid = Number(stop.getAttribute('data-build-id'));
|
||||
if (!sid || stop.disabled) return;
|
||||
if (!confirm('Stop this build now?')) return;
|
||||
postBuildAction({ action: 'stop', build_id: sid }).then(function () {
|
||||
refreshRecentBuilds();
|
||||
}).catch(function (err) {
|
||||
alert(err.message || 'Stop failed');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (document.body.getAttribute('data-view') === 'home' && document.getElementById('builds-recent-tbody')) {
|
||||
setTimeout(refreshRecentBuilds, 4000);
|
||||
bindBuildListTransport();
|
||||
refreshRecentBuilds();
|
||||
}
|
||||
|
||||
var themeSel = document.getElementById('theme-select');
|
||||
if (themeSel) {
|
||||
var cur = localStorage.getItem('crash_console_theme') || 'dark';
|
||||
|
||||
@@ -112,6 +112,7 @@ SQL);
|
||||
public static function healthSummary(): array {
|
||||
self::ensureSchema();
|
||||
BuildRunner::reconcileRunningBuilds();
|
||||
BuildRunner::reconcileQueuedBuilds();
|
||||
$pdo = Database::pdo();
|
||||
$running = (int) $pdo->query("SELECT COUNT(*) FROM builds WHERE status = 'running'")->fetchColumn();
|
||||
$failed = (int) $pdo->query("SELECT COUNT(*) FROM builds WHERE status = 'failed' AND created_at > (NOW() - INTERVAL 7 DAY)")->fetchColumn();
|
||||
|
||||
@@ -131,12 +131,6 @@ YAML;
|
||||
}
|
||||
|
||||
$phase = !empty($params['ssh_debug']) ? 'ssh_debug' : 'docker';
|
||||
BuildRepository::update($id, [
|
||||
'status' => 'running',
|
||||
'phase' => $phase,
|
||||
'started_at' => gmdate('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
$runner = (string) cfg('build.runner_script', '/workspace/scripts/docker-build-runner.sh');
|
||||
$repo = (string) cfg('build.repo_root', '/workspace');
|
||||
$gitRemote = trim((string) shell_exec(
|
||||
@@ -176,7 +170,16 @@ YAML;
|
||||
}
|
||||
$cmd = implode(' ', $env) . ' nohup ' . escapeshellarg($runner) . ' >> ' . escapeshellarg($logPath) . ' 2>&1 & echo $!';
|
||||
$pid = trim((string) shell_exec($cmd));
|
||||
if ($pid === '' || !ctype_digit($pid)) {
|
||||
self::failBuild($id, 'Build runner failed to start', $logPath);
|
||||
return BuildRepository::getById($id) ?? ['id' => $id, 'build_code' => $buildCode, 'status' => 'failed'];
|
||||
}
|
||||
file_put_contents($dir . '/runner.pid', $pid);
|
||||
BuildRepository::update($id, [
|
||||
'status' => 'running',
|
||||
'phase' => $phase,
|
||||
'started_at' => gmdate('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
return BuildRepository::getById($id) ?? ['id' => $id, 'build_code' => $buildCode];
|
||||
}
|
||||
@@ -195,7 +198,25 @@ YAML;
|
||||
|
||||
public static function stopBuild(int $id): bool {
|
||||
$build = BuildRepository::getById($id);
|
||||
if (!$build || ($build['status'] ?? '') !== 'running') {
|
||||
if (!$build) {
|
||||
return false;
|
||||
}
|
||||
$status = (string) ($build['status'] ?? '');
|
||||
if ($status === 'queued') {
|
||||
$dir = self::artifactDir($id);
|
||||
$logPath = $build['log_path'] ?? ($dir . '/build.log');
|
||||
if ($logPath) {
|
||||
file_put_contents($logPath, "\n[builder] Stopped while queued\n", FILE_APPEND);
|
||||
}
|
||||
BuildRepository::update($id, [
|
||||
'status' => 'cancelled',
|
||||
'phase' => 'stopped',
|
||||
'error_message' => 'Stopped while queued',
|
||||
'finished_at' => gmdate('Y-m-d H:i:s'),
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
if ($status !== 'running') {
|
||||
return false;
|
||||
}
|
||||
$dir = self::artifactDir($id);
|
||||
@@ -240,6 +261,19 @@ YAML;
|
||||
return self::enqueue($params, $userId);
|
||||
}
|
||||
|
||||
/** Stop an in-flight build (if any) and enqueue a fresh run with the same params. */
|
||||
public static function forceRerun(int $id, ?int $userId = null): array {
|
||||
$build = BuildRepository::getById($id);
|
||||
if (!$build) {
|
||||
throw new InvalidArgumentException('build_not_found');
|
||||
}
|
||||
$status = (string) ($build['status'] ?? '');
|
||||
if (in_array($status, ['running', 'queued'], true)) {
|
||||
self::stopBuild($id);
|
||||
}
|
||||
return self::rerunBuild($id, null, $userId);
|
||||
}
|
||||
|
||||
/** CircleCI-style: resume from the step that failed (git → docker-build → docker-run). */
|
||||
public static function detectFailedStep(int $buildId): string {
|
||||
$dir = self::artifactDir($buildId);
|
||||
@@ -364,6 +398,33 @@ YAML;
|
||||
}
|
||||
}
|
||||
|
||||
public static function reconcileQueuedBuilds(): void {
|
||||
BuildRepository::ensureSchema();
|
||||
$stmt = Database::pdo()->query("SELECT id, log_path, created_at FROM builds WHERE status = 'queued'");
|
||||
if (!$stmt) {
|
||||
return;
|
||||
}
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$id = (int) ($row['id'] ?? 0);
|
||||
if ($id <= 0) {
|
||||
continue;
|
||||
}
|
||||
$logPath = (string) ($row['log_path'] ?? (self::artifactDir($id) . '/build.log'));
|
||||
if (self::runnerAlive($id)) {
|
||||
BuildRepository::update($id, [
|
||||
'status' => 'running',
|
||||
'phase' => 'docker',
|
||||
'started_at' => gmdate('Y-m-d H:i:s'),
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
$created = strtotime((string) ($row['created_at'] ?? '')) ?: 0;
|
||||
if ($created > 0 && $created < time() - 120) {
|
||||
self::failBuild($id, 'Build stuck in queued (runner never started)', $logPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Last lines / markers from build.log for reconcile error_message (max 512 chars). */
|
||||
public static function summarizeLogFailure(?string $logPath): string {
|
||||
if (!$logPath || !is_file($logPath)) {
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
<section class="card card--lift" style="margin-bottom:16px">
|
||||
<h2>Ecosystem health</h2>
|
||||
<ul>
|
||||
<li>Docker: <strong><?= h($health['docker'] ?? 'unknown') ?></strong></li>
|
||||
<ul id="build-health-list">
|
||||
<li>Docker: <strong id="build-health-docker"><?= h($health['docker'] ?? 'unknown') ?></strong></li>
|
||||
<li>CI image version: <code><?= h($health['ci_version'] ?? '') ?></code></li>
|
||||
<li>Running builds: <?= (int) ($health['running'] ?? 0) ?></li>
|
||||
<li>Running builds: <span id="build-health-running"><?= (int) ($health['running'] ?? 0) ?></span></li>
|
||||
<li>Passed (7d): <?= (int) ($health['passed_7d'] ?? 0) ?> · Failed (7d): <?= (int) ($health['failed_7d'] ?? 0) ?></li>
|
||||
</ul>
|
||||
</section>
|
||||
@@ -40,9 +40,10 @@
|
||||
<section class="card card--lift">
|
||||
<h2>Recent builds (last 10)</h2>
|
||||
<div class="reports-table-wrap">
|
||||
<table class="data-table">
|
||||
<table class="data-table" id="builds-recent-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="build-col-controls" aria-label="Job controls"></th>
|
||||
<th>Code</th>
|
||||
<th>Status</th>
|
||||
<th>Phase</th>
|
||||
@@ -52,13 +53,22 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="builds-recent-tbody">
|
||||
<?php foreach ($builds as $b): ?>
|
||||
<tr data-build-id="<?= (int) $b['id'] ?>">
|
||||
<?php foreach ($builds as $b):
|
||||
$bStatus = (string) ($b['status'] ?? '');
|
||||
$isActive = in_array($bStatus, ['running', 'queued'], true);
|
||||
?>
|
||||
<tr data-build-id="<?= (int) $b['id'] ?>" data-build-status="<?= h($bStatus) ?>">
|
||||
<td class="build-row-controls">
|
||||
<div class="build-transport-actions">
|
||||
<button type="button" class="build-transport-btn build-transport-play" data-build-id="<?= (int) $b['id'] ?>" data-build-status="<?= h($bStatus) ?>" title="Re-run job" aria-label="Re-run build <?= h($b['build_code']) ?>"></button>
|
||||
<button type="button" class="build-transport-btn build-transport-stop" data-build-id="<?= (int) $b['id'] ?>" data-build-status="<?= h($bStatus) ?>" title="Stop job" aria-label="Stop build <?= h($b['build_code']) ?>"<?= $isActive ? '' : ' disabled' ?>></button>
|
||||
</div>
|
||||
</td>
|
||||
<td><a href="<?= h(Auth::basePath()) ?>/?view=build&id=<?= (int) $b['id'] ?>"><?= h($b['build_code']) ?></a></td>
|
||||
<td>
|
||||
<span class="build-status-cell-inner" data-build-status-cell="<?= (int) $b['id'] ?>">
|
||||
<?= build_status_icon((string) ($b['status'] ?? '')) ?>
|
||||
<span class="build-status-label"><?= h($b['status']) ?></span>
|
||||
<?= build_status_icon($bStatus) ?>
|
||||
<span class="build-status-label"><?= h($bStatus) ?></span>
|
||||
</span>
|
||||
</td>
|
||||
<td class="build-phase-cell" data-build-phase-cell="<?= (int) $b['id'] ?>"><?= h($b['phase']) ?></td>
|
||||
|
||||
Reference in New Issue
Block a user