@hot-updater/console 1.0.0-rc.2 → 1.0.0-rc.20

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 (275) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/ConsoleFeatureUnavailable-B9VYGA3N.js +1 -0
  3. package/.output/public/assets/HashValueDisplay-D880zntR.js +1 -0
  4. package/.output/public/assets/InsightsInfo-By_sPjTE.js +1 -0
  5. package/.output/public/assets/InsightsPageHeader-9_kx_LL7.js +1 -0
  6. package/.output/public/assets/NotFoundPage-Dys8C0cW.js +1 -0
  7. package/.output/public/assets/api-CtarlV0l.js +1 -0
  8. package/.output/public/assets/api-keys-DFF_QqtN.js +1 -0
  9. package/.output/public/assets/api-keys-evfsQ4sN.js +1 -0
  10. package/.output/public/assets/arrow-left-BP9D-ta_.js +1 -0
  11. package/.output/public/assets/arrow-up-right-CDP9Tevw.js +1 -0
  12. package/.output/public/assets/card-DzCPSRsh.js +1 -0
  13. package/.output/public/assets/console-features-api-yv7rm3DX.js +1 -0
  14. package/.output/public/assets/dialog-BDvCy_QO.js +1 -0
  15. package/.output/public/assets/dist-Clb6cQ9l.js +1 -0
  16. package/.output/public/assets/empty-C5x9mn_s.js +1 -0
  17. package/.output/public/assets/field-Bou7shoO.js +1 -0
  18. package/.output/public/assets/index-0eg3l8aC.js +10 -0
  19. package/.output/public/assets/input-group-DVtWFUUF.js +1 -0
  20. package/.output/public/assets/insights-2MbdiSNv.js +1 -0
  21. package/.output/public/assets/insights-C6tmCZHk.js +2 -0
  22. package/.output/public/assets/insights-CadS_aIA.js +42 -0
  23. package/.output/public/assets/insights-recovery-rpc-wvdyM4LD.js +1 -0
  24. package/.output/public/assets/insights-search-KIF7mRtB.js +1 -0
  25. package/.output/public/assets/insights-usage-rpc-qUkNtrWr.js +1 -0
  26. package/.output/public/assets/insights_.distribution-BhBs6PNA.js +1 -0
  27. package/.output/public/assets/insights_.distribution-CItZw3-8.js +1 -0
  28. package/.output/public/assets/insights_.distribution-CJUH_wAS.js +2 -0
  29. package/.output/public/assets/installations-CH2H7JQt.js +2 -0
  30. package/.output/public/assets/installations-DMeMuwsW.js +1 -0
  31. package/.output/public/assets/installations-DnvxI9Dt.js +1 -0
  32. package/.output/public/assets/loader-circle-Ca7C9YY1.js +1 -0
  33. package/.output/public/assets/mutation-x9L28_CD.js +1 -0
  34. package/.output/public/assets/preload-helper-BN-UkjOE.js +1 -0
  35. package/.output/public/assets/refresh-cw-CVGqvy8Z.js +1 -0
  36. package/.output/public/assets/routes-BhK8DuZA.js +1 -0
  37. package/.output/public/assets/routes-veKtDW39.js +2 -0
  38. package/.output/public/assets/sidebar-B45aMqRm.js +3 -0
  39. package/.output/public/assets/styles-Dn0pK1EX.css +2 -0
  40. package/.output/public/assets/table-CwLnN6KX.js +1 -0
  41. package/.output/public/assets/useCompositeListItem-CF7vsfcA.js +1 -0
  42. package/.output/public/assets/useMutation-CN1ygYEr.js +1 -0
  43. package/.output/public/assets/useOpenInteractionType-AnAMWYIY.js +1 -0
  44. package/.output/public/assets/valueToPercent-C-t8FT0y.js +1 -0
  45. package/.output/server/{__23tanstack-start-server-fn-resolver-B7ioJhtE.mjs → __23tanstack-start-server-fn-resolver-CUQLz7hs.mjs} +52 -40
  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 +49 -3
  49. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +4 -4
  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__react-query.mjs +8 -3
  67. package/.output/server/_libs/verkit.mjs +99 -1
  68. package/.output/server/_runtime.mjs +10 -1
  69. package/.output/server/_ssr/ConsoleFeatureUnavailable-DoxET2AT.mjs +58 -0
  70. package/.output/server/_ssr/{HashValueDisplay-DwpGLfrl.mjs → HashValueDisplay-BplboHEb.mjs} +3 -3
  71. package/.output/server/_ssr/InsightsInfo-o2YPd5Qc.mjs +37 -0
  72. package/.output/server/_ssr/InsightsPageHeader-NExCJL1I.mjs +154 -0
  73. package/.output/server/_ssr/NotFoundPage-Cd-A8ock.mjs +37 -0
  74. package/.output/server/_ssr/api-B1Ro0uys.mjs +202 -0
  75. package/.output/server/_ssr/api-C0G9MTMI.mjs +4205 -0
  76. package/.output/server/_ssr/{api-keys-CZeTPQys.mjs → api-keys-CT3HG7KT.mjs} +49 -9
  77. package/.output/server/_ssr/api-keys-Dvvy1sem.mjs +5 -0
  78. package/.output/server/_ssr/api-keys-rpc-b1qTTFfG.mjs +52 -0
  79. package/.output/server/_ssr/api-rpc-Cbns2MGx.mjs +338 -0
  80. package/.output/server/_ssr/{auth-rpc-DUaeOWKK.mjs → auth-rpc-DChS7m9y.mjs} +6 -6
  81. package/.output/server/_ssr/auth.server-Ccqz2iVv.mjs +37 -0
  82. package/.output/server/_ssr/bundleActivity-D17Pb_h5.mjs +19 -0
  83. package/.output/server/_ssr/card-DMbcvlPz.mjs +49 -0
  84. package/.output/server/_ssr/config-DMEJdhoW.mjs +13 -0
  85. package/.output/server/_ssr/config.server-DyypN6cQ.mjs +60 -0
  86. package/.output/server/_ssr/console-access-KpPv5lin.mjs +17 -0
  87. package/.output/server/_ssr/console-features-CsbyHYfK.mjs +55 -0
  88. package/.output/server/_ssr/console-features-api-B-KVuo4p.mjs +43 -0
  89. package/.output/server/_ssr/console-features-rpc-Djyo5740.mjs +19 -0
  90. package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-B_I3Ll4M.mjs} +28 -2
  91. package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-DuNm5mmo.mjs} +2 -2
  92. package/.output/server/_ssr/databaseRows-CXxf_VIB.mjs +132 -0
  93. package/.output/server/_ssr/{deleteBundle-BTGwBgzx.mjs → deleteBundle-Dcc21Lbd.mjs} +9 -19
  94. package/.output/server/_ssr/{dialog-DS-nw-pA.mjs → dialog-cOmLTUV2.mjs} +2 -2
  95. package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-B-r4u3Lg.mjs} +28 -5
  96. package/.output/server/_ssr/{downloadBundle-CNnYxYO2.mjs → downloadBundle-CexOFoZ4.mjs} +6 -7
  97. package/.output/server/_ssr/empty-BHoY_5jt.mjs +49 -0
  98. package/.output/server/_ssr/{table-CnZmon_Z.mjs → field-mjhcNEXD.mjs} +63 -51
  99. package/.output/server/_ssr/getBundleChildren-DbDXe9wB.mjs +22 -0
  100. package/.output/server/_ssr/{input-group-BpsnmvZw.mjs → input-group-96H30HWC.mjs} +2 -2
  101. package/.output/server/_ssr/insights-D25VDVaz.mjs +5 -0
  102. package/.output/server/_ssr/insights-D359GJFL.mjs +14 -0
  103. package/.output/server/_ssr/insights-DgFv1NsO.mjs +1238 -0
  104. package/.output/server/_ssr/insights-api-ChAKrbhG.mjs +105 -0
  105. package/.output/server/_ssr/insights-failures-WQ-ttjOx.mjs +44 -0
  106. package/.output/server/_ssr/insights-failures-rpc-BFsqlltN.mjs +21 -0
  107. package/.output/server/_ssr/insights-recovery-DkL6bxMW.mjs +28 -0
  108. package/.output/server/_ssr/insights-recovery-rpc-2P5m8F62.mjs +46 -0
  109. package/.output/server/_ssr/insights-recovery-rpc-Tj8vNC19.mjs +20 -0
  110. package/.output/server/_ssr/{insights-rpc-38DujK0G.mjs → insights-rpc-DrkVmV9o.mjs} +22 -14
  111. package/.output/server/_ssr/insights-search-BsalnNw-.mjs +25 -0
  112. package/.output/server/_ssr/insights-usage-1nVTQrLy.mjs +29 -0
  113. package/.output/server/_ssr/insights-usage-rpc-Ct_4xHhZ.mjs +29 -0
  114. package/.output/server/_ssr/insights-usage-rpc-DaYIRtFK.mjs +41 -0
  115. package/.output/server/_ssr/insightsRecovery-CpGucWN1.mjs +45 -0
  116. package/.output/server/_ssr/insightsUsage-JYpKZAF7.mjs +56 -0
  117. package/.output/server/_ssr/insights_.distribution-Bcu6uYs_.mjs +5 -0
  118. package/.output/server/_ssr/insights_.distribution-D3Csuyjw.mjs +14 -0
  119. package/.output/server/_ssr/insights_.distribution-jQZEF07J.mjs +335 -0
  120. package/.output/server/_ssr/installations-B8Vh54lH.mjs +69 -0
  121. package/.output/server/_ssr/installations-BI5kelHv.mjs +5 -0
  122. package/.output/server/_ssr/{installations-DiVKEF5T.mjs → installations-Cxikllmx.mjs} +517 -54
  123. package/.output/server/_ssr/listReleases-DqU9t1l3.mjs +79 -0
  124. package/.output/server/_ssr/releaseManagement-B3e7I8J9.mjs +11 -0
  125. package/.output/server/_ssr/{router-B9wG_9f8.mjs → router-CRkm-wHD.mjs} +117 -124
  126. package/.output/server/_ssr/routes-B3IJo_jL.mjs +292 -0
  127. package/.output/server/_ssr/{routes-D7Yh_jfj.mjs → routes-CNAi0Mmv.mjs} +176 -455
  128. package/.output/server/_ssr/runtime.server-NucbYjc7.mjs +1372 -0
  129. package/.output/server/_ssr/select-BkPAJFL7.mjs +107 -0
  130. package/.output/server/_ssr/{server-D_C2rMzf.mjs → server-DKJYsaCP.mjs} +6 -32
  131. package/.output/server/_ssr/{createSsrRpc-DjWNJ0uc.mjs → sidebar-Cw1VuC7d.mjs} +117 -63
  132. package/.output/server/_ssr/ssr.mjs +1 -1
  133. package/.output/server/_ssr/table-q0P9wz7x.mjs +53 -0
  134. package/.output/server/_ssr/updateFailures-DMU9f0pK.mjs +24 -0
  135. package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-BzSO-fr-.mjs} +2 -2
  136. package/.output/server/_tanstack-start-manifest_v-Bb0gKDbb.mjs +124 -0
  137. package/.output/server/index.mjs +273 -168
  138. package/README.md +71 -65
  139. package/lib/index.d.mts +13 -11
  140. package/lib/vite-internal.mjs +6 -3
  141. package/package.json +8 -8
  142. package/src/components/AppSidebar.tsx +91 -71
  143. package/src/components/ConsoleAccessPage.tsx +5 -1
  144. package/src/components/ConsoleFeatureUnavailable.tsx +82 -0
  145. package/src/components/NotFoundPage.tsx +7 -7
  146. package/src/components/features/FeatureSlots.tsx +99 -0
  147. package/src/components/features/api-keys/apiKeysFeatureUi.tsx +15 -0
  148. package/src/components/features/bundles/BundleMetadata.tsx +7 -4
  149. package/src/components/features/channels/ChannelManagementDialog.tsx +2 -5
  150. package/src/components/features/insights/AppUsage.tsx +370 -0
  151. package/src/components/features/insights/EstimatedCount.tsx +18 -0
  152. package/src/components/features/insights/EventDetails.tsx +117 -37
  153. package/src/components/features/insights/EventHistoryCard.tsx +112 -26
  154. package/src/components/features/insights/EventHistoryList.tsx +2 -2
  155. package/src/components/features/insights/InsightsControls.tsx +265 -102
  156. package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
  157. package/src/components/features/insights/InsightsInfo.tsx +48 -0
  158. package/src/components/features/insights/InsightsOverview.tsx +176 -173
  159. package/src/components/features/insights/InsightsPageHeader.tsx +35 -20
  160. package/src/components/features/insights/InsightsPeriodSelector.tsx +89 -0
  161. package/src/components/features/insights/InstallationHistoryCard.tsx +52 -10
  162. package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
  163. package/src/components/features/insights/ReleaseActivity.tsx +188 -0
  164. package/src/components/features/insights/ReportingDevicesSummary.tsx +65 -0
  165. package/src/components/features/insights/UpdateFailures.tsx +319 -0
  166. package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
  167. package/src/components/features/releases/ReleaseEditorSheet.tsx +24 -1
  168. package/src/components/ui/card.tsx +1 -1
  169. package/src/components/ui/empty.tsx +101 -0
  170. package/src/components/ui/scroll-area.tsx +52 -0
  171. package/src/components/ui/sidebar.tsx +22 -29
  172. package/src/components/ui/tabs.tsx +81 -0
  173. package/src/index.ts +12 -17
  174. package/src/lib/api-keys-api.ts +0 -28
  175. package/src/lib/api-keys-rpc.ts +27 -38
  176. package/src/lib/api-rpc.ts +197 -221
  177. package/src/lib/api.ts +11 -13
  178. package/src/lib/bundle-activity.ts +31 -0
  179. package/src/lib/console-access.ts +20 -0
  180. package/src/lib/console-features-api.ts +68 -0
  181. package/src/lib/console-features-rpc.ts +13 -0
  182. package/src/lib/console-features.ts +91 -0
  183. package/src/lib/insights-api.ts +37 -1
  184. package/src/lib/insights-failures-rpc.ts +21 -0
  185. package/src/lib/insights-failures.ts +129 -0
  186. package/src/lib/insights-recovery-rpc.ts +51 -0
  187. package/src/lib/insights-recovery.ts +68 -0
  188. package/src/lib/insights-retention.ts +21 -0
  189. package/src/lib/insights-rpc.ts +30 -12
  190. package/src/lib/insights-search.ts +58 -0
  191. package/src/lib/insights-usage-rpc.ts +29 -0
  192. package/src/lib/insights-usage.ts +74 -0
  193. package/src/lib/insights-view.ts +38 -2
  194. package/src/lib/scroll-restoration.ts +21 -0
  195. package/src/lib/server/adminInsights.ts +124 -0
  196. package/src/lib/server/auth.server.ts +13 -5
  197. package/src/lib/server/bundleActivity.ts +37 -0
  198. package/src/lib/server/config.server.ts +16 -32
  199. package/src/lib/server/console-feature-apis.ts +59 -0
  200. package/src/lib/server/console-runtime.server.ts +6 -40
  201. package/src/lib/server/deleteBundle.ts +16 -25
  202. package/src/lib/server/downloadBundle.ts +7 -10
  203. package/src/lib/server/getBundleChildren.ts +31 -122
  204. package/src/lib/server/insightsRecovery.ts +55 -0
  205. package/src/lib/server/insightsUsage.ts +63 -0
  206. package/src/lib/server/listReleases.ts +109 -44
  207. package/src/lib/server/releaseReachability.ts +4 -3
  208. package/src/lib/server/runtime.server.ts +132 -31
  209. package/src/lib/server/updateFailures.ts +31 -0
  210. package/src/routeTree.gen.ts +21 -21
  211. package/src/router.tsx +3 -2
  212. package/src/routes/-installations-search.ts +32 -8
  213. package/src/routes/-releases-search.ts +76 -29
  214. package/src/routes/__root.tsx +10 -4
  215. package/src/routes/api/bundles/$bundleId/download.ts +2 -5
  216. package/src/routes/api-keys.tsx +5 -2
  217. package/src/routes/index.tsx +167 -104
  218. package/src/routes/insights.tsx +100 -59
  219. package/src/routes/insights_.distribution.tsx +398 -0
  220. package/src/routes/installations.tsx +98 -41
  221. package/src/vite-internal.ts +6 -3
  222. package/.output/public/assets/HashValueDisplay-CB0Eskit.js +0 -1
  223. package/.output/public/assets/InsightsPageHeader-Car5LNiZ.js +0 -1
  224. package/.output/public/assets/api-keys-BzKocqRy.js +0 -1
  225. package/.output/public/assets/api-keys-api-BHcODB7q.js +0 -1
  226. package/.output/public/assets/card-Dhr_eFYj.js +0 -1
  227. package/.output/public/assets/chart-no-axes-combined-Dy4WJv2U.js +0 -1
  228. package/.output/public/assets/composite-Bp98A0Ld.js +0 -1
  229. package/.output/public/assets/createServerFn-CA8fs7QD.js +0 -3
  230. package/.output/public/assets/dialog-CUTrcPlB.js +0 -1
  231. package/.output/public/assets/dist-C_NydL0e.js +0 -1
  232. package/.output/public/assets/index-DdAz-CXv.js +0 -10
  233. package/.output/public/assets/input-group-cSOG4wh_.js +0 -1
  234. package/.output/public/assets/insights-C0PBSZ-n.js +0 -1
  235. package/.output/public/assets/installations-CE33wgbY.js +0 -2
  236. package/.output/public/assets/installations-CStZja8x.js +0 -1
  237. package/.output/public/assets/lazyRouteComponent-Ccp3bwBY.js +0 -1
  238. package/.output/public/assets/link-CI8idAUi.js +0 -1
  239. package/.output/public/assets/loader-circle-BoxLrsOu.js +0 -1
  240. package/.output/public/assets/preload-helper-CQ36nxok.js +0 -1
  241. package/.output/public/assets/refresh-cw-Bqc72UZG.js +0 -1
  242. package/.output/public/assets/routes-DGVXdzpw.js +0 -2
  243. package/.output/public/assets/routes-DTlwOr8I.js +0 -1
  244. package/.output/public/assets/shield-check-uK3MHWJZ.js +0 -1
  245. package/.output/public/assets/signing-CMf4aqDO.js +0 -1
  246. package/.output/public/assets/styles-BCvbtKsm.css +0 -2
  247. package/.output/public/assets/table-2vcV89QB.js +0 -1
  248. package/.output/public/assets/useCompositeListItem-CHLITE_4.js +0 -1
  249. package/.output/public/assets/x-DCd0j8c6.js +0 -1
  250. package/.output/server/_ssr/InsightsPageHeader-Df4Brj8u.mjs +0 -484
  251. package/.output/server/_ssr/api-keys-api-BUPQVSpk.mjs +0 -60
  252. package/.output/server/_ssr/api-keys-rpc-Cs7tS5_j.mjs +0 -136
  253. package/.output/server/_ssr/api-rpc-tAupfGQm.mjs +0 -1399
  254. package/.output/server/_ssr/auth.server-DW71WzsD.mjs +0 -47
  255. package/.output/server/_ssr/bundle-signing-rpc-BhnBw0-d.mjs +0 -16
  256. package/.output/server/_ssr/bundle-signing.server-C4NW5mWx.mjs +0 -12
  257. package/.output/server/_ssr/card-BRiPmi0C.mjs +0 -108
  258. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
  259. package/.output/server/_ssr/config.server-afDuAwaG.mjs +0 -368
  260. package/.output/server/_ssr/databaseJsonValue-d6I3nAS4.mjs +0 -45
  261. package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
  262. package/.output/server/_ssr/getBundleChildren-C0vh4ZvE.mjs +0 -50
  263. package/.output/server/_ssr/insights-BdS364Xu.mjs +0 -460
  264. package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +0 -303
  265. package/.output/server/_ssr/installations-CtA59MQG.mjs +0 -29
  266. package/.output/server/_ssr/routes-CKrrS2q2.mjs +0 -37
  267. package/.output/server/_ssr/runtime.server-DacU1TBV.mjs +0 -438
  268. package/.output/server/_ssr/signing-Dz1WkZOf.mjs +0 -133
  269. package/.output/server/_tanstack-start-manifest_v-2MjTyxil.mjs +0 -97
  270. package/src/components/features/insights/InsightsBundleReports.tsx +0 -51
  271. package/src/components/features/signing/BundleSigningPage.tsx +0 -139
  272. package/src/lib/bundle-signing-api.ts +0 -14
  273. package/src/lib/bundle-signing-rpc.ts +0 -16
  274. package/src/lib/server/bundle-signing.server.ts +0 -23
  275. package/src/routes/signing.tsx +0 -37
