@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,82 +1,123 @@
1
+ import { useQuery } from "@tanstack/react-query";
1
2
  import { createFileRoute } from "@tanstack/react-router";
2
- import { useState } from "react";
3
3
 
4
- import {
5
- InsightsBundleReports,
6
- type InsightsReportSelection,
7
- } from "@/components/features/insights/InsightsBundleReports";
4
+ import { ConsoleFeatureUnavailable } from "@/components/ConsoleFeatureUnavailable";
5
+ import { AppUsage } from "@/components/features/insights/AppUsage";
8
6
  import { InsightsControls } from "@/components/features/insights/InsightsControls";
9
7
  import { InsightsOverview } from "@/components/features/insights/InsightsOverview";
10
8
  import { InsightsPageHeader } from "@/components/features/insights/InsightsPageHeader";
11
- import { Button } from "@/components/ui/button";
9
+ import { UpdateFailures } from "@/components/features/insights/UpdateFailures";
10
+ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
11
+ import { requireConsoleFeature } from "@/lib/console-features-api";
12
12
  import {
13
- type InsightsWindow,
14
- type InsightsOverviewInput,
15
- useReportingInstallationsQuery,
13
+ useInsightsRetention,
14
+ useUpdateFailuresQuery,
16
15
  } from "@/lib/insights-api";
16
+ import { getRecoveryReportRpc } from "@/lib/insights-recovery-rpc";
17
+ import { validateInsightsSearch } from "@/lib/insights-search";
18
+ import type { AppUsageScope, UsageWindow } from "@/lib/insights-usage";
19
+ import { getAppUsageReportRpc } from "@/lib/insights-usage-rpc";
17
20
 
18
21
  export const Route = createFileRoute("/insights")({
22
+ beforeLoad: ({ context }) =>
23
+ requireConsoleFeature(context.queryClient, "insightsAnalytics"),
24
+ notFoundComponent: ConsoleFeatureUnavailable,
19
25
  component: InsightsPage,
26
+ validateSearch: validateInsightsSearch,
20
27
  });
21
28
 
