@hot-updater/console 1.0.0-rc.3 → 1.0.0-rc.30

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.
Files changed (280) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/ConsoleFeatureUnavailable-C2A-IEUj.js +1 -0
  3. package/.output/public/assets/EventDetails-Cmr-5qyj.js +1 -0
  4. package/.output/public/assets/InsightsInfo-BSBCWe3-.js +1 -0
  5. package/.output/public/assets/InsightsPageHeader-CD2fNN6p.js +1 -0
  6. package/.output/public/assets/NotFoundPage-jCpnR_sf.js +1 -0
  7. package/.output/public/assets/api-M5QUCPtH.js +1 -0
  8. package/.output/public/assets/api-keys-C07wGYuv.js +1 -0
  9. package/.output/public/assets/api-keys-EE1NZMDt.js +1 -0
  10. package/.output/public/assets/arrow-left-DD46PnTE.js +1 -0
  11. package/.output/public/assets/card-CYvj5tZx.js +1 -0
  12. package/.output/public/assets/console-features-api-Bk59EJ3s.js +1 -0
  13. package/.output/public/assets/dialog-JCXymiUi.js +1 -0
  14. package/.output/public/assets/dist-D6GJh2KA.js +1 -0
  15. package/.output/public/assets/empty-XdPINXx1.js +1 -0
  16. package/.output/public/assets/field-DzzNtTQg.js +1 -0
  17. package/.output/public/assets/index-GU56hAuf.js +11 -0
  18. package/.output/public/assets/input-group-BYLaHtwW.js +1 -0
  19. package/.output/public/assets/insights-BszMgsRK.js +42 -0
  20. package/.output/public/assets/insights-DQsl_Td9.js +1 -0
  21. package/.output/public/assets/insights-DpOzhVW7.js +2 -0
  22. package/.output/public/assets/insights-recovery-rpc-CTHkGU7B.js +1 -0
  23. package/.output/public/assets/insights-usage-rpc-D2B6f_Xe.js +1 -0
  24. package/.output/public/assets/insights_.distribution-DTT6zAk9.js +2 -0
  25. package/.output/public/assets/insights_.distribution-Dmk5sx7P.js +1 -0
  26. package/.output/public/assets/insights_.distribution-x-J185Zc.js +1 -0
  27. package/.output/public/assets/installations-2Z3CVLeT.js +2 -0
  28. package/.output/public/assets/installations-BF2P7U-4.js +1 -0
  29. package/.output/public/assets/installations-BcENidrS.js +1 -0
  30. package/.output/public/assets/loader-circle-OFX0xgRO.js +1 -0
  31. package/.output/public/assets/mutation-T1TB7pc4.js +1 -0
  32. package/.output/public/assets/preload-helper-I1Qxz43W.js +1 -0
  33. package/.output/public/assets/refresh-cw-Dq9bXycn.js +1 -0
  34. package/.output/public/assets/routes-BiY8Stye.js +2 -0
  35. package/.output/public/assets/routes-DwBZ3XAM.js +1 -0
  36. package/.output/public/assets/sidebar-CZeTS8e7.js +3 -0
  37. package/.output/public/assets/styles-BHmFL3Ss.css +2 -0
  38. package/.output/public/assets/table-BkWY5miS.js +1 -0
  39. package/.output/public/assets/useCompositeListItem-CnQYUiF9.js +1 -0
  40. package/.output/public/assets/useMutation-CowW32bj.js +1 -0
  41. package/.output/public/assets/valueToPercent-Bg4L42av.js +1 -0
  42. package/.output/server/{__23tanstack-start-server-fn-resolver-EMel9KYs.mjs → __23tanstack-start-server-fn-resolver-BhCfmV4w.mjs} +52 -48
  43. package/.output/server/_libs/@base-ui/react+[...].mjs +6891 -3364
  44. package/.output/server/_libs/@tanstack/react-router+[...].mjs +3802 -4022
  45. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +140 -97
  46. package/.output/server/_libs/lucide-react.mjs +64 -31
  47. package/.output/server/_libs/next-themes.mjs +1 -1
  48. package/.output/server/_libs/react-redux+[...].mjs +1 -1
  49. package/.output/server/_libs/recharts+[...].mjs +494 -780
  50. package/.output/server/_libs/sonner.mjs +1 -1
  51. package/.output/server/_libs/tanstack__history.mjs +68 -41
  52. package/.output/server/_libs/tanstack__query-core.mjs +1772 -310
  53. package/.output/server/_libs/tanstack__react-query.mjs +266 -21
  54. package/.output/server/_libs/verkit.mjs +99 -1
  55. package/.output/server/_ssr/ConsoleFeatureUnavailable-CxmgKN3V.mjs +58 -0
  56. package/.output/server/_ssr/EventDetails-BZ63i9RG.mjs +289 -0
  57. package/.output/server/_ssr/InsightsInfo-Dzb4MOKu.mjs +72 -0
  58. package/.output/server/_ssr/InsightsPageHeader-vA0_nYCk.mjs +161 -0
  59. package/.output/server/_ssr/NotFoundPage-B5W9MyXy.mjs +37 -0
  60. package/.output/server/_ssr/api-DeEqnrrM.mjs +203 -0
  61. package/.output/server/_ssr/api-keys-BgGUDfDY.mjs +5 -0
  62. package/.output/server/_ssr/api-keys-rpc-BQxVQR3u.mjs +52 -0
  63. package/.output/server/_ssr/{api-keys-BBl84_lN.mjs → api-keys-s5hSUx3p.mjs} +48 -8
  64. package/.output/server/_ssr/api-rpc-Cu2b-52B.mjs +296 -0
  65. package/.output/server/_ssr/{auth-rpc-gaibR719.mjs → auth-rpc-C5nwIq5q.mjs} +6 -6
  66. package/.output/server/_ssr/auth.server-BEWisUh1.mjs +5712 -0
  67. package/.output/server/_ssr/bundleActivity-DebSrpG0.mjs +19 -0
  68. package/.output/server/_ssr/card-BrzHgIMV.mjs +49 -0
  69. package/.output/server/_ssr/config-nsIcsHh8.mjs +15 -0
  70. package/.output/server/_ssr/config.server-CTZ2j8hg.mjs +36 -0
  71. package/.output/server/_ssr/console-access-CzZK7n93.mjs +17 -0
  72. package/.output/server/_ssr/console-features-BsjUyRwC.mjs +55 -0
  73. package/.output/server/_ssr/console-features-api-D87zBT2j.mjs +43 -0
  74. package/.output/server/_ssr/console-features-rpc-CT9CgHjn.mjs +19 -0
  75. package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-BdBf_ezN.mjs} +67 -56
  76. package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-B_y2lrwA.mjs} +2 -2
  77. package/.output/server/_ssr/{databaseJsonValue-d6I3nAS4.mjs → databaseJsonValue-Bhxciy7Z.mjs} +28 -5
  78. package/.output/server/_ssr/databaseRows-DwMAjaF8.mjs +74 -0
  79. package/.output/server/_ssr/{dialog-DNtKYR5K.mjs → dialog-Com7blkc.mjs} +2 -2
  80. package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-Cxo9ghYv.mjs} +140 -6
  81. package/.output/server/_ssr/{downloadBundle-A9zYC2go.mjs → downloadBundle-J1h1ilVu.mjs} +10 -10
  82. package/.output/server/_ssr/{empty-plugin-adapters-B5QvGpEl.mjs → empty-plugin-adapters-CYdinE-i.mjs} +1 -1
  83. package/.output/server/_ssr/empty-xB4Oxqe5.mjs +49 -0
  84. package/.output/server/_ssr/failureReports-C72EOpNj.mjs +33 -0
  85. package/.output/server/_ssr/{table-QQ9cnWf2.mjs → field-DHadIunv.mjs} +62 -50
  86. package/.output/server/_ssr/getBundleChildren-h_jo7zPh.mjs +22 -0
  87. package/.output/server/_ssr/{input-group-Oj4kUnE4.mjs → input-group-BoLVd6aM.mjs} +52 -3
  88. package/.output/server/_ssr/insights-C9yYMr6S.mjs +5 -0
  89. package/.output/server/_ssr/insights-D5Vr1-LU.mjs +14 -0
  90. package/.output/server/_ssr/insights-api-CwoqDPIC.mjs +122 -0
  91. package/.output/server/_ssr/insights-errors-F-7hOApm.mjs +34 -0
  92. package/.output/server/_ssr/insights-failures-BZaGNNij.mjs +47 -0
  93. package/.output/server/_ssr/insights-failures-rpc-Cn0h5GQN.mjs +36 -0
  94. package/.output/server/_ssr/insights-k-z2boTq.mjs +2140 -0
  95. package/.output/server/_ssr/{insights-recovery-DSFM-YTL.mjs → insights-recovery-Dpk6Mfb9.mjs} +9 -2
  96. package/.output/server/_ssr/insights-recovery-rpc-8zC6yDQG.mjs +20 -0
  97. package/.output/server/_ssr/insights-recovery-rpc-f1GkDalD.mjs +46 -0
  98. package/.output/server/_ssr/{insights-rpc-DBf0ouen.mjs → insights-rpc-BMZYat_c.mjs} +22 -14
  99. package/.output/server/_ssr/insights-usage-CLUWjUFz.mjs +29 -0
  100. package/.output/server/_ssr/insights-usage-rpc-C_rbTlzm.mjs +41 -0
  101. package/.output/server/_ssr/insights-usage-rpc-xA0701F_.mjs +29 -0
  102. package/.output/server/_ssr/insightsRecovery-9_N8XFIa.mjs +106 -0
  103. package/.output/server/_ssr/insightsUsage-BcqsRERl.mjs +56 -0
  104. package/.output/server/_ssr/insights_.distribution-BD8N3-RC.mjs +334 -0
  105. package/.output/server/_ssr/insights_.distribution-BW9aXDcX.mjs +5 -0
  106. package/.output/server/_ssr/insights_.distribution-DJJYtovL.mjs +14 -0
  107. package/.output/server/_ssr/installations-B5Mvy3PT.mjs +5 -0
  108. package/.output/server/_ssr/installations-COslBIxT.mjs +69 -0
  109. package/.output/server/_ssr/{installations-DQyJN-pZ.mjs → installations-CPTYrBnY.mjs} +428 -97
  110. package/.output/server/_ssr/listReleases-BitHzOYB.mjs +79 -0
  111. package/.output/server/_ssr/releaseManagement-B3e7I8J9.mjs +11 -0
  112. package/.output/server/_ssr/{router-C8J4nYEG.mjs → router-ChZfGLQb.mjs} +134 -138
  113. package/.output/server/_ssr/{routes-NlTRKPML.mjs → routes-BzIV6_XR.mjs} +174 -389
  114. package/.output/server/_ssr/routes-CwFJq8fZ.mjs +362 -0
  115. package/.output/server/_ssr/runtime.server-0kKViz4u.mjs +1597 -0
  116. package/.output/server/_ssr/schema-BKY9SqaY.mjs +25 -0
  117. package/.output/server/_ssr/select-1btAqVz9.mjs +107 -0
  118. package/.output/server/_ssr/server-ET4AuHDX.mjs +8330 -0
  119. package/.output/server/_ssr/{createSsrRpc-4rr4IJh3.mjs → sidebar-1gxSH3tz.mjs} +107 -47
  120. package/.output/server/_ssr/ssr.mjs +1 -1
  121. package/.output/server/_ssr/start-BXUOv_n8.mjs +4 -0
  122. package/.output/server/_ssr/table-CpDxIWGg.mjs +53 -0
  123. package/.output/server/_ssr/updateFailures-BI2k_oIa.mjs +40 -0
  124. package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-C8bwiZr8.mjs} +2 -2
  125. package/.output/server/_tanstack-start-manifest_v-DEx0iQr9.mjs +122 -0
  126. package/.output/server/index.mjs +253 -169
  127. package/LICENSE +13 -14
  128. package/README.md +82 -72
  129. package/lib/index.d.mts +23 -11
  130. package/lib/vite-internal.mjs +13 -3
  131. package/lib/vite.d.mts +2 -0
  132. package/package.json +14 -14
  133. package/src/components/AppSidebar.tsx +100 -71
  134. package/src/components/ConsoleAccessPage.tsx +5 -1
  135. package/src/components/ConsoleFeatureUnavailable.tsx +80 -0
  136. package/src/components/NotFoundPage.tsx +7 -7
  137. package/src/components/features/FeatureSlots.tsx +99 -0
  138. package/src/components/features/api-keys/apiKeysFeatureUi.tsx +15 -0
  139. package/src/components/features/bundles/BundleMetadata.tsx +8 -5
  140. package/src/components/features/bundles/RolloutCohortsDialog.tsx +1 -1
  141. package/src/components/features/channels/ChannelManagementDialog.tsx +2 -5
  142. package/src/components/features/insights/AppUsage.tsx +370 -0
  143. package/src/components/features/insights/BundleDistributionChart.tsx +253 -0
  144. package/src/components/features/insights/EstimatedCount.tsx +18 -0
  145. package/src/components/features/insights/EventDetails.tsx +294 -37
  146. package/src/components/features/insights/EventHistoryCard.tsx +112 -26
  147. package/src/components/features/insights/EventHistoryList.tsx +2 -2
  148. package/src/components/features/insights/FailureReports.tsx +394 -0
  149. package/src/components/features/insights/InsightsControls.tsx +265 -102
  150. package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
  151. package/src/components/features/insights/InsightsInfo.tsx +48 -0
  152. package/src/components/features/insights/InsightsOverview.tsx +257 -173
  153. package/src/components/features/insights/InsightsPageHeader.tsx +35 -20
  154. package/src/components/features/insights/InsightsPeriodSelector.tsx +89 -0
  155. package/src/components/features/insights/InstallationHistoryCard.tsx +58 -10
  156. package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
  157. package/src/components/features/insights/ReleaseActivity.tsx +293 -0
  158. package/src/components/features/insights/ReleaseAdoptionChart.tsx +325 -0
  159. package/src/components/features/insights/ReportingDevicesSummary.tsx +65 -0
  160. package/src/components/features/insights/UpdateFailures.tsx +451 -0
  161. package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
  162. package/src/components/features/releases/ReleaseEditorSheet.tsx +30 -20
  163. package/src/components/ui/card.tsx +1 -1
  164. package/src/components/ui/scroll-area.tsx +52 -0
  165. package/src/components/ui/sidebar.tsx +22 -29
  166. package/src/components/ui/tabs.tsx +81 -0
  167. package/src/index.ts +26 -19
  168. package/src/lib/api-keys-api.ts +0 -28
  169. package/src/lib/api-keys-rpc.ts +27 -38
  170. package/src/lib/api-rpc.ts +189 -266
  171. package/src/lib/api.ts +19 -102
  172. package/src/lib/bundle-activity.ts +31 -0
  173. package/src/lib/bundle-distribution.ts +81 -0
  174. package/src/lib/console-access.ts +20 -0
  175. package/src/lib/console-features-api.ts +68 -0
  176. package/src/lib/console-features-rpc.ts +13 -0
  177. package/src/lib/console-features.ts +91 -0
  178. package/src/lib/insights-api.ts +56 -2
  179. package/src/lib/insights-errors.ts +64 -0
  180. package/src/lib/insights-failures-rpc.ts +36 -0
  181. package/src/lib/insights-failures.ts +140 -0
  182. package/src/lib/insights-recovery-rpc.ts +43 -6
  183. package/src/lib/insights-recovery.ts +53 -17
  184. package/src/lib/insights-retention.ts +21 -0
  185. package/src/lib/insights-rpc.ts +30 -12
  186. package/src/lib/insights-search.ts +83 -0
  187. package/src/lib/insights-usage-rpc.ts +29 -0
  188. package/src/lib/insights-usage.ts +74 -0
  189. package/src/lib/insights-view.ts +38 -2
  190. package/src/lib/scroll-restoration.ts +21 -0
  191. package/src/lib/server/adminInsights.ts +124 -0
  192. package/src/lib/server/auth.server.ts +13 -5
  193. package/src/lib/server/bundleActivity.ts +37 -0
  194. package/src/lib/server/config.server.ts +21 -74
  195. package/src/lib/server/console-feature-apis.ts +59 -0
  196. package/src/lib/server/console-runtime.server.ts +82 -38
  197. package/src/lib/server/downloadBundle.ts +16 -15
  198. package/src/lib/server/failureReports.ts +49 -0
  199. package/src/lib/server/getBundleChildren.ts +31 -122
  200. package/src/lib/server/insightsRecovery.ts +129 -138
  201. package/src/lib/server/insightsUsage.ts +63 -0
  202. package/src/lib/server/listReleases.ts +109 -44
  203. package/src/lib/server/releaseReachability.ts +8 -7
  204. package/src/lib/server/runtime.server.ts +120 -31
  205. package/src/lib/server/updateFailures.ts +65 -0
  206. package/src/routeTree.gen.ts +44 -44
  207. package/src/router.tsx +3 -2
  208. package/src/routes/-installations-search.ts +32 -8
  209. package/src/routes/-releases-search.ts +76 -29
  210. package/src/routes/__root.tsx +10 -4
  211. package/src/routes/api/bundles/$bundleId/download.ts +2 -5
  212. package/src/routes/api-keys.tsx +5 -2
  213. package/src/routes/index.tsx +167 -104
  214. package/src/routes/insights.tsx +131 -61
  215. package/src/routes/insights_.distribution.tsx +398 -0
  216. package/src/routes/installations.tsx +98 -41
  217. package/src/vite-internal.ts +15 -3
  218. package/src/vite.ts +2 -0
  219. package/.output/public/assets/InsightsErrorAlert-1cg6LDJG.js +0 -1
  220. package/.output/public/assets/InsightsPageHeader-oNIg_lcd.js +0 -1
  221. package/.output/public/assets/InsightsRolloutCard-Zsv9gBjU.js +0 -42
  222. package/.output/public/assets/api-keys-ChJjsbP2.js +0 -1
  223. package/.output/public/assets/api-keys-api-LchP65vQ.js +0 -1
  224. package/.output/public/assets/card-iD7iiy4T.js +0 -1
  225. package/.output/public/assets/chart-no-axes-combined-C0G0fQ3J.js +0 -1
  226. package/.output/public/assets/createServerFn-DHc2erub.js +0 -3
  227. package/.output/public/assets/dialog-DX6o8qCL.js +0 -1
  228. package/.output/public/assets/dist-BPrb_vuw.js +0 -1
  229. package/.output/public/assets/index-C6Nb14xG.js +0 -10
  230. package/.output/public/assets/input-group-Pa3SAbEI.js +0 -1
  231. package/.output/public/assets/insights-C1uKy3p2.js +0 -1
  232. package/.output/public/assets/installations-Bj5AzZ3T.js +0 -1
  233. package/.output/public/assets/installations-DDTw9DSK.js +0 -2
  234. package/.output/public/assets/lazyRouteComponent-BN0MS05o.js +0 -1
  235. package/.output/public/assets/link-BG06whwB.js +0 -1
  236. package/.output/public/assets/loader-circle-Bbp8LGVC.js +0 -1
  237. package/.output/public/assets/preload-helper-B_l5jfgx.js +0 -1
  238. package/.output/public/assets/refresh-cw-DZgiAOMk.js +0 -1
  239. package/.output/public/assets/routes-9E2okfpG.js +0 -1
  240. package/.output/public/assets/routes-B3JviaDV.js +0 -2
  241. package/.output/public/assets/shield-check-DoIiaaXU.js +0 -1
  242. package/.output/public/assets/signing-BKy9fIdB.js +0 -1
  243. package/.output/public/assets/styles-C5-su40i.css +0 -2
  244. package/.output/public/assets/table-bpgpQkPz.js +0 -1
  245. package/.output/public/assets/useOpenInteractionType-4dPacbwb.js +0 -1
  246. package/.output/public/assets/x-Cr_sPM8E.js +0 -1
  247. package/.output/server/_ssr/InsightsErrorAlert-DzDVdfgW.mjs +0 -169
  248. package/.output/server/_ssr/InsightsPageHeader-CZ2AaQGk.mjs +0 -372
  249. package/.output/server/_ssr/InsightsRolloutCard-DHB2iM4t.mjs +0 -693
  250. package/.output/server/_ssr/api-keys-api-KSH04dLB.mjs +0 -60
  251. package/.output/server/_ssr/api-keys-rpc-DBmgnW5o.mjs +0 -136
  252. package/.output/server/_ssr/api-rpc-DX5B6b-L.mjs +0 -1399
  253. package/.output/server/_ssr/auth.server-DF_shCeq.mjs +0 -47
  254. package/.output/server/_ssr/bundle-signing-rpc-C01EKpb-.mjs +0 -16
  255. package/.output/server/_ssr/bundle-signing.server-Co_zHdJx.mjs +0 -12
  256. package/.output/server/_ssr/card-gGh8F6kQ.mjs +0 -108
  257. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
  258. package/.output/server/_ssr/config.server-NIXXHk-e.mjs +0 -368
  259. package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
  260. package/.output/server/_ssr/deleteBundle-C71v1GBr.mjs +0 -66
  261. package/.output/server/_ssr/getBundleChildren-CMGSGBWv.mjs +0 -50
  262. package/.output/server/_ssr/insights-oK2n5WBp.mjs +0 -406
  263. package/.output/server/_ssr/insights-recovery-rpc-CgeCAs2K.mjs +0 -16
  264. package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +0 -303
  265. package/.output/server/_ssr/insightsRecovery-DFkXGFjd.mjs +0 -108
  266. package/.output/server/_ssr/installations-DBmYuVBi.mjs +0 -29
  267. package/.output/server/_ssr/routes-C2RIYhyl.mjs +0 -37
  268. package/.output/server/_ssr/runtime.server-DLZpzphL.mjs +0 -438
  269. package/.output/server/_ssr/server-hgrXpJQY.mjs +0 -7468
  270. package/.output/server/_ssr/signing-bfOvUMSJ.mjs +0 -133
  271. package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
  272. package/.output/server/_tanstack-start-manifest_v-BAb_a_eC.mjs +0 -97
  273. package/src/components/features/insights/InsightsBundleReports.tsx +0 -51
  274. package/src/components/features/insights/InsightsRolloutCard.tsx +0 -505
  275. package/src/components/features/signing/BundleSigningPage.tsx +0 -139
  276. package/src/lib/bundle-signing-api.ts +0 -14
  277. package/src/lib/bundle-signing-rpc.ts +0 -16
  278. package/src/lib/server/bundle-signing.server.ts +0 -23
  279. package/src/lib/server/deleteBundle.ts +0 -145
  280. package/src/routes/signing.tsx +0 -37
