@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.
Files changed (285) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/ConsoleFeatureUnavailable-D0LPL1Gy.js +1 -0
  3. package/.output/public/assets/HashValueDisplay-BmuB4FW-.js +1 -0
  4. package/.output/public/assets/InsightsInfo--v4SC07c.js +1 -0
  5. package/.output/public/assets/InsightsPageHeader-dU83CAQY.js +1 -0
  6. package/.output/public/assets/NotFoundPage-B5mhGG2a.js +1 -0
  7. package/.output/public/assets/api-D-vOVlQ4.js +1 -0
  8. package/.output/public/assets/api-keys-Csh8ARKU.js +1 -0
  9. package/.output/public/assets/api-keys-OMPUoA_A.js +1 -0
  10. package/.output/public/assets/arrow-left-Bwq9X1eS.js +1 -0
  11. package/.output/public/assets/arrow-up-right-BkOVn4if.js +1 -0
  12. package/.output/public/assets/card-C_7d6O9a.js +1 -0
  13. package/.output/public/assets/console-features-api-C1jRZTGw.js +1 -0
  14. package/.output/public/assets/dialog-DE8wFN_P.js +1 -0
  15. package/.output/public/assets/dist-C7ENGzpS.js +1 -0
  16. package/.output/public/assets/empty-CVd5NlQD.js +1 -0
  17. package/.output/public/assets/field-C06q1iC9.js +1 -0
  18. package/.output/public/assets/index-BeWlQtO6.js +11 -0
  19. package/.output/public/assets/input-group-Bi61biIA.js +1 -0
  20. package/.output/public/assets/insights-BjnAtC0f.js +2 -0
  21. package/.output/public/assets/insights-D86m11yH.js +42 -0
  22. package/.output/public/assets/insights-DpYstMoY.js +1 -0
  23. package/.output/public/assets/insights-recovery-rpc-BIm5_ALc.js +1 -0
  24. package/.output/public/assets/insights-search-KIF7mRtB.js +1 -0
  25. package/.output/public/assets/insights-usage-rpc-VHj8vyBt.js +1 -0
  26. package/.output/public/assets/insights_.distribution-ADcrX48c.js +2 -0
  27. package/.output/public/assets/insights_.distribution-CuuJUN6Y.js +1 -0
  28. package/.output/public/assets/insights_.distribution-DJRjuXsO.js +1 -0
  29. package/.output/public/assets/installations-BSBBzqWs.js +2 -0
  30. package/.output/public/assets/installations-DqIE6WcO.js +1 -0
  31. package/.output/public/assets/installations-UsELbDvw.js +1 -0
  32. package/.output/public/assets/loader-circle-CnCBxzHp.js +1 -0
  33. package/.output/public/assets/mutation-Csp7xUNh.js +1 -0
  34. package/.output/public/assets/preload-helper-B-cOmpLH.js +1 -0
  35. package/.output/public/assets/refresh-cw-FF1NtzlQ.js +1 -0
  36. package/.output/public/assets/routes-CoN9XHP_.js +2 -0
  37. package/.output/public/assets/routes-DT1jB0Zi.js +1 -0
  38. package/.output/public/assets/sidebar-D8mJRF15.js +3 -0
  39. package/.output/public/assets/styles-Bwt7v6U5.css +2 -0
  40. package/.output/public/assets/table-WBgfvgXg.js +1 -0
  41. package/.output/public/assets/useCompositeListItem-CloBmDwn.js +1 -0
  42. package/.output/public/assets/useMutation-DO8cvsTx.js +1 -0
  43. package/.output/public/assets/useOpenInteractionType-ReGr9GzJ.js +1 -0
  44. package/.output/public/assets/valueToPercent-BvPAi4bP.js +1 -0
  45. package/.output/server/{__23tanstack-start-server-fn-resolver-B7ioJhtE.mjs → __23tanstack-start-server-fn-resolver-DMRD4e4f.mjs} +51 -47
  46. package/.output/server/_libs/@base-ui/react+[...].mjs +7044 -3622
  47. package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +1554 -0
  48. package/.output/server/_libs/@tanstack/react-router+[...].mjs +3803 -4023
  49. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +141 -98
  50. package/.output/server/_libs/d3-array.mjs +231 -0
  51. package/.output/server/_libs/d3-color.mjs +330 -0
  52. package/.output/server/_libs/d3-format.mjs +252 -0
  53. package/.output/server/_libs/d3-interpolate.mjs +207 -0
  54. package/.output/server/_libs/d3-path.mjs +77 -0
  55. package/.output/server/_libs/d3-scale+[...].mjs +1907 -0
  56. package/.output/server/_libs/d3-shape.mjs +818 -0
  57. package/.output/server/_libs/decimal.js-light.mjs +884 -0
  58. package/.output/server/_libs/es-toolkit.mjs +1159 -0
  59. package/.output/server/_libs/eventemitter3.mjs +236 -0
  60. package/.output/server/_libs/immer.mjs +532 -0
  61. package/.output/server/_libs/lucide-react.mjs +46 -32
  62. package/.output/server/_libs/next-themes.mjs +2 -2
  63. package/.output/server/_libs/react-redux+[...].mjs +205 -0
  64. package/.output/server/_libs/recharts+[...].mjs +14350 -0
  65. package/.output/server/_libs/sonner.mjs +2 -2
  66. package/.output/server/_libs/tanstack__history.mjs +68 -41
  67. package/.output/server/_libs/tanstack__query-core.mjs +1618 -309
  68. package/.output/server/_libs/tanstack__react-query.mjs +262 -22
  69. package/.output/server/_libs/verkit.mjs +99 -1
  70. package/.output/server/_runtime.mjs +10 -1
  71. package/.output/server/_ssr/ConsoleFeatureUnavailable-Du7jNPNS.mjs +58 -0
  72. package/.output/server/_ssr/{HashValueDisplay-DwpGLfrl.mjs → HashValueDisplay-GQvFkXoi.mjs} +3 -3
  73. package/.output/server/_ssr/InsightsInfo-Do7QSaFR.mjs +37 -0
  74. package/.output/server/_ssr/InsightsPageHeader-DSUWUWRc.mjs +154 -0
  75. package/.output/server/_ssr/NotFoundPage-DsF4Vrhg.mjs +37 -0
  76. package/.output/server/_ssr/api-BEiVs0H7.mjs +203 -0
  77. package/.output/server/_ssr/api-keys-BrvBWGRf.mjs +5 -0
  78. package/.output/server/_ssr/{api-keys-CZeTPQys.mjs → api-keys-K5v32nf7.mjs} +49 -9
  79. package/.output/server/_ssr/api-keys-rpc-B96bBllm.mjs +52 -0
  80. package/.output/server/_ssr/api-rpc-DcUe280B.mjs +296 -0
  81. package/.output/server/_ssr/{auth-rpc-DUaeOWKK.mjs → auth-rpc-C68W56DP.mjs} +6 -6
  82. package/.output/server/_ssr/auth.server-DM1J6NwR.mjs +5712 -0
  83. package/.output/server/_ssr/bundleActivity-CpuY9CTP.mjs +19 -0
  84. package/.output/server/_ssr/card-CPAEKx0w.mjs +49 -0
  85. package/.output/server/_ssr/config-nsIcsHh8.mjs +15 -0
  86. package/.output/server/_ssr/config.server-DDCZk3Sq.mjs +36 -0
  87. package/.output/server/_ssr/console-access-Da-2Qd7q.mjs +17 -0
  88. package/.output/server/_ssr/console-features-Dzu2iUGD.mjs +55 -0
  89. package/.output/server/_ssr/console-features-api-xAsSgFa_.mjs +43 -0
  90. package/.output/server/_ssr/console-features-rpc-qARGpB24.mjs +19 -0
  91. package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-BdBf_ezN.mjs} +67 -56
  92. package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-B_y2lrwA.mjs} +2 -2
  93. package/.output/server/_ssr/{databaseJsonValue-d6I3nAS4.mjs → databaseJsonValue-Bhxciy7Z.mjs} +28 -5
  94. package/.output/server/_ssr/databaseRows-DwMAjaF8.mjs +74 -0
  95. package/.output/server/_ssr/{dialog-DS-nw-pA.mjs → dialog-q__BJ7mQ.mjs} +2 -2
  96. package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-Cxo9ghYv.mjs} +140 -6
  97. package/.output/server/_ssr/{downloadBundle-CNnYxYO2.mjs → downloadBundle-BW9I-pMQ.mjs} +10 -10
  98. package/.output/server/_ssr/empty-DcBTAooP.mjs +49 -0
  99. package/.output/server/_ssr/{empty-plugin-adapters-B5QvGpEl.mjs → empty-plugin-adapters-CYdinE-i.mjs} +1 -1
  100. package/.output/server/_ssr/{table-CnZmon_Z.mjs → field-QCDBVpXA.mjs} +63 -51
  101. package/.output/server/_ssr/getBundleChildren-DIAGCF4a.mjs +22 -0
  102. package/.output/server/_ssr/{input-group-BpsnmvZw.mjs → input-group-hCk2nUoU.mjs} +2 -2
  103. package/.output/server/_ssr/insights-BfBO0RFr.mjs +1238 -0
  104. package/.output/server/_ssr/insights-DsAU3-TB.mjs +14 -0
  105. package/.output/server/_ssr/insights-RIfg447W.mjs +5 -0
  106. package/.output/server/_ssr/insights-api-CDn-QQHU.mjs +105 -0
  107. package/.output/server/_ssr/insights-failures-BBGMhuUr.mjs +44 -0
  108. package/.output/server/_ssr/insights-failures-rpc-rA2dctK2.mjs +21 -0
  109. package/.output/server/_ssr/insights-recovery-Dpk6Mfb9.mjs +28 -0
  110. package/.output/server/_ssr/insights-recovery-rpc-BCaF-xSd.mjs +46 -0
  111. package/.output/server/_ssr/insights-recovery-rpc-CcgYyUHk.mjs +20 -0
  112. package/.output/server/_ssr/{insights-rpc-38DujK0G.mjs → insights-rpc-keycmwzw.mjs} +22 -14
  113. package/.output/server/_ssr/insights-search-BRg5VsNe.mjs +25 -0
  114. package/.output/server/_ssr/insights-usage-cbw9n2Fq.mjs +29 -0
  115. package/.output/server/_ssr/insights-usage-rpc-BKgV1QDf.mjs +29 -0
  116. package/.output/server/_ssr/insights-usage-rpc-D-JYachy.mjs +41 -0
  117. package/.output/server/_ssr/insightsRecovery-B7oMc8d8.mjs +45 -0
  118. package/.output/server/_ssr/insightsUsage-Oa7kk5ek.mjs +56 -0
  119. package/.output/server/_ssr/insights_.distribution-BIqOqRW0.mjs +335 -0
  120. package/.output/server/_ssr/insights_.distribution-CuUrDa7P.mjs +5 -0
  121. package/.output/server/_ssr/insights_.distribution-RT_6x_ud.mjs +14 -0
  122. package/.output/server/_ssr/{installations-DiVKEF5T.mjs → installations-BO5CjX2z.mjs} +517 -54
  123. package/.output/server/_ssr/installations-BjaHiIfh.mjs +5 -0
  124. package/.output/server/_ssr/installations-Dt5luA-N.mjs +69 -0
  125. package/.output/server/_ssr/listReleases-BitHzOYB.mjs +79 -0
  126. package/.output/server/_ssr/releaseManagement-B3e7I8J9.mjs +11 -0
  127. package/.output/server/_ssr/{router-B9wG_9f8.mjs → router-DfVAixxe.mjs} +135 -139
  128. package/.output/server/_ssr/{routes-D7Yh_jfj.mjs → routes-C6LstNp8.mjs} +176 -455
  129. package/.output/server/_ssr/routes-Q3Uhmwwz.mjs +292 -0
  130. package/.output/server/_ssr/runtime.server-fL0o6I2b.mjs +1459 -0
  131. package/.output/server/_ssr/schema-BKY9SqaY.mjs +25 -0
  132. package/.output/server/_ssr/select-CUlInmjk.mjs +107 -0
  133. package/.output/server/_ssr/server-C43wq9ft.mjs +8330 -0
  134. package/.output/server/_ssr/{createSsrRpc-DjWNJ0uc.mjs → sidebar-DKbx2ach.mjs} +117 -63
  135. package/.output/server/_ssr/ssr.mjs +1 -1
  136. package/.output/server/_ssr/start-BXUOv_n8.mjs +4 -0
  137. package/.output/server/_ssr/table-NGWV8B-S.mjs +53 -0
  138. package/.output/server/_ssr/updateFailures-Jwe2Baab.mjs +24 -0
  139. package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-C8bwiZr8.mjs} +2 -2
  140. package/.output/server/_tanstack-start-manifest_v-WQ5qGn8n.mjs +124 -0
  141. package/.output/server/index.mjs +267 -162
  142. package/LICENSE +13 -14
  143. package/README.md +82 -72
  144. package/lib/index.d.mts +23 -11
  145. package/lib/vite-internal.mjs +13 -3
  146. package/lib/vite.d.mts +2 -0
  147. package/package.json +14 -14
  148. package/src/components/AppSidebar.tsx +100 -71
  149. package/src/components/ConsoleAccessPage.tsx +5 -1
  150. package/src/components/ConsoleFeatureUnavailable.tsx +80 -0
  151. package/src/components/NotFoundPage.tsx +7 -7
  152. package/src/components/features/FeatureSlots.tsx +99 -0
  153. package/src/components/features/api-keys/apiKeysFeatureUi.tsx +15 -0
  154. package/src/components/features/bundles/BundleMetadata.tsx +8 -5
  155. package/src/components/features/bundles/RolloutCohortsDialog.tsx +1 -1
  156. package/src/components/features/channels/ChannelManagementDialog.tsx +2 -5
  157. package/src/components/features/insights/AppUsage.tsx +370 -0
  158. package/src/components/features/insights/EstimatedCount.tsx +18 -0
  159. package/src/components/features/insights/EventDetails.tsx +117 -37
  160. package/src/components/features/insights/EventHistoryCard.tsx +112 -26
  161. package/src/components/features/insights/EventHistoryList.tsx +2 -2
  162. package/src/components/features/insights/InsightsControls.tsx +265 -102
  163. package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
  164. package/src/components/features/insights/InsightsInfo.tsx +48 -0
  165. package/src/components/features/insights/InsightsOverview.tsx +176 -173
  166. package/src/components/features/insights/InsightsPageHeader.tsx +35 -20
  167. package/src/components/features/insights/InsightsPeriodSelector.tsx +89 -0
  168. package/src/components/features/insights/InstallationHistoryCard.tsx +52 -10
  169. package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
  170. package/src/components/features/insights/ReleaseActivity.tsx +188 -0
  171. package/src/components/features/insights/ReportingDevicesSummary.tsx +65 -0
  172. package/src/components/features/insights/UpdateFailures.tsx +319 -0
  173. package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
  174. package/src/components/features/releases/ReleaseEditorSheet.tsx +30 -7
  175. package/src/components/ui/card.tsx +1 -1
  176. package/src/components/ui/empty.tsx +101 -0
  177. package/src/components/ui/scroll-area.tsx +52 -0
  178. package/src/components/ui/sidebar.tsx +22 -29
  179. package/src/components/ui/tabs.tsx +81 -0
  180. package/src/index.ts +26 -19
  181. package/src/lib/api-keys-api.ts +0 -28
  182. package/src/lib/api-keys-rpc.ts +27 -38
  183. package/src/lib/api-rpc.ts +189 -266
  184. package/src/lib/api.ts +19 -102
  185. package/src/lib/bundle-activity.ts +31 -0
  186. package/src/lib/console-access.ts +20 -0
  187. package/src/lib/console-features-api.ts +68 -0
  188. package/src/lib/console-features-rpc.ts +13 -0
  189. package/src/lib/console-features.ts +91 -0
  190. package/src/lib/insights-api.ts +37 -1
  191. package/src/lib/insights-failures-rpc.ts +21 -0
  192. package/src/lib/insights-failures.ts +129 -0
  193. package/src/lib/insights-recovery-rpc.ts +51 -0
  194. package/src/lib/insights-recovery.ts +68 -0
  195. package/src/lib/insights-retention.ts +21 -0
  196. package/src/lib/insights-rpc.ts +30 -12
  197. package/src/lib/insights-search.ts +58 -0
  198. package/src/lib/insights-usage-rpc.ts +29 -0
  199. package/src/lib/insights-usage.ts +74 -0
  200. package/src/lib/insights-view.ts +38 -2
  201. package/src/lib/scroll-restoration.ts +21 -0
  202. package/src/lib/server/adminInsights.ts +124 -0
  203. package/src/lib/server/auth.server.ts +13 -5
  204. package/src/lib/server/bundleActivity.ts +37 -0
  205. package/src/lib/server/config.server.ts +21 -74
  206. package/src/lib/server/console-feature-apis.ts +59 -0
  207. package/src/lib/server/console-runtime.server.ts +82 -38
  208. package/src/lib/server/downloadBundle.ts +16 -15
  209. package/src/lib/server/getBundleChildren.ts +31 -122
  210. package/src/lib/server/insightsRecovery.ts +55 -0
  211. package/src/lib/server/insightsUsage.ts +63 -0
  212. package/src/lib/server/listReleases.ts +109 -44
  213. package/src/lib/server/releaseReachability.ts +8 -7
  214. package/src/lib/server/runtime.server.ts +120 -31
  215. package/src/lib/server/updateFailures.ts +31 -0
  216. package/src/routeTree.gen.ts +44 -44
  217. package/src/router.tsx +3 -2
  218. package/src/routes/-installations-search.ts +32 -8
  219. package/src/routes/-releases-search.ts +76 -29
  220. package/src/routes/__root.tsx +10 -4
  221. package/src/routes/api/bundles/$bundleId/download.ts +2 -5
  222. package/src/routes/api-keys.tsx +5 -2
  223. package/src/routes/index.tsx +167 -104
  224. package/src/routes/insights.tsx +100 -59
  225. package/src/routes/insights_.distribution.tsx +398 -0
  226. package/src/routes/installations.tsx +98 -41
  227. package/src/vite-internal.ts +15 -3
  228. package/src/vite.ts +2 -0
  229. package/.output/public/assets/HashValueDisplay-CB0Eskit.js +0 -1
  230. package/.output/public/assets/InsightsPageHeader-Car5LNiZ.js +0 -1
  231. package/.output/public/assets/api-keys-BzKocqRy.js +0 -1
  232. package/.output/public/assets/api-keys-api-BHcODB7q.js +0 -1
  233. package/.output/public/assets/card-Dhr_eFYj.js +0 -1
  234. package/.output/public/assets/chart-no-axes-combined-Dy4WJv2U.js +0 -1
  235. package/.output/public/assets/composite-Bp98A0Ld.js +0 -1
  236. package/.output/public/assets/createServerFn-CA8fs7QD.js +0 -3
  237. package/.output/public/assets/dialog-CUTrcPlB.js +0 -1
  238. package/.output/public/assets/dist-C_NydL0e.js +0 -1
  239. package/.output/public/assets/index-DdAz-CXv.js +0 -10
  240. package/.output/public/assets/input-group-cSOG4wh_.js +0 -1
  241. package/.output/public/assets/insights-C0PBSZ-n.js +0 -1
  242. package/.output/public/assets/installations-CE33wgbY.js +0 -2
  243. package/.output/public/assets/installations-CStZja8x.js +0 -1
  244. package/.output/public/assets/lazyRouteComponent-Ccp3bwBY.js +0 -1
  245. package/.output/public/assets/link-CI8idAUi.js +0 -1
  246. package/.output/public/assets/loader-circle-BoxLrsOu.js +0 -1
  247. package/.output/public/assets/preload-helper-CQ36nxok.js +0 -1
  248. package/.output/public/assets/refresh-cw-Bqc72UZG.js +0 -1
  249. package/.output/public/assets/routes-DGVXdzpw.js +0 -2
  250. package/.output/public/assets/routes-DTlwOr8I.js +0 -1
  251. package/.output/public/assets/shield-check-uK3MHWJZ.js +0 -1
  252. package/.output/public/assets/signing-CMf4aqDO.js +0 -1
  253. package/.output/public/assets/styles-BCvbtKsm.css +0 -2
  254. package/.output/public/assets/table-2vcV89QB.js +0 -1
  255. package/.output/public/assets/useCompositeListItem-CHLITE_4.js +0 -1
  256. package/.output/public/assets/x-DCd0j8c6.js +0 -1
  257. package/.output/server/_ssr/InsightsPageHeader-Df4Brj8u.mjs +0 -484
  258. package/.output/server/_ssr/api-keys-api-BUPQVSpk.mjs +0 -60
  259. package/.output/server/_ssr/api-keys-rpc-Cs7tS5_j.mjs +0 -136
  260. package/.output/server/_ssr/api-rpc-tAupfGQm.mjs +0 -1399
  261. package/.output/server/_ssr/auth.server-DW71WzsD.mjs +0 -47
  262. package/.output/server/_ssr/bundle-signing-rpc-BhnBw0-d.mjs +0 -16
  263. package/.output/server/_ssr/bundle-signing.server-C4NW5mWx.mjs +0 -12
  264. package/.output/server/_ssr/card-BRiPmi0C.mjs +0 -108
  265. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
  266. package/.output/server/_ssr/config.server-afDuAwaG.mjs +0 -368
  267. package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
  268. package/.output/server/_ssr/deleteBundle-BTGwBgzx.mjs +0 -66
  269. package/.output/server/_ssr/getBundleChildren-C0vh4ZvE.mjs +0 -50
  270. package/.output/server/_ssr/insights-BdS364Xu.mjs +0 -460
  271. package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +0 -303
  272. package/.output/server/_ssr/installations-CtA59MQG.mjs +0 -29
  273. package/.output/server/_ssr/routes-CKrrS2q2.mjs +0 -37
  274. package/.output/server/_ssr/runtime.server-DacU1TBV.mjs +0 -438
  275. package/.output/server/_ssr/server-D_C2rMzf.mjs +0 -7468
  276. package/.output/server/_ssr/signing-Dz1WkZOf.mjs +0 -133
  277. package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
  278. package/.output/server/_tanstack-start-manifest_v-2MjTyxil.mjs +0 -97
  279. package/src/components/features/insights/InsightsBundleReports.tsx +0 -51
  280. package/src/components/features/signing/BundleSigningPage.tsx +0 -139
  281. package/src/lib/bundle-signing-api.ts +0 -14
  282. package/src/lib/bundle-signing-rpc.ts +0 -16
  283. package/src/lib/server/bundle-signing.server.ts +0 -23
  284. package/src/lib/server/deleteBundle.ts +0 -145
  285. package/src/routes/signing.tsx +0 -37
@@ -1,203 +1,206 @@
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";
13
18
  import { Skeleton } from "@/components/ui/skeleton";
14
- import type { ReportingInstallations } from "@/lib/insights-api";
15
- import { outcomeLabels } from "@/lib/insights-view";
19
+ import type { RecoveryInput, RecoveryReport } from "@/lib/insights-recovery";
16
20
 
17
- import { EventTimestamp, useInsightsTimeFormat } from "./EventDetails";
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
- 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
- }
26
+ const dates = new Intl.DateTimeFormat("en", {
27
+ month: "short",
28
+ day: "numeric",
29
+ timeZone: "UTC",
30
+ });
55
31
 
56
- if (props.status === "error") {
57
- return (
58
- <InsightsErrorAlert
59
- error={props.error}
60
- fallbackTitle="Reporting installations unavailable"
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
- const { active } = props;
66
- const bundle = active.bundle;
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="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>
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-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>
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 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>
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
- View events
110
- <ArrowUpRight aria-hidden="true" data-icon="inline-end" />
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-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
+ }