@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
|
@@ -3,6 +3,7 @@ import { ChartNoAxesCombined, ListIcon } from "lucide-react";
|
|
|
3
3
|
|
|
4
4
|
import { buttonVariants } from "@/components/ui/button";
|
|
5
5
|
import { SidebarTrigger } from "@/components/ui/sidebar";
|
|
6
|
+
import { useConsoleFeature } from "@/lib/console-features-api";
|
|
6
7
|
import type { InsightsSearch } from "@/lib/insights-search";
|
|
7
8
|
import { cn } from "@/lib/utils";
|
|
8
9
|
|
|
@@ -13,6 +14,8 @@ export function InsightsPageHeader({
|
|
|
13
14
|
readonly view: "overview" | "events" | "distribution";
|
|
14
15
|
readonly overviewSearch?: InsightsSearch;
|
|
15
16
|
}) {
|
|
17
|
+
// A self-hosted server's admin API serves events, not the overview.
|
|
18
|
+
const overview = useConsoleFeature("insightsAnalytics");
|
|
16
19
|
return (
|
|
17
20
|
<header className="sticky top-0 z-10 flex shrink-0 flex-wrap items-center gap-3 border-b bg-background px-3 py-3 sm:bg-card/70 sm:px-4 sm:backdrop-blur-sm">
|
|
18
21
|
<SidebarTrigger className="-ml-1 size-11 lg:size-7" />
|
|
@@ -24,20 +27,22 @@ export function InsightsPageHeader({
|
|
|
24
27
|
<h1 className="text-sm font-medium">Insights</h1>
|
|
25
28
|
</div>
|
|
26
29
|
<nav aria-label="Insights views" className="ml-auto flex gap-2">
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
{overview ? (
|
|
31
|
+
<Link
|
|
32
|
+
aria-current={view === "overview" ? "page" : undefined}
|
|
33
|
+
className={cn(
|
|
34
|
+
buttonVariants({
|
|
35
|
+
className: "h-11 px-3 lg:h-8 lg:px-2.5",
|
|
36
|
+
size: "lg",
|
|
37
|
+
variant: view === "overview" ? "secondary" : "ghost",
|
|
38
|
+
}),
|
|
39
|
+
)}
|
|
40
|
+
search={overviewSearch}
|
|
41
|
+
to="/insights"
|
|
42
|
+
>
|
|
43
|
+
Overview
|
|
44
|
+
</Link>
|
|
45
|
+
) : null}
|
|
41
46
|
<Link
|
|
42
47
|
aria-current={view === "events" ? "page" : undefined}
|
|
43
48
|
className={cn(
|
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
2
2
|
import type { InsightsWindow } from "@/lib/insights-api";
|
|
3
|
-
import { usageMetrics } from "@/lib/insights-usage";
|
|
3
|
+
import { usageMetrics, type UsageWindow } from "@/lib/insights-usage";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
/** A segmented control over fixed periods, each shown by its short value. */
|
|
6
|
+
export function PeriodSelector<TPeriod extends string>({
|
|
7
|
+
label,
|
|
8
|
+
onPeriodChange,
|
|
9
|
+
period,
|
|
10
|
+
periods,
|
|
8
11
|
}: {
|
|
9
|
-
readonly
|
|
10
|
-
readonly
|
|
12
|
+
readonly label: string;
|
|
13
|
+
readonly onPeriodChange: (period: TPeriod) => void;
|
|
14
|
+
readonly period: TPeriod;
|
|
15
|
+
readonly periods: readonly {
|
|
16
|
+
readonly value: TPeriod;
|
|
17
|
+
readonly name: string;
|
|
18
|
+
}[];
|
|
11
19
|
}) {
|
|
12
20
|
return (
|
|
13
21
|
<Tabs
|
|
14
|
-
value={
|
|
22
|
+
value={period}
|
|
15
23
|
onValueChange={(value) => {
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
const next = periods.find((item) => item.value === value);
|
|
25
|
+
if (next) onPeriodChange(next.value);
|
|
18
26
|
}}
|
|
19
27
|
>
|
|
20
|
-
<TabsList aria-label=
|
|
21
|
-
{(
|
|
28
|
+
<TabsList aria-label={label} className="min-h-11 sm:min-h-9">
|
|
29
|
+
{periods.map(({ value, name }) => (
|
|
22
30
|
<TabsTrigger
|
|
23
31
|
key={value}
|
|
24
32
|
value={value}
|
|
25
|
-
aria-label={
|
|
33
|
+
aria-label={name}
|
|
26
34
|
className="min-w-12 px-3"
|
|
27
35
|
>
|
|
28
36
|
{value}
|
|
@@ -32,3 +40,50 @@ export function InsightsPeriodSelector({
|
|
|
32
40
|
</Tabs>
|
|
33
41
|
);
|
|
34
42
|
}
|
|
43
|
+
|
|
44
|
+
const reportingPeriods = (["24h", "7d", "30d"] as const).map((value) => ({
|
|
45
|
+
value,
|
|
46
|
+
name: usageMetrics[value].period,
|
|
47
|
+
}));
|
|
48
|
+
const usagePeriods = (Object.keys(usageMetrics) as UsageWindow[]).map(
|
|
49
|
+
(value) => ({ value, name: usageMetrics[value].period }),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
export function InsightsPeriodSelector({
|
|
53
|
+
window,
|
|
54
|
+
onWindowChange,
|
|
55
|
+
}: {
|
|
56
|
+
readonly window: InsightsWindow;
|
|
57
|
+
readonly onWindowChange: (window: InsightsWindow) => void;
|
|
58
|
+
}) {
|
|
59
|
+
return (
|
|
60
|
+
<PeriodSelector
|
|
61
|
+
label="Reporting period"
|
|
62
|
+
onPeriodChange={onWindowChange}
|
|
63
|
+
period={window}
|
|
64
|
+
periods={reportingPeriods}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** App usage's periods: the reporting periods, and 12 months where daily totals cover it. */
|
|
70
|
+
export function UsagePeriodSelector({
|
|
71
|
+
window,
|
|
72
|
+
onWindowChange,
|
|
73
|
+
twelveMonths = true,
|
|
74
|
+
}: {
|
|
75
|
+
readonly window: UsageWindow;
|
|
76
|
+
readonly onWindowChange: (window: UsageWindow) => void;
|
|
77
|
+
readonly twelveMonths?: boolean;
|
|
78
|
+
}) {
|
|
79
|
+
return (
|
|
80
|
+
<PeriodSelector
|
|
81
|
+
label="Reporting period"
|
|
82
|
+
onPeriodChange={onWindowChange}
|
|
83
|
+
period={window}
|
|
84
|
+
periods={usagePeriods.filter(
|
|
85
|
+
({ value }) => twelveMonths || value !== "12m",
|
|
86
|
+
)}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -2,7 +2,13 @@ import { RefreshCw } from "lucide-react";
|
|
|
2
2
|
|
|
3
3
|
import { HashValueDisplay } from "@/components/HashValueDisplay";
|
|
4
4
|
import { Button } from "@/components/ui/button";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
Card,
|
|
7
|
+
CardContent,
|
|
8
|
+
CardDescription,
|
|
9
|
+
CardHeader,
|
|
10
|
+
CardTitle,
|
|
11
|
+
} from "@/components/ui/card";
|
|
6
12
|
import { Separator } from "@/components/ui/separator";
|
|
7
13
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
8
14
|
import {
|
|
@@ -13,33 +19,41 @@ import {
|
|
|
13
19
|
TableHeader,
|
|
14
20
|
TableRow,
|
|
15
21
|
} from "@/components/ui/table";
|
|
22
|
+
import {
|
|
23
|
+
DEFAULT_INSIGHTS_RETENTION,
|
|
24
|
+
formatDays,
|
|
25
|
+
type InsightsRetentionDays,
|
|
26
|
+
} from "@/lib/insights-retention";
|
|
16
27
|
import type {
|
|
17
28
|
InsightsEventRow,
|
|
18
29
|
InsightsInstallationViewRow,
|
|
19
30
|
InsightsViewPage,
|
|
20
31
|
} from "@/lib/insights-view";
|
|
21
32
|
|
|
33
|
+
import { BuiltinBundleBadge, isBuiltinBundle } from "./BuiltinBundle";
|
|
22
34
|
import {
|
|
23
35
|
EventBundleTransition,
|
|
24
36
|
EventTimestamp,
|
|
25
37
|
EventTypeDetails,
|
|
38
|
+
HttpResponseDetails,
|
|
26
39
|
useInsightsTimeFormat,
|
|
27
40
|
} from "./EventDetails";
|
|
28
41
|
import { EventHistoryList } from "./EventHistoryList";
|
|
29
42
|
import { InsightsErrorAlert } from "./InsightsErrorAlert";
|
|
30
43
|
import { InsightsPagination } from "./InsightsPagination";
|
|
31
44
|
|
|
32
|
-
const getUserLabel = (event: {
|
|
33
|
-
|
|
34
|
-
readonly userId: string | null;
|
|
35
|
-
}) => event.userId ?? event.username ?? "—";
|
|
45
|
+
const getUserLabel = (event: { readonly userId: string | null }) =>
|
|
46
|
+
event.userId ?? "—";
|
|
36
47
|
|
|
48
|
+
/** A download or a failed update leaves the installation on the bundle it ran. */
|
|
37
49
|
const getLastKnownBundleId = (
|
|
38
50
|
event: InsightsEventRow | InsightsInstallationViewRow,
|
|
39
51
|
) =>
|
|
40
52
|
"lastKnownBundleId" in event
|
|
41
53
|
? event.lastKnownBundleId
|
|
42
|
-
:
|
|
54
|
+
: event.type === "UPDATE_DOWNLOADED" || event.type === "UPDATE_FAILED"
|
|
55
|
+
? event.fromBundleId
|
|
56
|
+
: event.toBundleId;
|
|
43
57
|
|
|
44
58
|
export function InstallationHistoryCard({
|
|
45
59
|
error,
|
|
@@ -49,6 +63,7 @@ export function InstallationHistoryCard({
|
|
|
49
63
|
onPrevious,
|
|
50
64
|
onRefresh,
|
|
51
65
|
pageNumber,
|
|
66
|
+
retention = DEFAULT_INSIGHTS_RETENTION,
|
|
52
67
|
selectedEvent,
|
|
53
68
|
selectedInstallId,
|
|
54
69
|
}: {
|
|
@@ -59,6 +74,8 @@ export function InstallationHistoryCard({
|
|
|
59
74
|
readonly onNext: () => void;
|
|
60
75
|
readonly onPrevious: () => void;
|
|
61
76
|
readonly pageNumber: number;
|
|
77
|
+
/** How long the server keeps Insights rows. */
|
|
78
|
+
readonly retention?: InsightsRetentionDays;
|
|
62
79
|
readonly selectedEvent:
|
|
63
80
|
| InsightsEventRow
|
|
64
81
|
| InsightsInstallationViewRow
|
|
@@ -70,6 +87,15 @@ export function InstallationHistoryCard({
|
|
|
70
87
|
? getLastKnownBundleId(selectedEvent)
|
|
71
88
|
: undefined;
|
|
72
89
|
|
|
90
|
+
const pendingBundleId =
|
|
91
|
+
selectedEvent && "pendingBundleId" in selectedEvent
|
|
92
|
+
? selectedEvent.pendingBundleId
|
|
93
|
+
: selectedEvent &&
|
|
94
|
+
"type" in selectedEvent &&
|
|
95
|
+
selectedEvent.type === "UPDATE_DOWNLOADED"
|
|
96
|
+
? selectedEvent.toBundleId
|
|
97
|
+
: null;
|
|
98
|
+
|
|
73
99
|
return (
|
|
74
100
|
<Card className="@container min-h-0 min-w-0 shadow-sm">
|
|
75
101
|
<CardHeader className="p-4 sm:p-6">
|
|
@@ -82,6 +108,14 @@ export function InstallationHistoryCard({
|
|
|
82
108
|
: "Select an installation"}
|
|
83
109
|
</h2>
|
|
84
110
|
</CardTitle>
|
|
111
|
+
{selectedInstallId !== undefined ? (
|
|
112
|
+
<CardDescription>
|
|
113
|
+
Bundle changes and update failures are kept for{" "}
|
|
114
|
+
{formatDays(retention.rawDays)}; the latest report, for{" "}
|
|
115
|
+
{formatDays(retention.dailyDays)} after the installation last
|
|
116
|
+
reports.
|
|
117
|
+
</CardDescription>
|
|
118
|
+
) : null}
|
|
85
119
|
</div>
|
|
86
120
|
{onRefresh && selectedInstallId !== undefined ? (
|
|
87
121
|
<Button
|
|
@@ -127,7 +161,7 @@ export function InstallationHistoryCard({
|
|
|
127
161
|
<dt className="text-xs text-muted-foreground">
|
|
128
162
|
Last known bundle
|
|
129
163
|
</dt>
|
|
130
|
-
<dd className="mt-1 font-mono text-xs">
|
|
164
|
+
<dd className="mt-1 flex min-w-0 flex-wrap items-center gap-2 font-mono text-xs">
|
|
131
165
|
{lastKnownBundleId ? (
|
|
132
166
|
<HashValueDisplay
|
|
133
167
|
value={lastKnownBundleId}
|
|
@@ -136,9 +170,30 @@ export function InstallationHistoryCard({
|
|
|
136
170
|
) : (
|
|
137
171
|
"—"
|
|
138
172
|
)}
|
|
173
|
+
{isBuiltinBundle(
|
|
174
|
+
lastKnownBundleId,
|
|
175
|
+
selectedEvent.minBundleId,
|
|
176
|
+
) ? (
|
|
177
|
+
<BuiltinBundleBadge />
|
|
178
|
+
) : null}
|
|
139
179
|
</dd>
|
|
140
180
|
</div>
|
|
181
|
+
{pendingBundleId ? (
|
|
182
|
+
<div className="col-span-2 min-w-0">
|
|
183
|
+
<dt className="text-xs text-muted-foreground">
|
|
184
|
+
Downloaded · Not launched yet
|
|
185
|
+
</dt>
|
|
186
|
+
<dd className="mt-1 text-xs">
|
|
187
|
+
<HashValueDisplay value={pendingBundleId} />
|
|
188
|
+
</dd>
|
|
189
|
+
</div>
|
|
190
|
+
) : null}
|
|
141
191
|
</dl>
|
|
192
|
+
{selectedEvent.httpResponse ? (
|
|
193
|
+
<div className="mt-4">
|
|
194
|
+
<HttpResponseDetails event={selectedEvent} />
|
|
195
|
+
</div>
|
|
196
|
+
) : null}
|
|
142
197
|
</section>
|
|
143
198
|
<Separator />
|
|
144
199
|
</>
|
|
@@ -192,7 +247,7 @@ export function InstallationHistoryCard({
|
|
|
192
247
|
/>
|
|
193
248
|
</TableCell>
|
|
194
249
|
<TableCell>
|
|
195
|
-
<EventTypeDetails
|
|
250
|
+
<EventTypeDetails event={event} />
|
|
196
251
|
</TableCell>
|
|
197
252
|
<TableCell className="whitespace-normal text-xs">
|
|
198
253
|
<div className="flex flex-col gap-2">
|
|
@@ -226,8 +281,8 @@ export function InstallationHistoryCard({
|
|
|
226
281
|
) : (
|
|
227
282
|
<>
|
|
228
283
|
<div className="p-6 text-sm text-muted-foreground">
|
|
229
|
-
{pageNumber > 1
|
|
230
|
-
? "No bundle changes
|
|
284
|
+
{pageNumber > 1
|
|
285
|
+
? "No older bundle changes."
|
|
231
286
|
: "No bundle changes recorded yet."}
|
|
232
287
|
</div>
|
|
233
288
|
{history && (pageNumber > 1 || history.nextCursor) ? (
|
|
@@ -17,7 +17,7 @@ const getLastKnownBundleId = (event: InsightsInstallationViewRow) =>
|
|
|
17
17
|
event.lastKnownBundleId;
|
|
18
18
|
|
|
19
19
|
const getUserLabel = (event: InsightsInstallationViewRow) =>
|
|
20
|
-
event.userId ??
|
|
20
|
+
event.userId ?? "—";
|
|
21
21
|
|
|
22
22
|
export function InstallationMatchesCard({
|
|
23
23
|
error,
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { Link } from "@tanstack/react-router";
|
|
2
|
+
import { ChartLine } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import { buttonVariants } from "@/components/ui/button";
|
|
5
|
+
import {
|
|
6
|
+
Card,
|
|
7
|
+
CardContent,
|
|
8
|
+
CardFooter,
|
|
9
|
+
CardHeader,
|
|
10
|
+
CardTitle,
|
|
11
|
+
} from "@/components/ui/card";
|
|
12
|
+
import { Separator } from "@/components/ui/separator";
|
|
13
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
14
|
+
import type {
|
|
15
|
+
BundleActivityInput,
|
|
16
|
+
BundleActivityReport,
|
|
17
|
+
} from "@/lib/bundle-activity";
|
|
18
|
+
import { useBundleActivityQuery } from "@/lib/bundle-activity";
|
|
19
|
+
import { useConsoleFeature } from "@/lib/console-features-api";
|
|
20
|
+
import { useUpdateFailuresQuery } from "@/lib/insights-api";
|
|
21
|
+
import { failureRate, formatRate } from "@/lib/insights-failures";
|
|
22
|
+
import { adoptionWindow } from "@/lib/insights-search";
|
|
23
|
+
import { cn } from "@/lib/utils";
|
|
24
|
+
|
|
25
|
+
import type { ReleaseColumn } from "../FeatureSlots";
|
|
26
|
+
import { InsightsInfo } from "./InsightsInfo";
|
|
27
|
+
|
|
28
|
+
const activityStats = [
|
|
29
|
+
{ label: "Downloaded", field: "downloads", color: "text-primary/85" },
|
|
30
|
+
{ label: "Launched", field: "launched", color: "text-success/85" },
|
|
31
|
+
{ label: "Crashed", field: "failedLaunches", color: "text-warning/85" },
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
34
|
+
// As Release health rates crashes: over launches plus crashes, since an
|
|
35
|
+
// installation that crashes never reports running the bundle.
|
|
36
|
+
const crashRate = (report: BundleActivityReport): string => {
|
|
37
|
+
const attempts = report.launched + report.failedLaunches;
|
|
38
|
+
return attempts === 0
|
|
39
|
+
? "—"
|
|
40
|
+
: `${((report.failedLaunches / attempts) * 100).toFixed(2)}%`;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Downloads that neither launched nor crashed yet: waiting for a restart, or passed over for a newer bundle. */
|
|
44
|
+
const notLaunched = (report: BundleActivityReport) =>
|
|
45
|
+
Math.max(0, report.downloads - report.launched - report.failedLaunches);
|
|
46
|
+
|
|
47
|
+
function ReleaseMetricsInfo() {
|
|
48
|
+
return (
|
|
49
|
+
<InsightsInfo label="About release insight metrics">
|
|
50
|
+
Downloaded: got this bundle. Launched: ran it. Crashed: crashed on it and
|
|
51
|
+
rolled back. Once every installation restarts, Downloaded = Launched +
|
|
52
|
+
Crashed.
|
|
53
|
+
</InsightsInfo>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function BundleMovementSummary({
|
|
58
|
+
report,
|
|
59
|
+
loading = false,
|
|
60
|
+
input,
|
|
61
|
+
variant = "inline",
|
|
62
|
+
}: {
|
|
63
|
+
readonly report?: BundleActivityReport;
|
|
64
|
+
readonly loading?: boolean;
|
|
65
|
+
readonly input?: BundleActivityInput;
|
|
66
|
+
readonly variant?: "inline" | "card";
|
|
67
|
+
}) {
|
|
68
|
+
if (!report) {
|
|
69
|
+
return loading ? (
|
|
70
|
+
<Skeleton
|
|
71
|
+
aria-label="Loading release insights"
|
|
72
|
+
className={variant === "card" ? "h-16 w-full" : "h-4 w-40"}
|
|
73
|
+
/>
|
|
74
|
+
) : (
|
|
75
|
+
<span
|
|
76
|
+
aria-label="Release insights unavailable"
|
|
77
|
+
className="text-sm text-muted-foreground"
|
|
78
|
+
>
|
|
79
|
+
—
|
|
80
|
+
</span>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
// A release of the built-in bundle comes with the app: nothing downloads it.
|
|
84
|
+
const builtIn = input?.builtIn === true;
|
|
85
|
+
const waiting = builtIn ? 0 : notLaunched(report);
|
|
86
|
+
const metrics = (
|
|
87
|
+
<dl
|
|
88
|
+
className={cn(
|
|
89
|
+
variant === "card"
|
|
90
|
+
? "grid grid-cols-[1fr_1fr_auto] gap-4"
|
|
91
|
+
: "flex min-w-[220px] flex-wrap items-baseline gap-x-3 gap-y-1",
|
|
92
|
+
)}
|
|
93
|
+
>
|
|
94
|
+
{activityStats.map(({ label, field, color }) => (
|
|
95
|
+
<div
|
|
96
|
+
key={field}
|
|
97
|
+
className={cn(
|
|
98
|
+
"flex",
|
|
99
|
+
variant === "card"
|
|
100
|
+
? "min-w-0 flex-col gap-1"
|
|
101
|
+
: "items-baseline gap-1.5 whitespace-nowrap",
|
|
102
|
+
)}
|
|
103
|
+
>
|
|
104
|
+
<dt className="text-xs text-muted-foreground">{label}</dt>
|
|
105
|
+
<dd
|
|
106
|
+
className={cn(
|
|
107
|
+
"flex flex-wrap items-baseline gap-x-1.5 tabular-nums",
|
|
108
|
+
variant === "card"
|
|
109
|
+
? "text-xl font-semibold"
|
|
110
|
+
: "text-sm font-medium",
|
|
111
|
+
report[field] > 0 && !(field === "downloads" && builtIn)
|
|
112
|
+
? color
|
|
113
|
+
: "text-muted-foreground",
|
|
114
|
+
)}
|
|
115
|
+
>
|
|
116
|
+
{field === "downloads" && builtIn ? (
|
|
117
|
+
<span title="The built-in bundle is never downloaded">—</span>
|
|
118
|
+
) : (
|
|
119
|
+
report[field].toLocaleString()
|
|
120
|
+
)}
|
|
121
|
+
{field === "failedLaunches" ? (
|
|
122
|
+
<span className="text-xs font-normal text-muted-foreground">
|
|
123
|
+
({crashRate(report)})
|
|
124
|
+
</span>
|
|
125
|
+
) : null}
|
|
126
|
+
</dd>
|
|
127
|
+
</div>
|
|
128
|
+
))}
|
|
129
|
+
</dl>
|
|
130
|
+
);
|
|
131
|
+
return (
|
|
132
|
+
<div
|
|
133
|
+
className={cn(
|
|
134
|
+
"flex",
|
|
135
|
+
variant === "card" ? "flex-col gap-2" : "items-center gap-1",
|
|
136
|
+
)}
|
|
137
|
+
>
|
|
138
|
+
{input ? (
|
|
139
|
+
<Link
|
|
140
|
+
className="min-w-0 rounded-sm hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
141
|
+
to="/insights"
|
|
142
|
+
search={{
|
|
143
|
+
healthPlatform: input.platform,
|
|
144
|
+
healthChannel: input.channel,
|
|
145
|
+
releaseId: input.releaseId,
|
|
146
|
+
bundleWindow: adoptionWindow(input.releaseId),
|
|
147
|
+
}}
|
|
148
|
+
>
|
|
149
|
+
{metrics}
|
|
150
|
+
</Link>
|
|
151
|
+
) : (
|
|
152
|
+
metrics
|
|
153
|
+
)}
|
|
154
|
+
{variant === "card" && waiting > 0 ? (
|
|
155
|
+
<p className="text-xs text-muted-foreground">
|
|
156
|
+
{waiting.toLocaleString()} downloaded but not launched yet: waiting
|
|
157
|
+
for a restart, or passed over for a newer bundle.
|
|
158
|
+
</p>
|
|
159
|
+
) : null}
|
|
160
|
+
{variant === "inline" ? <ReleaseMetricsInfo /> : null}
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** The release's failed downloads and installs since its first report, and their rate. */
|
|
166
|
+
export function BundleDownloadFailures({
|
|
167
|
+
input,
|
|
168
|
+
}: {
|
|
169
|
+
readonly input: BundleActivityInput;
|
|
170
|
+
}) {
|
|
171
|
+
const query = useUpdateFailuresQuery({
|
|
172
|
+
platform: input.platform,
|
|
173
|
+
channel: input.channel,
|
|
174
|
+
releaseId: input.releaseId,
|
|
175
|
+
});
|
|
176
|
+
const report = query.data;
|
|
177
|
+
return (
|
|
178
|
+
<div className="flex items-center gap-1">
|
|
179
|
+
{report ? (
|
|
180
|
+
<dl>
|
|
181
|
+
<div className="flex flex-wrap items-baseline gap-x-1.5">
|
|
182
|
+
<dt className="text-xs text-muted-foreground">Download failures</dt>
|
|
183
|
+
<dd
|
|
184
|
+
className={cn(
|
|
185
|
+
"flex items-baseline gap-1.5 text-sm font-medium tabular-nums",
|
|
186
|
+
report.failedUpdates > 0
|
|
187
|
+
? "text-warning/85"
|
|
188
|
+
: "text-muted-foreground",
|
|
189
|
+
)}
|
|
190
|
+
>
|
|
191
|
+
{report.failedUpdates.toLocaleString()}
|
|
192
|
+
<span className="text-xs font-normal text-muted-foreground">
|
|
193
|
+
({formatRate(failureRate(report))})
|
|
194
|
+
</span>
|
|
195
|
+
</dd>
|
|
196
|
+
</div>
|
|
197
|
+
</dl>
|
|
198
|
+
) : query.isPending ? (
|
|
199
|
+
<Skeleton aria-label="Loading download failures" className="h-4 w-40" />
|
|
200
|
+
) : (
|
|
201
|
+
<span
|
|
202
|
+
aria-label="Download failures unavailable"
|
|
203
|
+
className="text-sm text-muted-foreground"
|
|
204
|
+
>
|
|
205
|
+
—
|
|
206
|
+
</span>
|
|
207
|
+
)}
|
|
208
|
+
<InsightsInfo label="About download failures">
|
|
209
|
+
Failed downloads or installs of this bundle, at most one per
|
|
210
|
+
installation a day. Rate = failures ÷ (failures + downloads).
|
|
211
|
+
</InsightsInfo>
|
|
212
|
+
</div>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** The bundle detail's Insights card, a section of the release editor: activity and download failures. */
|
|
217
|
+
export function BundleInsightsSummary({
|
|
218
|
+
input,
|
|
219
|
+
}: {
|
|
220
|
+
readonly input: BundleActivityInput;
|
|
221
|
+
}) {
|
|
222
|
+
const query = useBundleActivityQuery([input]);
|
|
223
|
+
return (
|
|
224
|
+
<Card>
|
|
225
|
+
<CardHeader className="flex-row items-center justify-between px-4 pt-4 pb-3">
|
|
226
|
+
<CardTitle className="text-sm font-medium">Insights</CardTitle>
|
|
227
|
+
<ReleaseMetricsInfo />
|
|
228
|
+
</CardHeader>
|
|
229
|
+
<CardContent className="flex flex-col gap-3 px-4 pb-4">
|
|
230
|
+
<BundleMovementSummary
|
|
231
|
+
variant="card"
|
|
232
|
+
input={input}
|
|
233
|
+
loading={query.isFetching}
|
|
234
|
+
report={query.data?.[input.releaseId]}
|
|
235
|
+
/>
|
|
236
|
+
<Separator />
|
|
237
|
+
<BundleDownloadFailures input={input} />
|
|
238
|
+
</CardContent>
|
|
239
|
+
<CardFooter className="px-4 pb-4">
|
|
240
|
+
<Link
|
|
241
|
+
className={buttonVariants({ variant: "outline", size: "sm" })}
|
|
242
|
+
to="/insights"
|
|
243
|
+
search={{
|
|
244
|
+
healthPlatform: input.platform,
|
|
245
|
+
healthChannel: input.channel,
|
|
246
|
+
releaseId: input.releaseId,
|
|
247
|
+
bundleWindow: adoptionWindow(input.releaseId),
|
|
248
|
+
healthChart: "adoption",
|
|
249
|
+
}}
|
|
250
|
+
>
|
|
251
|
+
<ChartLine aria-hidden="true" data-icon="inline-start" />
|
|
252
|
+
View adoption
|
|
253
|
+
</Link>
|
|
254
|
+
</CardFooter>
|
|
255
|
+
</Card>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* The release editor's Insights card where the console reads no release
|
|
261
|
+
* activity, as through a self-hosted server's admin API: download failures
|
|
262
|
+
* alone. With release activity, its card shows them.
|
|
263
|
+
*/
|
|
264
|
+
export function ReleaseFailuresSection({
|
|
265
|
+
input,
|
|
266
|
+
}: {
|
|
267
|
+
readonly input: BundleActivityInput;
|
|
268
|
+
}) {
|
|
269
|
+
const activity = useConsoleFeature("insightsAnalytics");
|
|
270
|
+
if (activity) return null;
|
|
271
|
+
return (
|
|
272
|
+
<Card>
|
|
273
|
+
<CardHeader className="px-4 pt-4 pb-3">
|
|
274
|
+
<CardTitle className="text-sm font-medium">Insights</CardTitle>
|
|
275
|
+
</CardHeader>
|
|
276
|
+
<CardContent className="px-4 pb-4">
|
|
277
|
+
<BundleDownloadFailures input={input} />
|
|
278
|
+
</CardContent>
|
|
279
|
+
</Card>
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* One release's activity in the Bundles page's Insights column. Every cell
|
|
285
|
+
* asks for the page's releases under one query key, so the page makes one
|
|
286
|
+
* request.
|
|
287
|
+
*/
|
|
288
|
+
function ReleaseActivityCell({
|
|
289
|
+
releaseId,
|
|
290
|
+
releases,
|
|
291
|
+
}: {
|
|
292
|
+
readonly releaseId: string;
|
|
293
|
+
readonly releases: readonly BundleActivityInput[];
|
|
294
|
+
}) {
|
|
295
|
+
const query = useBundleActivityQuery(releases);
|
|
296
|
+
return (
|
|
297
|
+
<BundleMovementSummary
|
|
298
|
+
input={releases.find((release) => release.releaseId === releaseId)}
|
|
299
|
+
loading={query.isFetching}
|
|
300
|
+
report={query.data?.[releaseId]}
|
|
301
|
+
/>
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** The Bundles page's Insights column: each release's downloads, launches, and crashes. */
|
|
306
|
+
export const releaseActivityColumn: ReleaseColumn = {
|
|
307
|
+
Cell: ReleaseActivityCell,
|
|
308
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
2
|
-
import type
|
|
3
|
-
import { usageMetrics } from "@/lib/insights-usage";
|
|
2
|
+
import { usageMetrics, type UsageWindow } from "@/lib/insights-usage";
|
|
4
3
|
|
|
4
|
+
import { EstimatedCount } from "./EstimatedCount";
|
|
5
5
|
import { InsightsInfo } from "./InsightsInfo";
|
|
6
6
|
|
|
7
7
|
export function ReportingDevicesSummary({
|
|
@@ -10,7 +10,7 @@ export function ReportingDevicesSummary({
|
|
|
10
10
|
isPending,
|
|
11
11
|
partial,
|
|
12
12
|
}: {
|
|
13
|
-
readonly window:
|
|
13
|
+
readonly window: UsageWindow;
|
|
14
14
|
readonly count: number | undefined;
|
|
15
15
|
readonly isPending: boolean;
|
|
16
16
|
readonly partial: boolean;
|
|
@@ -26,21 +26,21 @@ export function ReportingDevicesSummary({
|
|
|
26
26
|
<Skeleton aria-label="Loading active users" className="h-9 w-12" />
|
|
27
27
|
) : (
|
|
28
28
|
<p className="text-4xl font-semibold tracking-tight tabular-nums">
|
|
29
|
-
{count === undefined
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
{count === undefined ? (
|
|
30
|
+
"—"
|
|
31
|
+
) : partial ? (
|
|
32
|
+
`≥${count.toLocaleString()}`
|
|
33
|
+
) : (
|
|
34
|
+
<EstimatedCount value={count} />
|
|
35
|
+
)}
|
|
32
36
|
</p>
|
|
33
37
|
)}
|
|
34
38
|
<div className="flex items-center gap-1 text-sm text-muted-foreground">
|
|
35
39
|
<span>{metric.label}</span>
|
|
36
40
|
<InsightsInfo label={`How ${metric.label} is counted`}>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
installations per {metric.interval}.
|
|
41
|
-
{partial
|
|
42
|
-
? " Only part of the history is available; the total is a lower bound."
|
|
43
|
-
: ""}
|
|
41
|
+
Installations that reported in the last {metric.period}, estimated
|
|
42
|
+
within about 3%. The chart shows them per {metric.interval}.
|
|
43
|
+
{partial ? " Some history is missing, so this is a minimum." : ""}
|
|
44
44
|
</InsightsInfo>
|
|
45
45
|
</div>
|
|
46
46
|
</div>
|