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

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 (203) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/HashValueDisplay-CB0Eskit.js +1 -0
  3. package/.output/public/assets/InsightsPageHeader-Car5LNiZ.js +1 -0
  4. package/.output/public/assets/api-keys-BzKocqRy.js +1 -0
  5. package/.output/public/assets/api-keys-api-BHcODB7q.js +1 -0
  6. package/.output/public/assets/card-Dhr_eFYj.js +1 -0
  7. package/.output/public/assets/{chart-no-axes-combined-FaYMGmQS.js → chart-no-axes-combined-Dy4WJv2U.js} +1 -1
  8. package/.output/public/assets/composite-Bp98A0Ld.js +1 -0
  9. package/.output/public/assets/createServerFn-CA8fs7QD.js +3 -0
  10. package/.output/public/assets/dialog-CUTrcPlB.js +1 -0
  11. package/.output/public/assets/{dist-DspNKfMj.js → dist-C_NydL0e.js} +1 -1
  12. package/.output/public/assets/index-DdAz-CXv.js +10 -0
  13. package/.output/public/assets/input-group-cSOG4wh_.js +1 -0
  14. package/.output/public/assets/insights-C0PBSZ-n.js +1 -0
  15. package/.output/public/assets/installations-CE33wgbY.js +2 -0
  16. package/.output/public/assets/installations-CStZja8x.js +1 -0
  17. package/.output/public/assets/lazyRouteComponent-Ccp3bwBY.js +1 -0
  18. package/.output/public/assets/link-CI8idAUi.js +1 -0
  19. package/.output/public/assets/loader-circle-BoxLrsOu.js +1 -0
  20. package/.output/public/assets/refresh-cw-Bqc72UZG.js +1 -0
  21. package/.output/public/assets/{routes-DcQnVajO.js → routes-DGVXdzpw.js} +2 -2
  22. package/.output/public/assets/routes-DTlwOr8I.js +1 -0
  23. package/.output/public/assets/{shield-check-DE_hVuHu.js → shield-check-uK3MHWJZ.js} +1 -1
  24. package/.output/public/assets/signing-CMf4aqDO.js +1 -0
  25. package/.output/public/assets/styles-BCvbtKsm.css +2 -0
  26. package/.output/public/assets/table-2vcV89QB.js +1 -0
  27. package/.output/public/assets/useCompositeListItem-CHLITE_4.js +1 -0
  28. package/.output/public/assets/x-DCd0j8c6.js +1 -0
  29. package/.output/server/{__23tanstack-start-server-fn-resolver-DBP9TQ7K.mjs → __23tanstack-start-server-fn-resolver-B7ioJhtE.mjs} +44 -52
  30. package/.output/server/_libs/@base-ui/react+[...].mjs +1906 -4040
  31. package/.output/server/_libs/@tanstack/react-router+[...].mjs +52 -4
  32. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +2 -2
  33. package/.output/server/_libs/lucide-react.mjs +36 -3
  34. package/.output/server/_libs/next-themes.mjs +2 -2
  35. package/.output/server/_libs/sonner.mjs +2 -2
  36. package/.output/server/_libs/tanstack__react-query.mjs +2 -2
  37. package/.output/server/_libs/verkit.mjs +20 -15
  38. package/.output/server/_runtime.mjs +1 -10
  39. package/.output/server/_ssr/HashValueDisplay-DwpGLfrl.mjs +55 -0
  40. package/.output/server/_ssr/InsightsPageHeader-Df4Brj8u.mjs +484 -0
  41. package/.output/server/_ssr/{api-keys-BnM_o2nS.mjs → api-keys-CZeTPQys.mjs} +11 -13
  42. package/.output/server/_ssr/{api-keys-api-CIywsSF5.mjs → api-keys-api-BUPQVSpk.mjs} +2 -2
  43. package/.output/server/_ssr/{api-keys-rpc-BSp1wkIV.mjs → api-keys-rpc-Cs7tS5_j.mjs} +5 -5
  44. package/.output/server/_ssr/{api-rpc-cG5wd_tO.mjs → api-rpc-tAupfGQm.mjs} +73 -142
  45. package/.output/server/_ssr/{auth-rpc-BrJ6obHJ.mjs → auth-rpc-DUaeOWKK.mjs} +4 -4
  46. package/.output/server/_ssr/{auth.server-DwKoZoQM.mjs → auth.server-DW71WzsD.mjs} +2 -5
  47. package/.output/server/_ssr/{bundle-signing-rpc-COoQhW8E.mjs → bundle-signing-rpc-BhnBw0-d.mjs} +2 -2
  48. package/.output/server/_ssr/bundle-signing.server-C4NW5mWx.mjs +12 -0
  49. package/.output/server/_ssr/{alert-Dim3MGY5.mjs → card-BRiPmi0C.mjs} +61 -39
  50. package/.output/server/_ssr/{config.server-DEiLhKmy.mjs → config.server-afDuAwaG.mjs} +15 -8
  51. package/.output/server/_ssr/{createSsrRpc-9P0S0lJ3.mjs → createSsrRpc-DjWNJ0uc.mjs} +6 -6
  52. package/.output/server/_ssr/databaseJsonValue-d6I3nAS4.mjs +45 -0
  53. package/.output/server/_ssr/{databaseRows-CwbNwYSl.mjs → databaseRows-5bCRTxm-.mjs} +4 -44
  54. package/.output/server/_ssr/{deleteBundle-Cu_XOMt0.mjs → deleteBundle-BTGwBgzx.mjs} +2 -2
  55. package/.output/server/_ssr/{dialog-CQuRMNZ4.mjs → dialog-DS-nw-pA.mjs} +2 -2
  56. package/.output/server/_ssr/{dist-BVSLTpoP.mjs → dist-CyzPaI5v.mjs} +2 -2
  57. package/.output/server/_ssr/{getBundleChildren-BcRMFoDF.mjs → getBundleChildren-C0vh4ZvE.mjs} +2 -2
  58. package/.output/server/_ssr/input-group-BpsnmvZw.mjs +70 -0
  59. package/.output/server/_ssr/insights-BdS364Xu.mjs +460 -0
  60. package/.output/server/_ssr/insights-rpc-38DujK0G.mjs +57 -0
  61. package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +303 -0
  62. package/.output/server/_ssr/installations-CtA59MQG.mjs +29 -0
  63. package/.output/server/_ssr/installations-DiVKEF5T.mjs +627 -0
  64. package/.output/server/_ssr/{router-C-KUBfOi.mjs → router-B9wG_9f8.mjs} +37 -46
  65. package/.output/server/_ssr/{routes-Z7TwC6nC.mjs → routes-CKrrS2q2.mjs} +3 -3
  66. package/.output/server/_ssr/{routes-BM4ht9jb.mjs → routes-D7Yh_jfj.mjs} +402 -390
  67. package/.output/server/_ssr/runtime.server-DacU1TBV.mjs +438 -0
  68. package/.output/server/_ssr/{server-BjzmvXnO.mjs → server-D_C2rMzf.mjs} +5 -5
  69. package/.output/server/_ssr/{signing-k8GxyBDF.mjs → signing-Dz1WkZOf.mjs} +16 -85
  70. package/.output/server/_ssr/ssr.mjs +1 -1
  71. package/.output/server/_ssr/{field-DRIXKjZG.mjs → table-CnZmon_Z.mjs} +51 -5
  72. package/.output/server/_ssr/{uuidv7-BbJ-9D_I.mjs → uuidv7-D0QFkPL_.mjs} +3 -3
  73. package/.output/server/_tanstack-start-manifest_v-2MjTyxil.mjs +97 -0
  74. package/.output/server/index.mjs +168 -203
  75. package/lib/index.d.mts +4 -4
  76. package/lib/vite-internal.mjs +1 -1
  77. package/lib/vite.d.mts +0 -1
  78. package/package.json +8 -8
  79. package/src/components/AppSidebar.tsx +12 -16
  80. package/src/components/ConsoleAccessPage.tsx +1 -1
  81. package/src/components/HashValueDisplay.tsx +11 -2
  82. package/src/components/features/api-keys/ApiKeysPage.tsx +1 -1
  83. package/src/components/features/bundles/BundleChildrenPanel.tsx +7 -6
  84. package/src/components/features/bundles/BundleMetadata.tsx +1 -20
  85. package/src/components/features/channels/ChannelManagementDialog.tsx +1 -1
  86. package/src/components/features/insights/EventDetails.tsx +151 -0
  87. package/src/components/features/insights/EventHistoryCard.tsx +245 -0
  88. package/src/components/features/insights/EventHistoryList.tsx +63 -0
  89. package/src/components/features/insights/InsightsBundleReports.tsx +51 -0
  90. package/src/components/features/insights/InsightsControls.tsx +121 -0
  91. package/src/components/features/{analytics/AnalyticsErrorAlert.tsx → insights/InsightsErrorAlert.tsx} +6 -6
  92. package/src/components/features/insights/InsightsOverview.tsx +203 -0
  93. package/src/components/features/insights/InsightsPageHeader.tsx +48 -0
  94. package/src/components/features/insights/InsightsPagination.tsx +52 -0
  95. package/src/components/features/insights/InstallationHistoryCard.tsx +250 -0
  96. package/src/components/features/insights/InstallationMatchesCard.tsx +178 -0
  97. package/src/components/features/insights/InstallationPageHeader.tsx +90 -0
  98. package/src/components/features/releases/ReleaseEditorSheet.tsx +24 -24
  99. package/src/components/features/signing/BundleSigningPage.tsx +18 -126
  100. package/src/components/ui/badge.tsx +2 -0
  101. package/src/components/ui/combobox.tsx +1 -0
  102. package/src/components/ui/skeleton.tsx +4 -1
  103. package/src/components/ui/tooltip.tsx +1 -1
  104. package/src/index.ts +7 -3
  105. package/src/lib/api-rpc.ts +19 -90
  106. package/src/lib/api.ts +0 -99
  107. package/src/lib/insights-api.ts +100 -0
  108. package/src/lib/insights-rpc.ts +88 -0
  109. package/src/lib/insights-view.ts +15 -0
  110. package/src/lib/public-bundle-error.ts +19 -0
  111. package/src/lib/server/bundle-signing.server.ts +5 -82
  112. package/src/lib/server/console-runtime.server.ts +0 -9
  113. package/src/lib/server/releaseReachability.ts +1 -4
  114. package/src/lib/server/runtime.server.ts +6 -142
  115. package/src/routeTree.gen.ts +21 -21
  116. package/src/router.tsx +3 -0
  117. package/src/routes/-installations-search.ts +25 -12
  118. package/src/routes/__root.tsx +6 -16
  119. package/src/routes/api-keys.tsx +1 -1
  120. package/src/routes/index.tsx +21 -81
  121. package/src/routes/insights.tsx +84 -0
  122. package/src/routes/installations.tsx +294 -142
  123. package/src/styles.css +6 -0
  124. package/.output/public/assets/AnalyticsCapabilityContext-BCke57-7.js +0 -1
  125. package/.output/public/assets/BundleActivityChart-CnsBe_1J.js +0 -42
  126. package/.output/public/assets/BundleIdDisplay-Dky75zLU.js +0 -1
  127. package/.output/public/assets/alert-C-1KvDW8.js +0 -1
  128. package/.output/public/assets/analytics-Bqz0c-BT.js +0 -1
  129. package/.output/public/assets/analytics-api-CJdrTLLO.js +0 -1
  130. package/.output/public/assets/api-B_Rh-uzC.js +0 -1
  131. package/.output/public/assets/api-keys-BurCFhzH.js +0 -1
  132. package/.output/public/assets/api-keys-api-BEDJdQg3.js +0 -1
  133. package/.output/public/assets/badge-Bf0fZ-Gx.js +0 -1
  134. package/.output/public/assets/createServerFn-gM2-LFBy.js +0 -3
  135. package/.output/public/assets/dialog-BS60BRr2.js +0 -1
  136. package/.output/public/assets/download-O-yNBc9H.js +0 -1
  137. package/.output/public/assets/field-Buq148-A.js +0 -1
  138. package/.output/public/assets/index-kFhd2S78.js +0 -10
  139. package/.output/public/assets/installations-BrQ-od41.js +0 -2
  140. package/.output/public/assets/installations-CGlZ3ROz.js +0 -1
  141. package/.output/public/assets/lazyRouteComponent-rPzgA7S-.js +0 -1
  142. package/.output/public/assets/loader-circle-DCYF-wJl.js +0 -1
  143. package/.output/public/assets/refresh-cw-n8XYbPPj.js +0 -1
  144. package/.output/public/assets/routes-CrdokJ80.js +0 -1
  145. package/.output/public/assets/search-DLhbWw3W.js +0 -1
  146. package/.output/public/assets/signing-HRexfjsH.js +0 -1
  147. package/.output/public/assets/styles-eB4EtvFS.css +0 -2
  148. package/.output/public/assets/table-D8CLieHX.js +0 -1
  149. package/.output/public/assets/textarea-YJLTVAoq.js +0 -1
  150. package/.output/public/assets/useMutation-JsGVjQMY.js +0 -1
  151. package/.output/public/assets/useOpenInteractionType-4cRD5xO0.js +0 -1
  152. package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +0 -1554
  153. package/.output/server/_libs/d3-array.mjs +0 -231
  154. package/.output/server/_libs/d3-color.mjs +0 -330
  155. package/.output/server/_libs/d3-format.mjs +0 -252
  156. package/.output/server/_libs/d3-interpolate.mjs +0 -207
  157. package/.output/server/_libs/d3-path.mjs +0 -77
  158. package/.output/server/_libs/d3-scale+[...].mjs +0 -1907
  159. package/.output/server/_libs/d3-shape.mjs +0 -818
  160. package/.output/server/_libs/decimal.js-light.mjs +0 -884
  161. package/.output/server/_libs/es-toolkit.mjs +0 -1134
  162. package/.output/server/_libs/eventemitter3.mjs +0 -236
  163. package/.output/server/_libs/immer.mjs +0 -532
  164. package/.output/server/_libs/react-redux+[...].mjs +0 -205
  165. package/.output/server/_libs/recharts+[...].mjs +0 -14117
  166. package/.output/server/_ssr/AnalyticsCapabilityContext-ui4DX8GO.mjs +0 -15
  167. package/.output/server/_ssr/BundleActivityChart-BiB9ZORH.mjs +0 -288
  168. package/.output/server/_ssr/BundleIdDisplay-Czk6iR6p.mjs +0 -13
  169. package/.output/server/_ssr/analytics-DtMTn8fn.mjs +0 -858
  170. package/.output/server/_ssr/analytics-api-Cg1M5v_H.mjs +0 -71
  171. package/.output/server/_ssr/analytics-input-fRRRn0uA.mjs +0 -96
  172. package/.output/server/_ssr/analytics-rpc-DY5uXvHT.mjs +0 -155
  173. package/.output/server/_ssr/api-BTSv1tZb.mjs +0 -282
  174. package/.output/server/_ssr/badge-DCxbEeof.mjs +0 -26
  175. package/.output/server/_ssr/bundle-signing.server-D5u8KkMp.mjs +0 -51
  176. package/.output/server/_ssr/installations-B-azgAvX.mjs +0 -19
  177. package/.output/server/_ssr/installations-Ctvzp2JH.mjs +0 -535
  178. package/.output/server/_ssr/runtime.server-awTLiiXW.mjs +0 -527
  179. package/.output/server/_ssr/table-v1yCvYo7.mjs +0 -53
  180. package/.output/server/_ssr/textarea-DYHnskhb.mjs +0 -14
  181. package/.output/server/_tanstack-start-manifest_v-CH4StzJu.mjs +0 -108
  182. package/src/components/features/analytics/ActivityChart.tsx +0 -150
  183. package/src/components/features/analytics/AnalyticsCapabilityContext.tsx +0 -24
  184. package/src/components/features/analytics/AnalyticsControls.tsx +0 -115
  185. package/src/components/features/analytics/AnalyticsOverview.tsx +0 -208
  186. package/src/components/features/analytics/BundleDistribution.tsx +0 -133
  187. package/src/components/features/analytics/BundleSelector.tsx +0 -114
  188. package/src/components/features/analytics/InstallationHistoryCard.tsx +0 -248
  189. package/src/components/features/analytics/InstallationMatchesCard.tsx +0 -132
  190. package/src/components/features/analytics/InstallationPageHeader.tsx +0 -138
  191. package/src/components/features/analytics/InstallationPagination.tsx +0 -48
  192. package/src/components/features/analytics/InstallationSearch.tsx +0 -230
  193. package/src/components/features/analytics/UpdateOutcomes.tsx +0 -147
  194. package/src/components/features/bundles/BundleActivityChart.tsx +0 -193
  195. package/src/components/features/bundles/BundleAnalyticsSummary.tsx +0 -123
  196. package/src/lib/analytics-api.ts +0 -145
  197. package/src/lib/analytics-error.ts +0 -19
  198. package/src/lib/analytics-input.ts +0 -187
  199. package/src/lib/analytics-overview.ts +0 -170
  200. package/src/lib/analytics-rpc.ts +0 -218
  201. package/src/lib/server/releaseActivity.ts +0 -27
  202. package/src/routes/analytics.tsx +0 -139
  203. /package/.output/public/assets/{preload-helper-TKOBYGYD.js → preload-helper-CQ36nxok.js} +0 -0
