Bound provisioning history and poll only active operations

This commit is contained in:
2026-10-05 14:52:30 -04:00
parent 16a5bc3534
commit 041c024f95
11 changed files with 279 additions and 44 deletions
+31
View File
@@ -0,0 +1,31 @@
import type { ProvisioningAction } from './provisioning.js';
import type { ProvisioningDeployment } from './provisioning-detail.js';
export type ProvisioningListRow = Pick<ProvisioningDeployment,
'id' | 'server' | 'server_deleted_at' | 'action' | 'state' | 'stage' | 'started_at' |
'finished_at' | 'triggered_by' | 'error' | 'warnings' | 'server_exists'> & { canCancelRemoval: number };
export interface ProvisioningListOptions {
view?: 'active' | 'history' | 'all';
action?: ProvisioningAction;
status?: 'attention';
server?: string;
limit?: number;
offset?: number;
}
export interface ProvisioningListPage {
deployments: ProvisioningListRow[];
total: number;
needsReview: number;
limit: number;
offset: number;
}
export function provisioningPageSize(value: number): number {
return Number.isFinite(value) ? Math.min(100, Math.max(1, Math.floor(value) || 25)) : 25;
}
export function provisioningPageOffset(value: number): number {
return Number.isFinite(value) ? Math.min(1_000_000, Math.max(0, Math.floor(value))) : 0;
}
+3 -1
View File
@@ -378,7 +378,9 @@ export function db(): Database.Database {
handle.exec(`CREATE INDEX IF NOT EXISTS idx_server_deployments_server
ON server_deployments(server, server_deleted_at);
CREATE INDEX IF NOT EXISTS idx_server_deployments_state
ON server_deployments(state, action, server_deleted_at);`);
ON server_deployments(state, action, server_deleted_at);
CREATE INDEX IF NOT EXISTS idx_server_deployments_started_at
ON server_deployments(started_at);`);
installReportRevisions(handle);
} catch (err) {
handle.close();
+49 -3
View File
@@ -10,6 +10,8 @@ import { bindNativeOperation } from './native.js';
import { workerActivity } from '../pipeline/activity.js';
import { canCancelCreation } from './cancel-creation.js';
import { nativeDeployment } from '../backends/native.js';
import { serverTitle } from '../../ui/server-labels.js';
import { provisioningPageSize, provisioningPageOffset, type ProvisioningListOptions, type ProvisioningListPage, type ProvisioningListRow } from '../../provisioning-list.js';
export function beginDeployment(server: string, action: ProvisioningAction, triggeredBy = 'ui'): string {
const id = randomUUID();
@@ -250,10 +252,54 @@ function preserveOutcome(id: string): void {
details: { state: row.state, finishedAt: row.finished_at, error: row.error, warnings: warningMessages(row.warnings) } } });
}
export function listDeployments() {
return db().prepare(`SELECT d.*, (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,
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 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.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
FROM server_deployments d ORDER BY started_at DESC, rowid DESC`).all(REMOVING_SERVER);
`;
function deploymentListQuery(options: ProvisioningListOptions) {
const conditions = [options.view === 'active' ? `(${activeDeployment})`
: options.view === 'history' ? `NOT (${activeDeployment})` : '1'];
const parameters: string[] = [];
if (options.action) { conditions.push('d.action = ?'); parameters.push(options.action); }
if (options.status === 'attention') conditions.push(reviewDeployment);
const search = (options.server ?? '').slice(0, 100).toLowerCase();
if (search) {
// Read only distinct names to preserve friendly map/lobby searches, including deleted servers.
const names = (db().prepare('SELECT DISTINCT server FROM server_deployments').all() as { server: string }[])
.map(row => row.server).filter(name => `${name} ${serverTitle(name)}`.toLowerCase().includes(search));
conditions.push('d.server IN (SELECT value FROM json_each(?))');
parameters.push(JSON.stringify(names));
}
return { where: conditions.join(' AND '), parameters,
limit: provisioningPageSize(options.limit ?? 25), offset: provisioningPageOffset(options.offset ?? 0) };
}
function deploymentListRows(query: ReturnType<typeof deploymentListQuery>, active: boolean): ProvisioningListRow[] {
return 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[];
}
/** All list reads omit observation snapshots; retained history is always bounded. */
export function listDeployments(options: ProvisioningListOptions = {}): ProvisioningListRow[] {
return deploymentListRows(deploymentListQuery(options), options.view === 'active');
}
export function deploymentListPage(options: ProvisioningListOptions = {}): ProvisioningListPage {
return db().transaction(() => {
const query = deploymentListQuery(options);
const deployments = deploymentListRows(query, options.view === 'active');
const total = options.view === 'active' ? deployments.length : (db().prepare(
`SELECT COUNT(*) AS total FROM server_deployments d WHERE ${query.where}`
).get(...query.parameters) as { total: number }).total;
const { needsReview } = db().prepare(`SELECT COUNT(*) AS needsReview FROM server_deployments d WHERE ${reviewDeployment}`)
.get() as { needsReview: number };
return { deployments, total, needsReview, limit: query.limit, offset: query.offset };
})();
}
export function interruptDeployments(): void {
+1 -1
View File
@@ -122,7 +122,7 @@ export function provisioningReturn(value: string | null | undefined): { href: st
if (url.origin !== 'https://electric-cherry.invalid') return fallback;
if (url.pathname === '/provisioning') {
const params = new URLSearchParams();
for (const key of ['view', 'action', 'status', 'server', 'limit']) {
for (const key of ['view', 'action', 'status', 'server', 'limit', 'offset']) {
const item = url.searchParams.get(key);
if (item) params.set(key, item);
}