@hot-updater/console 1.0.0-rc.8 → 1.0.0
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/ConsoleFeatureUnavailable-jB4Loglh.js +1 -0
- package/.output/public/assets/EventDetails-B6yUsyNA.js +1 -0
- package/.output/public/assets/HashValueDisplay-BFX_kY3-.js +1 -0
- package/.output/public/assets/InsightsInfo-BNO0WznZ.js +1 -0
- package/.output/public/assets/InsightsPageHeader-B892oVml.js +1 -0
- package/.output/public/assets/NotFoundPage-DIqCpWa3.js +1 -0
- package/.output/public/assets/alert-dialog-Bbk_4-WJ.js +1 -0
- package/.output/public/assets/api-DCM0wr6O.js +1 -0
- package/.output/public/assets/api-keys-CIO9L_wu.js +1 -0
- package/.output/public/assets/api-keys-DVTL_--f.js +1 -0
- package/.output/public/assets/arrow-left-CnMYpqB7.js +1 -0
- package/.output/public/assets/arrow-up-right-BtffZA6e.js +1 -0
- package/.output/public/assets/chevron-right-BqD5wtFb.js +1 -0
- package/.output/public/assets/clipboard-DS9ucUQL.js +1 -0
- package/.output/public/assets/console-features-api-BH5m_PtQ.js +1 -0
- package/.output/public/assets/dialog-Bqbb7B0N.js +1 -0
- package/.output/public/assets/dist-LxKJB-Vu.js +1 -0
- package/.output/public/assets/empty-CuT2UhFp.js +1 -0
- package/.output/public/assets/field-BBDpjIqu.js +1 -0
- package/.output/public/assets/index-6o51GkFe.js +11 -0
- package/.output/public/assets/input-group-ja_30qaF.js +1 -0
- package/.output/public/assets/insights-CmraDzQh.js +1 -0
- package/.output/public/assets/insights-CxZ2GCYV.js +2 -0
- package/.output/public/assets/insights-api-BigAU9sZ.js +1 -0
- package/.output/public/assets/insights-usage-rpc-DYEhsCKT.js +1 -0
- package/.output/public/assets/insights-vNRCrfnS.js +42 -0
- package/.output/public/assets/insights_.distribution-ChOerfJt.js +1 -0
- package/.output/public/assets/insights_.distribution-D-e4iga8.js +1 -0
- package/.output/public/assets/insights_.distribution-DZtkibX1.js +2 -0
- package/.output/public/assets/installations-BazSJXE0.js +2 -0
- package/.output/public/assets/installations-ClHTqdAO.js +1 -0
- package/.output/public/assets/installations-DpKsBuHi.js +1 -0
- package/.output/public/assets/key-round-wS_SIZ6b.js +1 -0
- package/.output/public/assets/list-CgrYiadM.js +1 -0
- package/.output/public/assets/refresh-cw-BYgoNCco.js +1 -0
- package/.output/public/assets/remote-config-BWwHOkCK.js +1 -0
- package/.output/public/assets/remote-config-Br56BODb.js +6 -0
- package/.output/public/assets/remote-config-CXOi0LRu.js +1 -0
- package/.output/public/assets/routes-B88Erkpk.js +2 -0
- package/.output/public/assets/routes-CfzsAleC.js +1 -0
- package/.output/public/assets/search-DaTDPW25.js +1 -0
- package/.output/public/assets/select-BZZBXMkr.js +1 -0
- package/.output/public/assets/sidebar-DNVyJPfX.js +3 -0
- package/.output/public/assets/smartphone-DpTFgtKf.js +1 -0
- package/.output/public/assets/styles-DTGEAJFv.css +2 -0
- package/.output/public/assets/switch-DZ1TPwx-.js +1 -0
- package/.output/public/assets/table-Gm4qRWwE.js +1 -0
- package/.output/public/assets/tabs-C7FOEjFC.js +1 -0
- package/.output/public/assets/undo-2-CmrHjwFc.js +1 -0
- package/.output/public/assets/useCompositeListItem-9E4pj_CH.js +1 -0
- package/.output/public/assets/valueToPercent-CH8GCHaL.js +1 -0
- package/.output/server/__23tanstack-start-server-fn-resolver-BpRlos5Q.mjs +170 -0
- package/.output/server/_libs/@base-ui/react+[...].mjs +4389 -4207
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +3802 -4022
- package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +140 -97
- package/.output/server/_libs/lucide-react.mjs +221 -9
- package/.output/server/_libs/next-themes.mjs +1 -1
- package/.output/server/_libs/react-redux+[...].mjs +1 -1
- package/.output/server/_libs/recharts+[...].mjs +483 -901
- package/.output/server/_libs/sonner.mjs +1 -1
- package/.output/server/_libs/tanstack__history.mjs +68 -41
- package/.output/server/_libs/tanstack__query-core.mjs +2010 -309
- package/.output/server/_libs/tanstack__react-query.mjs +448 -21
- package/.output/server/_libs/verkit.mjs +102 -1
- package/.output/server/_ssr/BundleIdDisplay-x94o9rlU.mjs +13 -0
- package/.output/server/_ssr/ConsoleFeatureUnavailable-ldW8v8-q.mjs +58 -0
- package/.output/server/_ssr/EventDetails-Q8kfj9Nr.mjs +344 -0
- package/.output/server/_ssr/{HashValueDisplay-C5tS4oGo.mjs → HashValueDisplay-n1GholGS.mjs} +3 -3
- package/.output/server/_ssr/InsightsInfo-DuJFatQO.mjs +75 -0
- package/.output/server/_ssr/InsightsPageHeader-BSUX3Ye9.mjs +145 -0
- package/.output/server/_ssr/NotFoundPage-DZzOOzS3.mjs +37 -0
- package/.output/server/_ssr/{dialog-WiIAKl4L.mjs → alert-dialog-C43Doa0I.mjs} +6 -83
- package/.output/server/_ssr/{api-keys-aXriWrv7.mjs → api-keys-B4sYm8dM.mjs} +48 -9
- package/.output/server/_ssr/api-keys-CEnM9fqG.mjs +5 -0
- package/.output/server/_ssr/api-keys-rpc-BIFuUgMt.mjs +52 -0
- package/.output/server/_ssr/api-oZjRn9tP.mjs +186 -0
- package/.output/server/_ssr/api-rpc-BNiFRRK8.mjs +240 -0
- package/.output/server/_ssr/{auth-BF8mbAgF.mjs → auth-UP0wXs4i.mjs} +1 -1
- package/.output/server/_ssr/{auth-rpc-B7w3AdRr.mjs → auth-rpc-DnZqYxyq.mjs} +6 -6
- package/.output/server/_ssr/auth.server-DFpyxRDd.mjs +5374 -0
- package/.output/server/_ssr/bundleActivity-M5gzBFNg.mjs +18 -0
- package/.output/server/_ssr/config-WVX4IxSR.mjs +15 -0
- package/.output/server/_ssr/config.server-DskXCAyI.mjs +35 -0
- package/.output/server/_ssr/console-access-BJfAXknd.mjs +17 -0
- package/.output/server/_ssr/console-features-Oz0q20jJ.mjs +61 -0
- package/.output/server/_ssr/console-features-api-C9kiKvaV.mjs +43 -0
- package/.output/server/_ssr/console-features-rpc-P0GL7Z-R.mjs +19 -0
- package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-Cm6Tn0_Y.mjs} +67 -56
- package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-RLR4H-Fn.mjs} +2 -2
- package/.output/server/_ssr/databaseConfig-C5SkAyUy.mjs +5 -0
- package/.output/server/_ssr/{databaseJsonValue-d6I3nAS4.mjs → databaseJsonValue-Csbuzc9Q.mjs} +28 -5
- package/.output/server/_ssr/databaseRows-CPphvbag.mjs +73 -0
- package/.output/server/_ssr/dialog-BbYDMjdC.mjs +84 -0
- package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-CbQbBIw-.mjs} +140 -7
- package/.output/server/_ssr/{downloadBundle-CI7JUKvC.mjs → downloadBundle-CZysjmNW.mjs} +10 -10
- package/.output/server/_ssr/empty-CVLGfxuu.mjs +60 -0
- package/.output/server/_ssr/{empty-plugin-adapters-B5QvGpEl.mjs → empty-plugin-adapters-CPtwX8a7.mjs} +1 -1
- package/.output/server/_ssr/failureReports-DKDhVsXA.mjs +33 -0
- package/.output/server/_ssr/field-asw83X0v.mjs +189 -0
- package/.output/server/_ssr/getBundleChildren-Cv9iz1As.mjs +22 -0
- package/.output/server/_ssr/{input-group-Coj6SwVP.mjs → input-group-CuYJHosb.mjs} +2 -2
- package/.output/server/_ssr/insights-B55FG5fm.mjs +14 -0
- package/.output/server/_ssr/insights-CWnHIFUV.mjs +5 -0
- package/.output/server/_ssr/insights-CpLpedFZ.mjs +2315 -0
- package/.output/server/_ssr/insights-api-sjws23WF.mjs +122 -0
- package/.output/server/_ssr/insights-errors-BjnFUyZj.mjs +34 -0
- package/.output/server/_ssr/insights-failures-rpc-BvbWvqNf.mjs +36 -0
- package/.output/server/_ssr/insights-failures-xaa41Buf.mjs +46 -0
- package/.output/server/_ssr/insights-recovery-BWQhiZaF.mjs +28 -0
- package/.output/server/_ssr/insights-recovery-rpc-GiYXdkaY.mjs +32 -0
- package/.output/server/_ssr/{insights-rpc-l2-oBYth.mjs → insights-rpc-DP_8Rmz4.mjs} +22 -14
- package/.output/server/_ssr/insights-usage-BpBM0U0c.mjs +29 -0
- package/.output/server/_ssr/insights-usage-rpc-BiolgB8e.mjs +29 -0
- package/.output/server/_ssr/insights-usage-rpc-KStL0zOx.mjs +41 -0
- package/.output/server/_ssr/insightsUsage-CzhVjsc8.mjs +56 -0
- package/.output/server/_ssr/insights_.distribution-CVrnbP3R.mjs +7 -0
- package/.output/server/_ssr/insights_.distribution-D2HaNTAA.mjs +16 -0
- package/.output/server/_ssr/{insights_.distribution-INw6QxbU.mjs → insights_.distribution-DN9pR422.mjs} +50 -22
- package/.output/server/_ssr/installations-BHmbbnH8.mjs +5 -0
- package/.output/server/_ssr/{installations-DH-tQuRo.mjs → installations-BSCmFq8Q.mjs} +279 -367
- package/.output/server/_ssr/installations-DD1g__Nc.mjs +69 -0
- package/.output/server/_ssr/listReleases-BfUBlCHo.mjs +79 -0
- package/.output/server/_ssr/plugins-BaD1slHx.mjs +2086 -0
- package/.output/server/_ssr/release-adoption-005tQRpo.mjs +55 -0
- package/.output/server/_ssr/release-adoption-rpc-BsstqxUp.mjs +41 -0
- package/.output/server/_ssr/releaseAdoption-CtOFkwli.mjs +114 -0
- package/.output/server/_ssr/releaseManagement-DFMUnDF5.mjs +11 -0
- package/.output/server/_ssr/remote-config-C2Ee8lXO.mjs +5 -0
- package/.output/server/_ssr/remote-config-Dv1ccMo-.mjs +17 -0
- package/.output/server/_ssr/remote-config-of0lGQH8.mjs +2648 -0
- package/.output/server/_ssr/remote-config-rpc-BzX31mUS.mjs +115 -0
- package/.output/server/_ssr/rolldown-runtime-rSIU-vHC.mjs +13 -0
- package/.output/server/_ssr/{router-DfFyAp5p.mjs → router-D8NuoJ_I.mjs} +117 -136
- package/.output/server/_ssr/{routes-BtsfVx-p.mjs → routes-DgvnFqDy.mjs} +195 -429
- package/.output/server/_ssr/routes-V8sDybGf.mjs +409 -0
- package/.output/server/_ssr/runtime.server-De0QVWc9.mjs +104 -0
- package/.output/server/_ssr/schema-BiAVyEj2.mjs +361 -0
- package/.output/server/_ssr/{select-CyvLaMza.mjs → select-t3iXRwc9.mjs} +3 -3
- package/.output/server/_ssr/server-h4VxnExB.mjs +8325 -0
- package/.output/server/_ssr/{createSsrRpc-C-dLcQuy.mjs → sidebar-DJcErBMo.mjs} +107 -47
- package/.output/server/_ssr/ssr.mjs +1 -1
- package/.output/server/_ssr/start-BfFbHKMK.mjs +4 -0
- package/.output/server/_ssr/switch-E-btwMGt.mjs +43 -0
- package/.output/server/_ssr/{table-CkVhsNpe.mjs → table-37Dd23TN.mjs} +2 -2
- package/.output/server/_ssr/tabs-DEFnTm95.mjs +45 -0
- package/.output/server/_ssr/updateFailures-H6pnnYK4.mjs +40 -0
- package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-C88s9jWF.mjs} +2 -2
- package/.output/server/_tanstack-start-manifest_v-55AGsehh.mjs +139 -0
- package/.output/server/index.mjs +303 -205
- package/LICENSE +13 -14
- package/README.md +80 -71
- package/lib/index.d.mts +23 -11
- package/lib/vite-internal.mjs +13 -3
- package/lib/vite.d.mts +2 -0
- package/package.json +14 -14
- package/src/components/AppSidebar.tsx +74 -75
- package/src/components/ConsoleAccessPage.tsx +5 -1
- package/src/components/ConsoleFeatureUnavailable.tsx +80 -0
- package/src/components/NotFoundPage.tsx +7 -7
- package/src/components/features/FeatureSlots.tsx +103 -0
- package/src/components/features/api-keys/apiKeysFeatureUi.tsx +15 -0
- package/src/components/features/bundles/BundleMetadata.tsx +8 -5
- package/src/components/features/bundles/RolloutCohortsDialog.tsx +1 -1
- package/src/components/features/channels/ChannelManagementDialog.tsx +8 -6
- package/src/components/features/insights/AppUsage.tsx +41 -14
- package/src/components/features/insights/BuiltinBundle.tsx +36 -0
- package/src/components/features/insights/ComparedBundlesChart.tsx +310 -0
- package/src/components/features/insights/EstimatedCount.tsx +18 -0
- package/src/components/features/insights/EventDetails.tsx +390 -26
- package/src/components/features/insights/EventHistoryCard.tsx +111 -25
- package/src/components/features/insights/EventHistoryList.tsx +1 -1
- package/src/components/features/insights/FailureReports.tsx +407 -0
- package/src/components/features/insights/InsightsControls.tsx +240 -142
- package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
- package/src/components/features/insights/InsightsOverview.tsx +601 -403
- package/src/components/features/insights/InsightsPageHeader.tsx +19 -14
- package/src/components/features/insights/InsightsPeriodSelector.tsx +67 -12
- package/src/components/features/insights/InstallationHistoryCard.tsx +65 -10
- package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
- package/src/components/features/insights/ReleaseActivity.tsx +308 -0
- package/src/components/features/insights/ReportingDevicesSummary.tsx +13 -13
- package/src/components/features/insights/RollbackButton.tsx +118 -0
- package/src/components/features/insights/UpdateFailures.tsx +382 -0
- package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
- package/src/components/features/releases/ReleaseEditorSheet.tsx +27 -13
- package/src/components/features/remote-config/ConditionDialog.tsx +592 -0
- package/src/components/features/remote-config/ConditionsCard.tsx +247 -0
- package/src/components/features/remote-config/ParameterDialog.tsx +426 -0
- package/src/components/features/remote-config/ParametersCard.tsx +254 -0
- package/src/components/features/remote-config/PreviewCard.tsx +284 -0
- package/src/components/features/remote-config/PublishDialog.tsx +325 -0
- package/src/components/features/remote-config/RemoteConfigPage.tsx +353 -0
- package/src/components/features/remote-config/RuleSummary.tsx +21 -0
- package/src/components/features/remote-config/ValueInput.tsx +151 -0
- package/src/components/features/remote-config/VersionsCard.tsx +384 -0
- package/src/components/features/remote-config/remoteConfigFeatureUi.tsx +15 -0
- package/src/components/features/remote-config/useDateTimeText.ts +16 -0
- package/src/components/ui/sidebar.tsx +22 -29
- package/src/index.ts +26 -19
- package/src/lib/api-keys-api.ts +0 -28
- package/src/lib/api-keys-rpc.ts +27 -38
- package/src/lib/api-rpc.ts +125 -288
- package/src/lib/api.ts +15 -134
- package/src/lib/bundle-activity.ts +24 -4
- package/src/lib/console-access.ts +20 -0
- package/src/lib/console-features-api.ts +68 -0
- package/src/lib/console-features-rpc.ts +13 -0
- package/src/lib/console-features.ts +98 -0
- package/src/lib/insights-api.ts +56 -2
- package/src/lib/insights-errors.ts +64 -0
- package/src/lib/insights-failures-rpc.ts +36 -0
- package/src/lib/insights-failures.ts +130 -0
- package/src/lib/insights-recovery-rpc.ts +13 -17
- package/src/lib/insights-recovery.ts +12 -36
- package/src/lib/insights-retention.ts +21 -0
- package/src/lib/insights-rpc.ts +30 -12
- package/src/lib/insights-search.ts +50 -4
- package/src/lib/insights-usage-rpc.ts +15 -9
- package/src/lib/insights-usage.ts +41 -3
- package/src/lib/insights-view.ts +34 -5
- package/src/lib/release-adoption-api.ts +214 -0
- package/src/lib/release-adoption-rpc.ts +49 -0
- package/src/lib/release-adoption.ts +164 -0
- package/src/lib/remote-config-api.ts +119 -0
- package/src/lib/remote-config-draft.ts +666 -0
- package/src/lib/remote-config-rpc.ts +196 -0
- package/src/lib/scroll-restoration.ts +21 -0
- package/src/lib/server/auth.server.ts +13 -5
- package/src/lib/server/bundleActivity.ts +27 -28
- package/src/lib/server/config.server.ts +21 -76
- package/src/lib/server/console-feature-apis.ts +70 -0
- package/src/lib/server/console-runtime.server.ts +82 -38
- package/src/lib/server/downloadBundle.ts +14 -15
- package/src/lib/server/failureReports.ts +50 -0
- package/src/lib/server/getBundleChildren.ts +31 -122
- package/src/lib/server/insightsUsage.ts +52 -113
- package/src/lib/server/listReleases.ts +109 -44
- package/src/lib/server/releaseAdoption.ts +151 -0
- package/src/lib/server/releaseReachability.ts +8 -8
- package/src/lib/server/remoteConfig.ts +75 -0
- package/src/lib/server/runtime.server.ts +120 -31
- package/src/lib/server/updateFailures.ts +66 -0
- package/src/lib/use-dialog-target.ts +27 -0
- package/src/routeTree.gen.ts +44 -44
- package/src/router.tsx +3 -2
- package/src/routes/-installations-search.ts +32 -8
- package/src/routes/-releases-search.ts +76 -29
- package/src/routes/__root.tsx +7 -1
- package/src/routes/api/bundles/$bundleId/download.ts +2 -5
- package/src/routes/api-keys.tsx +5 -2
- package/src/routes/index.tsx +210 -152
- package/src/routes/insights.tsx +91 -23
- package/src/routes/insights_.distribution.tsx +50 -9
- package/src/routes/installations.tsx +97 -40
- package/src/routes/remote-config.tsx +42 -0
- package/src/styles.css +9 -0
- package/src/vite-internal.ts +15 -3
- package/src/vite.ts +2 -0
- package/.output/public/assets/HashValueDisplay-BpTc3Oyg.js +0 -1
- package/.output/public/assets/InsightsPageHeader-DC2eEst8.js +0 -1
- package/.output/public/assets/api-CdUyUqzR.js +0 -42
- package/.output/public/assets/api-keys-64PM9PwW.js +0 -1
- package/.output/public/assets/api-keys-api-BeaJnvY5.js +0 -1
- package/.output/public/assets/arrow-left-04Rh8Zgp.js +0 -1
- package/.output/public/assets/card-CYHs5FZv.js +0 -1
- package/.output/public/assets/chart-no-axes-combined-DwyMsVFW.js +0 -1
- package/.output/public/assets/createServerFn-DU71xucx.js +0 -3
- package/.output/public/assets/dialog-IVMkBRCq.js +0 -1
- package/.output/public/assets/dist-BR4GzeEu.js +0 -1
- package/.output/public/assets/field-B3CYDpjg.js +0 -1
- package/.output/public/assets/index-C39o7rJH.js +0 -10
- package/.output/public/assets/info-C3y9HNoU.js +0 -1
- package/.output/public/assets/input-group-nLZylhWF.js +0 -1
- package/.output/public/assets/insights-Bm9v8uO3.js +0 -2
- package/.output/public/assets/insights-DS3iO7Up.js +0 -1
- package/.output/public/assets/insights-search-Byj3g3Mp.js +0 -1
- package/.output/public/assets/insights-usage-rpc-BaGR3lgq.js +0 -1
- package/.output/public/assets/insights_.distribution-B1i6cjlQ.js +0 -1
- package/.output/public/assets/insights_.distribution-C1Wf27eB.js +0 -2
- package/.output/public/assets/installations-CkFW4fV9.js +0 -2
- package/.output/public/assets/installations-DxTaIcHD.js +0 -1
- package/.output/public/assets/lazyRouteComponent-BapzjYnb.js +0 -1
- package/.output/public/assets/list-Dqt53fWZ.js +0 -1
- package/.output/public/assets/loader-circle-CUKfUqow.js +0 -1
- package/.output/public/assets/preload-helper-ClBMtM1g.js +0 -1
- package/.output/public/assets/refresh-cw-CNE_2PnD.js +0 -1
- package/.output/public/assets/routes-Cgtvspjg.js +0 -2
- package/.output/public/assets/routes-O2r2UUN8.js +0 -1
- package/.output/public/assets/shield-check-BFcJ46kz.js +0 -1
- package/.output/public/assets/signing-BgspC-xP.js +0 -1
- package/.output/public/assets/styles-DnrWuBwd.css +0 -2
- package/.output/public/assets/table-BuWQgxJJ.js +0 -1
- package/.output/public/assets/useMutation-CSV9NCUl.js +0 -1
- package/.output/public/assets/useOpenInteractionType-D5kduCTD.js +0 -1
- package/.output/public/assets/valueToPercent-BAtcbSLk.js +0 -1
- package/.output/server/__23tanstack-start-server-fn-resolver-CmYkol9D.mjs +0 -150
- package/.output/server/_ssr/InsightsPageHeader-Dma3dVj1.mjs +0 -64
- package/.output/server/_ssr/api-C3bGmgY0.mjs +0 -332
- package/.output/server/_ssr/api-keys-api-BRF6rbRC.mjs +0 -60
- package/.output/server/_ssr/api-keys-rpc-BMa1ZM6N.mjs +0 -136
- package/.output/server/_ssr/api-rpc-BYQ2e0z6.mjs +0 -1399
- package/.output/server/_ssr/auth.server-CmicHh0J.mjs +0 -47
- package/.output/server/_ssr/bundle-signing-rpc-DbQ4pFAX.mjs +0 -16
- package/.output/server/_ssr/bundle-signing.server-Uqt1OF49.mjs +0 -12
- package/.output/server/_ssr/bundleActivity-pBrPSG01.mjs +0 -28
- package/.output/server/_ssr/card-B7KSO_25.mjs +0 -108
- package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
- package/.output/server/_ssr/config.server-C-d1QaBU.mjs +0 -368
- package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
- package/.output/server/_ssr/deleteBundle-8X-jq7Z7.mjs +0 -66
- package/.output/server/_ssr/field-BDwuU8RE.mjs +0 -74
- package/.output/server/_ssr/getBundleChildren-DoL8l_AS.mjs +0 -50
- package/.output/server/_ssr/insights-BDJjaDLp.mjs +0 -938
- package/.output/server/_ssr/insights-D0FWpfOS.mjs +0 -10
- package/.output/server/_ssr/insights-recovery-Cv6RSyeR.mjs +0 -21
- package/.output/server/_ssr/insights-recovery-rpc-BE9YsK_T.mjs +0 -37
- package/.output/server/_ssr/insights-search-1czJMZgk.mjs +0 -21
- package/.output/server/_ssr/insights-usage-rpc-BE_aCnea.mjs +0 -184
- package/.output/server/_ssr/insights-usage-rpc-CtAolz-t.mjs +0 -24
- package/.output/server/_ssr/insightsContract-B09K_Px1.mjs +0 -303
- package/.output/server/_ssr/insightsHistory-Ci4rYAgl.mjs +0 -41
- package/.output/server/_ssr/insightsRecovery-CaghWL2t.mjs +0 -113
- package/.output/server/_ssr/insightsUsage-DNIj6jxF.mjs +0 -71
- package/.output/server/_ssr/insights_.distribution-Cp0kf1vW.mjs +0 -10
- package/.output/server/_ssr/installations-Bhyiwx8G.mjs +0 -29
- package/.output/server/_ssr/routes-xBkI2Fmt.mjs +0 -37
- package/.output/server/_ssr/runtime.server-CsiKFp_Z.mjs +0 -436
- package/.output/server/_ssr/server-BdkuOWe8.mjs +0 -7468
- package/.output/server/_ssr/signing-CQ70saFm.mjs +0 -133
- package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
- package/.output/server/_tanstack-start-manifest_v-CrBsoKQQ.mjs +0 -125
- package/src/components/features/bundles/BundleActivityChart.tsx +0 -97
- package/src/components/features/bundles/BundleInsightsSummary.tsx +0 -126
- package/src/components/features/signing/BundleSigningPage.tsx +0 -139
- package/src/lib/bundle-signing-api.ts +0 -14
- package/src/lib/bundle-signing-rpc.ts +0 -16
- package/src/lib/server/bundle-signing.server.ts +0 -23
- package/src/lib/server/deleteBundle.ts +0 -145
- package/src/lib/server/insightsHistory.ts +0 -52
- package/src/lib/server/insightsRecovery.ts +0 -187
- package/src/routes/signing.tsx +0 -37
package/src/lib/api.ts
CHANGED
|
@@ -1,29 +1,18 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
ChannelInsertInput,
|
|
2
|
+
ReleaseFilter,
|
|
4
3
|
ReleasePolicyPatch,
|
|
5
4
|
} from "@hot-updater/plugin-core";
|
|
6
|
-
import {
|
|
7
|
-
type QueryClient,
|
|
8
|
-
type QueryKey,
|
|
9
|
-
useMutation,
|
|
10
|
-
useQuery,
|
|
11
|
-
useQueryClient,
|
|
12
|
-
} from "@tanstack/react-query";
|
|
5
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
13
6
|
|
|
14
7
|
import {
|
|
15
8
|
createChannel as createChannelApi,
|
|
16
9
|
deleteChannel as deleteChannelApi,
|
|
17
|
-
deleteBundle as deleteBundleApi,
|
|
18
|
-
deleteBundles as deleteBundlesApi,
|
|
19
10
|
deleteRelease as deleteReleaseApi,
|
|
20
11
|
getBundle,
|
|
21
12
|
getBundleChildCounts,
|
|
22
13
|
getBundleChildren,
|
|
23
|
-
getBundles,
|
|
24
14
|
getChannels,
|
|
25
15
|
getConfig,
|
|
26
|
-
getConfigLoaded,
|
|
27
16
|
getRelease,
|
|
28
17
|
getReleaseCatalogDiagnostics,
|
|
29
18
|
getReleases,
|
|
@@ -32,28 +21,11 @@ import {
|
|
|
32
21
|
updateRelease as updateReleaseApi,
|
|
33
22
|
} from "./api-rpc";
|
|
34
23
|
|
|
35
|
-
type BundleFilters = {
|
|
36
|
-
platform?: "ios" | "android";
|
|
37
|
-
page?: number;
|
|
38
|
-
limit?: string;
|
|
39
|
-
after?: string;
|
|
40
|
-
before?: string;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
type BundlesQueryData = Awaited<ReturnType<typeof getBundles>>;
|
|
44
|
-
|
|
45
|
-
const bundleListQueryKey = ["bundles"] as const;
|
|
46
24
|
const releaseListQueryKey = ["releases"] as const;
|
|
47
25
|
|
|
48
26
|
export const queryKeys = {
|
|
49
27
|
config: ["config"] as const,
|
|
50
28
|
channels: ["channels"] as const,
|
|
51
|
-
configLoaded: ["config-loaded"] as const,
|
|
52
|
-
bundles: {
|
|
53
|
-
all: bundleListQueryKey,
|
|
54
|
-
list: (filters?: BundleFilters) =>
|
|
55
|
-
[...bundleListQueryKey, filters ?? {}] as const,
|
|
56
|
-
},
|
|
57
29
|
releases: {
|
|
58
30
|
all: releaseListQueryKey,
|
|
59
31
|
list: (filters?: ReleaseFilters) =>
|
|
@@ -71,51 +43,13 @@ export const queryKeys = {
|
|
|
71
43
|
};
|
|
72
44
|
|
|
73
45
|
export type ReleaseFilters = {
|
|
74
|
-
|
|
46
|
+
/** One of the filter sets the release indexes serve; none lists every release. */
|
|
47
|
+
filter?: ReleaseFilter;
|
|
48
|
+
/** Releases older than this id: the next page. */
|
|
75
49
|
beforeReleaseId?: string;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
enabled?: boolean;
|
|
79
|
-
platform?: "ios" | "android";
|
|
50
|
+
/** Releases newer than this id: the previous page. */
|
|
51
|
+
afterReleaseId?: string;
|
|
80
52
|
limit?: number;
|
|
81
|
-
page?: number;
|
|
82
|
-
targetAppVersion?: string;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
function removeBundleFromQueryData(
|
|
86
|
-
data: BundlesQueryData | undefined,
|
|
87
|
-
bundleId: string,
|
|
88
|
-
) {
|
|
89
|
-
if (!data) {
|
|
90
|
-
return data;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
...data,
|
|
95
|
-
data: data.data.filter((bundle) => bundle.id !== bundleId),
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function removeBundlesFromQueryData(
|
|
100
|
-
data: BundlesQueryData | undefined,
|
|
101
|
-
bundleIds: readonly string[],
|
|
102
|
-
) {
|
|
103
|
-
if (!data) {
|
|
104
|
-
return data;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const bundleIdSet = new Set(bundleIds);
|
|
108
|
-
return {
|
|
109
|
-
...data,
|
|
110
|
-
data: data.data.filter((bundle) => !bundleIdSet.has(bundle.id)),
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const invalidateInBackground = (
|
|
115
|
-
queryClient: QueryClient,
|
|
116
|
-
queryKey: QueryKey,
|
|
117
|
-
) => {
|
|
118
|
-
void queryClient.invalidateQueries({ queryKey }).catch(() => undefined);
|
|
119
53
|
};
|
|
120
54
|
|
|
121
55
|
// Query Hooks
|
|
@@ -135,23 +69,6 @@ export function useChannelsQuery() {
|
|
|
135
69
|
});
|
|
136
70
|
}
|
|
137
71
|
|
|
138
|
-
export function useConfigLoadedQuery() {
|
|
139
|
-
return useQuery({
|
|
140
|
-
queryKey: queryKeys.configLoaded,
|
|
141
|
-
queryFn: () => getConfigLoaded(),
|
|
142
|
-
staleTime: Infinity,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export function useBundlesQuery(filters?: BundleFilters) {
|
|
147
|
-
return useQuery({
|
|
148
|
-
queryKey: queryKeys.bundles.list(filters),
|
|
149
|
-
queryFn: () => getBundles({ data: filters }),
|
|
150
|
-
staleTime: Infinity,
|
|
151
|
-
placeholderData: (previousData) => previousData,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
72
|
export function useReleasesQuery(filters?: ReleaseFilters) {
|
|
156
73
|
return useQuery({
|
|
157
74
|
queryKey: queryKeys.releases.list(filters),
|
|
@@ -213,7 +130,7 @@ export function useCreateChannelMutation() {
|
|
|
213
130
|
const queryClient = useQueryClient();
|
|
214
131
|
|
|
215
132
|
return useMutation({
|
|
216
|
-
mutationFn: (input:
|
|
133
|
+
mutationFn: (input: { name: string }) =>
|
|
217
134
|
createChannelApi({ data: input }).then((response) => response.data),
|
|
218
135
|
onSuccess: async () => {
|
|
219
136
|
await queryClient.invalidateQueries({ queryKey: queryKeys.channels });
|
|
@@ -225,7 +142,7 @@ export function useDeleteChannelMutation() {
|
|
|
225
142
|
const queryClient = useQueryClient();
|
|
226
143
|
|
|
227
144
|
return useMutation({
|
|
228
|
-
mutationFn: (input:
|
|
145
|
+
mutationFn: (input: { id: string }) =>
|
|
229
146
|
deleteChannelApi({ data: input }).then((response) => response.data),
|
|
230
147
|
onSettled: async () => {
|
|
231
148
|
await queryClient.invalidateQueries({ queryKey: queryKeys.channels });
|
|
@@ -233,48 +150,6 @@ export function useDeleteChannelMutation() {
|
|
|
233
150
|
});
|
|
234
151
|
}
|
|
235
152
|
|
|
236
|
-
export function useDeleteBundleMutation() {
|
|
237
|
-
const queryClient = useQueryClient();
|
|
238
|
-
|
|
239
|
-
return useMutation({
|
|
240
|
-
mutationFn: (params: { bundleId: string }) =>
|
|
241
|
-
deleteBundleApi({ data: params }),
|
|
242
|
-
onSuccess: (_, vars) => {
|
|
243
|
-
queryClient.removeQueries({ queryKey: queryKeys.bundle(vars.bundleId) });
|
|
244
|
-
queryClient.setQueriesData(
|
|
245
|
-
{ queryKey: queryKeys.bundles.all },
|
|
246
|
-
(data: BundlesQueryData | undefined) =>
|
|
247
|
-
removeBundleFromQueryData(data, vars.bundleId),
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
invalidateInBackground(queryClient, queryKeys.bundles.all);
|
|
251
|
-
invalidateInBackground(queryClient, queryKeys.bundleChildren.all);
|
|
252
|
-
},
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export function useDeleteBundlesMutation() {
|
|
257
|
-
const queryClient = useQueryClient();
|
|
258
|
-
|
|
259
|
-
return useMutation({
|
|
260
|
-
mutationFn: (params: { bundleIds: string[] }) =>
|
|
261
|
-
deleteBundlesApi({ data: params }),
|
|
262
|
-
onSuccess: (_, vars) => {
|
|
263
|
-
for (const bundleId of vars.bundleIds) {
|
|
264
|
-
queryClient.removeQueries({ queryKey: queryKeys.bundle(bundleId) });
|
|
265
|
-
}
|
|
266
|
-
queryClient.setQueriesData(
|
|
267
|
-
{ queryKey: queryKeys.bundles.all },
|
|
268
|
-
(data: BundlesQueryData | undefined) =>
|
|
269
|
-
removeBundlesFromQueryData(data, vars.bundleIds),
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
invalidateInBackground(queryClient, queryKeys.bundles.all);
|
|
273
|
-
invalidateInBackground(queryClient, queryKeys.bundleChildren.all);
|
|
274
|
-
},
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
|
|
278
153
|
export function useUpdateReleaseMutation() {
|
|
279
154
|
const queryClient = useQueryClient();
|
|
280
155
|
return useMutation({
|
|
@@ -315,10 +190,16 @@ export function useDeleteReleaseMutation() {
|
|
|
315
190
|
queryClient.removeQueries({
|
|
316
191
|
queryKey: queryKeys.release(input.releaseId),
|
|
317
192
|
});
|
|
193
|
+
// Core deletes the artifact with its last release, and the patches
|
|
194
|
+
// built on it, so the bundle reads can change too.
|
|
318
195
|
await Promise.all([
|
|
319
196
|
queryClient.invalidateQueries({ queryKey: queryKeys.releases.all }),
|
|
320
197
|
queryClient.invalidateQueries({ queryKey: ["release-catalog"] }),
|
|
321
198
|
queryClient.invalidateQueries({ queryKey: queryKeys.channels }),
|
|
199
|
+
queryClient.invalidateQueries({ queryKey: ["bundle"] }),
|
|
200
|
+
queryClient.invalidateQueries({
|
|
201
|
+
queryKey: queryKeys.bundleChildren.all,
|
|
202
|
+
}),
|
|
322
203
|
]);
|
|
323
204
|
},
|
|
324
205
|
});
|
|
@@ -6,12 +6,32 @@ import { getBundleActivityRpc } from "./insights-recovery-rpc";
|
|
|
6
6
|
export type BundleActivityInput = Pick<
|
|
7
7
|
RecoveryInput,
|
|
8
8
|
"platform" | "channel"
|
|
9
|
-
> & {
|
|
9
|
+
> & {
|
|
10
|
+
readonly releaseId: string;
|
|
11
|
+
/** The release runs the native build's built-in bundle, which nothing downloads. */
|
|
12
|
+
readonly builtIn?: boolean;
|
|
13
|
+
};
|
|
10
14
|
|
|
15
|
+
export type BundleActivityReport = {
|
|
16
|
+
/** Installations that downloaded the release. */
|
|
17
|
+
readonly downloads: number;
|
|
18
|
+
/** Installations that reported running it, once each at the first such report. */
|
|
19
|
+
readonly launched: number;
|
|
20
|
+
/** Launches that crashed on it and rolled back. */
|
|
21
|
+
readonly failedLaunches: number;
|
|
22
|
+
readonly measuredAtMs: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Release activity for bundle rows; mounted only where the console reads it. */
|
|
11
26
|
export function useBundleActivityQuery(inputs: readonly BundleActivityInput[]) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
27
|
+
// The server reads only what names a release.
|
|
28
|
+
const sorted = inputs
|
|
29
|
+
.map(({ releaseId, platform, channel }) => ({
|
|
30
|
+
releaseId,
|
|
31
|
+
platform,
|
|
32
|
+
channel,
|
|
33
|
+
}))
|
|
34
|
+
.sort((a, b) => a.releaseId.localeCompare(b.releaseId));
|
|
15
35
|
return useQuery({
|
|
16
36
|
queryKey: ["bundle-activity", sorted],
|
|
17
37
|
queryFn: () => getBundleActivityRpc({ data: sorted }),
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createMiddleware } from "@tanstack/react-start";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Refuses a caller the console does not authorize before a server function
|
|
5
|
+
* reads its input. TanStack Start runs a function's middleware before the
|
|
6
|
+
* function's own validator, so an anonymous or unlisted caller gets the access
|
|
7
|
+
* check's 401 or 403, never an input error, whatever it sends. Every server
|
|
8
|
+
* function except the sign-in reads in `auth-rpc.ts` uses it, and
|
|
9
|
+
* `console-access.spec.ts` checks that.
|
|
10
|
+
*/
|
|
11
|
+
export const consoleAccess = createMiddleware({ type: "function" }).server(
|
|
12
|
+
async ({ next }) => {
|
|
13
|
+
const [{ getRequest }, { requireConsoleAccess }] = await Promise.all([
|
|
14
|
+
import("@tanstack/react-start/server"),
|
|
15
|
+
import("./server/auth.server"),
|
|
16
|
+
]);
|
|
17
|
+
await requireConsoleAccess(getRequest());
|
|
18
|
+
return next();
|
|
19
|
+
},
|
|
20
|
+
);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type QueryClient,
|
|
3
|
+
queryOptions,
|
|
4
|
+
useQuery,
|
|
5
|
+
} from "@tanstack/react-query";
|
|
6
|
+
import { notFound } from "@tanstack/react-router";
|
|
7
|
+
|
|
8
|
+
import { type ConsoleFeature, consoleFeatures } from "./console-features";
|
|
9
|
+
import { getConsoleFeaturesRpc } from "./console-features-rpc";
|
|
10
|
+
|
|
11
|
+
export const consoleFeaturesQueryKey = ["console", "features"] as const;
|
|
12
|
+
|
|
13
|
+
export const consoleFeaturesQueryOptions = () =>
|
|
14
|
+
queryOptions({
|
|
15
|
+
queryKey: consoleFeaturesQueryKey,
|
|
16
|
+
queryFn: () => getConsoleFeaturesRpc(),
|
|
17
|
+
// A server's plugins change only when it restarts: one read serves a visit.
|
|
18
|
+
staleTime: Infinity,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/** The features the console serves; the authorized layout loads them first. */
|
|
22
|
+
export const useConsoleFeatures = () => useQuery(consoleFeaturesQueryOptions());
|
|
23
|
+
|
|
24
|
+
/** Whether `feature` is on; false until the features load. */
|
|
25
|
+
export const useConsoleFeature = (feature: ConsoleFeature): boolean =>
|
|
26
|
+
useConsoleFeatures().data?.features[feature] === true;
|
|
27
|
+
|
|
28
|
+
/** A feature route's not-found data: the feature its guard found off. */
|
|
29
|
+
type ConsoleFeatureNotFoundData = { readonly feature: ConsoleFeature };
|
|
30
|
+
|
|
31
|
+
/** The feature in a route's not-found data, when a feature guard threw it. */
|
|
32
|
+
export const notFoundFeature = (data: unknown): ConsoleFeature | undefined => {
|
|
33
|
+
const feature =
|
|
34
|
+
typeof data === "object" && data !== null
|
|
35
|
+
? Reflect.get(data, "feature")
|
|
36
|
+
: undefined;
|
|
37
|
+
return typeof feature === "string" && Object.hasOwn(consoleFeatures, feature)
|
|
38
|
+
? (feature as ConsoleFeature)
|
|
39
|
+
: undefined;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A feature route's `beforeLoad`: not found, naming the feature, when the
|
|
44
|
+
* console does not serve it. A failed features read lets the route load: the
|
|
45
|
+
* root layout answers a visitor it does not authorize, and the route's own
|
|
46
|
+
* reads report any other failure. The failure leaves the cache, which a
|
|
47
|
+
* server render sends to the browser: a refused visitor's failure is the 401
|
|
48
|
+
* or 403 Response, which the render cannot serialize.
|
|
49
|
+
*/
|
|
50
|
+
export const requireConsoleFeature = async (
|
|
51
|
+
queryClient: QueryClient,
|
|
52
|
+
feature: ConsoleFeature,
|
|
53
|
+
): Promise<void> => {
|
|
54
|
+
const featureSet = await queryClient
|
|
55
|
+
.ensureQueryData(consoleFeaturesQueryOptions())
|
|
56
|
+
.catch(() => {
|
|
57
|
+
queryClient.removeQueries({
|
|
58
|
+
queryKey: consoleFeaturesQueryKey,
|
|
59
|
+
exact: true,
|
|
60
|
+
});
|
|
61
|
+
return undefined;
|
|
62
|
+
});
|
|
63
|
+
if (featureSet !== undefined && !featureSet.features[feature]) {
|
|
64
|
+
throw notFound({
|
|
65
|
+
data: { feature } satisfies ConsoleFeatureNotFoundData,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
2
|
+
|
|
3
|
+
import { consoleAccess } from "./console-access";
|
|
4
|
+
import type { ConsoleFeatureSet } from "./console-features";
|
|
5
|
+
|
|
6
|
+
/** The features this console serves, for navigation and route guards. */
|
|
7
|
+
export const getConsoleFeaturesRpc = createServerFn({ method: "GET" })
|
|
8
|
+
.middleware([consoleAccess])
|
|
9
|
+
.handler(async (): Promise<ConsoleFeatureSet> => {
|
|
10
|
+
const { prepareConfig } = await import("./server/config.server");
|
|
11
|
+
const { runtime } = await prepareConfig();
|
|
12
|
+
return { features: await runtime.features(), remote: runtime.remote };
|
|
13
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The console's features and the server plugin each one needs. The console
|
|
3
|
+
* serves a feature only when its config lists the plugin, as the server runs
|
|
4
|
+
* it, and against a self-hosted server (`standaloneRepository`) only the
|
|
5
|
+
* features its admin API serves (`remote`); the rest read the database.
|
|
6
|
+
* Navigation, route guards, and server functions all read this registry. Its
|
|
7
|
+
* other halves, keyed by the same features, hold what serves each feature on
|
|
8
|
+
* the server (`server/console-feature-apis.ts`) and what each adds to pages
|
|
9
|
+
* (`components/features/FeatureSlots.tsx`). Their order is the sidebar's,
|
|
10
|
+
* below Bundles.
|
|
11
|
+
*/
|
|
12
|
+
export const consoleFeatures = {
|
|
13
|
+
/** Event history and installation lookups, which the admin API serves too. */
|
|
14
|
+
insights: { plugin: "insights", label: "Insights", remote: true },
|
|
15
|
+
/** App usage, distribution, and release activity, read from the database. */
|
|
16
|
+
insightsAnalytics: { plugin: "insights", label: "Insights", remote: false },
|
|
17
|
+
/** Template editing, publishing, and rollback, which the admin API serves too. */
|
|
18
|
+
remoteConfig: {
|
|
19
|
+
plugin: "remoteConfig",
|
|
20
|
+
label: "Remote Config",
|
|
21
|
+
remote: true,
|
|
22
|
+
},
|
|
23
|
+
/** API key management: `apiKeys()` mounts no admin routes to manage them. */
|
|
24
|
+
apiKeys: { plugin: "apiKeys", label: "API keys", remote: false },
|
|
25
|
+
} as const satisfies Readonly<
|
|
26
|
+
Record<
|
|
27
|
+
string,
|
|
28
|
+
{
|
|
29
|
+
readonly plugin: string;
|
|
30
|
+
/** How navigation and the not-installed page name the feature. */
|
|
31
|
+
readonly label: string;
|
|
32
|
+
readonly remote: boolean;
|
|
33
|
+
}
|
|
34
|
+
>
|
|
35
|
+
>;
|
|
36
|
+
|
|
37
|
+
export type ConsoleFeature = keyof typeof consoleFeatures;
|
|
38
|
+
|
|
39
|
+
/** Whether each feature is on. */
|
|
40
|
+
export type ConsoleFeatures = Readonly<Record<ConsoleFeature, boolean>>;
|
|
41
|
+
|
|
42
|
+
/** What the console serves, as its client reads it. */
|
|
43
|
+
export type ConsoleFeatureSet = {
|
|
44
|
+
readonly features: ConsoleFeatures;
|
|
45
|
+
/** Whether the console reaches a self-hosted server through its admin API. */
|
|
46
|
+
readonly remote: boolean;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const featureIds = Object.keys(consoleFeatures) as ConsoleFeature[];
|
|
50
|
+
|
|
51
|
+
/** The features on for the plugins the console's config lists, from where the console reads them. */
|
|
52
|
+
export const resolveConsoleFeatures = (
|
|
53
|
+
plugins: readonly string[],
|
|
54
|
+
{ remote }: { readonly remote: boolean },
|
|
55
|
+
): ConsoleFeatures => {
|
|
56
|
+
const installed = new Set(plugins);
|
|
57
|
+
return Object.fromEntries(
|
|
58
|
+
featureIds.map((feature) => {
|
|
59
|
+
const { plugin, remote: servedRemotely } = consoleFeatures[feature];
|
|
60
|
+
return [feature, installed.has(plugin) && (servedRemotely || !remote)];
|
|
61
|
+
}),
|
|
62
|
+
) as Record<ConsoleFeature, boolean>;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** Why a feature is off: its plugin is missing, or it reads a database the console does not open. */
|
|
66
|
+
export const unavailableReason = (
|
|
67
|
+
feature: ConsoleFeature,
|
|
68
|
+
{ remote }: { readonly remote: boolean },
|
|
69
|
+
): "plugin" | "remote" =>
|
|
70
|
+
remote && !consoleFeatures[feature].remote ? "remote" : "plugin";
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A server function refused a feature the console does not serve: the
|
|
74
|
+
* features' 404. The client recognizes it by `name`, which survives
|
|
75
|
+
* serialization where the class does not.
|
|
76
|
+
*/
|
|
77
|
+
export class ConsoleFeatureUnavailableError extends Error {
|
|
78
|
+
override readonly name = "ConsoleFeatureUnavailableError";
|
|
79
|
+
readonly status = 404;
|
|
80
|
+
|
|
81
|
+
constructor(
|
|
82
|
+
readonly feature: ConsoleFeature,
|
|
83
|
+
{ remote }: { readonly remote: boolean },
|
|
84
|
+
) {
|
|
85
|
+
super(
|
|
86
|
+
unavailableReason(feature, { remote }) === "remote"
|
|
87
|
+
? "The console reads this from the database, and it reaches a self-hosted server through its admin API instead."
|
|
88
|
+
: `The server runs without the ${consoleFeatures[feature].plugin}() plugin.`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const isConsoleFeatureUnavailableError = (
|
|
94
|
+
error: unknown,
|
|
95
|
+
): error is Error & { readonly feature: ConsoleFeature } =>
|
|
96
|
+
error instanceof Error &&
|
|
97
|
+
error.name === "ConsoleFeatureUnavailableError" &&
|
|
98
|
+
featureIds.includes(Reflect.get(error, "feature") as ConsoleFeature);
|
package/src/lib/insights-api.ts
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
|
-
import type { InsightsEventPageInput } from "@hot-updater/server";
|
|
2
|
-
import { useQuery } from "@tanstack/react-query";
|
|
1
|
+
import type { InsightsEventPageInput } from "@hot-updater/server/plugins";
|
|
2
|
+
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
|
|
3
3
|
|
|
4
|
+
import type { FailureReportsInput } from "./insights-errors";
|
|
5
|
+
import {
|
|
6
|
+
readUpdateFailuresInput,
|
|
7
|
+
type UpdateFailuresInput,
|
|
8
|
+
} from "./insights-failures";
|
|
9
|
+
import {
|
|
10
|
+
getUpdateFailuresRpc,
|
|
11
|
+
listFailureReportsRpc,
|
|
12
|
+
} from "./insights-failures-rpc";
|
|
13
|
+
import {
|
|
14
|
+
DEFAULT_INSIGHTS_RETENTION,
|
|
15
|
+
type InsightsRetentionDays,
|
|
16
|
+
} from "./insights-retention";
|
|
4
17
|
import {
|
|
5
18
|
findInsightsInstallationsRpc,
|
|
6
19
|
getInsightsInstallationRpc,
|
|
20
|
+
getInsightsRetentionRpc,
|
|
7
21
|
getReportingInstallationsRpc,
|
|
8
22
|
listInsightsEventsRpc,
|
|
9
23
|
listInsightsInstallationEventsRpc,
|
|
@@ -98,3 +112,43 @@ export const useInsightsInstallationEventsQuery = (
|
|
|
98
112
|
refetchOnWindowFocus: true,
|
|
99
113
|
staleTime: STALE_TIME_MS,
|
|
100
114
|
});
|
|
115
|
+
|
|
116
|
+
/** A release's or a channel's update failures; lifetime for a release without a window. */
|
|
117
|
+
export const useUpdateFailuresQuery = (
|
|
118
|
+
input: UpdateFailuresInput,
|
|
119
|
+
enabled = true,
|
|
120
|
+
) =>
|
|
121
|
+
useQuery({
|
|
122
|
+
queryKey: ["insights", "update-failures", input],
|
|
123
|
+
queryFn: () =>
|
|
124
|
+
getUpdateFailuresRpc({ data: readUpdateFailuresInput(input) }),
|
|
125
|
+
enabled,
|
|
126
|
+
refetchOnWindowFocus: true,
|
|
127
|
+
staleTime: STALE_TIME_MS,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* How long the server keeps Insights rows; the defaults until it answers, or
|
|
132
|
+
* when it does not report them.
|
|
133
|
+
*/
|
|
134
|
+
export const useInsightsRetention = (): InsightsRetentionDays =>
|
|
135
|
+
useQuery({
|
|
136
|
+
queryKey: ["insights", "retention"],
|
|
137
|
+
queryFn: () => getInsightsRetentionRpc(),
|
|
138
|
+
// A server's periods change only when it restarts.
|
|
139
|
+
staleTime: Infinity,
|
|
140
|
+
}).data ?? DEFAULT_INSIGHTS_RETENTION;
|
|
141
|
+
|
|
142
|
+
export const useFailureReportsQuery = (
|
|
143
|
+
input: FailureReportsInput,
|
|
144
|
+
enabled: boolean,
|
|
145
|
+
) =>
|
|
146
|
+
useInfiniteQuery({
|
|
147
|
+
queryKey: ["insights", "failure-reports", input],
|
|
148
|
+
queryFn: ({ pageParam }) =>
|
|
149
|
+
listFailureReportsRpc({ data: { ...input, cursor: pageParam } }),
|
|
150
|
+
initialPageParam: undefined as string | undefined,
|
|
151
|
+
getNextPageParam: (page) => page.nextCursor ?? undefined,
|
|
152
|
+
enabled,
|
|
153
|
+
staleTime: STALE_TIME_MS,
|
|
154
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { UpdateFailuresInput } from "./insights-failures";
|
|
2
|
+
import { readUpdateFailuresInput } from "./insights-failures";
|
|
3
|
+
import type { InsightsEventRow } from "./insights-view";
|
|
4
|
+
|
|
5
|
+
export type FailureReportsInput = UpdateFailuresInput & {
|
|
6
|
+
readonly beforeReceivedAtMs: number;
|
|
7
|
+
readonly cursor?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type FailureReportsPage = {
|
|
11
|
+
readonly data: readonly InsightsEventRow[];
|
|
12
|
+
readonly nextCursor: string | null;
|
|
13
|
+
readonly scanned: number;
|
|
14
|
+
readonly sinceMs: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function readFailureReportsInput(input: FailureReportsInput) {
|
|
18
|
+
readUpdateFailuresInput(input);
|
|
19
|
+
if (
|
|
20
|
+
!Number.isSafeInteger(input.beforeReceivedAtMs) ||
|
|
21
|
+
input.beforeReceivedAtMs < 0 ||
|
|
22
|
+
(input.cursor !== undefined &&
|
|
23
|
+
(typeof input.cursor !== "string" ||
|
|
24
|
+
!input.cursor.length ||
|
|
25
|
+
input.cursor.length > 8_192))
|
|
26
|
+
) {
|
|
27
|
+
throw new Error("Invalid failure report range or cursor.");
|
|
28
|
+
}
|
|
29
|
+
return input;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Exact text and reported context only; never guess a cause from the message. */
|
|
33
|
+
export function groupFailureReports(events: readonly InsightsEventRow[]) {
|
|
34
|
+
const groups = new Map<string, InsightsEventRow[]>();
|
|
35
|
+
for (const event of events) {
|
|
36
|
+
if (event.type !== "UPDATE_FAILED" || !event.failure) continue;
|
|
37
|
+
const f = event.failure;
|
|
38
|
+
const key = JSON.stringify([
|
|
39
|
+
f.stage,
|
|
40
|
+
f.reason,
|
|
41
|
+
f.errorMessage ?? null,
|
|
42
|
+
f.resource ?? null,
|
|
43
|
+
f.httpStatus ?? null,
|
|
44
|
+
f.originCode ?? null,
|
|
45
|
+
f.transport ?? null,
|
|
46
|
+
]);
|
|
47
|
+
const group = groups.get(key) ?? [];
|
|
48
|
+
group.push(event);
|
|
49
|
+
groups.set(key, group);
|
|
50
|
+
}
|
|
51
|
+
return [...groups]
|
|
52
|
+
.map(([key, reports]) => ({
|
|
53
|
+
key,
|
|
54
|
+
reports: reports.sort((a, b) => b.receivedAtMs - a.receivedAtMs),
|
|
55
|
+
installations: new Set(reports.map((event) => event.installId)).size,
|
|
56
|
+
}))
|
|
57
|
+
.sort(
|
|
58
|
+
(a, b) =>
|
|
59
|
+
b.reports.length - a.reports.length ||
|
|
60
|
+
b.reports[0]!.receivedAtMs - a.reports[0]!.receivedAtMs,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type FailureGroup = ReturnType<typeof groupFailureReports>[number];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
2
|
+
|
|
3
|
+
import { consoleAccess } from "./console-access";
|
|
4
|
+
import { readFailureReportsInput } from "./insights-errors";
|
|
5
|
+
import { readUpdateFailuresInput } from "./insights-failures";
|
|
6
|
+
|
|
7
|
+
export const getUpdateFailuresRpc = createServerFn({ method: "GET" })
|
|
8
|
+
.middleware([consoleAccess])
|
|
9
|
+
.validator(readUpdateFailuresInput)
|
|
10
|
+
.handler(async ({ data }) => {
|
|
11
|
+
const [{ prepareConfig }, { getUpdateFailuresReport }, { requireFeature }] =
|
|
12
|
+
await Promise.all([
|
|
13
|
+
import("./server/config.server"),
|
|
14
|
+
import("./server/updateFailures"),
|
|
15
|
+
import("./server/runtime.server"),
|
|
16
|
+
]);
|
|
17
|
+
const { runtime } = await prepareConfig();
|
|
18
|
+
return getUpdateFailuresReport(
|
|
19
|
+
await requireFeature(runtime, "insights"),
|
|
20
|
+
data,
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const listFailureReportsRpc = createServerFn({ method: "GET" })
|
|
25
|
+
.middleware([consoleAccess])
|
|
26
|
+
.validator(readFailureReportsInput)
|
|
27
|
+
.handler(async ({ data }) => {
|
|
28
|
+
const [{ prepareConfig }, { listFailureReports }, { requireFeature }] =
|
|
29
|
+
await Promise.all([
|
|
30
|
+
import("./server/config.server"),
|
|
31
|
+
import("./server/failureReports"),
|
|
32
|
+
import("./server/runtime.server"),
|
|
33
|
+
]);
|
|
34
|
+
const { runtime } = await prepareConfig();
|
|
35
|
+
return listFailureReports(await requireFeature(runtime, "insights"), data);
|
|
36
|
+
});
|