@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
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { HotUpdaterCoreApi, ReleaseRow } from "@hot-updater/plugin-core";
|
|
2
|
+
import type {
|
|
3
|
+
InsightsBundleEventFilter,
|
|
4
|
+
InsightsModel,
|
|
5
|
+
} from "@hot-updater/server/plugins";
|
|
6
|
+
|
|
7
|
+
import { insightsPeriodEnd, recoveryWindows } from "../insights-recovery";
|
|
8
|
+
import {
|
|
9
|
+
ADOPTION_CANDIDATES,
|
|
10
|
+
type AdoptionRelease,
|
|
11
|
+
type AdoptionReleaseInput,
|
|
12
|
+
type AdoptionReleaseResult,
|
|
13
|
+
type AdoptionReleasesInput,
|
|
14
|
+
type BundleEventsInput,
|
|
15
|
+
type BundleEventsSeries,
|
|
16
|
+
readAdoptionReleaseInput,
|
|
17
|
+
readAdoptionReleasesInput,
|
|
18
|
+
readBundleEventsInput,
|
|
19
|
+
} from "../release-adoption";
|
|
20
|
+
|
|
21
|
+
type ReleaseReads = Pick<
|
|
22
|
+
HotUpdaterCoreApi,
|
|
23
|
+
"findChannelByName" | "getRelease" | "listReleases"
|
|
24
|
+
>;
|
|
25
|
+
|
|
26
|
+
/** Rows read past a focused release to find the bundle before it. */
|
|
27
|
+
const PREVIOUS_ROWS = 4;
|
|
28
|
+
|
|
29
|
+
const labelOf = (release: ReleaseRow): AdoptionRelease => ({
|
|
30
|
+
releaseId: release.id,
|
|
31
|
+
bundleId: release.bundle_id!,
|
|
32
|
+
deployedAtMs: release.created_at_ms,
|
|
33
|
+
targetAppVersion: release.target_app_version,
|
|
34
|
+
enabled: release.enabled,
|
|
35
|
+
revision: release.revision,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// A release without a bundle has nothing to apply.
|
|
39
|
+
const bundles = (releases: readonly ReleaseRow[]) =>
|
|
40
|
+
releases.filter((release) => release.bundle_id).map(labelOf);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The newest bundle deployments of a channel and platform, newest first: one
|
|
44
|
+
* read of the channel and one of its newest releases.
|
|
45
|
+
*/
|
|
46
|
+
export async function listAdoptionReleases(
|
|
47
|
+
core: ReleaseReads,
|
|
48
|
+
input: AdoptionReleasesInput,
|
|
49
|
+
): Promise<readonly AdoptionRelease[]> {
|
|
50
|
+
const { platform, channel } = readAdoptionReleasesInput(input);
|
|
51
|
+
const row = await core.findChannelByName(channel);
|
|
52
|
+
if (row === null) return [];
|
|
53
|
+
return bundles(
|
|
54
|
+
await core.listReleases({
|
|
55
|
+
filter: { kind: "channelPlatform", channelId: row.id, platform },
|
|
56
|
+
order: "desc",
|
|
57
|
+
limit: ADOPTION_CANDIDATES,
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* One bundle deployment of the channel and platform, for a focus the newest
|
|
64
|
+
* ones leave out, and with `withPrevious` the bundle deployed before it.
|
|
65
|
+
*/
|
|
66
|
+
export async function getAdoptionRelease(
|
|
67
|
+
core: ReleaseReads,
|
|
68
|
+
input: AdoptionReleaseInput,
|
|
69
|
+
): Promise<AdoptionReleaseResult> {
|
|
70
|
+
const { platform, channel, releaseId, withPrevious } =
|
|
71
|
+
readAdoptionReleaseInput(input);
|
|
72
|
+
const [row, release] = await Promise.all([
|
|
73
|
+
core.findChannelByName(channel),
|
|
74
|
+
core.getRelease(releaseId),
|
|
75
|
+
]);
|
|
76
|
+
if (
|
|
77
|
+
row === null ||
|
|
78
|
+
release === null ||
|
|
79
|
+
!release.bundle_id ||
|
|
80
|
+
release.platform !== platform ||
|
|
81
|
+
release.channel_id !== row.id
|
|
82
|
+
)
|
|
83
|
+
return withPrevious ? { release: null, previous: null } : { release: null };
|
|
84
|
+
if (!withPrevious) return { release: labelOf(release) };
|
|
85
|
+
// Newest first, `after` a release lists older ones.
|
|
86
|
+
const older = await core.listReleases({
|
|
87
|
+
filter: { kind: "channelPlatform", channelId: row.id, platform },
|
|
88
|
+
order: "desc",
|
|
89
|
+
after: release.id,
|
|
90
|
+
limit: PREVIOUS_ROWS,
|
|
91
|
+
});
|
|
92
|
+
return { release: labelOf(release), previous: bundles(older)[0] ?? null };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* One bundle's counts of one type in each interval of the period, from
|
|
97
|
+
* their hourly counts, never the events or the release table. Downloads are
|
|
98
|
+
* the bundle's download hours, which count the downloads a launch or crash
|
|
99
|
+
* implied too. Launches add two series: apply reports, and the kept
|
|
100
|
+
* UNCHANGED reports that moved an installation to the bundle with no apply
|
|
101
|
+
* report.
|
|
102
|
+
*/
|
|
103
|
+
export async function getBundleEvents(
|
|
104
|
+
model: Pick<InsightsModel, "countEventSeries">,
|
|
105
|
+
input: BundleEventsInput,
|
|
106
|
+
now = Date.now(),
|
|
107
|
+
): Promise<BundleEventsSeries> {
|
|
108
|
+
const { platform, channel, window, endMs, bundleId, type } =
|
|
109
|
+
readBundleEventsInput(input);
|
|
110
|
+
const { durationMs, intervalMs } = recoveryWindows[window];
|
|
111
|
+
const end = Math.min(endMs, insightsPeriodEnd(now));
|
|
112
|
+
const timeRange = { start: Math.max(0, end - durationMs), end };
|
|
113
|
+
const series = (filter: InsightsBundleEventFilter) =>
|
|
114
|
+
model.countEventSeries({ filter, timeRange, intervalMs });
|
|
115
|
+
const points =
|
|
116
|
+
type === "RECOVERED"
|
|
117
|
+
? await series({ platform, channel, type, fromBundleId: bundleId })
|
|
118
|
+
: type === "DOWNLOADED"
|
|
119
|
+
? await series({
|
|
120
|
+
platform,
|
|
121
|
+
channel,
|
|
122
|
+
type: "UPDATE_DOWNLOADED",
|
|
123
|
+
toBundleId: bundleId,
|
|
124
|
+
})
|
|
125
|
+
: await Promise.all([
|
|
126
|
+
series({
|
|
127
|
+
platform,
|
|
128
|
+
channel,
|
|
129
|
+
type: "UPDATE_APPLIED",
|
|
130
|
+
toBundleId: bundleId,
|
|
131
|
+
}),
|
|
132
|
+
series({
|
|
133
|
+
platform,
|
|
134
|
+
channel,
|
|
135
|
+
type: "UNCHANGED",
|
|
136
|
+
toBundleId: bundleId,
|
|
137
|
+
}),
|
|
138
|
+
]).then(([applied, moved]) =>
|
|
139
|
+
applied.map((point, index) => ({
|
|
140
|
+
startMs: point.startMs,
|
|
141
|
+
events: point.events + (moved[index]?.events ?? 0),
|
|
142
|
+
})),
|
|
143
|
+
);
|
|
144
|
+
return {
|
|
145
|
+
bundleId,
|
|
146
|
+
type,
|
|
147
|
+
measuredAtMs: now,
|
|
148
|
+
points,
|
|
149
|
+
total: points.reduce((sum, point) => sum + point.events, 0),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
isReleaseEligibleForCohort,
|
|
3
|
-
NUMERIC_COHORT_SIZE,
|
|
4
|
-
} from "@hot-updater/core";
|
|
5
1
|
import type {
|
|
6
2
|
CompiledReleaseCatalog,
|
|
7
|
-
|
|
3
|
+
HotUpdaterCoreApi,
|
|
8
4
|
ReleaseRow,
|
|
9
5
|
} from "@hot-updater/plugin-core";
|
|
6
|
+
import {
|
|
7
|
+
isReleaseEligibleForCohort,
|
|
8
|
+
NUMERIC_COHORT_SIZE,
|
|
9
|
+
} from "@hot-updater/protocol";
|
|
10
10
|
|
|
11
11
|
export type ReleaseReachabilityRow = ReleaseRow & {
|
|
12
12
|
readonly currentlyUnreachable: boolean;
|
|
@@ -37,7 +37,6 @@ function collectReachableFromIndexes(
|
|
|
37
37
|
for (const cohort of cohorts) {
|
|
38
38
|
const selected = descriptors.find(
|
|
39
39
|
(descriptor) =>
|
|
40
|
-
descriptor.kind === "BUNDLE" &&
|
|
41
40
|
descriptor.bundleId !== null &&
|
|
42
41
|
isReleaseEligibleForCohort(descriptor, cohort),
|
|
43
42
|
);
|
|
@@ -63,15 +62,16 @@ export function collectCurrentlyReachableReleaseIds(
|
|
|
63
62
|
return reachableReleaseIds;
|
|
64
63
|
}
|
|
65
64
|
|
|
65
|
+
/** Marks each release its scope's compiled catalog no longer serves: one catalog read per scope. */
|
|
66
66
|
export async function addReleaseReachability(
|
|
67
|
-
|
|
67
|
+
core: Pick<HotUpdaterCoreApi, "getReleaseCatalogRow">,
|
|
68
68
|
releases: readonly ReleaseRow[],
|
|
69
69
|
): Promise<readonly ReleaseReachabilityRow[]> {
|
|
70
70
|
const scopeKeys = [...new Set(releases.map(({ scope_key }) => scope_key))];
|
|
71
71
|
const reachableByScope = new Map(
|
|
72
72
|
await Promise.all(
|
|
73
73
|
scopeKeys.map(async (scopeKey) => {
|
|
74
|
-
const row = await
|
|
74
|
+
const row = await core.getReleaseCatalogRow(scopeKey);
|
|
75
75
|
const reachableReleaseIds =
|
|
76
76
|
row === null
|
|
77
77
|
? new Set<string>()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { RemoteDatabase } from "@hot-updater/plugin-core";
|
|
2
|
+
import {
|
|
3
|
+
createRemoteConfigAdminApi,
|
|
4
|
+
type RemoteConfigActive,
|
|
5
|
+
type RemoteConfigAdminApi,
|
|
6
|
+
type RemoteConfigPublishResult,
|
|
7
|
+
type RemoteConfigTemplateIssue,
|
|
8
|
+
RemoteConfigValidationError,
|
|
9
|
+
type RemoteConfigVersionDetail,
|
|
10
|
+
type RemoteConfigVersionsPage,
|
|
11
|
+
} from "@hot-updater/server/plugins";
|
|
12
|
+
|
|
13
|
+
import { ConsoleFeatureUnavailableError } from "../console-features";
|
|
14
|
+
|
|
15
|
+
/** How a publish from the console ended; `invalid` lists what to fix. */
|
|
16
|
+
export type ConsolePublishResult =
|
|
17
|
+
| RemoteConfigPublishResult
|
|
18
|
+
| {
|
|
19
|
+
readonly status: "invalid";
|
|
20
|
+
readonly issues: readonly RemoteConfigTemplateIssue[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** The Remote Config the console pages use. */
|
|
24
|
+
export interface ConsoleRemoteConfig {
|
|
25
|
+
getActive(): Promise<RemoteConfigActive>;
|
|
26
|
+
listVersions(input: {
|
|
27
|
+
readonly limit?: number;
|
|
28
|
+
readonly cursor?: string;
|
|
29
|
+
}): Promise<RemoteConfigVersionsPage>;
|
|
30
|
+
getVersion(version: number): Promise<RemoteConfigVersionDetail | null>;
|
|
31
|
+
publish(input: {
|
|
32
|
+
readonly template: unknown;
|
|
33
|
+
readonly baseVersion: number;
|
|
34
|
+
readonly description?: string;
|
|
35
|
+
}): Promise<ConsolePublishResult>;
|
|
36
|
+
rollback(input: {
|
|
37
|
+
readonly version: number;
|
|
38
|
+
readonly baseVersion: number;
|
|
39
|
+
}): Promise<ConsolePublishResult | { readonly status: "not_found" }>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const invalidOf = (error: unknown): ConsolePublishResult => {
|
|
43
|
+
if (error instanceof RemoteConfigValidationError) {
|
|
44
|
+
return { status: "invalid", issues: error.issues };
|
|
45
|
+
}
|
|
46
|
+
throw error;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Remote Config over the `remoteConfig()` API the console assembled on the
|
|
51
|
+
* database, or over the admin routes, which answer as the API does.
|
|
52
|
+
*/
|
|
53
|
+
export const createLocalRemoteConfig = (
|
|
54
|
+
api: RemoteConfigAdminApi,
|
|
55
|
+
): ConsoleRemoteConfig => ({
|
|
56
|
+
getActive: () => api.getActive(),
|
|
57
|
+
listVersions: (input) => api.listVersions(input),
|
|
58
|
+
getVersion: (version) => api.getVersion(version),
|
|
59
|
+
publish: (input) => api.publish(input).catch(invalidOf),
|
|
60
|
+
rollback: (input) => api.rollback(input),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Remote Config over a self-hosted server's admin routes. A route the server
|
|
65
|
+
* does not mount answers core's 404: it runs without `remoteConfig()`.
|
|
66
|
+
*/
|
|
67
|
+
export const createAdminRemoteConfig = (
|
|
68
|
+
fetchAdmin: RemoteDatabase["fetchAdmin"],
|
|
69
|
+
): ConsoleRemoteConfig =>
|
|
70
|
+
createLocalRemoteConfig(
|
|
71
|
+
createRemoteConfigAdminApi(fetchAdmin, {
|
|
72
|
+
unavailable: () =>
|
|
73
|
+
new ConsoleFeatureUnavailableError("remoteConfig", { remote: true }),
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
@@ -1,36 +1,125 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
DatabasePlugin,
|
|
1
|
+
import {
|
|
2
|
+
type ConfiguredDatabase,
|
|
3
|
+
isRemoteDatabase,
|
|
5
4
|
} from "@hot-updater/plugin-core";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
type ConsoleFeature,
|
|
8
|
+
ConsoleFeatureUnavailableError,
|
|
9
|
+
type ConsoleFeatures,
|
|
10
|
+
consoleFeatures,
|
|
11
|
+
resolveConsoleFeatures,
|
|
12
|
+
} from "../console-features";
|
|
6
13
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from "
|
|
10
|
-
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
type ConsoleFeatureApis,
|
|
15
|
+
consoleFeatureApis,
|
|
16
|
+
} from "./console-feature-apis";
|
|
17
|
+
|
|
18
|
+
export type { ConsoleFeatureApis } from "./console-feature-apis";
|
|
19
|
+
|
|
20
|
+
export interface ConsoleRuntime {
|
|
21
|
+
/** Whether the console reaches a self-hosted server through its admin API. */
|
|
22
|
+
readonly remote: boolean;
|
|
23
|
+
/** The features on here, from the plugins the console's config lists. */
|
|
24
|
+
features(): Promise<ConsoleFeatures>;
|
|
25
|
+
/** What serves the features the console can serve here; {@link requireFeature} hands it out. */
|
|
26
|
+
readonly apis: Partial<ConsoleFeatureApis>;
|
|
15
27
|
}
|
|
16
28
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
30
|
+
typeof value === "object" && value !== null;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Whether one of Hot Updater's own factories made `plugin`: the brand they
|
|
34
|
+
* set under a registered symbol, read here with a copy of the key so the
|
|
35
|
+
* console imports no package's internals. Assembly refuses any other plugin
|
|
36
|
+
* that takes one of their ids, so features key on it.
|
|
37
|
+
*/
|
|
38
|
+
const isOfficialPlugin = (plugin: unknown): boolean =>
|
|
39
|
+
isRecord(plugin) &&
|
|
40
|
+
Reflect.get(plugin, Symbol.for("@hot-updater/server/official-plugin")) ===
|
|
41
|
+
true;
|
|
42
|
+
|
|
43
|
+
const featureIds = Object.keys(consoleFeatureApis) as ConsoleFeature[];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* What serves each feature, built by its factory: over the plugins' APIs the
|
|
47
|
+
* console assembled, or over a self-hosted server's admin API. A feature
|
|
48
|
+
* without its plugin, or without a factory for the admin API, has none.
|
|
49
|
+
*/
|
|
50
|
+
const featureApis = (
|
|
51
|
+
build: (feature: ConsoleFeature) => unknown,
|
|
52
|
+
): Partial<ConsoleFeatureApis> =>
|
|
53
|
+
Object.fromEntries(
|
|
54
|
+
featureIds.flatMap((feature) => {
|
|
55
|
+
const api = build(feature);
|
|
56
|
+
return api === undefined ? [] : [[feature, api]];
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The console's features and what serves them, for the plugins its config
|
|
62
|
+
* lists, the plugins the server runs:
|
|
63
|
+
* - a self-hosted server (`standaloneRepository`) runs them itself; the
|
|
64
|
+
* console serves what that server's admin API serves of their features,
|
|
65
|
+
* and nothing that needs the database;
|
|
66
|
+
* - otherwise the console runs them over the database, as the server does,
|
|
67
|
+
* and serves their features through `api`, their APIs.
|
|
68
|
+
*/
|
|
69
|
+
export const createConsoleRuntime = (config: {
|
|
70
|
+
readonly database: ConfiguredDatabase;
|
|
71
|
+
readonly plugins: readonly unknown[];
|
|
72
|
+
readonly api?: Readonly<Record<string, unknown>>;
|
|
73
|
+
}): ConsoleRuntime => {
|
|
74
|
+
const { database } = config;
|
|
75
|
+
// A feature's plugin is Hot Updater's own, which alone may take its id:
|
|
76
|
+
// the server's assembly refuses any other, and features key on the
|
|
77
|
+
// factory's mark, so another plugin under that id turns on nothing.
|
|
78
|
+
const official = new Set(
|
|
79
|
+
config.plugins
|
|
80
|
+
.filter(isOfficialPlugin)
|
|
81
|
+
.map((plugin) => (plugin as { id: string }).id),
|
|
82
|
+
);
|
|
83
|
+
if (isRemoteDatabase(database)) {
|
|
84
|
+
const { fetchAdmin } = database;
|
|
85
|
+
const features = resolveConsoleFeatures([...official], { remote: true });
|
|
86
|
+
return {
|
|
87
|
+
remote: true,
|
|
88
|
+
features: async () => features,
|
|
89
|
+
apis: featureApis((feature) =>
|
|
90
|
+
consoleFeatureApis[feature].remote?.(fetchAdmin),
|
|
91
|
+
),
|
|
92
|
+
};
|
|
34
93
|
}
|
|
35
|
-
|
|
36
|
-
}
|
|
94
|
+
const api = config.api ?? {};
|
|
95
|
+
const features = resolveConsoleFeatures([...official], { remote: false });
|
|
96
|
+
return {
|
|
97
|
+
remote: false,
|
|
98
|
+
features: async () => features,
|
|
99
|
+
apis: featureApis((feature) => {
|
|
100
|
+
const { plugin } = consoleFeatures[feature];
|
|
101
|
+
const pluginApi = official.has(plugin) ? api[plugin] : undefined;
|
|
102
|
+
return pluginApi === undefined
|
|
103
|
+
? undefined
|
|
104
|
+
: consoleFeatureApis[feature].local(pluginApi as never);
|
|
105
|
+
}),
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* What serves `feature`. Every feature's server function asks for it first,
|
|
111
|
+
* so a feature the console does not serve is refused with one error the
|
|
112
|
+
* client recognizes, before anything is read.
|
|
113
|
+
*/
|
|
114
|
+
export const requireFeature = async <F extends ConsoleFeature>(
|
|
115
|
+
runtime: ConsoleRuntime,
|
|
116
|
+
feature: F,
|
|
117
|
+
): Promise<ConsoleFeatureApis[F]> => {
|
|
118
|
+
const api = runtime.apis[feature];
|
|
119
|
+
if (api === undefined || !(await runtime.features())[feature]) {
|
|
120
|
+
throw new ConsoleFeatureUnavailableError(feature, {
|
|
121
|
+
remote: runtime.remote,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return api as ConsoleFeatureApis[F];
|
|
125
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { InsightsReads } from "@hot-updater/server/plugins";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
checkFailureRate,
|
|
5
|
+
failureRate,
|
|
6
|
+
readUpdateFailuresInput,
|
|
7
|
+
type UpdateFailuresInput,
|
|
8
|
+
type UpdateFailuresReport,
|
|
9
|
+
} from "../insights-failures";
|
|
10
|
+
import { insightsPeriodEnd, recoveryWindows } from "../insights-recovery";
|
|
11
|
+
|
|
12
|
+
/** A release's or a channel's update failures over a period that ends with the current hour. */
|
|
13
|
+
export async function getUpdateFailuresReport(
|
|
14
|
+
reads: Pick<InsightsReads, "getUpdateFailures">,
|
|
15
|
+
input: UpdateFailuresInput,
|
|
16
|
+
now = Date.now(),
|
|
17
|
+
): Promise<UpdateFailuresReport> {
|
|
18
|
+
readUpdateFailuresInput(input);
|
|
19
|
+
const end = insightsPeriodEnd(now);
|
|
20
|
+
const timeRange =
|
|
21
|
+
input.window === undefined
|
|
22
|
+
? undefined
|
|
23
|
+
: {
|
|
24
|
+
start: Math.max(0, end - recoveryWindows[input.window].durationMs),
|
|
25
|
+
end,
|
|
26
|
+
};
|
|
27
|
+
const scope = {
|
|
28
|
+
platform: input.platform,
|
|
29
|
+
channel: input.channel,
|
|
30
|
+
...(input.releaseId === undefined ? {} : { releaseId: input.releaseId }),
|
|
31
|
+
};
|
|
32
|
+
const previousRange =
|
|
33
|
+
timeRange && timeRange.start >= timeRange.end - timeRange.start
|
|
34
|
+
? {
|
|
35
|
+
start: timeRange.start - (timeRange.end - timeRange.start),
|
|
36
|
+
end: timeRange.start,
|
|
37
|
+
}
|
|
38
|
+
: undefined;
|
|
39
|
+
const [failures, previous] = await Promise.all([
|
|
40
|
+
reads.getUpdateFailures({
|
|
41
|
+
...scope,
|
|
42
|
+
...(timeRange === undefined ? {} : { timeRange }),
|
|
43
|
+
}),
|
|
44
|
+
previousRange === undefined
|
|
45
|
+
? undefined
|
|
46
|
+
: reads.getUpdateFailures({ ...scope, timeRange: previousRange }),
|
|
47
|
+
]);
|
|
48
|
+
return {
|
|
49
|
+
...failures,
|
|
50
|
+
startMs: timeRange?.start ?? null,
|
|
51
|
+
endMs: end,
|
|
52
|
+
...(previous === undefined
|
|
53
|
+
? {}
|
|
54
|
+
: {
|
|
55
|
+
previous: {
|
|
56
|
+
attemptRate: failureRate(previous),
|
|
57
|
+
checkRate: previous.checks
|
|
58
|
+
? checkFailureRate(previous.checks)
|
|
59
|
+
: null,
|
|
60
|
+
complete:
|
|
61
|
+
previous.coverage.kind === "complete" &&
|
|
62
|
+
failures.coverage.kind === "complete",
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What a dialog opened for, kept while it animates closed. Closing clears
|
|
5
|
+
* the target at once, and without the kept one the dialog would empty or
|
|
6
|
+
* switch to another state, such as "Delete ?", during its exit animation.
|
|
7
|
+
*/
|
|
8
|
+
export function useDialogTarget<T>(target: T | null): T | null {
|
|
9
|
+
const [shown, setShown] = useState(target);
|
|
10
|
+
if (target !== null && target !== shown) setShown(target);
|
|
11
|
+
return target ?? shown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A number that changes each time `open` turns true, to key a dialog's form
|
|
16
|
+
* so every opening starts from its props, even one that comes before the
|
|
17
|
+
* last close finished animating.
|
|
18
|
+
*/
|
|
19
|
+
export function useOpenSession(open: boolean): number {
|
|
20
|
+
const [session, setSession] = useState(0);
|
|
21
|
+
const [wasOpen, setWasOpen] = useState(open);
|
|
22
|
+
if (open !== wasOpen) {
|
|
23
|
+
setWasOpen(open);
|
|
24
|
+
if (open) setSession(session + 1);
|
|
25
|
+
}
|
|
26
|
+
return session;
|
|
27
|
+
}
|