Simplify provisioning queue and add shared filters

This commit is contained in:
2026-10-06 16:56:41 -04:00
parent 36c2907602
commit ad5609cd23
10 changed files with 310 additions and 94 deletions
+39 -5
View File
@@ -60,9 +60,11 @@ function warningMessages(raw: string): string[] {
function sourceDetails(source: Omit<ProvisioningSource, 'label' | 'href'>): ProvisioningSource {
const id = source.source_id;
if (source.kind === 'campaign') {
const row = db().prepare('SELECT action FROM deployments WHERE id = ?').get(id) as { action: string } | undefined;
const action = row?.action === 'promote' ? 'Promotion' : row?.action === 'rollback' ? 'Rollback' : row?.action === 'drift' ? 'Reconciliation' : 'Campaign';
return { ...source, label: `${action} ${id.slice(0, 8)}`, href: row ? `/campaigns/${encodeURIComponent(id)}` : null };
const row = db().prepare(`SELECT d.action, d.beta_id, s.name FROM deployments d
LEFT JOIN scripts s ON s.id=COALESCE(d.script_id, (SELECT script_id FROM deployment_scripts WHERE deployment_id=d.id ORDER BY script_id LIMIT 1))
WHERE d.id=?`).get(id) as { action: string; beta_id: number | null; name: string | null } | undefined;
const action = row?.action === 'promote' ? 'Promotion' : row?.action === 'rollback' ? 'Rollback' : row?.action === 'drift' ? 'Maintenance repair' : 'Campaign';
return { ...source, label: row ? `${action}${row.beta_id ? ` · Beta #${row.beta_id}` : row.name ? ` · ${row.name}` : action === 'Campaign' ? '' : ' campaign'}` : 'Campaign unavailable', href: row ? `/campaigns/${encodeURIComponent(id)}` : null };
}
if (source.kind === 'beta') {
const row = db().prepare('SELECT notes FROM betas WHERE id = ?').get(id) as { notes: string | null } | undefined;
@@ -251,9 +253,13 @@ function preserveOutcome(id: string): void {
}
const activeDeployment = "d.server_deleted_at IS NULL AND d.state IN ('queued', 'awaiting-health', 'running', 'waiting')";
const reviewDeployment = "d.state IN ('failed', 'interrupted', 'warning')";
const reviewDeployment = `d.state IN ('failed', 'interrupted', 'warning') AND NOT EXISTS (
SELECT 1 FROM retention_controls c WHERE c.category='provisioning' AND c.record_id=d.id
AND c.resolved_at >= COALESCE(d.finished_at,d.started_at))`;
const deploymentListColumns = `d.id, d.server, d.server_deleted_at, d.action, d.state, d.stage,
d.started_at, d.finished_at, d.triggered_by, d.error, d.warnings,
d.phase_key AS phase, d.phase_started_at AS phaseStartedAt,
CASE WHEN json_valid(d.observation_json) THEN json_extract(d.observation_json,'$.blocker.code') END AS waitCode,
(d.server_deleted_at IS NULL AND EXISTS(SELECT 1 FROM servers s WHERE s.name = d.server AND s.destroyed_at IS NULL)) AS server_exists,
(d.server_deleted_at IS NULL AND d.action = 'destroy' AND d.state = 'waiting' AND d.stage != ?) AS canCancelRemoval,
(d.server_deleted_at IS NULL AND d.state IN ('queued','waiting','running','awaiting-health','interrupted')) AS canCancel
@@ -265,6 +271,16 @@ function deploymentListQuery(options: ProvisioningListOptions) {
const parameters: string[] = [];
if (options.action) { conditions.push('d.action = ?'); parameters.push(options.action); }
if (options.status === 'attention') conditions.push(reviewDeployment);
else if (options.status === 'pending') conditions.push("d.state IN ('queued','waiting')");
else if (options.status) { conditions.push('d.state=?'); parameters.push(options.status); }
const linked = (kinds: string[]) => `(EXISTS (SELECT 1 FROM provisioning_sources src WHERE src.operation_id=d.id AND src.kind IN (${kinds.map(kind => `'${kind}'`).join(',')}))
OR ${kinds.map(kind => `d.triggered_by LIKE '${kind}:%'`).join(' OR ')})`;
if (options.source === 'campaign' || options.source === 'beta') conditions.push(linked([options.source]));
else if (options.source === 'maintenance') conditions.push(linked(['maintenance', 'watchdog']));
else if (options.source === 'manual' || options.source === 'system') {
conditions.push(`NOT ${linked(['campaign', 'beta', 'maintenance', 'watchdog'])}`);
conditions.push(options.source === 'manual' ? "d.triggered_by='ui'" : "d.triggered_by!='ui'");
}
const search = (options.server ?? '').slice(0, 100).toLowerCase();
if (search) {
// Read only distinct names to preserve friendly map/lobby searches, including deleted servers.
@@ -278,9 +294,27 @@ function deploymentListQuery(options: ProvisioningListOptions) {
}
function deploymentListRows(query: ReturnType<typeof deploymentListQuery>, active: boolean): ProvisioningListRow[] {
return db().prepare(`SELECT ${deploymentListColumns} FROM server_deployments d WHERE ${query.where}
const rows = db().prepare(`SELECT ${deploymentListColumns} FROM server_deployments d WHERE ${query.where}
ORDER BY d.started_at DESC, d.rowid DESC ${active ? '' : 'LIMIT ? OFFSET ?'}`)
.all(REMOVING_SERVER, ...query.parameters, ...(active ? [] : [query.limit, query.offset])) as ProvisioningListRow[];
if (!rows.length) return rows;
const links = db().prepare(`SELECT operation_id,kind,source_id FROM provisioning_sources
WHERE operation_id IN (SELECT value FROM json_each(?)) ORDER BY kind,source_id`).all(JSON.stringify(rows.map(row => row.id))) as
(Omit<ProvisioningSource, 'label' | 'href'> & { operation_id: string })[];
const byOperation = new Map<string, Omit<ProvisioningSource, 'label' | 'href'>[]>();
for (const { operation_id, ...link } of links) byOperation.set(operation_id, [...(byOperation.get(operation_id) ?? []), link]);
const cache = new Map<string, ProvisioningSource>();
for (const row of rows) {
const sources = byOperation.get(row.id) ?? [];
const fallback = row.triggered_by.match(/^(campaign|beta|maintenance|watchdog):(.+)$/);
if (fallback && !sources.some(source => source.kind === fallback[1] && source.source_id === fallback[2])) sources.unshift({ kind: fallback[1] as ProvisioningSource['kind'], source_id: fallback[2] });
row.sources = sources.map(source => {
const key = `${source.kind}:${source.source_id}`;
if (!cache.has(key)) cache.set(key, sourceDetails(source));
return cache.get(key)!;
});
}
return rows;
}
/** All list reads omit observation snapshots; retained history is always bounded. */