92 lines
3.2 KiB
TypeScript
92 lines
3.2 KiB
TypeScript
import type { ScheduledOperationType, ScheduleTargetSelector } from '@multi-simadmin/contracts';
|
|
|
|
import type { SqliteDatabase } from '../../infrastructure/database/database.js';
|
|
|
|
export interface ResolvedTarget {
|
|
readonly id: string;
|
|
readonly revision: number;
|
|
}
|
|
|
|
interface TargetRow {
|
|
id: string;
|
|
config_revision: number;
|
|
}
|
|
|
|
export interface OperationTargetResolution {
|
|
readonly targets: readonly ResolvedTarget[];
|
|
readonly targetSnapshot: readonly string[];
|
|
readonly unavailableInstanceIds: readonly string[];
|
|
}
|
|
|
|
const operationIds: Record<ScheduledOperationType, string> = {
|
|
'restart-service': 'postServiceRestart',
|
|
'reboot-system': 'postSystemReboot',
|
|
'send-sms': 'postSmsSend',
|
|
};
|
|
|
|
export function resolveTargets(
|
|
db: SqliteDatabase,
|
|
selector: ScheduleTargetSelector,
|
|
): readonly ResolvedTarget[] {
|
|
if (selector.mode === 'fixed') {
|
|
const placeholders = selector.instanceIds.map(() => '?').join(',');
|
|
const rows = db
|
|
.prepare(
|
|
`SELECT id, config_revision FROM instances
|
|
WHERE enabled = 1 AND id IN (${placeholders})`,
|
|
)
|
|
.all(...selector.instanceIds) as TargetRow[];
|
|
const byId = new Map(rows.map((row) => [row.id, row]));
|
|
return selector.instanceIds.flatMap((id) => {
|
|
const row = byId.get(id);
|
|
return row ? [{ id: row.id, revision: row.config_revision }] : [];
|
|
});
|
|
}
|
|
|
|
const placeholders = selector.tags.map(() => '?').join(',');
|
|
const comparison = selector.match === 'all' ? '= ?' : '> 0';
|
|
const parameters: Array<string | number> = [...selector.tags];
|
|
if (selector.match === 'all') parameters.push(selector.tags.length);
|
|
const rows = db
|
|
.prepare(
|
|
`SELECT i.id, i.config_revision
|
|
FROM instances i
|
|
JOIN instance_tags t ON t.instance_id = i.id
|
|
WHERE i.enabled = 1 AND t.tag IN (${placeholders})
|
|
GROUP BY i.id, i.config_revision
|
|
HAVING COUNT(DISTINCT t.tag) ${comparison}
|
|
ORDER BY i.id ASC`,
|
|
)
|
|
.all(...parameters) as TargetRow[];
|
|
return rows.map((row) => ({ id: row.id, revision: row.config_revision }));
|
|
}
|
|
|
|
export function resolveOperationTargets(
|
|
db: SqliteDatabase,
|
|
selector: ScheduleTargetSelector,
|
|
operationType: ScheduledOperationType,
|
|
): OperationTargetResolution {
|
|
const candidates = resolveTargets(db, selector);
|
|
if (candidates.length === 0)
|
|
return { targets: [], targetSnapshot: [], unavailableInstanceIds: [] };
|
|
const placeholders = candidates.map(() => '?').join(',');
|
|
const rows = db
|
|
.prepare(
|
|
`SELECT instance_id, state FROM capabilities
|
|
WHERE operation_id = ? AND instance_id IN (${placeholders})`,
|
|
)
|
|
.all(operationIds[operationType], ...candidates.map((target) => target.id)) as Array<{
|
|
instance_id: string;
|
|
state: string;
|
|
}>;
|
|
const states = new Map(rows.map((row) => [row.instance_id, row.state]));
|
|
const availableStates = new Set(['supported', 'auth-required', 'degraded']);
|
|
return {
|
|
targets: candidates.filter((target) => availableStates.has(states.get(target.id) ?? 'unknown')),
|
|
targetSnapshot: candidates.map((target) => target.id),
|
|
unavailableInstanceIds: candidates
|
|
.filter((target) => !availableStates.has(states.get(target.id) ?? 'unknown'))
|
|
.map((target) => target.id),
|
|
};
|
|
}
|