feat(fleet): show instance resource metrics

This commit is contained in:
chick
2026-07-19 00:22:23 +08:00
parent 2411801240
commit 23f18963ef
12 changed files with 312 additions and 30 deletions
+54 -8
View File
@@ -1,13 +1,8 @@
import type { FleetDataSource, FleetSnapshot } from './fleet-page.js';
import type { FleetInstance } from './fleet-table-view-model.js';
import type { FleetInstance, FleetStatus } from './fleet-table-view-model.js';
interface InstancePage {
readonly items?: readonly unknown[];
readonly page?: {
readonly page?: unknown;
readonly pageSize?: unknown;
readonly total?: unknown;
};
}
function parseInstance(value: unknown): FleetInstance {
@@ -25,18 +20,69 @@ function parseInstance(value: unknown): FleetInstance {
return { id: item.id, name: item.name, url: item.origin, tags: item.tags };
}
type Resources = NonNullable<NonNullable<FleetStatus['summary']>['resources']>;
function parseResources(value: unknown): Resources {
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
const item = value as Record<string, unknown>;
const number = (field: string): number | undefined => {
const fieldValue = item[field];
return typeof fieldValue === 'number' && Number.isFinite(fieldValue) ? fieldValue : undefined;
};
const cpuPercent = number('cpuPercent');
const memoryPercent = number('memoryPercent');
const maxTemperatureCelsius = number('maxTemperatureCelsius');
const phoneNumbers = Array.isArray(item.phoneNumbers)
? item.phoneNumbers.filter((phone): phone is string => typeof phone === 'string')
: undefined;
return {
...(cpuPercent === undefined ? {} : { cpuPercent }),
...(memoryPercent === undefined ? {} : { memoryPercent }),
...(maxTemperatureCelsius === undefined ? {} : { maxTemperatureCelsius }),
...(phoneNumbers?.length ? { phoneNumbers } : {}),
};
}
export function createFleetApiDataSource(fetcher: typeof fetch = fetch): FleetDataSource {
return {
async load(): Promise<FleetSnapshot> {
async load(signal): Promise<FleetSnapshot> {
const response = await fetcher('/api/v1/instances', {
method: 'GET',
credentials: 'same-origin',
headers: { accept: 'application/json' },
...(signal ? { signal } : {}),
});
if (!response.ok) throw new Error(`Fleet request failed (${response.status}).`);
const body = (await response.json()) as InstancePage;
if (!Array.isArray(body.items)) throw new Error('Fleet response is invalid.');
return { instances: body.items.map(parseInstance), statuses: new Map() };
const instances = body.items.map(parseInstance);
const entries = await Promise.all(
instances.map(async (instance): Promise<readonly [string, FleetStatus]> => {
try {
const resourceResponse = await fetcher(
`/api/v1/instances/${encodeURIComponent(instance.id)}/resources`,
{
method: 'GET',
credentials: 'same-origin',
headers: { accept: 'application/json' },
...(signal ? { signal } : {}),
},
);
if (!resourceResponse.ok)
return [instance.id, { reachable: false, summary: { resources: {} } }];
return [
instance.id,
{
reachable: true,
authenticated: true,
summary: { resources: parseResources(await resourceResponse.json()) },
},
];
} catch {
return [instance.id, { reachable: false, summary: { resources: {} } }];
}
}),
);
return { instances, statuses: new Map(entries) };
},
};
}