mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
- Deleted chartConfig.ts and its exports from index.ts, as they are no longer needed. - Removed latency.ts and its related functions for latency statistics and formatting. - Cleaned up usageIndex.ts by removing functions related to indexing usage details, as they are not in use.
172 lines
4.8 KiB
TypeScript
172 lines
4.8 KiB
TypeScript
export type StatusBlockState = 'success' | 'failure' | 'mixed' | 'idle';
|
|
|
|
export interface StatusBlockDetail {
|
|
success: number;
|
|
failure: number;
|
|
rate: number;
|
|
startTime: number;
|
|
endTime: number;
|
|
}
|
|
|
|
export interface StatusBarData {
|
|
blocks: StatusBlockState[];
|
|
blockDetails: StatusBlockDetail[];
|
|
successRate: number;
|
|
totalSuccess: number;
|
|
totalFailure: number;
|
|
}
|
|
|
|
export interface RecentRequestBucket {
|
|
time?: string;
|
|
success: number;
|
|
failed: number;
|
|
}
|
|
|
|
export type ApiKeyUsageResponse = Record<string, Record<string, RecentRequestBucket[]>>;
|
|
|
|
const RECENT_REQUEST_BLOCK_COUNT = 20;
|
|
const RECENT_REQUEST_BLOCK_DURATION_MS = 10 * 60 * 1000;
|
|
|
|
const toFiniteNumber = (value: unknown): number => {
|
|
const numberValue = typeof value === 'number' ? value : Number(value);
|
|
return Number.isFinite(numberValue) ? numberValue : 0;
|
|
};
|
|
|
|
export function buildRecentRequestCompositeKey(baseUrl: unknown, apiKey: unknown): string {
|
|
const normalizedBaseUrl = String(baseUrl ?? '').trim();
|
|
const normalizedApiKey = String(apiKey ?? '').trim();
|
|
return `${normalizedBaseUrl}|${normalizedApiKey}`;
|
|
}
|
|
|
|
export function normalizeRecentRequestAuthIndex(value: unknown): string | null {
|
|
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
return value.toString();
|
|
}
|
|
if (typeof value === 'string') {
|
|
const trimmed = value.trim();
|
|
return trimmed ? trimmed : null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function normalizeRecentRequestBuckets(input: unknown): RecentRequestBucket[] {
|
|
if (!Array.isArray(input)) {
|
|
return [];
|
|
}
|
|
|
|
return input.slice(-RECENT_REQUEST_BLOCK_COUNT).map((item) => {
|
|
const record = item && typeof item === 'object' ? (item as Record<string, unknown>) : {};
|
|
const time = typeof record.time === 'string' ? record.time : undefined;
|
|
|
|
return {
|
|
...(time ? { time } : {}),
|
|
success: toFiniteNumber(record.success),
|
|
failed: toFiniteNumber(record.failed),
|
|
};
|
|
});
|
|
}
|
|
|
|
export function mergeRecentRequestBucketGroups(
|
|
groups: RecentRequestBucket[][]
|
|
): RecentRequestBucket[] {
|
|
const normalizedGroups = groups
|
|
.map((group) => normalizeRecentRequestBuckets(group))
|
|
.filter((group) => group.length > 0);
|
|
|
|
if (normalizedGroups.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
const mergedLength = Math.min(
|
|
RECENT_REQUEST_BLOCK_COUNT,
|
|
Math.max(...normalizedGroups.map((group) => group.length))
|
|
);
|
|
const merged: RecentRequestBucket[] = Array.from({ length: mergedLength }, () => ({
|
|
success: 0,
|
|
failed: 0,
|
|
}));
|
|
|
|
normalizedGroups.forEach((group) => {
|
|
const tail = group.slice(-mergedLength);
|
|
const offset = mergedLength - tail.length;
|
|
|
|
tail.forEach((bucket, index) => {
|
|
const target = merged[offset + index];
|
|
target.success += bucket.success;
|
|
target.failed += bucket.failed;
|
|
if (!target.time && bucket.time) {
|
|
target.time = bucket.time;
|
|
}
|
|
});
|
|
});
|
|
|
|
return merged;
|
|
}
|
|
|
|
export function sumRecentRequests(
|
|
buckets: RecentRequestBucket[]
|
|
): { success: number; failure: number } {
|
|
return normalizeRecentRequestBuckets(buckets).reduce(
|
|
(total, bucket) => ({
|
|
success: total.success + bucket.success,
|
|
failure: total.failure + bucket.failed,
|
|
}),
|
|
{ success: 0, failure: 0 }
|
|
);
|
|
}
|
|
|
|
export function statusBarDataFromRecentRequests(buckets: RecentRequestBucket[]): StatusBarData {
|
|
const normalizedBuckets = normalizeRecentRequestBuckets(buckets);
|
|
const emptyBucketCount = Math.max(0, RECENT_REQUEST_BLOCK_COUNT - normalizedBuckets.length);
|
|
const blockStats = [
|
|
...Array.from({ length: emptyBucketCount }, () => ({ success: 0, failed: 0 })),
|
|
...normalizedBuckets.slice(-RECENT_REQUEST_BLOCK_COUNT),
|
|
];
|
|
|
|
const now = Date.now();
|
|
const windowStart = now - RECENT_REQUEST_BLOCK_COUNT * RECENT_REQUEST_BLOCK_DURATION_MS;
|
|
|
|
const blocks: StatusBlockState[] = [];
|
|
const blockDetails: StatusBarData['blockDetails'] = [];
|
|
let totalSuccess = 0;
|
|
let totalFailure = 0;
|
|
|
|
blockStats.forEach((bucket, index) => {
|
|
const success = bucket.success;
|
|
const failure = bucket.failed;
|
|
const total = success + failure;
|
|
|
|
totalSuccess += success;
|
|
totalFailure += failure;
|
|
|
|
if (total === 0) {
|
|
blocks.push('idle');
|
|
} else if (failure === 0) {
|
|
blocks.push('success');
|
|
} else if (success === 0) {
|
|
blocks.push('failure');
|
|
} else {
|
|
blocks.push('mixed');
|
|
}
|
|
|
|
const blockStartTime = windowStart + index * RECENT_REQUEST_BLOCK_DURATION_MS;
|
|
blockDetails.push({
|
|
success,
|
|
failure,
|
|
rate: total > 0 ? success / total : -1,
|
|
startTime: blockStartTime,
|
|
endTime: blockStartTime + RECENT_REQUEST_BLOCK_DURATION_MS,
|
|
});
|
|
});
|
|
|
|
const total = totalSuccess + totalFailure;
|
|
|
|
return {
|
|
blocks,
|
|
blockDetails,
|
|
successRate: total > 0 ? (totalSuccess / total) * 100 : 100,
|
|
totalSuccess,
|
|
totalFailure,
|
|
};
|
|
}
|