@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.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/HashValueDisplay-CB0Eskit.js +1 -0
- package/.output/public/assets/InsightsPageHeader-Car5LNiZ.js +1 -0
- package/.output/public/assets/api-keys-BzKocqRy.js +1 -0
- package/.output/public/assets/api-keys-api-BHcODB7q.js +1 -0
- package/.output/public/assets/card-Dhr_eFYj.js +1 -0
- package/.output/public/assets/{chart-no-axes-combined-FaYMGmQS.js → chart-no-axes-combined-Dy4WJv2U.js} +1 -1
- package/.output/public/assets/composite-Bp98A0Ld.js +1 -0
- package/.output/public/assets/createServerFn-CA8fs7QD.js +3 -0
- package/.output/public/assets/dialog-CUTrcPlB.js +1 -0
- package/.output/public/assets/{dist-DspNKfMj.js → dist-C_NydL0e.js} +1 -1
- package/.output/public/assets/index-DdAz-CXv.js +10 -0
- package/.output/public/assets/input-group-cSOG4wh_.js +1 -0
- package/.output/public/assets/insights-C0PBSZ-n.js +1 -0
- package/.output/public/assets/installations-CE33wgbY.js +2 -0
- package/.output/public/assets/installations-CStZja8x.js +1 -0
- package/.output/public/assets/lazyRouteComponent-Ccp3bwBY.js +1 -0
- package/.output/public/assets/link-CI8idAUi.js +1 -0
- package/.output/public/assets/loader-circle-BoxLrsOu.js +1 -0
- package/.output/public/assets/refresh-cw-Bqc72UZG.js +1 -0
- package/.output/public/assets/{routes-DcQnVajO.js → routes-DGVXdzpw.js} +2 -2
- package/.output/public/assets/routes-DTlwOr8I.js +1 -0
- package/.output/public/assets/{shield-check-DE_hVuHu.js → shield-check-uK3MHWJZ.js} +1 -1
- package/.output/public/assets/signing-CMf4aqDO.js +1 -0
- package/.output/public/assets/styles-BCvbtKsm.css +2 -0
- package/.output/public/assets/table-2vcV89QB.js +1 -0
- package/.output/public/assets/useCompositeListItem-CHLITE_4.js +1 -0
- package/.output/public/assets/x-DCd0j8c6.js +1 -0
- package/.output/server/{__23tanstack-start-server-fn-resolver-DBP9TQ7K.mjs → __23tanstack-start-server-fn-resolver-B7ioJhtE.mjs} +44 -52
- package/.output/server/_libs/@base-ui/react+[...].mjs +1906 -4040
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +52 -4
- package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +2 -2
- package/.output/server/_libs/lucide-react.mjs +36 -3
- package/.output/server/_libs/next-themes.mjs +2 -2
- package/.output/server/_libs/sonner.mjs +2 -2
- package/.output/server/_libs/tanstack__react-query.mjs +2 -2
- package/.output/server/_libs/verkit.mjs +20 -15
- package/.output/server/_runtime.mjs +1 -10
- package/.output/server/_ssr/HashValueDisplay-DwpGLfrl.mjs +55 -0
- package/.output/server/_ssr/InsightsPageHeader-Df4Brj8u.mjs +484 -0
- package/.output/server/_ssr/{api-keys-BnM_o2nS.mjs → api-keys-CZeTPQys.mjs} +11 -13
- package/.output/server/_ssr/{api-keys-api-CIywsSF5.mjs → api-keys-api-BUPQVSpk.mjs} +2 -2
- package/.output/server/_ssr/{api-keys-rpc-BSp1wkIV.mjs → api-keys-rpc-Cs7tS5_j.mjs} +5 -5
- package/.output/server/_ssr/{api-rpc-cG5wd_tO.mjs → api-rpc-tAupfGQm.mjs} +73 -142
- package/.output/server/_ssr/{auth-rpc-BrJ6obHJ.mjs → auth-rpc-DUaeOWKK.mjs} +4 -4
- package/.output/server/_ssr/{auth.server-DwKoZoQM.mjs → auth.server-DW71WzsD.mjs} +2 -5
- package/.output/server/_ssr/{bundle-signing-rpc-COoQhW8E.mjs → bundle-signing-rpc-BhnBw0-d.mjs} +2 -2
- package/.output/server/_ssr/bundle-signing.server-C4NW5mWx.mjs +12 -0
- package/.output/server/_ssr/{alert-Dim3MGY5.mjs → card-BRiPmi0C.mjs} +61 -39
- package/.output/server/_ssr/{config.server-DEiLhKmy.mjs → config.server-afDuAwaG.mjs} +15 -8
- package/.output/server/_ssr/{createSsrRpc-9P0S0lJ3.mjs → createSsrRpc-DjWNJ0uc.mjs} +6 -6
- package/.output/server/_ssr/databaseJsonValue-d6I3nAS4.mjs +45 -0
- package/.output/server/_ssr/{databaseRows-CwbNwYSl.mjs → databaseRows-5bCRTxm-.mjs} +4 -44
- package/.output/server/_ssr/{deleteBundle-Cu_XOMt0.mjs → deleteBundle-BTGwBgzx.mjs} +2 -2
- package/.output/server/_ssr/{dialog-CQuRMNZ4.mjs → dialog-DS-nw-pA.mjs} +2 -2
- package/.output/server/_ssr/{dist-BVSLTpoP.mjs → dist-CyzPaI5v.mjs} +2 -2
- package/.output/server/_ssr/{getBundleChildren-BcRMFoDF.mjs → getBundleChildren-C0vh4ZvE.mjs} +2 -2
- package/.output/server/_ssr/input-group-BpsnmvZw.mjs +70 -0
- package/.output/server/_ssr/insights-BdS364Xu.mjs +460 -0
- package/.output/server/_ssr/insights-rpc-38DujK0G.mjs +57 -0
- package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +303 -0
- package/.output/server/_ssr/installations-CtA59MQG.mjs +29 -0
- package/.output/server/_ssr/installations-DiVKEF5T.mjs +627 -0
- package/.output/server/_ssr/{router-C-KUBfOi.mjs → router-B9wG_9f8.mjs} +37 -46
- package/.output/server/_ssr/{routes-Z7TwC6nC.mjs → routes-CKrrS2q2.mjs} +3 -3
- package/.output/server/_ssr/{routes-BM4ht9jb.mjs → routes-D7Yh_jfj.mjs} +402 -390
- package/.output/server/_ssr/runtime.server-DacU1TBV.mjs +438 -0
- package/.output/server/_ssr/{server-BjzmvXnO.mjs → server-D_C2rMzf.mjs} +5 -5
- package/.output/server/_ssr/{signing-k8GxyBDF.mjs → signing-Dz1WkZOf.mjs} +16 -85
- package/.output/server/_ssr/ssr.mjs +1 -1
- package/.output/server/_ssr/{field-DRIXKjZG.mjs → table-CnZmon_Z.mjs} +51 -5
- package/.output/server/_ssr/{uuidv7-BbJ-9D_I.mjs → uuidv7-D0QFkPL_.mjs} +3 -3
- package/.output/server/_tanstack-start-manifest_v-2MjTyxil.mjs +97 -0
- package/.output/server/index.mjs +168 -203
- package/lib/index.d.mts +4 -4
- package/lib/vite-internal.mjs +1 -1
- package/lib/vite.d.mts +0 -1
- package/package.json +8 -8
- package/src/components/AppSidebar.tsx +12 -16
- package/src/components/ConsoleAccessPage.tsx +1 -1
- package/src/components/HashValueDisplay.tsx +11 -2
- package/src/components/features/api-keys/ApiKeysPage.tsx +1 -1
- package/src/components/features/bundles/BundleChildrenPanel.tsx +7 -6
- package/src/components/features/bundles/BundleMetadata.tsx +1 -20
- package/src/components/features/channels/ChannelManagementDialog.tsx +1 -1
- package/src/components/features/insights/EventDetails.tsx +151 -0
- package/src/components/features/insights/EventHistoryCard.tsx +245 -0
- package/src/components/features/insights/EventHistoryList.tsx +63 -0
- package/src/components/features/insights/InsightsBundleReports.tsx +51 -0
- package/src/components/features/insights/InsightsControls.tsx +121 -0
- package/src/components/features/{analytics/AnalyticsErrorAlert.tsx → insights/InsightsErrorAlert.tsx} +6 -6
- package/src/components/features/insights/InsightsOverview.tsx +203 -0
- package/src/components/features/insights/InsightsPageHeader.tsx +48 -0
- package/src/components/features/insights/InsightsPagination.tsx +52 -0
- package/src/components/features/insights/InstallationHistoryCard.tsx +250 -0
- package/src/components/features/insights/InstallationMatchesCard.tsx +178 -0
- package/src/components/features/insights/InstallationPageHeader.tsx +90 -0
- package/src/components/features/releases/ReleaseEditorSheet.tsx +24 -24
- package/src/components/features/signing/BundleSigningPage.tsx +18 -126
- package/src/components/ui/badge.tsx +2 -0
- package/src/components/ui/combobox.tsx +1 -0
- package/src/components/ui/skeleton.tsx +4 -1
- package/src/components/ui/tooltip.tsx +1 -1
- package/src/index.ts +7 -3
- package/src/lib/api-rpc.ts +19 -90
- package/src/lib/api.ts +0 -99
- package/src/lib/insights-api.ts +100 -0
- package/src/lib/insights-rpc.ts +88 -0
- package/src/lib/insights-view.ts +15 -0
- package/src/lib/public-bundle-error.ts +19 -0
- package/src/lib/server/bundle-signing.server.ts +5 -82
- package/src/lib/server/console-runtime.server.ts +0 -9
- package/src/lib/server/releaseReachability.ts +1 -4
- package/src/lib/server/runtime.server.ts +6 -142
- package/src/routeTree.gen.ts +21 -21
- package/src/router.tsx +3 -0
- package/src/routes/-installations-search.ts +25 -12
- package/src/routes/__root.tsx +6 -16
- package/src/routes/api-keys.tsx +1 -1
- package/src/routes/index.tsx +21 -81
- package/src/routes/insights.tsx +84 -0
- package/src/routes/installations.tsx +294 -142
- package/src/styles.css +6 -0
- package/.output/public/assets/AnalyticsCapabilityContext-BCke57-7.js +0 -1
- package/.output/public/assets/BundleActivityChart-CnsBe_1J.js +0 -42
- package/.output/public/assets/BundleIdDisplay-Dky75zLU.js +0 -1
- package/.output/public/assets/alert-C-1KvDW8.js +0 -1
- package/.output/public/assets/analytics-Bqz0c-BT.js +0 -1
- package/.output/public/assets/analytics-api-CJdrTLLO.js +0 -1
- package/.output/public/assets/api-B_Rh-uzC.js +0 -1
- package/.output/public/assets/api-keys-BurCFhzH.js +0 -1
- package/.output/public/assets/api-keys-api-BEDJdQg3.js +0 -1
- package/.output/public/assets/badge-Bf0fZ-Gx.js +0 -1
- package/.output/public/assets/createServerFn-gM2-LFBy.js +0 -3
- package/.output/public/assets/dialog-BS60BRr2.js +0 -1
- package/.output/public/assets/download-O-yNBc9H.js +0 -1
- package/.output/public/assets/field-Buq148-A.js +0 -1
- package/.output/public/assets/index-kFhd2S78.js +0 -10
- package/.output/public/assets/installations-BrQ-od41.js +0 -2
- package/.output/public/assets/installations-CGlZ3ROz.js +0 -1
- package/.output/public/assets/lazyRouteComponent-rPzgA7S-.js +0 -1
- package/.output/public/assets/loader-circle-DCYF-wJl.js +0 -1
- package/.output/public/assets/refresh-cw-n8XYbPPj.js +0 -1
- package/.output/public/assets/routes-CrdokJ80.js +0 -1
- package/.output/public/assets/search-DLhbWw3W.js +0 -1
- package/.output/public/assets/signing-HRexfjsH.js +0 -1
- package/.output/public/assets/styles-eB4EtvFS.css +0 -2
- package/.output/public/assets/table-D8CLieHX.js +0 -1
- package/.output/public/assets/textarea-YJLTVAoq.js +0 -1
- package/.output/public/assets/useMutation-JsGVjQMY.js +0 -1
- package/.output/public/assets/useOpenInteractionType-4cRD5xO0.js +0 -1
- package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +0 -1554
- package/.output/server/_libs/d3-array.mjs +0 -231
- package/.output/server/_libs/d3-color.mjs +0 -330
- package/.output/server/_libs/d3-format.mjs +0 -252
- package/.output/server/_libs/d3-interpolate.mjs +0 -207
- package/.output/server/_libs/d3-path.mjs +0 -77
- package/.output/server/_libs/d3-scale+[...].mjs +0 -1907
- package/.output/server/_libs/d3-shape.mjs +0 -818
- package/.output/server/_libs/decimal.js-light.mjs +0 -884
- package/.output/server/_libs/es-toolkit.mjs +0 -1134
- package/.output/server/_libs/eventemitter3.mjs +0 -236
- package/.output/server/_libs/immer.mjs +0 -532
- package/.output/server/_libs/react-redux+[...].mjs +0 -205
- package/.output/server/_libs/recharts+[...].mjs +0 -14117
- package/.output/server/_ssr/AnalyticsCapabilityContext-ui4DX8GO.mjs +0 -15
- package/.output/server/_ssr/BundleActivityChart-BiB9ZORH.mjs +0 -288
- package/.output/server/_ssr/BundleIdDisplay-Czk6iR6p.mjs +0 -13
- package/.output/server/_ssr/analytics-DtMTn8fn.mjs +0 -858
- package/.output/server/_ssr/analytics-api-Cg1M5v_H.mjs +0 -71
- package/.output/server/_ssr/analytics-input-fRRRn0uA.mjs +0 -96
- package/.output/server/_ssr/analytics-rpc-DY5uXvHT.mjs +0 -155
- package/.output/server/_ssr/api-BTSv1tZb.mjs +0 -282
- package/.output/server/_ssr/badge-DCxbEeof.mjs +0 -26
- package/.output/server/_ssr/bundle-signing.server-D5u8KkMp.mjs +0 -51
- package/.output/server/_ssr/installations-B-azgAvX.mjs +0 -19
- package/.output/server/_ssr/installations-Ctvzp2JH.mjs +0 -535
- package/.output/server/_ssr/runtime.server-awTLiiXW.mjs +0 -527
- package/.output/server/_ssr/table-v1yCvYo7.mjs +0 -53
- package/.output/server/_ssr/textarea-DYHnskhb.mjs +0 -14
- package/.output/server/_tanstack-start-manifest_v-CH4StzJu.mjs +0 -108
- package/src/components/features/analytics/ActivityChart.tsx +0 -150
- package/src/components/features/analytics/AnalyticsCapabilityContext.tsx +0 -24
- package/src/components/features/analytics/AnalyticsControls.tsx +0 -115
- package/src/components/features/analytics/AnalyticsOverview.tsx +0 -208
- package/src/components/features/analytics/BundleDistribution.tsx +0 -133
- package/src/components/features/analytics/BundleSelector.tsx +0 -114
- package/src/components/features/analytics/InstallationHistoryCard.tsx +0 -248
- package/src/components/features/analytics/InstallationMatchesCard.tsx +0 -132
- package/src/components/features/analytics/InstallationPageHeader.tsx +0 -138
- package/src/components/features/analytics/InstallationPagination.tsx +0 -48
- package/src/components/features/analytics/InstallationSearch.tsx +0 -230
- package/src/components/features/analytics/UpdateOutcomes.tsx +0 -147
- package/src/components/features/bundles/BundleActivityChart.tsx +0 -193
- package/src/components/features/bundles/BundleAnalyticsSummary.tsx +0 -123
- package/src/lib/analytics-api.ts +0 -145
- package/src/lib/analytics-error.ts +0 -19
- package/src/lib/analytics-input.ts +0 -187
- package/src/lib/analytics-overview.ts +0 -170
- package/src/lib/analytics-rpc.ts +0 -218
- package/src/lib/server/releaseActivity.ts +0 -27
- package/src/routes/analytics.tsx +0 -139
- /package/.output/public/assets/{preload-helper-TKOBYGYD.js → preload-helper-CQ36nxok.js} +0 -0
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
Sun,
|
|
9
9
|
} from "lucide-react";
|
|
10
10
|
|
|
11
|
-
import { useAnalyticsCapability } from "@/components/features/analytics/AnalyticsCapabilityContext";
|
|
12
11
|
import { HotUpdaterLogo } from "@/components/HotUpdaterLogo";
|
|
13
12
|
import { useTheme } from "@/components/ThemeProvider";
|
|
14
13
|
import {
|
|
@@ -26,15 +25,14 @@ import {
|
|
|
26
25
|
import { useApiKeyCapabilityQuery } from "@/lib/api-keys-api";
|
|
27
26
|
|
|
28
27
|
export function AppSidebar() {
|
|
29
|
-
const analyticsCapability = useAnalyticsCapability();
|
|
30
28
|
const apiKeyCapability = useApiKeyCapabilityQuery();
|
|
31
29
|
const { theme, setTheme } = useTheme();
|
|
32
30
|
const routerState = useRouterState();
|
|
33
31
|
const currentPath = routerState.location.pathname;
|
|
34
32
|
|
|
35
33
|
const isBundlesActive = currentPath === "/";
|
|
36
|
-
const
|
|
37
|
-
currentPath === "/
|
|
34
|
+
const isInsightsActive =
|
|
35
|
+
currentPath === "/insights" || currentPath === "/installations";
|
|
38
36
|
const isApiKeysActive = currentPath === "/api-keys";
|
|
39
37
|
const isSigningActive = currentPath === "/signing";
|
|
40
38
|
|
|
@@ -98,18 +96,16 @@ export function AppSidebar() {
|
|
|
98
96
|
<span>Bundles</span>
|
|
99
97
|
</SidebarMenuButton>
|
|
100
98
|
</SidebarMenuItem>
|
|
101
|
-
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
</SidebarMenuItem>
|
|
112
|
-
) : null}
|
|
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>
|
|
113
109
|
{apiKeyCapability.data?.apiKeys ? (
|
|
114
110
|
<SidebarMenuItem>
|
|
115
111
|
<SidebarMenuButton
|
|
@@ -8,15 +8,22 @@ interface HashValueDisplayProps {
|
|
|
8
8
|
value: string;
|
|
9
9
|
maxLength?: number;
|
|
10
10
|
className?: string;
|
|
11
|
+
buttonClassName?: string;
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
export const shortenIdentifier = (value: string, maxLength = 12) =>
|
|
15
|
+
value.length > maxLength
|
|
16
|
+
? `${value.slice(0, Math.max(1, maxLength - 4))}…${value.slice(-4)}`
|
|
17
|
+
: value;
|
|
18
|
+
|
|
13
19
|
export function HashValueDisplay({
|
|
14
20
|
value,
|
|
15
21
|
maxLength = 12,
|
|
16
22
|
className,
|
|
23
|
+
buttonClassName,
|
|
17
24
|
}: HashValueDisplayProps) {
|
|
18
25
|
const isTruncated = value.length > maxLength;
|
|
19
|
-
const truncated =
|
|
26
|
+
const truncated = shortenIdentifier(value, maxLength);
|
|
20
27
|
|
|
21
28
|
const copyValue = async () => {
|
|
22
29
|
try {
|
|
@@ -61,12 +68,14 @@ export function HashValueDisplay({
|
|
|
61
68
|
render={
|
|
62
69
|
<button
|
|
63
70
|
type="button"
|
|
71
|
+
aria-label={`Copy full value: ${value}`}
|
|
64
72
|
translate="no"
|
|
65
73
|
className={cn(
|
|
66
74
|
"ring-ring/30 bg-muted/40 border-border/70 inline-flex min-w-0 max-w-full cursor-pointer items-center rounded-md border px-1.5 py-0.5 align-top shadow-xs outline-none transition-[background-color,border-color,transform,box-shadow]",
|
|
67
75
|
"hover:bg-muted/70 active:scale-[0.98] active:bg-muted/85 focus-visible:ring-[2px]",
|
|
68
|
-
"touch-manipulation select-none",
|
|
76
|
+
"touch-manipulation select-none motion-reduce:transition-none motion-reduce:active:scale-100",
|
|
69
77
|
!isTruncated && "cursor-copy",
|
|
78
|
+
buttonClassName,
|
|
70
79
|
)}
|
|
71
80
|
onClick={handleClick}
|
|
72
81
|
onKeyDown={handleKeyDown}
|
|
@@ -236,7 +236,7 @@ function RevokeApiKeyDialog({ record }: { record: ApiKeyView }) {
|
|
|
236
236
|
<AlertDialogTitle>Revoke {record.name}?</AlertDialogTitle>
|
|
237
237
|
<AlertDialogDescription>
|
|
238
238
|
Apps using {record.prefix}… will immediately lose OTA update and
|
|
239
|
-
|
|
239
|
+
insights access. This action cannot be undone.
|
|
240
240
|
</AlertDialogDescription>
|
|
241
241
|
</AlertDialogHeader>
|
|
242
242
|
<AlertDialogFooter>
|
|
@@ -40,9 +40,10 @@ export function BundleChildrenPanel({
|
|
|
40
40
|
id={panelId}
|
|
41
41
|
>
|
|
42
42
|
<div className="flex flex-col gap-4">
|
|
43
|
+
<h3 className="text-sm font-medium">Advanced artifact diagnostics</h3>
|
|
43
44
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
44
45
|
<div className="flex min-w-0 items-start gap-2 text-sm sm:items-center">
|
|
45
|
-
<span className="text-muted-foreground">Base
|
|
46
|
+
<span className="text-muted-foreground">Base artifact ID</span>
|
|
46
47
|
<BundleIdDisplay bundleId={bundle.id} fullOnMobile maxLength={18} />
|
|
47
48
|
</div>
|
|
48
49
|
<Badge variant="outline">
|
|
@@ -58,7 +59,7 @@ export function BundleChildrenPanel({
|
|
|
58
59
|
) : bundles.length > 0 ? (
|
|
59
60
|
<div className="flex flex-col gap-2">
|
|
60
61
|
<div className="text-xs font-semibold uppercase text-muted-foreground/70">
|
|
61
|
-
Patch
|
|
62
|
+
Patch artifacts from this base
|
|
62
63
|
</div>
|
|
63
64
|
{isMobile ? (
|
|
64
65
|
<div className="flex flex-col gap-2">
|
|
@@ -70,7 +71,7 @@ export function BundleChildrenPanel({
|
|
|
70
71
|
<div className="flex flex-col gap-3">
|
|
71
72
|
<div className="space-y-1">
|
|
72
73
|
<div className="text-[11px] font-medium uppercase text-muted-foreground/70">
|
|
73
|
-
|
|
74
|
+
Target artifact ID
|
|
74
75
|
</div>
|
|
75
76
|
<BundleIdDisplay
|
|
76
77
|
bundleId={childBundle.id}
|
|
@@ -130,7 +131,7 @@ export function BundleChildrenPanel({
|
|
|
130
131
|
<Table className="min-w-max">
|
|
131
132
|
<TableHeader>
|
|
132
133
|
<TableRow className="hover:bg-transparent">
|
|
133
|
-
<TableHead>
|
|
134
|
+
<TableHead>Target artifact ID</TableHead>
|
|
134
135
|
<TableHead>Relation</TableHead>
|
|
135
136
|
<TableHead>Artifact</TableHead>
|
|
136
137
|
<TableHead>Created</TableHead>
|
|
@@ -188,10 +189,10 @@ export function BundleChildrenPanel({
|
|
|
188
189
|
) : (
|
|
189
190
|
<div className="flex flex-col gap-2">
|
|
190
191
|
<div className="text-xs font-semibold uppercase text-muted-foreground/70">
|
|
191
|
-
Patch
|
|
192
|
+
Patch artifacts from this base
|
|
192
193
|
</div>
|
|
193
194
|
<div className="rounded-md border bg-background p-3 text-sm text-muted-foreground">
|
|
194
|
-
No direct patch
|
|
195
|
+
No direct patch artifacts.
|
|
195
196
|
</div>
|
|
196
197
|
</div>
|
|
197
198
|
)}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getPatchBaseBundleId,
|
|
3
|
-
getPatchBaseFileHash,
|
|
4
|
-
getPatchFileHash,
|
|
5
|
-
} from "@hot-updater/core";
|
|
1
|
+
import { getPatchBaseFileHash, getPatchFileHash } from "@hot-updater/core";
|
|
6
2
|
import type { Bundle, ReleaseRow } from "@hot-updater/plugin-core";
|
|
7
3
|
import { ExternalLink } from "lucide-react";
|
|
8
4
|
|
|
9
|
-
import { BundleIdDisplay } from "@/components/BundleIdDisplay";
|
|
10
5
|
import { HashValueDisplay } from "@/components/HashValueDisplay";
|
|
11
6
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
12
7
|
import { useConfigQuery } from "@/lib/api";
|
|
@@ -28,7 +23,6 @@ function Row({ label, value }: { label: string; value: React.ReactNode }) {
|
|
|
28
23
|
|
|
29
24
|
export function BundleMetadata({ bundle, release }: BundleMetadataProps) {
|
|
30
25
|
const { data: configData, isFetched } = useConfigQuery();
|
|
31
|
-
const patchBaseBundleId = bundle ? getPatchBaseBundleId(bundle) : null;
|
|
32
26
|
const hbcPatchFileHash = bundle ? getPatchFileHash(bundle) : null;
|
|
33
27
|
const hbcPatchBaseFileHash = bundle ? getPatchBaseFileHash(bundle) : null;
|
|
34
28
|
const gitCommitUrl =
|
|
@@ -43,7 +37,6 @@ export function BundleMetadata({ bundle, release }: BundleMetadataProps) {
|
|
|
43
37
|
target ||
|
|
44
38
|
bundle?.gitCommitHash ||
|
|
45
39
|
bundle?.fileHash ||
|
|
46
|
-
patchBaseBundleId ||
|
|
47
40
|
hbcPatchBaseFileHash ||
|
|
48
41
|
hbcPatchFileHash;
|
|
49
42
|
|
|
@@ -101,18 +94,6 @@ export function BundleMetadata({ bundle, release }: BundleMetadataProps) {
|
|
|
101
94
|
}
|
|
102
95
|
/>
|
|
103
96
|
) : 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
97
|
{hbcPatchBaseFileHash ? (
|
|
117
98
|
<Row
|
|
118
99
|
label="Base hash"
|
|
@@ -107,7 +107,7 @@ export function ChannelManagementDialog({
|
|
|
107
107
|
Channels
|
|
108
108
|
</DialogTitle>
|
|
109
109
|
<DialogDescription>
|
|
110
|
-
Create deployment channels and remove them when no
|
|
110
|
+
Create deployment channels and remove them when no bundle uses
|
|
111
111
|
them. Empty channels remain available until you delete them.
|
|
112
112
|
</DialogDescription>
|
|
113
113
|
</DialogHeader>
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Activity,
|
|
3
|
+
Check,
|
|
4
|
+
ChevronDown,
|
|
5
|
+
PackageCheck,
|
|
6
|
+
RotateCcw,
|
|
7
|
+
} from "lucide-react";
|
|
8
|
+
import { useEffect, useState } from "react";
|
|
9
|
+
|
|
10
|
+
import { HashValueDisplay } from "@/components/HashValueDisplay";
|
|
11
|
+
import { Badge } from "@/components/ui/badge";
|
|
12
|
+
import type { InsightsEventRow } from "@/lib/insights-view";
|
|
13
|
+
|
|
14
|
+
type EventHistoryRow = InsightsEventRow;
|
|
15
|
+
|
|
16
|
+
const eventTypes = {
|
|
17
|
+
UPDATE_APPLIED: { label: "Bundle applied", variant: "success", icon: Check },
|
|
18
|
+
RECOVERED: { label: "Recovered", variant: "warning", icon: RotateCcw },
|
|
19
|
+
RELEASE_ADOPTED: {
|
|
20
|
+
label: "Bundle adopted",
|
|
21
|
+
variant: "success",
|
|
22
|
+
icon: PackageCheck,
|
|
23
|
+
},
|
|
24
|
+
UNCHANGED: {
|
|
25
|
+
label: "Activity reported",
|
|
26
|
+
variant: "secondary",
|
|
27
|
+
icon: Activity,
|
|
28
|
+
},
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
export function useInsightsTimeFormat() {
|
|
32
|
+
const [timeZone, setTimeZone] = useState("UTC");
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
setTimeZone(Intl.DateTimeFormat().resolvedOptions().timeZone);
|
|
35
|
+
}, []);
|
|
36
|
+
return new Intl.DateTimeFormat("en", {
|
|
37
|
+
timeZone,
|
|
38
|
+
timeZoneName: "shortOffset",
|
|
39
|
+
year: "numeric",
|
|
40
|
+
month: "2-digit",
|
|
41
|
+
day: "2-digit",
|
|
42
|
+
hour: "2-digit",
|
|
43
|
+
minute: "2-digit",
|
|
44
|
+
second: "2-digit",
|
|
45
|
+
hourCycle: "h23",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function formatInsightsTimestamp(
|
|
50
|
+
value: number,
|
|
51
|
+
formatter: Intl.DateTimeFormat,
|
|
52
|
+
) {
|
|
53
|
+
const parts = Object.fromEntries(
|
|
54
|
+
formatter
|
|
55
|
+
.formatToParts(new Date(value))
|
|
56
|
+
.map(({ type, value }) => [type, value]),
|
|
57
|
+
);
|
|
58
|
+
const offset = parts.timeZoneName === "GMT" ? "GMT+0" : parts.timeZoneName;
|
|
59
|
+
return `${parts.year}/${parts.month}/${parts.day} ${parts.hour}:${parts.minute}:${parts.second} ${offset}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function EventTimestamp({
|
|
63
|
+
value,
|
|
64
|
+
formatter,
|
|
65
|
+
touch = false,
|
|
66
|
+
}: {
|
|
67
|
+
readonly value: number;
|
|
68
|
+
readonly formatter: Intl.DateTimeFormat;
|
|
69
|
+
readonly touch?: boolean;
|
|
70
|
+
}) {
|
|
71
|
+
const date = new Date(value);
|
|
72
|
+
const localTime = formatInsightsTimestamp(value, formatter);
|
|
73
|
+
return (
|
|
74
|
+
<details className="group/time">
|
|
75
|
+
<summary
|
|
76
|
+
className={`cursor-pointer list-none rounded-md outline-none focus-visible:ring-2 focus-visible:ring-ring/30 [&::-webkit-details-marker]:hidden ${touch ? "flex min-h-11 items-center" : ""}`}
|
|
77
|
+
>
|
|
78
|
+
<span className="flex items-center gap-1">
|
|
79
|
+
<time dateTime={date.toISOString()} className="whitespace-nowrap">
|
|
80
|
+
{localTime}
|
|
81
|
+
</time>
|
|
82
|
+
<ChevronDown
|
|
83
|
+
aria-hidden="true"
|
|
84
|
+
className="size-3 text-muted-foreground group-open/time:rotate-180"
|
|
85
|
+
/>
|
|
86
|
+
</span>
|
|
87
|
+
</summary>
|
|
88
|
+
<p className="mt-2 text-muted-foreground">
|
|
89
|
+
{date.toISOString().replace("T", " ").replace("Z", " UTC")}
|
|
90
|
+
</p>
|
|
91
|
+
</details>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function EventTypeBadge({
|
|
96
|
+
type,
|
|
97
|
+
}: {
|
|
98
|
+
readonly type: EventHistoryRow["type"];
|
|
99
|
+
}) {
|
|
100
|
+
const eventType = eventTypes[type];
|
|
101
|
+
const Icon = eventType.icon;
|
|
102
|
+
return (
|
|
103
|
+
<Badge
|
|
104
|
+
variant={eventType.variant}
|
|
105
|
+
className="gap-1 whitespace-nowrap [&_svg]:size-3"
|
|
106
|
+
title={
|
|
107
|
+
type === "UNCHANGED"
|
|
108
|
+
? "App activity reported on the current bundle without a bundle transition."
|
|
109
|
+
: type === "RELEASE_ADOPTED"
|
|
110
|
+
? "A different release was adopted without changing the bundle."
|
|
111
|
+
: undefined
|
|
112
|
+
}
|
|
113
|
+
>
|
|
114
|
+
<Icon aria-hidden="true" />
|
|
115
|
+
{eventType.label}
|
|
116
|
+
</Badge>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function EventBundleTransition({
|
|
121
|
+
event,
|
|
122
|
+
touch = false,
|
|
123
|
+
}: {
|
|
124
|
+
readonly event: Pick<EventHistoryRow, "type" | "fromBundleId" | "toBundleId">;
|
|
125
|
+
readonly touch?: boolean;
|
|
126
|
+
}) {
|
|
127
|
+
return (
|
|
128
|
+
<dl className="grid grid-cols-[auto_minmax(0,1fr)] items-center gap-x-2 gap-y-2">
|
|
129
|
+
{event.fromBundleId ? (
|
|
130
|
+
<>
|
|
131
|
+
<dt className="text-muted-foreground">From</dt>
|
|
132
|
+
<dd>
|
|
133
|
+
<HashValueDisplay
|
|
134
|
+
value={event.fromBundleId}
|
|
135
|
+
buttonClassName={touch ? "min-h-11 px-3" : undefined}
|
|
136
|
+
/>
|
|
137
|
+
</dd>
|
|
138
|
+
</>
|
|
139
|
+
) : null}
|
|
140
|
+
<dt className="text-muted-foreground">
|
|
141
|
+
{event.type === "UNCHANGED" ? "Current" : "To"}
|
|
142
|
+
</dt>
|
|
143
|
+
<dd>
|
|
144
|
+
<HashValueDisplay
|
|
145
|
+
value={event.toBundleId}
|
|
146
|
+
buttonClassName={touch ? "min-h-11 px-3" : undefined}
|
|
147
|
+
/>
|
|
148
|
+
</dd>
|
|
149
|
+
</dl>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { Link } from "@tanstack/react-router";
|
|
2
|
+
import { ArrowUpRight, RefreshCw } from "lucide-react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
import { HashValueDisplay } from "@/components/HashValueDisplay";
|
|
6
|
+
import { Button, buttonVariants } from "@/components/ui/button";
|
|
7
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
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 type { InsightsEventRow, InsightsViewPage } from "@/lib/insights-view";
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
EventBundleTransition,
|
|
21
|
+
EventTimestamp,
|
|
22
|
+
EventTypeBadge,
|
|
23
|
+
useInsightsTimeFormat,
|
|
24
|
+
} from "./EventDetails";
|
|
25
|
+
import { EventHistoryList } from "./EventHistoryList";
|
|
26
|
+
import { InsightsErrorAlert } from "./InsightsErrorAlert";
|
|
27
|
+
import { InsightsPagination } from "./InsightsPagination";
|
|
28
|
+
|
|
29
|
+
type EventsLocationState = {
|
|
30
|
+
readonly eventsBefore: number;
|
|
31
|
+
readonly eventsCursor?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function EventIdentity({
|
|
35
|
+
event,
|
|
36
|
+
eventsLocation,
|
|
37
|
+
touch = false,
|
|
38
|
+
}: {
|
|
39
|
+
readonly event: InsightsEventRow;
|
|
40
|
+
readonly eventsLocation: EventsLocationState;
|
|
41
|
+
readonly touch?: boolean;
|
|
42
|
+
}) {
|
|
43
|
+
return (
|
|
44
|
+
<div
|
|
45
|
+
className={
|
|
46
|
+
touch
|
|
47
|
+
? "flex min-w-0 flex-wrap items-center justify-between gap-2"
|
|
48
|
+
: "flex min-w-0 flex-col items-start gap-2"
|
|
49
|
+
}
|
|
50
|
+
>
|
|
51
|
+
<Link
|
|
52
|
+
className={buttonVariants({
|
|
53
|
+
className: touch
|
|
54
|
+
? "-ml-2 h-auto min-h-11 min-w-0 flex-1 justify-start gap-2 whitespace-normal text-sm"
|
|
55
|
+
: "-ml-2 max-w-full justify-start",
|
|
56
|
+
size: "sm",
|
|
57
|
+
variant: "ghost",
|
|
58
|
+
})}
|
|
59
|
+
to="/installations"
|
|
60
|
+
search={{
|
|
61
|
+
...eventsLocation,
|
|
62
|
+
query: event.installId || undefined,
|
|
63
|
+
installId: event.installId,
|
|
64
|
+
}}
|
|
65
|
+
aria-label={`View history for ${event.userId ?? event.username ?? "anonymous installation"} (${event.installId})`}
|
|
66
|
+
>
|
|
67
|
+
<span
|
|
68
|
+
className={touch ? "min-w-0 text-left wrap-anywhere" : "truncate"}
|
|
69
|
+
>
|
|
70
|
+
{event.userId ?? event.username ?? "Anonymous installation"}
|
|
71
|
+
</span>
|
|
72
|
+
<ArrowUpRight aria-hidden="true" data-icon="inline-end" />
|
|
73
|
+
</Link>
|
|
74
|
+
<HashValueDisplay
|
|
75
|
+
value={event.installId}
|
|
76
|
+
maxLength={13}
|
|
77
|
+
buttonClassName={touch ? "min-h-11 px-3" : undefined}
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function EventHistoryCard({
|
|
84
|
+
children,
|
|
85
|
+
error,
|
|
86
|
+
eventsLocation,
|
|
87
|
+
history,
|
|
88
|
+
isFetching,
|
|
89
|
+
isLoading,
|
|
90
|
+
onNext,
|
|
91
|
+
onPrevious,
|
|
92
|
+
onRefresh,
|
|
93
|
+
pageNumber,
|
|
94
|
+
title = "All events",
|
|
95
|
+
}: {
|
|
96
|
+
readonly children?: ReactNode;
|
|
97
|
+
readonly error: Error | null;
|
|
98
|
+
readonly eventsLocation: EventsLocationState;
|
|
99
|
+
readonly history: InsightsViewPage<InsightsEventRow> | undefined;
|
|
100
|
+
readonly isFetching: boolean;
|
|
101
|
+
readonly isLoading: boolean;
|
|
102
|
+
readonly onNext: () => void;
|
|
103
|
+
readonly onPrevious: () => void;
|
|
104
|
+
readonly onRefresh: () => void;
|
|
105
|
+
readonly pageNumber: number;
|
|
106
|
+
readonly title?: string;
|
|
107
|
+
}) {
|
|
108
|
+
const dateTimeFormat = useInsightsTimeFormat();
|
|
109
|
+
const hasPrevious = pageNumber > 1;
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<Card className="@container min-w-0 shadow-sm" aria-busy={isFetching}>
|
|
113
|
+
<CardHeader className="gap-4 p-4 sm:p-6">
|
|
114
|
+
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
115
|
+
<CardTitle>
|
|
116
|
+
<h2>{title}</h2>
|
|
117
|
+
</CardTitle>
|
|
118
|
+
<Button
|
|
119
|
+
className="h-11 lg:h-8"
|
|
120
|
+
disabled={isFetching}
|
|
121
|
+
onClick={onRefresh}
|
|
122
|
+
size="lg"
|
|
123
|
+
variant="outline"
|
|
124
|
+
>
|
|
125
|
+
<RefreshCw aria-hidden="true" data-icon="inline-start" />
|
|
126
|
+
Refresh
|
|
127
|
+
</Button>
|
|
128
|
+
</div>
|
|
129
|
+
{children}
|
|
130
|
+
</CardHeader>
|
|
131
|
+
<CardContent className="min-w-0 p-0">
|
|
132
|
+
{isLoading ? (
|
|
133
|
+
<div
|
|
134
|
+
className="flex flex-col gap-4 p-6"
|
|
135
|
+
role="status"
|
|
136
|
+
aria-label="Loading events"
|
|
137
|
+
>
|
|
138
|
+
<Skeleton className="h-12 w-full" />
|
|
139
|
+
<Skeleton className="h-12 w-full" />
|
|
140
|
+
<Skeleton className="h-12 w-full" />
|
|
141
|
+
</div>
|
|
142
|
+
) : error ? (
|
|
143
|
+
<div className="px-6 pb-6">
|
|
144
|
+
<InsightsErrorAlert
|
|
145
|
+
error={error}
|
|
146
|
+
fallbackTitle="Event history unavailable"
|
|
147
|
+
/>
|
|
148
|
+
</div>
|
|
149
|
+
) : history ? (
|
|
150
|
+
<>
|
|
151
|
+
{history.data.length > 0 ? (
|
|
152
|
+
<>
|
|
153
|
+
<div className="@[58rem]:hidden">
|
|
154
|
+
<EventHistoryList
|
|
155
|
+
events={history.data}
|
|
156
|
+
formatter={dateTimeFormat}
|
|
157
|
+
renderIdentity={(event) => (
|
|
158
|
+
<EventIdentity
|
|
159
|
+
event={event}
|
|
160
|
+
eventsLocation={eventsLocation}
|
|
161
|
+
touch
|
|
162
|
+
/>
|
|
163
|
+
)}
|
|
164
|
+
/>
|
|
165
|
+
</div>
|
|
166
|
+
<div className="hidden @[58rem]:block">
|
|
167
|
+
<Table className="min-w-4xl table-fixed">
|
|
168
|
+
<TableHeader>
|
|
169
|
+
<TableRow className="[&>th]:px-4 sm:[&>th]:px-6">
|
|
170
|
+
<TableHead className="w-64">Time</TableHead>
|
|
171
|
+
<TableHead className="w-48">Event</TableHead>
|
|
172
|
+
<TableHead className="w-48">
|
|
173
|
+
User / installation
|
|
174
|
+
</TableHead>
|
|
175
|
+
<TableHead className="w-32">App</TableHead>
|
|
176
|
+
<TableHead className="w-52">Bundle</TableHead>
|
|
177
|
+
</TableRow>
|
|
178
|
+
</TableHeader>
|
|
179
|
+
<TableBody>
|
|
180
|
+
{history.data.map((event) => (
|
|
181
|
+
<TableRow
|
|
182
|
+
key={event.id}
|
|
183
|
+
className="[&>td]:px-4 [&>td]:py-4 [&>td]:align-top sm:[&>td]:px-6"
|
|
184
|
+
>
|
|
185
|
+
<TableCell className="whitespace-normal text-xs tabular-nums">
|
|
186
|
+
<EventTimestamp
|
|
187
|
+
value={event.receivedAtMs}
|
|
188
|
+
formatter={dateTimeFormat}
|
|
189
|
+
/>
|
|
190
|
+
</TableCell>
|
|
191
|
+
<TableCell>
|
|
192
|
+
<EventTypeBadge type={event.type} />
|
|
193
|
+
</TableCell>
|
|
194
|
+
<TableCell>
|
|
195
|
+
<EventIdentity
|
|
196
|
+
event={event}
|
|
197
|
+
eventsLocation={eventsLocation}
|
|
198
|
+
/>
|
|
199
|
+
</TableCell>
|
|
200
|
+
<TableCell className="whitespace-normal text-xs">
|
|
201
|
+
<div className="flex flex-col gap-2">
|
|
202
|
+
<span>
|
|
203
|
+
{event.platform === "ios" ? "iOS" : "Android"}{" "}
|
|
204
|
+
{event.appVersion}
|
|
205
|
+
</span>
|
|
206
|
+
<span className="text-muted-foreground">
|
|
207
|
+
{event.channel}
|
|
208
|
+
</span>
|
|
209
|
+
</div>
|
|
210
|
+
</TableCell>
|
|
211
|
+
<TableCell className="whitespace-normal text-xs">
|
|
212
|
+
<EventBundleTransition event={event} />
|
|
213
|
+
</TableCell>
|
|
214
|
+
</TableRow>
|
|
215
|
+
))}
|
|
216
|
+
</TableBody>
|
|
217
|
+
</Table>
|
|
218
|
+
</div>
|
|
219
|
+
</>
|
|
220
|
+
) : (
|
|
221
|
+
<div className="px-6 pb-6 text-sm text-muted-foreground">
|
|
222
|
+
{hasPrevious || history.nextCursor
|
|
223
|
+
? "No events on this page"
|
|
224
|
+
: title === "All events"
|
|
225
|
+
? "No events recorded yet"
|
|
226
|
+
: "No matching reports in this period"}
|
|
227
|
+
</div>
|
|
228
|
+
)}
|
|
229
|
+
{history.data.length > 0 || hasPrevious || history.nextCursor ? (
|
|
230
|
+
<InsightsPagination
|
|
231
|
+
hasPrevious={hasPrevious}
|
|
232
|
+
label={title}
|
|
233
|
+
nextCursor={history.nextCursor}
|
|
234
|
+
onNext={onNext}
|
|
235
|
+
onPrevious={onPrevious}
|
|
236
|
+
pageLength={history.data.length}
|
|
237
|
+
pageNumber={pageNumber}
|
|
238
|
+
/>
|
|
239
|
+
) : null}
|
|
240
|
+
</>
|
|
241
|
+
) : null}
|
|
242
|
+
</CardContent>
|
|
243
|
+
</Card>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
import type { InsightsEventRow } from "@/lib/insights-view";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
EventBundleTransition,
|
|
7
|
+
EventTimestamp,
|
|
8
|
+
EventTypeBadge,
|
|
9
|
+
} from "./EventDetails";
|
|
10
|
+
|
|
11
|
+
export function EventHistoryList<T extends InsightsEventRow>({
|
|
12
|
+
events,
|
|
13
|
+
formatter,
|
|
14
|
+
renderIdentity,
|
|
15
|
+
}: {
|
|
16
|
+
readonly events: readonly T[];
|
|
17
|
+
readonly formatter: Intl.DateTimeFormat;
|
|
18
|
+
readonly renderIdentity?: (event: T) => ReactNode;
|
|
19
|
+
}) {
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
<p className="px-4 pb-2 text-xs text-muted-foreground sm:px-6">
|
|
23
|
+
Time in your browser zone
|
|
24
|
+
</p>
|
|
25
|
+
<ol aria-label="Events" className="divide-y border-y">
|
|
26
|
+
{events.map((event) => (
|
|
27
|
+
<li
|
|
28
|
+
key={event.id}
|
|
29
|
+
className="flex min-w-0 flex-col gap-3 px-4 py-4 sm:px-6"
|
|
30
|
+
>
|
|
31
|
+
<div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-1">
|
|
32
|
+
<EventTypeBadge type={event.type} />
|
|
33
|
+
<div className="text-xs tabular-nums">
|
|
34
|
+
<EventTimestamp
|
|
35
|
+
value={event.receivedAtMs}
|
|
36
|
+
formatter={formatter}
|
|
37
|
+
touch
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
{renderIdentity?.(event)}
|
|
42
|
+
<dl className="flex flex-wrap gap-x-2 gap-y-1 text-sm">
|
|
43
|
+
<div>
|
|
44
|
+
<dt className="sr-only">App</dt>
|
|
45
|
+
<dd>
|
|
46
|
+
{event.platform === "ios" ? "iOS" : "Android"}{" "}
|
|
47
|
+
{event.appVersion}
|
|
48
|
+
</dd>
|
|
49
|
+
</div>
|
|
50
|
+
<div className="min-w-0 text-muted-foreground">
|
|
51
|
+
<dt className="sr-only">Channel</dt>
|
|
52
|
+
<dd className="wrap-anywhere">{event.channel}</dd>
|
|
53
|
+
</div>
|
|
54
|
+
</dl>
|
|
55
|
+
<div className="text-xs" aria-label="Bundle">
|
|
56
|
+
<EventBundleTransition event={event} touch />
|
|
57
|
+
</div>
|
|
58
|
+
</li>
|
|
59
|
+
))}
|
|
60
|
+
</ol>
|
|
61
|
+
</>
|
|
62
|
+
);
|
|
63
|
+
}
|