fix: restore frontend source code (src/web/) that was accidentally deleted

Frontend source is needed for development/building the SPA.
Only Node.js runtime server code should be removed.
This commit is contained in:
boos
2026-05-18 09:33:56 +08:00
parent 5d29f03d50
commit 0eb7c32585
274 changed files with 80446 additions and 0 deletions
@@ -0,0 +1,98 @@
export type MissingTokenModelAccount = {
accountId: number;
username: string | null;
siteId: number;
siteName: string;
missingGroups?: string[];
requiredGroups?: string[];
availableGroups?: string[];
groupCoverageUncertain?: boolean;
};
export type MissingTokenModelsByName = Record<string, MissingTokenModelAccount[]>;
export type RouteMissingTokenHint = {
modelName: string;
accounts: MissingTokenModelAccount[];
};
type RoutePatternLike = {
id: number;
modelPattern: string;
};
export function normalizeMissingTokenModels(
withoutTokenByModel: MissingTokenModelsByName,
): MissingTokenModelsByName {
const normalized: MissingTokenModelsByName = {};
for (const modelName of Object.keys(withoutTokenByModel || {})) {
const normalizedModelName = String(modelName || '').trim();
if (!normalizedModelName) continue;
const accountMap = new Map<number, MissingTokenModelAccount>();
for (const account of withoutTokenByModel[modelName] || []) {
if (!account || !Number.isFinite(account.accountId)) continue;
const accountName = (account.username || '').trim();
const siteName = String(account.siteName || '').trim();
const normalizeLabels = (labels: unknown): string[] => Array.isArray(labels)
? Array.from(new Set(labels
.map((label) => String(label || '').trim())
.filter(Boolean)))
.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }))
: [];
accountMap.set(account.accountId, {
accountId: account.accountId,
username: accountName || null,
siteId: account.siteId,
siteName,
...(normalizeLabels(account.missingGroups).length > 0 ? { missingGroups: normalizeLabels(account.missingGroups) } : {}),
...(normalizeLabels(account.requiredGroups).length > 0 ? { requiredGroups: normalizeLabels(account.requiredGroups) } : {}),
...(normalizeLabels(account.availableGroups).length > 0 ? { availableGroups: normalizeLabels(account.availableGroups) } : {}),
...(account.groupCoverageUncertain === true ? { groupCoverageUncertain: true } : {}),
});
}
if (accountMap.size > 0) {
normalized[normalizedModelName] = Array.from(accountMap.values()).sort((a, b) => a.accountId - b.accountId);
}
}
return normalized;
}
export function buildRouteMissingTokenIndex(
routes: RoutePatternLike[],
missingByModel: MissingTokenModelsByName,
matchesModelPattern: (model: string, pattern: string) => boolean,
): Record<number, RouteMissingTokenHint[]> {
const index: Record<number, RouteMissingTokenHint[]> = {};
for (const route of routes || []) {
const modelPattern = (route.modelPattern || '').trim();
if (!modelPattern) {
index[route.id] = [];
continue;
}
const matchedHints: RouteMissingTokenHint[] = [];
for (const [modelName, accounts] of Object.entries(missingByModel || {})) {
if (!matchesModelPattern(modelName, modelPattern)) continue;
const dedupedAccounts = new Map<number, MissingTokenModelAccount>();
for (const account of accounts || []) {
if (!account || !Number.isFinite(account.accountId)) continue;
dedupedAccounts.set(account.accountId, account);
}
matchedHints.push({
modelName,
accounts: Array.from(dedupedAccounts.values()).sort((a, b) => {
const siteCompare = String(a.siteName || '').localeCompare(String(b.siteName || ''), undefined, { sensitivity: 'base' });
if (siteCompare !== 0) return siteCompare;
return a.accountId - b.accountId;
}),
});
}
matchedHints.sort((a, b) => a.modelName.localeCompare(b.modelName, undefined, { sensitivity: 'base' }));
index[route.id] = matchedHints;
}
return index;
}