@hot-updater/console 1.0.0-rc.4 → 1.0.0-rc.41
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-DRzeMGRM.js +1 -0
- package/.output/public/assets/EventDetails-SB5ygJzS.js +1 -0
- package/.output/public/assets/HashValueDisplay-BFX_kY3-.js +1 -0
- package/.output/public/assets/InsightsInfo-btYppp0B.js +1 -0
- package/.output/public/assets/InsightsPageHeader-Ba8psD8t.js +1 -0
- package/.output/public/assets/NotFoundPage-CyRG2eca.js +1 -0
- package/.output/public/assets/alert-dialog-Bbk_4-WJ.js +1 -0
- package/.output/public/assets/api-DUgI6yOI.js +1 -0
- package/.output/public/assets/api-keys-CIO9L_wu.js +1 -0
- package/.output/public/assets/api-keys-CmkNKOhS.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-BMsI3Bj8.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-BEfQ2_7f.js +11 -0
- package/.output/public/assets/input-group-ja_30qaF.js +1 -0
- package/.output/public/assets/insights-B0ib9qOE.js +2 -0
- package/.output/public/assets/insights-BIMJx-0p.js +1 -0
- package/.output/public/assets/insights-CndqcWEj.js +42 -0
- package/.output/public/assets/insights-api-B6Ewc-OG.js +1 -0
- package/.output/public/assets/insights-usage-rpc-DYEhsCKT.js +1 -0
- package/.output/public/assets/insights_.distribution-B26PMy4k.js +2 -0
- package/.output/public/assets/insights_.distribution-BBDtqqYS.js +1 -0
- package/.output/public/assets/insights_.distribution-DhHmYS8k.js +1 -0
- package/.output/public/assets/installations-C3cMuwRx.js +1 -0
- package/.output/public/assets/installations-C3yrjwDh.js +2 -0
- package/.output/public/assets/installations-D3edlN75.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-0i-gDkeU.js +1 -0
- package/.output/public/assets/remote-config-An1ROIOQ.js +6 -0
- package/.output/public/assets/remote-config-CoeWgA9i.js +1 -0
- package/.output/public/assets/routes-DNoaJIQV.js +2 -0
- package/.output/public/assets/routes-cWBDtghZ.js +1 -0
- package/.output/public/assets/search-DaTDPW25.js +1 -0
- package/.output/public/assets/select-CZMQKT7F.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-D8mxMvCB.css +2 -0
- package/.output/public/assets/switch-CU4-fupc.js +1 -0
- package/.output/public/assets/table-Gm4qRWwE.js +1 -0
- package/.output/public/assets/tabs-R1I8CRjR.js +1 -0
- package/.output/public/assets/undo-2-CmrHjwFc.js +1 -0
- package/.output/public/assets/useCompositeListItem-BaPeC9ZO.js +1 -0
- package/.output/public/assets/valueToPercent-CH8GCHaL.js +1 -0
- package/.output/server/__23tanstack-start-server-fn-resolver-DTAqlLRd.mjs +178 -0
- package/.output/server/_libs/@base-ui/react+[...].mjs +6450 -2741
- 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 +235 -31
- 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-BNoVW3Dv.mjs +13 -0
- package/.output/server/_ssr/ConsoleFeatureUnavailable-BPFa-cmJ.mjs +58 -0
- package/.output/server/_ssr/EventDetails-9CeshfdR.mjs +344 -0
- package/.output/server/_ssr/HashValueDisplay-DXZlkcps.mjs +55 -0
- package/.output/server/_ssr/InsightsInfo-DLNdJQ0m.mjs +75 -0
- package/.output/server/_ssr/InsightsPageHeader-DZGVDgvv.mjs +145 -0
- package/.output/server/_ssr/NotFoundPage-mYHHg-2s.mjs +37 -0
- package/.output/server/_ssr/{dialog-DUaIgpgd.mjs → alert-dialog-Chkim7vZ.mjs} +6 -83
- package/.output/server/_ssr/api-C-Il5-sa.mjs +203 -0
- package/.output/server/_ssr/api-keys-01FGqj4d.mjs +5 -0
- package/.output/server/_ssr/{api-keys-DJml_XoK.mjs → api-keys-B_f9h7ln.mjs} +48 -10
- package/.output/server/_ssr/api-keys-rpc-Cf4YCyFc.mjs +52 -0
- package/.output/server/_ssr/api-rpc-DRqwxxT_.mjs +296 -0
- package/.output/server/_ssr/{auth-BF8mbAgF.mjs → auth-UP0wXs4i.mjs} +1 -1
- package/.output/server/_ssr/{auth-rpc-D_hv52-J.mjs → auth-rpc-D8nFpU9C.mjs} +6 -6
- package/.output/server/_ssr/auth.server-COZoXu4i.mjs +5425 -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-Dm2l7w0O.mjs +36 -0
- package/.output/server/_ssr/console-access--o9FXikU.mjs +17 -0
- package/.output/server/_ssr/console-features-BftSkf3f.mjs +60 -0
- package/.output/server/_ssr/console-features-api-CYclUQ4h.mjs +43 -0
- package/.output/server/_ssr/console-features-rpc-1WaAkPM_.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-BUuNU-qt.mjs +74 -0
- package/.output/server/_ssr/dialog-mfSANqjC.mjs +84 -0
- package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-wmnTk7w6.mjs} +140 -6
- package/.output/server/_ssr/{downloadBundle-B3ert-pY.mjs → downloadBundle-DViuIa9U.mjs} +10 -10
- package/.output/server/_ssr/empty-BPayFad3.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-DjZ5t7rH.mjs +189 -0
- package/.output/server/_ssr/getBundleChildren-CmjDg6Re.mjs +22 -0
- package/.output/server/_ssr/{input-group-Bb8ueylQ.mjs → input-group-Dx-MEkNM.mjs} +3 -52
- package/.output/server/_ssr/insights-BopxyJjk.mjs +14 -0
- package/.output/server/_ssr/insights-Bvm03d61.mjs +5 -0
- package/.output/server/_ssr/insights-CrLAHSDy.mjs +2315 -0
- package/.output/server/_ssr/insights-api-Bg8ZV_h_.mjs +122 -0
- package/.output/server/_ssr/insights-errors-BjnFUyZj.mjs +34 -0
- package/.output/server/_ssr/insights-failures-rpc-tyy5m9Cc.mjs +36 -0
- package/.output/server/_ssr/insights-failures-xaa41Buf.mjs +46 -0
- package/.output/server/_ssr/{insights-recovery-DSFM-YTL.mjs → insights-recovery-BWQhiZaF.mjs} +9 -2
- package/.output/server/_ssr/insights-recovery-rpc-CPJOiJAV.mjs +32 -0
- package/.output/server/_ssr/{insights-rpc-DrIQCjpD.mjs → insights-rpc-DoC3hla9.mjs} +22 -14
- package/.output/server/_ssr/insights-usage-BpBM0U0c.mjs +29 -0
- package/.output/server/_ssr/insights-usage-rpc-CFuTYp3T.mjs +29 -0
- package/.output/server/_ssr/insights-usage-rpc-O2ruP4iE.mjs +41 -0
- package/.output/server/_ssr/insightsUsage-CzhVjsc8.mjs +56 -0
- package/.output/server/_ssr/insights_.distribution-B3BVPD00.mjs +16 -0
- package/.output/server/_ssr/insights_.distribution-BMoisxRG.mjs +347 -0
- package/.output/server/_ssr/insights_.distribution-BiCcFyrG.mjs +7 -0
- package/.output/server/_ssr/{installations-YumqpNpa.mjs → installations-Cx3jIrwl.mjs} +280 -364
- package/.output/server/_ssr/installations-DTGPaKXx.mjs +69 -0
- package/.output/server/_ssr/installations-osiAuNKr.mjs +5 -0
- package/.output/server/_ssr/listReleases-BfUBlCHo.mjs +79 -0
- package/.output/server/_ssr/release-adoption-005tQRpo.mjs +55 -0
- package/.output/server/_ssr/release-adoption-rpc-BVySDR0L.mjs +41 -0
- package/.output/server/_ssr/releaseAdoption-2PJPA09N.mjs +114 -0
- package/.output/server/_ssr/releaseManagement-BTCWEY8-.mjs +11 -0
- package/.output/server/_ssr/remote-config-CUoAxUMy.mjs +430 -0
- package/.output/server/_ssr/remote-config-Cc5P4J-r.mjs +17 -0
- package/.output/server/_ssr/remote-config-CjdxcZ9B.mjs +5 -0
- package/.output/server/_ssr/remote-config-D2GmbGwI.mjs +2594 -0
- package/.output/server/_ssr/remote-config-rpc-kjoZ9PRS.mjs +115 -0
- package/.output/server/_ssr/rolldown-runtime-rSIU-vHC.mjs +13 -0
- package/.output/server/_ssr/{router-iP95Vqi2.mjs → router-DwGVewL-.mjs} +141 -139
- package/.output/server/_ssr/{routes-ivsV2TDu.mjs → routes-C4yxv3JU.mjs} +192 -685
- package/.output/server/_ssr/routes-D3_aegLY.mjs +409 -0
- package/.output/server/_ssr/runtime.server-BoWf4QhU.mjs +1716 -0
- package/.output/server/_ssr/schema-DrOtkRVD.mjs +361 -0
- package/.output/server/_ssr/select-DkzM2miy.mjs +107 -0
- package/.output/server/_ssr/server-BW0gzB0p.mjs +8325 -0
- package/.output/server/_ssr/{createSsrRpc-NHF2PxuK.mjs → sidebar-DPcOijvL.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-BqJ2mAwC.mjs +17 -0
- package/.output/server/_ssr/{table-D1mL8fJu.mjs → table-R7RY92q9.mjs} +2 -2
- package/.output/server/_ssr/tabs-trDcbRkT.mjs +45 -0
- package/.output/server/_ssr/updateFailures-H6pnnYK4.mjs +40 -0
- package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-ctA_XVZA.mjs} +2 -2
- package/.output/server/_tanstack-start-manifest_v-BwInaUTw.mjs +139 -0
- package/.output/server/index.mjs +315 -154
- package/LICENSE +13 -14
- package/README.md +82 -72
- 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 +100 -71
- 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 +2 -5
- package/src/components/features/insights/AppUsage.tsx +366 -0
- 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 +400 -39
- package/src/components/features/insights/EventHistoryCard.tsx +112 -26
- package/src/components/features/insights/EventHistoryList.tsx +2 -2
- package/src/components/features/insights/FailureReports.tsx +407 -0
- package/src/components/features/insights/InsightsControls.tsx +265 -88
- package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
- package/src/components/features/insights/InsightsInfo.tsx +48 -0
- package/src/components/features/insights/InsightsOverview.tsx +604 -355
- package/src/components/features/insights/InsightsPageHeader.tsx +35 -20
- package/src/components/features/insights/InsightsPeriodSelector.tsx +89 -0
- package/src/components/features/insights/InstallationHistoryCard.tsx +66 -11
- 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 +48 -0
- 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 +26 -12
- package/src/components/features/remote-config/ConditionDialog.tsx +581 -0
- package/src/components/features/remote-config/ConditionsCard.tsx +244 -0
- package/src/components/features/remote-config/ParameterDialog.tsx +407 -0
- package/src/components/features/remote-config/ParametersCard.tsx +244 -0
- package/src/components/features/remote-config/PreviewCard.tsx +284 -0
- package/src/components/features/remote-config/PublishDialog.tsx +319 -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 +148 -0
- package/src/components/features/remote-config/VersionsCard.tsx +361 -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/card.tsx +1 -1
- package/src/components/ui/scroll-area.tsx +52 -0
- package/src/components/ui/sidebar.tsx +22 -29
- package/src/components/ui/tabs.tsx +81 -0
- 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 +189 -266
- package/src/lib/api.ts +19 -102
- 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 +97 -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 +10 -27
- package/src/lib/insights-retention.ts +21 -0
- package/src/lib/insights-rpc.ts +30 -12
- package/src/lib/insights-search.ts +93 -0
- package/src/lib/insights-usage-rpc.ts +29 -0
- package/src/lib/insights-usage.ts +76 -0
- 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 +652 -0
- package/src/lib/remote-config-rpc.ts +196 -0
- package/src/lib/scroll-restoration.ts +21 -0
- package/src/lib/server/adminInsights.ts +124 -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 -74
- 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 +16 -15
- package/src/lib/server/failureReports.ts +49 -0
- package/src/lib/server/getBundleChildren.ts +31 -122
- package/src/lib/server/insightsUsage.ts +63 -0
- package/src/lib/server/listReleases.ts +109 -44
- package/src/lib/server/releaseAdoption.ts +154 -0
- package/src/lib/server/releaseReachability.ts +8 -7
- package/src/lib/server/remoteConfig.ts +191 -0
- package/src/lib/server/runtime.server.ts +120 -31
- package/src/lib/server/updateFailures.ts +65 -0
- package/src/routeTree.gen.ts +65 -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 +10 -4
- 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 +133 -18
- package/src/routes/insights_.distribution.tsx +422 -0
- package/src/routes/installations.tsx +98 -41
- 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/InsightsPageHeader-C-Xc2MB9.js +0 -1
- package/.output/public/assets/api-keys-C1sgC6ko.js +0 -1
- package/.output/public/assets/api-keys-api-LchP65vQ.js +0 -1
- package/.output/public/assets/badge-BuMDU0Rf.js +0 -1
- package/.output/public/assets/card-BEvx_QKh.js +0 -1
- package/.output/public/assets/chart-no-axes-combined-C0G0fQ3J.js +0 -1
- package/.output/public/assets/composite-CHTH1Xhh.js +0 -1
- package/.output/public/assets/createServerFn-DHc2erub.js +0 -3
- package/.output/public/assets/dialog-dyZ1SphZ.js +0 -1
- package/.output/public/assets/dist-Bh1gaDfB.js +0 -1
- package/.output/public/assets/field-79SVH26s.js +0 -1
- package/.output/public/assets/index-BNkxg4jz.js +0 -10
- package/.output/public/assets/input-group-BbHfaBzQ.js +0 -1
- package/.output/public/assets/insights-D_7uSKfc.js +0 -1
- package/.output/public/assets/installations-BzoHWuXw.js +0 -1
- package/.output/public/assets/installations-DvjbUqd1.js +0 -2
- package/.output/public/assets/lazyRouteComponent-BN0MS05o.js +0 -1
- package/.output/public/assets/link-BG06whwB.js +0 -1
- package/.output/public/assets/loader-circle-Bbp8LGVC.js +0 -1
- package/.output/public/assets/preload-helper-uTix4PVD.js +0 -1
- package/.output/public/assets/refresh-cw-DZgiAOMk.js +0 -1
- package/.output/public/assets/routes-BFE-oRIT.js +0 -1
- package/.output/public/assets/routes-DaPMY31P.js +0 -2
- package/.output/public/assets/shield-check-DoIiaaXU.js +0 -1
- package/.output/public/assets/signing-BTRhaF5D.js +0 -1
- package/.output/public/assets/styles-C7U0oVke.css +0 -2
- package/.output/public/assets/table-BBEzJ7Xl.js +0 -1
- package/.output/public/assets/useCompositeListItem-BkPDaqyj.js +0 -42
- package/.output/server/__23tanstack-start-server-fn-resolver-Dr2ixDgQ.mjs +0 -146
- package/.output/server/_ssr/InsightsPageHeader-C5Has8Ry.mjs +0 -60
- package/.output/server/_ssr/api-keys-api-CrCZyvnl.mjs +0 -60
- package/.output/server/_ssr/api-keys-rpc-CFaNf-q0.mjs +0 -136
- package/.output/server/_ssr/api-rpc-B61KL3Eo.mjs +0 -1399
- package/.output/server/_ssr/auth.server-DF_shCeq.mjs +0 -47
- package/.output/server/_ssr/badge-BgsCP9Dp.mjs +0 -28
- package/.output/server/_ssr/bundle-signing-rpc-BBipZnFs.mjs +0 -16
- package/.output/server/_ssr/bundle-signing.server-Co_zHdJx.mjs +0 -12
- package/.output/server/_ssr/bundleActivity-D3_fIda2.mjs +0 -27
- package/.output/server/_ssr/card-DrbxCPtG.mjs +0 -86
- package/.output/server/_ssr/chart-qxa-_30a.mjs +0 -162
- package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
- package/.output/server/_ssr/config.server-CBr6PDj5.mjs +0 -368
- package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
- package/.output/server/_ssr/deleteBundle-BAq3JuGr.mjs +0 -66
- package/.output/server/_ssr/field-CqNUlqUq.mjs +0 -74
- package/.output/server/_ssr/getBundleChildren-COMt16m_.mjs +0 -50
- package/.output/server/_ssr/insights-B-DUbksX.mjs +0 -502
- package/.output/server/_ssr/insights-recovery-rpc-P71FH66n.mjs +0 -37
- package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +0 -303
- package/.output/server/_ssr/insightsRecovery-CqcosG7B.mjs +0 -149
- package/.output/server/_ssr/installations-BQrppti7.mjs +0 -29
- package/.output/server/_ssr/routes-BDabsmc7.mjs +0 -37
- package/.output/server/_ssr/runtime.server-jmZOKIQM.mjs +0 -438
- package/.output/server/_ssr/server-p7yrVBfD.mjs +0 -7468
- package/.output/server/_ssr/signing-CxCbnFCt.mjs +0 -134
- package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
- package/.output/server/_tanstack-start-manifest_v-Cwpu0C9Q.mjs +0 -100
- package/src/components/features/bundles/BundleActivityChart.tsx +0 -95
- package/src/components/features/bundles/BundleInsightsSummary.tsx +0 -121
- 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 -186
- package/src/routes/signing.tsx +0 -37
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { useQuery } from "@tanstack/react-query";
|
|
2
1
|
import { Link } from "@tanstack/react-router";
|
|
3
|
-
import {
|
|
2
|
+
import { ListIcon, RotateCcw, RotateCw, TriangleAlert, X } from "lucide-react";
|
|
4
3
|
import { useState } from "react";
|
|
5
|
-
import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts";
|
|
6
4
|
|
|
5
|
+
import { BundleIdDisplay } from "@/components/BundleIdDisplay";
|
|
6
|
+
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
|
7
|
+
import { Badge } from "@/components/ui/badge";
|
|
7
8
|
import { Button, buttonVariants } from "@/components/ui/button";
|
|
8
9
|
import {
|
|
9
10
|
Card,
|
|
@@ -13,380 +14,628 @@ import {
|
|
|
13
14
|
CardTitle,
|
|
14
15
|
} from "@/components/ui/card";
|
|
15
16
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
Empty,
|
|
18
|
+
EmptyContent,
|
|
19
|
+
EmptyDescription,
|
|
20
|
+
EmptyHeader,
|
|
21
|
+
EmptyTitle,
|
|
22
|
+
} from "@/components/ui/empty";
|
|
23
|
+
import {
|
|
24
|
+
Select,
|
|
25
|
+
SelectContent,
|
|
26
|
+
SelectGroup,
|
|
27
|
+
SelectItem,
|
|
28
|
+
SelectTrigger,
|
|
29
|
+
SelectValue,
|
|
30
|
+
} from "@/components/ui/select";
|
|
20
31
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
32
|
+
import {
|
|
33
|
+
Table,
|
|
34
|
+
TableBody,
|
|
35
|
+
TableCell,
|
|
36
|
+
TableHead,
|
|
37
|
+
TableHeader,
|
|
38
|
+
TableRow,
|
|
39
|
+
} from "@/components/ui/table";
|
|
40
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
41
|
+
import type { RecoveryInput } from "@/lib/insights-recovery";
|
|
42
|
+
import type { HealthChart } from "@/lib/insights-search";
|
|
43
|
+
import {
|
|
44
|
+
CRASH_MIN_ATTEMPTS,
|
|
45
|
+
CRASH_RATE_THRESHOLD,
|
|
46
|
+
crashRateOf,
|
|
47
|
+
DEFAULT_ADOPTION_RELEASES,
|
|
48
|
+
MAX_ADOPTION_RELEASES,
|
|
49
|
+
recommendsRollback,
|
|
50
|
+
} from "@/lib/release-adoption";
|
|
51
|
+
import type {
|
|
52
|
+
ComparedBundle,
|
|
53
|
+
ReleaseHealthState,
|
|
54
|
+
} from "@/lib/release-adoption-api";
|
|
55
|
+
import { cn } from "@/lib/utils";
|
|
24
56
|
|
|
57
|
+
import {
|
|
58
|
+
ComparedBundlesChart,
|
|
59
|
+
formatDeployedAt,
|
|
60
|
+
useSeriesColors,
|
|
61
|
+
} from "./ComparedBundlesChart";
|
|
25
62
|
import { InsightsErrorAlert } from "./InsightsErrorAlert";
|
|
63
|
+
import { InsightsInfo } from "./InsightsInfo";
|
|
64
|
+
import { InsightsPeriodSelector } from "./InsightsPeriodSelector";
|
|
65
|
+
import { RollbackButton } from "./RollbackButton";
|
|
26
66
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
const colors = [
|
|
39
|
-
"var(--chart-2)",
|
|
40
|
-
"var(--foreground)",
|
|
41
|
-
"var(--chart-1)",
|
|
42
|
-
"var(--muted-foreground)",
|
|
43
|
-
"var(--chart-4)",
|
|
44
|
-
];
|
|
45
|
-
const dashes = [undefined, "6 3", "2 3"];
|
|
67
|
+
const HOUR = 3_600_000;
|
|
68
|
+
const DAY = 24 * HOUR;
|
|
69
|
+
const percent = (rate: number) => `${(rate * 100).toFixed(1)}%`;
|
|
70
|
+
const lengthOf = (durationMs: number) =>
|
|
71
|
+
durationMs > DAY ? `${durationMs / DAY} days` : `${durationMs / HOUR} hours`;
|
|
72
|
+
const cadenceOf = (intervalMs: number) =>
|
|
73
|
+
intervalMs === HOUR
|
|
74
|
+
? "Hourly"
|
|
75
|
+
: intervalMs === DAY
|
|
76
|
+
? "Daily"
|
|
77
|
+
: `Every ${intervalMs / HOUR} hours`;
|
|
46
78
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
79
|
+
/** A bundle's crash numbers over the period, once both counts are read. */
|
|
80
|
+
const crashesOf = ({ release, launched, recovered }: ComparedBundle) =>
|
|
81
|
+
launched === undefined || recovered === undefined
|
|
82
|
+
? undefined
|
|
83
|
+
: {
|
|
84
|
+
crashes: recovered.total,
|
|
85
|
+
...crashRateOf(launched.total, recovered.total),
|
|
86
|
+
recommended: recommendsRollback(
|
|
87
|
+
release,
|
|
88
|
+
launched.total,
|
|
89
|
+
recovered.total,
|
|
90
|
+
),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/** A line sample for the legend: solid or dashed, in the text color. */
|
|
94
|
+
function LineSample({ dashed }: { readonly dashed?: boolean }) {
|
|
95
|
+
return (
|
|
96
|
+
<svg
|
|
97
|
+
aria-hidden="true"
|
|
98
|
+
className="h-3 w-6 shrink-0"
|
|
99
|
+
viewBox="0 0 24 12"
|
|
100
|
+
preserveAspectRatio="none"
|
|
101
|
+
>
|
|
102
|
+
<line
|
|
103
|
+
x1="0"
|
|
104
|
+
y1="6"
|
|
105
|
+
x2="24"
|
|
106
|
+
y2="6"
|
|
107
|
+
stroke="currentColor"
|
|
108
|
+
strokeWidth="2"
|
|
109
|
+
strokeDasharray={dashed ? "6 4" : undefined}
|
|
110
|
+
/>
|
|
111
|
+
</svg>
|
|
52
112
|
);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Release health: how the newest bundles fare after their deployment, on
|
|
117
|
+
* one timeline, per interval. Adoption shows installations downloading each
|
|
118
|
+
* bundle (dashed) and launching it (solid), so the gap is the restarts still
|
|
119
|
+
* to come; Crashes shows them crashing back from it, and offers a rollback
|
|
120
|
+
* when a bundle crashes for too many of them.
|
|
121
|
+
*/
|
|
122
|
+
export function InsightsOverview({
|
|
123
|
+
input,
|
|
124
|
+
chart,
|
|
125
|
+
onChartChange,
|
|
126
|
+
health,
|
|
127
|
+
onReleasesChange,
|
|
128
|
+
onWindowChange,
|
|
129
|
+
}: {
|
|
130
|
+
readonly input: RecoveryInput;
|
|
131
|
+
readonly chart: HealthChart;
|
|
132
|
+
readonly onChartChange: (chart: HealthChart) => void;
|
|
133
|
+
readonly health: ReleaseHealthState;
|
|
134
|
+
/** Chooses the bundles to compare; undefined returns to the default. */
|
|
135
|
+
readonly onReleasesChange: (releaseIds?: readonly string[]) => void;
|
|
136
|
+
readonly onWindowChange: (window: RecoveryInput["window"]) => void;
|
|
137
|
+
}) {
|
|
138
|
+
const [active, setActive] = useState<string | null>(null);
|
|
139
|
+
const { period, candidates, releases } = health;
|
|
140
|
+
const ids = (releases ?? []).map(({ release }) => release.releaseId);
|
|
141
|
+
const colorOf = useSeriesColors(ids);
|
|
142
|
+
const addable = (candidates ?? []).filter(
|
|
143
|
+
(candidate) => !ids.includes(candidate.releaseId),
|
|
61
144
|
);
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
145
|
+
const full = ids.length >= MAX_ADOPTION_RELEASES;
|
|
146
|
+
const wider =
|
|
147
|
+
period.durationMs < 7 * DAY
|
|
148
|
+
? "7d"
|
|
149
|
+
: period.durationMs < 30 * DAY
|
|
150
|
+
? "30d"
|
|
151
|
+
: null;
|
|
152
|
+
const reset = health.isDefault ? null : (
|
|
153
|
+
<Button
|
|
154
|
+
variant="ghost"
|
|
155
|
+
size="sm"
|
|
156
|
+
onClick={() => onReleasesChange(undefined)}
|
|
157
|
+
>
|
|
158
|
+
<RotateCcw aria-hidden="true" data-icon="inline-start" />
|
|
159
|
+
Show the newest {DEFAULT_ADOPTION_RELEASES}
|
|
160
|
+
</Button>
|
|
161
|
+
);
|
|
162
|
+
// Adoption draws each bundle's launches solid and its downloads dashed;
|
|
163
|
+
// Crashes draws its crashes.
|
|
164
|
+
const seriesOf = (bundle: ComparedBundle) =>
|
|
165
|
+
chart === "adoption" ? bundle.launched : bundle.recovered;
|
|
166
|
+
const loaded = (releases ?? []).filter(
|
|
167
|
+
(bundle) =>
|
|
168
|
+
seriesOf(bundle) !== undefined &&
|
|
169
|
+
(chart !== "adoption" || bundle.downloaded !== undefined),
|
|
170
|
+
);
|
|
171
|
+
const failed =
|
|
172
|
+
(releases ?? []).find(({ error }) => error !== null)?.error ?? null;
|
|
173
|
+
const flagged = (releases ?? []).flatMap((bundle) => {
|
|
174
|
+
const crashes = crashesOf(bundle);
|
|
175
|
+
return crashes?.recommended ? [{ ...bundle, ...crashes }] : [];
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const remove = (releaseId: string) => {
|
|
179
|
+
const rest = ids.filter((id) => id !== releaseId);
|
|
180
|
+
onReleasesChange(rest.length === 0 ? undefined : rest);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const heading =
|
|
184
|
+
chart === "adoption" ? (
|
|
185
|
+
<div className="flex flex-col gap-1">
|
|
186
|
+
<div className="flex items-center gap-1 text-sm font-medium">
|
|
187
|
+
Installations downloading and launching each bundle
|
|
188
|
+
<InsightsInfo label="About adoption">
|
|
189
|
+
Dashed: installations that downloaded the bundle. Solid:
|
|
190
|
+
installations that launched it. After a forced update the two lines
|
|
191
|
+
meet.
|
|
192
|
+
</InsightsInfo>
|
|
193
|
+
</div>
|
|
194
|
+
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground">
|
|
195
|
+
<p>
|
|
196
|
+
{cadenceOf(period.intervalMs)} · last {lengthOf(period.durationMs)}{" "}
|
|
197
|
+
· UTC
|
|
97
198
|
</p>
|
|
199
|
+
<ul aria-label="Line styles" className="flex items-center gap-3">
|
|
200
|
+
<li className="flex items-center gap-1.5">
|
|
201
|
+
<LineSample dashed />
|
|
202
|
+
Downloaded
|
|
203
|
+
</li>
|
|
204
|
+
<li className="flex items-center gap-1.5">
|
|
205
|
+
<LineSample />
|
|
206
|
+
Launched
|
|
207
|
+
</li>
|
|
208
|
+
</ul>
|
|
98
209
|
</div>
|
|
99
|
-
<ToggleGroup
|
|
100
|
-
aria-label="Activity metric"
|
|
101
|
-
value={[metric]}
|
|
102
|
-
onValueChange={(values) => {
|
|
103
|
-
if (values[0] === "active" || values[0] === "rollback")
|
|
104
|
-
setMetric(values[0]);
|
|
105
|
-
}}
|
|
106
|
-
spacing={0}
|
|
107
|
-
variant="outline"
|
|
108
|
-
>
|
|
109
|
-
<ToggleGroupItem className="h-11 lg:h-8" value="active">
|
|
110
|
-
Active
|
|
111
|
-
</ToggleGroupItem>
|
|
112
|
-
<ToggleGroupItem className="h-11 lg:h-8" value="rollback">
|
|
113
|
-
Rollback
|
|
114
|
-
</ToggleGroupItem>
|
|
115
|
-
</ToggleGroup>
|
|
116
210
|
</div>
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
after
|
|
211
|
+
) : (
|
|
212
|
+
<div className="flex flex-col gap-1">
|
|
213
|
+
<div className="flex items-center gap-1 text-sm font-medium">
|
|
214
|
+
Crashes after each deployment
|
|
215
|
+
<InsightsInfo label="About crashes">
|
|
216
|
+
Installations that crashed on a bundle and rolled back. Crash rate =
|
|
217
|
+
crashed ÷ (launched + crashed). A rollback is suggested at{" "}
|
|
218
|
+
{percent(CRASH_RATE_THRESHOLD)} once {CRASH_MIN_ATTEMPTS}{" "}
|
|
219
|
+
installations tried it.
|
|
220
|
+
</InsightsInfo>
|
|
121
221
|
</div>
|
|
222
|
+
<p className="text-xs text-muted-foreground">
|
|
223
|
+
{cadenceOf(period.intervalMs)} · last {lengthOf(period.durationMs)} ·
|
|
224
|
+
UTC
|
|
225
|
+
</p>
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
const body = (() => {
|
|
230
|
+
if (releases === undefined) {
|
|
231
|
+
return health.error ? (
|
|
232
|
+
<InsightsErrorAlert
|
|
233
|
+
error={health.error}
|
|
234
|
+
fallbackTitle="Release health unavailable"
|
|
235
|
+
/>
|
|
236
|
+
) : (
|
|
237
|
+
<Skeleton aria-label="Loading release health" className="h-64" />
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
if (releases.length === 0) {
|
|
241
|
+
return (
|
|
242
|
+
<Empty className="min-h-64">
|
|
243
|
+
<EmptyHeader>
|
|
244
|
+
<EmptyTitle>No bundles deployed here yet</EmptyTitle>
|
|
245
|
+
<EmptyDescription>
|
|
246
|
+
Release health follows the bundle deployments of the selected
|
|
247
|
+
channel and health platform. Deploy one, or choose another in the
|
|
248
|
+
filters.
|
|
249
|
+
</EmptyDescription>
|
|
250
|
+
</EmptyHeader>
|
|
251
|
+
{reset ? <EmptyContent>{reset}</EmptyContent> : null}
|
|
252
|
+
</Empty>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (loaded.length === 0) {
|
|
256
|
+
return failed ? (
|
|
257
|
+
<InsightsErrorAlert
|
|
258
|
+
error={failed}
|
|
259
|
+
fallbackTitle="Release health unavailable"
|
|
260
|
+
/>
|
|
122
261
|
) : (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
262
|
+
<Skeleton aria-label="Loading release health" className="h-64" />
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
if (
|
|
266
|
+
loaded.every(
|
|
267
|
+
(bundle) =>
|
|
268
|
+
seriesOf(bundle)!.total === 0 &&
|
|
269
|
+
(chart !== "adoption" || bundle.downloaded!.total === 0),
|
|
270
|
+
)
|
|
271
|
+
) {
|
|
272
|
+
return chart === "adoption" ? (
|
|
273
|
+
<Empty className="min-h-48">
|
|
274
|
+
<EmptyHeader>
|
|
275
|
+
<EmptyTitle>
|
|
276
|
+
No installation downloaded or launched these bundles in the last{" "}
|
|
277
|
+
{lengthOf(period.durationMs)}
|
|
278
|
+
</EmptyTitle>
|
|
279
|
+
<EmptyDescription>
|
|
280
|
+
{wider
|
|
281
|
+
? "Choose a longer period, or refresh after apps download them."
|
|
282
|
+
: "Refresh after apps download them."}
|
|
283
|
+
</EmptyDescription>
|
|
284
|
+
</EmptyHeader>
|
|
285
|
+
{wider ? (
|
|
286
|
+
<EmptyContent>
|
|
287
|
+
<Button
|
|
288
|
+
variant="outline"
|
|
289
|
+
size="sm"
|
|
290
|
+
onClick={() => onWindowChange(wider)}
|
|
140
291
|
>
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
292
|
+
Show {lengthOf(wider === "7d" ? 7 * DAY : 30 * DAY)}
|
|
293
|
+
</Button>
|
|
294
|
+
</EmptyContent>
|
|
295
|
+
) : null}
|
|
296
|
+
</Empty>
|
|
297
|
+
) : (
|
|
298
|
+
<Empty className="min-h-48">
|
|
299
|
+
<EmptyHeader>
|
|
300
|
+
<EmptyTitle>
|
|
301
|
+
No crashes in the last {lengthOf(period.durationMs)}
|
|
302
|
+
</EmptyTitle>
|
|
303
|
+
<EmptyDescription>
|
|
304
|
+
No launch of these bundles crashed and recovered.
|
|
305
|
+
</EmptyDescription>
|
|
306
|
+
</EmptyHeader>
|
|
307
|
+
</Empty>
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
return (
|
|
311
|
+
<ComparedBundlesChart
|
|
312
|
+
period={period}
|
|
313
|
+
lines={releases.map((bundle) => ({
|
|
314
|
+
release: bundle.release,
|
|
315
|
+
points: seriesOf(bundle)?.points,
|
|
316
|
+
...(chart === "adoption"
|
|
317
|
+
? { dashed: bundle.downloaded?.points }
|
|
318
|
+
: {}),
|
|
319
|
+
measuredAtMs: seriesOf(bundle)?.measuredAtMs,
|
|
320
|
+
}))}
|
|
321
|
+
colorOf={colorOf}
|
|
322
|
+
active={active}
|
|
323
|
+
names={
|
|
324
|
+
chart === "adoption"
|
|
325
|
+
? { solid: "Launched", dashed: "Downloaded" }
|
|
326
|
+
: { solid: "Crashed" }
|
|
327
|
+
}
|
|
328
|
+
label={
|
|
329
|
+
chart === "adoption"
|
|
330
|
+
? "Installations downloading (dashed) and launching (solid) each bundle per interval"
|
|
331
|
+
: "Crashes of each bundle per interval"
|
|
332
|
+
}
|
|
333
|
+
/>
|
|
334
|
+
);
|
|
335
|
+
})();
|
|
336
|
+
|
|
337
|
+
return (
|
|
338
|
+
<section aria-label="Release health">
|
|
339
|
+
<Card className="min-w-0 overflow-hidden shadow-sm">
|
|
340
|
+
<CardHeader className="flex flex-col gap-4 p-6">
|
|
341
|
+
<div className="flex flex-wrap items-center justify-between gap-4">
|
|
342
|
+
<CardTitle>Release health</CardTitle>
|
|
343
|
+
<div className="flex items-center gap-2">
|
|
344
|
+
<InsightsPeriodSelector
|
|
345
|
+
window={input.window}
|
|
346
|
+
onWindowChange={onWindowChange}
|
|
347
|
+
/>
|
|
348
|
+
<Button
|
|
349
|
+
aria-label="Refresh release health"
|
|
350
|
+
variant="ghost"
|
|
351
|
+
size="icon-lg"
|
|
352
|
+
disabled={health.isFetching}
|
|
353
|
+
onClick={health.refresh}
|
|
201
354
|
>
|
|
202
|
-
<
|
|
355
|
+
<RotateCw
|
|
203
356
|
aria-hidden="true"
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
x2="20"
|
|
211
|
-
y1="4"
|
|
212
|
-
y2="4"
|
|
213
|
-
stroke={colors[index % colors.length]}
|
|
214
|
-
strokeWidth="2"
|
|
215
|
-
strokeDasharray={
|
|
216
|
-
dashes[Math.floor(index / colors.length) % dashes.length]
|
|
217
|
-
}
|
|
218
|
-
/>
|
|
219
|
-
</svg>
|
|
220
|
-
<span className="min-w-0 truncate font-mono">
|
|
221
|
-
{series.releaseId}
|
|
222
|
-
</span>
|
|
223
|
-
{series.points.some((point) => point.spike) ? (
|
|
224
|
-
<TriangleAlert
|
|
225
|
-
aria-label="Rollback spike"
|
|
226
|
-
className="size-3 shrink-0"
|
|
227
|
-
/>
|
|
228
|
-
) : null}
|
|
229
|
-
<span className="ml-auto tabular-nums">
|
|
230
|
-
{(metric === "active"
|
|
231
|
-
? series.activeInstallations
|
|
232
|
-
: series.recoveredInstallations
|
|
233
|
-
).toLocaleString()}
|
|
234
|
-
</span>
|
|
235
|
-
</button>
|
|
236
|
-
))}
|
|
357
|
+
className={cn(
|
|
358
|
+
health.isFetching && "motion-safe:animate-spin",
|
|
359
|
+
)}
|
|
360
|
+
/>
|
|
361
|
+
</Button>
|
|
362
|
+
</div>
|
|
237
363
|
</div>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
className="size-3 shrink-0"
|
|
253
|
-
aria-hidden="true"
|
|
254
|
-
/>
|
|
255
|
-
Rollback spike on {dates.format(lastSpike.startMs)}. Review
|
|
256
|
-
this bundle’s delivery settings.
|
|
257
|
-
</p>
|
|
258
|
-
) : null}
|
|
259
|
-
</div>
|
|
260
|
-
<Link
|
|
261
|
-
className={buttonVariants({ variant: "outline", size: "sm" })}
|
|
262
|
-
to="/"
|
|
263
|
-
search={{ releaseId: selected.releaseId }}
|
|
364
|
+
</CardHeader>
|
|
365
|
+
<CardContent
|
|
366
|
+
aria-busy={health.isFetching}
|
|
367
|
+
className="flex flex-col gap-5 px-6 pb-6"
|
|
368
|
+
>
|
|
369
|
+
<Tabs
|
|
370
|
+
value={chart}
|
|
371
|
+
onValueChange={(value) => onChartChange(value as HealthChart)}
|
|
372
|
+
className="gap-5"
|
|
373
|
+
>
|
|
374
|
+
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
375
|
+
<TabsList
|
|
376
|
+
aria-label="Release health chart"
|
|
377
|
+
className="min-h-11 sm:min-h-9"
|
|
264
378
|
>
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
379
|
+
<TabsTrigger value="adoption" className="px-3">
|
|
380
|
+
Adoption
|
|
381
|
+
</TabsTrigger>
|
|
382
|
+
<TabsTrigger value="crashes" className="px-3">
|
|
383
|
+
Crashes
|
|
384
|
+
{flagged.length > 0 ? (
|
|
385
|
+
<>
|
|
386
|
+
<TriangleAlert
|
|
387
|
+
aria-hidden="true"
|
|
388
|
+
className="text-destructive"
|
|
389
|
+
/>
|
|
390
|
+
<span className="sr-only">, rollback recommended</span>
|
|
391
|
+
</>
|
|
392
|
+
) : null}
|
|
393
|
+
</TabsTrigger>
|
|
394
|
+
</TabsList>
|
|
395
|
+
{reset}
|
|
268
396
|
</div>
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
{
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
397
|
+
<TabsContent value="adoption" className="flex flex-col gap-4">
|
|
398
|
+
{heading}
|
|
399
|
+
{chart === "adoption" ? body : null}
|
|
400
|
+
</TabsContent>
|
|
401
|
+
<TabsContent value="crashes" className="flex flex-col gap-4">
|
|
402
|
+
{flagged.map((bundle) => (
|
|
403
|
+
<Alert key={bundle.release.releaseId} variant="destructive">
|
|
404
|
+
<TriangleAlert aria-hidden="true" />
|
|
405
|
+
<AlertTitle>
|
|
406
|
+
Roll back{" "}
|
|
407
|
+
<BundleIdDisplay
|
|
408
|
+
bundleId={bundle.release.releaseId}
|
|
409
|
+
className="text-sm"
|
|
410
|
+
/>
|
|
411
|
+
</AlertTitle>
|
|
412
|
+
<AlertDescription className="flex flex-col items-start gap-3">
|
|
413
|
+
It crashed for {percent(bundle.rate)} of the installations
|
|
414
|
+
that tried it ({bundle.crashes.toLocaleString()} of{" "}
|
|
415
|
+
{bundle.attempts.toLocaleString()}), above{" "}
|
|
416
|
+
{percent(CRASH_RATE_THRESHOLD)}.
|
|
417
|
+
<RollbackButton
|
|
418
|
+
release={bundle.release}
|
|
419
|
+
rate={bundle.rate}
|
|
420
|
+
crashes={bundle.crashes}
|
|
421
|
+
attempts={bundle.attempts}
|
|
422
|
+
/>
|
|
423
|
+
</AlertDescription>
|
|
424
|
+
</Alert>
|
|
425
|
+
))}
|
|
426
|
+
{heading}
|
|
427
|
+
{chart === "crashes" ? body : null}
|
|
428
|
+
</TabsContent>
|
|
429
|
+
</Tabs>
|
|
430
|
+
{releases !== undefined && releases.length > 0 ? (
|
|
431
|
+
<>
|
|
432
|
+
{failed && loaded.length > 0 ? (
|
|
433
|
+
<InsightsErrorAlert
|
|
434
|
+
error={failed}
|
|
435
|
+
fallbackTitle="Some bundles could not be read"
|
|
436
|
+
/>
|
|
437
|
+
) : null}
|
|
438
|
+
<Table aria-label="Compared bundles">
|
|
439
|
+
<TableHeader>
|
|
440
|
+
<TableRow>
|
|
441
|
+
<TableHead>Bundle</TableHead>
|
|
442
|
+
{chart === "adoption" ? (
|
|
443
|
+
<>
|
|
444
|
+
<TableHead className="text-right">Downloaded</TableHead>
|
|
445
|
+
<TableHead className="text-right">Launched</TableHead>
|
|
446
|
+
</>
|
|
447
|
+
) : (
|
|
448
|
+
<>
|
|
449
|
+
<TableHead className="text-right">Crashed</TableHead>
|
|
450
|
+
<TableHead className="text-right">Crash rate</TableHead>
|
|
451
|
+
</>
|
|
452
|
+
)}
|
|
453
|
+
<TableHead className="w-10">
|
|
454
|
+
<span className="sr-only">Remove</span>
|
|
455
|
+
</TableHead>
|
|
456
|
+
</TableRow>
|
|
457
|
+
</TableHeader>
|
|
458
|
+
<TableBody>
|
|
459
|
+
{releases.map((bundle) => {
|
|
460
|
+
const { release } = bundle;
|
|
461
|
+
const crashes = crashesOf(bundle);
|
|
462
|
+
return (
|
|
463
|
+
<TableRow
|
|
464
|
+
key={release.releaseId}
|
|
465
|
+
data-state={
|
|
466
|
+
active === release.releaseId ? "selected" : undefined
|
|
467
|
+
}
|
|
468
|
+
onMouseEnter={() => setActive(release.releaseId)}
|
|
469
|
+
onMouseLeave={() => setActive(null)}
|
|
470
|
+
onFocus={() => setActive(release.releaseId)}
|
|
471
|
+
onBlur={() => setActive(null)}
|
|
302
472
|
>
|
|
303
|
-
{
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
473
|
+
{/* The bundle cell takes the room left, and wraps in it. */}
|
|
474
|
+
<TableCell className="w-full max-w-0 whitespace-normal">
|
|
475
|
+
<div className="flex min-w-0 items-center gap-2 sm:gap-3">
|
|
476
|
+
{/* Narrower on a phone, so a bundle's ID keeps more of the row. */}
|
|
477
|
+
<svg
|
|
478
|
+
aria-hidden="true"
|
|
479
|
+
className="h-3 w-4 shrink-0 sm:w-6"
|
|
480
|
+
viewBox="0 0 24 12"
|
|
481
|
+
preserveAspectRatio="none"
|
|
482
|
+
>
|
|
483
|
+
<line
|
|
484
|
+
x1="0"
|
|
485
|
+
y1="6"
|
|
486
|
+
x2="24"
|
|
487
|
+
y2="6"
|
|
488
|
+
stroke={colorOf(release.releaseId)}
|
|
489
|
+
strokeWidth="3"
|
|
490
|
+
/>
|
|
491
|
+
</svg>
|
|
492
|
+
<div className="flex min-w-0 flex-col">
|
|
493
|
+
<span className="flex min-w-0 items-center gap-2">
|
|
494
|
+
<BundleIdDisplay
|
|
495
|
+
bundleId={release.releaseId}
|
|
496
|
+
className="min-w-0"
|
|
497
|
+
/>
|
|
498
|
+
{release.enabled ? null : (
|
|
499
|
+
<Badge variant="outline">Disabled</Badge>
|
|
500
|
+
)}
|
|
501
|
+
</span>
|
|
502
|
+
<span className="text-xs break-words text-muted-foreground">
|
|
503
|
+
Deployed{" "}
|
|
504
|
+
{formatDeployedAt(release.deployedAtMs)}
|
|
505
|
+
{release.targetAppVersion
|
|
506
|
+
? ` · ${release.targetAppVersion}`
|
|
507
|
+
: ""}
|
|
508
|
+
</span>
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
</TableCell>
|
|
512
|
+
{chart === "adoption" ? (
|
|
513
|
+
<>
|
|
514
|
+
<TableCell className="text-right whitespace-nowrap tabular-nums">
|
|
515
|
+
{bundle.downloaded?.total.toLocaleString() ?? "—"}
|
|
516
|
+
</TableCell>
|
|
517
|
+
<TableCell className="text-right font-medium whitespace-nowrap tabular-nums">
|
|
518
|
+
{bundle.launched?.total.toLocaleString() ?? "—"}
|
|
519
|
+
</TableCell>
|
|
520
|
+
</>
|
|
521
|
+
) : (
|
|
522
|
+
<>
|
|
523
|
+
<TableCell className="text-right whitespace-nowrap tabular-nums">
|
|
524
|
+
{crashes === undefined ? (
|
|
525
|
+
"—"
|
|
526
|
+
) : crashes.crashes === 0 ? (
|
|
527
|
+
<span className="text-muted-foreground">0</span>
|
|
528
|
+
) : (
|
|
529
|
+
crashes.crashes.toLocaleString()
|
|
530
|
+
)}
|
|
531
|
+
</TableCell>
|
|
532
|
+
<TableCell className="text-right whitespace-nowrap tabular-nums">
|
|
533
|
+
{crashes === undefined ||
|
|
534
|
+
crashes.attempts === 0 ? (
|
|
535
|
+
"—"
|
|
536
|
+
) : crashes.recommended ? (
|
|
537
|
+
<span className="inline-flex items-center gap-1 font-medium text-destructive">
|
|
538
|
+
<TriangleAlert
|
|
539
|
+
aria-hidden="true"
|
|
540
|
+
className="size-3.5"
|
|
541
|
+
/>
|
|
542
|
+
{percent(crashes.rate)}
|
|
543
|
+
<span className="sr-only">
|
|
544
|
+
, rollback recommended
|
|
545
|
+
</span>
|
|
546
|
+
</span>
|
|
547
|
+
) : (
|
|
548
|
+
<span
|
|
549
|
+
className={cn(
|
|
550
|
+
crashes.attempts < CRASH_MIN_ATTEMPTS &&
|
|
551
|
+
"text-muted-foreground",
|
|
552
|
+
)}
|
|
553
|
+
title={
|
|
554
|
+
crashes.attempts < CRASH_MIN_ATTEMPTS
|
|
555
|
+
? `Only ${crashes.attempts} installations tried it`
|
|
556
|
+
: undefined
|
|
557
|
+
}
|
|
558
|
+
>
|
|
559
|
+
{percent(crashes.rate)}
|
|
560
|
+
</span>
|
|
561
|
+
)}
|
|
562
|
+
</TableCell>
|
|
563
|
+
</>
|
|
564
|
+
)}
|
|
565
|
+
<TableCell>
|
|
566
|
+
<Button
|
|
567
|
+
aria-label={`Remove ${release.releaseId}`}
|
|
568
|
+
variant="ghost"
|
|
569
|
+
size="icon-xs"
|
|
570
|
+
onClick={() => remove(release.releaseId)}
|
|
571
|
+
>
|
|
572
|
+
<X aria-hidden="true" />
|
|
573
|
+
</Button>
|
|
574
|
+
</TableCell>
|
|
575
|
+
</TableRow>
|
|
576
|
+
);
|
|
577
|
+
})}
|
|
578
|
+
</TableBody>
|
|
579
|
+
</Table>
|
|
580
|
+
{addable.length > 0 ? (
|
|
581
|
+
<Select
|
|
582
|
+
items={Object.fromEntries(
|
|
583
|
+
addable.map((candidate) => [
|
|
584
|
+
candidate.releaseId,
|
|
585
|
+
`${candidate.releaseId} · ${formatDeployedAt(candidate.deployedAtMs)}`,
|
|
586
|
+
]),
|
|
587
|
+
)}
|
|
588
|
+
value={null}
|
|
589
|
+
disabled={full}
|
|
590
|
+
onValueChange={(value) => {
|
|
591
|
+
if (value !== null) onReleasesChange([...ids, value]);
|
|
592
|
+
}}
|
|
593
|
+
>
|
|
594
|
+
<SelectTrigger
|
|
595
|
+
aria-label="Add a bundle"
|
|
596
|
+
className="min-h-11 w-full sm:min-h-9 sm:w-80"
|
|
597
|
+
>
|
|
598
|
+
<SelectValue
|
|
599
|
+
placeholder={
|
|
600
|
+
full
|
|
601
|
+
? `Up to ${MAX_ADOPTION_RELEASES} bundles`
|
|
602
|
+
: "Add a bundle"
|
|
603
|
+
}
|
|
604
|
+
/>
|
|
605
|
+
</SelectTrigger>
|
|
606
|
+
<SelectContent>
|
|
607
|
+
<SelectGroup>
|
|
608
|
+
{addable.map((candidate) => (
|
|
609
|
+
<SelectItem
|
|
610
|
+
key={candidate.releaseId}
|
|
611
|
+
value={candidate.releaseId}
|
|
309
612
|
>
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
613
|
+
<span className="flex min-w-0 flex-col gap-0.5">
|
|
614
|
+
<BundleIdDisplay bundleId={candidate.releaseId} />{" "}
|
|
615
|
+
<span className="text-xs text-muted-foreground">
|
|
616
|
+
Deployed{" "}
|
|
617
|
+
{formatDeployedAt(candidate.deployedAtMs)}
|
|
618
|
+
</span>
|
|
619
|
+
</span>
|
|
620
|
+
</SelectItem>
|
|
314
621
|
))}
|
|
315
|
-
</
|
|
316
|
-
|
|
317
|
-
</
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
</details>
|
|
321
|
-
</>
|
|
322
|
-
)}
|
|
323
|
-
</>
|
|
324
|
-
);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
export function InsightsOverview({ input }: { readonly input: RecoveryInput }) {
|
|
328
|
-
const query = useQuery({
|
|
329
|
-
queryKey: ["rollout-activity", input],
|
|
330
|
-
queryFn: () => getRecoveryReportRpc({ data: input }),
|
|
331
|
-
staleTime: 30_000,
|
|
332
|
-
});
|
|
333
|
-
return (
|
|
334
|
-
<Card className="min-w-0 overflow-hidden shadow-sm">
|
|
335
|
-
<CardHeader className="flex-row items-center justify-between gap-4 px-4 pt-4 pb-2 sm:px-6 sm:pt-6">
|
|
336
|
-
<CardTitle className="text-sm font-medium">
|
|
337
|
-
Bundle activity ·{" "}
|
|
338
|
-
{input.window === "30d"
|
|
339
|
-
? "30 days"
|
|
340
|
-
: input.window === "7d"
|
|
341
|
-
? "7 days"
|
|
342
|
-
: "24 hours"}
|
|
343
|
-
</CardTitle>
|
|
344
|
-
<Button
|
|
345
|
-
aria-label="Refresh bundle activity"
|
|
346
|
-
variant="ghost"
|
|
347
|
-
size="icon-sm"
|
|
348
|
-
disabled={query.isFetching}
|
|
349
|
-
onClick={() => void query.refetch()}
|
|
350
|
-
>
|
|
351
|
-
<RotateCw aria-hidden="true" />
|
|
352
|
-
</Button>
|
|
353
|
-
</CardHeader>
|
|
354
|
-
<CardContent className="flex flex-col gap-4 px-4 pb-4 sm:px-6 sm:pb-6">
|
|
355
|
-
{query.isPending ? (
|
|
356
|
-
<Skeleton
|
|
357
|
-
aria-label="Loading bundle activity"
|
|
358
|
-
className="h-64 w-full"
|
|
359
|
-
/>
|
|
360
|
-
) : query.error ? (
|
|
361
|
-
<InsightsErrorAlert
|
|
362
|
-
error={query.error}
|
|
363
|
-
fallbackTitle="Bundle activity unavailable"
|
|
364
|
-
/>
|
|
365
|
-
) : query.data ? (
|
|
366
|
-
<ActivityChart key={JSON.stringify(input)} report={query.data} />
|
|
367
|
-
) : null}
|
|
368
|
-
</CardContent>
|
|
369
|
-
{query.data && !query.error ? (
|
|
370
|
-
<CardFooter className="flex-col items-start gap-2 border-t bg-muted/15 px-4 py-3 text-xs text-muted-foreground sm:px-6">
|
|
371
|
-
<p>
|
|
372
|
-
Active follows each installation’s last reported ID in this period.
|
|
373
|
-
Rollbacks count installations recovered from each ID per interval.
|
|
374
|
-
</p>
|
|
375
|
-
{query.data.unattributedInstallations > 0 ? (
|
|
376
|
-
<p>
|
|
377
|
-
{query.data.unattributedInstallations.toLocaleString()} reporting
|
|
378
|
-
installations have no observed bundle ID and are excluded.
|
|
379
|
-
</p>
|
|
380
|
-
) : null}
|
|
381
|
-
{query.data.truncated ? (
|
|
382
|
-
<p>
|
|
383
|
-
Partial history — only reports since{" "}
|
|
384
|
-
{times.format(query.data.sinceMs)} UTC are included. Choose a
|
|
385
|
-
shorter period to see more complete counts.
|
|
386
|
-
</p>
|
|
622
|
+
</SelectGroup>
|
|
623
|
+
</SelectContent>
|
|
624
|
+
</Select>
|
|
625
|
+
) : null}
|
|
626
|
+
</>
|
|
387
627
|
) : null}
|
|
628
|
+
</CardContent>
|
|
629
|
+
<CardFooter className="flex items-center justify-end border-t px-6 py-5">
|
|
630
|
+
<Link
|
|
631
|
+
className={buttonVariants({ variant: "outline", size: "sm" })}
|
|
632
|
+
to="/installations"
|
|
633
|
+
>
|
|
634
|
+
<ListIcon aria-hidden="true" />
|
|
635
|
+
Event history
|
|
636
|
+
</Link>
|
|
388
637
|
</CardFooter>
|
|
389
|
-
|
|
390
|
-
</
|
|
638
|
+
</Card>
|
|
639
|
+
</section>
|
|
391
640
|
);
|
|
392
641
|
}
|