llm-simple-router 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/recommended-providers.json +76 -0
- package/config/recommended-retry-rules.json +10 -0
- package/dist/admin/api-response.d.ts +27 -0
- package/dist/admin/api-response.js +40 -0
- package/dist/admin/constants.d.ts +0 -2
- package/dist/admin/constants.js +0 -3
- package/dist/admin/groups.js +9 -5
- package/dist/admin/logs.js +3 -2
- package/dist/admin/mappings.js +7 -6
- package/dist/admin/metrics.js +23 -5
- package/dist/admin/monitor.js +2 -1
- package/dist/admin/providers.js +13 -4
- package/dist/admin/proxy-enhancement.js +11 -6
- package/dist/admin/recommended.js +1 -9
- package/dist/admin/retry-rules.js +8 -4
- package/dist/admin/router-keys.js +5 -1
- package/dist/admin/routes.js +2 -0
- package/dist/admin/settings-import-export.js +3 -2
- package/dist/admin/settings.js +7 -5
- package/dist/admin/setup.js +3 -2
- package/dist/admin/stats.js +20 -3
- package/dist/admin/upgrade.d.ts +13 -0
- package/dist/admin/upgrade.js +114 -0
- package/dist/admin/usage.js +12 -24
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +11 -0
- package/dist/db/index.d.ts +3 -3
- package/dist/db/index.js +2 -2
- package/dist/db/mappings.js +5 -8
- package/dist/db/metrics.js +3 -4
- package/dist/db/providers.d.ts +8 -0
- package/dist/db/providers.js +6 -0
- package/dist/db/retry-rules.d.ts +1 -0
- package/dist/db/retry-rules.js +3 -0
- package/dist/db/settings.d.ts +2 -0
- package/dist/db/settings.js +7 -0
- package/dist/db/stats.d.ts +1 -2
- package/dist/db/stats.js +7 -11
- package/dist/index.d.ts +2 -0
- package/dist/index.js +55 -34
- package/dist/metrics/metrics-extractor.js +1 -1
- package/dist/metrics/sse-parser.js +2 -0
- package/dist/middleware/admin-auth.js +6 -5
- package/dist/middleware/auth.js +1 -10
- package/dist/monitor/request-tracker.d.ts +1 -0
- package/dist/monitor/request-tracker.js +9 -45
- package/dist/monitor/runtime-collector.js +1 -1
- package/dist/monitor/stream-content-accumulator.d.ts +14 -0
- package/dist/monitor/stream-content-accumulator.js +58 -0
- package/dist/proxy/anthropic.d.ts +2 -1
- package/dist/proxy/anthropic.js +3 -3
- package/dist/proxy/enhancement/directive-parser.d.ts +18 -0
- package/dist/proxy/{directive-parser.js → enhancement/directive-parser.js} +44 -0
- package/dist/proxy/{enhancement-handler.js → enhancement/enhancement-handler.js} +152 -32
- package/dist/proxy/enhancement/index.d.ts +3 -0
- package/dist/proxy/enhancement/index.js +3 -0
- package/dist/proxy/{response-cleaner.js → enhancement/response-cleaner.js} +14 -0
- package/dist/proxy/log-helpers.d.ts +1 -1
- package/dist/proxy/mapping-resolver.js +4 -4
- package/dist/proxy/openai.d.ts +2 -1
- package/dist/proxy/openai.js +4 -4
- package/dist/proxy/orchestrator.d.ts +0 -1
- package/dist/proxy/orchestrator.js +1 -3
- package/dist/proxy/proxy-core.d.ts +0 -4
- package/dist/proxy/proxy-core.js +0 -2
- package/dist/proxy/proxy-handler.d.ts +1 -1
- package/dist/proxy/proxy-handler.js +52 -132
- package/dist/proxy/proxy-logging.d.ts +0 -2
- package/dist/proxy/proxy-logging.js +1 -3
- package/dist/proxy/resilience.d.ts +5 -2
- package/dist/proxy/resilience.js +16 -7
- package/dist/proxy/strategy/failover.js +2 -7
- package/dist/proxy/strategy/random.js +2 -2
- package/dist/proxy/strategy/round-robin.js +2 -2
- package/dist/proxy/strategy/scheduled.js +1 -8
- package/dist/proxy/strategy/targets-rule.d.ts +1 -0
- package/dist/proxy/strategy/targets-rule.js +5 -0
- package/dist/proxy/transport-fn.d.ts +25 -0
- package/dist/proxy/transport-fn.js +55 -0
- package/dist/proxy/transport.d.ts +0 -25
- package/dist/proxy/transport.js +0 -38
- package/dist/upgrade/checker.d.ts +25 -0
- package/dist/upgrade/checker.js +120 -0
- package/dist/upgrade/deployment.d.ts +2 -0
- package/dist/upgrade/deployment.js +20 -0
- package/dist/upgrade/version.d.ts +1 -0
- package/dist/upgrade/version.js +13 -0
- package/dist/utils/password.js +4 -2
- package/dist/utils/time-range.d.ts +9 -0
- package/dist/utils/time-range.js +40 -0
- package/frontend-dist/assets/CardContent-WrBnGhTg.js +1 -0
- package/frontend-dist/assets/CardTitle-BcDYk7cq.js +1 -0
- package/frontend-dist/assets/Checkbox-MZf0YsDG.js +1 -0
- package/frontend-dist/assets/CollapsibleTrigger-CrOH9HlW.js +1 -0
- package/frontend-dist/assets/Collection-DcTx_Y54.js +1 -0
- package/frontend-dist/assets/Dashboard-D0oDrSLr.js +3 -0
- package/frontend-dist/assets/DialogTitle-Cl5Cd7QH.js +1 -0
- package/frontend-dist/assets/{Input-l5ZurXX5.js → Input-O0ebU-Va.js} +1 -1
- package/frontend-dist/assets/Label-C_S0y7Um.js +1 -0
- package/frontend-dist/assets/Login-DGY7uF8P.js +1 -0
- package/frontend-dist/assets/Logs-ls8pv89b.js +1 -0
- package/frontend-dist/assets/ModelMappings-DGlf0S4s.js +1 -0
- package/frontend-dist/assets/Monitor-BSI87grz.js +1 -0
- package/frontend-dist/assets/PopperContent-C6Q7hDmf.js +1 -0
- package/frontend-dist/assets/Providers-ZkRpj8_m.js +1 -0
- package/frontend-dist/assets/ProxyEnhancement-DFPI1W6Z.js +5 -0
- package/frontend-dist/assets/RetryRules-DtM31qsl.js +1 -0
- package/frontend-dist/assets/RouterKeys-D63tRFKm.js +1 -0
- package/frontend-dist/assets/RovingFocusItem-BJoylAKU.js +1 -0
- package/frontend-dist/assets/SelectValue-CLp5z6_I.js +1 -0
- package/frontend-dist/assets/Settings-DSgRKbTQ.js +6 -0
- package/frontend-dist/assets/Setup-BDmj6CRk.js +1 -0
- package/frontend-dist/assets/Switch-Wz-t_zkv.js +1 -0
- package/frontend-dist/assets/TableHeader-DGtcqGkw.js +1 -0
- package/frontend-dist/assets/TabsTrigger-CPCi2HIa.js +1 -0
- package/frontend-dist/assets/Teleport-DdjYHlNK.js +3 -0
- package/frontend-dist/assets/TooltipTrigger-H_QoPY1n.js +1 -0
- package/frontend-dist/assets/UnifiedRequestDialog-BAAfMJJl.js +3 -0
- package/frontend-dist/assets/{VisuallyHidden-BwwTtzb9.js → VisuallyHidden-Cyk-jWwh.js} +1 -1
- package/frontend-dist/assets/VisuallyHiddenInput-CYjNe_H8.js +1 -0
- package/frontend-dist/assets/alert-dialog-Bi3dliLl.js +1 -0
- package/frontend-dist/assets/badge-Kkta3e9W.js +1 -0
- package/frontend-dist/assets/button-BQ3s7yNh.js +12 -0
- package/frontend-dist/assets/{createLucideIcon-Biq59l_W.js → createLucideIcon-D1tkPDOQ.js} +1 -1
- package/frontend-dist/assets/dialog-DoIATUYw.js +1 -0
- package/frontend-dist/assets/{file-text-DoRW0hQW.js → file-text-Dt6QP1bZ.js} +1 -1
- package/frontend-dist/assets/index-BY0E7CHR.js +1 -0
- package/frontend-dist/assets/index-Bnrh1mFY.css +1 -0
- package/frontend-dist/assets/lib-CxwxnlwW.js +1 -0
- package/frontend-dist/assets/{ohash.D__AXeF1-BGxYMs6k.js → ohash.D__AXeF1-b0PiKZB_.js} +1 -1
- package/frontend-dist/assets/{useClipboard-vaHkvJHw.js → useClipboard-Cnnz6AAN.js} +1 -1
- package/frontend-dist/assets/useLogRetention-DYP5LOAc.js +1 -0
- package/frontend-dist/assets/useNonce-DKbOCfgM.js +1 -0
- package/frontend-dist/assets/x-CAoitXRt.js +1 -0
- package/frontend-dist/index.html +18 -9
- package/package.json +2 -1
- package/dist/proxy/directive-parser.d.ts +0 -7
- package/frontend-dist/assets/CardContent-CIO85eT6.js +0 -1
- package/frontend-dist/assets/CardTitle-DiqIReMT.js +0 -1
- package/frontend-dist/assets/Checkbox-C2u5pIp4.js +0 -1
- package/frontend-dist/assets/CollapsibleTrigger-RKFL41om.js +0 -1
- package/frontend-dist/assets/Collection-iiNnuTQj.js +0 -1
- package/frontend-dist/assets/Dashboard-DOEqP6gF.js +0 -3
- package/frontend-dist/assets/DialogTitle-CEqndrf6.js +0 -1
- package/frontend-dist/assets/Label-PgGtS8v2.js +0 -1
- package/frontend-dist/assets/Login-DaN6ZcCx.js +0 -1
- package/frontend-dist/assets/Logs-CleRQ7Xk.js +0 -1
- package/frontend-dist/assets/ModelMappings-CacA_ua_.js +0 -1
- package/frontend-dist/assets/Monitor-LSMFOBN2.js +0 -1
- package/frontend-dist/assets/PopperContent-zLFHqQP0.js +0 -1
- package/frontend-dist/assets/Providers-NT5MUDU0.js +0 -1
- package/frontend-dist/assets/ProxyEnhancement-DhOy8nNy.js +0 -5
- package/frontend-dist/assets/RetryRules-7arWa3jB.js +0 -1
- package/frontend-dist/assets/RouterKeys-CdaZunRg.js +0 -1
- package/frontend-dist/assets/SelectValue-CSg-MKW_.js +0 -1
- package/frontend-dist/assets/Settings-1ntV9XE3.js +0 -6
- package/frontend-dist/assets/Setup-CXLTDhYJ.js +0 -1
- package/frontend-dist/assets/Switch-DivrIFE3.js +0 -1
- package/frontend-dist/assets/TableHeader-Bn0bodWx.js +0 -1
- package/frontend-dist/assets/TabsContent-MWvOH_LJ.js +0 -1
- package/frontend-dist/assets/TabsTrigger-WKkUfO2M.js +0 -1
- package/frontend-dist/assets/Teleport-B0PNXZbP.js +0 -3
- package/frontend-dist/assets/UnifiedRequestDialog-Ba2e7YuJ.js +0 -3
- package/frontend-dist/assets/VisuallyHiddenInput-EGZSP7s8.js +0 -1
- package/frontend-dist/assets/alert-dialog-CS1yFhdV.js +0 -1
- package/frontend-dist/assets/badge-C-QcC5n2.js +0 -1
- package/frontend-dist/assets/button-Dbz2Be22.js +0 -12
- package/frontend-dist/assets/dialog-Cr0YQlLW.js +0 -1
- package/frontend-dist/assets/index-0H2uCGbx.js +0 -1
- package/frontend-dist/assets/index-D-cdVNCb.css +0 -1
- package/frontend-dist/assets/lib-B0lieqgg.js +0 -1
- package/frontend-dist/assets/useForwardExpose-C2_ks3sW.js +0 -1
- package/frontend-dist/assets/useLogRetention-Cs_fiKql.js +0 -1
- package/frontend-dist/assets/useNonce-C9do0jOI.js +0 -1
- package/frontend-dist/assets/x-BlTnH_0_.js +0 -1
- /package/dist/proxy/{enhancement-handler.d.ts → enhancement/enhancement-handler.d.ts} +0 -0
- /package/dist/proxy/{response-cleaner.d.ts → enhancement/response-cleaner.d.ts} +0 -0
package/dist/proxy/transport.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { request as httpRequestFn } from "http";
|
|
2
2
|
import { request as httpsRequestFn } from "https";
|
|
3
3
|
import { UPSTREAM_SUCCESS, filterHeaders } from "./types.js";
|
|
4
|
-
import { callStream } from "./stream-proxy.js";
|
|
5
4
|
// Re-export callStream from stream-proxy.ts for external consumers
|
|
6
5
|
export { callStream } from "./stream-proxy.js";
|
|
7
6
|
// ---------- Constants ----------
|
|
@@ -16,9 +15,6 @@ export const _transportInternals = {
|
|
|
16
15
|
: httpRequestFn(options);
|
|
17
16
|
},
|
|
18
17
|
};
|
|
19
|
-
export function createUpstreamRequest(url, options) {
|
|
20
|
-
return _transportInternals.createUpstreamRequest(url, options);
|
|
21
|
-
}
|
|
22
18
|
export function buildRequestOptions(url, headers, method = "POST") {
|
|
23
19
|
return {
|
|
24
20
|
hostname: url.hostname,
|
|
@@ -92,37 +88,3 @@ export function callGet(backend, apiKey, clientHeaders, upstreamPath, buildHeade
|
|
|
92
88
|
req.end();
|
|
93
89
|
});
|
|
94
90
|
}
|
|
95
|
-
export function proxyNonStreamCompat(backend, apiKey, body, clientHeaders, upstreamPath, buildHeaders) {
|
|
96
|
-
return callNonStream(backend, apiKey, body, clientHeaders, upstreamPath, buildHeaders)
|
|
97
|
-
.then((r) => {
|
|
98
|
-
if (r.kind === "throw")
|
|
99
|
-
throw r.error;
|
|
100
|
-
return {
|
|
101
|
-
statusCode: r.statusCode,
|
|
102
|
-
body: "body" in r ? r.body : "",
|
|
103
|
-
headers: "headers" in r ? r.headers : {},
|
|
104
|
-
sentHeaders: r.sentHeaders,
|
|
105
|
-
sentBody: "sentBody" in r ? r.sentBody : "",
|
|
106
|
-
};
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
export function proxyStreamCompat(backend, apiKey, body, clientHeaders, reply, timeoutMs, upstreamPath, buildHeaders, metricsTransform, checkEarlyError) {
|
|
110
|
-
return new Promise((resolve, reject) => {
|
|
111
|
-
function onResult(r) {
|
|
112
|
-
if (r.kind === "throw") {
|
|
113
|
-
reject(r.error);
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
const metrics = (r.kind === "stream_success" || r.kind === "stream_abort") ? r.metrics : undefined;
|
|
117
|
-
resolve({
|
|
118
|
-
statusCode: r.statusCode,
|
|
119
|
-
responseBody: r.kind === "stream_success" ? undefined : ("body" in r ? r.body : undefined),
|
|
120
|
-
upstreamResponseHeaders: ("upstreamResponseHeaders" in r ? r.upstreamResponseHeaders : undefined) ?? ("headers" in r ? r.headers : {}) ?? {},
|
|
121
|
-
sentHeaders: r.sentHeaders,
|
|
122
|
-
metricsResult: metrics ?? undefined,
|
|
123
|
-
abnormalClose: r.kind === "stream_abort",
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
callStream(backend, apiKey, body, clientHeaders, reply, timeoutMs, upstreamPath, buildHeaders, metricsTransform, checkEarlyError, onResult);
|
|
127
|
-
});
|
|
128
|
-
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface NpmStatus {
|
|
2
|
+
hasUpdate: boolean;
|
|
3
|
+
currentVersion: string;
|
|
4
|
+
latestVersion: string | null;
|
|
5
|
+
}
|
|
6
|
+
export interface ConfigStatus {
|
|
7
|
+
hasUpdate: boolean;
|
|
8
|
+
providerChanges: number;
|
|
9
|
+
retryRuleChanges: number;
|
|
10
|
+
}
|
|
11
|
+
export interface UpgradeStatus {
|
|
12
|
+
npm: NpmStatus;
|
|
13
|
+
config: ConfigStatus;
|
|
14
|
+
lastCheckedAt: string | null;
|
|
15
|
+
}
|
|
16
|
+
export interface CheckerOptions {
|
|
17
|
+
npmRegistryUrl?: string;
|
|
18
|
+
configBaseUrl?: string;
|
|
19
|
+
configDir?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function fetchJson(url: string, redirects?: number): Promise<unknown>;
|
|
22
|
+
export declare function createUpgradeChecker(options?: CheckerOptions): {
|
|
23
|
+
check: (sourceOverride?: string) => Promise<void>;
|
|
24
|
+
getStatus: () => UpgradeStatus;
|
|
25
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import https from 'node:https';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { getInstalledVersion } from './version.js';
|
|
6
|
+
const DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org/llm-simple-router';
|
|
7
|
+
const DEFAULT_GITHUB_CONFIG_BASE = 'https://raw.githubusercontent.com/zhushanwen321/llm-simple-router/main/config';
|
|
8
|
+
const CHECK_TIMEOUT_MS = 5000;
|
|
9
|
+
const MAX_REDIRECTS = 5;
|
|
10
|
+
export async function fetchJson(url, redirects = 0) {
|
|
11
|
+
if (redirects > MAX_REDIRECTS)
|
|
12
|
+
throw new Error('too many redirects');
|
|
13
|
+
const mod = url.startsWith('https') ? https : http;
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const timer = setTimeout(() => reject(new Error('timeout')), CHECK_TIMEOUT_MS);
|
|
16
|
+
mod.get(url, (res) => {
|
|
17
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
18
|
+
clearTimeout(timer);
|
|
19
|
+
res.resume();
|
|
20
|
+
fetchJson(res.headers.location, redirects + 1).then(resolve, reject);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
|
|
24
|
+
clearTimeout(timer);
|
|
25
|
+
res.resume();
|
|
26
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
let data = '';
|
|
30
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
31
|
+
res.on('end', () => {
|
|
32
|
+
clearTimeout(timer);
|
|
33
|
+
try {
|
|
34
|
+
resolve(JSON.parse(data));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
reject(new Error('invalid json'));
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}).on('error', (err) => {
|
|
41
|
+
clearTimeout(timer);
|
|
42
|
+
reject(err);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
export function createUpgradeChecker(options) {
|
|
47
|
+
const npmRegistryUrl = options?.npmRegistryUrl ?? DEFAULT_NPM_REGISTRY;
|
|
48
|
+
const configBaseUrl = options?.configBaseUrl ?? DEFAULT_GITHUB_CONFIG_BASE;
|
|
49
|
+
const configDir = options?.configDir ?? path.resolve(process.cwd(), 'config');
|
|
50
|
+
let npmStatus = {
|
|
51
|
+
hasUpdate: false,
|
|
52
|
+
currentVersion: getInstalledVersion(),
|
|
53
|
+
latestVersion: null,
|
|
54
|
+
};
|
|
55
|
+
let configStatus = {
|
|
56
|
+
hasUpdate: false,
|
|
57
|
+
providerChanges: 0,
|
|
58
|
+
retryRuleChanges: 0,
|
|
59
|
+
};
|
|
60
|
+
let lastCheckedAt = null;
|
|
61
|
+
function loadLocalJson(filename) {
|
|
62
|
+
const filePath = path.join(configDir, filename);
|
|
63
|
+
try {
|
|
64
|
+
if (!fs.existsSync(filePath))
|
|
65
|
+
return null;
|
|
66
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function checkNpm() {
|
|
73
|
+
try {
|
|
74
|
+
const data = await fetchJson(npmRegistryUrl);
|
|
75
|
+
const latest = data?.['dist-tags']?.latest ?? null;
|
|
76
|
+
npmStatus = {
|
|
77
|
+
hasUpdate: latest !== null && latest !== npmStatus.currentVersion,
|
|
78
|
+
currentVersion: npmStatus.currentVersion,
|
|
79
|
+
latestVersion: latest,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
process.stderr.write('[upgrade] failed to check npm version\n');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function checkConfig(sourceOverride) {
|
|
87
|
+
try {
|
|
88
|
+
const base = sourceOverride ?? configBaseUrl;
|
|
89
|
+
const [providersResult, rulesResult] = await Promise.allSettled([
|
|
90
|
+
fetchJson(`${base}/recommended-providers.json`),
|
|
91
|
+
fetchJson(`${base}/recommended-retry-rules.json`),
|
|
92
|
+
]);
|
|
93
|
+
const remoteProviders = providersResult.status === 'fulfilled' ? providersResult.value : null;
|
|
94
|
+
const remoteRules = rulesResult.status === 'fulfilled' ? rulesResult.value : null;
|
|
95
|
+
if (!remoteProviders && !remoteRules) {
|
|
96
|
+
throw new Error('both providers and rules fetch failed');
|
|
97
|
+
}
|
|
98
|
+
const localProviders = loadLocalJson('recommended-providers.json');
|
|
99
|
+
const localRules = loadLocalJson('recommended-retry-rules.json');
|
|
100
|
+
const providersChanged = remoteProviders !== null && JSON.stringify(remoteProviders) !== JSON.stringify(localProviders);
|
|
101
|
+
const rulesChanged = remoteRules !== null && JSON.stringify(remoteRules) !== JSON.stringify(localRules);
|
|
102
|
+
configStatus = {
|
|
103
|
+
hasUpdate: providersChanged || rulesChanged,
|
|
104
|
+
providerChanges: providersChanged ? 1 : 0,
|
|
105
|
+
retryRuleChanges: rulesChanged ? 1 : 0,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
process.stderr.write('[upgrade] failed to check config update\n');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function check(sourceOverride) {
|
|
113
|
+
await Promise.allSettled([checkNpm(), checkConfig(sourceOverride)]);
|
|
114
|
+
lastCheckedAt = new Date().toISOString();
|
|
115
|
+
}
|
|
116
|
+
function getStatus() {
|
|
117
|
+
return { npm: { ...npmStatus }, config: { ...configStatus }, lastCheckedAt };
|
|
118
|
+
}
|
|
119
|
+
return { check, getStatus };
|
|
120
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
let cachedDeployment = null;
|
|
4
|
+
export function detectDeployment() {
|
|
5
|
+
if (cachedDeployment)
|
|
6
|
+
return cachedDeployment;
|
|
7
|
+
if (existsSync('/.dockerenv') || existsSync('/run/.containerenv')) {
|
|
8
|
+
cachedDeployment = 'docker';
|
|
9
|
+
return cachedDeployment;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
execSync('npm --version', { stdio: 'pipe', timeout: 3000 });
|
|
13
|
+
cachedDeployment = 'npm';
|
|
14
|
+
return cachedDeployment;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// 不缓存 unknown,下次调用时重试检测
|
|
18
|
+
return 'unknown';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getInstalledVersion(): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
let cachedVersion = null;
|
|
5
|
+
export function getInstalledVersion() {
|
|
6
|
+
if (cachedVersion)
|
|
7
|
+
return cachedVersion;
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const pkgPath = path.resolve(__dirname, '../../package.json');
|
|
10
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
11
|
+
cachedVersion = pkg.version;
|
|
12
|
+
return cachedVersion;
|
|
13
|
+
}
|
package/dist/utils/password.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomBytes, scryptSync } from "node:crypto";
|
|
1
|
+
import { randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
|
|
2
2
|
const SCRYPT_KEYLEN = 64;
|
|
3
3
|
const SALT_BYTES = 16;
|
|
4
4
|
export function hashPassword(password) {
|
|
@@ -11,5 +11,7 @@ export function verifyPassword(password, stored) {
|
|
|
11
11
|
if (!salt || !hash)
|
|
12
12
|
return false;
|
|
13
13
|
const derived = scryptSync(password, salt, SCRYPT_KEYLEN).toString("hex");
|
|
14
|
-
|
|
14
|
+
if (derived.length !== hash.length)
|
|
15
|
+
return false;
|
|
16
|
+
return timingSafeEqual(Buffer.from(derived), Buffer.from(hash));
|
|
15
17
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
export type DashboardPeriod = "window" | "weekly" | "monthly";
|
|
3
|
+
export interface TimeRange {
|
|
4
|
+
startTime: string;
|
|
5
|
+
endTime: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function resolveTimeRange(period: DashboardPeriod, db: Database.Database, routerKeyId?: string): TimeRange;
|
|
8
|
+
/** 从 date 对象中计算出当周的周一 */
|
|
9
|
+
export declare function getMonday(date: Date): Date;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { getLatestWindow } from "../db/usage-windows.js";
|
|
2
|
+
import { toSqliteDatetime } from "./datetime.js";
|
|
3
|
+
// 5 小时窗口,与 usage-windows 的默认窗口时长对齐
|
|
4
|
+
const WINDOW_DURATION_MS = 5 * 3600_000;
|
|
5
|
+
export function resolveTimeRange(period, db, routerKeyId) {
|
|
6
|
+
const now = new Date();
|
|
7
|
+
switch (period) {
|
|
8
|
+
case "window": {
|
|
9
|
+
const latest = getLatestWindow(db, routerKeyId);
|
|
10
|
+
if (!latest) {
|
|
11
|
+
// 无窗口数据时回退到当前小时的 5h 区间
|
|
12
|
+
const start = new Date(now);
|
|
13
|
+
start.setMinutes(0, 0, 0);
|
|
14
|
+
return {
|
|
15
|
+
startTime: toSqliteDatetime(start),
|
|
16
|
+
endTime: toSqliteDatetime(new Date(start.getTime() + WINDOW_DURATION_MS)),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return { startTime: latest.start_time, endTime: latest.end_time };
|
|
20
|
+
}
|
|
21
|
+
case "weekly": {
|
|
22
|
+
const monday = getMonday(now);
|
|
23
|
+
monday.setHours(0, 0, 0, 0);
|
|
24
|
+
return { startTime: toSqliteDatetime(monday), endTime: toSqliteDatetime(now) };
|
|
25
|
+
}
|
|
26
|
+
case "monthly": {
|
|
27
|
+
const first = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
28
|
+
return { startTime: toSqliteDatetime(first), endTime: toSqliteDatetime(now) };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** 从 date 对象中计算出当周的周一 */
|
|
33
|
+
export function getMonday(date) {
|
|
34
|
+
const d = new Date(date);
|
|
35
|
+
const day = d.getDay();
|
|
36
|
+
// 周日 getDay()=0,需要回退到上周一
|
|
37
|
+
const diff = d.getDate() - day + (day === 0 ? -6 : 1);
|
|
38
|
+
d.setDate(diff);
|
|
39
|
+
return d;
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Ft as e,Nt as t,W as n,at as r,ct as i,q as a,r as o}from"./button-BQ3s7yNh.js";var s=[`data-size`],c=a({__name:`Card`,props:{class:{type:[Boolean,null,String,Object,Array]},size:{default:`default`}},setup(a){let c=a;return(l,u)=>(r(),n(`div`,{"data-slot":`card`,"data-size":a.size,class:e(t(o)(`ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-lg py-4 text-sm ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg group/card flex flex-col`,c.class))},[i(l.$slots,`default`)],10,s))}}),l=a({__name:`CardContent`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(a){let s=a;return(a,c)=>(r(),n(`div`,{"data-slot":`card-content`,class:e(t(o)(`px-4 group-data-[size=sm]/card:px-3`,s.class))},[i(a.$slots,`default`)],2))}});export{c as n,l as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Ft as e,Nt as t,W as n,at as r,ct as i,q as a,r as o}from"./button-BQ3s7yNh.js";var s=a({__name:`CardHeader`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(a){let s=a;return(a,c)=>(r(),n(`div`,{"data-slot":`card-header`,class:e(t(o)(`gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]`,s.class))},[i(a.$slots,`default`)],2))}}),c=a({__name:`CardTitle`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(a){let s=a;return(a,c)=>(r(),n(`div`,{"data-slot":`card-title`,class:e(t(o)(`text-base leading-snug font-medium group-data-[size=sm]/card:text-sm cn-font-heading`,s.class))},[i(a.$slots,`default`)],2))}});export{s as n,c as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{B as e,H as t,I as n,It as r,K as i,L as a,Nt as o,U as s,Y as c,at as l,ct as u,et as d,i as f,m as p,o as m,q as h,r as g,ut as _,vt as v,x as y}from"./button-BQ3s7yNh.js";import{n as b,t as x}from"./ohash.D__AXeF1-b0PiKZB_.js";import{g as S,o as C,u as w,y as T}from"./Teleport-DdjYHlNK.js";import{n as E}from"./VisuallyHidden-Cyk-jWwh.js";import{t as D}from"./VisuallyHiddenInput-CYjNe_H8.js";import{t as O}from"./RovingFocusItem-BJoylAKU.js";function k(e,t){return S(e)?!1:Array.isArray(e)?e.some(e=>x(e,t)):x(e,t)}var[A,j]=T(`CheckboxGroupRoot`);function M(e){return e===`indeterminate`}function N(e){return M(e)?`indeterminate`:e?`checked`:`unchecked`}var[P,F]=T(`CheckboxRoot`),I=h({inheritAttrs:!1,__name:`CheckboxRoot`,props:{defaultValue:{type:null,required:!1},modelValue:{type:null,required:!1,default:void 0},disabled:{type:Boolean,required:!1},value:{type:null,required:!1,default:`on`},id:{type:String,required:!1},trueValue:{type:null,required:!1,default:()=>!0},falseValue:{type:null,required:!1,default:()=>!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`},name:{type:String,required:!1},required:{type:Boolean,required:!1}},emits:[`update:modelValue`],setup(r,{emit:i}){let c=r,h=i,{forwardRef:g,currentElement:y}=m(),b=A(null),C=p(c,`modelValue`,h,{defaultValue:c.defaultValue??c.falseValue,passive:c.modelValue===void 0}),w=e(()=>b?.disabled.value||c.disabled),T=e(()=>x(C.value,c.trueValue)),j=e(()=>S(b?.modelValue.value)?C.value===`indeterminate`?`indeterminate`:T.value:k(b.modelValue.value,c.value));function P(){if(S(b?.modelValue.value))C.value===`indeterminate`?C.value=c.trueValue:C.value=T.value?c.falseValue:c.trueValue;else{let e=[...b.modelValue.value||[]];if(k(e,c.value)){let t=e.findIndex(e=>x(e,c.value));e.splice(t,1)}else e.push(c.value);b.modelValue.value=e}}let I=E(y),L=e(()=>c.id&&y.value?document.querySelector(`[for="${c.id}"]`)?.innerText:void 0);return F({disabled:w,state:j}),(e,r)=>(l(),t(_(o(b)?.rovingFocus.value?o(O):o(f)),d(e.$attrs,{id:e.id,ref:o(g),role:`checkbox`,"as-child":e.asChild,as:e.as,type:e.as===`button`?`button`:void 0,"aria-checked":o(M)(j.value)?`mixed`:j.value,"aria-required":e.required,"aria-label":e.$attrs[`aria-label`]||L.value,"data-state":o(N)(j.value),"data-disabled":w.value?``:void 0,disabled:w.value,focusable:o(b)?.rovingFocus.value?!w.value:void 0,onKeydown:n(a(()=>{},[`prevent`]),[`enter`]),onClick:P}),{default:v(()=>[u(e.$slots,`default`,{modelValue:o(C),state:j.value}),o(I)&&e.name&&!o(b)?(l(),t(o(D),{key:0,type:`checkbox`,checked:!!j.value,name:e.name,value:e.value,disabled:w.value,required:e.required},null,8,[`checked`,`name`,`value`,`disabled`,`required`])):s(`v-if`,!0)]),_:3},16,[`id`,`as-child`,`as`,`type`,`aria-checked`,`aria-required`,`aria-label`,`data-state`,`data-disabled`,`disabled`,`focusable`,`onKeydown`]))}}),L=h({__name:`CheckboxIndicator`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`span`}},setup(e){let{forwardRef:n}=m(),r=P();return(e,a)=>(l(),t(o(C),{present:e.forceMount||o(M)(o(r).state.value)||o(r).state.value===!0},{default:v(()=>[i(o(f),d({ref:o(n),"data-state":o(N)(o(r).state.value),"data-disabled":o(r).disabled.value?``:void 0,style:{pointerEvents:`none`},"as-child":e.asChild,as:e.as},e.$attrs),{default:v(()=>[u(e.$slots,`default`)]),_:3},16,[`data-state`,`data-disabled`,`as-child`,`as`])]),_:3},8,[`present`]))}}),R=h({__name:`Checkbox`,props:{defaultValue:{},modelValue:{},disabled:{type:Boolean},value:{},id:{},trueValue:{},falseValue:{},asChild:{type:Boolean},as:{},name:{},required:{type:Boolean},class:{type:[Boolean,null,String,Object,Array]}},emits:[`update:modelValue`],setup(e,{emit:n}){let a=e,s=n,f=w(y(a,`class`),s);return(e,n)=>(l(),t(o(I),d({"data-slot":`checkbox`},o(f),{class:o(g)(`border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-md border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-3 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50`,a.class)}),{default:v(t=>[i(o(L),{"data-slot":`checkbox-indicator`,class:`[&>svg]:size-3.5 grid place-content-center text-current transition-none`},{default:v(()=>[u(e.$slots,`default`,r(c(t)),()=>[i(o(b))])]),_:2},1024)]),_:3},16,[`class`]))}});export{R as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{B as e,Et as t,H as n,It as r,K as i,Nt as a,U as o,Y as s,at as c,ct as l,d as u,et as d,ht as f,i as p,jt as m,m as h,o as g,q as _,rt as v,tt as y,vt as b}from"./button-BQ3s7yNh.js";import{c as x,o as S,u as C,y as w}from"./Teleport-DdjYHlNK.js";var[T,E]=w(`CollapsibleRoot`),D=_({__name:`CollapsibleRoot`,props:{defaultOpen:{type:Boolean,required:!1,default:!1},open:{type:Boolean,required:!1,default:void 0},disabled:{type:Boolean,required:!1},unmountOnHide:{type:Boolean,required:!1,default:!0},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`update:open`],setup(e,{expose:t,emit:r}){let i=e,o=h(i,`open`,r,{defaultValue:i.defaultOpen,passive:i.open===void 0}),{disabled:s,unmountOnHide:u}=m(i);return E({contentId:``,disabled:s,open:o,unmountOnHide:u,onOpenToggle:()=>{s.value||(o.value=!o.value)}}),t({open:o}),g(),(e,t)=>(c(),n(a(p),{as:e.as,"as-child":i.asChild,"data-state":a(o)?`open`:`closed`,"data-disabled":a(s)?``:void 0},{default:b(()=>[l(e.$slots,`default`,{open:a(o)})]),_:3},8,[`as`,`as-child`,`data-state`,`data-disabled`]))}}),O=_({inheritAttrs:!1,__name:`CollapsibleContent`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`contentFound`],setup(r,{emit:s}){let m=r,h=s,_=T();_.contentId||=x(void 0,`reka-collapsible-content`);let C=t(),{forwardRef:w,currentElement:E}=g(),D=t(0),O=t(0),k=e(()=>_.open.value),A=t(k.value),j=t();f(()=>[k.value,C.value?.present],async()=>{await y();let e=E.value;if(!e)return;j.value=j.value||{transitionDuration:e.style.transitionDuration,animationName:e.style.animationName},e.style.transitionDuration=`0s`,e.style.animationName=`none`;let t=e.getBoundingClientRect();O.value=t.height,D.value=t.width,A.value||(e.style.transitionDuration=j.value.transitionDuration,e.style.animationName=j.value.animationName)},{immediate:!0});let M=e(()=>A.value&&_.open.value);return v(()=>{requestAnimationFrame(()=>{A.value=!1})}),u(E,`beforematch`,e=>{requestAnimationFrame(()=>{_.onOpenToggle(),h(`contentFound`)})}),(e,t)=>(c(),n(a(S),{ref_key:`presentRef`,ref:C,present:e.forceMount||a(_).open.value,"force-mount":!0},{default:b(({present:t})=>[i(a(p),d(e.$attrs,{id:a(_).contentId,ref:a(w),"as-child":m.asChild,as:e.as,hidden:t?void 0:a(_).unmountOnHide.value?``:`until-found`,"data-state":M.value?void 0:a(_).open.value?`open`:`closed`,"data-disabled":a(_).disabled?.value?``:void 0,style:{"--reka-collapsible-content-height":`${O.value}px`,"--reka-collapsible-content-width":`${D.value}px`}}),{default:b(()=>[!a(_).unmountOnHide.value||t?l(e.$slots,`default`,{key:0}):o(`v-if`,!0)]),_:2},1040,[`id`,`as-child`,`as`,`hidden`,`data-state`,`data-disabled`,`style`])]),_:3},8,[`present`]))}}),k=_({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(e){let t=e;g();let r=T();return(e,i)=>(c(),n(a(p),{type:e.as===`button`?`button`:void 0,as:e.as,"as-child":t.asChild,"aria-controls":a(r).contentId,"aria-expanded":a(r).open.value,"data-state":a(r).open.value?`open`:`closed`,"data-disabled":a(r).disabled?.value?``:void 0,disabled:a(r).disabled?.value,onClick:a(r).onOpenToggle},{default:b(()=>[l(e.$slots,`default`)]),_:3},8,[`type`,`as`,`as-child`,`aria-controls`,`aria-expanded`,`data-state`,`data-disabled`,`disabled`,`onClick`]))}}),A=_({__name:`Collapsible`,props:{defaultOpen:{type:Boolean},open:{type:Boolean},disabled:{type:Boolean},unmountOnHide:{type:Boolean},asChild:{type:Boolean},as:{}},emits:[`update:open`],setup(e,{emit:t}){let i=C(e,t);return(e,t)=>(c(),n(a(D),d({"data-slot":`collapsible`},a(i)),{default:b(t=>[l(e.$slots,`default`,r(s(t)))]),_:3},16))}}),j=_({__name:`CollapsibleContent`,props:{forceMount:{type:Boolean},asChild:{type:Boolean},as:{}},setup(e){let t=e;return(e,r)=>(c(),n(a(O),d({"data-slot":`collapsible-content`},t),{default:b(()=>[l(e.$slots,`default`)]),_:3},16))}}),M=_({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean},as:{}},setup(e){let t=e;return(e,r)=>(c(),n(a(k),d({"data-slot":`collapsible-trigger`},t),{default:b(()=>[l(e.$slots,`default`)]),_:3},16))}});export{j as n,A as r,M as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{B as e,Ct as t,Et as n,X as r,Z as i,a,gt as o,ht as s,ot as c,q as l,u}from"./button-BQ3s7yNh.js";import{h as d}from"./Teleport-DdjYHlNK.js";function f(t){let r=d({dir:n(`ltr`)});return e(()=>t?.value||r.dir?.value||`ltr`)}function p(){let t=n();return{primitiveElement:t,currentElement:e(()=>[`#text`,`#comment`].includes(t.value?.$el.nodeName)?t.value?.$el.nextElementSibling:u(t))}}var m=`data-reka-collection-item`;function h(u={}){let{key:d=``,isProvider:f=!1}=u,h=`${d}CollectionProvider`,g;if(f){let e=n(new Map);g={collectionRef:n(),itemMap:e},c(h,g)}else g=i(h);let _=(e=!1)=>{let t=g.collectionRef.value;if(!t)return[];let n=Array.from(t.querySelectorAll(`[${m}]`)),r=Array.from(g.itemMap.value.values()).sort((e,t)=>n.indexOf(e.ref)-n.indexOf(t.ref));return e?r:r.filter(e=>e.ref.dataset.disabled!==``)},v=l({name:`CollectionSlot`,inheritAttrs:!1,setup(e,{slots:t,attrs:n}){let{primitiveElement:i,currentElement:o}=p();return s(o,()=>{g.collectionRef.value=o.value}),()=>r(a,{ref:i,...n},t)}}),y=l({name:`CollectionItem`,inheritAttrs:!1,props:{value:{validator:()=>!0}},setup(e,{slots:n,attrs:i}){let{primitiveElement:s,currentElement:c}=p();return o(n=>{if(c.value){let r=t(c.value);g.itemMap.value.set(r,{ref:c.value,value:e.value}),n(()=>g.itemMap.value.delete(r))}}),()=>r(a,{...i,[m]:``,ref:s},n)}});return{getItems:_,reactiveItems:e(()=>Array.from(g.itemMap.value.values())),itemMapSize:e(()=>g.itemMap.value.size),CollectionSlot:v,CollectionItem:y}}export{p as n,f as r,h as t};
|