@hot-updater/console 1.0.0-rc.2 → 1.0.0-rc.21
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-D0LPL1Gy.js +1 -0
- package/.output/public/assets/HashValueDisplay-BmuB4FW-.js +1 -0
- package/.output/public/assets/InsightsInfo--v4SC07c.js +1 -0
- package/.output/public/assets/InsightsPageHeader-dU83CAQY.js +1 -0
- package/.output/public/assets/NotFoundPage-B5mhGG2a.js +1 -0
- package/.output/public/assets/api-D-vOVlQ4.js +1 -0
- package/.output/public/assets/api-keys-Csh8ARKU.js +1 -0
- package/.output/public/assets/api-keys-OMPUoA_A.js +1 -0
- package/.output/public/assets/arrow-left-Bwq9X1eS.js +1 -0
- package/.output/public/assets/arrow-up-right-BkOVn4if.js +1 -0
- package/.output/public/assets/card-C_7d6O9a.js +1 -0
- package/.output/public/assets/console-features-api-C1jRZTGw.js +1 -0
- package/.output/public/assets/dialog-DE8wFN_P.js +1 -0
- package/.output/public/assets/dist-C7ENGzpS.js +1 -0
- package/.output/public/assets/empty-CVd5NlQD.js +1 -0
- package/.output/public/assets/field-C06q1iC9.js +1 -0
- package/.output/public/assets/index-BeWlQtO6.js +11 -0
- package/.output/public/assets/input-group-Bi61biIA.js +1 -0
- package/.output/public/assets/insights-BjnAtC0f.js +2 -0
- package/.output/public/assets/insights-D86m11yH.js +42 -0
- package/.output/public/assets/insights-DpYstMoY.js +1 -0
- package/.output/public/assets/insights-recovery-rpc-BIm5_ALc.js +1 -0
- package/.output/public/assets/insights-search-KIF7mRtB.js +1 -0
- package/.output/public/assets/insights-usage-rpc-VHj8vyBt.js +1 -0
- package/.output/public/assets/insights_.distribution-ADcrX48c.js +2 -0
- package/.output/public/assets/insights_.distribution-CuuJUN6Y.js +1 -0
- package/.output/public/assets/insights_.distribution-DJRjuXsO.js +1 -0
- package/.output/public/assets/installations-BSBBzqWs.js +2 -0
- package/.output/public/assets/installations-DqIE6WcO.js +1 -0
- package/.output/public/assets/installations-UsELbDvw.js +1 -0
- package/.output/public/assets/loader-circle-CnCBxzHp.js +1 -0
- package/.output/public/assets/mutation-Csp7xUNh.js +1 -0
- package/.output/public/assets/preload-helper-B-cOmpLH.js +1 -0
- package/.output/public/assets/refresh-cw-FF1NtzlQ.js +1 -0
- package/.output/public/assets/routes-CoN9XHP_.js +2 -0
- package/.output/public/assets/routes-DT1jB0Zi.js +1 -0
- package/.output/public/assets/sidebar-D8mJRF15.js +3 -0
- package/.output/public/assets/styles-Bwt7v6U5.css +2 -0
- package/.output/public/assets/table-WBgfvgXg.js +1 -0
- package/.output/public/assets/useCompositeListItem-CloBmDwn.js +1 -0
- package/.output/public/assets/useMutation-DO8cvsTx.js +1 -0
- package/.output/public/assets/useOpenInteractionType-ReGr9GzJ.js +1 -0
- package/.output/public/assets/valueToPercent-BvPAi4bP.js +1 -0
- package/.output/server/{__23tanstack-start-server-fn-resolver-B7ioJhtE.mjs → __23tanstack-start-server-fn-resolver-DMRD4e4f.mjs} +51 -47
- package/.output/server/_libs/@base-ui/react+[...].mjs +7044 -3622
- package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +1554 -0
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +3803 -4023
- package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +141 -98
- package/.output/server/_libs/d3-array.mjs +231 -0
- package/.output/server/_libs/d3-color.mjs +330 -0
- package/.output/server/_libs/d3-format.mjs +252 -0
- package/.output/server/_libs/d3-interpolate.mjs +207 -0
- package/.output/server/_libs/d3-path.mjs +77 -0
- package/.output/server/_libs/d3-scale+[...].mjs +1907 -0
- package/.output/server/_libs/d3-shape.mjs +818 -0
- package/.output/server/_libs/decimal.js-light.mjs +884 -0
- package/.output/server/_libs/es-toolkit.mjs +1159 -0
- package/.output/server/_libs/eventemitter3.mjs +236 -0
- package/.output/server/_libs/immer.mjs +532 -0
- package/.output/server/_libs/lucide-react.mjs +46 -32
- package/.output/server/_libs/next-themes.mjs +2 -2
- package/.output/server/_libs/react-redux+[...].mjs +205 -0
- package/.output/server/_libs/recharts+[...].mjs +14350 -0
- package/.output/server/_libs/sonner.mjs +2 -2
- package/.output/server/_libs/tanstack__history.mjs +68 -41
- package/.output/server/_libs/tanstack__query-core.mjs +1618 -309
- package/.output/server/_libs/tanstack__react-query.mjs +262 -22
- package/.output/server/_libs/verkit.mjs +99 -1
- package/.output/server/_runtime.mjs +10 -1
- package/.output/server/_ssr/ConsoleFeatureUnavailable-Du7jNPNS.mjs +58 -0
- package/.output/server/_ssr/{HashValueDisplay-DwpGLfrl.mjs → HashValueDisplay-GQvFkXoi.mjs} +3 -3
- package/.output/server/_ssr/InsightsInfo-Do7QSaFR.mjs +37 -0
- package/.output/server/_ssr/InsightsPageHeader-DSUWUWRc.mjs +154 -0
- package/.output/server/_ssr/NotFoundPage-DsF4Vrhg.mjs +37 -0
- package/.output/server/_ssr/api-BEiVs0H7.mjs +203 -0
- package/.output/server/_ssr/api-keys-BrvBWGRf.mjs +5 -0
- package/.output/server/_ssr/{api-keys-CZeTPQys.mjs → api-keys-K5v32nf7.mjs} +49 -9
- package/.output/server/_ssr/api-keys-rpc-B96bBllm.mjs +52 -0
- package/.output/server/_ssr/api-rpc-DcUe280B.mjs +296 -0
- package/.output/server/_ssr/{auth-rpc-DUaeOWKK.mjs → auth-rpc-C68W56DP.mjs} +6 -6
- package/.output/server/_ssr/auth.server-DM1J6NwR.mjs +5712 -0
- package/.output/server/_ssr/bundleActivity-CpuY9CTP.mjs +19 -0
- package/.output/server/_ssr/card-CPAEKx0w.mjs +49 -0
- package/.output/server/_ssr/config-nsIcsHh8.mjs +15 -0
- package/.output/server/_ssr/config.server-DDCZk3Sq.mjs +36 -0
- package/.output/server/_ssr/console-access-Da-2Qd7q.mjs +17 -0
- package/.output/server/_ssr/console-features-Dzu2iUGD.mjs +55 -0
- package/.output/server/_ssr/console-features-api-xAsSgFa_.mjs +43 -0
- package/.output/server/_ssr/console-features-rpc-qARGpB24.mjs +19 -0
- package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-BdBf_ezN.mjs} +67 -56
- package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-B_y2lrwA.mjs} +2 -2
- package/.output/server/_ssr/{databaseJsonValue-d6I3nAS4.mjs → databaseJsonValue-Bhxciy7Z.mjs} +28 -5
- package/.output/server/_ssr/databaseRows-DwMAjaF8.mjs +74 -0
- package/.output/server/_ssr/{dialog-DS-nw-pA.mjs → dialog-q__BJ7mQ.mjs} +2 -2
- package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-Cxo9ghYv.mjs} +140 -6
- package/.output/server/_ssr/{downloadBundle-CNnYxYO2.mjs → downloadBundle-BW9I-pMQ.mjs} +10 -10
- package/.output/server/_ssr/empty-DcBTAooP.mjs +49 -0
- package/.output/server/_ssr/{empty-plugin-adapters-B5QvGpEl.mjs → empty-plugin-adapters-CYdinE-i.mjs} +1 -1
- package/.output/server/_ssr/{table-CnZmon_Z.mjs → field-QCDBVpXA.mjs} +63 -51
- package/.output/server/_ssr/getBundleChildren-DIAGCF4a.mjs +22 -0
- package/.output/server/_ssr/{input-group-BpsnmvZw.mjs → input-group-hCk2nUoU.mjs} +2 -2
- package/.output/server/_ssr/insights-BfBO0RFr.mjs +1238 -0
- package/.output/server/_ssr/insights-DsAU3-TB.mjs +14 -0
- package/.output/server/_ssr/insights-RIfg447W.mjs +5 -0
- package/.output/server/_ssr/insights-api-CDn-QQHU.mjs +105 -0
- package/.output/server/_ssr/insights-failures-BBGMhuUr.mjs +44 -0
- package/.output/server/_ssr/insights-failures-rpc-rA2dctK2.mjs +21 -0
- package/.output/server/_ssr/insights-recovery-Dpk6Mfb9.mjs +28 -0
- package/.output/server/_ssr/insights-recovery-rpc-BCaF-xSd.mjs +46 -0
- package/.output/server/_ssr/insights-recovery-rpc-CcgYyUHk.mjs +20 -0
- package/.output/server/_ssr/{insights-rpc-38DujK0G.mjs → insights-rpc-keycmwzw.mjs} +22 -14
- package/.output/server/_ssr/insights-search-BRg5VsNe.mjs +25 -0
- package/.output/server/_ssr/insights-usage-cbw9n2Fq.mjs +29 -0
- package/.output/server/_ssr/insights-usage-rpc-BKgV1QDf.mjs +29 -0
- package/.output/server/_ssr/insights-usage-rpc-D-JYachy.mjs +41 -0
- package/.output/server/_ssr/insightsRecovery-B7oMc8d8.mjs +45 -0
- package/.output/server/_ssr/insightsUsage-Oa7kk5ek.mjs +56 -0
- package/.output/server/_ssr/insights_.distribution-BIqOqRW0.mjs +335 -0
- package/.output/server/_ssr/insights_.distribution-CuUrDa7P.mjs +5 -0
- package/.output/server/_ssr/insights_.distribution-RT_6x_ud.mjs +14 -0
- package/.output/server/_ssr/{installations-DiVKEF5T.mjs → installations-BO5CjX2z.mjs} +517 -54
- package/.output/server/_ssr/installations-BjaHiIfh.mjs +5 -0
- package/.output/server/_ssr/installations-Dt5luA-N.mjs +69 -0
- package/.output/server/_ssr/listReleases-BitHzOYB.mjs +79 -0
- package/.output/server/_ssr/releaseManagement-B3e7I8J9.mjs +11 -0
- package/.output/server/_ssr/{router-B9wG_9f8.mjs → router-DfVAixxe.mjs} +135 -139
- package/.output/server/_ssr/{routes-D7Yh_jfj.mjs → routes-C6LstNp8.mjs} +176 -455
- package/.output/server/_ssr/routes-Q3Uhmwwz.mjs +292 -0
- package/.output/server/_ssr/runtime.server-fL0o6I2b.mjs +1459 -0
- package/.output/server/_ssr/schema-BKY9SqaY.mjs +25 -0
- package/.output/server/_ssr/select-CUlInmjk.mjs +107 -0
- package/.output/server/_ssr/server-C43wq9ft.mjs +8330 -0
- package/.output/server/_ssr/{createSsrRpc-DjWNJ0uc.mjs → sidebar-DKbx2ach.mjs} +117 -63
- package/.output/server/_ssr/ssr.mjs +1 -1
- package/.output/server/_ssr/start-BXUOv_n8.mjs +4 -0
- package/.output/server/_ssr/table-NGWV8B-S.mjs +53 -0
- package/.output/server/_ssr/updateFailures-Jwe2Baab.mjs +24 -0
- package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-C8bwiZr8.mjs} +2 -2
- package/.output/server/_tanstack-start-manifest_v-WQ5qGn8n.mjs +124 -0
- package/.output/server/index.mjs +267 -162
- 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 +99 -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 +370 -0
- package/src/components/features/insights/EstimatedCount.tsx +18 -0
- package/src/components/features/insights/EventDetails.tsx +117 -37
- package/src/components/features/insights/EventHistoryCard.tsx +112 -26
- package/src/components/features/insights/EventHistoryList.tsx +2 -2
- package/src/components/features/insights/InsightsControls.tsx +265 -102
- 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 +176 -173
- 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 +52 -10
- package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
- package/src/components/features/insights/ReleaseActivity.tsx +188 -0
- package/src/components/features/insights/ReportingDevicesSummary.tsx +65 -0
- package/src/components/features/insights/UpdateFailures.tsx +319 -0
- package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
- package/src/components/features/releases/ReleaseEditorSheet.tsx +30 -7
- package/src/components/ui/card.tsx +1 -1
- package/src/components/ui/empty.tsx +101 -0
- 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 +31 -0
- 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 +91 -0
- package/src/lib/insights-api.ts +37 -1
- package/src/lib/insights-failures-rpc.ts +21 -0
- package/src/lib/insights-failures.ts +129 -0
- package/src/lib/insights-recovery-rpc.ts +51 -0
- package/src/lib/insights-recovery.ts +68 -0
- package/src/lib/insights-retention.ts +21 -0
- package/src/lib/insights-rpc.ts +30 -12
- package/src/lib/insights-search.ts +58 -0
- package/src/lib/insights-usage-rpc.ts +29 -0
- package/src/lib/insights-usage.ts +74 -0
- package/src/lib/insights-view.ts +38 -2
- 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 +37 -0
- package/src/lib/server/config.server.ts +21 -74
- package/src/lib/server/console-feature-apis.ts +59 -0
- package/src/lib/server/console-runtime.server.ts +82 -38
- package/src/lib/server/downloadBundle.ts +16 -15
- package/src/lib/server/getBundleChildren.ts +31 -122
- package/src/lib/server/insightsRecovery.ts +55 -0
- package/src/lib/server/insightsUsage.ts +63 -0
- package/src/lib/server/listReleases.ts +109 -44
- package/src/lib/server/releaseReachability.ts +8 -7
- package/src/lib/server/runtime.server.ts +120 -31
- package/src/lib/server/updateFailures.ts +31 -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 +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 +167 -104
- package/src/routes/insights.tsx +100 -59
- package/src/routes/insights_.distribution.tsx +398 -0
- package/src/routes/installations.tsx +98 -41
- package/src/vite-internal.ts +15 -3
- package/src/vite.ts +2 -0
- package/.output/public/assets/HashValueDisplay-CB0Eskit.js +0 -1
- package/.output/public/assets/InsightsPageHeader-Car5LNiZ.js +0 -1
- package/.output/public/assets/api-keys-BzKocqRy.js +0 -1
- package/.output/public/assets/api-keys-api-BHcODB7q.js +0 -1
- package/.output/public/assets/card-Dhr_eFYj.js +0 -1
- package/.output/public/assets/chart-no-axes-combined-Dy4WJv2U.js +0 -1
- package/.output/public/assets/composite-Bp98A0Ld.js +0 -1
- package/.output/public/assets/createServerFn-CA8fs7QD.js +0 -3
- package/.output/public/assets/dialog-CUTrcPlB.js +0 -1
- package/.output/public/assets/dist-C_NydL0e.js +0 -1
- package/.output/public/assets/index-DdAz-CXv.js +0 -10
- package/.output/public/assets/input-group-cSOG4wh_.js +0 -1
- package/.output/public/assets/insights-C0PBSZ-n.js +0 -1
- package/.output/public/assets/installations-CE33wgbY.js +0 -2
- package/.output/public/assets/installations-CStZja8x.js +0 -1
- package/.output/public/assets/lazyRouteComponent-Ccp3bwBY.js +0 -1
- package/.output/public/assets/link-CI8idAUi.js +0 -1
- package/.output/public/assets/loader-circle-BoxLrsOu.js +0 -1
- package/.output/public/assets/preload-helper-CQ36nxok.js +0 -1
- package/.output/public/assets/refresh-cw-Bqc72UZG.js +0 -1
- package/.output/public/assets/routes-DGVXdzpw.js +0 -2
- package/.output/public/assets/routes-DTlwOr8I.js +0 -1
- package/.output/public/assets/shield-check-uK3MHWJZ.js +0 -1
- package/.output/public/assets/signing-CMf4aqDO.js +0 -1
- package/.output/public/assets/styles-BCvbtKsm.css +0 -2
- package/.output/public/assets/table-2vcV89QB.js +0 -1
- package/.output/public/assets/useCompositeListItem-CHLITE_4.js +0 -1
- package/.output/public/assets/x-DCd0j8c6.js +0 -1
- package/.output/server/_ssr/InsightsPageHeader-Df4Brj8u.mjs +0 -484
- package/.output/server/_ssr/api-keys-api-BUPQVSpk.mjs +0 -60
- package/.output/server/_ssr/api-keys-rpc-Cs7tS5_j.mjs +0 -136
- package/.output/server/_ssr/api-rpc-tAupfGQm.mjs +0 -1399
- package/.output/server/_ssr/auth.server-DW71WzsD.mjs +0 -47
- package/.output/server/_ssr/bundle-signing-rpc-BhnBw0-d.mjs +0 -16
- package/.output/server/_ssr/bundle-signing.server-C4NW5mWx.mjs +0 -12
- package/.output/server/_ssr/card-BRiPmi0C.mjs +0 -108
- package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
- package/.output/server/_ssr/config.server-afDuAwaG.mjs +0 -368
- package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
- package/.output/server/_ssr/deleteBundle-BTGwBgzx.mjs +0 -66
- package/.output/server/_ssr/getBundleChildren-C0vh4ZvE.mjs +0 -50
- package/.output/server/_ssr/insights-BdS364Xu.mjs +0 -460
- package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +0 -303
- package/.output/server/_ssr/installations-CtA59MQG.mjs +0 -29
- package/.output/server/_ssr/routes-CKrrS2q2.mjs +0 -37
- package/.output/server/_ssr/runtime.server-DacU1TBV.mjs +0 -438
- package/.output/server/_ssr/server-D_C2rMzf.mjs +0 -7468
- package/.output/server/_ssr/signing-Dz1WkZOf.mjs +0 -133
- package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
- package/.output/server/_tanstack-start-manifest_v-2MjTyxil.mjs +0 -97
- package/src/components/features/insights/InsightsBundleReports.tsx +0 -51
- 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/routes/signing.tsx +0 -37
|
@@ -1,203 +1,206 @@
|
|
|
1
1
|
import { Link } from "@tanstack/react-router";
|
|
2
|
-
import {
|
|
2
|
+
import { ListIcon, RotateCw } from "lucide-react";
|
|
3
|
+
import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts";
|
|
3
4
|
|
|
4
5
|
import { Button, buttonVariants } from "@/components/ui/button";
|
|
5
6
|
import {
|
|
6
7
|
Card,
|
|
7
8
|
CardContent,
|
|
8
|
-
CardDescription,
|
|
9
9
|
CardFooter,
|
|
10
10
|
CardHeader,
|
|
11
11
|
CardTitle,
|
|
12
12
|
} from "@/components/ui/card";
|
|
13
|
+
import {
|
|
14
|
+
ChartContainer,
|
|
15
|
+
ChartTooltip,
|
|
16
|
+
ChartTooltipContent,
|
|
17
|
+
} from "@/components/ui/chart";
|
|
13
18
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
14
|
-
import type {
|
|
15
|
-
import { outcomeLabels } from "@/lib/insights-view";
|
|
19
|
+
import type { RecoveryInput, RecoveryReport } from "@/lib/insights-recovery";
|
|
16
20
|
|
|
17
|
-
import {
|
|
18
|
-
import type { InsightsReportSelection } from "./InsightsBundleReports";
|
|
21
|
+
import { EstimatedCount } from "./EstimatedCount";
|
|
19
22
|
import { InsightsErrorAlert } from "./InsightsErrorAlert";
|
|
23
|
+
import { InsightsInfo } from "./InsightsInfo";
|
|
24
|
+
import { InsightsPeriodSelector } from "./InsightsPeriodSelector";
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
readonly active: ReportingInstallations;
|
|
27
|
-
readonly onOutcomeSelect: (selection: InsightsReportSelection) => void;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const windowCopy = {
|
|
31
|
-
"24h": "the last 24 hours",
|
|
32
|
-
"7d": "the last 7 days",
|
|
33
|
-
"30d": "the last 30 days",
|
|
34
|
-
} as const;
|
|
35
|
-
|
|
36
|
-
export function InsightsOverview(props: InsightsOverviewProps) {
|
|
37
|
-
const dateTimeFormat = useInsightsTimeFormat();
|
|
38
|
-
|
|
39
|
-
if (props.status === "loading") {
|
|
40
|
-
return (
|
|
41
|
-
<Card aria-label="Loading reporting installations" className="shadow-sm">
|
|
42
|
-
<CardHeader className="px-4 pt-4 pb-2 sm:px-6 sm:pt-6">
|
|
43
|
-
<Skeleton className="h-4 w-40" />
|
|
44
|
-
<Skeleton className="h-3 w-full max-w-md" />
|
|
45
|
-
</CardHeader>
|
|
46
|
-
<CardContent className="px-4 pb-6 sm:px-6">
|
|
47
|
-
<Skeleton className="h-12 w-28" />
|
|
48
|
-
</CardContent>
|
|
49
|
-
<CardFooter className="border-t px-4 py-3 sm:px-6">
|
|
50
|
-
<Skeleton className="h-8 w-full max-w-sm" />
|
|
51
|
-
</CardFooter>
|
|
52
|
-
</Card>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
26
|
+
const dates = new Intl.DateTimeFormat("en", {
|
|
27
|
+
month: "short",
|
|
28
|
+
day: "numeric",
|
|
29
|
+
timeZone: "UTC",
|
|
30
|
+
});
|
|
55
31
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
);
|
|
63
|
-
}
|
|
32
|
+
const rate = (report: RecoveryReport): string => {
|
|
33
|
+
const attempts = report.activeDays + report.failedLaunches;
|
|
34
|
+
return attempts === 0
|
|
35
|
+
? "—"
|
|
36
|
+
: `${((report.failedLaunches / attempts) * 100).toFixed(2)}%`;
|
|
37
|
+
};
|
|
64
38
|
|
|
65
|
-
|
|
66
|
-
const
|
|
39
|
+
function ReleaseHealth({ report }: { readonly report: RecoveryReport }) {
|
|
40
|
+
const attempts = report.activeDays + report.failedLaunches;
|
|
41
|
+
const activeInstallations = (
|
|
42
|
+
<EstimatedCount value={report.activeInstallations} />
|
|
43
|
+
);
|
|
44
|
+
const metrics = [
|
|
45
|
+
["Active installations", activeInstallations],
|
|
46
|
+
["Active days", report.activeDays.toLocaleString()],
|
|
47
|
+
["Failed launches", report.failedLaunches.toLocaleString()],
|
|
48
|
+
["Crash rate", attempts === 0 ? "— / No launch reports" : rate(report)],
|
|
49
|
+
] as const;
|
|
50
|
+
return (
|
|
51
|
+
<div className="flex flex-col gap-8">
|
|
52
|
+
<dl className="grid grid-cols-2 gap-5 lg:grid-cols-4">
|
|
53
|
+
{metrics.map(([label, value]) => (
|
|
54
|
+
<div key={label} className="flex flex-col gap-1">
|
|
55
|
+
<dt className="flex items-center gap-1 text-sm text-muted-foreground">
|
|
56
|
+
{label}
|
|
57
|
+
{label === "Active installations" ? (
|
|
58
|
+
<InsightsInfo label="About active installations">
|
|
59
|
+
Distinct installations that reported in this scope and period,
|
|
60
|
+
or that launched this release, estimated: typically within
|
|
61
|
+
about 3%.
|
|
62
|
+
</InsightsInfo>
|
|
63
|
+
) : label === "Active days" ? (
|
|
64
|
+
<InsightsInfo label="About active days">
|
|
65
|
+
Each installation counts once for each UTC day it launched,
|
|
66
|
+
and once more on a day an update applies or recovers. The
|
|
67
|
+
chart shows each day's daily active installations.
|
|
68
|
+
</InsightsInfo>
|
|
69
|
+
) : label === "Crash rate" ? (
|
|
70
|
+
<InsightsInfo label="About crash rate">
|
|
71
|
+
Reported OTA launch failures divided by active days plus
|
|
72
|
+
failed launches for the same scope and period.
|
|
73
|
+
</InsightsInfo>
|
|
74
|
+
) : null}
|
|
75
|
+
</dt>
|
|
76
|
+
<dd className="text-3xl font-semibold tracking-tight tabular-nums">
|
|
77
|
+
{value}
|
|
78
|
+
</dd>
|
|
79
|
+
</div>
|
|
80
|
+
))}
|
|
81
|
+
</dl>
|
|
82
|
+
{report.points.length === 0 ? (
|
|
83
|
+
<div className="flex h-56 items-center justify-center text-sm text-muted-foreground">
|
|
84
|
+
No launch reports in this period.
|
|
85
|
+
</div>
|
|
86
|
+
) : (
|
|
87
|
+
<ChartContainer
|
|
88
|
+
aria-label="Daily active installations and failed launches"
|
|
89
|
+
className="h-64 w-full aspect-auto"
|
|
90
|
+
config={{
|
|
91
|
+
dailyActiveInstallations: {
|
|
92
|
+
label: "Daily active installations",
|
|
93
|
+
color: "var(--chart-2)",
|
|
94
|
+
},
|
|
95
|
+
failedLaunches: {
|
|
96
|
+
label: "Failed launches",
|
|
97
|
+
color: "var(--chart-1)",
|
|
98
|
+
},
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
<LineChart data={report.points} margin={{ left: -12, right: 12 }}>
|
|
102
|
+
<CartesianGrid vertical={false} />
|
|
103
|
+
<XAxis
|
|
104
|
+
axisLine={false}
|
|
105
|
+
dataKey="startMs"
|
|
106
|
+
tickFormatter={(value) => dates.format(value)}
|
|
107
|
+
tickLine={false}
|
|
108
|
+
/>
|
|
109
|
+
<YAxis allowDecimals={false} axisLine={false} tickLine={false} />
|
|
110
|
+
<ChartTooltip
|
|
111
|
+
content={
|
|
112
|
+
<ChartTooltipContent
|
|
113
|
+
labelFormatter={(_, payload) =>
|
|
114
|
+
`${dates.format(payload[0]?.payload.startMs)} · UTC`
|
|
115
|
+
}
|
|
116
|
+
/>
|
|
117
|
+
}
|
|
118
|
+
/>
|
|
119
|
+
<Line
|
|
120
|
+
dataKey="dailyActiveInstallations"
|
|
121
|
+
stroke="var(--color-dailyActiveInstallations)"
|
|
122
|
+
strokeWidth={2}
|
|
123
|
+
isAnimationActive={false}
|
|
124
|
+
/>
|
|
125
|
+
<Line
|
|
126
|
+
dataKey="failedLaunches"
|
|
127
|
+
stroke="var(--color-failedLaunches)"
|
|
128
|
+
strokeWidth={2}
|
|
129
|
+
isAnimationActive={false}
|
|
130
|
+
/>
|
|
131
|
+
</LineChart>
|
|
132
|
+
</ChartContainer>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
67
137
|
|
|
138
|
+
export function InsightsOverview({
|
|
139
|
+
input,
|
|
140
|
+
onWindowChange,
|
|
141
|
+
query,
|
|
142
|
+
onRefresh,
|
|
143
|
+
}: {
|
|
144
|
+
readonly input: RecoveryInput;
|
|
145
|
+
readonly query: {
|
|
146
|
+
readonly data: RecoveryReport | undefined;
|
|
147
|
+
readonly error: Error | null;
|
|
148
|
+
readonly isPending: boolean;
|
|
149
|
+
readonly isFetching: boolean;
|
|
150
|
+
};
|
|
151
|
+
readonly onRefresh: () => void;
|
|
152
|
+
readonly onWindowChange: (window: RecoveryInput["window"]) => void;
|
|
153
|
+
}) {
|
|
68
154
|
return (
|
|
69
|
-
<section
|
|
70
|
-
aria-labelledby="reporting-installations-heading"
|
|
71
|
-
className="flex flex-col gap-4"
|
|
72
|
-
>
|
|
155
|
+
<section aria-label="Release health">
|
|
73
156
|
<Card className="min-w-0 overflow-hidden shadow-sm">
|
|
74
|
-
<CardHeader className="
|
|
75
|
-
<
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
157
|
+
<CardHeader className="flex flex-col gap-4 p-6">
|
|
158
|
+
<div className="flex flex-wrap items-center justify-between gap-4">
|
|
159
|
+
<CardTitle>Release health</CardTitle>
|
|
160
|
+
<div className="flex items-center gap-2">
|
|
161
|
+
<InsightsPeriodSelector
|
|
162
|
+
window={input.window}
|
|
163
|
+
onWindowChange={onWindowChange}
|
|
164
|
+
/>
|
|
165
|
+
<Button
|
|
166
|
+
aria-label="Refresh release health"
|
|
167
|
+
variant="ghost"
|
|
168
|
+
size="icon-lg"
|
|
169
|
+
disabled={query.isFetching}
|
|
170
|
+
onClick={onRefresh}
|
|
171
|
+
>
|
|
172
|
+
<RotateCw aria-hidden="true" />
|
|
173
|
+
</Button>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
85
176
|
</CardHeader>
|
|
86
|
-
<CardContent className="px-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
177
|
+
<CardContent className="px-6 pb-6">
|
|
178
|
+
{query.isPending ? (
|
|
179
|
+
<Skeleton aria-label="Loading release health" className="h-80" />
|
|
180
|
+
) : query.error ? (
|
|
181
|
+
<InsightsErrorAlert
|
|
182
|
+
error={query.error}
|
|
183
|
+
fallbackTitle="Release health unavailable"
|
|
184
|
+
/>
|
|
185
|
+
) : query.data ? (
|
|
186
|
+
<ReleaseHealth report={query.data} />
|
|
187
|
+
) : null}
|
|
90
188
|
</CardContent>
|
|
91
|
-
<CardFooter className="flex
|
|
92
|
-
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
touch
|
|
98
|
-
value={active.reportingInstallations.measuredAtMs}
|
|
99
|
-
/>
|
|
100
|
-
</dd>
|
|
101
|
-
</dl>
|
|
189
|
+
<CardFooter className="flex items-center justify-between border-t px-6 py-5">
|
|
190
|
+
{query.data?.coverage.kind === "partial" ? (
|
|
191
|
+
<span className="text-xs text-muted-foreground">Partial</span>
|
|
192
|
+
) : (
|
|
193
|
+
<span />
|
|
194
|
+
)}
|
|
102
195
|
<Link
|
|
103
|
-
className={buttonVariants({
|
|
104
|
-
className: "h-11 px-3 lg:h-8",
|
|
105
|
-
variant: "outline",
|
|
106
|
-
})}
|
|
196
|
+
className={buttonVariants({ variant: "outline", size: "sm" })}
|
|
107
197
|
to="/installations"
|
|
108
198
|
>
|
|
109
|
-
|
|
110
|
-
|
|
199
|
+
<ListIcon aria-hidden="true" />
|
|
200
|
+
Event history
|
|
111
201
|
</Link>
|
|
112
202
|
</CardFooter>
|
|
113
203
|
</Card>
|
|
114
|
-
{bundle ? (
|
|
115
|
-
<Card className="min-w-0 overflow-hidden shadow-sm">
|
|
116
|
-
<CardHeader>
|
|
117
|
-
<CardTitle>Selected bundle</CardTitle>
|
|
118
|
-
<CardDescription className="break-all">
|
|
119
|
-
{bundle.bundleId}
|
|
120
|
-
</CardDescription>
|
|
121
|
-
</CardHeader>
|
|
122
|
-
<CardContent>
|
|
123
|
-
<dl className="flex flex-col gap-5">
|
|
124
|
-
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
125
|
-
<div>
|
|
126
|
-
<dt className="text-sm">Selected bundle installations</dt>
|
|
127
|
-
<dd className="mt-1 text-2xl font-semibold tabular-nums">
|
|
128
|
-
{bundle.reportingInstallations.count.toLocaleString()}
|
|
129
|
-
</dd>
|
|
130
|
-
</div>
|
|
131
|
-
<div className="text-xs text-muted-foreground">
|
|
132
|
-
Measured at{" "}
|
|
133
|
-
<EventTimestamp
|
|
134
|
-
formatter={dateTimeFormat}
|
|
135
|
-
touch
|
|
136
|
-
value={bundle.reportingInstallations.measuredAtMs}
|
|
137
|
-
/>
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
{(
|
|
141
|
-
[
|
|
142
|
-
["applied", bundle.appliedReports],
|
|
143
|
-
["recovered", bundle.recoveredReports],
|
|
144
|
-
["adopted", bundle.adoptedReports],
|
|
145
|
-
] as const
|
|
146
|
-
).map(([outcome, measurement]) => (
|
|
147
|
-
<div
|
|
148
|
-
className="flex flex-wrap items-center justify-between gap-3"
|
|
149
|
-
key={outcome}
|
|
150
|
-
>
|
|
151
|
-
<div>
|
|
152
|
-
<dt className="text-sm">{outcomeLabels[outcome]}</dt>
|
|
153
|
-
<dd className="mt-1 text-2xl font-semibold tabular-nums">
|
|
154
|
-
{measurement.count.toLocaleString()}
|
|
155
|
-
</dd>
|
|
156
|
-
<div className="mt-1 text-xs text-muted-foreground">
|
|
157
|
-
Measured at{" "}
|
|
158
|
-
<EventTimestamp
|
|
159
|
-
formatter={dateTimeFormat}
|
|
160
|
-
touch
|
|
161
|
-
value={measurement.measuredAtMs}
|
|
162
|
-
/>
|
|
163
|
-
</div>
|
|
164
|
-
</div>
|
|
165
|
-
<Button
|
|
166
|
-
className="h-11 lg:h-8"
|
|
167
|
-
variant="outline"
|
|
168
|
-
onClick={() =>
|
|
169
|
-
props.onOutcomeSelect({
|
|
170
|
-
bundle: {
|
|
171
|
-
platform: active.platform,
|
|
172
|
-
channel: active.channel,
|
|
173
|
-
bundleId: bundle.bundleId,
|
|
174
|
-
outcome,
|
|
175
|
-
},
|
|
176
|
-
sinceMs: active.sinceMs,
|
|
177
|
-
beforeReceivedAtMs: active.beforeReceivedAtMs,
|
|
178
|
-
})
|
|
179
|
-
}
|
|
180
|
-
>
|
|
181
|
-
View {outcome} reports
|
|
182
|
-
<ArrowUpRight aria-hidden="true" data-icon="inline-end" />
|
|
183
|
-
</Button>
|
|
184
|
-
</div>
|
|
185
|
-
))}
|
|
186
|
-
</dl>
|
|
187
|
-
</CardContent>
|
|
188
|
-
<CardFooter>
|
|
189
|
-
<p className="text-xs text-muted-foreground">
|
|
190
|
-
Installation counts are independent live measurements. Outcome
|
|
191
|
-
counts are accepted reports, not unique devices or update
|
|
192
|
-
attempts. Recovery reports identify the bundle recovered from.
|
|
193
|
-
</p>
|
|
194
|
-
</CardFooter>
|
|
195
|
-
</Card>
|
|
196
|
-
) : null}
|
|
197
|
-
<p className="text-xs text-muted-foreground">
|
|
198
|
-
Offline installations, opt-outs, and reports that did not reach the
|
|
199
|
-
server are absent.
|
|
200
|
-
</p>
|
|
201
204
|
</section>
|
|
202
205
|
);
|
|
203
206
|
}
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { Link } from "@tanstack/react-router";
|
|
2
|
-
import { ChartNoAxesCombined } from "lucide-react";
|
|
2
|
+
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";
|
|
7
|
+
import type { InsightsSearch } from "@/lib/insights-search";
|
|
8
|
+
import { cn } from "@/lib/utils";
|
|
6
9
|
|
|
7
10
|
export function InsightsPageHeader({
|
|
8
11
|
view,
|
|
12
|
+
overviewSearch,
|
|
9
13
|
}: {
|
|
10
|
-
readonly view: "overview" | "events";
|
|
14
|
+
readonly view: "overview" | "events" | "distribution";
|
|
15
|
+
readonly overviewSearch?: InsightsSearch;
|
|
11
16
|
}) {
|
|
17
|
+
// A self-hosted server's admin API serves events, not the overview.
|
|
18
|
+
const overview = useConsoleFeature("insightsAnalytics");
|
|
12
19
|
return (
|
|
13
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">
|
|
14
21
|
<SidebarTrigger className="-ml-1 size-11 lg:size-7" />
|
|
@@ -19,28 +26,36 @@ export function InsightsPageHeader({
|
|
|
19
26
|
/>
|
|
20
27
|
<h1 className="text-sm font-medium">Insights</h1>
|
|
21
28
|
</div>
|
|
22
|
-
<nav aria-label="Insights views" className="ml-auto flex gap-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
className
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
<nav aria-label="Insights views" className="ml-auto flex gap-2">
|
|
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}
|
|
34
46
|
<Link
|
|
35
47
|
aria-current={view === "events" ? "page" : undefined}
|
|
36
|
-
className={
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
className={cn(
|
|
49
|
+
buttonVariants({
|
|
50
|
+
className: "h-11 px-3 lg:h-8 lg:px-2.5",
|
|
51
|
+
size: "lg",
|
|
52
|
+
variant: view === "events" ? "secondary" : "ghost",
|
|
53
|
+
}),
|
|
54
|
+
)}
|
|
41
55
|
to="/installations"
|
|
42
56
|
>
|
|
43
|
-
|
|
57
|
+
<ListIcon aria-hidden="true" data-icon="inline-start" />
|
|
58
|
+
All events
|
|
44
59
|
</Link>
|
|
45
60
|
</nav>
|
|
46
61
|
</header>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
2
|
+
import type { InsightsWindow } from "@/lib/insights-api";
|
|
3
|
+
import { usageMetrics, type UsageWindow } from "@/lib/insights-usage";
|
|
4
|
+
|
|
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,
|
|
11
|
+
}: {
|
|
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
|
+
}[];
|
|
19
|
+
}) {
|
|
20
|
+
return (
|
|
21
|
+
<Tabs
|
|
22
|
+
value={period}
|
|
23
|
+
onValueChange={(value) => {
|
|
24
|
+
const next = periods.find((item) => item.value === value);
|
|
25
|
+
if (next) onPeriodChange(next.value);
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
<TabsList aria-label={label} className="min-h-11 sm:min-h-9">
|
|
29
|
+
{periods.map(({ value, name }) => (
|
|
30
|
+
<TabsTrigger
|
|
31
|
+
key={value}
|
|
32
|
+
value={value}
|
|
33
|
+
aria-label={name}
|
|
34
|
+
className="min-w-12 px-3"
|
|
35
|
+
>
|
|
36
|
+
{value}
|
|
37
|
+
</TabsTrigger>
|
|
38
|
+
))}
|
|
39
|
+
</TabsList>
|
|
40
|
+
</Tabs>
|
|
41
|
+
);
|
|
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
|
+
}
|