@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
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Hot Updater Console (v2)
1
+ # Hot Updater Console
2
2
 
3
3
  Modern web-based management console for Hot Updater built with **TanStack Start** and **shadcn/ui**.
4
4
 
@@ -6,11 +6,11 @@ Modern web-based management console for Hot Updater built with **TanStack Start*
6
6
 
7
7
  - **Bundle Management** - View, filter, and manage OTA update bundles
8
8
  - **Real-time Filtering** - Filter by platform (iOS/Android) and channel
9
- - **Bundle Editor** - Edit bundle configurations with intuitive forms
9
+ - **Release Editor** - Edit delivery policy, rollout, and target cohorts
10
10
  - **Rollout Control** - Adjust rollout percentage with visual slider
11
11
  - **Rollout Statistics** - View deployment metrics and success rates
12
- - **Channel Promotion** - Copy or move bundles between channels
13
- - **Emergency Rollback** - One-click disable and rollback
12
+ - **Channel Promotion** - Copy or move releases between channels
13
+ - **Rollback** - Disable a release and save; devices reconsider it on their next update check
14
14
  - **Dark Mode** - Full dark mode support with system preference detection
15
15
  - **Responsive Design** - Works seamlessly on desktop, tablet, and mobile
16
16
 
@@ -21,13 +21,11 @@ Modern web-based management console for Hot Updater built with **TanStack Start*
21
21
  - **TanStack Start** - Full-stack React framework with SSR
22
22
  - **TanStack Router** - File-based routing with type safety
23
23
  - **TanStack Query** - Data fetching and caching
24
- - **TanStack Form** - Form state management and validation
25
- - **TanStack Table** - Powerful table with sorting and pagination
26
24
  - **React 19** - Latest React features
27
25
 
28
26
  ### UI Components
29
27
 
30
- - **shadcn/ui** - High-quality accessible components (Radix Mira style)
28
+ - **shadcn/ui** - High-quality accessible components built on Base UI
31
29
  - **Tailwind CSS v4** - Utility-first CSS with oklch color system
32
30
  - **Lucide React** - Beautiful icon library
33
31
  - **Sonner** - Toast notifications
@@ -35,32 +33,38 @@ Modern web-based management console for Hot Updater built with **TanStack Start*
35
33
  ### Backend
36
34
 
37
35
  - **TanStack Start Server Functions** - Type-safe server endpoints
38
- - **Hot Updater Integration** - Storage and database plugin integration
36
+ - **Hot Updater Integration** - Storage and database adapter integration
39
37
 
40
38
  ## 📦 Installation
41
39
 
42
40
  ```bash
43
- # Install dependencies (from monorepo root)
41
+ # Install and build workspace dependencies (from monorepo root)
44
42
  pnpm install
43
+ pnpm -w build
45
44
  ```
46
45
 
47
46
  ## 🏃‍♂️ Development
48
47
 
49
48
  ```bash
50
- # Start development server
51
- pnpm dev
49
+ # Run these commands from the monorepo root
50
+ # Start the local development server
51
+ pnpm --dir packages/console dev
52
52
 
53
- # Build for production
54
- pnpm build
55
-
56
- # Preview production build
57
- pnpm preview
53
+ # Build and preview the local console package
54
+ pnpm --dir packages/console build
55
+ pnpm --dir packages/console preview
58
56
 
59
57
  # Type checking
60
- pnpm test:type
58
+ pnpm --dir packages/console test:type
61
59
  ```
62
60
 
