This commit is contained in:
2026-03-06 23:08:38 +01:00
parent d21f476f7e
commit 168a25bf73
4 changed files with 223 additions and 24 deletions

View File

@@ -31,7 +31,7 @@ $commands = $pdo->query('SELECT * FROM ' . $table('commands') . ' ORDER BY label
$renderRuns = function (array $runs): string {
ob_start();
if (!$runs) {
echo '<tr><td colspan="7" class="muted">Noch keine Runs.</td></tr>';
echo '<tr><td colspan="8" class="muted">Noch keine Runs.</td></tr>';
return ob_get_clean();
}
foreach ($runs as $r) {
@@ -49,6 +49,17 @@ $renderRuns = function (array $runs): string {
echo '<td>' . e((string)($r['created_by'] ?? '')) . '</td>';
echo '<td class="muted" style="max-width:240px;"><code>' . e($snippet) . '</code></td>';
echo '<td>' . (!empty($r['timeout_sec']) ? e((string)$r['timeout_sec']) . 's' : 'default') . '</td>';
$status = (string)($r['status'] ?? '');
echo '<td>';
if ($status === 'queued') {
echo '<button class="nav-link" type="button" data-queue-action="cancel" data-run-id="' . e((string)$r['id']) . '">Stoppen</button>';
echo '<button class="nav-link" type="button" data-queue-action="delete" data-run-id="' . e((string)$r['id']) . '">Löschen</button>';
} elseif ($status === 'running') {
echo '<span class="muted">läuft</span>';
} else {
echo '<button class="nav-link" type="button" data-queue-action="delete" data-run-id="' . e((string)$r['id']) . '">Löschen</button>';
}
echo '</td>';
echo '</tr>';
}
return ob_get_clean();
@@ -61,15 +72,75 @@ if (isset($_GET['queue_json'])) {
LEFT JOIN ' . $table('hosts') . ' h ON h.id = r.host_id
ORDER BY r.id DESC LIMIT 20'
)->fetchAll(PDO::FETCH_ASSOC);
$count = (int)$pdo->query(
"SELECT COUNT(*) FROM " . $table('runs') . " WHERE status IN ('queued','running','cancel_requested')"
)->fetchColumn();
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'ok' => true,
'next' => 10,
'count' => $count,
'html' => $renderRuns($runs),
]);
exit;
}
if (isset($_GET['queue_action_json'])) {
$runId = (int)($_POST['run_id'] ?? 0);
$action = (string)($_POST['action'] ?? '');
$error = null;
if ($runId <= 0 || !in_array($action, ['cancel', 'delete'], true)) {
$error = 'Ungültige Anfrage.';
} else {
$stmt = $pdo->prepare('SELECT * FROM ' . $table('runs') . ' WHERE id = :id LIMIT 1');
$stmt->execute(['id' => $runId]);
$run = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$run) {
$error = 'Run nicht gefunden.';
} else {
$status = (string)($run['status'] ?? '');
$driver = (string)$pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
$nowExpr = $driver === 'pgsql' ? 'NOW()' : "DATETIME('now')";
if ($action === 'cancel') {
if ($status !== 'queued') {
$error = 'Nur queued Runs können gestoppt werden.';
} else {
$pdo->exec('UPDATE ' . $table('runs') . ' SET status = \'canceled\', finished_at = ' . $nowExpr . ' WHERE id = ' . (int)$runId);
try {
$redis = module_fn('pi_control', 'redis');
$payload = json_encode(['run_id' => $runId]);
$redis->command(['LREM', $queueName, '0', $payload]);
} catch (\Throwable $e) {
// ignore redis cleanup errors
}
}
} elseif ($action === 'delete') {
if ($status === 'running') {
$error = 'Laufende Runs können nicht gelöscht werden.';
} else {
$pdo->prepare('DELETE FROM ' . $table('runs') . ' WHERE id = :id')->execute(['id' => $runId]);
try {
$redis = module_fn('pi_control', 'redis');
$payload = json_encode(['run_id' => $runId]);
$redis->command(['LREM', $queueName, '0', $payload]);
} catch (\Throwable $e) {
// ignore redis cleanup errors
}
}
}
}
}
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'ok' => $error === null,
'error' => $error,
]);
exit;
}
if (isset($_GET['open_console_json'])) {
$hostId = (int)($_POST['terminal_host_id'] ?? 0);
$presetId = (int)($_POST['terminal_preset_id'] ?? 0);
@@ -309,28 +380,42 @@ $runs = $pdo->query(
<div class="card form-card" style="background:var(--panel-2);">
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap;">
<strong>Queue</strong>
<div class="muted" style="display:flex; align-items:center; gap:10px;">
<span>Update in <span data-queue-countdown>10</span>s</span>
<button class="icon-button" type="button" data-queue-refresh title="Jetzt aktualisieren">↻</button>
</div>
<button class="icon-button queue-button" type="button" data-queue-button>
Queue
<span class="queue-badge" data-queue-count>0</span>
</button>
</div>
<div style="margin-top:.75rem; overflow:auto;">
<table class="table" style="min-width:560px;">
<thead>
<tr>
<th>ID</th>
<th>Status</th>
<th>Host</th>
<th>Command</th>
<th>Von</th>
<th>Output</th>
<th>Timeout</th>
</tr>
</thead>
<tbody data-queue-body>
<?= $renderRuns($runs) ?>
</tbody>
</table>
</div>
<div class="modal" data-queue-modal aria-hidden="true">
<div class="modal-card">
<div class="modal-header">
<strong>Queue</strong>
<div class="modal-actions">
<span class="muted">Update in <span data-queue-countdown>10</span>s</span>
<button class="icon-button" type="button" data-queue-refresh title="Jetzt aktualisieren">↻</button>
<button class="icon-button" type="button" data-queue-close title="Schließen">×</button>
</div>
</div>
<div style="margin-top:.75rem; overflow:auto;">
<table class="table" style="min-width:720px;">
<thead>
<tr>
<th>ID</th>
<th>Status</th>
<th>Host</th>
<th>Command</th>
<th>Von</th>
<th>Output</th>
<th>Timeout</th>
<th>Aktion</th>
</tr>
</thead>
<tbody data-queue-body>
<?= $renderRuns($runs) ?>
</tbody>
</table>
</div>
</div>
</div>
</div>