22
29
  function InsightsPage() {
23
- const [window, setWindow] = useState<InsightsWindow>("30d");
24
- const [scope, setScope] = useState<Omit<InsightsOverviewInput, "window">>({
25
- platform: "ios",
26
- channel: "production",
30
+ const search = Route.useSearch();
31
+ const navigate = Route.useNavigate();
32
+ const retention = useInsightsRetention();
33
+ const usageWindow = search.window ?? "24h";
34
+ const bundleWindow = search.bundleWindow ?? "7d";
35
+ const channel = search.channel ?? search.healthChannel ?? "production";
36
+ const scope: AppUsageScope = {
37
+ platform: search.platform ?? "all",
38
+ channel,
39
+ appVersion: search.appVersion,
40
+ };
41
+ const setUsageWindow = (window: UsageWindow) =>
42
+ void navigate({ search: { ...search, window } });
43
+ const input = { ...scope, window: usageWindow };
44
+ const bundleInput = {
45
+ platform: search.healthPlatform ?? "ios",
46
+ channel,
47
+ window: bundleWindow,
48
+ ...(search.releaseId === undefined ? {} : { releaseId: search.releaseId }),
49
+ } as const;
50
+ const query = useQuery({
51
+ queryKey: ["insights", "app-usage", input],
52
+ queryFn: () => getAppUsageReportRpc({ data: input }),
53
+ staleTime: 30_000,
27
54
  });
28
- const [selection, setSelection] = useState<InsightsReportSelection | null>(
29
- null,
30
- );
31
- const active = useReportingInstallationsQuery({ ...scope, window });
32
-
55
+ const bundleQuery = useQuery({
56
+ queryKey: ["insights", "recovery", bundleInput],
57
+ queryFn: () => getRecoveryReportRpc({ data: bundleInput }),
58
+ staleTime: 30_000,
59
+ });
60
+ const failuresQuery = useUpdateFailuresQuery(bundleInput);
33
61
  return (
34
62
  <div className="flex h-svh min-h-0 flex-col">
35
- <InsightsPageHeader view="overview" />
36
- <div className="min-h-0 min-w-0 flex-1 overflow-y-auto bg-muted/5 p-3 sm:p-6">
37
- <div className="mx-auto flex w-full max-w-4xl flex-col gap-4 sm:gap-6">
63
+ <InsightsPageHeader view="overview" overviewSearch={search} />
64
+ <div className="min-h-0 min-w-0 flex-1 overflow-y-auto bg-muted/5 px-4 py-6 sm:p-8">
65
+ <div className="mx-auto flex w-full max-w-6xl flex-col gap-6">
38
66
  <InsightsControls
67
+ key={JSON.stringify({ scope, bundleInput })}
39
68
  scope={scope}
40
- onScopeChange={(next) => {
41
- setScope(next);
42
- setSelection(null);
43
- }}
44
- onWindowChange={(next) => {
45
- setWindow(next);
46
- setSelection(null);
47
- }}
48
- window={window}
69
+ releaseScope={bundleInput}
70
+ appVersions={query.data?.appVersions ?? []}
71
+ onFiltersChange={({ scope, releaseScope }) =>
72
+ void navigate({
73
+ search: {
74
+ ...search,
75
+ ...scope,
76
+ appVersion: scope.appVersion,
77
+ healthPlatform: releaseScope.platform,
78
+ healthChannel: undefined,
79
+ releaseId: releaseScope.releaseId,
80
+ },
81
+ })
82
+ }
49
83
  />
50
- <Button
51
- className="h-11 self-end lg:h-8"
52
- variant="outline"
53
- disabled={active.isFetching}
54
- onClick={() => {
55
- setSelection(null);
56
- void active.refetch();
57
- }}
58
- >
59
- Refresh overview
60
- </Button>
61
- {active.isLoading ? (
62
- <InsightsOverview status="loading" />
63
- ) : active.error ? (
64
- <InsightsOverview status="error" error={active.error} />
65
- ) : active.data ? (
66
- <InsightsOverview
67
- status="success"
68
- active={active.data}
69
- onOutcomeSelect={setSelection}
70
- />
71
- ) : (
72
- <InsightsOverview status="loading" />
73
- )}
74
- {selection ? (
75
- <InsightsBundleReports
76
- key={JSON.stringify(selection)}
77
- selection={selection}
78
- />
84
+ {query.data?.truncated && !query.error ? (
85
+ <Alert>
86
+ <AlertTitle>Partial history</AlertTitle>
87
+ <AlertDescription>
88
+ Only reports since{" "}
89
+ {new Date(query.data.sinceMs).toLocaleString("en", {
90
+ timeZone: "UTC",
91
+ })}{" "}
92
+ UTC are available. The active-user count is a lower bound.
93
+ Choose a shorter period for a more complete view.
94
+ </AlertDescription>
95
+ </Alert>
79
96
  ) : null}
97
+ <AppUsage
98
+ query={query}
99
+ search={{ ...scope, window: usageWindow, bundleWindow }}
100
+ window={usageWindow}
101
+ onWindowChange={setUsageWindow}
102
+ retention={retention}
103
+ />
104
+ <InsightsOverview
105
+ input={bundleInput}
106
+ query={bundleQuery}
107
+ onWindowChange={(window) =>
108
+ void navigate({
109
+ search: {
110
+ ...search,
111
+ bundleWindow: window,
112
+ },
113
+ })
114
+ }
115
+ onRefresh={() => void bundleQuery.refetch()}
116
+ />
117
+ <UpdateFailures
118
+ query={failuresQuery}
119
+ onRefresh={() => void failuresQuery.refetch()}
120
+ />
80
121
  </div>
81
122
  </div>
82
123
  </div>
@@ -0,0 +1,398 @@
1
+ import { useQuery } from "@tanstack/react-query";
2
+ import { createFileRoute, Link } from "@tanstack/react-router";
3
+ import { ArrowLeft, ArrowUpRight, RotateCw } from "lucide-react";
4
+
5
+ import { ConsoleFeatureUnavailable } from "@/components/ConsoleFeatureUnavailable";
6
+ import { InsightsErrorAlert } from "@/components/features/insights/InsightsErrorAlert";
7
+ import { InsightsInfo } from "@/components/features/insights/InsightsInfo";
8
+ import { InsightsPageHeader } from "@/components/features/insights/InsightsPageHeader";
9
+ import { UsagePeriodSelector } from "@/components/features/insights/InsightsPeriodSelector";
10
+ import { PlatformIcon } from "@/components/PlatformIcon";
11
+ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
12
+ import { Badge } from "@/components/ui/badge";
13
+ import { Button, buttonVariants } from "@/components/ui/button";
14
+ import {
15
+ Card,
16
+ CardContent,
17
+ CardFooter,
18
+ CardHeader,
19
+ CardTitle,
20
+ } from "@/components/ui/card";
21
+ import {
22
+ Empty,
23
+ EmptyDescription,
24
+ EmptyHeader,
25
+ EmptyTitle,
26
+ } from "@/components/ui/empty";
27
+ import { Field, FieldLabel } from "@/components/ui/field";
28
+ import { Progress } from "@/components/ui/progress";
29
+ import {
30
+ Select,
31
+ SelectContent,
32
+ SelectGroup,
33
+ SelectItem,
34
+ SelectTrigger,
35
+ SelectValue,
36
+ } from "@/components/ui/select";
37
+ import { Skeleton } from "@/components/ui/skeleton";
38
+ import { requireConsoleFeature } from "@/lib/console-features-api";
39
+ import { useInsightsRetention } from "@/lib/insights-api";
40
+ import { keepsTwelveMonths } from "@/lib/insights-retention";
41
+ import {
42
+ validateDistributionSearch,
43
+ validateInsightsSearch,
44
+ } from "@/lib/insights-search";
45
+ import { getAppUsageReportRpc } from "@/lib/insights-usage-rpc";
46
+
47
+ const PAGE_SIZE = 20;
48
+ const dates = new Intl.DateTimeFormat("en", {
49
+ month: "short",
50
+ day: "numeric",
51
+ timeZone: "UTC",
52
+ });
53
+ export const Route = createFileRoute("/insights_/distribution")({
54
+ beforeLoad: ({ context }) =>
55
+ requireConsoleFeature(context.queryClient, "insightsAnalytics"),
56
+ notFoundComponent: ConsoleFeatureUnavailable,
57
+ component: DistributionPage,
58
+ validateSearch: validateDistributionSearch,
59
+ });
60
+
61
+ function DistributionPage() {
62
+ const search = Route.useSearch();
63
+ const retention = useInsightsRetention();
64
+ const navigate = Route.useNavigate();
65
+ const input = {
66
+ platform: search.platform ?? "all",
67
+ channel: search.channel ?? "production",
68
+ appVersion: search.appVersion,
69
+ window: search.window ?? "24h",
70
+ };
71
+ const query = useQuery({
72
+ queryKey: ["insights", "app-usage", input],
73
+ queryFn: () => getAppUsageReportRpc({ data: input }),
74
+ staleTime: 30_000,
75
+ });
76
+ const report = query.error ? undefined : query.data;
77
+ const distributionPeriod = report
78
+ ? `since ${dates.format(report.distributionSinceMs)}, 00:00 UTC`
79
+ : "in this period";
80
+ const rows = (report?.bundleDistribution ?? []).filter(
81
+ (row) => !search.version || row.appVersion === search.version,
82
+ );
83
+ const total = rows.reduce((sum, row) => sum + row.installations, 0);
84
+ const pageCount = Math.max(1, Math.ceil(rows.length / PAGE_SIZE));
85
+ const page = Math.min(search.page ?? 1, pageCount);
86
+ const versions = [
87
+ ...new Set([
88
+ ...(report?.versions.map((row) => row.name) ?? []),
89
+ ...(search.version ? [search.version] : []),
90
+ ]),
91
+ ];
92
+ const overviewSearch = validateInsightsSearch(search);
93
+ const changeSearch = (next: Partial<typeof search>) =>
94
+ void navigate({ search: { ...search, page: undefined, ...next } });
95
+
96
+ return (
97
+ <div className="flex h-svh min-h-0 flex-col">
98
+ <InsightsPageHeader view="distribution" overviewSearch={overviewSearch} />
99
+ <div className="min-h-0 min-w-0 flex-1 overflow-y-auto bg-muted/5 px-4 py-6 sm:p-8">
100
+ <div className="mx-auto flex w-full max-w-6xl flex-col gap-6">
101
+ <Link
102
+ to="/insights"
103
+ search={overviewSearch}
104
+ className={buttonVariants({
105
+ variant: "ghost",
106
+ className: "h-11 w-fit sm:h-9",
107
+ })}
108
+ >
109
+ <ArrowLeft aria-hidden="true" data-icon="inline-start" />
110
+ Back to overview
111
+ </Link>
112
+ <div className="flex flex-wrap items-center justify-between gap-4">
113
+ <h2 className="text-2xl font-semibold tracking-tight">
114
+ Distribution
115
+ </h2>
116
+ <div className="flex items-center gap-3">
117
+ <UsagePeriodSelector
118
+ twelveMonths={keepsTwelveMonths(retention)}
119
+ window={input.window}
120
+ onWindowChange={(window) => changeSearch({ window })}
121
+ />
122
+ <Button
123
+ aria-label="Refresh distribution"
124
+ variant="ghost"
125
+ size="icon"
126
+ className="size-11 sm:size-9"
127
+ disabled={query.isFetching}
128
+ onClick={() => void query.refetch()}
129
+ >
130
+ <RotateCw aria-hidden="true" />
131
+ </Button>
132
+ </div>
133
+ </div>
134
+ <div className="flex flex-wrap items-end justify-between gap-4">
135
+ <Field className="w-full sm:w-64">
136
+ <FieldLabel htmlFor="distribution-version">
137
+ App version
138
+ </FieldLabel>
139
+ <Select
140
+ items={Object.fromEntries([
141
+ ["all", "All versions"],
142
+ ...versions.map((version) => [`version:${version}`, version]),
143
+ ])}
144
+ value={search.version ? `version:${search.version}` : "all"}
145
+ onValueChange={(value) => {
146
+ if (value !== null)
147
+ changeSearch({
148
+ version: value === "all" ? undefined : value.slice(8),
149
+ });
150
+ }}
151
+ >
152
+ <SelectTrigger
153
+ id="distribution-version"
154
+ className="min-h-11 w-full sm:min-h-9"
155
+ >
156
+ <SelectValue />
157
+ </SelectTrigger>
158
+ <SelectContent>
159
+ <SelectGroup>
160
+ <SelectItem value="all">All versions</SelectItem>
161
+ {versions.map((version) => (
162
+ <SelectItem value={`version:${version}`} key={version}>
163
+ {version}
164
+ </SelectItem>
165
+ ))}
166
+ </SelectGroup>
167
+ </SelectContent>
168
+ </Select>
169
+ </Field>
170
+ <div
171
+ className="flex min-h-9 flex-wrap items-center gap-2"
172
+ aria-label="Distribution scope"
173
+ >
174
+ <Badge variant="outline" className="gap-1.5">
175
+ {input.platform !== "all" ? (
176
+ <PlatformIcon
177
+ platform={input.platform}
178
+ className="size-3.5"
179
+ />
180
+ ) : null}
181
+ {input.platform === "all"
182
+ ? "All platforms"
183
+ : input.platform === "ios"
184
+ ? "iOS"
185
+ : "Android"}
186
+ </Badge>
187
+ <Badge variant="outline">{input.channel}</Badge>
188
+ {input.appVersion ? (
189
+ <Badge variant="outline">Reports from {input.appVersion}</Badge>
190
+ ) : null}
191
+ </div>
192
+ </div>
193
+ <Card
194
+ aria-label="Bundles by app version"
195
+ role="region"
196
+ className="min-w-0"
197
+ >
198
+ <CardHeader className="flex flex-col gap-3">
199
+ <div className="flex items-center justify-between gap-2">
200
+ <CardTitle>Bundles by app version</CardTitle>
201
+ <InsightsInfo label="How bundle distribution is counted">
202
+ Each installation counts once by its latest matching report{" "}
203
+ {distributionPeriod}. Latest reports are counted by UTC day.
204
+ Share is within its app version. Unknown bundle means no
205
+ deployment ID was observed for the reported file. These counts
206
+ describe reporting installations, not all installed devices.
207
+ </InsightsInfo>
208
+ </div>
209
+ <div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
210
+ {query.isPending ? (
211
+ <Skeleton className="h-9 w-16" />
212
+ ) : (
213
+ <p className="text-3xl font-semibold tabular-nums">
214
+ {report
215
+ ? `${report.truncated ? "≥" : ""}${total.toLocaleString()}`
216
+ : "—"}
217
+ </p>
218
+ )}
219
+ <span className="text-sm text-muted-foreground">
220
+ Reporting installations
221
+ </span>
222
+ </div>
223
+ </CardHeader>
224
+ <CardContent className="flex min-w-0 flex-col gap-4">
225
+ {report?.truncated ? (
226
+ <Alert>
227
+ <AlertTitle>Partial history</AlertTitle>
228
+ <AlertDescription>
229
+ Counts and shares reflect reports since{" "}
230
+ {new Date(report.sinceMs).toLocaleString("en", {
231
+ timeZone: "UTC",
232
+ })}{" "}
233
+ UTC. Choose a shorter period for a more complete view.
234
+ </AlertDescription>
235
+ </Alert>
236
+ ) : null}
237
+ {query.isPending ? (
238
+ <Skeleton
239
+ aria-label="Loading bundle distribution"
240
+ className="h-64"
241
+ />
242
+ ) : query.error ? (
243
+ <div className="flex flex-col items-start gap-4">
244
+ <InsightsErrorAlert
245
+ error={query.error}
246
+ fallbackTitle="Distribution unavailable"
247
+ />
248
+ <Button
249
+ variant="outline"
250
+ onClick={() => void query.refetch()}
251
+ >
252
+ Retry distribution
253
+ </Button>
254
+ </div>
255
+ ) : rows.length === 0 ? (
256
+ <Empty>
257
+ <EmptyHeader>
258
+ <EmptyTitle>No bundles reported</EmptyTitle>
259
+ <EmptyDescription>
260
+ Choose another version or period, or change the filters in
261
+ the overview.
262
+ </EmptyDescription>
263
+ </EmptyHeader>
264
+ <Button
265
+ variant="outline"
266
+ onClick={() => changeSearch({ version: undefined })}
267
+ >
268
+ Show all versions
269
+ </Button>
270
+ </Empty>
271
+ ) : (
272
+ <div className="min-w-0">
273
+ <div
274
+ aria-hidden="true"
275
+ className="hidden grid-cols-[8rem_minmax(0,1fr)_7rem_7rem] gap-4 border-b px-3 pb-3 text-xs text-muted-foreground md:grid"
276
+ >
277
+ <span>App version</span>
278
+ <span>Bundle ID</span>
279
+ <span className="text-right">Active installations</span>
280
+ <span className="text-right">Version share</span>
281
+ </div>
282
+ <ul aria-label="Bundle distribution" className="divide-y">
283
+ {rows
284
+ .slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE)
285
+ .map((row) => {
286
+ const versionTotal =
287
+ report?.versions.find(
288
+ (version) => version.name === row.appVersion,
289
+ )?.installations ?? 0;
290
+ const share =
291
+ versionTotal > 0
292
+ ? (row.installations / versionTotal) * 100
293
+ : 0;
294
+ return (
295
+ <li
296
+ key={JSON.stringify([
297
+ row.appVersion,
298
+ row.platform,
299
+ row.releaseId,
300
+ ])}
301
+ className="grid min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-x-4 gap-y-3 px-1 py-4 md:grid-cols-[8rem_minmax(0,1fr)_7rem_7rem] md:px-3"
302
+ >
303
+ <div className="flex min-w-0 flex-col gap-1">
304
+ <span
305
+ className="truncate text-sm font-medium"
306
+ title={row.appVersion}
307
+ >
308
+ {row.appVersion}
309
+ </span>
310
+ <span className="flex items-center gap-1.5 text-xs text-muted-foreground">
311
+ <PlatformIcon
312
+ platform={row.platform}
313
+ className="size-3.5"
314
+ />
315
+ {row.platform === "ios" ? "iOS" : "Android"}
316
+ </span>
317
+ </div>
318
+ <div className="col-span-2 row-start-2 min-w-0 md:col-span-1 md:row-auto">
319
+ {row.releaseId ? (
320
+ <Link
321
+ to="/"
322
+ search={{ releaseId: row.releaseId }}
323
+ aria-label={`Review bundle ${row.releaseId} for app version ${row.appVersion}`}
324
+ className="flex min-h-11 min-w-0 items-center gap-2 text-xs underline-offset-4 hover:underline md:min-h-9"
325
+ >
326
+ <span className="break-all font-mono">
327
+ {row.releaseId}
328
+ </span>
329
+ <ArrowUpRight
330
+ aria-hidden="true"
331
+ className="size-3.5 shrink-0"
332
+ />
333
+ </Link>
334
+ ) : (
335
+ <span className="text-sm text-muted-foreground">
336
+ Unknown bundle
337
+ </span>
338
+ )}
339
+ </div>
340
+ <div className="col-start-2 row-start-1 text-right md:col-auto md:row-auto">
341
+ <span className="text-sm font-medium tabular-nums">
342
+ {row.installations.toLocaleString()}
343
+ </span>
344
+ <span className="ml-1 text-xs text-muted-foreground md:sr-only">
345
+ active
346
+ </span>
347
+ <p className="mt-1 text-xs tabular-nums text-muted-foreground md:hidden">
348
+ {share.toFixed(1)}% of version
349
+ </p>
350
+ </div>
351
+ <div className="hidden flex-col gap-2 md:flex">
352
+ <span className="text-right text-sm tabular-nums">
353
+ {share.toFixed(1)}%
354
+ </span>
355
+ <Progress
356
+ aria-label={`${row.releaseId ?? "Unknown bundle"} share in ${row.appVersion} on ${row.platform}`}
357
+ value={share}
358
+ />
359
+ </div>
360
+ </li>
361
+ );
362
+ })}
363
+ </ul>
364
+ </div>
365
+ )}
366
+ </CardContent>
367
+ {report && rows.length > 0 ? (
368
+ <CardFooter className="flex flex-wrap items-center justify-between gap-3 border-t pt-4">
369
+ <p className="text-xs tabular-nums text-muted-foreground">
370
+ {(page - 1) * PAGE_SIZE + 1}–
371
+ {Math.min(page * PAGE_SIZE, rows.length)} of {rows.length}
372
+ </p>
373
+ <nav aria-label="Distribution pages" className="flex gap-2">
374
+ <Button
375
+ variant="outline"
376
+ className="h-11 sm:h-9"
377
+ disabled={page <= 1}
378
+ onClick={() => changeSearch({ page: page - 1 })}
379
+ >
380
+ Previous
381
+ </Button>
382
+ <Button
383
+ variant="outline"
384
+ className="h-11 sm:h-9"
385
+ disabled={page >= pageCount}
386
+ onClick={() => changeSearch({ page: page + 1 })}
387
+ >
388
+ Next
389
+ </Button>
390
+ </nav>
391
+ </CardFooter>
392
+ ) : null}
393
+ </Card>
394
+ </div>
395
+ </div>
396
+ </div>
397
+ );
398
+ }