63
- The console will be available at `http://localhost:3000`.
61
+ The development server is available at `http://localhost:3000`.
62
+
63
+ This package's Vite configuration uses the local console adapter, which grants
64
+ management access without sign-in in both development and built output. Keep
65
+ this build and preview local. For production hosting, follow the
66
+ [Console deployment guide](https://hot-updater.dev/docs/guides/console-deployment)
67
+ and configure its hosted template with a `ConsoleAuthAdapter`.
64
68
 
65
69
  ## 📁 Project Structure
66
70
 
@@ -68,26 +72,27 @@ The console will be available at `http://localhost:3000`.
68
72
  src/
69
73
  ├── routes/
70
74
  │ ├── __root.tsx # Root layout with providers
71
- │ ├── index.tsx # Bundle list page
72
- │ └── api/ # API routes (future)
75
+ │ ├── index.tsx # Bundle/release list page
76
+ │ ├── -releases-search.ts # URL filter validation
77
+ │ └── api/ # Authentication and bundle downloads
73
78
  ├── components/
74
79
  │ ├── ui/ # shadcn components
75
80
  │ ├── features/
76
- │ │ └── bundles/ # Bundle-related components
81
+ │ │ ├── bundles/ # Bundle metadata and rollout details
82
+ │ │ └── releases/ # Release policy editor
77
83
  │ ├── PlatformIcon.tsx # iOS/Android icons
78
84
  │ ├── BundleIdDisplay.tsx # Truncated bundle ID with tooltip
79
85
  │ ├── RolloutPercentageBadge.tsx
80
86
  │ ├── TimestampDisplay.tsx # UUIDv7 timestamp formatting
81
87
  │ ├── ChannelBadge.tsx
82
88
  │ └── EnabledStatusIcon.tsx
83
- ├── hooks/
84
- │ └── useFilterParams.ts # URL-based filter state
85
89
  ├── lib/
86
90
  │ ├── api.ts # React Query hooks
91
+ │ ├── api-rpc.ts # TanStack Start server functions
87
92
  │ ├── constants.ts # Shared constants
88
93
  │ ├── utils.ts # Utility functions
89
94
  │ └── server/
90
- │ ├── api.server.ts # Server functions
95
+ │ ├── auth.server.ts # Management access checks
91
96
  │ └── config.server.ts # Hot Updater config loader
92
97
  └── styles.css # Global styles & theme variables
93
98
  ```
@@ -96,64 +101,68 @@ src/
96
101
 
97
102
  ### Bundle List Page
98
103
 
99
- - **FilterToolbar** - Platform and channel filters with reset button
100
- - **BundlesTable** - Server-side paginated table (20 per page)
101
- - **BundleTableColumns** - Column definitions with custom cell renderers
104
+ - **BundlesPage** - Release-backed list with server-side pagination (20 per page)
105
+ - **BundleFilterToolbar** - Platform, channel, and enabled-state filters
106
+ - Both live in `src/routes/index.tsx`.
102
107
 
103
- ### Bundle Editor Sheet
108
+ ### Release Editor Sheet
104
109
 
105
- - **BundleEditorSheet** - Right-side slide-out panel
106
- - **BundleEditorForm** - TanStack Form with validation
110
+ - **ReleaseEditorSheet** - Slide-out editor for release policy, rollout, and promotion
107
111
  - **BundleMetadata** - Read-only bundle information display
112
+ - Saving preflights the policy, then updates it with the expected release revision.
113
+ - To roll back a release, turn off **Enabled** and select **Save changes**.
114
+ On the next update check, devices select a previous eligible release or the
115
+ built-in app when none remains; the app still controls applying the result.
108
116
 
109
117
  ### Dialogs
110
118
 
111
- - **PromoteChannelDialog** - Channel promotion with copy/move toggle
112
- - **DeleteBundleDialog** - Confirmation dialog with bundle details
113
- - **EmergencyRollbackButton** - One-click disable + 0% rollout
119
+ - **ReleaseEditorSheet** contains promotion and release-deletion dialogs.
120
+ - **RolloutCohortsDialog** displays rollout and targeted cohorts.
121
+ - **ChannelManagementDialog** creates and deletes channels.
114
122
 
115
123
  ## 🔌 API Integration
116
124
 
117
- The console integrates with the configured Hot Updater storage and database plugins through TanStack Start server functions:
118
-
119
- - `getConfig()` - Load console configuration
120
- - `getChannels()` - List available channels
121
- - `getBundles(filters)` - List bundles with pagination
122
- - `getBundle(bundleId)` - Get single bundle details
123
- - `updateBundle(bundleId, data)` - Update bundle configuration
124
- - `createBundle(bundle)` - Create new bundle
125
- - `deleteBundle(bundleId)` - Delete bundle
125
+ The console integrates with the configured Hot Updater storage and database
126
+ adapters through TanStack Start server functions in `src/lib/api-rpc.ts`.
127
+ Functions with input take a `{ data: input }` argument:
128
+
129
+ - `getConfig()` and `getChannels()` load configuration and channels.
130
+ - `getReleases({ data: { filter, limit, beforeReleaseId, afterReleaseId } })`
131
+ lists releases using the supported `ReleaseFilter` shapes.
132
+ - `getRelease({ data: { releaseId } })` reads a release.
133
+ - `preflightRelease({ data: { releaseId, expectedRevision, patch } })` validates
134
+ a `ReleasePolicyPatch`; `updateRelease` takes the same input to save it.
135
+ - `deleteRelease({ data: { releaseId, expectedRevision } })` deletes a release.
136
+ - `promoteRelease({ data: { releaseId, expectedRevision, targetChannel, action } })`
137
+ copies or moves a release; `action` is `"copy"` or `"move"`.
138
+ - `getBundles({ data: { platform, limit, after, before } })` lists bundle artifacts;
139
+ `getBundle({ data: { bundleId } })` reads one and
140
+ `deleteBundle({ data: { bundleId } })` deletes one.
141
+
142
+ Use one pagination cursor at a time.
126
143
 
127
144
  ## 🎯 Configuration
128
145
 
129
- Configure Hot Updater in `hot-updater.config.ts`:
146
+ The console reads `database`, `storage`, and `plugins` from
147
+ [hot-updater.config.ts](hot-updater.config.ts), as the CLI does, and runs the
148
+ plugins over the database as the server does. The checked-in
149
+ [demo database](demoDatabase.ts) seeds sample bundles, releases, and Insights
150
+ events through `assembleServer` from `@hot-updater/cli-tools`, over
151
+ `database.withoutLatency()`. For an empty local mock console, use this
152
+ `hot-updater.config.ts`:
130
153
 
131
154
  ```typescript
132
- import {
133
- createMockDatabaseData,
134
- mockDatabase,
135
- mockStorage,
136
- } from "@hot-updater/mock";
137
- import { bundleToRow, type Bundle } from "@hot-updater/plugin-core";
138
-
139
- const bundles: Bundle[] = [
140
- // ... your bundles
141
- ];
142
-
143
- const databaseData = createMockDatabaseData();
144
- for (const bundle of bundles) {
145
- databaseData.bundles.set(bundle.id, bundleToRow(bundle));
146
- }
155
+ import { mockDatabase, mockStorage } from "@hot-updater/mock";
147
156
 
148
157
  export default {
158
+ database: mockDatabase({ latency: { min: 500, max: 700 } }),
149
159
  storage: mockStorage({}),
150
- database: mockDatabase({
151
- latency: { min: 500, max: 700 },
152
- data: databaseData,
153
- }),
154
160
  };
155
161
  ```
156
162
 
163
+ Add `plugins`, such as `[insights(), apiKeys()]`, to show their pages. Mock
164
+ data is held in memory and resets when the configuration is reloaded.
165
+
157
166
  ## 🌈 Theming
158
167
 
159
168
  The console uses Tailwind CSS v4 with oklch color space for accessible colors. Theme variables are defined in `src/styles.css`:
@@ -167,23 +176,23 @@ The console uses Tailwind CSS v4 with oklch color space for accessible colors. T
167
176
  - Full TypeScript strict mode
168
177
  - Type-safe server functions with TanStack Start
169
178
  - Type-safe routing with TanStack Router
170
- - Type-safe forms with TanStack Form
179
+ - Typed release-policy drafts and revision-aware mutations
171
180
 
172
181
  ## 📊 Data Flow
173
182
 
174
- 1. **URL State** → `useFilterParams()` hook manages filter state in URL
175
- 2. **Server Functions** → TanStack Start server functions call the configured storage and database plugins
176
- 3. **React Query** → `useBundlesQuery()` fetches and caches data
183
+ 1. **URL State** → `Route.useSearch()` and `-releases-search.ts` manage release filters
184
+ 2. **Server Functions** → TanStack Start server functions call the configured storage and database adapters
185
+ 3. **React Query** → `useReleasesQuery()` and `useBundleQuery()` fetch and cache data
177
186
  4. **UI Components** → Display data with shadcn components
178
- 5. **Mutations** → `useUpdateBundleMutation()` updates data with optimistic updates
179
- 6. **Cache Invalidation** → React Query automatically refreshes affected queries
187
+ 5. **Mutations** → `usePreflightReleaseMutation()` validates changes before `useUpdateReleaseMutation()` saves them
188
+ 6. **Cache Invalidation** → Successful updates invalidate release, catalog, and channel queries
180
189
 
181
190
  ## 🚦 Development Guidelines
182
191
 
183
192
  - **Server-only code** must use `.server.ts` extension
184
193
  - **Client-side constants** live in `src/lib/constants.ts`
185
194
  - **shadcn components** are customizable in `src/components/ui/`
186
- - **Form validation** uses TanStack Form validators
195
+ - **Release validation** checks the draft locally and preflights changes on the server
187
196
  - **Toast notifications** use Sonner for success/error feedback
188
197
 
189
198
  ## 🐛 Troubleshooting
@@ -196,12 +205,13 @@ The console uses Tailwind CSS v4 with oklch color space for accessible colors. T
196
205
  ### Development Server
197
206
 
198
207
  - Default port is 3000
199
- - Change port: `pnpm dev --port 3001`
208
+ - Change port: `pnpm --dir packages/console dev --port 3001`
200
209
 
201
210
  ### Hot Updater Config
202
211
 
203
- - Ensure `hot-updater.config.ts` is at package root
204
- - Verify the storage and database plugins are correctly initialized
212
+ - Ensure `hot-updater.config.ts` is at the package root and sets `database`
213
+ and `storage`
214
+ - Verify its storage and database adapters are correctly initialized
205
215
 
206
216
  ## 📝 License
207
217
 
package/lib/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { ConfigInput, DatabasePlugin } from "@hot-updater/plugin-core";
1
+ import { AnyHotUpdaterPlugin, ConfiguredDatabase, StorageAdapter } from "@hot-updater/plugin-core";
2
2
  //#region src/index.d.ts
3
3
  type ConsoleAuthProvider = "google" | "github";
4
4
  type ConsolePrincipal = Readonly<{
@@ -20,17 +20,29 @@ type ConsoleAuthAdapter = Readonly<{
20
20
  getAccess(request: Request): Promise<ConsoleAccess>;
21
21
  getProviders(request: Request): Promise<readonly ConsoleAuthProvider[]>;
22
22
  }>;
23
- type ConsoleSigningConfig = Readonly<{
24
- enabled: boolean;
25
- provider?: string;
26
- }>;
27
- type HotUpdaterConsoleConfig = Readonly<Omit<Pick<ConfigInput, "console" | "database" | "signing" | "storage">, "database"> & {
28
- database: DatabasePlugin;
29
- }>;
30
- type ResolvedHotUpdaterConsoleConfig = Readonly<Omit<HotUpdaterConsoleConfig, "signing"> & {
31
- signing?: ConsoleSigningConfig;
23
+ type HotUpdaterConsoleConfig = Readonly<{
24
+ /**
25
+ * The database your server runs on, such as `dynamoDB(...)`, or
26
+ * `standaloneRepository(...)`, which reaches a self-hosted server through
27
+ * its admin API.
28
+ */
29
+ database: ConfiguredDatabase;
30
+ /** The storage your server lists; the console reads bundle files with it. */
31
+ storage: StorageAdapter;
32
+ /**
33
+ * The plugins your server runs, such as the `plugins` a managed provider
34
+ * package exports or `[insights(), apiKeys()]`. The console runs them over
35
+ * `database` as the server does, and shows only the built-in features of
36
+ * the plugins listed. With `standaloneRepository`, they run on the server,
37
+ * and the console shows the features its admin API serves.
38
+ */
39
+ plugins?: readonly AnyHotUpdaterPlugin[];
40
+ console?: {
41
+ /** The Git repository whose commits the console links bundles to. */
42
+ gitUrl?: string;
43
+ };
32
44
  }>;
33
45
  type HotUpdaterConsoleConfigSource = HotUpdaterConsoleConfig | ((request: Request) => HotUpdaterConsoleConfig | Promise<HotUpdaterConsoleConfig>);
34
46
  declare const defineConsoleConfig: <const TConfig extends HotUpdaterConsoleConfigSource>(config: TConfig) => TConfig;
35
47
  //#endregion
36
- export { ConsoleAccess, ConsoleAuthAdapter, ConsoleAuthProvider, ConsolePrincipal, ConsoleSigningConfig, HotUpdaterConsoleConfig, HotUpdaterConsoleConfigSource, ResolvedHotUpdaterConsoleConfig, defineConsoleConfig };
48
+ export { ConsoleAccess, ConsoleAuthAdapter, ConsoleAuthProvider, ConsolePrincipal, HotUpdaterConsoleConfig, HotUpdaterConsoleConfigSource, defineConsoleConfig };
@@ -38,7 +38,7 @@ const createConsoleModulesPlugin = (mode) => {
38
38
  const cacheDirectory = path.join(root, ".hot-updater/console");
39
39
  generatedRouteTreeFile = path.join(cacheDirectory, "routeTree.gen.ts");
40
40
  authFile = resolveFromRoot(root, mode.options.auth ?? "console.auth.ts");
41
- configFile = resolveFromRoot(root, mode.options.config ?? "hot-updater.config.ts");
41
+ configFile = resolveFromRoot(root, mode.options.config ?? "console.config.ts");
42
42
  writeFileIfChanged(path.join(cacheDirectory, "router.ts"), [`export { getRouter } from ${JSON.stringify(virtualPackageRouterModuleId)};`, ""].join("\n"));
43
43
  }
44
44
  return {
@@ -72,11 +72,21 @@ const createConsoleModulesPlugin = (mode) => {
72
72
  "import { loadConfig } from \"@hot-updater/cli-tools\";",
73
73
  "export default async () => {",
74
74
  " const config = await loadConfig(null);",
75
+ " if (config.database === undefined) {",
76
+ " throw new Error(",
77
+ " \"Set database in hot-updater.config.ts: the database your server runs on, such as d1Database(...), or standaloneRepository({ baseUrl }) to reach a self-hosted server through its admin API.\",",
78
+ " );",
79
+ " }",
80
+ " if (config.storage === undefined) {",
81
+ " throw new Error(",
82
+ " \"Set storage in hot-updater.config.ts: where your server stores bundles, which the console reads, such as r2Storage(...).\",",
83
+ " );",
84
+ " }",
75
85
  " return {",
76
- " console: { gitUrl: config.console.gitUrl },",
77
86
  " database: config.database,",
78
- " signing: config.signing,",
79
87
  " storage: config.storage,",
88
+ " plugins: config.plugins,",
89
+ " console: { gitUrl: config.console.gitUrl },",
80
90
  " };",
81
91
  "};"
82
92
  ].join("\n");
package/lib/vite.d.mts CHANGED
@@ -1,7 +1,9 @@
1
1
  import { PluginOption } from "vite";
2
2
  //#region src/vite.d.ts
3
3
  interface HotUpdaterConsolePluginOptions {
4
+ /** The console's auth module, `console.auth.ts` by default. */
4
5
  readonly auth?: string;
6
+ /** The module whose default export is `defineConsoleConfig(...)`, `console.config.ts` by default. */
5
7
  readonly config?: string;
6
8
  }
7
9
  declare const hotUpdaterConsole: (options?: HotUpdaterConsolePluginOptions) => PluginOption[];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/console",
3
3
  "type": "module",
4
- "version": "1.0.0-rc.2",
4
+ "version": "1.0.0-rc.21",
5
5
  "files": [
6
6
  ".output",
7
7
  "lib",
@@ -38,11 +38,11 @@
38
38
  "@fontsource-variable/inter": "^5.2.8",
39
39
  "@tailwindcss/vite": "^4.2.1",
40
40
  "@tanstack/react-devtools": "^0.10.0",
41
- "@tanstack/react-query": "^5.90.21",
42
- "@tanstack/react-router": "^1.167.0",
43
- "@tanstack/react-router-devtools": "^1.166.7",
44
- "@tanstack/react-router-ssr-query": "^1.166.7",
45
- "@tanstack/react-start": "^1.166.11",
41
+ "@tanstack/react-query": "^5.104.0",
42
+ "@tanstack/react-router": "^1.170.41",
43
+ "@tanstack/react-router-devtools": "^1.167.2",
44
+ "@tanstack/react-router-ssr-query": "^1.167.3",
45
+ "@tanstack/react-start": "^1.168.60",
46
46
  "@vitejs/plugin-react": "^6.0.1",
47
47
  "class-variance-authority": "^0.7.1",
48
48
  "clsx": "^2.1.1",
@@ -58,16 +58,16 @@
58
58
  "tailwindcss": "^4.2.1",
59
59
  "tw-animate-css": "^1.4.0",
60
60
  "verkit": "0.4.0",
61
- "@hot-updater/bsdiff": "1.0.0-rc.0",
62
- "@hot-updater/server": "1.0.0-rc.2",
63
- "@hot-updater/plugin-core": "1.0.0-rc.2",
64
- "@hot-updater/core": "1.0.0-rc.0"
61
+ "@hot-updater/bsdiff": "1.0.0-rc.21",
62
+ "@hot-updater/protocol": "1.0.0-rc.21",
63
+ "@hot-updater/server": "1.0.0-rc.21",
64
+ "@hot-updater/plugin-core": "1.0.0-rc.21"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@tanstack/devtools-vite": "^0.6.0",
68
68
  "@tanstack/react-form": "^1.28.5",
69
69
  "@tanstack/react-table": "^8.21.3",
70
- "@tanstack/router-plugin": "^1.166.9",
70
+ "@tanstack/router-plugin": "^1.168.42",
71
71
  "@testing-library/dom": "^10.4.0",
72
72
  "@testing-library/react": "^16.3.2",
73
73
  "@types/node": "^25.5.0",
@@ -86,11 +86,11 @@
86
86
  "vite-tsconfig-paths": "^6.1.1",
87
87
  "vitest": "4.1.4",
88
88
  "web-vitals": "^5.1.0",
89
- "@hot-updater/cli-tools": "1.0.0-rc.2",
90
- "@hot-updater/mock": "1.0.0-rc.2"
89
+ "@hot-updater/cli-tools": "1.0.0-rc.21",
90
+ "@hot-updater/mock": "1.0.0-rc.21"
91
91
  },
92
92
  "description": "React Native OTA solution for self-hosted",
93
- "license": "MIT",
93
+ "license": "SEE LICENSE IN LICENSE",
94
94
  "repository": {
95
95
  "type": "git",
96
96
  "url": "https://github.com/gronxb/hot-updater"
@@ -1,13 +1,9 @@
1
1
  import { Link, useRouterState } from "@tanstack/react-router";
2
- import {
3
- ChartNoAxesCombined,
4
- KeyRound,
5
- Moon,
6
- Package,
7
- ShieldCheck,
8
- Sun,
9
- } from "lucide-react";
2
+ import { LogOut, Moon, Package, Sun } from "lucide-react";
3
+ import { type ReactElement, type ReactNode, useState } from "react";
4
+ import { toast } from "sonner";
10
5
 
6
+ import { useFeatureUi } from "@/components/features/FeatureSlots";
11
7
  import { HotUpdaterLogo } from "@/components/HotUpdaterLogo";
12
8
  import { useTheme } from "@/components/ThemeProvider";
13
9
  import {
@@ -21,26 +17,84 @@ import {
21
17
  SidebarMenu,
22
18
  SidebarMenuButton,
23
19
  SidebarMenuItem,
20
+ useSidebar,
24
21
  } from "@/components/ui/sidebar";
25
- import { useApiKeyCapabilityQuery } from "@/lib/api-keys-api";
26
22
 
27
- export function AppSidebar() {
28
- const apiKeyCapability = useApiKeyCapabilityQuery();
23
+ type NavigationItem = {
24
+ readonly label: string;
25
+ readonly icon: ReactNode;
26
+ readonly active: boolean;
27
+ readonly link: ReactElement;
28
+ };
29
+
30
+ export function AppSidebar({ canSignOut = false }: { canSignOut?: boolean }) {
31
+ const { setOpenMobile } = useSidebar();
32
+ const closeMobileSidebar = () => setOpenMobile(false);
33
+ const [signingOut, setSigningOut] = useState(false);
34
+ const featureUi = useFeatureUi();
29
35
  const { theme, setTheme } = useTheme();
30
36
  const routerState = useRouterState();
31
37
  const currentPath = routerState.location.pathname;
32
38
 
33
- const isBundlesActive = currentPath === "/";
34
- const isInsightsActive =
35
- currentPath === "/insights" || currentPath === "/installations";
36
- const isApiKeysActive = currentPath === "/api-keys";
37
- const isSigningActive = currentPath === "/signing";
39
+ const navigation: readonly NavigationItem[] = [
40
+ {
41
+ label: "Bundles",
42
+ icon: <Package />,
43
+ active: currentPath === "/",
44
+ link: (
45
+ <Link
46
+ to="/"
47
+ onClick={closeMobileSidebar}
48
+ search={{
49
+ afterReleaseId: undefined,
50
+ beforeReleaseId: undefined,
51
+ channelId: undefined,
52
+ enabled: undefined,
53
+ releaseId: undefined,
54
+ platform: undefined,
55
+ bundleId: undefined,
56
+ scopeKey: undefined,
57
+ }}
58
+ />
59
+ ),
60
+ },
61
+ // Each feature that is on adds its item, as the feature registry lists it.
62
+ ...featureUi.flatMap(({ features, label, navigation }) =>
63
+ navigation === undefined
64
+ ? []
65
+ : [
66
+ {
67
+ label,
68
+ icon: navigation.icon,
69
+ active: navigation.paths.includes(currentPath),
70
+ link: navigation.link({ features, onClick: closeMobileSidebar }),
71
+ },
72
+ ],
73
+ ),
74
+ ];
75
+
76
+ const signOut = async () => {
77
+ setSigningOut(true);
78
+ try {
79
+ const response = await fetch("/api/auth/sign-out", {
80
+ body: "{}",
81
+ headers: { "content-type": "application/json" },
82
+ method: "POST",
83
+ });
84
+ if (!response.ok) throw new Error("Sign-out failed. Please try again.");
85
+ window.location.reload();
86
+ } catch {
87
+ setSigningOut(false);
88
+ toast.error("Sign-out failed. Please try again.");
89
+ }
90
+ };
38
91
 
39
92
  return (
40
93
  <Sidebar collapsible="icon">
41
94
  <SidebarHeader className="h-12 justify-center">
42
95
  <Link
43
96
  to="/"
97
+ onClick={closeMobileSidebar}
44
98
  search={{
45
99
  afterReleaseId: undefined,
46
100
  beforeReleaseId: undefined,
@@ -48,9 +102,8 @@ export function AppSidebar() {
48
102
  enabled: undefined,
49
103
  releaseId: undefined,
50
104
  platform: undefined,
51
- targetAppVersion: undefined,
52
105
  bundleId: undefined,
53
- page: undefined,
106
+ scopeKey: undefined,
54
107
  }}
55
108
  className="flex items-center gap-3 p-1 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:p-2"
56
109
  >
@@ -71,63 +124,18 @@ export function AppSidebar() {
71
124
  <SidebarGroupLabel>Navigation</SidebarGroupLabel>
72
125
  <SidebarGroupContent>
73
126
  <SidebarMenu>
74
- <SidebarMenuItem>
75
- <SidebarMenuButton
76
- isActive={isBundlesActive}
77
- render={
78
- <Link
79
- to="/"
80
- search={{
81
- afterReleaseId: undefined,
82
- beforeReleaseId: undefined,
83
- channelId: undefined,
84
- enabled: undefined,
85
- releaseId: undefined,
86
- platform: undefined,
87
- targetAppVersion: undefined,
88
- bundleId: undefined,
89
- page: undefined,
90
- }}
91
- />
92
- }
93
- tooltip="Bundles"
94
- >
95
- <Package />
96
- <span>Bundles</span>
97
- </SidebarMenuButton>
98
- </SidebarMenuItem>
99
- <SidebarMenuItem>
100
- <SidebarMenuButton
101
- isActive={isInsightsActive}
102
- render={<Link to="/insights" />}
103
- tooltip="Insights"
104
- >
105
- <ChartNoAxesCombined />
106
- <span>Insights</span>
107
- </SidebarMenuButton>
108
- </SidebarMenuItem>
109
- {apiKeyCapability.data?.apiKeys ? (
110
- <SidebarMenuItem>
127
+ {navigation.map((item) => (
128
+ <SidebarMenuItem key={item.label}>
111
129
  <SidebarMenuButton
112
- isActive={isApiKeysActive}
113
- render={<Link to="/api-keys" />}
114
- tooltip="API keys"
130
+ isActive={item.active}
131
+ render={item.link}
132
+ tooltip={item.label}
115
133
  >
116
- <KeyRound />
117
- <span>API keys</span>
134
+ {item.icon}
135
+ <span>{item.label}</span>
118
136
  </SidebarMenuButton>
119
137
  </SidebarMenuItem>
120
- ) : null}
121
- <SidebarMenuItem>
122
- <SidebarMenuButton
123
- isActive={isSigningActive}
124
- render={<Link to="/signing" />}
125
- tooltip="Bundle signing"
126
- >
127
- <ShieldCheck />
128
- <span>Bundle signing</span>
129
- </SidebarMenuButton>
130
- </SidebarMenuItem>
138
+ ))}
131
139
  </SidebarMenu>
132
140
  </SidebarGroupContent>
133
141
  </SidebarGroup>
@@ -135,6 +143,18 @@ export function AppSidebar() {
135
143
 
136
144
  <SidebarFooter>
137
145
  <SidebarMenu>
146
+ {canSignOut ? (
147
+ <SidebarMenuItem>
148
+ <SidebarMenuButton
149
+ disabled={signingOut}
150
+ onClick={() => void signOut()}
151
+ tooltip="Sign out"
152
+ >
153
+ <LogOut />
154
+ <span>{signingOut ? "Signing out…" : "Sign out"}</span>
155
+ </SidebarMenuButton>
156
+ </SidebarMenuItem>
157
+ ) : null}
138
158
  <SidebarMenuItem>
139
159
  <SidebarMenuButton
140
160
  onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
@@ -149,6 +169,15 @@ export function AppSidebar() {
149
169
  </SidebarMenuButton>
150
170
  </SidebarMenuItem>
151
171
  </SidebarMenu>
172
+ {/* The Console's license asks a hosted service to keep this notice. */}
173
+ <a
174
+ href="https://hot-updater.dev"
175
+ target="_blank"
176
+ rel="noreferrer"
177
+ className="px-2 pb-1 text-[11px] text-sidebar-foreground/60 hover:text-sidebar-foreground group-data-[collapsible=icon]:hidden"
178
+ >
179
+ Powered by hot-updater
180
+ </a>
152
181
  </SidebarFooter>
153
182
  </Sidebar>
154
183
  );