Add Prettier and ESLint tooling (Refs #77)
This commit is contained in:
+96
-27
@@ -15,29 +15,78 @@ export function scalingReason(reason: string): string {
|
||||
}
|
||||
|
||||
export function policyStatus(view: ScalingView, globallyPaused: boolean) {
|
||||
if (view.policy.pausedReason) return { label: 'Needs attention', reason: scalingReason(view.policy.pausedReason), tone: 'warning' as const };
|
||||
if (globallyPaused || view.policy.mode === 'off') return {
|
||||
label: globallyPaused ? 'Globally paused' : 'Paused',
|
||||
reason: view.unsettled.length ? 'No new scaling requests. Outstanding jobs are still tracked.' : 'No new scaling requests.',
|
||||
tone: 'neutral' as const
|
||||
if (view.policy.pausedReason)
|
||||
return {
|
||||
label: 'Needs attention',
|
||||
reason: scalingReason(view.policy.pausedReason),
|
||||
tone: 'warning' as const
|
||||
};
|
||||
if (globallyPaused || view.policy.mode === 'off')
|
||||
return {
|
||||
label: globallyPaused ? 'Globally paused' : 'Paused',
|
||||
reason: view.unsettled.length
|
||||
? 'No new scaling requests. Outstanding jobs are still tracked.'
|
||||
: 'No new scaling requests.',
|
||||
tone: 'neutral' as const
|
||||
};
|
||||
if (view.decision.action === 'create')
|
||||
return {
|
||||
label: 'Adding capacity',
|
||||
reason: scalingReason(view.decision.reason),
|
||||
tone: 'info' as const
|
||||
};
|
||||
if (view.decision.action === 'destroy')
|
||||
return {
|
||||
label: 'Removing surplus',
|
||||
reason: scalingReason(view.decision.reason),
|
||||
tone: 'info' as const
|
||||
};
|
||||
if (
|
||||
view.decision.reason.endsWith('Empty spare target is satisfied.') &&
|
||||
view.decision.deficit === 0
|
||||
)
|
||||
return { label: 'Spare targets satisfied', reason: '', tone: 'online' as const };
|
||||
if (view.decision.reason.endsWith('Waiting for a continuous empty interval.'))
|
||||
return {
|
||||
label: 'Waiting to remove surplus',
|
||||
reason: `Lobbies must stay empty for ${view.policy.emptyMinutes} minutes.`,
|
||||
tone: 'neutral' as const
|
||||
};
|
||||
return {
|
||||
label: 'Scaling on hold',
|
||||
reason: scalingReason(view.decision.reason),
|
||||
tone: 'warning' as const
|
||||
};
|
||||
if (view.decision.action === 'create') return { label: 'Adding capacity', reason: scalingReason(view.decision.reason), tone: 'info' as const };
|
||||
if (view.decision.action === 'destroy') return { label: 'Removing surplus', reason: scalingReason(view.decision.reason), tone: 'info' as const };
|
||||
if (view.decision.reason.endsWith('Empty spare target is satisfied.') && view.decision.deficit === 0) return { label: 'Spare targets satisfied', reason: '', tone: 'online' as const };
|
||||
if (view.decision.reason.endsWith('Waiting for a continuous empty interval.')) return { label: 'Waiting to remove surplus', reason: `Lobbies must stay empty for ${view.policy.emptyMinutes} minutes.`, tone: 'neutral' as const };
|
||||
return { label: 'Scaling on hold', reason: scalingReason(view.decision.reason), tone: 'warning' as const };
|
||||
}
|
||||
|
||||
export function lobbyGroups(view: ScalingView) {
|
||||
const maps = view.policy.mapSlug === '*' ? zombieMaps(view.policy.game) : zombieMaps(view.policy.game).filter(map => map.slug === view.policy.mapSlug);
|
||||
const maps =
|
||||
view.policy.mapSlug === '*'
|
||||
? zombieMaps(view.policy.game)
|
||||
: zombieMaps(view.policy.game).filter((map) => map.slug === view.policy.mapSlug);
|
||||
const assigned = new Set<ScalingMember>();
|
||||
const groups = maps.map(map => {
|
||||
const members = view.snapshot.members.filter(member => (member.mapSlug ?? parseServerName(member.name)?.mapSlug) === map.slug);
|
||||
members.forEach(member => assigned.add(member));
|
||||
return { slug: map.slug, label: map.friendly, members, ready: members.filter(member => member.state === 'empty').length, target: view.policy.spares };
|
||||
const groups = maps.map((map) => {
|
||||
const members = view.snapshot.members.filter(
|
||||
(member) => (member.mapSlug ?? parseServerName(member.name)?.mapSlug) === map.slug
|
||||
);
|
||||
members.forEach((member) => assigned.add(member));
|
||||
return {
|
||||
slug: map.slug,
|
||||
label: map.friendly,
|
||||
members,
|
||||
ready: members.filter((member) => member.state === 'empty').length,
|
||||
target: view.policy.spares
|
||||
};
|
||||
});
|
||||
const unassigned = view.snapshot.members.filter(member => !assigned.has(member));
|
||||
if (unassigned.length) groups.push({ slug: 'unassigned', label: 'Other lobbies', members: unassigned, ready: unassigned.filter(member => member.state === 'empty').length, target: 0 });
|
||||
const unassigned = view.snapshot.members.filter((member) => !assigned.has(member));
|
||||
if (unassigned.length)
|
||||
groups.push({
|
||||
slug: 'unassigned',
|
||||
label: 'Other lobbies',
|
||||
members: unassigned,
|
||||
ready: unassigned.filter((member) => member.state === 'empty').length,
|
||||
target: 0
|
||||
});
|
||||
return groups;
|
||||
}
|
||||
|
||||
@@ -54,12 +103,18 @@ export interface ScalingActivity {
|
||||
}
|
||||
|
||||
export function scalingActivity(view: ScalingView): ScalingActivity[] {
|
||||
const operationIds = new Set([...view.history.map(item => item.operationId), ...view.unsettled.map(item => item.id)].filter((id): id is string => !!id));
|
||||
const operationIds = new Set(
|
||||
[
|
||||
...view.history.map((item) => item.operationId),
|
||||
...view.unsettled.map((item) => item.id)
|
||||
].filter((id): id is string => !!id)
|
||||
);
|
||||
const groups = new Map<string, ScalingActivity>();
|
||||
for (const item of view.history) {
|
||||
// Older wait decisions store the job reference in their reason rather than operationId.
|
||||
const reference = item.decision.reason.match(/Provisioning ([\w-]+):/)?.[1];
|
||||
const operationId = item.operationId ?? (reference && operationIds.has(reference) ? reference : null);
|
||||
const operationId =
|
||||
item.operationId ?? (reference && operationIds.has(reference) ? reference : null);
|
||||
const key = operationId ?? item.id;
|
||||
const existing = groups.get(key);
|
||||
if (existing) {
|
||||
@@ -72,11 +127,18 @@ export function scalingActivity(view: ScalingView): ScalingActivity[] {
|
||||
existing.operationState = item.operationState;
|
||||
existing.reason = scalingReason(item.decision.reason);
|
||||
}
|
||||
} else groups.set(key, {
|
||||
id: key, at: item.at, lastSeenAt: item.lastSeenAt, operationId,
|
||||
operationState: item.operationState, action: item.decision.action,
|
||||
server: item.decision.server, reason: scalingReason(item.decision.reason), evaluations: 1
|
||||
});
|
||||
} else
|
||||
groups.set(key, {
|
||||
id: key,
|
||||
at: item.at,
|
||||
lastSeenAt: item.lastSeenAt,
|
||||
operationId,
|
||||
operationState: item.operationState,
|
||||
action: item.decision.action,
|
||||
server: item.decision.server,
|
||||
reason: scalingReason(item.decision.reason),
|
||||
evaluations: 1
|
||||
});
|
||||
}
|
||||
return [...groups.values()].sort((a, b) => b.lastSeenAt - a.lastSeenAt);
|
||||
}
|
||||
@@ -84,10 +146,17 @@ export function scalingActivity(view: ScalingView): ScalingActivity[] {
|
||||
/** Advance observation ages even when polling fails, using the fleet freshness window. */
|
||||
export function observationAge(at: number, now: number): { label: string; stale: boolean } {
|
||||
const age = now - at;
|
||||
if (!Number.isFinite(at) || !Number.isFinite(age) || age < -1000) return { label: 'Check observation time', stale: true };
|
||||
if (!Number.isFinite(at) || !Number.isFinite(age) || age < -1000)
|
||||
return { label: 'Check observation time', stale: true };
|
||||
const seconds = Math.max(0, Math.floor(age / 1000));
|
||||
const elapsed = seconds < 60 ? `${seconds}s` : seconds < 3600 ? `${Math.floor(seconds / 60)}m`
|
||||
: seconds < 86400 ? `${Math.floor(seconds / 3600)}h` : `${Math.floor(seconds / 86400)}d`;
|
||||
const elapsed =
|
||||
seconds < 60
|
||||
? `${seconds}s`
|
||||
: seconds < 3600
|
||||
? `${Math.floor(seconds / 60)}m`
|
||||
: seconds < 86400
|
||||
? `${Math.floor(seconds / 3600)}h`
|
||||
: `${Math.floor(seconds / 86400)}d`;
|
||||
const stale = !freshStatusTimestamp(new Date(at).toISOString(), now);
|
||||
return { label: `${stale ? 'Stale · ' : ''}${elapsed} ago`, stale };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user