@@ -1,203 +1,287 @@
1
1
  import { Link } from "@tanstack/react-router";
2
- import { ArrowUpRight } from "lucide-react";
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";
18
+ import {
19
+ Empty,
20
+ EmptyDescription,
21
+ EmptyHeader,
22
+ EmptyTitle,
23
+ } from "@/components/ui/empty";
13
24
  import { Skeleton } from "@/components/ui/skeleton";
14
- import type { ReportingInstallations } from "@/lib/insights-api";
15
- import { outcomeLabels } from "@/lib/insights-view";
25
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
26
+ import type { RecoveryInput, RecoveryReport } from "@/lib/insights-recovery";
27
+ import type { HealthChart } from "@/lib/insights-search";
28
+ import { cn } from "@/lib/utils";
16
29
 
17
- import { EventTimestamp, useInsightsTimeFormat } from "./EventDetails";
18
- import type { InsightsReportSelection } from "./InsightsBundleReports";
30
+ import { BundleDistributionChart } from "./BundleDistributionChart";
31
+ import { EstimatedCount } from "./EstimatedCount";
19
32
  import { InsightsErrorAlert } from "./InsightsErrorAlert";
33
+ import { InsightsInfo } from "./InsightsInfo";
34
+ import { InsightsPeriodSelector } from "./InsightsPeriodSelector";
35
+ import { ReleaseAdoptionChart } from "./ReleaseAdoptionChart";
20
36
 