@@ -1,12 +1,4 @@
1
- import {
2
- AlertTriangle,
3
- CheckCircle2,
4
- Clipboard,
5
- Download,
6
- Info,
7
- RefreshCw,
8
- } from "lucide-react";
9
- import { toast } from "sonner";
1
+ import { AlertTriangle, CheckCircle2, Info, RefreshCw } from "lucide-react";
10
2
 
11
3
  import {
12
4
  Alert,
@@ -24,12 +16,9 @@ import {
24
16
  CardTitle,
25
17
  } from "@/components/ui/card";
26
18
  import { Skeleton } from "@/components/ui/skeleton";
27
- import { Textarea } from "@/components/ui/textarea";
28
19
  import { useBundleSigningInspectionQuery } from "@/lib/bundle-signing-api";
29
20
  import type { BundleSigningInspection } from "@/lib/bundle-signing-rpc";
30
21
 
31
- const PUBLIC_KEY_FILENAME = "hot-updater-public-key.pem";
32
-
33
22
  function InspectionLoading() {
34
23
  return (
35
24
  <div aria-label="Loading bundle signing" className="flex flex-col gap-6">
@@ -80,12 +69,7 @@ function SigningStatusCard({
80
69
  readonly inspection: BundleSigningInspection;
81
70
  }) {
82
71
  const enabled = inspection.status === "enabled";
83
- const misconfigured = inspection.status === "misconfigured";
84
- const status = enabled
85
- ? "Enabled"
86
- : misconfigured
87
- ? "Misconfigured"
88
- : "Disabled";
72
+ const status = enabled ? "Enabled" : "Disabled";
89
73
 
90
74
  return (
91
75
  <Card>
@@ -94,20 +78,12 @@ function SigningStatusCard({
94
78
  <CardTitle className="text-sm font-medium">
95
79
  <h2>Signing status</h2>
96
80
  </CardTitle>
97
- <Badge
98
- variant={
99
- misconfigured ? "destructive" : enabled ? "default" : "outline"
100
- }
101
- >
102
- {status}
103
- </Badge>
81
+ <Badge variant={enabled ? "default" : "outline"}>{status}</Badge>
104
82
  </div>
105
83
  <CardDescription>
106
84
  {enabled
107
- ? "The Console found the public key configured for bundle verification."
108
- : misconfigured
109
- ? "Signing is enabled, but the public key cannot be inspected safely."
110
- : "Bundle signing is disabled in the current Hot Updater configuration."}
85
+ ? "Bundle signing is enabled in the current Hot Updater configuration."
86
+ : "Bundle signing is disabled in the current Hot Updater configuration."}
111
87
  </CardDescription>
112
88
  </CardHeader>
113
89
  <CardContent className="flex flex-col gap-5">
@@ -121,105 +97,24 @@ function SigningStatusCard({
121
97
  </AlertDescription>
122
98
  </Alert>
123
99
  ) : (
124
- <>
125
- <dl className="grid gap-4 sm:grid-cols-2">
126
- <div className="flex min-w-0 flex-col gap-1">
127
- <dt className="text-xs text-muted-foreground">Provider</dt>
128
- <dd className="truncate text-sm font-medium">
129
- {inspection.provider}
130
- </dd>
131
- </div>
132
- {inspection.status === "enabled" ? (
133
- <div className="flex min-w-0 flex-col gap-1">
134
- <dt className="text-xs text-muted-foreground">Algorithm</dt>
135
- <dd className="text-sm font-medium">
136
- {inspection.algorithm}
137
- </dd>
138
- </div>
139
- ) : null}
140
- </dl>
141
- {inspection.status === "enabled" ? (
142
- <div className="flex min-w-0 flex-col gap-1 border-t pt-4">
143
- <p className="text-xs text-muted-foreground">
144
- SPKI SHA-256 fingerprint
145
- </p>
146
- <p className="break-all font-mono text-xs">
147
- {inspection.fingerprint}
148
- </p>
149
- </div>
150
- ) : (
151
- <Alert variant="destructive">
152
- <AlertTriangle aria-hidden="true" />
153
- <AlertTitle>Public key unavailable</AlertTitle>
154
- <AlertDescription>{inspection.message}</AlertDescription>
155
- </Alert>
156
- )}
157
- </>
100
+ <dl className="grid gap-4 sm:grid-cols-2">
101
+ <div className="flex min-w-0 flex-col gap-1">
102
+ <dt className="text-xs text-muted-foreground">Provider</dt>
103
+ <dd className="truncate text-sm font-medium">
104
+ {inspection.provider}
105
+ </dd>
106
+ </div>
107
+ <div className="flex min-w-0 flex-col gap-1">
108
+ <dt className="text-xs text-muted-foreground">Algorithm</dt>
109
+ <dd className="text-sm font-medium">{inspection.algorithm}</dd>
110
+ </div>
111
+ </dl>
158
112
  )}
159
113
  </CardContent>
160
114
  </Card>
161
115
  );
162
116
  }
163
117
 
164
- function PublicKeyCard({ publicKey }: { readonly publicKey: string }) {
165
- const copyPublicKey = async () => {
166
- try {
167
- await navigator.clipboard.writeText(publicKey);
168
- toast.success("Public key copied");
169
- } catch {
170
- toast.error("Unable to copy public key");
171
- }
172
- };
173
-
174
- const downloadPublicKey = () => {
175
- const url = URL.createObjectURL(
176
- new Blob([`${publicKey}\n`], { type: "application/x-pem-file" }),
177
- );
178
- const link = document.createElement("a");
179
- link.download = PUBLIC_KEY_FILENAME;
180
- link.href = url;
181
- link.click();
182
- URL.revokeObjectURL(url);
183
- };
184
-
185
- return (
186
- <Card>
187
- <CardHeader>
188
- <CardTitle className="text-sm font-medium">
189
- <h2>Public key</h2>
190
- </CardTitle>
191
- <CardDescription>
192
- This public key may be embedded in native app configuration. Private
193
- key material is never available in the Console.
194
- </CardDescription>
195
- </CardHeader>
196
- <CardContent className="flex flex-col gap-3">
197
- <Textarea
198
- aria-label="Bundle signing public key"
199
- className="min-h-56 resize-none font-mono text-xs"
200
- readOnly
201
- spellCheck={false}
202
- value={publicKey}
203
- />
204
- <div className="flex flex-wrap gap-2">
205
- <Button
206
- onClick={() => void copyPublicKey()}
207
- size="sm"
208
- variant="outline"
209
- >
210
- <Clipboard data-icon="inline-start" />
211
- Copy
212
- </Button>
213
- <Button onClick={downloadPublicKey} size="sm" variant="outline">
214
- <Download data-icon="inline-start" />
215
- Download
216
- </Button>
217
- </div>
218
- </CardContent>
219
- </Card>
220
- );
221
- }
222
-
223
118
  export function BundleSigningPage() {
224
119
  const inspection = useBundleSigningInspectionQuery();
225
120
 
@@ -231,15 +126,12 @@ export function BundleSigningPage() {
231
126
  return (
232
127
  <div className="flex flex-col gap-6">
233
128
  <SigningStatusCard inspection={inspection.data} />
234
- {inspection.data.status === "enabled" ? (
235
- <PublicKeyCard publicKey={inspection.data.publicKey} />
236
- ) : null}
237
129
  <Alert>
238
130
  <CheckCircle2 aria-hidden="true" />
239
131
  <AlertTitle>Read-only inspection</AlertTitle>
240
132
  <AlertDescription>
241
133
  Configure or rotate signing keys through the provider or Hot Updater
242
- CLI. The Console only reads a checked-in public key file.
134
+ CLI. The Console never accesses signing credentials.
243
135
  </AlertDescription>
244
136
  </Alert>
245
137
  </div>
@@ -13,6 +13,8 @@ const badgeVariants = cva(
13
13
  "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
14
14
  secondary:
15
15
  "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
16
+ success: "border-success/20 bg-success/10 text-success",
17
+ warning: "border-warning/20 bg-warning/10 text-warning",
16
18
  destructive:
17
19
  "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
18
20
  outline: "text-foreground",
@@ -39,6 +39,7 @@ function ComboboxTrigger({
39
39
  function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {
40
40
  return (
41
41
  <ComboboxPrimitive.Clear
42
+ aria-label="Clear selection"
42
43
  data-slot="combobox-clear"
43
44
  render={<InputGroupButton variant="ghost" size="icon-xs" />}
44
45
  className={cn(className)}
@@ -4,7 +4,10 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
4
4
  return (
5
5
  <div
6
6
  data-slot="skeleton"
7
- className={cn("bg-muted rounded-md animate-pulse", className)}
7
+ className={cn(
8
+ "bg-muted rounded-md animate-pulse motion-reduce:animate-none",
9
+ className,
10
+ )}
8
11
  {...props}
9
12
  />
10
13
  );
@@ -50,7 +50,7 @@ function TooltipContent({
50
50
  <TooltipPrimitive.Popup
51
51
  data-slot="tooltip-content"
52
52
  className={cn(
53
- "z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
53
+ "z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 motion-reduce:animate-none! data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
54
54
  className,
55
55
  )}
56
56
  {...props}
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ConfigInput } from "@hot-updater/plugin-core";
1
+ import type { ConfigInput, DatabasePlugin } from "@hot-updater/plugin-core";
2
2
 
3
3
  export type ConsoleAuthProvider = "google" | "github";
4
4
 
@@ -22,11 +22,15 @@ export type ConsoleAuthAdapter = Readonly<{
22
22
  export type ConsoleSigningConfig = Readonly<{
23
23
  enabled: boolean;
24
24
  provider?: string;
25
- publicKeyPath?: string;
26
25
  }>;
27
26
 
28
27
  export type HotUpdaterConsoleConfig = Readonly<
29
- Pick<ConfigInput, "console" | "database" | "signing" | "storage">
28
+ Omit<
29
+ Pick<ConfigInput, "console" | "database" | "signing" | "storage">,
30
+ "database"
31
+ > & {
32
+ database: DatabasePlugin;
33
+ }
30
34
  >;
31
35
 
32
36
  export type ResolvedHotUpdaterConsoleConfig = Readonly<
@@ -12,15 +12,9 @@ import {
12
12
  } from "@hot-updater/plugin-core";
13
13
  import { createServerFn } from "@tanstack/react-start";
14
14
 
15
- import {
16
- parseBundleEventAnalyticsInput,
17
- parseBundleEventSummaryInput,
18
- parseInstallationHistoryInput,
19
- parseSearchInstallationsInput,
20
- } from "./analytics-input";
21
15
  import { DEFAULT_PAGE_LIMIT } from "./constants";
16
+ import { withPublicBundleMutationErrors } from "./public-bundle-error";
22
17
  import { listReleases } from "./server/listReleases";
23
- import { getReleaseActivity30d } from "./server/releaseActivity";
24
18
  import { addReleaseReachability } from "./server/releaseReachability";
25
19
 
26
20
  type GetBundlesInput = {
@@ -78,7 +72,7 @@ export const getReleases = createServerFn({ method: "GET" })
78
72
  .inputValidator((input: GetReleasesInput | undefined) => input)
79
73
  .handler(async ({ data }) => {
80
74
  const { prepareConfig } = await import("./server/config.server");
81
- const { config, hotUpdater } = await prepareConfig();
75
+ const { config } = await prepareConfig();
82
76
  const result = await listReleases(config.database.models.releases, {
83
77
  ...(data?.afterReleaseId === undefined
84
78
  ? {}
@@ -96,22 +90,13 @@ export const getReleases = createServerFn({ method: "GET" })
96
90
  : { targetAppVersion: data.targetAppVersion }),
97
91
  limit: data?.limit ?? DEFAULT_PAGE_LIMIT,
98
92
  });
99
- const [releases, activityByBundleId] = await Promise.all([
100
- addReleaseReachability(
101
- config.database.models.releaseCatalogs,
102
- result.data,
103
- ),
104
- getReleaseActivity30d(hotUpdater, result.data),
105
- ]);
93
+ const releases = await addReleaseReachability(
94
+ config.database.models.releaseCatalogs,
95
+ result.data,
96
+ );
106
97
  return {
107
98
  ...result,
108
- data: releases.map((release) => ({
109
- ...release,
110
- activity30d:
111
- release.bundle_id === null
112
- ? null
113
- : (activityByBundleId.get(release.bundle_id) ?? null),
114
- })),
99
+ data: releases,
115
100
  };
116
101
  });
117
102
 
@@ -128,7 +113,9 @@ export const updateRelease = createServerFn({ method: "POST" })
128
113
  .handler(async ({ data }) => {
129
114
  const { prepareConfig } = await import("./server/config.server");
130
115
  const { config } = await prepareConfig();
131
- return updateReleasePolicy({ database: config.database, ...data });
116
+ return withPublicBundleMutationErrors(() =>
117
+ updateReleasePolicy({ database: config.database, ...data }),
118
+ );
132
119
  });
133
120
 
134
121
  export const preflightRelease = createServerFn({ method: "POST" })
@@ -136,7 +123,9 @@ export const preflightRelease = createServerFn({ method: "POST" })
136
123
  .handler(async ({ data }) => {
137
124
  const { prepareConfig } = await import("./server/config.server");
138
125
  const { config } = await prepareConfig();
139
- return preflightReleasePolicy({ database: config.database, ...data });
126
+ return withPublicBundleMutationErrors(() =>
127
+ preflightReleasePolicy({ database: config.database, ...data }),
128
+ );
140
129
  });
141
130
 
142
131
  export const deleteRelease = createServerFn({ method: "POST" })
@@ -144,7 +133,9 @@ export const deleteRelease = createServerFn({ method: "POST" })
144
133
  .handler(async ({ data }) => {
145
134
  const { prepareConfig } = await import("./server/config.server");
146
135
  const { config } = await prepareConfig();
147
- return deleteReleaseMutation({ database: config.database, ...data });
136
+ return withPublicBundleMutationErrors(() =>
137
+ deleteReleaseMutation({ database: config.database, ...data }),
138
+ );
148
139
  });
149
140
 
150
141
  export const promoteRelease = createServerFn({ method: "POST" })
@@ -159,7 +150,9 @@ export const promoteRelease = createServerFn({ method: "POST" })
159
150
  .handler(async ({ data }) => {
160
151
  const { prepareConfig } = await import("./server/config.server");
161
152
  const { config } = await prepareConfig();
162
- return promoteReleaseMutation({ database: config.database, ...data });
153
+ return withPublicBundleMutationErrors(() =>
154
+ promoteReleaseMutation({ database: config.database, ...data }),
155
+ );
163
156
  });
164
157
 
165
158
  export const getReleaseCatalogDiagnostics = createServerFn({ method: "GET" })
@@ -317,70 +310,6 @@ export const getBundle = createServerFn({ method: "GET" })
317
310
  }
318
311
  });
319
312
 
320
- export const getBundleEventSummary = createServerFn({ method: "GET" })
321
- .inputValidator(parseBundleEventSummaryInput)
322
- .handler(async ({ data }) => {
323
- try {
324
- const { prepareConfig } = await import("./server/config.server");
325
- const { getBundleEventSummary: getBundleEventSummaryWithRuntime } =
326
- await import("./server/runtime.server");
327
- const { hotUpdater } = await prepareConfig();
328
-
329
- return await getBundleEventSummaryWithRuntime(hotUpdater, data);
330
- } catch (error) {
331
- console.error("Error during bundle event summary retrieval:", error);
332
- throw error;
333
- }
334
- });
335
-
336
- export const getBundleEventAnalytics = createServerFn({ method: "GET" })
337
- .inputValidator(parseBundleEventAnalyticsInput)
338
- .handler(async ({ data }) => {
339
- try {
340
- const { prepareConfig } = await import("./server/config.server");
341
- const { getBundleEventAnalytics: getBundleEventAnalyticsWithRuntime } =
342
- await import("./server/runtime.server");
343
- const { hotUpdater } = await prepareConfig();
344
-
345
- return await getBundleEventAnalyticsWithRuntime(hotUpdater, data);
346
- } catch (error) {
347
- console.error("Error during bundle event analytics retrieval:", error);
348
- throw error;
349
- }
350
- });
351
-
352
- export const searchInstallations = createServerFn({ method: "GET" })
353
- .inputValidator(parseSearchInstallationsInput)
354
- .handler(async ({ data }) => {
355
- try {
356
- const { prepareConfig } = await import("./server/config.server");
357
- const { searchInstallations: searchInstallationsWithRuntime } =
358
- await import("./server/runtime.server");
359
- const { hotUpdater } = await prepareConfig();
360
-
361
- return await searchInstallationsWithRuntime(hotUpdater, data);
362
- } catch (error) {
363
- console.error("Error during installation search:", error);
364
- throw error;
365
- }
366
- });
367
-
368
- export const getInstallationHistory = createServerFn({ method: "GET" })
369
- .inputValidator(parseInstallationHistoryInput)
370
- .handler(async ({ data }) => {
371
- try {
372
- const { prepareConfig } = await import("./server/config.server");
373
- const { getInstallationHistory: getInstallationHistoryWithRuntime } =
374
- await import("./server/runtime.server");
375
- const { hotUpdater } = await prepareConfig();
376
-
377
- return await getInstallationHistoryWithRuntime(hotUpdater, data);
378
- } catch (error) {
379
- console.error("Error during installation history retrieval:", error);
380
- throw error;
381
- }
382
- });
383
-
384
313
  export const getBundleChildren = createServerFn({ method: "GET" })
385
314
  .inputValidator((input: GetBundleChildrenInput) => input)
386
315
  .handler(async ({ data }) => {
package/src/lib/api.ts CHANGED
@@ -3,7 +3,6 @@ import type {
3
3
  ChannelInsertInput,
4
4
  ReleasePolicyPatch,
5
5
  } from "@hot-updater/plugin-core";
6
- import type { BundleEventAnalyticsWindow } from "@hot-updater/server";
7
6
  import {
8
7
  type QueryClient,
9
8
  type QueryKey,
@@ -21,19 +20,15 @@ import {
21
20
  getBundle,
22
21
  getBundleChildCounts,
23
22
  getBundleChildren,
24
- getBundleEventAnalytics as getBundleEventAnalyticsApi,
25
- getBundleEventSummary as getBundleEventSummaryApi,
26
23
  getBundles,
27
24
  getChannels,
28
25
  getConfig,
29
26
  getConfigLoaded,
30
- getInstallationHistory as getInstallationHistoryApi,
31
27
  getRelease,
32
28
  getReleaseCatalogDiagnostics,
33
29
  getReleases,
34
30
  preflightRelease as preflightReleaseApi,
35
31
  promoteRelease as promoteReleaseApi,
36
- searchInstallations as searchInstallationsApi,
37
32
  updateRelease as updateReleaseApi,
38
33
  } from "./api-rpc";
39
34
 
@@ -47,23 +42,6 @@ type BundleFilters = {
47
42
 
48
43
  type BundlesQueryData = Awaited<ReturnType<typeof getBundles>>;
49
44
 
50
- const ANALYTICS_STALE_TIME_MS = 30_000;
51
-
52
- export type BundleEventSummary = Awaited<
53
- ReturnType<typeof getBundleEventSummaryApi>
54
- >;
55
- export type BundleEventAnalytics = Awaited<
56
- ReturnType<typeof getBundleEventAnalyticsApi>
57
- >;
58
- export type InstallationSearchResult = Awaited<
59
- ReturnType<typeof searchInstallationsApi>
60
- >;
61
- export type InstallationHistoryResult = Awaited<
62
- ReturnType<typeof getInstallationHistoryApi>
63
- >;
64
- export type InstallationSearchRow = InstallationSearchResult["data"][number];
65
- export type InstallationHistoryRow = InstallationHistoryResult["data"][number];
66
-
67
45
  const bundleListQueryKey = ["bundles"] as const;
68
46
  const releaseListQueryKey = ["releases"] as const;
69
47
 
@@ -90,21 +68,6 @@ export const queryKeys = {
90
68
  ["bundle-children", "counts", ...bundleIds] as const,
91
69
  },
92
70
  bundle: (bundleId: string) => ["bundle", bundleId] as const,
93
- bundleEventSummary: (bundleId: string) =>
94
- ["bundle-event-summary", bundleId] as const,
95
- bundleEventAnalytics: (input: {
96
- bundleId: string;
97
- window: BundleEventAnalyticsWindow;
98
- limit?: number;
99
- offset?: number;
100
- }) => ["bundle-event-analytics", input] as const,
101
-
102
- installations: {
103
- search: (input: { query: string; limit?: number; offset?: number }) =>
104
- ["installations", "search", input] as const,
105
- history: (input: { installId: string; limit?: number; offset?: number }) =>
106
- ["installations", "history", input] as const,
107
- },
108
71
  };
109
72
 
110
73
  export type ReleaseFilters = {
@@ -222,68 +185,6 @@ export function useBundleQuery(bundleId: string) {
222
185
  });
223
186
  }
224
187
 
225
- export function useBundleEventSummaryQuery(bundleId: string, enabled: boolean) {
226
- return useQuery({
227
- queryKey: queryKeys.bundleEventSummary(bundleId),
228
- queryFn: () => getBundleEventSummaryApi({ data: { bundleId } }),
229
- staleTime: ANALYTICS_STALE_TIME_MS,
230
- refetchOnWindowFocus: true,
231
- enabled: enabled && bundleId.length > 0,
232
- });
233
- }
234
-
235
- export function useBundleEventAnalyticsQuery(
236
- input: {
237
- bundleId: string;
238
- window: BundleEventAnalyticsWindow;
239
- limit?: number;
240
- offset?: number;
241
- },
242
- enabled: boolean,
243
- ) {
244
- return useQuery({
245
- queryKey: queryKeys.bundleEventAnalytics(input),
246
- queryFn: () => getBundleEventAnalyticsApi({ data: input }),
247
- staleTime: ANALYTICS_STALE_TIME_MS,
248
- refetchOnWindowFocus: true,
249
- enabled: enabled && input.bundleId.length > 0,
250
- });
251
- }
252
-
253
- export function useInstallationSearchQuery(
254
- input: {
255
- query: string;
256
- limit?: number;
257
- offset?: number;
258
- },
259
- enabled: boolean,
260
- ) {
261
- return useQuery({
262
- queryKey: queryKeys.installations.search(input),
263
- queryFn: () => searchInstallationsApi({ data: input }),
264
- staleTime: ANALYTICS_STALE_TIME_MS,
265
- refetchOnWindowFocus: true,
266
- enabled: enabled && input.query.trim().length > 0,
267
- });
268
- }
269
-
270
- export function useInstallationHistoryQuery(
271
- input: {
272
- installId: string;
273
- limit?: number;
274
- offset?: number;
275
- },
276
- enabled: boolean,
277
- ) {
278
- return useQuery({
279
- queryKey: queryKeys.installations.history(input),
280
- queryFn: () => getInstallationHistoryApi({ data: input }),
281
- staleTime: ANALYTICS_STALE_TIME_MS,
282
- refetchOnWindowFocus: true,
283
- enabled: enabled && input.installId.length > 0,
284
- });
285
- }
286
-
287
188
  export function useBundleChildrenQuery(baseBundleId: string) {
288
189
  return useQuery({
289
190
  queryKey: queryKeys.bundleChildren.list(baseBundleId),
@@ -0,0 +1,100 @@
1
+ import type { InsightsEventPageInput } from "@hot-updater/server";
2
+ import { useQuery } from "@tanstack/react-query";
3
+
4
+ import {
5
+ findInsightsInstallationsRpc,
6
+ getInsightsInstallationRpc,
7
+ getReportingInstallationsRpc,
8
+ listInsightsEventsRpc,
9
+ listInsightsInstallationEventsRpc,
10
+ type InsightsWindow,
11
+ type InsightsOverviewInput,
12
+ type ReportingInstallations,
13
+ } from "./insights-rpc";
14
+
15
+ export type { InsightsWindow, InsightsOverviewInput, ReportingInstallations };
16
+
17
+ const STALE_TIME_MS = 30_000;
18
+
19
+ const queryKeys = {
20
+ reportingInstallations: (input: InsightsOverviewInput) =>
21
+ ["insights", "reporting-installations", input] as const,
22
+ events: (input: InsightsEventPageInput) =>
23
+ ["insights", "events", input] as const,
24
+ installations: (input: {
25
+ readonly cursor?: string;
26
+ readonly identity: string;
27
+ readonly limit: number;
28
+ }) => ["insights", "installations", input] as const,
29
+ installation: (installId: string) =>
30
+ ["insights", "installation", installId] as const,
31
+ installationEvents: (input: {
32
+ readonly beforeReceivedAtMs: number;
33
+ readonly cursor?: string;
34
+ readonly installId: string;
35
+ readonly limit: number;
36
+ }) => ["insights", "installation-events", input] as const,
37
+ };
38
+
39
+ export const useReportingInstallationsQuery = (input: InsightsOverviewInput) =>
40
+ useQuery({
41
+ queryKey: queryKeys.reportingInstallations(input),
42
+ queryFn: () => getReportingInstallationsRpc({ data: input }),
43
+ refetchOnWindowFocus: true,
44
+ staleTime: STALE_TIME_MS,
45
+ });
46
+
47
+ export const useInsightsEventsQuery = (
48
+ input: InsightsEventPageInput,
49
+ enabled: boolean,
50
+ ) =>
51
+ useQuery({
52
+ queryKey: queryKeys.events(input),
53
+ queryFn: () => listInsightsEventsRpc({ data: input }),
54
+ enabled,
55
+ refetchOnWindowFocus: true,
56
+ staleTime: STALE_TIME_MS,
57
+ });
58
+
59
+ export const useInsightsInstallationsQuery = (
60
+ input: {
61
+ readonly cursor?: string;
62
+ readonly identity: string;
63
+ readonly limit: number;
64
+ },
65
+ enabled: boolean,
66
+ ) =>
67
+ useQuery({
68
+ queryKey: queryKeys.installations(input),
69
+ queryFn: () => findInsightsInstallationsRpc({ data: input }),
70
+ enabled: enabled && input.identity.length > 0,
71
+ staleTime: STALE_TIME_MS,
72
+ });
73
+
74
+ export const useInsightsInstallationQuery = (
75
+ installId: string,
76
+ enabled: boolean,
77
+ ) =>
78
+ useQuery({
79
+ queryKey: queryKeys.installation(installId),
80
+ queryFn: () => getInsightsInstallationRpc({ data: { installId } }),
81
+ enabled: enabled && installId.length > 0,
82
+ staleTime: STALE_TIME_MS,
83
+ });
84
+
85
+ export const useInsightsInstallationEventsQuery = (
86
+ input: {
87
+ readonly beforeReceivedAtMs: number;
88
+ readonly cursor?: string;
89
+ readonly installId: string;
90
+ readonly limit: number;
91
+ },
92
+ enabled: boolean,
93
+ ) =>
94
+ useQuery({
95
+ queryKey: queryKeys.installationEvents(input),
96
+ queryFn: () => listInsightsInstallationEventsRpc({ data: input }),
97
+ enabled: enabled && input.installId.length > 0,
98
+ refetchOnWindowFocus: true,
99
+ staleTime: STALE_TIME_MS,
100
+ });