cartethyia 2.5.1 → 2.5.3
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/.env.example +5 -1
- package/dist/dashboard/assets/{AccountCooldown-ChpdzxRH.js → AccountCooldown-Bjo92Qat.js} +1 -1
- package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
- package/dist/dashboard/assets/{BannedPage-ChqnTRpZ.js → BannedPage-hfCzi-Ux.js} +1 -1
- package/dist/dashboard/assets/BannedPage-hfCzi-Ux.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js +1 -0
- package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BXUaRCQQ.js → CliToolsPage-DEDyKBlC.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DEDyKBlC.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js +1 -0
- package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-bsLuZmYH.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-bsLuZmYH.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-_UWhwziJ.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-_UWhwziJ.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DDVIfZGW.js +1 -0
- package/dist/dashboard/assets/LoginPage-DDVIfZGW.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js +1 -0
- package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-B53YYMng.js +1 -0
- package/dist/dashboard/assets/OverviewPage-B53YYMng.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js +7 -0
- package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BpOQCnun.js +1 -0
- package/dist/dashboard/assets/QuotaPage-BpOQCnun.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js +1 -0
- package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-D6pmQ86L.js +1 -0
- package/dist/dashboard/assets/SetupPage-D6pmQ86L.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DWvKiuSl.js +1 -0
- package/dist/dashboard/assets/SortableList-DWvKiuSl.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-DqK5E26W.js → StudioPage-CmkUrgdj.js} +3 -3
- package/dist/dashboard/assets/StudioPage-CmkUrgdj.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-B47uOydr.js +56 -0
- package/dist/dashboard/assets/UsagePage-B47uOydr.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-BmDbsior.js → api-keys-uNywYlfv.js} +1 -1
- package/dist/dashboard/assets/api-keys-uNywYlfv.js.gz +0 -0
- package/dist/dashboard/assets/{app-C-Y4oZT0.js → app-CafWtVXA.js} +1 -1
- package/dist/dashboard/assets/{app-C-Y4oZT0.js.gz → app-CafWtVXA.js.gz} +0 -0
- package/dist/dashboard/assets/app-DxivpfYG.css +2 -0
- package/dist/dashboard/assets/app-DxivpfYG.css.gz +0 -0
- package/dist/dashboard/assets/{assert-BHfaei-X.js → assert-NHwYq95t.js} +1 -1
- package/dist/dashboard/assets/{assert-BHfaei-X.js.gz → assert-NHwYq95t.js.gz} +0 -0
- package/dist/dashboard/assets/badge-6uzDDvqs.js +1 -0
- package/dist/dashboard/assets/badge-6uzDDvqs.js.gz +0 -0
- package/dist/dashboard/assets/banner-4_WNg2WZ.js +1 -0
- package/dist/dashboard/assets/banner-4_WNg2WZ.js.gz +0 -0
- package/dist/dashboard/assets/card--AtFedAq.js +1 -0
- package/dist/dashboard/assets/card--AtFedAq.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-B0wBq-RL.js → cli-tools-B3e7dx3N.js} +1 -1
- package/dist/dashboard/assets/cli-tools-B3e7dx3N.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-NreUx6c8.js +1 -0
- package/dist/dashboard/assets/clipboard-button-NreUx6c8.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BtpsJU98.js +1 -0
- package/dist/dashboard/assets/dialog-BtpsJU98.js.gz +0 -0
- package/dist/dashboard/assets/drawer-WwwtzihF.js +1 -0
- package/dist/dashboard/assets/drawer-WwwtzihF.js.gz +0 -0
- package/dist/dashboard/assets/entry-CkryrIea.js +4 -0
- package/dist/dashboard/assets/entry-CkryrIea.js.gz +0 -0
- package/dist/dashboard/assets/entry-TJIye_jT.js +1 -0
- package/dist/dashboard/assets/entry-TJIye_jT.js.gz +0 -0
- package/dist/dashboard/assets/{entry-D4O2229X.js → entry-jy9_4-Ke.js} +1 -1
- package/dist/dashboard/assets/entry-jy9_4-Ke.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-D_eM3IgG.js +1 -0
- package/dist/dashboard/assets/github-badge-D_eM3IgG.js.gz +0 -0
- package/dist/dashboard/assets/{index-CcD1eSJ6.js → index-BydyKaDz.js} +2 -2
- package/dist/dashboard/assets/index-BydyKaDz.js.gz +0 -0
- package/dist/dashboard/assets/inline-BcNGF8Gr.js +1 -0
- package/dist/dashboard/assets/inline-BcNGF8Gr.js.gz +0 -0
- package/dist/dashboard/assets/input-BG41et9G.js +1 -0
- package/dist/dashboard/assets/input-BG41et9G.js.gz +0 -0
- package/dist/dashboard/assets/layout-C8mer_Ak.js +1 -0
- package/dist/dashboard/assets/layout-C8mer_Ak.js.gz +0 -0
- package/dist/dashboard/assets/list-row-Gh_W4OR_.js +1 -0
- package/dist/dashboard/assets/list-row-Gh_W4OR_.js.gz +0 -0
- package/dist/dashboard/assets/{live-yfEJFF9T.js → live-BJGYRBnR.js} +1 -1
- package/dist/dashboard/assets/live-BJGYRBnR.js.gz +0 -0
- package/dist/dashboard/assets/{model-bans-CT-TxSNX.js → model-bans-DeVLDA7I.js} +1 -1
- package/dist/dashboard/assets/model-bans-DeVLDA7I.js.gz +0 -0
- package/dist/dashboard/assets/numbers-Bs4LOOuf.js +2 -0
- package/dist/dashboard/assets/numbers-Bs4LOOuf.js.gz +0 -0
- package/dist/dashboard/assets/{routing-Cs6jl49L.js → routing-CcusqXA7.js} +1 -1
- package/dist/dashboard/assets/routing-CcusqXA7.js.gz +0 -0
- package/dist/dashboard/assets/select-BO6h8yNv.js +41 -0
- package/dist/dashboard/assets/select-BO6h8yNv.js.gz +0 -0
- package/dist/dashboard/assets/{settings-CfcTcOyU.js → settings-mpmXTgUa.js} +1 -1
- package/dist/dashboard/assets/settings-mpmXTgUa.js.gz +0 -0
- package/dist/dashboard/assets/stack-DwLmjUFK.js +1 -0
- package/dist/dashboard/assets/stack-DwLmjUFK.js.gz +0 -0
- package/dist/dashboard/assets/state-DYdQmdAl.js +1 -0
- package/dist/dashboard/assets/state-DYdQmdAl.js.gz +0 -0
- package/dist/dashboard/assets/switch-CidSfbG7.js +1 -0
- package/dist/dashboard/assets/switch-CidSfbG7.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BQKMcJ1H.js → useMutation-w4u4CVue.js} +1 -1
- package/dist/dashboard/assets/useMutation-w4u4CVue.js.gz +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +51 -54
- package/package.json +1 -1
- package/scripts/cli/command/console-menu.ts +248 -246
- package/scripts/cli/command/tray.ts +208 -204
- package/src/boot/app.ts +3 -2
- package/src/config/spec.ts +26 -2
- package/src/console/backup/routes.ts +52 -1
- package/src/console/backup/service.ts +60 -4
- package/src/console/observability/contracts.ts +11 -1
- package/src/console/observability/store.ts +9 -6
- package/src/console/share/share-router.ts +6 -9
- package/src/observability/payload-capture.ts +366 -344
- package/src/observability/payload-store.ts +12 -2
- package/src/observability/telemetry-status.ts +28 -16
- package/src/persistence/stores/api-key-store.ts +6 -0
- package/src/persistence/stores/share-store.ts +583 -556
- package/src/persistence/stores/telemetry-store.ts +1 -1
- package/src/protocol/response/gemini.ts +18 -4
- package/src/providers/discovery/openrouter-free-roster.ts +3 -11
- package/src/providers/discovery/probe-phases.ts +42 -5
- package/src/providers/discovery/probing-service.ts +25 -2
- package/src/providers/integrations/antigravity/antigravity-quota.ts +11 -3
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +4 -4
- package/src/providers/integrations/buddy/workbuddy/shared.ts +201 -200
- package/src/providers/integrations/claude/claude.ts +3 -1
- package/src/providers/integrations/cline/cline-quota.ts +8 -11
- package/src/providers/integrations/cloudflareai/cloudflareai-models.ts +3 -1
- package/src/providers/integrations/inferhub.ts +3 -1
- package/src/providers/integrations/kilo/kilo.ts +0 -1
- package/src/providers/integrations/kiro/kiro-catalog.ts +8 -2
- package/src/providers/integrations/opencode/provider.ts +532 -532
- package/src/providers/integrations/openrouter/provider.ts +5 -15
- package/src/providers/integrations/tokenharbor.ts +2 -2
- package/src/providers/integrations/trae/trae-catalog.ts +5 -3
- package/src/providers/operations/account-health-service.ts +23 -0
- package/src/providers/operations/client-versions.ts +9 -2
- package/src/providers/provider-metadata.ts +7 -1
- package/src/security/api-key-auth.ts +23 -0
- package/src/transport/dispatch/attempt-finalize.ts +46 -5
- package/src/transport/dispatch/proxy-request.ts +9 -0
- package/src/transport/dispatch/streaming/stream.ts +16 -2
- package/src/transport/guards/auth-guard.ts +2 -3
- package/src/transport/routing/route-catalog.ts +806 -799
- package/dist/dashboard/assets/AccountCooldown-ChpdzxRH.js.gz +0 -0
- package/dist/dashboard/assets/BannedPage-ChqnTRpZ.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-Bb9CNuoh.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-Bb9CNuoh.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BXUaRCQQ.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CisNZOUt.js +0 -1
- package/dist/dashboard/assets/CombosPage-CisNZOUt.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-BOpI_LOH.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-BOpI_LOH.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-DAkdvJ4k.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-DAkdvJ4k.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-DeT-VXHd.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-DeT-VXHd.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-CytxSeGP.js +0 -1
- package/dist/dashboard/assets/LoginPage-CytxSeGP.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js +0 -1
- package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CU7S1wJs.js +0 -1
- package/dist/dashboard/assets/OverviewPage-CU7S1wJs.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-DLm2baot.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-DLm2baot.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DOXfuzfL.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-DOXfuzfL.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-wvk0ciQN.js +0 -7
- package/dist/dashboard/assets/ProxyPage-wvk0ciQN.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-Cn_fcUHE.js +0 -1
- package/dist/dashboard/assets/QuotaPage-Cn_fcUHE.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-8-eVadhc.js +0 -1
- package/dist/dashboard/assets/SettingsPage-8-eVadhc.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-uEIXPyGO.js +0 -1
- package/dist/dashboard/assets/SetupPage-uEIXPyGO.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DCY0TPTZ.js +0 -1
- package/dist/dashboard/assets/SortableList-DCY0TPTZ.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-DqK5E26W.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-38JfiZ0H.js +0 -56
- package/dist/dashboard/assets/UsagePage-38JfiZ0H.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BmDbsior.js.gz +0 -0
- package/dist/dashboard/assets/app-BbpSarVV.css +0 -2
- package/dist/dashboard/assets/app-BbpSarVV.css.gz +0 -0
- package/dist/dashboard/assets/badge-DpXT4Ixt.js +0 -1
- package/dist/dashboard/assets/badge-DpXT4Ixt.js.gz +0 -0
- package/dist/dashboard/assets/banner-CSAAWLJv.js +0 -1
- package/dist/dashboard/assets/banner-CSAAWLJv.js.gz +0 -0
- package/dist/dashboard/assets/card-DEhRXXw4.js +0 -1
- package/dist/dashboard/assets/card-DEhRXXw4.js.gz +0 -0
- package/dist/dashboard/assets/check-D69FzdvC.js +0 -1
- package/dist/dashboard/assets/check-D69FzdvC.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-B0wBq-RL.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js +0 -1
- package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js.gz +0 -0
- package/dist/dashboard/assets/copy-ngmQ9zcZ.js +0 -1
- package/dist/dashboard/assets/copy-ngmQ9zcZ.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BvQeBMfP.js +0 -1
- package/dist/dashboard/assets/dialog-BvQeBMfP.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CMwPXwQB.js +0 -1
- package/dist/dashboard/assets/drawer-CMwPXwQB.js.gz +0 -0
- package/dist/dashboard/assets/entry--yj84zk3.js +0 -4
- package/dist/dashboard/assets/entry--yj84zk3.js.gz +0 -0
- package/dist/dashboard/assets/entry-D4O2229X.js.gz +0 -0
- package/dist/dashboard/assets/entry-DKvr5Q9K.js +0 -1
- package/dist/dashboard/assets/entry-DKvr5Q9K.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-zliLvBwr.js +0 -1
- package/dist/dashboard/assets/github-badge-zliLvBwr.js.gz +0 -0
- package/dist/dashboard/assets/index-CcD1eSJ6.js.gz +0 -0
- package/dist/dashboard/assets/inline-D1IlZZ8H.js +0 -1
- package/dist/dashboard/assets/inline-D1IlZZ8H.js.gz +0 -0
- package/dist/dashboard/assets/input-BpVZdrbc.js +0 -1
- package/dist/dashboard/assets/input-BpVZdrbc.js.gz +0 -0
- package/dist/dashboard/assets/layout-BioLW7gN.js +0 -1
- package/dist/dashboard/assets/layout-BioLW7gN.js.gz +0 -0
- package/dist/dashboard/assets/list-row-CwVcGAIc.js +0 -1
- package/dist/dashboard/assets/list-row-CwVcGAIc.js.gz +0 -0
- package/dist/dashboard/assets/live-yfEJFF9T.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-CT-TxSNX.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CFL4_Dqc.js +0 -1
- package/dist/dashboard/assets/numbers-CFL4_Dqc.js.gz +0 -0
- package/dist/dashboard/assets/routing-Cs6jl49L.js.gz +0 -0
- package/dist/dashboard/assets/select-XPJ-dIw3.js +0 -41
- package/dist/dashboard/assets/select-XPJ-dIw3.js.gz +0 -0
- package/dist/dashboard/assets/settings-CfcTcOyU.js.gz +0 -0
- package/dist/dashboard/assets/stack-C9_uUjaA.js +0 -1
- package/dist/dashboard/assets/stack-C9_uUjaA.js.gz +0 -0
- package/dist/dashboard/assets/state-DA0B95UQ.js +0 -1
- package/dist/dashboard/assets/state-DA0B95UQ.js.gz +0 -0
- package/dist/dashboard/assets/switch-Cittxy-x.js +0 -1
- package/dist/dashboard/assets/switch-Cittxy-x.js.gz +0 -0
- package/dist/dashboard/assets/toast-CNt_jzO4.js +0 -2
- package/dist/dashboard/assets/toast-CNt_jzO4.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BQKMcJ1H.js.gz +0 -0
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* re-authentication.
|
|
20
20
|
*/
|
|
21
21
|
import type { CartethyiaDatabase } from "../../persistence/db-handle";
|
|
22
|
+
import type { Table } from "drizzle-orm";
|
|
22
23
|
import {
|
|
23
24
|
models,
|
|
24
25
|
providerAccounts,
|
|
@@ -29,7 +30,12 @@ import {
|
|
|
29
30
|
} from "../../persistence/schema";
|
|
30
31
|
import { ConsoleDomainError } from "../shared/errors";
|
|
31
32
|
import { tablesForSection, tableName } from "./contracts";
|
|
32
|
-
import type {
|
|
33
|
+
import type {
|
|
34
|
+
BackupSection,
|
|
35
|
+
RestoreWarning,
|
|
36
|
+
ValidatedRestore,
|
|
37
|
+
ValidatedTable,
|
|
38
|
+
} from "./contracts";
|
|
33
39
|
import {
|
|
34
40
|
applyRestore,
|
|
35
41
|
decompressBackup,
|
|
@@ -90,6 +96,53 @@ export interface ImportResult {
|
|
|
90
96
|
readonly format: "native" | "nine_router";
|
|
91
97
|
}
|
|
92
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Which parts of a backup file a restore should actually write.
|
|
101
|
+
*
|
|
102
|
+
* Absent or empty means "everything", which is the historical behavior and
|
|
103
|
+
* what the dashboard's checklist defaults to. A table named here but absent
|
|
104
|
+
* from the file is simply not restored — it is not an error, because the
|
|
105
|
+
* checklist is built from an inspection of *this* file and a selection never
|
|
106
|
+
* invents a table.
|
|
107
|
+
*/
|
|
108
|
+
export interface BackupRestoreSelection {
|
|
109
|
+
/** Table names to restore; absent or empty restores every table present. */
|
|
110
|
+
readonly tables?: readonly string[];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Narrows a validated backup to the tables the operator selected.
|
|
115
|
+
*
|
|
116
|
+
* The tenant row is the one thing that is not independently selectable: it is
|
|
117
|
+
* not a table in either section (`BACKUP_SECTIONS` are config and telemetry),
|
|
118
|
+
* so it never appears on the checklist, and every tenant-scoped table
|
|
119
|
+
* cascades from it. Dropping it because it was not named would make a partial
|
|
120
|
+
* restore fail on a foreign key for a table the operator *did* ask for. So it
|
|
121
|
+
* rides along with any config selection and is written only when one is made.
|
|
122
|
+
*
|
|
123
|
+
* A table named in the selection but missing from the file is a no-op rather
|
|
124
|
+
* than an error: the checklist is built from an inspection of this file, but
|
|
125
|
+
* the selection is validated as names only, and refusing to restore anything
|
|
126
|
+
* over one stale name would be worse than ignoring it.
|
|
127
|
+
*/
|
|
128
|
+
function restrictToSelection(
|
|
129
|
+
validation: ValidatedRestore,
|
|
130
|
+
selection: BackupRestoreSelection | undefined,
|
|
131
|
+
): ValidatedRestore {
|
|
132
|
+
const wanted = selection?.tables;
|
|
133
|
+
if (wanted === undefined || wanted.length === 0) return validation;
|
|
134
|
+
const selected = new Set(wanted);
|
|
135
|
+
const tables = new Map<Table, ValidatedTable>();
|
|
136
|
+
for (const [table, entry] of validation.tables) {
|
|
137
|
+
if (selected.has(tableName(table))) tables.set(table, entry);
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
...validation,
|
|
141
|
+
tables,
|
|
142
|
+
tenantRows: tables.size > 0 ? validation.tenantRows : [],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
93
146
|
function sectionsOrDefault(sections: readonly BackupSection[] | undefined): readonly BackupSection[] {
|
|
94
147
|
if (sections === undefined || sections.length === 0) return ["config"];
|
|
95
148
|
for (const section of sections) {
|
|
@@ -246,6 +299,7 @@ export class BackupService {
|
|
|
246
299
|
password: unknown,
|
|
247
300
|
payload: unknown,
|
|
248
301
|
tenantId: string,
|
|
302
|
+
selection?: BackupRestoreSelection,
|
|
249
303
|
): Promise<ImportResult> {
|
|
250
304
|
if (typeof password !== "string" || !(await this.options.verifyPassword(password))) {
|
|
251
305
|
throw new ConsoleDomainError("unauthorized", 401, "password is incorrect");
|
|
@@ -258,11 +312,11 @@ export class BackupService {
|
|
|
258
312
|
|
|
259
313
|
if (detected.kind === "nine_router") {
|
|
260
314
|
const { payload: converted, report } = convert9RouterBackup(detected.payload, tenantId);
|
|
261
|
-
const result = await this.apply(converted, tenantId);
|
|
315
|
+
const result = await this.apply(converted, tenantId, selection);
|
|
262
316
|
return { ...result, report, format: "nine_router" };
|
|
263
317
|
}
|
|
264
318
|
|
|
265
|
-
const result = await this.apply(detected.payload, tenantId);
|
|
319
|
+
const result = await this.apply(detected.payload, tenantId, selection);
|
|
266
320
|
return { ...result, format: "native" };
|
|
267
321
|
}
|
|
268
322
|
|
|
@@ -348,13 +402,15 @@ export class BackupService {
|
|
|
348
402
|
private async apply(
|
|
349
403
|
payload: unknown,
|
|
350
404
|
tenantId: string,
|
|
405
|
+
selection?: BackupRestoreSelection,
|
|
351
406
|
): Promise<{ restored: Record<string, number>; skipped: Record<string, number>; warnings: readonly RestoreWarning[] }> {
|
|
352
407
|
const validation = validateRestorePayload(payload, tenantId);
|
|
353
408
|
if (!validation.ok) {
|
|
354
409
|
throw new ConsoleDomainError("invalid_request", 400, validation.error);
|
|
355
410
|
}
|
|
356
411
|
try {
|
|
357
|
-
const
|
|
412
|
+
const scoped = restrictToSelection(validation.value, selection);
|
|
413
|
+
const compatible = await omitRowsForUnavailableProviders(this.options.db, scoped, tenantId);
|
|
358
414
|
const applied = await applyRestore(this.options.db, compatible, restoreOrder(), tenantId);
|
|
359
415
|
return { ...applied, warnings: compatible.warnings };
|
|
360
416
|
} catch (error) {
|
|
@@ -45,7 +45,17 @@ export interface SystemHealthResponse {
|
|
|
45
45
|
* gateway itself grew.
|
|
46
46
|
*/
|
|
47
47
|
database_in_process: boolean;
|
|
48
|
-
|
|
48
|
+
/**
|
|
49
|
+
* `null` when the platform does not report it — there is no `loadavg` on
|
|
50
|
+
* Windows and Node hands back a zeroed tuple rather than failing, so on
|
|
51
|
+
* Windows this is always `null` rather than a confident-looking `0`.
|
|
52
|
+
*
|
|
53
|
+
* Nullable rather than `NaN`: `NaN` is not a JSON value and serializes to
|
|
54
|
+
* `null` anyway, which means a client validating the field as a number
|
|
55
|
+
* rejects the *whole* health payload. One unavailable metric then reads as
|
|
56
|
+
* every metric being unavailable.
|
|
57
|
+
*/
|
|
58
|
+
cpu_percent: number | null;
|
|
49
59
|
cpu_cores: number;
|
|
50
60
|
pid: number;
|
|
51
61
|
platform: string;
|
|
@@ -330,12 +330,15 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
|
|
|
330
330
|
// syscall and Node reports a zeroed tuple rather than failing. Divided by
|
|
331
331
|
// cores that is a confident-looking 0.0% on a busy gateway, which is worse
|
|
332
332
|
// than saying nothing: an operator reads it as "idle" and stops looking.
|
|
333
|
-
// Reported as
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
333
|
+
// Reported as null instead of as a measurement.
|
|
334
|
+
//
|
|
335
|
+
// A real zero load is *not* the same thing and is reported as zero: an
|
|
336
|
+
// idle machine genuinely has nothing queued, and collapsing that into
|
|
337
|
+
// "unavailable" would hide a number the platform did supply.
|
|
338
|
+
const cpuPercent: number | null =
|
|
339
|
+
process.platform === "win32"
|
|
340
|
+
? null
|
|
341
|
+
: Math.round(((loadavg()[0] ?? 0) / Math.max(cpus().length, 1)) * 10000) / 100;
|
|
339
342
|
|
|
340
343
|
let databaseHealthy = true;
|
|
341
344
|
let total = 0;
|
|
@@ -23,7 +23,6 @@ import type { ShareFamilyStats, ShareStatsPort } from "./share-stats";
|
|
|
23
23
|
import { consoleSseResponse, createConsoleSseStream } from "../observability/sse";
|
|
24
24
|
import { GatewayError, publicGatewayErrorBody } from "../../protocol/gateway-error";
|
|
25
25
|
import {
|
|
26
|
-
hashShareToken,
|
|
27
26
|
type ShareLinkPolicy,
|
|
28
27
|
type ShareLinkRefusal,
|
|
29
28
|
type ShareLinkStore,
|
|
@@ -224,7 +223,7 @@ async function resolveFamilyStats(
|
|
|
224
223
|
stats: ShareStatsPort,
|
|
225
224
|
token: string,
|
|
226
225
|
): Promise<ShareFamilyStats | null> {
|
|
227
|
-
const resolved = await shareStore.resolveShareLink(
|
|
226
|
+
const resolved = await shareStore.resolveShareLink(token);
|
|
228
227
|
if (resolved === null) return null;
|
|
229
228
|
const templateId = resolved.key.id;
|
|
230
229
|
const tenantId = resolved.key.tenantId;
|
|
@@ -267,8 +266,7 @@ export function createShareRouter(options: ShareRouterOptions): Elysia {
|
|
|
267
266
|
if (typeof params.token !== "string") return notFound();
|
|
268
267
|
const token = params.token;
|
|
269
268
|
if (token.length < MIN_TOKEN_LENGTH) return notFound();
|
|
270
|
-
const
|
|
271
|
-
const outcome = await shareStore.resolveShareLinkOutcome(tokenHash);
|
|
269
|
+
const outcome = await shareStore.resolveShareLinkOutcome(token);
|
|
272
270
|
if (!outcome.ok) return notFound(outcome.refusal);
|
|
273
271
|
const resolved = outcome.resolution;
|
|
274
272
|
const row = resolved.key;
|
|
@@ -276,7 +274,7 @@ export function createShareRouter(options: ShareRouterOptions): Elysia {
|
|
|
276
274
|
const clientIpKey = clientIp === null ? undefined : canonicalClientIpKey(clientIp);
|
|
277
275
|
const models = await modelsForShare(db, row);
|
|
278
276
|
const modelInfo = await modelInfoForShare(db, row, models);
|
|
279
|
-
void shareStore.touchView(
|
|
277
|
+
void shareStore.touchView(token).catch(() => undefined);
|
|
280
278
|
const policy = {
|
|
281
279
|
name: row.name,
|
|
282
280
|
keyPrefix: row.keyPrefix,
|
|
@@ -330,7 +328,7 @@ export function createShareRouter(options: ShareRouterOptions): Elysia {
|
|
|
330
328
|
if (typeof params.token !== "string") return notFound();
|
|
331
329
|
const token = params.token;
|
|
332
330
|
if (token.length < MIN_TOKEN_LENGTH) return notFound();
|
|
333
|
-
const resolved = await shareStore.resolveShareLink(
|
|
331
|
+
const resolved = await shareStore.resolveShareLink(token);
|
|
334
332
|
const image = resolved?.key.sharePopupImage;
|
|
335
333
|
const mime = resolved?.key.sharePopupImageMime;
|
|
336
334
|
if (!image || !mime) return notFound();
|
|
@@ -355,8 +353,7 @@ export function createShareRouter(options: ShareRouterOptions): Elysia {
|
|
|
355
353
|
if (clientIpKey === undefined)
|
|
356
354
|
return json({ error: { code: "client_ip_invalid", message: "Client IP could not be normalized" } }, 400);
|
|
357
355
|
|
|
358
|
-
const
|
|
359
|
-
const outcome = await shareStore.resolveShareLinkOutcome(tokenHash);
|
|
356
|
+
const outcome = await shareStore.resolveShareLinkOutcome(token);
|
|
360
357
|
// A handoff link reveals an existing key; it never mints one.
|
|
361
358
|
if (!outcome.ok) return notFound(outcome.refusal);
|
|
362
359
|
const resolved = outcome.resolution;
|
|
@@ -374,7 +371,7 @@ export function createShareRouter(options: ShareRouterOptions): Elysia {
|
|
|
374
371
|
}
|
|
375
372
|
const template = resolved.key;
|
|
376
373
|
const generated = generateApiKeySecret(resolveShareKeyPrefix(template.keyPrefix ?? undefined));
|
|
377
|
-
const issued = await shareStore.issueSharedApiKey(
|
|
374
|
+
const issued = await shareStore.issueSharedApiKey(token, {
|
|
378
375
|
keyHash: generated.hash,
|
|
379
376
|
keyPrefix: generated.prefix,
|
|
380
377
|
clientIp,
|