21
- type InsightsOverviewProps =
22
- | { readonly status: "loading" }
23
- | { readonly status: "error"; readonly error: Error }
24
- | {
25
- readonly status: "success";
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
- }
37
+ const dates = new Intl.DateTimeFormat("en", {
38
+ month: "short",
39
+ day: "numeric",
40
+ timeZone: "UTC",
41
+ });
55
42
 
56
- if (props.status === "error") {
57
- return (
58
- <InsightsErrorAlert
59
- error={props.error}
60
- fallbackTitle="Reporting installations unavailable"
61
- />
62
- );
63
- }
43
+ const rate = (report: RecoveryReport): string => {
44
+ const attempts = report.activeDays + report.failedLaunches;
45
+ return attempts === 0
46
+ ? "—"
47
+ : `${((report.failedLaunches / attempts) * 100).toFixed(2)}%`;
48
+ };
64
49
 
65
- const { active } = props;
66
- const bundle = active.bundle;
50
+ function ReleaseHealth({
51
+ report,
52
+ releaseId,
53
+ window,
54
+ chart,
55
+ onChartChange,
56
+ onReleaseChange,
57
+ onWindowChange,
58
+ }: {
59
+ readonly report: RecoveryReport;
60
+ readonly releaseId?: string;
61
+ readonly window: RecoveryInput["window"];
62
+ readonly onWindowChange: (window: RecoveryInput["window"]) => void;
63
+ readonly chart: HealthChart;
64
+ readonly onChartChange: (chart: HealthChart) => void;
65
+ readonly onReleaseChange: (releaseId: string) => void;
66
+ }) {
67
+ const attempts = report.activeDays + report.failedLaunches;
68
+ const activeInstallations = (
69
+ <EstimatedCount value={report.activeInstallations} />
70
+ );
71
+ const metrics = [
72
+ ["Active installations", activeInstallations],
73
+ ["Active days", report.activeDays.toLocaleString()],
74
+ ["Failed launches", report.failedLaunches.toLocaleString()],
75
+ ["Crash rate", attempts === 0 ? "— / No launch reports" : rate(report)],
76
+ ] as const;
77
+ return (
78
+ <div className="flex flex-col gap-8">
79
+ <dl className="grid grid-cols-2 gap-5 lg:grid-cols-4">
80
+ {metrics.map(([label, value]) => (
81
+ <div key={label} className="flex flex-col gap-1">
82
+ <dt className="flex items-center gap-1 text-sm text-muted-foreground">
83
+ {label}
84
+ {label === "Active installations" ? (
85
+ <InsightsInfo label="About active installations">
86
+ Distinct installations that reported in this scope and period,
87
+ or that launched this release, estimated: typically within
88
+ about 3%.
89
+ </InsightsInfo>
90
+ ) : label === "Active days" ? (
91
+ <InsightsInfo label="About active days">
92
+ Each installation counts once for each UTC day it launched,
93
+ and once more on a day an update applies or recovers. The
94
+ activity totals include these additional reports.
95
+ </InsightsInfo>
96
+ ) : label === "Crash rate" ? (
97
+ <InsightsInfo label="About crash rate">
98
+ Reported OTA launch failures divided by active days plus
99
+ failed launches for the same scope and period.
100
+ </InsightsInfo>
101
+ ) : null}
102
+ </dt>
103
+ <dd className="text-3xl font-semibold tracking-tight tabular-nums">
104
+ {value}
105
+ </dd>
106
+ </div>
107
+ ))}
108
+ </dl>
109
+ <Tabs
110
+ value={chart}
111
+ onValueChange={(value) => onChartChange(value as HealthChart)}
112
+ className="gap-5"
113
+ >
114
+ <TabsList
115
+ aria-label="Release health chart"
116
+ className="min-h-11 sm:min-h-9"
117
+ >
118
+ <TabsTrigger value="share" className="px-3">
119
+ Bundle share
120
+ </TabsTrigger>
121
+ <TabsTrigger value="adoption" className="px-3">
122
+ Adoption
123
+ </TabsTrigger>
124
+ <TabsTrigger value="failures" className="px-3">
125
+ Launch failures
126
+ </TabsTrigger>
127
+ </TabsList>
128
+ <TabsContent value="share">
129
+ <BundleDistributionChart
130
+ history={report.distribution}
131
+ releaseId={releaseId}
132
+ />
133
+ </TabsContent>
134
+ <TabsContent value="adoption">
135
+ <ReleaseAdoptionChart
136
+ report={report}
137
+ releaseId={releaseId}
138
+ window={window}
139
+ onReleaseChange={onReleaseChange}
140
+ onWindowChange={onWindowChange}
141
+ />
142
+ </TabsContent>
143
+ <TabsContent value="failures">
144
+ {report.points.length === 0 ? (
145
+ <Empty className="min-h-64">
146
+ <EmptyHeader>
147
+ <EmptyTitle>No launch reports in this period.</EmptyTitle>
148
+ <EmptyDescription>
149
+ Choose a longer period or refresh after an app reports.
150
+ </EmptyDescription>
151
+ </EmptyHeader>
152
+ </Empty>
153
+ ) : (
154
+ <ChartContainer
155
+ aria-label="Daily failed launches"
156
+ className="h-64 w-full aspect-auto"
157
+ config={{
158
+ failedLaunches: {
159
+ label: "Failed launches",
160
+ color: "var(--chart-1)",
161
+ },
162
+ }}
163
+ >
164
+ <LineChart data={report.points} margin={{ left: -12, right: 12 }}>
165
+ <CartesianGrid vertical={false} />
166
+ <XAxis
167
+ axisLine={false}
168
+ dataKey="startMs"
169
+ tickFormatter={(value) => dates.format(value)}
170
+ tickLine={false}
171
+ />
172
+ <YAxis
173
+ allowDecimals={false}
174
+ axisLine={false}
175
+ tickLine={false}
176
+ />
177
+ <ChartTooltip
178
+ content={
179
+ <ChartTooltipContent
180
+ labelFormatter={(_, payload) =>
181
+ `${dates.format(payload[0]?.payload.startMs)} · UTC`
182
+ }
183
+ />
184
+ }
185
+ />
186
+ <Line
187
+ dataKey="failedLaunches"
188
+ stroke="var(--color-failedLaunches)"
189
+ strokeWidth={2}
190
+ isAnimationActive={false}
191
+ />
192
+ </LineChart>
193
+ </ChartContainer>
194
+ )}
195
+ </TabsContent>
196
+ </Tabs>
197
+ </div>
198
+ );
199
+ }
67
200
 
