@hot-updater/console 1.0.0-rc.0 → 1.0.0-rc.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/.output/nitro.json +1 -1
- package/.output/public/assets/HashValueDisplay-CB0Eskit.js +1 -0
- package/.output/public/assets/InsightsPageHeader-Car5LNiZ.js +1 -0
- package/.output/public/assets/api-keys-BzKocqRy.js +1 -0
- package/.output/public/assets/api-keys-api-BHcODB7q.js +1 -0
- package/.output/public/assets/card-Dhr_eFYj.js +1 -0
- package/.output/public/assets/{chart-no-axes-combined-FaYMGmQS.js → chart-no-axes-combined-Dy4WJv2U.js} +1 -1
- package/.output/public/assets/composite-Bp98A0Ld.js +1 -0
- package/.output/public/assets/createServerFn-CA8fs7QD.js +3 -0
- package/.output/public/assets/dialog-CUTrcPlB.js +1 -0
- package/.output/public/assets/{dist-DspNKfMj.js → dist-C_NydL0e.js} +1 -1
- package/.output/public/assets/index-DdAz-CXv.js +10 -0
- package/.output/public/assets/input-group-cSOG4wh_.js +1 -0
- package/.output/public/assets/insights-C0PBSZ-n.js +1 -0
- package/.output/public/assets/installations-CE33wgbY.js +2 -0
- package/.output/public/assets/installations-CStZja8x.js +1 -0
- package/.output/public/assets/lazyRouteComponent-Ccp3bwBY.js +1 -0
- package/.output/public/assets/link-CI8idAUi.js +1 -0
- package/.output/public/assets/loader-circle-BoxLrsOu.js +1 -0
- package/.output/public/assets/refresh-cw-Bqc72UZG.js +1 -0
- package/.output/public/assets/{routes-DcQnVajO.js → routes-DGVXdzpw.js} +2 -2
- package/.output/public/assets/routes-DTlwOr8I.js +1 -0
- package/.output/public/assets/{shield-check-DE_hVuHu.js → shield-check-uK3MHWJZ.js} +1 -1
- package/.output/public/assets/signing-CMf4aqDO.js +1 -0
- package/.output/public/assets/styles-BCvbtKsm.css +2 -0
- package/.output/public/assets/table-2vcV89QB.js +1 -0
- package/.output/public/assets/useCompositeListItem-CHLITE_4.js +1 -0
- package/.output/public/assets/x-DCd0j8c6.js +1 -0
- package/.output/server/{__23tanstack-start-server-fn-resolver-DBP9TQ7K.mjs → __23tanstack-start-server-fn-resolver-B7ioJhtE.mjs} +44 -52
- package/.output/server/_libs/@base-ui/react+[...].mjs +1906 -4040
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +52 -4
- package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +2 -2
- package/.output/server/_libs/lucide-react.mjs +36 -3
- package/.output/server/_libs/next-themes.mjs +2 -2
- package/.output/server/_libs/sonner.mjs +2 -2
- package/.output/server/_libs/tanstack__react-query.mjs +2 -2
- package/.output/server/_libs/verkit.mjs +20 -15
- package/.output/server/_runtime.mjs +1 -10
- package/.output/server/_ssr/HashValueDisplay-DwpGLfrl.mjs +55 -0
- package/.output/server/_ssr/InsightsPageHeader-Df4Brj8u.mjs +484 -0
- package/.output/server/_ssr/{api-keys-BnM_o2nS.mjs → api-keys-CZeTPQys.mjs} +11 -13
- package/.output/server/_ssr/{api-keys-api-CIywsSF5.mjs → api-keys-api-BUPQVSpk.mjs} +2 -2
- package/.output/server/_ssr/{api-keys-rpc-BSp1wkIV.mjs → api-keys-rpc-Cs7tS5_j.mjs} +5 -5
- package/.output/server/_ssr/{api-rpc-cG5wd_tO.mjs → api-rpc-tAupfGQm.mjs} +73 -142
- package/.output/server/_ssr/{auth-rpc-BrJ6obHJ.mjs → auth-rpc-DUaeOWKK.mjs} +4 -4
- package/.output/server/_ssr/{auth.server-DwKoZoQM.mjs → auth.server-DW71WzsD.mjs} +2 -5
- package/.output/server/_ssr/{bundle-signing-rpc-COoQhW8E.mjs → bundle-signing-rpc-BhnBw0-d.mjs} +2 -2
- package/.output/server/_ssr/bundle-signing.server-C4NW5mWx.mjs +12 -0
- package/.output/server/_ssr/{alert-Dim3MGY5.mjs → card-BRiPmi0C.mjs} +61 -39
- package/.output/server/_ssr/{config.server-DEiLhKmy.mjs → config.server-afDuAwaG.mjs} +15 -8
- package/.output/server/_ssr/{createSsrRpc-9P0S0lJ3.mjs → createSsrRpc-DjWNJ0uc.mjs} +6 -6
- package/.output/server/_ssr/databaseJsonValue-d6I3nAS4.mjs +45 -0
- package/.output/server/_ssr/{databaseRows-CwbNwYSl.mjs → databaseRows-5bCRTxm-.mjs} +4 -44
- package/.output/server/_ssr/{deleteBundle-Cu_XOMt0.mjs → deleteBundle-BTGwBgzx.mjs} +2 -2
- package/.output/server/_ssr/{dialog-CQuRMNZ4.mjs → dialog-DS-nw-pA.mjs} +2 -2
- package/.output/server/_ssr/{dist-BVSLTpoP.mjs → dist-CyzPaI5v.mjs} +2 -2
- package/.output/server/_ssr/{getBundleChildren-BcRMFoDF.mjs → getBundleChildren-C0vh4ZvE.mjs} +2 -2
- package/.output/server/_ssr/input-group-BpsnmvZw.mjs +70 -0
- package/.output/server/_ssr/insights-BdS364Xu.mjs +460 -0
- package/.output/server/_ssr/insights-rpc-38DujK0G.mjs +57 -0
- package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +303 -0
- package/.output/server/_ssr/installations-CtA59MQG.mjs +29 -0
- package/.output/server/_ssr/installations-DiVKEF5T.mjs +627 -0
- package/.output/server/_ssr/{router-C-KUBfOi.mjs → router-B9wG_9f8.mjs} +37 -46
- package/.output/server/_ssr/{routes-Z7TwC6nC.mjs → routes-CKrrS2q2.mjs} +3 -3
- package/.output/server/_ssr/{routes-BM4ht9jb.mjs → routes-D7Yh_jfj.mjs} +402 -390
- package/.output/server/_ssr/runtime.server-DacU1TBV.mjs +438 -0
- package/.output/server/_ssr/{server-BjzmvXnO.mjs → server-D_C2rMzf.mjs} +5 -5
- package/.output/server/_ssr/{signing-k8GxyBDF.mjs → signing-Dz1WkZOf.mjs} +16 -85
- package/.output/server/_ssr/ssr.mjs +1 -1
- package/.output/server/_ssr/{field-DRIXKjZG.mjs → table-CnZmon_Z.mjs} +51 -5
- package/.output/server/_ssr/{uuidv7-BbJ-9D_I.mjs → uuidv7-D0QFkPL_.mjs} +3 -3
- package/.output/server/_tanstack-start-manifest_v-2MjTyxil.mjs +97 -0
- package/.output/server/index.mjs +168 -203
- package/lib/index.d.mts +4 -4
- package/lib/vite-internal.mjs +1 -1
- package/lib/vite.d.mts +0 -1
- package/package.json +8 -8
- package/src/components/AppSidebar.tsx +12 -16
- package/src/components/ConsoleAccessPage.tsx +1 -1
- package/src/components/HashValueDisplay.tsx +11 -2
- package/src/components/features/api-keys/ApiKeysPage.tsx +1 -1
- package/src/components/features/bundles/BundleChildrenPanel.tsx +7 -6
- package/src/components/features/bundles/BundleMetadata.tsx +1 -20
- package/src/components/features/channels/ChannelManagementDialog.tsx +1 -1
- package/src/components/features/insights/EventDetails.tsx +151 -0
- package/src/components/features/insights/EventHistoryCard.tsx +245 -0
- package/src/components/features/insights/EventHistoryList.tsx +63 -0
- package/src/components/features/insights/InsightsBundleReports.tsx +51 -0
- package/src/components/features/insights/InsightsControls.tsx +121 -0
- package/src/components/features/{analytics/AnalyticsErrorAlert.tsx → insights/InsightsErrorAlert.tsx} +6 -6
- package/src/components/features/insights/InsightsOverview.tsx +203 -0
- package/src/components/features/insights/InsightsPageHeader.tsx +48 -0
- package/src/components/features/insights/InsightsPagination.tsx +52 -0
- package/src/components/features/insights/InstallationHistoryCard.tsx +250 -0
- package/src/components/features/insights/InstallationMatchesCard.tsx +178 -0
- package/src/components/features/insights/InstallationPageHeader.tsx +90 -0
- package/src/components/features/releases/ReleaseEditorSheet.tsx +24 -24
- package/src/components/features/signing/BundleSigningPage.tsx +18 -126
- package/src/components/ui/badge.tsx +2 -0
- package/src/components/ui/combobox.tsx +1 -0
- package/src/components/ui/skeleton.tsx +4 -1
- package/src/components/ui/tooltip.tsx +1 -1
- package/src/index.ts +7 -3
- package/src/lib/api-rpc.ts +19 -90
- package/src/lib/api.ts +0 -99
- package/src/lib/insights-api.ts +100 -0
- package/src/lib/insights-rpc.ts +88 -0
- package/src/lib/insights-view.ts +15 -0
- package/src/lib/public-bundle-error.ts +19 -0
- package/src/lib/server/bundle-signing.server.ts +5 -82
- package/src/lib/server/console-runtime.server.ts +0 -9
- package/src/lib/server/releaseReachability.ts +1 -4
- package/src/lib/server/runtime.server.ts +6 -142
- package/src/routeTree.gen.ts +21 -21
- package/src/router.tsx +3 -0
- package/src/routes/-installations-search.ts +25 -12
- package/src/routes/__root.tsx +6 -16
- package/src/routes/api-keys.tsx +1 -1
- package/src/routes/index.tsx +21 -81
- package/src/routes/insights.tsx +84 -0
- package/src/routes/installations.tsx +294 -142
- package/src/styles.css +6 -0
- package/.output/public/assets/AnalyticsCapabilityContext-BCke57-7.js +0 -1
- package/.output/public/assets/BundleActivityChart-CnsBe_1J.js +0 -42
- package/.output/public/assets/BundleIdDisplay-Dky75zLU.js +0 -1
- package/.output/public/assets/alert-C-1KvDW8.js +0 -1
- package/.output/public/assets/analytics-Bqz0c-BT.js +0 -1
- package/.output/public/assets/analytics-api-CJdrTLLO.js +0 -1
- package/.output/public/assets/api-B_Rh-uzC.js +0 -1
- package/.output/public/assets/api-keys-BurCFhzH.js +0 -1
- package/.output/public/assets/api-keys-api-BEDJdQg3.js +0 -1
- package/.output/public/assets/badge-Bf0fZ-Gx.js +0 -1
- package/.output/public/assets/createServerFn-gM2-LFBy.js +0 -3
- package/.output/public/assets/dialog-BS60BRr2.js +0 -1
- package/.output/public/assets/download-O-yNBc9H.js +0 -1
- package/.output/public/assets/field-Buq148-A.js +0 -1
- package/.output/public/assets/index-kFhd2S78.js +0 -10
- package/.output/public/assets/installations-BrQ-od41.js +0 -2
- package/.output/public/assets/installations-CGlZ3ROz.js +0 -1
- package/.output/public/assets/lazyRouteComponent-rPzgA7S-.js +0 -1
- package/.output/public/assets/loader-circle-DCYF-wJl.js +0 -1
- package/.output/public/assets/refresh-cw-n8XYbPPj.js +0 -1
- package/.output/public/assets/routes-CrdokJ80.js +0 -1
- package/.output/public/assets/search-DLhbWw3W.js +0 -1
- package/.output/public/assets/signing-HRexfjsH.js +0 -1
- package/.output/public/assets/styles-eB4EtvFS.css +0 -2
- package/.output/public/assets/table-D8CLieHX.js +0 -1
- package/.output/public/assets/textarea-YJLTVAoq.js +0 -1
- package/.output/public/assets/useMutation-JsGVjQMY.js +0 -1
- package/.output/public/assets/useOpenInteractionType-4cRD5xO0.js +0 -1
- package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +0 -1554
- package/.output/server/_libs/d3-array.mjs +0 -231
- package/.output/server/_libs/d3-color.mjs +0 -330
- package/.output/server/_libs/d3-format.mjs +0 -252
- package/.output/server/_libs/d3-interpolate.mjs +0 -207
- package/.output/server/_libs/d3-path.mjs +0 -77
- package/.output/server/_libs/d3-scale+[...].mjs +0 -1907
- package/.output/server/_libs/d3-shape.mjs +0 -818
- package/.output/server/_libs/decimal.js-light.mjs +0 -884
- package/.output/server/_libs/es-toolkit.mjs +0 -1134
- package/.output/server/_libs/eventemitter3.mjs +0 -236
- package/.output/server/_libs/immer.mjs +0 -532
- package/.output/server/_libs/react-redux+[...].mjs +0 -205
- package/.output/server/_libs/recharts+[...].mjs +0 -14117
- package/.output/server/_ssr/AnalyticsCapabilityContext-ui4DX8GO.mjs +0 -15
- package/.output/server/_ssr/BundleActivityChart-BiB9ZORH.mjs +0 -288
- package/.output/server/_ssr/BundleIdDisplay-Czk6iR6p.mjs +0 -13
- package/.output/server/_ssr/analytics-DtMTn8fn.mjs +0 -858
- package/.output/server/_ssr/analytics-api-Cg1M5v_H.mjs +0 -71
- package/.output/server/_ssr/analytics-input-fRRRn0uA.mjs +0 -96
- package/.output/server/_ssr/analytics-rpc-DY5uXvHT.mjs +0 -155
- package/.output/server/_ssr/api-BTSv1tZb.mjs +0 -282
- package/.output/server/_ssr/badge-DCxbEeof.mjs +0 -26
- package/.output/server/_ssr/bundle-signing.server-D5u8KkMp.mjs +0 -51
- package/.output/server/_ssr/installations-B-azgAvX.mjs +0 -19
- package/.output/server/_ssr/installations-Ctvzp2JH.mjs +0 -535
- package/.output/server/_ssr/runtime.server-awTLiiXW.mjs +0 -527
- package/.output/server/_ssr/table-v1yCvYo7.mjs +0 -53
- package/.output/server/_ssr/textarea-DYHnskhb.mjs +0 -14
- package/.output/server/_tanstack-start-manifest_v-CH4StzJu.mjs +0 -108
- package/src/components/features/analytics/ActivityChart.tsx +0 -150
- package/src/components/features/analytics/AnalyticsCapabilityContext.tsx +0 -24
- package/src/components/features/analytics/AnalyticsControls.tsx +0 -115
- package/src/components/features/analytics/AnalyticsOverview.tsx +0 -208
- package/src/components/features/analytics/BundleDistribution.tsx +0 -133
- package/src/components/features/analytics/BundleSelector.tsx +0 -114
- package/src/components/features/analytics/InstallationHistoryCard.tsx +0 -248
- package/src/components/features/analytics/InstallationMatchesCard.tsx +0 -132
- package/src/components/features/analytics/InstallationPageHeader.tsx +0 -138
- package/src/components/features/analytics/InstallationPagination.tsx +0 -48
- package/src/components/features/analytics/InstallationSearch.tsx +0 -230
- package/src/components/features/analytics/UpdateOutcomes.tsx +0 -147
- package/src/components/features/bundles/BundleActivityChart.tsx +0 -193
- package/src/components/features/bundles/BundleAnalyticsSummary.tsx +0 -123
- package/src/lib/analytics-api.ts +0 -145
- package/src/lib/analytics-error.ts +0 -19
- package/src/lib/analytics-input.ts +0 -187
- package/src/lib/analytics-overview.ts +0 -170
- package/src/lib/analytics-rpc.ts +0 -218
- package/src/lib/server/releaseActivity.ts +0 -27
- package/src/routes/analytics.tsx +0 -139
- /package/.output/public/assets/{preload-helper-TKOBYGYD.js → preload-helper-CQ36nxok.js} +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ReportingOverview,
|
|
3
|
+
InsightsScope,
|
|
4
|
+
InsightsEventPageInput,
|
|
5
|
+
InsightsInstallationEventPageInput,
|
|
6
|
+
ActiveInstallationWindow,
|
|
7
|
+
InsightsProvider,
|
|
8
|
+
} from "@hot-updater/server";
|
|
9
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
InsightsInstallationViewRow,
|
|
13
|
+
InsightsViewPage,
|
|
14
|
+
} from "./insights-view";
|
|
15
|
+
|
|
16
|
+
export type InsightsWindow = ActiveInstallationWindow;
|
|
17
|
+
export type ReportingInstallations = ReportingOverview;
|
|
18
|
+
export type InsightsOverviewInput = InsightsScope & {
|
|
19
|
+
readonly window: InsightsWindow;
|
|
20
|
+
readonly bundleId?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type EventsPageInput = InsightsEventPageInput;
|
|
24
|
+
|
|
25
|
+
type InstallationEventsPageInput = InsightsInstallationEventPageInput;
|
|
26
|
+
|
|
27
|
+
type InstallationsPageInput = {
|
|
28
|
+
readonly cursor?: string;
|
|
29
|
+
readonly identity: string;
|
|
30
|
+
readonly limit: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const runtime = async (): Promise<InsightsProvider> => {
|
|
34
|
+
const { prepareConfig } = await import("./server/config.server");
|
|
35
|
+
const { hotUpdater } = await prepareConfig();
|
|
36
|
+
return hotUpdater;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const readOverview = (input: InsightsOverviewInput) => input;
|
|
40
|
+
const readEventsPage = (input: EventsPageInput) => input;
|
|
41
|
+
const readInstallationEventsPage = (input: InstallationEventsPageInput) =>
|
|
42
|
+
input;
|
|
43
|
+
const readInstallationPage = (input: InstallationsPageInput) => input;
|
|
44
|
+
const readInstallId = (input: { readonly installId: string }) => input;
|
|
45
|
+
|
|
46
|
+
export const getReportingInstallationsRpc = createServerFn({ method: "GET" })
|
|
47
|
+
.validator(readOverview)
|
|
48
|
+
.handler(async ({ data }) => (await runtime()).getReportingOverview(data));
|
|
49
|
+
|
|
50
|
+
export const listInsightsEventsRpc = createServerFn({ method: "GET" })
|
|
51
|
+
.validator(readEventsPage)
|
|
52
|
+
.handler(async ({ data }) => (await runtime()).listEvents(data));
|
|
53
|
+
|
|
54
|
+
export const listInsightsInstallationEventsRpc = createServerFn({
|
|
55
|
+
method: "GET",
|
|
56
|
+
})
|
|
57
|
+
.validator(readInstallationEventsPage)
|
|
58
|
+
.handler(async ({ data }) => (await runtime()).listInstallationEvents(data));
|
|
59
|
+
|
|
60
|
+
export const getInsightsInstallationRpc = createServerFn({ method: "GET" })
|
|
61
|
+
.validator(readInstallId)
|
|
62
|
+
.handler(async ({ data }) => (await runtime()).getInstallation(data));
|
|
63
|
+
|
|
64
|
+
export const findInsightsInstallationsRpc = createServerFn({ method: "GET" })
|
|
65
|
+
.validator(readInstallationPage)
|
|
66
|
+
.handler(async ({ data }) => {
|
|
67
|
+
const insights = await runtime();
|
|
68
|
+
const install = data.cursor
|
|
69
|
+
? null
|
|
70
|
+
: await insights.getInstallation({ installId: data.identity });
|
|
71
|
+
const matches = await insights.pageInstallationsByCurrentUserId({
|
|
72
|
+
cursor: data.cursor,
|
|
73
|
+
limit: Math.max(1, data.limit - (install === null ? 0 : 1)),
|
|
74
|
+
userId: data.identity,
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
data:
|
|
78
|
+
install === null
|
|
79
|
+
? matches.data
|
|
80
|
+
: [
|
|
81
|
+
install,
|
|
82
|
+
...matches.data.filter(
|
|
83
|
+
({ installId }) => installId !== install.installId,
|
|
84
|
+
),
|
|
85
|
+
],
|
|
86
|
+
nextCursor: matches.nextCursor,
|
|
87
|
+
} satisfies InsightsViewPage<InsightsInstallationViewRow>;
|
|
88
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { EventHistoryRow, InstallationRow } from "@hot-updater/server";
|
|
2
|
+
|
|
3
|
+
export type InsightsEventRow = EventHistoryRow;
|
|
4
|
+
export type InsightsInstallationViewRow = InstallationRow;
|
|
5
|
+
|
|
6
|
+
export type InsightsViewPage<TRow> = {
|
|
7
|
+
readonly data: readonly TRow[];
|
|
8
|
+
readonly nextCursor: string | null;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const outcomeLabels = {
|
|
12
|
+
applied: "Applied reports",
|
|
13
|
+
recovered: "Recovered-from reports",
|
|
14
|
+
adopted: "Adopted reports",
|
|
15
|
+
} as const;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ReleaseManagementError } from "@hot-updater/plugin-core";
|
|
2
|
+
|
|
3
|
+
export const withPublicBundleMutationErrors = async <T>(
|
|
4
|
+
action: () => Promise<T>,
|
|
5
|
+
): Promise<T> => {
|
|
6
|
+
try {
|
|
7
|
+
return await action();
|
|
8
|
+
} catch (error) {
|
|
9
|
+
if (error instanceof ReleaseManagementError) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
error.message
|
|
12
|
+
.replace(/\bRelease\b/g, "Bundle")
|
|
13
|
+
.replace("Bundle Bundle", "deployed Bundle"),
|
|
14
|
+
{ cause: error },
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
@@ -1,100 +1,23 @@
|
|
|
1
|
-
import { createHash, createPublicKey } from "node:crypto";
|
|
2
|
-
import { readFile, stat } from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
1
|
import type { ConsoleSigningConfig } from "../../index";
|
|
6
2
|
|
|
7
|
-
const MAX_PUBLIC_KEY_BYTES = 16 * 1024;
|
|
8
3
|
const DEFAULT_PROVIDER = "Configured provider";
|
|
9
|
-
const PUBLIC_KEY_HEADER = "-----BEGIN PUBLIC KEY-----";
|
|
10
|
-
const PUBLIC_KEY_FOOTER = "-----END PUBLIC KEY-----";
|
|
11
4
|
|
|
12
5
|
export type BundleSigningInspection =
|
|
13
6
|
| { readonly status: "disabled" }
|
|
14
|
-
| {
|
|
15
|
-
readonly status: "misconfigured";
|
|
16
|
-
readonly provider: string;
|
|
17
|
-
readonly message: string;
|
|
18
|
-
}
|
|
19
7
|
| {
|
|
20
8
|
readonly status: "enabled";
|
|
21
9
|
readonly provider: string;
|
|
22
10
|
readonly algorithm: "RSA-SHA256";
|
|
23
|
-
readonly fingerprint: string;
|
|
24
|
-
readonly publicKey: string;
|
|
25
11
|
};
|
|
26
12
|
|
|
27
|
-
const misconfigured = (
|
|
28
|
-
provider: string,
|
|
29
|
-
message: string,
|
|
30
|
-
): BundleSigningInspection => ({ message, provider, status: "misconfigured" });
|
|
31
|
-
|
|
32
|
-
const isSpkiPublicKeyPem = (value: string): boolean => {
|
|
33
|
-
const trimmed = value.trim();
|
|
34
|
-
return (
|
|
35
|
-
trimmed.startsWith(PUBLIC_KEY_HEADER) &&
|
|
36
|
-
trimmed.endsWith(PUBLIC_KEY_FOOTER) &&
|
|
37
|
-
!trimmed.includes("PRIVATE KEY")
|
|
38
|
-
);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
13
|
export const inspectBundleSigning = async (
|
|
42
14
|
signing: ConsoleSigningConfig | undefined,
|
|
43
15
|
): Promise<BundleSigningInspection> => {
|
|
44
16
|
if (!signing?.enabled) return { status: "disabled" };
|
|
45
17
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
const publicKeyPath = path.isAbsolute(signing.publicKeyPath)
|
|
53
|
-
? signing.publicKeyPath
|
|
54
|
-
: path.resolve(process.cwd(), signing.publicKeyPath);
|
|
55
|
-
const publicKeyStat = await stat(publicKeyPath);
|
|
56
|
-
if (!publicKeyStat.isFile() || publicKeyStat.size > MAX_PUBLIC_KEY_BYTES) {
|
|
57
|
-
return misconfigured(
|
|
58
|
-
provider,
|
|
59
|
-
"The configured public key could not be loaded.",
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const publicKeyPem = await readFile(publicKeyPath, "utf8");
|
|
64
|
-
if (!isSpkiPublicKeyPem(publicKeyPem)) {
|
|
65
|
-
return misconfigured(
|
|
66
|
-
provider,
|
|
67
|
-
"The configured public key is not a valid SPKI public key.",
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const publicKey = createPublicKey(publicKeyPem);
|
|
72
|
-
if (publicKey.asymmetricKeyType !== "rsa") {
|
|
73
|
-
return misconfigured(
|
|
74
|
-
provider,
|
|
75
|
-
"The configured public key is not an RSA public key.",
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
if ((publicKey.asymmetricKeyDetails?.modulusLength ?? 0) < 2048) {
|
|
79
|
-
return misconfigured(
|
|
80
|
-
provider,
|
|
81
|
-
"The configured RSA public key must be at least 2048 bits.",
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const canonicalDer = publicKey.export({ format: "der", type: "spki" });
|
|
86
|
-
const canonicalPem = publicKey.export({ format: "pem", type: "spki" });
|
|
87
|
-
return {
|
|
88
|
-
algorithm: "RSA-SHA256",
|
|
89
|
-
fingerprint: createHash("sha256").update(canonicalDer).digest("hex"),
|
|
90
|
-
provider,
|
|
91
|
-
publicKey: canonicalPem.toString().trim(),
|
|
92
|
-
status: "enabled",
|
|
93
|
-
};
|
|
94
|
-
} catch {
|
|
95
|
-
return misconfigured(
|
|
96
|
-
provider,
|
|
97
|
-
"The configured public key could not be loaded.",
|
|
98
|
-
);
|
|
99
|
-
}
|
|
18
|
+
return {
|
|
19
|
+
algorithm: "RSA-SHA256",
|
|
20
|
+
provider: signing.provider ?? DEFAULT_PROVIDER,
|
|
21
|
+
status: "enabled",
|
|
22
|
+
};
|
|
100
23
|
};
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
|
|
3
1
|
import type {
|
|
4
2
|
ConsoleAuthAdapter,
|
|
5
3
|
ConsoleSigningConfig,
|
|
@@ -34,16 +32,9 @@ export const sanitizeConsoleSigningConfig = (
|
|
|
34
32
|
)
|
|
35
33
|
return undefined;
|
|
36
34
|
|
|
37
|
-
// Console never reads private material, even for legacy local configs.
|
|
38
|
-
const publicKeyPath =
|
|
39
|
-
readString(signing, "publicKeyPath") ??
|
|
40
|
-
(privateKeyPath
|
|
41
|
-
? path.join(path.dirname(privateKeyPath), "public-key.pem")
|
|
42
|
-
: undefined);
|
|
43
35
|
return {
|
|
44
36
|
enabled: true,
|
|
45
37
|
provider: getProviderName(signing),
|
|
46
|
-
...(publicKeyPath === undefined ? {} : { publicKeyPath }),
|
|
47
38
|
};
|
|
48
39
|
};
|
|
49
40
|
|
|
@@ -7,15 +7,12 @@ import type {
|
|
|
7
7
|
ReleaseCatalogModel,
|
|
8
8
|
ReleaseRow,
|
|
9
9
|
} from "@hot-updater/plugin-core";
|
|
10
|
-
import type { BundleEventSummary } from "@hot-updater/server";
|
|
11
10
|
|
|
12
11
|
export type ReleaseReachabilityRow = ReleaseRow & {
|
|
13
12
|
readonly currentlyUnreachable: boolean;
|
|
14
13
|
};
|
|
15
14
|
|
|
16
|
-
export type ReleaseListRow = ReleaseReachabilityRow
|
|
17
|
-
readonly activity30d: BundleEventSummary | null;
|
|
18
|
-
};
|
|
15
|
+
export type ReleaseListRow = ReleaseReachabilityRow;
|
|
19
16
|
|
|
20
17
|
const NON_TARGETED_COHORT = "release-catalog-non-targeted-cohort";
|
|
21
18
|
|
|
@@ -1,47 +1,17 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
AnalyticsModel,
|
|
3
2
|
ApiKeyModel,
|
|
4
3
|
BundleRepository,
|
|
4
|
+
DatabasePlugin,
|
|
5
5
|
} from "@hot-updater/plugin-core";
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
type
|
|
9
|
-
createAnalyticsProvider,
|
|
10
|
-
type InstallationHistoryRow,
|
|
11
|
-
type InstallationSearchRow,
|
|
12
|
-
type OffsetPaginationResult,
|
|
7
|
+
createInsightsProvider,
|
|
8
|
+
type InsightsProvider,
|
|
13
9
|
} from "@hot-updater/server";
|
|
14
10
|
|
|
15
|
-
import {
|
|
16
|
-
parseActiveInstallationInput,
|
|
17
|
-
parseBundleEventAnalyticsInput,
|
|
18
|
-
parseBundleEventSummaryInput,
|
|
19
|
-
parseInstallationHistoryInput,
|
|
20
|
-
parseSearchInstallationsInput,
|
|
21
|
-
} from "../analytics-input";
|
|
22
|
-
|
|
23
|
-
export type InstallationSearchResult =
|
|
24
|
-
OffsetPaginationResult<InstallationSearchRow>;
|
|
25
|
-
export type InstallationHistoryResult =
|
|
26
|
-
OffsetPaginationResult<InstallationHistoryRow>;
|
|
27
|
-
|
|
28
11
|
export function createRuntimeHotUpdater(config: {
|
|
29
|
-
readonly database:
|
|
30
|
-
}):
|
|
31
|
-
|
|
32
|
-
const analytics: unknown =
|
|
33
|
-
typeof models === "object" && models !== null
|
|
34
|
-
? Reflect.get(models, "analytics")
|
|
35
|
-
: undefined;
|
|
36
|
-
if (
|
|
37
|
-
typeof analytics !== "object" ||
|
|
38
|
-
analytics === null ||
|
|
39
|
-
typeof Reflect.get(analytics, "append") !== "function" ||
|
|
40
|
-
typeof Reflect.get(analytics, "scan") !== "function"
|
|
41
|
-
) {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
return createAnalyticsProvider(analytics as AnalyticsModel);
|
|
12
|
+
readonly database: DatabasePlugin;
|
|
13
|
+
}): InsightsProvider {
|
|
14
|
+
return createInsightsProvider(config.database.models.insights);
|
|
45
15
|
}
|
|
46
16
|
|
|
47
17
|
export function createApiKeyStore(config: {
|
|
@@ -64,109 +34,3 @@ export function createApiKeyStore(config: {
|
|
|
64
34
|
}
|
|
65
35
|
return apiKeys as ApiKeyModel;
|
|
66
36
|
}
|
|
67
|
-
|
|
68
|
-
const providerMethods = [
|
|
69
|
-
"appendBundleEvent",
|
|
70
|
-
"getBundleEventSummary",
|
|
71
|
-
"getBundleEventSummaries",
|
|
72
|
-
"getBundleEventAnalytics",
|
|
73
|
-
"getBundleEventOverview",
|
|
74
|
-
"getActiveInstallationOverview",
|
|
75
|
-
"searchInstallations",
|
|
76
|
-
"getInstallationHistory",
|
|
77
|
-
] as const;
|
|
78
|
-
|
|
79
|
-
const parseAnalyticsProvider = (value: unknown): AnalyticsProvider | null => {
|
|
80
|
-
if (
|
|
81
|
-
typeof value !== "object" ||
|
|
82
|
-
value === null ||
|
|
83
|
-
Reflect.get(value, "mode") !== "bounded" ||
|
|
84
|
-
typeof Reflect.get(value, "maxMatchingRows") !== "number" ||
|
|
85
|
-
!providerMethods.every(
|
|
86
|
-
(method) => typeof Reflect.get(value, method) === "function",
|
|
87
|
-
)
|
|
88
|
-
) {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
return value as AnalyticsProvider;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
export const getAnalyticsCapability = async (provider: unknown) => {
|
|
95
|
-
const parsed = parseAnalyticsProvider(provider);
|
|
96
|
-
return parsed === null
|
|
97
|
-
? ({
|
|
98
|
-
analytics: false,
|
|
99
|
-
analyticsQueries: false,
|
|
100
|
-
eventIngestion: false,
|
|
101
|
-
} as const)
|
|
102
|
-
: ({
|
|
103
|
-
analytics: true,
|
|
104
|
-
analyticsQueries: true,
|
|
105
|
-
eventIngestion: true,
|
|
106
|
-
maxMatchingRows: parsed.maxMatchingRows,
|
|
107
|
-
mode: "bounded",
|
|
108
|
-
} as const);
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const requireAnalyticsSupport = async (
|
|
112
|
-
provider: unknown,
|
|
113
|
-
): Promise<AnalyticsProvider> => {
|
|
114
|
-
const parsedProvider = parseAnalyticsProvider(provider);
|
|
115
|
-
if (parsedProvider === null) {
|
|
116
|
-
throw new Error(
|
|
117
|
-
"Analytics are not supported by the configured database plugin.",
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
return parsedProvider;
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
export async function getBundleEventSummary(provider: unknown, input: unknown) {
|
|
124
|
-
const { bundleId } = parseBundleEventSummaryInput(input);
|
|
125
|
-
return (await requireAnalyticsSupport(provider)).getBundleEventSummary(
|
|
126
|
-
bundleId,
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export async function getActiveInstallationOverview(
|
|
131
|
-
provider: unknown,
|
|
132
|
-
input: unknown,
|
|
133
|
-
) {
|
|
134
|
-
const parsed: ActiveInstallationInput = parseActiveInstallationInput(input);
|
|
135
|
-
return (
|
|
136
|
-
await requireAnalyticsSupport(provider)
|
|
137
|
-
).getActiveInstallationOverview(parsed);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export async function getBundleEventAnalytics(
|
|
141
|
-
provider: unknown,
|
|
142
|
-
input: unknown,
|
|
143
|
-
) {
|
|
144
|
-
const parsed = parseBundleEventAnalyticsInput(input);
|
|
145
|
-
return (await requireAnalyticsSupport(provider)).getBundleEventAnalytics(
|
|
146
|
-
parsed.bundleId,
|
|
147
|
-
parsed.window,
|
|
148
|
-
parsed.limit ?? 50,
|
|
149
|
-
parsed.offset ?? 0,
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export async function searchInstallations(provider: unknown, input: unknown) {
|
|
154
|
-
const parsed = parseSearchInstallationsInput(input);
|
|
155
|
-
return (await requireAnalyticsSupport(provider)).searchInstallations(
|
|
156
|
-
parsed.query,
|
|
157
|
-
parsed.limit ?? 50,
|
|
158
|
-
parsed.offset ?? 0,
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export async function getInstallationHistory(
|
|
163
|
-
provider: unknown,
|
|
164
|
-
input: unknown,
|
|
165
|
-
) {
|
|
166
|
-
const parsed = parseInstallationHistoryInput(input);
|
|
167
|
-
return (await requireAnalyticsSupport(provider)).getInstallationHistory(
|
|
168
|
-
parsed.installId,
|
|
169
|
-
parsed.limit ?? 50,
|
|
170
|
-
parsed.offset ?? 0,
|
|
171
|
-
);
|
|
172
|
-
}
|
package/src/routeTree.gen.ts
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
import { Route as rootRouteImport } from './routes/__root'
|
|
12
12
|
import { Route as SigningRouteImport } from './routes/signing'
|
|
13
13
|
import { Route as InstallationsRouteImport } from './routes/installations'
|
|
14
|
+
import { Route as InsightsRouteImport } from './routes/insights'
|
|
14
15
|
import { Route as ApiKeysRouteImport } from './routes/api-keys'
|
|
15
|
-
import { Route as AnalyticsRouteImport } from './routes/analytics'
|
|
16
16
|
import { Route as IndexRouteImport } from './routes/index'
|
|
17
17
|
import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$'
|
|
18
18
|
import { Route as ApiBundlesBundleIdDownloadRouteImport } from './routes/api/bundles/$bundleId/download'
|
|
@@ -27,16 +27,16 @@ const InstallationsRoute = InstallationsRouteImport.update({
|
|
|
27
27
|
path: '/installations',
|
|
28
28
|
getParentRoute: () => rootRouteImport,
|
|
29
29
|
} as any)
|
|
30
|
+
const InsightsRoute = InsightsRouteImport.update({
|
|
31
|
+
id: '/insights',
|
|
32
|
+
path: '/insights',
|
|
33
|
+
getParentRoute: () => rootRouteImport,
|
|
34
|
+
} as any)
|
|
30
35
|
const ApiKeysRoute = ApiKeysRouteImport.update({
|
|
31
36
|
id: '/api-keys',
|
|
32
37
|
path: '/api-keys',
|
|
33
38
|
getParentRoute: () => rootRouteImport,
|
|
34
39
|
} as any)
|
|
35
|
-
const AnalyticsRoute = AnalyticsRouteImport.update({
|
|
36
|
-
id: '/analytics',
|
|
37
|
-
path: '/analytics',
|
|
38
|
-
getParentRoute: () => rootRouteImport,
|
|
39
|
-
} as any)
|
|
40
40
|
const IndexRoute = IndexRouteImport.update({
|
|
41
41
|
id: '/',
|
|
42
42
|
path: '/',
|
|
@@ -56,8 +56,8 @@ const ApiBundlesBundleIdDownloadRoute =
|
|
|
56
56
|
|
|
57
57
|
export interface FileRoutesByFullPath {
|
|
58
58
|
'/': typeof IndexRoute
|
|
59
|
-
'/analytics': typeof AnalyticsRoute
|
|
60
59
|
'/api-keys': typeof ApiKeysRoute
|
|
60
|
+
'/insights': typeof InsightsRoute
|
|
61
61
|
'/installations': typeof InstallationsRoute
|
|
62
62
|
'/signing': typeof SigningRoute
|
|
63
63
|
'/api/auth/$': typeof ApiAuthSplatRoute
|
|
@@ -65,8 +65,8 @@ export interface FileRoutesByFullPath {
|
|
|
65
65
|
}
|
|
66
66
|
export interface FileRoutesByTo {
|
|
67
67
|
'/': typeof IndexRoute
|
|
68
|
-
'/analytics': typeof AnalyticsRoute
|
|
69
68
|
'/api-keys': typeof ApiKeysRoute
|
|
69
|
+
'/insights': typeof InsightsRoute
|
|
70
70
|
'/installations': typeof InstallationsRoute
|
|
71
71
|
'/signing': typeof SigningRoute
|
|
72
72
|
'/api/auth/$': typeof ApiAuthSplatRoute
|
|
@@ -75,8 +75,8 @@ export interface FileRoutesByTo {
|
|
|
75
75
|
export interface FileRoutesById {
|
|
76
76
|
__root__: typeof rootRouteImport
|
|
77
77
|
'/': typeof IndexRoute
|
|
78
|
-
'/analytics': typeof AnalyticsRoute
|
|
79
78
|
'/api-keys': typeof ApiKeysRoute
|
|
79
|
+
'/insights': typeof InsightsRoute
|
|
80
80
|
'/installations': typeof InstallationsRoute
|
|
81
81
|
'/signing': typeof SigningRoute
|
|
82
82
|
'/api/auth/$': typeof ApiAuthSplatRoute
|
|
@@ -86,8 +86,8 @@ export interface FileRouteTypes {
|
|
|
86
86
|
fileRoutesByFullPath: FileRoutesByFullPath
|
|
87
87
|
fullPaths:
|
|
88
88
|
| '/'
|
|
89
|
-
| '/analytics'
|
|
90
89
|
| '/api-keys'
|
|
90
|
+
| '/insights'
|
|
91
91
|
| '/installations'
|
|
92
92
|
| '/signing'
|
|
93
93
|
| '/api/auth/$'
|
|
@@ -95,8 +95,8 @@ export interface FileRouteTypes {
|
|
|
95
95
|
fileRoutesByTo: FileRoutesByTo
|
|
96
96
|
to:
|
|
97
97
|
| '/'
|
|
98
|
-
| '/analytics'
|
|
99
98
|
| '/api-keys'
|
|
99
|
+
| '/insights'
|
|
100
100
|
| '/installations'
|
|
101
101
|
| '/signing'
|
|
102
102
|
| '/api/auth/$'
|
|
@@ -104,8 +104,8 @@ export interface FileRouteTypes {
|
|
|
104
104
|
id:
|
|
105
105
|
| '__root__'
|
|
106
106
|
| '/'
|
|
107
|
-
| '/analytics'
|
|
108
107
|
| '/api-keys'
|
|
108
|
+
| '/insights'
|
|
109
109
|
| '/installations'
|
|
110
110
|
| '/signing'
|
|
111
111
|
| '/api/auth/$'
|
|
@@ -114,8 +114,8 @@ export interface FileRouteTypes {
|
|
|
114
114
|
}
|
|
115
115
|
export interface RootRouteChildren {
|
|
116
116
|
IndexRoute: typeof IndexRoute
|
|
117
|
-
AnalyticsRoute: typeof AnalyticsRoute
|
|
118
117
|
ApiKeysRoute: typeof ApiKeysRoute
|
|
118
|
+
InsightsRoute: typeof InsightsRoute
|
|
119
119
|
InstallationsRoute: typeof InstallationsRoute
|
|
120
120
|
SigningRoute: typeof SigningRoute
|
|
121
121
|
ApiAuthSplatRoute: typeof ApiAuthSplatRoute
|
|
@@ -138,6 +138,13 @@ declare module '@tanstack/react-router' {
|
|
|
138
138
|
preLoaderRoute: typeof InstallationsRouteImport
|
|
139
139
|
parentRoute: typeof rootRouteImport
|
|
140
140
|
}
|
|
141
|
+
'/insights': {
|
|
142
|
+
id: '/insights'
|
|
143
|
+
path: '/insights'
|
|
144
|
+
fullPath: '/insights'
|
|
145
|
+
preLoaderRoute: typeof InsightsRouteImport
|
|
146
|
+
parentRoute: typeof rootRouteImport
|
|
147
|
+
}
|
|
141
148
|
'/api-keys': {
|
|
142
149
|
id: '/api-keys'
|
|
143
150
|
path: '/api-keys'
|
|
@@ -145,13 +152,6 @@ declare module '@tanstack/react-router' {
|
|
|
145
152
|
preLoaderRoute: typeof ApiKeysRouteImport
|
|
146
153
|
parentRoute: typeof rootRouteImport
|
|
147
154
|
}
|
|
148
|
-
'/analytics': {
|
|
149
|
-
id: '/analytics'
|
|
150
|
-
path: '/analytics'
|
|
151
|
-
fullPath: '/analytics'
|
|
152
|
-
preLoaderRoute: typeof AnalyticsRouteImport
|
|
153
|
-
parentRoute: typeof rootRouteImport
|
|
154
|
-
}
|
|
155
155
|
'/': {
|
|
156
156
|
id: '/'
|
|
157
157
|
path: '/'
|
|
@@ -178,8 +178,8 @@ declare module '@tanstack/react-router' {
|
|
|
178
178
|
|
|
179
179
|
const rootRouteChildren: RootRouteChildren = {
|
|
180
180
|
IndexRoute: IndexRoute,
|
|
181
|
-
AnalyticsRoute: AnalyticsRoute,
|
|
182
181
|
ApiKeysRoute: ApiKeysRoute,
|
|
182
|
+
InsightsRoute: InsightsRoute,
|
|
183
183
|
InstallationsRoute: InstallationsRoute,
|
|
184
184
|
SigningRoute: SigningRoute,
|
|
185
185
|
ApiAuthSplatRoute: ApiAuthSplatRoute,
|
package/src/router.tsx
CHANGED
|
@@ -4,6 +4,8 @@ import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query
|
|
|
4
4
|
// Import the route tree selected by the local or hosted Vite integration.
|
|
5
5
|
import { routeTree } from "virtual:hot-updater-console/route-tree";
|
|
6
6
|
|
|
7
|
+
import { getInsightsScrollRestorationKey } from "./routes/-installations-search";
|
|
8
|
+
|
|
7
9
|
// Create a new router instance
|
|
8
10
|
export const getRouter = () => {
|
|
9
11
|
const queryClient = new QueryClient();
|
|
@@ -12,6 +14,7 @@ export const getRouter = () => {
|
|
|
12
14
|
context: { queryClient },
|
|
13
15
|
|
|
14
16
|
scrollRestoration: true,
|
|
17
|
+
getScrollRestorationKey: getInsightsScrollRestorationKey,
|
|
15
18
|
defaultPreloadStaleTime: 0,
|
|
16
19
|
});
|
|
17
20
|
|
|
@@ -1,19 +1,32 @@
|
|
|
1
|
+
import type { ParsedLocation } from "@tanstack/react-router";
|
|
2
|
+
|
|
3
|
+
const readCursor = (value: unknown): string | undefined =>
|
|
4
|
+
typeof value === "string" && value.length > 0 ? value : undefined;
|
|
5
|
+
|
|
6
|
+
const readTimestamp = (value: unknown): number | undefined =>
|
|
7
|
+
typeof value === "number" && Number.isSafeInteger(value) && value >= 0
|
|
8
|
+
? value
|
|
9
|
+
: undefined;
|
|
10
|
+
|
|
11
|
+
export function getInsightsScrollRestorationKey(location: ParsedLocation) {
|
|
12
|
+
if (location.pathname === "/installations") {
|
|
13
|
+
const search = validateInstallationsSearch(location.search);
|
|
14
|
+
if (search.query === undefined && search.installId === undefined) {
|
|
15
|
+
return `/installations?eventsBefore=${search.eventsBefore ?? "new"}&eventsCursor=${search.eventsCursor ?? "first"}`;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return location.state.__TSR_key!;
|
|
19
|
+
}
|
|
20
|
+
|
|
1
21
|
export function validateInstallationsSearch(search: Record<string, unknown>) {
|
|
2
22
|
return {
|
|
3
23
|
query: typeof search.query === "string" ? search.query : undefined,
|
|
4
24
|
installId:
|
|
5
25
|
typeof search.installId === "string" ? search.installId : undefined,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
: 0,
|
|
12
|
-
historyOffset:
|
|
13
|
-
typeof search.historyOffset === "number" &&
|
|
14
|
-
Number.isSafeInteger(search.historyOffset) &&
|
|
15
|
-
search.historyOffset >= 0
|
|
16
|
-
? search.historyOffset
|
|
17
|
-
: 0,
|
|
26
|
+
eventsCursor: readCursor(search.eventsCursor),
|
|
27
|
+
eventsBefore: readTimestamp(search.eventsBefore),
|
|
28
|
+
searchCursor: readCursor(search.searchCursor),
|
|
29
|
+
historyCursor: readCursor(search.historyCursor),
|
|
30
|
+
historyBefore: readTimestamp(search.historyBefore),
|
|
18
31
|
};
|
|
19
32
|
}
|
package/src/routes/__root.tsx
CHANGED
|
@@ -9,16 +9,11 @@ import { useEffect, useState } from "react";
|
|
|
9
9
|
|
|
10
10
|
import { AppSidebar } from "@/components/AppSidebar";
|
|
11
11
|
import { ConsoleAccessPage } from "@/components/ConsoleAccessPage";
|
|
12
|
-
import { AnalyticsCapabilityProvider } from "@/components/features/analytics/AnalyticsCapabilityContext";
|
|
13
12
|
import { NotFoundPage } from "@/components/NotFoundPage";
|
|
14
13
|
import { ThemeProvider } from "@/components/ThemeProvider";
|
|
15
14
|
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
|
|
16
15
|
import { Toaster } from "@/components/ui/sonner";
|
|
17
16
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
18
|
-
import {
|
|
19
|
-
getAnalyticsCapabilityState,
|
|
20
|
-
useAnalyticsCapabilitiesQuery,
|
|
21
|
-
} from "@/lib/analytics-api";
|
|
22
17
|
import {
|
|
23
18
|
getConsoleAccessRpc,
|
|
24
19
|
getConsoleAuthProvidersRpc,
|
|
@@ -149,9 +144,6 @@ function RootLayout() {
|
|
|
149
144
|
}
|
|
150
145
|
|
|
151
146
|
function AuthorizedConsole() {
|
|
152
|
-
const capabilityQuery = useAnalyticsCapabilitiesQuery();
|
|
153
|
-
const capability = getAnalyticsCapabilityState(capabilityQuery);
|
|
154
|
-
|
|
155
147
|
useEffect(() => {
|
|
156
148
|
if (
|
|
157
149
|
import.meta.env.DEV &&
|
|
@@ -162,13 +154,11 @@ function AuthorizedConsole() {
|
|
|
162
154
|
}
|
|
163
155
|
}, []);
|
|
164
156
|
return (
|
|
165
|
-
<
|
|
166
|
-
<
|
|
167
|
-
|
|
168
|
-
<
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
</SidebarProvider>
|
|
172
|
-
</AnalyticsCapabilityProvider>
|
|
157
|
+
<SidebarProvider>
|
|
158
|
+
<AppSidebar />
|
|
159
|
+
<SidebarInset className="min-h-0 min-w-0 overflow-hidden">
|
|
160
|
+
<Outlet />
|
|
161
|
+
</SidebarInset>
|
|
162
|
+
</SidebarProvider>
|
|
173
163
|
);
|
|
174
164
|
}
|
package/src/routes/api-keys.tsx
CHANGED
|
@@ -23,7 +23,7 @@ function ApiKeysRoute() {
|
|
|
23
23
|
<h1 className="text-sm font-medium">API keys</h1>
|
|
24
24
|
</div>
|
|
25
25
|
<p className="basis-full pl-9 text-xs text-muted-foreground sm:basis-auto sm:pl-0">
|
|
26
|
-
API keys can download OTA updates and submit
|
|
26
|
+
API keys can download OTA updates and submit insights only.
|
|
27
27
|
</p>
|
|
28
28
|
</header>
|
|
29
29
|
|