@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
@@ -2,27 +2,48 @@ import {
2
2
  Activity,
3
3
  Check,
4
4
  ChevronDown,
5
- PackageCheck,
5
+ CircleAlert,
6
+ Download,
6
7
  RotateCcw,
7
8
  } from "lucide-react";
8
9
  import { useEffect, useState } from "react";
9
10
 
10
11
  import { HashValueDisplay } from "@/components/HashValueDisplay";
11
12
  import { Badge } from "@/components/ui/badge";
13
+ import { describeFailure } from "@/lib/insights-failures";
12
14
  import type { InsightsEventRow } from "@/lib/insights-view";
13
15
 
14
16
  type EventHistoryRow = InsightsEventRow;
15
17
 
16
18
  const eventTypes = {
17
- UPDATE_APPLIED: { label: "Bundle applied", variant: "success", icon: Check },
18
- RECOVERED: { label: "Recovered", variant: "warning", icon: RotateCcw },
19
- RELEASE_ADOPTED: {
20
- label: "Bundle adopted",
19
+ UPDATE_DOWNLOADED: {
20
+ label: "Downloaded",
21
+ description:
22
+ "Download complete. Waiting for the app to restart and apply it.",
23
+ variant: "default",
24
+ icon: Download,
25
+ },
26
+ UPDATE_APPLIED: {
27
+ label: "Update applied",
28
+ description: "The app started using the downloaded update.",
21
29
  variant: "success",
22
- icon: PackageCheck,
30
+ icon: Check,
31
+ },
32
+ RECOVERED: {
33
+ label: "Recovered",
34
+ description: "Recovered from a crashed bundle.",
35
+ variant: "warning",
36
+ icon: RotateCcw,
37
+ },
38
+ UPDATE_FAILED: {
39
+ label: "Update failed",
40
+ description: "An update check, download, or install failed.",
41
+ variant: "destructive",
42
+ icon: CircleAlert,
23
43
  },
24
44
  UNCHANGED: {
25
- label: "Activity reported",
45
+ label: "No change",
46
+ description: "No download or apply was reported at this point.",
26
47
  variant: "secondary",
27
48
  icon: Activity,
28
49
  },
@@ -92,28 +113,63 @@ export function EventTimestamp({
92
113
  );
93
114
  }
94
115
 
95
- export function EventTypeBadge({
96
- type,
116
+ const deliveries: Readonly<Record<string, string>> = {
117
+ patch: "A patch delivered it.",
118
+ manifest: "Only the changed files were downloaded.",
119
+ archive: "The full archive was downloaded.",
120
+ };
121
+
122
+ /** What the event's report added: where an update failed, how a bundle arrived, why a process exited. */
123
+ const eventNote = (
124
+ event: Pick<
125
+ EventHistoryRow,
126
+ "type" | "failure" | "delivery" | "patchFallback" | "previousProcessExit"
127
+ >,
128
+ ): string | null => {
129
+ if (event.type === "UPDATE_FAILED" && event.failure) {
130
+ return describeFailure(event.failure);
131
+ }
132
+ if (event.type === "UPDATE_DOWNLOADED" && event.delivery) {
133
+ const delivered = deliveries[event.delivery] ?? null;
134
+ return event.patchFallback
135
+ ? `The patch failed. ${delivered ?? ""}`.trim()
136
+ : delivered;
137
+ }
138
+ if (event.type === "RECOVERED" && event.previousProcessExit) {
139
+ return `The crashed process exited with ${event.previousProcessExit}.`;
140
+ }
141
+ return null;
142
+ };
143
+
144
+ export function EventTypeDetails({
145
+ event,
97
146
  }: {
98
- readonly type: EventHistoryRow["type"];
147
+ readonly event: Pick<
148
+ EventHistoryRow,
149
+ "type" | "failure" | "delivery" | "patchFallback" | "previousProcessExit"
150
+ >;
99
151
  }) {
100
- const eventType = eventTypes[type];
152
+ const eventType = eventTypes[event.type];
101
153
  const Icon = eventType.icon;
154
+ const note = eventNote(event);
102
155
  return (
103
- <Badge
104
- variant={eventType.variant}
105
- className="gap-1 whitespace-nowrap [&_svg]:size-3"
106
- title={
107
- type === "UNCHANGED"
108
- ? "App activity reported on the current bundle without a bundle transition."
109
- : type === "RELEASE_ADOPTED"
110
- ? "A different release was adopted without changing the bundle."
111
- : undefined
112
- }
113
- >
114
- <Icon aria-hidden="true" />
115
- {eventType.label}
116
- </Badge>
156
+ <div className="flex min-w-0 flex-col items-start gap-2">
157
+ <Badge
158
+ variant={eventType.variant}
159
+ className="gap-1 whitespace-nowrap [&_svg]:size-3"
160
+ >
161
+ <Icon aria-hidden="true" />
162
+ {eventType.label}
163
+ </Badge>
164
+ <p className="max-w-56 whitespace-normal text-xs text-muted-foreground">
165
+ {event.type === "UPDATE_FAILED" && note ? note : eventType.description}
166
+ </p>
167
+ {event.type !== "UPDATE_FAILED" && note ? (
168
+ <p className="max-w-56 whitespace-normal text-xs text-muted-foreground">
169
+ {note}
170
+ </p>
171
+ ) : null}
172
+ </div>
117
173
  );
118
174
  }
119
175
 
@@ -121,14 +177,28 @@ export function EventBundleTransition({
121
177
  event,
122
178
  touch = false,
123
179
  }: {
124
- readonly event: Pick<EventHistoryRow, "type" | "fromBundleId" | "toBundleId">;
180
+ readonly event: Pick<
181
+ EventHistoryRow,
182
+ "type" | "fromBundleId" | "toBundleId" | "failure"
183
+ >;
125
184
  readonly touch?: boolean;
126
185
  }) {
186
+ const downloaded = event.type === "UPDATE_DOWNLOADED";
187
+ const failed = event.type === "UPDATE_FAILED";
188
+ // A failed check targets no bundle: it names the one running.
189
+ const targeted = !failed || event.failure?.stage !== "check";
190
+ const changed =
191
+ downloaded ||
192
+ failed ||
193
+ event.type === "UPDATE_APPLIED" ||
194
+ event.type === "RECOVERED";
127
195
  return (
128
196
  <dl className="grid grid-cols-[auto_minmax(0,1fr)] items-center gap-x-2 gap-y-2">
129
- {event.fromBundleId ? (
197
+ {event.fromBundleId && changed ? (
130
198
  <>
131
- <dt className="text-muted-foreground">From</dt>
199
+ <dt className="text-muted-foreground">
200
+ {downloaded || failed ? "Running" : "From"}
201
+ </dt>
132
202
  <dd>
133
203
  <HashValueDisplay
134
204
  value={event.fromBundleId}
@@ -137,15 +207,25 @@ export function EventBundleTransition({
137
207
  </dd>
138
208
  </>
139
209
  ) : null}
140
- <dt className="text-muted-foreground">
141
- {event.type === "UNCHANGED" ? "Current" : "To"}
142
- </dt>
143
- <dd>
144
- <HashValueDisplay
145
- value={event.toBundleId}
146
- buttonClassName={touch ? "min-h-11 px-3" : undefined}
147
- />
148
- </dd>
210
+ {targeted ? (
211
+ <>
212
+ <dt className="text-muted-foreground">
213
+ {downloaded
214
+ ? "Pending"
215
+ : failed
216
+ ? "Target"
217
+ : changed
218
+ ? "To"
219
+ : "Current"}
220
+ </dt>
221
+ <dd>
222
+ <HashValueDisplay
223
+ value={event.toBundleId}
224
+ buttonClassName={touch ? "min-h-11 px-3" : undefined}
225
+ />
226
+ </dd>
227
+ </>
228
+ ) : null}
149
229
  </dl>
150
230
  );
151
231
  }
@@ -4,7 +4,13 @@ import type { ReactNode } from "react";
4
4
 
5
5
  import { HashValueDisplay } from "@/components/HashValueDisplay";
6
6
  import { Button, buttonVariants } from "@/components/ui/button";
7
- import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
7
+ import {
8
+ Card,
9
+ CardContent,
10
+ CardDescription,
11
+ CardHeader,
12
+ CardTitle,
13
+ } from "@/components/ui/card";
8
14
  import { Skeleton } from "@/components/ui/skeleton";
9
15
  import {
10
16
  Table,
@@ -14,23 +20,39 @@ import {
14
20
  TableHeader,
15
21
  TableRow,
16
22
  } from "@/components/ui/table";
17
- import type { InsightsEventRow, InsightsViewPage } from "@/lib/insights-view";
23
+ import {
24
+ EVENT_RANGES,
25
+ type EventRange,
26
+ eventRanges,
27
+ type InsightsEventRow,
28
+ type InsightsViewPage,
29
+ widerEventRange,
30
+ } from "@/lib/insights-view";
18
31
 
19
32
  import {
20
33
  EventBundleTransition,
21
34
  EventTimestamp,
22
- EventTypeBadge,
35
+ EventTypeDetails,
23
36
  useInsightsTimeFormat,
24
37
  } from "./EventDetails";
25
38
  import { EventHistoryList } from "./EventHistoryList";
26
39
  import { InsightsErrorAlert } from "./InsightsErrorAlert";
27
40
  import { InsightsPagination } from "./InsightsPagination";
41
+ import { PeriodSelector } from "./InsightsPeriodSelector";
28
42
 
29
43
  type EventsLocationState = {
30
44
  readonly eventsBefore: number;
31
45
  readonly eventsCursor?: string;
46
+ readonly eventsRange?: EventRange;
32
47
  };
33
48
 
49
+ /** The ranges within the days raw events are kept, the shortest always. */
50
+ const rangesWithin = (days: number) =>
51
+ EVENT_RANGES.filter(
52
+ (value, position) =>
53
+ position === 0 || eventRanges[value].durationMs <= days * 86_400_000,
54
+ );
55
+
34
56
  function EventIdentity({
35
57
  event,
36
58
  eventsLocation,
@@ -62,12 +84,17 @@ function EventIdentity({
62
84
  query: event.installId || undefined,
63
85
  installId: event.installId,
64
86
  }}
65
- aria-label={`View history for ${event.userId ?? event.username ?? "anonymous installation"} (${event.installId})`}
87
+ state={(previous) => ({
88
+ insightsPagination: {
89
+ eventsBack: previous.insightsPagination?.eventsBack,
90
+ },
91
+ })}
92
+ aria-label={`View history for ${event.userId ?? "anonymous installation"} (${event.installId})`}
66
93
  >
67
94
  <span
68
95
  className={touch ? "min-w-0 text-left wrap-anywhere" : "truncate"}
69
96
  >
70
- {event.userId ?? event.username ?? "Anonymous installation"}
97
+ {event.userId ?? "Anonymous installation"}
71
98
  </span>
72
99
  <ArrowUpRight aria-hidden="true" data-icon="inline-end" />
73
100
  </Link>
@@ -82,6 +109,7 @@ function EventIdentity({
82
109
 
83
110
  export function EventHistoryCard({
84
111
  children,
112
+ description,
85
113
  error,
86
114
  eventsLocation,
87
115
  history,
@@ -89,11 +117,16 @@ export function EventHistoryCard({
89
117
  isLoading,
90
118
  onNext,
91
119
  onPrevious,
120
+ onRangeChange,
92
121
  onRefresh,
93
122
  pageNumber,
123
+ range,
124
+ rawDays = 90,
94
125
  title = "All events",
95
126
  }: {
96
127
  readonly children?: ReactNode;
128
+ /** One line under the title that says what the list holds. */
129
+ readonly description?: ReactNode;
97
130
  readonly error: Error | null;
98
131
  readonly eventsLocation: EventsLocationState;
99
132
  readonly history: InsightsViewPage<InsightsEventRow> | undefined;
@@ -101,30 +134,68 @@ export function EventHistoryCard({
101
134
  readonly isLoading: boolean;
102
135
  readonly onNext: () => void;
103
136
  readonly onPrevious: () => void;
137
+ /** Lets the list change its range; a list with a fixed window leaves it out. */
138
+ readonly onRangeChange?: (range: EventRange) => void;
104
139
  readonly onRefresh: () => void;
105
140
  readonly pageNumber: number;
141
+ /** The time range the list reads, named by its empty and end states. */
142
+ readonly range?: EventRange;
143
+ /** How long the server keeps raw events: the longest range offered. */
144
+ readonly rawDays?: number;
106
145
  readonly title?: string;
107
146
  }) {
108
147
  const dateTimeFormat = useInsightsTimeFormat();
109
148
  const hasPrevious = pageNumber > 1;
149
+ const ranges = rangesWithin(rawDays);
150
+ const next = range === undefined ? undefined : widerEventRange(range);
151
+ const wider = next !== undefined && ranges.includes(next) ? next : undefined;
152
+ const widen =
153
+ wider !== undefined && onRangeChange ? (
154
+ <Button
155
+ className="h-11 px-3 lg:h-8"
156
+ onClick={() => onRangeChange(wider)}
157
+ type="button"
158
+ variant="outline"
159
+ >
160
+ {`Show the last ${eventRanges[wider].period}`}
161
+ </Button>
162
+ ) : null;
110
163
 
111
164
  return (
112
165
  <Card className="@container min-w-0 shadow-sm" aria-busy={isFetching}>
113
166
  <CardHeader className="gap-4 p-4 sm:p-6">
114
- <div className="flex flex-wrap items-center justify-between gap-3">
115
- <CardTitle>
116
- <h2>{title}</h2>
117
- </CardTitle>
118
- <Button
119
- className="h-11 lg:h-8"
120
- disabled={isFetching}
121
- onClick={onRefresh}
122
- size="lg"
123
- variant="outline"
124
- >
125
- <RefreshCw aria-hidden="true" data-icon="inline-start" />
126
- Refresh
127
- </Button>
167
+ <div className="flex flex-col gap-1.5">
168
+ <div className="flex flex-wrap items-center justify-between gap-3">
169
+ <CardTitle>
170
+ <h2>{title}</h2>
171
+ </CardTitle>
172
+ <div className="flex flex-wrap items-center gap-2">
173
+ {range !== undefined && onRangeChange ? (
174
+ <PeriodSelector
175
+ label="Time range"
176
+ onPeriodChange={onRangeChange}
177
+ period={range}
178
+ periods={ranges.map((value) => ({
179
+ value,
180
+ name: `Last ${eventRanges[value].period}`,
181
+ }))}
182
+ />
183
+ ) : null}
184
+ <Button
185
+ className="h-11 lg:h-8"
186
+ disabled={isFetching}
187
+ onClick={onRefresh}
188
+ size="lg"
189
+ variant="outline"
190
+ >
191
+ <RefreshCw aria-hidden="true" data-icon="inline-start" />
192
+ Refresh
193
+ </Button>
194
+ </div>
195
+ </div>
196
+ {description ? (
197
+ <CardDescription>{description}</CardDescription>
198
+ ) : null}
128
199
  </div>
129
200
  {children}
130
201
  </CardHeader>
@@ -189,7 +260,7 @@ export function EventHistoryCard({
189
260
  />
190
261
  </TableCell>
191
262
  <TableCell>
192
- <EventTypeBadge type={event.type} />
263
+ <EventTypeDetails event={event} />
193
264
  </TableCell>
194
265
  <TableCell>
195
266
  <EventIdentity
@@ -218,14 +289,29 @@ export function EventHistoryCard({
218
289
  </div>
219
290
  </>
220
291
  ) : (
221
- <div className="px-6 pb-6 text-sm text-muted-foreground">
222
- {hasPrevious || history.nextCursor
223
- ? "No events on this page"
224
- : title === "All events"
225
- ? "No events recorded yet"
226
- : "No matching reports in this period"}
292
+ <div className="flex flex-wrap items-center justify-between gap-3 px-4 pb-4 sm:px-6 sm:pb-6">
293
+ <p className="text-sm text-muted-foreground">
294
+ {hasPrevious
295
+ ? "No older events in this range."
296
+ : range !== undefined
297
+ ? `No events in the last ${eventRanges[range].period}.`
298
+ : title === "All events"
299
+ ? "No events recorded yet"
300
+ : "No matching reports in this period"}
301
+ </p>
302
+ {hasPrevious ? null : widen}
227
303
  </div>
228
304
  )}
305
+ {range !== undefined &&
306
+ history.nextCursor === null &&
307
+ (history.data.length > 0 || hasPrevious) ? (
308
+ <div className="flex flex-wrap items-center justify-between gap-3 border-t px-4 py-3 sm:px-6">
309
+ <p className="text-sm text-muted-foreground">
310
+ Start of range reached
311
+ </p>
312
+ {widen}
313
+ </div>
314
+ ) : null}
229
315
  {history.data.length > 0 || hasPrevious || history.nextCursor ? (
230
316
  <InsightsPagination
231
317
  hasPrevious={hasPrevious}
@@ -5,7 +5,7 @@ import type { InsightsEventRow } from "@/lib/insights-view";
5
5
  import {
6
6
  EventBundleTransition,
7
7
  EventTimestamp,
8
- EventTypeBadge,
8
+ EventTypeDetails,
9
9
  } from "./EventDetails";
10
10
 
11
11
  export function EventHistoryList<T extends InsightsEventRow>({
@@ -29,7 +29,7 @@ export function EventHistoryList<T extends InsightsEventRow>({
29
29
  className="flex min-w-0 flex-col gap-3 px-4 py-4 sm:px-6"
30
30
  >
31
31
  <div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-1">
32
- <EventTypeBadge type={event.type} />
32
+ <EventTypeDetails event={event} />
33
33
  <div className="text-xs tabular-nums">
34
34
  <EventTimestamp
35
35
  value={event.receivedAtMs}