Move host operation history to a paginated Hosts page
This commit is contained in:
@@ -100,6 +100,12 @@ export interface HostOperation {
|
||||
payload: Record<string, unknown>; state: HostOperationState; result: unknown; error: string | null;
|
||||
createdAt: string; updatedAt: string;
|
||||
}
|
||||
export interface HostOperationSummary extends Omit<HostOperation, 'payload' | 'result'> {
|
||||
hostName: string | null;
|
||||
}
|
||||
export interface HostOperationHistoryPage {
|
||||
operations: HostOperationSummary[]; page: number; pageSize: 25; total: number; totalPages: number;
|
||||
}
|
||||
|
||||
export interface HostPreparationItem {
|
||||
kind: 'runtime' | 'library'; label: string;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { db } from '../db/index.js';
|
||||
import { ensureHostSchema } from './schema.js';
|
||||
import { recordInstanceResources, validateInstanceResources } from './calibration.js';
|
||||
import { refreshHostAvailability, scheduleAvailabilityRefresh } from './availability.js';
|
||||
import type { Host, HostDecommission, HostDeletion, HostInventory, HostMode, HostOperation, HostOperationState } from '../../hosts.js';
|
||||
import type { Host, HostDecommission, HostDeletion, HostInventory, HostMode, HostOperation, HostOperationHistoryPage, HostOperationState } from '../../hosts.js';
|
||||
|
||||
export const AGENT_PROTOCOL_VERSION = 1;
|
||||
export const HEARTBEAT_MAX_AGE_MS = 90_000;
|
||||
@@ -356,6 +356,22 @@ export function listHostOperations(hostId: string, limit = 100, summary = false)
|
||||
return (database().prepare(`SELECT ${columns} FROM ec_host_operations o WHERE o.host_id=? ORDER BY o.created_at DESC,o.rowid DESC LIMIT ?`)
|
||||
.all(hostId, Math.min(500, Math.max(1, limit))) as OperationRow[]).map(operationFromRow);
|
||||
}
|
||||
/** Browse retained history without loading command payloads or credential-bearing results. */
|
||||
export function listHostOperationHistory(page = 1): HostOperationHistoryPage {
|
||||
const handle = database();
|
||||
const pageSize = 25;
|
||||
return handle.transaction((): HostOperationHistoryPage => {
|
||||
const { total } = handle.prepare('SELECT COUNT(*) AS total FROM ec_host_operations').get() as { total: number };
|
||||
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
||||
const currentPage = Math.min(totalPages, Number.isSafeInteger(page) && page > 0 ? page : 1);
|
||||
const operations = handle.prepare(`SELECT o.id,o.host_id AS hostId,h.name AS hostName,o.kind,
|
||||
o.instance_id AS instanceId,o.generation,o.state,o.error,o.created_at AS createdAt,o.updated_at AS updatedAt
|
||||
FROM ec_host_operations o LEFT JOIN ec_hosts h ON h.id=o.host_id AND h.deleted_at IS NULL
|
||||
ORDER BY o.created_at DESC,o.rowid DESC LIMIT ? OFFSET ?`)
|
||||
.all(pageSize, (currentPage - 1) * pageSize) as HostOperationHistoryPage['operations'];
|
||||
return { operations, page: currentPage, pageSize, total, totalPages };
|
||||
})();
|
||||
}
|
||||
/** Heartbeats can tell an executing create to stop at its next safe command boundary. */
|
||||
export function pendingCreationCancellations(hostId: string): string[] {
|
||||
return (database().prepare(`SELECT json_extract(payload,'$.createOperationId') AS id FROM ec_host_operations
|
||||
|
||||
@@ -31,6 +31,8 @@ export function ensureHostSchema(handle: Database.Database): void {
|
||||
UNIQUE(host_id, idempotency_key)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ec_host_operations_queue ON ec_host_operations(host_id, state, created_at);
|
||||
-- Reversing this index also reverses its implicit rowid tie-breaker.
|
||||
CREATE INDEX IF NOT EXISTS ec_host_operations_history ON ec_host_operations(created_at);
|
||||
-- Polling checks cancellations and preparation by kind across retained
|
||||
-- history. The queue index cannot narrow those lookups beyond host_id,
|
||||
-- causing a history scan for every queued command on Node's main thread.
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<p>This deletes the host's game-library copies, server files, agent data, credentials, and service installation. Electric Cherry's uploaded libraries on the controller and its operation history are kept.</p>
|
||||
<p>The host remains visible until cleanup is confirmed. Offline hosts wait for reconnection. Unsent commands will be cancelled.</p>
|
||||
{#if checking}<p role="status">Checking servers and unfinished operations…</p>
|
||||
{:else if deletion && !deletion.allowed}<div role="status" class="blocked"><strong>This host still has work to finish:</strong><ul>{#each deletion.blockers as blocker}<li>{blocker}</li>{/each}</ul><p>Open <a href={`/hosts/${host?.id}`} onclick={() => open = false}>host details</a> to review its operations or drain its servers.</p></div>
|
||||
{:else if deletion && !deletion.allowed}<div role="status" class="blocked"><strong>This host still has work to finish:</strong><ul>{#each deletion.blockers as blocker}<li>{blocker}</li>{/each}</ul><p>Review <a href="/hosts/operations" onclick={() => open = false}>operation history</a> or open <a href={`/hosts/${host?.id}`} onclick={() => open = false}>host details</a> to drain its servers.</p></div>
|
||||
{:else if deletion?.allowed}<p>Cleanup cannot be undone once it starts. Adding this machine again requires installing and enrolling the agent again.</p>{/if}
|
||||
{#if error}<p role="alert" class="ui-error">{error}</p>{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user