@@ -0,0 +1,31 @@
1
+ import {
2
+ readUpdateFailuresInput,
3
+ type UpdateFailuresInput,
4
+ type UpdateFailuresReport,
5
+ } from "../insights-failures";
6
+ import { insightsPeriodEnd, recoveryWindows } from "../insights-recovery";
7
+ import type { ConsoleInsightsReads } from "./adminInsights";
8
+
9
+ /** A release's or a channel's update failures over a period that ends with the current hour. */
10
+ export async function getUpdateFailuresReport(
11
+ reads: Pick<ConsoleInsightsReads, "getUpdateFailures">,
12
+ input: UpdateFailuresInput,
13
+ now = Date.now(),
14
+ ): Promise<UpdateFailuresReport> {
15
+ readUpdateFailuresInput(input);
16
+ const end = insightsPeriodEnd(now);
17
+ const timeRange =
18
+ input.window === undefined
19
+ ? undefined
20
+ : {
21
+ start: Math.max(0, end - recoveryWindows[input.window].durationMs),
22
+ end,
23
+ };
24
+ const failures = await reads.getUpdateFailures({
25
+ platform: input.platform,
26
+ channel: input.channel,
27
+ ...(input.releaseId === undefined ? {} : { releaseId: input.releaseId }),
28
+ ...(timeRange === undefined ? {} : { timeRange }),
29
+ });
30
+ return { ...failures, startMs: timeRange?.start ?? null, endMs: end };
31
+ }
@@ -9,19 +9,14 @@
9
9
  // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