201
+ export function InsightsOverview({
202
+ input,
203
+ onWindowChange,
204
+ query,
205
+ onRefresh,
206
+ chart,
207
+ onChartChange,
208
+ onReleaseChange,
209
+ }: {
210
+ readonly input: RecoveryInput;
211
+ readonly chart: HealthChart;
212
+ readonly onChartChange: (chart: HealthChart) => void;
213
+ /** Chooses the Release health release, as the Release ID filter does. */
214
+ readonly onReleaseChange: (releaseId: string) => void;
215
+ readonly query: {
216
+ readonly data: RecoveryReport | undefined;
217
+ readonly error: Error | null;
218
+ readonly isPending: boolean;
219
+ readonly isFetching: boolean;
220
+ };
221
+ readonly onRefresh: () => void;
222
+ readonly onWindowChange: (window: RecoveryInput["window"]) => void;
223
+ }) {
68
224
  return (
69
- <section
70
- aria-labelledby="reporting-installations-heading"
71
- className="flex flex-col gap-4"
72
- >
225
+ <section aria-label="Release health">
73
226
  <Card className="min-w-0 overflow-hidden shadow-sm">
74
- <CardHeader className="gap-2 px-4 pt-4 pb-2 sm:px-6 sm:pt-6">
75
- <CardTitle className="text-sm font-medium">
76
- <h2 id="reporting-installations-heading">
77
- Reporting installations
78
- </h2>
79
- </CardTitle>
80
- <CardDescription>
81
- {active.platform === "ios" ? "iOS" : "Android"} · {active.channel} ·{" "}
82
- {windowCopy[active.window]}. Installations are counted by their last
83
- report received.
84
- </CardDescription>
227
+ <CardHeader className="flex flex-col gap-4 p-6">
228
+ <div className="flex flex-wrap items-center justify-between gap-4">
229
+ <CardTitle>Release health</CardTitle>
230
+ <div className="flex items-center gap-2">
231
+ <InsightsPeriodSelector
232
+ window={input.window}
233
+ onWindowChange={onWindowChange}
234
+ />
235
+ <Button
236
+ aria-label="Refresh release health"
237
+ variant="ghost"
238
+ size="icon-lg"
239
+ disabled={query.isFetching}
240
+ onClick={onRefresh}
241
+ >
242
+ <RotateCw
243
+ aria-hidden="true"
244
+ className={cn(query.isFetching && "motion-safe:animate-spin")}
245
+ />
246
+ </Button>
247
+ </div>
248
+ </div>
85
249
  </CardHeader>
86
- <CardContent className="px-4 pt-2 pb-5 sm:px-6 sm:pb-6">
87
- <p className="text-4xl font-semibold tracking-tight tabular-nums sm:text-5xl">
88
- {active.reportingInstallations.count.toLocaleString()}
89
- </p>
250
+ <CardContent aria-busy={query.isFetching} className="px-6 pb-6">
251
+ {query.isPending ? (
252
+ <Skeleton aria-label="Loading release health" className="h-80" />
253
+ ) : query.error ? (
254
+ <InsightsErrorAlert
255
+ error={query.error}
256
+ fallbackTitle="Release health unavailable"
257
+ />
258
+ ) : query.data ? (
259
+ <ReleaseHealth
260
+ report={query.data}
261
+ releaseId={input.releaseId}
262
+ window={input.window}
263
+ onWindowChange={onWindowChange}
264
+ chart={chart}
265
+ onChartChange={onChartChange}
266
+ onReleaseChange={onReleaseChange}
267
+ />
268
+ ) : null}
90
269
  </CardContent>
91
- <CardFooter className="flex flex-wrap items-center justify-between gap-3 border-t bg-muted/15 px-4 py-3 sm:px-6">
92
- <dl className="min-w-0 text-xs">
93
- <dt className="text-muted-foreground">Measured at</dt>
94
- <dd className="font-medium tabular-nums">
95
- <EventTimestamp
96
- formatter={dateTimeFormat}
97
- touch
98
- value={active.reportingInstallations.measuredAtMs}
99
- />
100
- </dd>
101
- </dl>
270
+ <CardFooter className="flex items-center justify-between border-t px-6 py-5">
271
+ {query.data?.coverage.kind === "partial" ? (
272
+ <span className="text-xs text-muted-foreground">Partial</span>
273
+ ) : (
274
+ <span />
275
+ )}
102
276
  <Link
103
- className={buttonVariants({
104
- className: "h-11 px-3 lg:h-8",
105
- variant: "outline",
106
- })}
277
+ className={buttonVariants({ variant: "outline", size: "sm" })}
107
278
  to="/installations"
108
279
  >
109
- View events
110
- <ArrowUpRight aria-hidden="true" data-icon="inline-end" />
280
+ <ListIcon aria-hidden="true" />
281
+ Event history
111
282
  </Link>
112
283
  </CardFooter>
113
284
  </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
285
  </section>
202
286
  );
203
287
  }
@@ -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-1">
23
- <Link
24
- aria-current={view === "overview" ? "page" : undefined}
25
- className={buttonVariants({
26
- className: "h-11 px-3 lg:h-8 lg:px-2.5",
27
- size: "lg",
28
- variant: view === "overview" ? "secondary" : "ghost",
29
- })}
30
- to="/insights"
31
- >
32
- Overview
33
- </Link>
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={buttonVariants({
37
- className: "h-11 px-3 lg:h-8 lg:px-2.5",
38
- size: "lg",
39
- variant: view === "events" ? "secondary" : "ghost",
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
- Events
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
+ }