@hot-updater/console 0.36.7 → 1.0.0-rc.0

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 (266) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/AnalyticsCapabilityContext-BCke57-7.js +1 -0
  3. package/.output/public/assets/BundleActivityChart-CnsBe_1J.js +42 -0
  4. package/.output/public/assets/BundleIdDisplay-Dky75zLU.js +1 -0
  5. package/.output/public/assets/alert-C-1KvDW8.js +1 -0
  6. package/.output/public/assets/analytics-Bqz0c-BT.js +1 -0
  7. package/.output/public/assets/analytics-api-CJdrTLLO.js +1 -0
  8. package/.output/public/assets/api-B_Rh-uzC.js +1 -0
  9. package/.output/public/assets/api-keys-BurCFhzH.js +1 -0
  10. package/.output/public/assets/api-keys-api-BEDJdQg3.js +1 -0
  11. package/.output/public/assets/badge-Bf0fZ-Gx.js +1 -0
  12. package/.output/public/assets/chart-no-axes-combined-FaYMGmQS.js +1 -0
  13. package/.output/public/assets/createServerFn-gM2-LFBy.js +3 -0
  14. package/.output/public/assets/dialog-BS60BRr2.js +1 -0
  15. package/.output/public/assets/dist-DspNKfMj.js +1 -0
  16. package/.output/public/assets/download-O-yNBc9H.js +1 -0
  17. package/.output/public/assets/field-Buq148-A.js +1 -0
  18. package/.output/public/assets/index-kFhd2S78.js +10 -0
  19. package/.output/public/assets/installations-BrQ-od41.js +2 -0
  20. package/.output/public/assets/installations-CGlZ3ROz.js +1 -0
  21. package/.output/public/assets/lazyRouteComponent-rPzgA7S-.js +1 -0
  22. package/.output/public/assets/loader-circle-DCYF-wJl.js +1 -0
  23. package/.output/public/assets/preload-helper-TKOBYGYD.js +1 -0
  24. package/.output/public/assets/refresh-cw-n8XYbPPj.js +1 -0
  25. package/.output/public/assets/routes-CrdokJ80.js +1 -0
  26. package/.output/public/assets/routes-DcQnVajO.js +2 -0
  27. package/.output/public/assets/search-DLhbWw3W.js +1 -0
  28. package/.output/public/assets/shield-check-DE_hVuHu.js +1 -0
  29. package/.output/public/assets/signing-HRexfjsH.js +1 -0
  30. package/.output/public/assets/styles-eB4EtvFS.css +2 -0
  31. package/.output/public/assets/table-D8CLieHX.js +1 -0
  32. package/.output/public/assets/textarea-YJLTVAoq.js +1 -0
  33. package/.output/public/assets/useMutation-JsGVjQMY.js +1 -0
  34. package/.output/public/assets/useOpenInteractionType-4cRD5xO0.js +1 -0
  35. package/.output/server/__23tanstack-start-server-fn-resolver-DBP9TQ7K.mjs +146 -0
  36. package/.output/server/_libs/@base-ui/react+[...].mjs +16124 -0
  37. package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +1554 -0
  38. package/.output/server/_libs/@tanstack/react-router+[...].mjs +41 -10
  39. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +132 -0
  40. package/.output/server/_libs/d3-array.mjs +231 -0
  41. package/.output/server/_libs/d3-color.mjs +330 -0
  42. package/.output/server/_libs/d3-format.mjs +252 -0
  43. package/.output/server/_libs/d3-interpolate.mjs +207 -0
  44. package/.output/server/_libs/d3-path.mjs +77 -0
  45. package/.output/server/_libs/d3-scale+[...].mjs +1907 -0
  46. package/.output/server/_libs/d3-shape.mjs +818 -0
  47. package/.output/server/_libs/decimal.js-light.mjs +884 -0
  48. package/.output/server/_libs/es-toolkit.mjs +1134 -0
  49. package/.output/server/_libs/eventemitter3.mjs +236 -0
  50. package/.output/server/_libs/immer.mjs +532 -0
  51. package/.output/server/_libs/lucide-react.mjs +141 -13
  52. package/.output/server/_libs/next-themes.mjs +2 -2
  53. package/.output/server/_libs/react-redux+[...].mjs +205 -0
  54. package/.output/server/_libs/recharts+[...].mjs +14117 -0
  55. package/.output/server/_libs/sonner.mjs +2 -2
  56. package/.output/server/_libs/tanstack__query-core.mjs +114 -1
  57. package/.output/server/_libs/tanstack__react-query.mjs +3 -4
  58. package/.output/server/_runtime.mjs +10 -1
  59. package/.output/server/_ssr/AnalyticsCapabilityContext-ui4DX8GO.mjs +15 -0
  60. package/.output/server/_ssr/BundleActivityChart-BiB9ZORH.mjs +288 -0
  61. package/.output/server/_ssr/BundleIdDisplay-Czk6iR6p.mjs +13 -0
  62. package/.output/server/_ssr/alert-Dim3MGY5.mjs +86 -0
  63. package/.output/server/_ssr/analytics-DtMTn8fn.mjs +858 -0
  64. package/.output/server/_ssr/analytics-api-Cg1M5v_H.mjs +71 -0
  65. package/.output/server/_ssr/analytics-input-fRRRn0uA.mjs +96 -0
  66. package/.output/server/_ssr/analytics-rpc-DY5uXvHT.mjs +155 -0
  67. package/.output/server/_ssr/api-BTSv1tZb.mjs +282 -0
  68. package/.output/server/_ssr/api-keys-BnM_o2nS.mjs +300 -0
  69. package/.output/server/_ssr/api-keys-api-CIywsSF5.mjs +60 -0
  70. package/.output/server/_ssr/api-keys-rpc-BSp1wkIV.mjs +136 -0
  71. package/.output/server/_ssr/api-rpc-cG5wd_tO.mjs +1468 -0
  72. package/.output/server/_ssr/auth-BF8mbAgF.mjs +21 -0
  73. package/.output/server/_ssr/auth-rpc-BrJ6obHJ.mjs +24 -0
  74. package/.output/server/_ssr/auth.server-DwKoZoQM.mjs +50 -0
  75. package/.output/server/_ssr/badge-DCxbEeof.mjs +26 -0
  76. package/.output/server/_ssr/bundle-signing-rpc-COoQhW8E.mjs +16 -0
  77. package/.output/server/_ssr/bundle-signing.server-D5u8KkMp.mjs +51 -0
  78. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +13 -0
  79. package/.output/server/_ssr/config.server-DEiLhKmy.mjs +361 -0
  80. package/.output/server/_ssr/{createServerFn-DzU0k62V.mjs → createServerFn-B2hcXoPM.mjs} +1 -1
  81. package/.output/server/_ssr/createServerRpc-DB29C_5p.mjs +12 -0
  82. package/.output/server/_ssr/{sidebar-Cv_H0_Jv.mjs → createSsrRpc-9P0S0lJ3.mjs} +68 -50
  83. package/.output/server/_ssr/databaseRows-CwbNwYSl.mjs +166 -0
  84. package/.output/server/_ssr/deleteBundle-Cu_XOMt0.mjs +66 -0
  85. package/.output/server/_ssr/dialog-CQuRMNZ4.mjs +170 -0
  86. package/.output/server/_ssr/dist-BVSLTpoP.mjs +250 -0
  87. package/.output/server/_ssr/downloadBundle-CNnYxYO2.mjs +24 -0
  88. package/.output/server/_ssr/{empty-plugin-adapters-B4HCzgmM.mjs → empty-plugin-adapters-B5QvGpEl.mjs} +1 -1
  89. package/.output/server/_ssr/field-DRIXKjZG.mjs +74 -0
  90. package/.output/server/_ssr/{getBundleChildren-CVtSiH5t.mjs → getBundleChildren-BcRMFoDF.mjs} +10 -18
  91. package/.output/server/_ssr/installations-B-azgAvX.mjs +19 -0
  92. package/.output/server/_ssr/installations-Ctvzp2JH.mjs +535 -0
  93. package/.output/server/_ssr/router-C-KUBfOi.mjs +513 -0
  94. package/.output/server/_ssr/routes-BM4ht9jb.mjs +2286 -0
  95. package/.output/server/_ssr/routes-Z7TwC6nC.mjs +37 -0
  96. package/.output/server/_ssr/runtime.server-awTLiiXW.mjs +527 -0
  97. package/.output/server/_ssr/server-BjzmvXnO.mjs +7468 -0
  98. package/.output/server/_ssr/signing-k8GxyBDF.mjs +202 -0
  99. package/.output/server/_ssr/ssr.mjs +4 -5740
  100. package/.output/server/_ssr/start-DpViQGnt.mjs +4 -0
  101. package/.output/server/_ssr/table-v1yCvYo7.mjs +53 -0
  102. package/.output/server/_ssr/textarea-DYHnskhb.mjs +14 -0
  103. package/.output/server/_ssr/uuidv7-BbJ-9D_I.mjs +31 -0
  104. package/.output/server/_tanstack-start-manifest_v-CH4StzJu.mjs +108 -0
  105. package/.output/server/index.mjs +256 -53
  106. package/README.md +29 -8
  107. package/lib/index.d.mts +36 -0
  108. package/lib/index.mjs +4 -0
  109. package/lib/vite-internal.mjs +127 -0
  110. package/lib/vite.d.mts +10 -0
  111. package/lib/vite.mjs +5 -0
  112. package/package.json +51 -29
  113. package/public/apple-touch-icon.png +0 -0
  114. package/public/favicon-16x16.png +0 -0
  115. package/public/favicon-32x32.png +0 -0
  116. package/public/favicon.ico +0 -0
  117. package/public/logo.svg +1 -0
  118. package/public/manifest.json +30 -0
  119. package/public/robots.txt +3 -0
  120. package/src/components/AppSidebar.tsx +159 -0
  121. package/src/components/BundleIdDisplay.tsx +18 -0
  122. package/src/components/ChannelBadge.tsx +31 -0
  123. package/src/components/ConsoleAccessPage.tsx +122 -0
  124. package/src/components/EnabledStatusIcon.tsx +54 -0
  125. package/src/components/HashValueDisplay.tsx +85 -0
  126. package/src/components/HotUpdaterLogo.tsx +30 -0
  127. package/src/components/NotFoundPage.tsx +33 -0
  128. package/src/components/PlatformIcon.tsx +39 -0
  129. package/src/components/RolloutPercentageBadge.tsx +28 -0
  130. package/src/components/ThemeProvider.tsx +116 -0
  131. package/src/components/TimestampDisplay.tsx +41 -0
  132. package/src/components/features/analytics/ActivityChart.tsx +150 -0
  133. package/src/components/features/analytics/AnalyticsCapabilityContext.tsx +24 -0
  134. package/src/components/features/analytics/AnalyticsControls.tsx +115 -0
  135. package/src/components/features/analytics/AnalyticsErrorAlert.tsx +21 -0
  136. package/src/components/features/analytics/AnalyticsOverview.tsx +208 -0
  137. package/src/components/features/analytics/BundleDistribution.tsx +133 -0
  138. package/src/components/features/analytics/BundleSelector.tsx +114 -0
  139. package/src/components/features/analytics/InstallationHistoryCard.tsx +248 -0
  140. package/src/components/features/analytics/InstallationMatchesCard.tsx +132 -0
  141. package/src/components/features/analytics/InstallationPageHeader.tsx +138 -0
  142. package/src/components/features/analytics/InstallationPagination.tsx +48 -0
  143. package/src/components/features/analytics/InstallationSearch.tsx +230 -0
  144. package/src/components/features/analytics/UpdateOutcomes.tsx +147 -0
  145. package/src/components/features/api-keys/ApiKeysPage.tsx +377 -0
  146. package/src/components/features/bundles/BundleActivityChart.tsx +193 -0
  147. package/src/components/features/bundles/BundleAnalyticsSummary.tsx +123 -0
  148. package/src/components/features/bundles/BundleChildrenPanel.tsx +201 -0
  149. package/src/components/features/bundles/BundleMetadata.tsx +136 -0
  150. package/src/components/features/bundles/RolloutCohortsDialog.tsx +201 -0
  151. package/src/components/features/channels/ChannelManagementDialog.tsx +228 -0
  152. package/src/components/features/releases/ReleaseEditorSheet.tsx +879 -0
  153. package/src/components/features/releases/ReleaseStateBadge.tsx +20 -0
  154. package/src/components/features/signing/BundleSigningPage.tsx +247 -0
  155. package/src/components/ui/alert-dialog.tsx +185 -0
  156. package/src/components/ui/alert.tsx +76 -0
  157. package/src/components/ui/badge.tsx +49 -0
  158. package/src/components/ui/button.tsx +58 -0
  159. package/src/components/ui/card.tsx +89 -0
  160. package/src/components/ui/chart.tsx +371 -0
  161. package/src/components/ui/combobox.tsx +303 -0
  162. package/src/components/ui/dialog.tsx +156 -0
  163. package/src/components/ui/dropdown-menu.tsx +270 -0
  164. package/src/components/ui/field.tsx +235 -0
  165. package/src/components/ui/input-group.tsx +147 -0
  166. package/src/components/ui/input.tsx +19 -0
  167. package/src/components/ui/label.tsx +25 -0
  168. package/src/components/ui/progress.tsx +84 -0
  169. package/src/components/ui/select.tsx +205 -0
  170. package/src/components/ui/separator.tsx +23 -0
  171. package/src/components/ui/sheet.tsx +132 -0
  172. package/src/components/ui/sidebar.tsx +779 -0
  173. package/src/components/ui/skeleton.tsx +13 -0
  174. package/src/components/ui/slider.tsx +56 -0
  175. package/src/components/ui/sonner.tsx +45 -0
  176. package/src/components/ui/switch.tsx +25 -0
  177. package/src/components/ui/table.tsx +114 -0
  178. package/src/components/ui/textarea.tsx +18 -0
  179. package/src/components/ui/toggle-group.tsx +89 -0
  180. package/src/components/ui/toggle.tsx +43 -0
  181. package/src/components/ui/tooltip.tsx +66 -0
  182. package/src/hooks/use-mobile.ts +30 -0
  183. package/src/index.ts +48 -0
  184. package/src/lib/analytics-api.ts +145 -0
  185. package/src/lib/analytics-error.ts +19 -0
  186. package/src/lib/analytics-input.ts +187 -0
  187. package/src/lib/analytics-overview.ts +170 -0
  188. package/src/lib/analytics-rpc.ts +218 -0
  189. package/src/lib/api-keys-api.ts +69 -0
  190. package/src/lib/api-keys-rpc.ts +87 -0
  191. package/src/lib/api-rpc.ts +478 -0
  192. package/src/lib/api.ts +444 -0
  193. package/src/lib/auth-rpc.ts +16 -0
  194. package/src/lib/bundle-signing-api.ts +14 -0
  195. package/src/lib/bundle-signing-rpc.ts +16 -0
  196. package/src/lib/constants.ts +1 -0
  197. package/src/lib/git.ts +51 -0
  198. package/src/lib/server/auth.server.ts +43 -0
  199. package/src/lib/server/bundle-signing.server.ts +100 -0
  200. package/src/lib/server/config.server.ts +109 -0
  201. package/src/lib/server/console-runtime.server.ts +70 -0
  202. package/src/lib/server/deleteBundle.ts +145 -0
  203. package/src/lib/server/downloadBundle.ts +46 -0
  204. package/src/lib/server/getBundleChildren.ts +130 -0
  205. package/src/lib/server/listReleases.ts +61 -0
  206. package/src/lib/server/releaseActivity.ts +27 -0
  207. package/src/lib/server/releaseReachability.ts +95 -0
  208. package/src/lib/server/runtime.server.ts +172 -0
  209. package/src/lib/utils.ts +9 -0
  210. package/src/logo.svg +1 -0
  211. package/src/routeTree.gen.ts +199 -0
  212. package/src/router.tsx +21 -0
  213. package/src/routes/-installations-search.ts +19 -0
  214. package/src/routes/-releases-search.ts +74 -0
  215. package/src/routes/__root.tsx +174 -0
  216. package/src/routes/analytics.tsx +139 -0
  217. package/src/routes/api/auth/$.ts +19 -0
  218. package/src/routes/api/bundles/$bundleId/download.ts +17 -0
  219. package/src/routes/api-keys.tsx +37 -0
  220. package/src/routes/index.tsx +982 -0
  221. package/src/routes/installations.tsx +194 -0
  222. package/src/routes/signing.tsx +37 -0
  223. package/src/styles.css +152 -0
  224. package/src/virtual-modules.d.ts +21 -0
  225. package/src/vite-env.d.ts +1 -0
  226. package/src/vite-internal.ts +195 -0
  227. package/src/vite.ts +12 -0
  228. package/.output/public/assets/dist-DJX53faP.js +0 -3
  229. package/.output/public/assets/index-Iylis5kY.js +0 -10
  230. package/.output/public/assets/routes-HT21hs0C.js +0 -54
  231. package/.output/public/assets/styles-C0Re4quB.css +0 -2
  232. package/.output/server/__23tanstack-start-server-fn-resolver-CgwgZ0-i.mjs +0 -66
  233. package/.output/server/_libs/@floating-ui/core+[...].mjs +0 -698
  234. package/.output/server/_libs/@floating-ui/dom+[...].mjs +0 -644
  235. package/.output/server/_libs/@floating-ui/react-dom+[...].mjs +0 -839
  236. package/.output/server/_libs/@radix-ui/react-alert-dialog+[...].mjs +0 -2138
  237. package/.output/server/_libs/@radix-ui/react-popper+[...].mjs +0 -308
  238. package/.output/server/_libs/@radix-ui/react-select+[...].mjs +0 -1038
  239. package/.output/server/_libs/@tanstack/devtools-event-client+[...].mjs +0 -196
  240. package/.output/server/_libs/@tanstack/form-core+[...].mjs +0 -3228
  241. package/.output/server/_libs/@tanstack/react-form+[...].mjs +0 -430
  242. package/.output/server/_libs/@tanstack/react-table+[...].mjs +0 -2382
  243. package/.output/server/_libs/radix-ui__number.mjs +0 -6
  244. package/.output/server/_libs/radix-ui__primitive.mjs +0 -9
  245. package/.output/server/_libs/radix-ui__react-arrow.mjs +0 -23
  246. package/.output/server/_libs/radix-ui__react-collection.mjs +0 -78
  247. package/.output/server/_libs/radix-ui__react-direction.mjs +0 -13
  248. package/.output/server/_libs/radix-ui__react-label.mjs +0 -22
  249. package/.output/server/_libs/radix-ui__react-separator.mjs +0 -30
  250. package/.output/server/_libs/radix-ui__react-slider.mjs +0 -510
  251. package/.output/server/_libs/radix-ui__react-switch.mjs +0 -157
  252. package/.output/server/_libs/radix-ui__react-tooltip.mjs +0 -493
  253. package/.output/server/_ssr/api-rpc-BIbDUKSz.mjs +0 -292
  254. package/.output/server/_ssr/config.server-BSS366KT.mjs +0 -41
  255. package/.output/server/_ssr/deleteBundle-BgbZTtpW.mjs +0 -141
  256. package/.output/server/_ssr/dist-KboydRVk.mjs +0 -120
  257. package/.output/server/_ssr/router-C3hqEy3k.mjs +0 -307
  258. package/.output/server/_ssr/routes-B0lQvvlC.mjs +0 -3048
  259. package/.output/server/_ssr/start-02sLteYG.mjs +0 -4
  260. package/.output/server/_ssr/storageProfile-FVkjPoGb.mjs +0 -9
  261. package/.output/server/_tanstack-start-manifest_v-Bpx52WOU.mjs +0 -20
  262. package/.output/server/node_modules/tslib/modules/index.js +0 -70
  263. package/.output/server/node_modules/tslib/modules/package.json +0 -3
  264. package/.output/server/node_modules/tslib/package.json +0 -47
  265. package/.output/server/node_modules/tslib/tslib.js +0 -484
  266. package/.output/server/package.json +0 -9