10
10
 
11
11
  import { Route as rootRouteImport } from './routes/__root'
12
- import { Route as SigningRouteImport } from './routes/signing'
13
12
  import { Route as InstallationsRouteImport } from './routes/installations'
14
13
  import { Route as InsightsRouteImport } from './routes/insights'
15
14
  import { Route as ApiKeysRouteImport } from './routes/api-keys'
16
15
  import { Route as IndexRouteImport } from './routes/index'
16
+ import { Route as InsightsDistributionRouteImport } from './routes/insights_.distribution'
17
17
  import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$'
18
18
  import { Route as ApiBundlesBundleIdDownloadRouteImport } from './routes/api/bundles/$bundleId/download'
19
19
 
20
- const SigningRoute = SigningRouteImport.update({
21
- id: '/signing',
22
- path: '/signing',
23
- getParentRoute: () => rootRouteImport,
24
- } as any)
25
20
  const InstallationsRoute = InstallationsRouteImport.update({
26
21
  id: '/installations',
27
22
  path: '/installations',
@@ -42,6 +37,11 @@ const IndexRoute = IndexRouteImport.update({
42
37
  path: '/',
43
38
  getParentRoute: () => rootRouteImport,
44
39
  } as any)
40
+ const InsightsDistributionRoute = InsightsDistributionRouteImport.update({
41
+ id: '/insights_/distribution',
42
+ path: '/insights/distribution',
43
+ getParentRoute: () => rootRouteImport,
44
+ } as any)
45
45
  const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({
46
46
  id: '/api/auth/$',
47
47
  path: '/api/auth/$',
@@ -59,7 +59,7 @@ export interface FileRoutesByFullPath {
59
59
  '/api-keys': typeof ApiKeysRoute
60
60
  '/insights': typeof InsightsRoute
61
61
  '/installations': typeof InstallationsRoute
62
- '/signing': typeof SigningRoute
62
+ '/insights/distribution': typeof InsightsDistributionRoute
63
63
  '/api/auth/$': typeof ApiAuthSplatRoute
64
64
  '/api/bundles/$bundleId/download': typeof ApiBundlesBundleIdDownloadRoute
65
65
  }
@@ -68,7 +68,7 @@ export interface FileRoutesByTo {
68
68
  '/api-keys': typeof ApiKeysRoute
69
69
  '/insights': typeof InsightsRoute
70
70
  '/installations': typeof InstallationsRoute
71
- '/signing': typeof SigningRoute
71
+ '/insights/distribution': typeof InsightsDistributionRoute
72
72
  '/api/auth/$': typeof ApiAuthSplatRoute
73
73
  '/api/bundles/$bundleId/download': typeof ApiBundlesBundleIdDownloadRoute
74
74
  }
@@ -78,7 +78,7 @@ export interface FileRoutesById {
78
78
  '/api-keys': typeof ApiKeysRoute
79
79
  '/insights': typeof InsightsRoute
80
80
  '/installations': typeof InstallationsRoute
81
- '/signing': typeof SigningRoute
81
+ '/insights_/distribution': typeof InsightsDistributionRoute
82
82
  '/api/auth/$': typeof ApiAuthSplatRoute
83
83
  '/api/bundles/$bundleId/download': typeof ApiBundlesBundleIdDownloadRoute
84
84
  }
@@ -89,7 +89,7 @@ export interface FileRouteTypes {
89
89
  | '/api-keys'
90
90
  | '/insights'
91
91
  | '/installations'
92
- | '/signing'
92
+ | '/insights/distribution'
93
93
  | '/api/auth/$'
94
94
  | '/api/bundles/$bundleId/download'
95
95
  fileRoutesByTo: FileRoutesByTo
@@ -98,7 +98,7 @@ export interface FileRouteTypes {
98
98
  | '/api-keys'
99
99
  | '/insights'
100
100
  | '/installations'
101
- | '/signing'
101
+ | '/insights/distribution'
102
102
  | '/api/auth/$'
103
103
  | '/api/bundles/$bundleId/download'
104
104
  id:
@@ -107,7 +107,7 @@ export interface FileRouteTypes {
107
107
  | '/api-keys'
108
108
  | '/insights'
109
109
  | '/installations'
110
- | '/signing'
110
+ | '/insights_/distribution'
111
111
  | '/api/auth/$'
112
112
  | '/api/bundles/$bundleId/download'
113
113
  fileRoutesById: FileRoutesById
@@ -117,20 +117,13 @@ export interface RootRouteChildren {
117
117
  ApiKeysRoute: typeof ApiKeysRoute
118
118
  InsightsRoute: typeof InsightsRoute
119
119
  InstallationsRoute: typeof InstallationsRoute
120
- SigningRoute: typeof SigningRoute
120
+ InsightsDistributionRoute: typeof InsightsDistributionRoute
121
121
  ApiAuthSplatRoute: typeof ApiAuthSplatRoute
122
122
  ApiBundlesBundleIdDownloadRoute: typeof ApiBundlesBundleIdDownloadRoute
123
123
  }
124
124
 
125
125
  declare module '@tanstack/react-router' {
126
126
  interface FileRoutesByPath {
127
- '/signing': {
128
- id: '/signing'
129
- path: '/signing'
130
- fullPath: '/signing'
131
- preLoaderRoute: typeof SigningRouteImport
132
- parentRoute: typeof rootRouteImport
133
- }
134
127
  '/installations': {
135
128
  id: '/installations'
136
129
  path: '/installations'
@@ -159,6 +152,13 @@ declare module '@tanstack/react-router' {
159
152
  preLoaderRoute: typeof IndexRouteImport
160
153
  parentRoute: typeof rootRouteImport
161
154
  }
155
+ '/insights_/distribution': {
156
+ id: '/insights_/distribution'
157
+ path: '/insights/distribution'
158
+ fullPath: '/insights/distribution'
159
+ preLoaderRoute: typeof InsightsDistributionRouteImport
160
+ parentRoute: typeof rootRouteImport
161
+ }
162
162
  '/api/auth/$': {
163
163
  id: '/api/auth/$'
164
164
  path: '/api/auth/$'
@@ -181,7 +181,7 @@ const rootRouteChildren: RootRouteChildren = {
181
181
  ApiKeysRoute: ApiKeysRoute,
182
182
  InsightsRoute: InsightsRoute,
183
183
  InstallationsRoute: InstallationsRoute,
184
- SigningRoute: SigningRoute,
184
+ InsightsDistributionRoute: InsightsDistributionRoute,
185
185
  ApiAuthSplatRoute: ApiAuthSplatRoute,
186
186
  ApiBundlesBundleIdDownloadRoute: ApiBundlesBundleIdDownloadRoute,
187
187
  }
package/src/router.tsx CHANGED
@@ -4,7 +4,7 @@ import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query
4
4
  // Import the route tree selected by the local or hosted Vite integration.
5
5
  import { routeTree } from "virtual:hot-updater-console/route-tree";
6
6
 
7
- import { getInsightsScrollRestorationKey } from "./routes/-installations-search";
7
+ import { routeScrollRestorationKey } from "./lib/scroll-restoration";
8
8
 
9
9
  // Create a new router instance
10
10
  export const getRouter = () => {
@@ -14,7 +14,8 @@ export const getRouter = () => {
14
14
  context: { queryClient },
15
15
 
16
16
  scrollRestoration: true,
17
- getScrollRestorationKey: getInsightsScrollRestorationKey,
17
+ getScrollRestorationKey: (location): string =>
18
+ routeScrollRestorationKey(router, location),
18
19
  defaultPreloadStaleTime: 0,
19
20
  });
20
21
 
@@ -1,5 +1,23 @@
1
1
  import type { ParsedLocation } from "@tanstack/react-router";
2
2
 
3
+ import {
4
+ DEFAULT_EVENT_RANGE,
5
+ EVENT_RANGES,
6
+ type EventRange,
7
+ } from "../lib/insights-view";
8
+
9
+ export type InsightsPaginationState = {
10
+ readonly eventsBack?: readonly string[];
11
+ readonly searchBack?: readonly string[];
12
+ readonly historyBack?: readonly string[];
13
+ };
14
+
15
+ declare module "@tanstack/react-router" {
16
+ interface HistoryState {
17
+ insightsPagination?: InsightsPaginationState;
18
+ }
19
+ }
20
+
3
21
  const readCursor = (value: unknown): string | undefined =>
4
22
  typeof value === "string" && value.length > 0 ? value : undefined;
5
23
 
@@ -8,14 +26,19 @@ const readTimestamp = (value: unknown): number | undefined =>
8
26
  ? value
9
27
  : undefined;
10
28
 
11
- export function getInsightsScrollRestorationKey(location: ParsedLocation) {
12
- if (location.pathname === "/installations") {
13
- const search = validateInstallationsSearch(location.search);
14
- if (search.query === undefined && search.installId === undefined) {
15
- return `/installations?eventsBefore=${search.eventsBefore ?? "new"}&eventsCursor=${search.eventsCursor ?? "first"}`;
16
- }
17
- }
18
- return location.state.__TSR_key!;
29
+ const readEventRange = (value: unknown): EventRange | undefined =>
30
+ EVENT_RANGES.find((range) => range === value);
31
+
32
+ /**
33
+ * The installations route's scroll-restoration key: every visit to one page
34
+ * of the event list shares its scroll position, and a lookup keeps its own
35
+ * for each history entry.
36
+ */
37
+ export function installationsScrollRestorationKey(location: ParsedLocation) {
38
+ const search = validateInstallationsSearch(location.search);
39
+ return search.query === undefined && search.installId === undefined
40
+ ? `/installations?eventsBefore=${search.eventsBefore ?? "new"}&eventsRange=${search.eventsRange ?? DEFAULT_EVENT_RANGE}&eventsCursor=${search.eventsCursor ?? "first"}`
41
+ : location.state.__TSR_key!;
19
42
  }
20
43
 
21
44
  export function validateInstallationsSearch(search: Record<string, unknown>) {
@@ -25,6 +48,7 @@ export function validateInstallationsSearch(search: Record<string, unknown>) {
25
48
  typeof search.installId === "string" ? search.installId : undefined,
26
49
  eventsCursor: readCursor(search.eventsCursor),
27
50
  eventsBefore: readTimestamp(search.eventsBefore),
51
+ eventsRange: readEventRange(search.eventsRange),
28
52
  searchCursor: readCursor(search.searchCursor),
29
53
  historyCursor: readCursor(search.historyCursor),
30
54
  historyBefore: readTimestamp(search.historyBefore),
@@ -1,13 +1,17 @@
1
+ import type { ReleaseFilter } from "@hot-updater/plugin-core";
2
+
1
3
  export interface ReleaseSearch {
4
+ /** Releases newer than this id: the previous page. */
2
5
  afterReleaseId?: string;
6
+ /** Releases older than this id: the next page. */
3
7
  beforeReleaseId?: string;
4
8
  bundleId?: string;
5
9
  channelId?: string;
6
10
  enabled?: boolean;
7
- page?: number;
8
11
  platform?: "ios" | "android";
9
12
  releaseId?: string;
10
- targetAppVersion?: string;
13
+ /** One catalog scope: a channel, platform, and target kind. */
14
+ scopeKey?: string;
11
15
  }
12
16
 
13
17
  const optionalString = (value: unknown) =>
@@ -15,15 +19,14 @@ const optionalString = (value: unknown) =>
15
19
  ? value.trim()
16
20
  : undefined;
17
21
 
22
+ /**
23
+ * The search a URL holds, reduced to one of the filter sets the release
24
+ * indexes serve: a bundle, a scope (with enabled), or a channel with its
25
+ * platform (with enabled). A channel without a platform lists iOS.
26
+ */
18
27
  export function validateReleaseSearch(
19
28
  search: Record<string, unknown>,
20
29
  ): ReleaseSearch {
21
- const parsedPage =
22
- typeof search.page === "number"
23
- ? search.page
24
- : typeof search.page === "string"
25
- ? Number(search.page)
26
- : undefined;
27
30
  const beforeReleaseId = optionalString(search.beforeReleaseId);
28
31
  const afterReleaseId = beforeReleaseId
29
32
  ? undefined
@@ -34,41 +37,85 @@ export function validateReleaseSearch(
34
37
  : search.enabled === false || search.enabled === "false"
35
38
  ? false
36
39
  : undefined;
40
+ const bundleId = optionalString(search.bundleId);
41
+ const scopeKey = bundleId ? undefined : optionalString(search.scopeKey);
42
+ const channelId =
43
+ bundleId || scopeKey ? undefined : optionalString(search.channelId);
44
+ const platform =
45
+ channelId === undefined
46
+ ? undefined
47
+ : search.platform === "android"
48
+ ? "android"
49
+ : "ios";
37
50
 
38
51
  return {
39
52
  afterReleaseId,
40
53
  beforeReleaseId,
41
- bundleId: optionalString(search.bundleId),
42
- channelId: optionalString(search.channelId),
43
- enabled,
44
- page:
45
- parsedPage !== undefined && Number.isInteger(parsedPage) && parsedPage > 1
46
- ? parsedPage
47
- : undefined,
48
- platform:
49
- search.platform === "ios" || search.platform === "android"
50
- ? search.platform
51
- : undefined,
54
+ bundleId,
55
+ channelId,
56
+ enabled: scopeKey || channelId ? enabled : undefined,
57
+ platform,
52
58
  releaseId: optionalString(search.releaseId),
53
- targetAppVersion: optionalString(search.targetAppVersion),
59
+ scopeKey,
54
60
  };
55
61
  }
56
62
 
63
+ export function releaseFilterOf(search: ReleaseSearch): ReleaseFilter {
64
+ const enabled =
65
+ search.enabled === undefined ? {} : { enabled: search.enabled };
66
+ if (search.bundleId) return { kind: "bundle", bundleId: search.bundleId };
67
+ if (search.scopeKey) {
68
+ return { kind: "scope", scopeKey: search.scopeKey, ...enabled };
69
+ }
70
+ if (search.channelId && search.platform) {
71
+ return {
72
+ kind: "channelPlatform",
73
+ channelId: search.channelId,
74
+ platform: search.platform,
75
+ ...enabled,
76
+ };
77
+ }
78
+ return { kind: "all" };
79
+ }
80
+
81
+ export function hasReleaseFilters(search: ReleaseSearch): boolean {
82
+ return releaseFilterOf(search).kind !== "all";
83
+ }
84
+
85
+ /**
86
+ * A new filter starts from the first page. A bundle or scope filter replaces
87
+ * the others, and choosing a channel or platform clears them.
88
+ */
57
89
  export function updateReleaseFilters(
58
90
  current: ReleaseSearch,
59
91
  filters: Partial<
60
92
  Pick<
61
93
  ReleaseSearch,
62
- "bundleId" | "channelId" | "enabled" | "platform" | "targetAppVersion"
94
+ "bundleId" | "channelId" | "enabled" | "platform" | "scopeKey"
63
95
  >
64
96
  >,
65
97
  ): ReleaseSearch {
66
- return {
67
- ...current,
68
- ...filters,
69
- afterReleaseId: undefined,
70
- beforeReleaseId: undefined,
71
- page: undefined,
72
- releaseId: undefined,
73
- };
98
+ const next = { ...current, ...filters };
99
+ if (filters.bundleId)
100
+ return validateReleaseSearch({ bundleId: next.bundleId });
101
+ if (filters.scopeKey) {
102
+ return validateReleaseSearch({
103
+ scopeKey: next.scopeKey,
104
+ enabled: next.enabled,
105
+ });
106
+ }
107
+ if ("channelId" in filters || "platform" in filters) {
108
+ return validateReleaseSearch({
109
+ channelId: next.channelId,
110
+ enabled: next.enabled,
111
+ platform: next.platform,
112
+ });
113
+ }
114
+ return validateReleaseSearch({
115
+ bundleId: next.bundleId,
116
+ channelId: next.channelId,
117
+ enabled: next.enabled,
118
+ platform: next.platform,
119
+ scopeKey: next.scopeKey,
120
+ });
74
121
  }
@@ -18,6 +18,7 @@ import {
18
18
  getConsoleAccessRpc,
19
19
  getConsoleAuthProvidersRpc,
20
20
  } from "@/lib/auth-rpc";
21
+ import { consoleFeaturesQueryOptions } from "@/lib/console-features-api";
21
22
 
22
23
  import appCss from "../styles.css?url";
23
24
 
@@ -60,11 +61,16 @@ export const Route = createRootRouteWithContext<{
60
61
  ],
61
62
  }),
62
63
 
63
- loader: async () => {
64
+ loader: async ({ context }) => {
64
65
  const [access, providers] = await Promise.all([
65
66
  getConsoleAccessRpc(),
66
67
  getConsoleAuthProvidersRpc(),
67
68
  ]);
69
+ // The authorized layout's navigation shows only the features on, so they
70
+ // load with it; the query keeps them for the visit.
71
+ if (access.status === "authorized") {
72
+ await context.queryClient.prefetchQuery(consoleFeaturesQueryOptions());
73
+ }
68
74
  return { access, providers };
69
75
  },
70
76
 
@@ -140,10 +146,10 @@ function RootLayout() {
140
146
  return <ConsoleAccessPage access={access} providers={providers} />;
141
147
  }
142
148
 
143
- return <AuthorizedConsole />;
149
+ return <AuthorizedConsole canSignOut={providers.length > 0} />;
144
150
  }
145
151
 
146
- function AuthorizedConsole() {
152
+ function AuthorizedConsole({ canSignOut }: { canSignOut: boolean }) {
147
153
  useEffect(() => {
148
154
  if (
149
155
  import.meta.env.DEV &&
@@ -155,7 +161,7 @@ function AuthorizedConsole() {
155
161
  }, []);
156
162
  return (
157
163
  <SidebarProvider>
158
- <AppSidebar />
164
+ <AppSidebar canSignOut={canSignOut} />
159
165
  <SidebarInset className="min-h-0 min-w-0 overflow-hidden">
160
166
  <Outlet />
161
167
  </SidebarInset>
@@ -6,11 +6,8 @@ export const Route = createFileRoute("/api/bundles/$bundleId/download")({
6
6
  GET: async ({ params, request }) => {
7
7
  const { prepareConfig } = await import("@/lib/server/config.server");
8
8
  const { downloadBundle } = await import("@/lib/server/downloadBundle");
9
- const { databaseClient, storagePlugin } = await prepareConfig(request);
10
- return downloadBundle(params.bundleId, {
11
- databaseClient,
12
- storagePlugin,
13
- });
9
+ const { core, storagePlugin } = await prepareConfig(request);
10
+ return downloadBundle(params.bundleId, { core, storagePlugin });
14
11
  },
15
12
  },
16
13
  },
@@ -1,12 +1,15 @@
1
1
  import { createFileRoute } from "@tanstack/react-router";
2
2
  import { KeyRound } from "lucide-react";
3
3
 
4
+ import { ConsoleFeatureUnavailable } from "@/components/ConsoleFeatureUnavailable";
4
5
  import { ApiKeysPage } from "@/components/features/api-keys/ApiKeysPage";
5
6
  import { SidebarTrigger } from "@/components/ui/sidebar";
6
- import { ensureApiKeyRouteAccess } from "@/lib/api-keys-api";
7
+ import { requireConsoleFeature } from "@/lib/console-features-api";
7
8
 
8
9
  export const Route = createFileRoute("/api-keys")({
9
- beforeLoad: ({ context }) => ensureApiKeyRouteAccess(context.queryClient),
10
+ beforeLoad: ({ context }) =>
11
+ requireConsoleFeature(context.queryClient, "apiKeys"),
12
+ notFoundComponent: ConsoleFeatureUnavailable,
10
13
  component: ApiKeysRoute,
11
14
  });
12
15