@@ -0,0 +1,201 @@
1
+ import type { Bundle } from "@hot-updater/plugin-core";
2
+ import { ArrowRight } from "lucide-react";
3
+
4
+ import { BundleIdDisplay } from "@/components/BundleIdDisplay";
5
+ import { TimestampDisplay } from "@/components/TimestampDisplay";
6
+ import { Badge } from "@/components/ui/badge";
7
+ import { Button } from "@/components/ui/button";
8
+ import { Skeleton } from "@/components/ui/skeleton";
9
+ import {
10
+ Table,
11
+ TableBody,
12
+ TableCell,
13
+ TableHead,
14
+ TableHeader,
15
+ TableRow,
16
+ } from "@/components/ui/table";
17
+ import { useIsMobile } from "@/hooks/use-mobile";
18
+
19
+ interface BundleChildrenPanelProps {
20
+ panelId: string;
21
+ bundle: Bundle;
22
+ bundles: Bundle[];
23
+ loading: boolean;
24
+ onDetailClick: (bundle: Bundle) => void;
25
+ }
26
+
27
+ export function BundleChildrenPanel({
28
+ panelId,
29
+ bundle,
30
+ bundles,
31
+ loading,
32
+ onDetailClick,
33
+ }: BundleChildrenPanelProps) {
34
+ const isMobile = useIsMobile();
35
+
36
+ return (
37
+ <div
38
+ aria-live="polite"
39
+ className="border-t bg-muted/10 p-3 sm:p-4"
40
+ id={panelId}
41
+ >
42
+ <div className="flex flex-col gap-4">
43
+ <div className="flex flex-wrap items-center justify-between gap-3">
44
+ <div className="flex min-w-0 items-start gap-2 text-sm sm:items-center">
45
+ <span className="text-muted-foreground">Base bundle</span>
46
+ <BundleIdDisplay bundleId={bundle.id} fullOnMobile maxLength={18} />
47
+ </div>
48
+ <Badge variant="outline">
49
+ {bundles.length} {bundles.length === 1 ? "patch" : "patches"}
50
+ </Badge>
51
+ </div>
52
+
53
+ {loading ? (
54
+ <div className="flex flex-col gap-2">
55
+ <Skeleton className="h-10 w-full" />
56
+ <Skeleton className="h-10 w-full" />
57
+ </div>
58
+ ) : bundles.length > 0 ? (
59
+ <div className="flex flex-col gap-2">
60
+ <div className="text-xs font-semibold uppercase text-muted-foreground/70">
61
+ Patch bundles from this base
62
+ </div>
63
+ {isMobile ? (
64
+ <div className="flex flex-col gap-2">
65
+ {bundles.map((childBundle) => (
66
+ <div
67
+ className="rounded-md border bg-background p-3"
68
+ key={childBundle.id}
69
+ >
70
+ <div className="flex flex-col gap-3">
71
+ <div className="space-y-1">
72
+ <div className="text-[11px] font-medium uppercase text-muted-foreground/70">
73
+ Patch Bundle
74
+ </div>
75
+ <BundleIdDisplay
76
+ bundleId={childBundle.id}
77
+ fullOnMobile
78
+ maxLength={18}
79
+ />
80
+ </div>
81
+ <div className="space-y-1">
82
+ <div className="text-[11px] font-medium uppercase text-muted-foreground/70">
83
+ Relation
84
+ </div>
85
+ <div className="flex flex-col items-start gap-1 text-sm">
86
+ <BundleIdDisplay
87
+ bundleId={bundle.id}
88
+ fullOnMobile
89
+ maxLength={12}
90
+ />
91
+ <ArrowRight className="size-4 rotate-90 text-muted-foreground" />
92
+ <BundleIdDisplay
93
+ bundleId={childBundle.id}
94
+ fullOnMobile
95
+ maxLength={12}
96
+ />
97
+ </div>
98
+ </div>
99
+ <div className="flex items-center justify-between gap-3">
100
+ <div className="space-y-1">
101
+ <div className="text-[11px] font-medium uppercase text-muted-foreground/70">
102
+ Artifact
103
+ </div>
104
+ <Badge variant="secondary">bsdiff</Badge>
105
+ </div>
106
+ <div className="space-y-1 text-right">
107
+ <div className="text-[11px] font-medium uppercase text-muted-foreground/70">
108
+ Created
109
+ </div>
110
+ <div className="text-xs tabular-nums text-foreground">
111
+ <TimestampDisplay uuid={childBundle.id} />
112
+ </div>
113
+ </div>
114
+ </div>
115
+ <Button
116
+ className="w-full"
117
+ onClick={() => onDetailClick(childBundle)}
118
+ size="sm"
119
+ type="button"
120
+ variant="outline"
121
+ >
122
+ Detail
123
+ </Button>
124
+ </div>
125
+ </div>
126
+ ))}
127
+ </div>
128
+ ) : (
129
+ <div className="overflow-x-auto rounded-md border bg-background">
130
+ <Table className="min-w-max">
131
+ <TableHeader>
132
+ <TableRow className="hover:bg-transparent">
133
+ <TableHead>Patch Bundle</TableHead>
134
+ <TableHead>Relation</TableHead>
135
+ <TableHead>Artifact</TableHead>
136
+ <TableHead>Created</TableHead>
137
+ <TableHead className="w-24 text-right">Detail</TableHead>
138
+ </TableRow>
139
+ </TableHeader>
140
+ <TableBody>
141
+ {bundles.map((childBundle) => (
142
+ <TableRow key={childBundle.id}>
143
+ <TableCell>
144
+ <BundleIdDisplay
145
+ bundleId={childBundle.id}
146
+ fullOnMobile
147
+ maxLength={18}
148
+ />
149
+ </TableCell>
150
+ <TableCell>
151
+ <div className="flex min-w-[280px] flex-col items-start gap-1 sm:flex-row sm:items-center sm:gap-2">
152
+ <BundleIdDisplay
153
+ bundleId={bundle.id}
154
+ fullOnMobile
155
+ maxLength={12}
156
+ />
157
+ <ArrowRight className="size-4 shrink-0 rotate-90 text-muted-foreground sm:rotate-0" />
158
+ <BundleIdDisplay
159
+ bundleId={childBundle.id}
160
+ fullOnMobile
161
+ maxLength={12}
162
+ />
163
+ </div>
164
+ </TableCell>
165
+ <TableCell>
166
+ <Badge variant="secondary">bsdiff</Badge>
167
+ </TableCell>
168
+ <TableCell className="tabular-nums">
169
+ <TimestampDisplay uuid={childBundle.id} />
170
+ </TableCell>
171
+ <TableCell className="text-right">
172
+ <Button
173
+ onClick={() => onDetailClick(childBundle)}
174
+ size="sm"
175
+ type="button"
176
+ variant="outline"
177
+ >
178
+ Detail
179
+ </Button>
180
+ </TableCell>
181
+ </TableRow>
182
+ ))}
183
+ </TableBody>
184
+ </Table>
185
+ </div>
186
+ )}
187
+ </div>
188
+ ) : (
189
+ <div className="flex flex-col gap-2">
190
+ <div className="text-xs font-semibold uppercase text-muted-foreground/70">
191
+ Patch bundles from this base
192
+ </div>
193
+ <div className="rounded-md border bg-background p-3 text-sm text-muted-foreground">
194
+ No direct patch bundles.
195
+ </div>
196
+ </div>
197
+ )}
198
+ </div>
199
+ </div>
200
+ );
201
+ }
@@ -0,0 +1,136 @@
1
+ import {
2
+ getPatchBaseBundleId,
3
+ getPatchBaseFileHash,
4
+ getPatchFileHash,
5
+ } from "@hot-updater/core";
6
+ import type { Bundle, ReleaseRow } from "@hot-updater/plugin-core";
7
+ import { ExternalLink } from "lucide-react";
8
+
9
+ import { BundleIdDisplay } from "@/components/BundleIdDisplay";
10
+ import { HashValueDisplay } from "@/components/HashValueDisplay";
11
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
12
+ import { useConfigQuery } from "@/lib/api";
13
+ import { getCommitUrl } from "@/lib/git";
14
+
15
+ interface BundleMetadataProps {
16
+ readonly bundle: Bundle | null | undefined;
17
+ readonly release: ReleaseRow;
18
+ }
19
+
20
+ function Row({ label, value }: { label: string; value: React.ReactNode }) {
21
+ return (
22
+ <div className="flex flex-col gap-1.5 sm:flex-row sm:items-start sm:justify-between sm:gap-4">
23
+ <dt className="text-muted-foreground">{label}</dt>
24
+ <dd className="min-w-0 text-left text-sm sm:text-right">{value}</dd>
25
+ </div>
26
+ );
27
+ }
28
+
29
+ export function BundleMetadata({ bundle, release }: BundleMetadataProps) {
30
+ const { data: configData, isFetched } = useConfigQuery();
31
+ const patchBaseBundleId = bundle ? getPatchBaseBundleId(bundle) : null;
32
+ const hbcPatchFileHash = bundle ? getPatchFileHash(bundle) : null;
33
+ const hbcPatchBaseFileHash = bundle ? getPatchBaseFileHash(bundle) : null;
34
+ const gitCommitUrl =
35
+ bundle?.gitCommitHash && isFetched
36
+ ? getCommitUrl(configData?.console.gitUrl, bundle.gitCommitHash)
37
+ : null;
38
+ const target =
39
+ release.strategy === "APP_VERSION"
40
+ ? release.target_app_version
41
+ : release.fingerprint_hash;
42
+ const hasMetadata =
43
+ target ||
44
+ bundle?.gitCommitHash ||
45
+ bundle?.fileHash ||
46
+ patchBaseBundleId ||
47
+ hbcPatchBaseFileHash ||
48
+ hbcPatchFileHash;
49
+
50
+ if (!hasMetadata) return null;
51
+
52
+ return (
53
+ <Card>
54
+ <CardHeader className="p-4 pb-3">
55
+ <CardTitle className="text-sm font-medium">Metadata</CardTitle>
56
+ </CardHeader>
57
+ <CardContent className="px-4 pb-4">
58
+ <dl className="flex flex-col gap-3 text-sm">
59
+ <Row
60
+ label={
61
+ release.strategy === "APP_VERSION"
62
+ ? "Target app version"
63
+ : "Fingerprint target"
64
+ }
65
+ value={
66
+ <span className="break-all font-mono text-xs" translate="no">
67
+ {target ?? "—"}
68
+ </span>
69
+ }
70
+ />
71
+ {bundle?.gitCommitHash ? (
72
+ <Row
73
+ label="Git commit"
74
+ value={
75
+ <div className="flex items-center justify-start gap-1 sm:justify-end">
76
+ <HashValueDisplay
77
+ className={gitCommitUrl ? "text-primary" : undefined}
78
+ maxLength={12}
79
+ value={bundle.gitCommitHash}
80
+ />
81
+ {gitCommitUrl ? (
82
+ <a
83
+ aria-label="Open git commit"
84
+ className="shrink-0 text-primary hover:underline"
85
+ href={gitCommitUrl}
86
+ rel="noopener noreferrer"
87
+ target="_blank"
88
+ >
89
+ <ExternalLink aria-hidden="true" />
90
+ </a>
91
+ ) : null}
92
+ </div>
93
+ }
94
+ />
95
+ ) : null}
96
+ {bundle?.fileHash ? (
97
+ <Row
98
+ label="Bundle hash"
99
+ value={
100
+ <HashValueDisplay maxLength={16} value={bundle.fileHash} />
101
+ }
102
+ />
103
+ ) : null}
104
+ {patchBaseBundleId ? (
105
+ <Row
106
+ label="Patch base"
107
+ value={
108
+ <BundleIdDisplay
109
+ bundleId={patchBaseBundleId}
110
+ fullOnMobile
111
+ maxLength={18}
112
+ />
113
+ }
114
+ />
115
+ ) : null}
116
+ {hbcPatchBaseFileHash ? (
117
+ <Row
118
+ label="Base hash"
119
+ value={
120
+ <HashValueDisplay maxLength={16} value={hbcPatchBaseFileHash} />
121
+ }
122
+ />
123
+ ) : null}
124
+ {hbcPatchFileHash ? (
125
+ <Row
126
+ label="Patch hash"
127
+ value={
128
+ <HashValueDisplay maxLength={16} value={hbcPatchFileHash} />
129
+ }
130
+ />
131
+ ) : null}
132
+ </dl>
133
+ </CardContent>
134
+ </Card>
135
+ );
136
+ }
@@ -0,0 +1,201 @@
1
+ import {
2
+ getNumericCohortRolloutPosition,
3
+ NUMERIC_COHORT_SIZE,
4
+ normalizeRolloutCohortCount,
5
+ } from "@hot-updater/core";
6
+ import { List } from "lucide-react";
7
+
8
+ import { Badge } from "@/components/ui/badge";
9
+ import { Button } from "@/components/ui/button";
10
+ import {
11
+ Card,
12
+ CardContent,
13
+ CardDescription,
14
+ CardHeader,
15
+ CardTitle,
16
+ } from "@/components/ui/card";
17
+ import {
18
+ Dialog,
19
+ DialogContent,
20
+ DialogDescription,
21
+ DialogFooter,
22
+ DialogHeader,
23
+ DialogTitle,
24
+ DialogTrigger,
25
+ } from "@/components/ui/dialog";
26
+ import { useIsMobile } from "@/hooks/use-mobile";
27
+
28
+ interface RolloutCohortsDialogProps {
29
+ readonly releaseId: string;
30
+ readonly rolloutCohortCount: number | null | undefined;
31
+ readonly targetCohorts?: readonly string[] | null | undefined;
32
+ }
33
+
34
+ const ALL_NUMERIC_COHORTS = Array.from(
35
+ { length: NUMERIC_COHORT_SIZE },
36
+ (_, index) => index + 1,
37
+ );
38
+
39
+ export function RolloutCohortsDialog({
40
+ releaseId,
41
+ rolloutCohortCount,
42
+ targetCohorts,
43
+ }: RolloutCohortsDialogProps) {
44
+ const normalizedRolloutCount =
45
+ normalizeRolloutCohortCount(rolloutCohortCount);
46
+ const normalizedTargetCohorts = targetCohorts ?? [];
47
+ const hasTargetCohorts = normalizedTargetCohorts.length > 0;
48
+ const isPartialRollout =
49
+ normalizedRolloutCount > 0 && normalizedRolloutCount < NUMERIC_COHORT_SIZE;
50
+ const isFullRollout = normalizedRolloutCount === NUMERIC_COHORT_SIZE;
51
+ const isMobile = useIsMobile();
52
+
53
+ const rolloutCohorts = isFullRollout
54
+ ? ALL_NUMERIC_COHORTS
55
+ : isPartialRollout
56
+ ? ALL_NUMERIC_COHORTS.filter(
57
+ (cohortValue) =>
58
+ getNumericCohortRolloutPosition(releaseId, cohortValue) <
59
+ normalizedRolloutCount,
60
+ )
61
+ : [];
62
+ const rolloutPercentage = (normalizedRolloutCount / 10).toFixed(1);
63
+ const selectedCount = isFullRollout
64
+ ? NUMERIC_COHORT_SIZE
65
+ : rolloutCohorts.length;
66
+ const excludedCount = NUMERIC_COHORT_SIZE - selectedCount;
67
+
68
+ const dialogBody = (
69
+ <div className="flex flex-col gap-4">
70
+ <div className="grid grid-cols-2 gap-3">
71
+ <Card>
72
+ <CardHeader className="p-4">
73
+ <CardDescription>Selected cohorts</CardDescription>
74
+ <CardTitle className="font-mono text-2xl tabular-nums">
75
+ {selectedCount}
76
+ </CardTitle>
77
+ </CardHeader>
78
+ </Card>
79
+ <Card>
80
+ <CardHeader className="p-4">
81
+ <CardDescription>Excluded cohorts</CardDescription>
82
+ <CardTitle className="font-mono text-2xl tabular-nums">
83
+ {excludedCount}
84
+ </CardTitle>
85
+ </CardHeader>
86
+ </Card>
87
+ </div>
88
+
89
+ {hasTargetCohorts ? (
90
+ <Card>
91
+ <CardHeader className="p-4 pb-3">
92
+ <CardTitle className="text-sm">Additional cohorts</CardTitle>
93
+ <CardDescription>
94
+ Always included in addition to the percentage rollout.
95
+ </CardDescription>
96
+ </CardHeader>
97
+ <CardContent className="p-4 pt-0">
98
+ <div className="flex flex-wrap gap-2 rounded-lg border bg-muted/20 p-3">
99
+ {normalizedTargetCohorts.map((cohort) => (
100
+ <Badge
101
+ className="max-w-full font-mono"
102
+ key={cohort}
103
+ variant="secondary"
104
+ >
105
+ <span className="truncate">{cohort}</span>
106
+ </Badge>
107
+ ))}
108
+ </div>
109
+ </CardContent>
110
+ </Card>
111
+ ) : null}
112
+
113
+ <Card>
114
+ <CardHeader className="p-4 pb-3">
115
+ <CardTitle className="text-sm">Numeric cohorts</CardTitle>
116
+ </CardHeader>
117
+ <CardContent className="p-4 pt-0">
118
+ <div className="max-h-[50vh] overflow-y-auto overscroll-contain rounded-lg border bg-muted/20 p-3 sm:max-h-[45vh]">
119
+ {rolloutCohorts.length > 0 ? (
120
+ <div
121
+ aria-label="Included numeric cohorts"
122
+ className="grid grid-cols-3 gap-2 sm:grid-cols-6 lg:grid-cols-8"
123
+ role="list"
124
+ >
125
+ {rolloutCohorts.map((cohortValue) => (
126
+ <Badge
127
+ className="justify-center font-mono tabular-nums"
128
+ key={cohortValue}
129
+ role="listitem"
130
+ variant="outline"
131
+ >
132
+ {cohortValue}
133
+ </Badge>
134
+ ))}
135
+ </div>
136
+ ) : (
137
+ <p className="text-sm text-muted-foreground">
138
+ No numeric cohorts are included.
139
+ </p>
140
+ )}
141
+ </div>
142
+ </CardContent>
143
+ </Card>
144
+ </div>
145
+ );
146
+
147
+ return (
148
+ <Dialog>
149
+ <DialogTrigger
150
+ render={<Button size="sm" type="button" variant="outline" />}
151
+ >
152
+ <List data-icon="inline-start" />
153
+ Preview cohorts
154
+ </DialogTrigger>
155
+ <DialogContent
156
+ className={
157
+ isMobile
158
+ ? "top-0 left-0 h-dvh max-w-none translate-x-0 translate-y-0 rounded-none border-0 p-0"
159
+ : "sm:max-w-3xl"
160
+ }
161
+ >
162
+ {isMobile ? (
163
+ <div className="flex h-full flex-col overflow-hidden">
164
+ <DialogHeader className="shrink-0 border-b border-border/70 px-4 py-4">
165
+ <DialogTitle>Rolled out cohorts</DialogTitle>
166
+ <DialogDescription>
167
+ {rolloutPercentage}% includes {selectedCount} of{" "}
168
+ {NUMERIC_COHORT_SIZE} numeric cohorts.
169
+ {hasTargetCohorts
170
+ ? " Additional cohorts are always included."
171
+ : ""}
172
+ </DialogDescription>
173
+ </DialogHeader>
174
+ <div className="flex-1 overflow-y-auto overscroll-contain px-4 py-4">
175
+ {dialogBody}
176
+ </div>
177
+ <DialogFooter
178
+ className="shrink-0 border-t border-border/70 px-4 py-3"
179
+ showCloseButton
180
+ />
181
+ </div>
182
+ ) : (
183
+ <>
184
+ <DialogHeader>
185
+ <DialogTitle>Rolled out cohorts</DialogTitle>
186
+ <DialogDescription>
187
+ {rolloutPercentage}% includes {selectedCount} of{" "}
188
+ {NUMERIC_COHORT_SIZE} numeric cohorts.
189
+ {hasTargetCohorts
190
+ ? " Additional cohorts are always included."
191
+ : ""}
192
+ </DialogDescription>
193
+ </DialogHeader>
194
+ {dialogBody}
195
+ <DialogFooter showCloseButton />
196
+ </>
197
+ )}
198
+ </DialogContent>
199
+ </Dialog>
200
+ );
201
+ }