@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
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { Link } from "@tanstack/react-router";
|
|
2
|
-
import { ArrowLeft, Search, X } from "lucide-react";
|
|
3
|
-
|
|
4
|
-
import { Button, buttonVariants } from "@/components/ui/button";
|
|
5
|
-
import {
|
|
6
|
-
Card,
|
|
7
|
-
CardContent,
|
|
8
|
-
CardDescription,
|
|
9
|
-
CardTitle,
|
|
10
|
-
} from "@/components/ui/card";
|
|
11
|
-
import {
|
|
12
|
-
Field,
|
|
13
|
-
FieldDescription,
|
|
14
|
-
FieldGroup,
|
|
15
|
-
FieldLabel,
|
|
16
|
-
} from "@/components/ui/field";
|
|
17
|
-
import {
|
|
18
|
-
InputGroup,
|
|
19
|
-
InputGroupAddon,
|
|
20
|
-
InputGroupButton,
|
|
21
|
-
InputGroupInput,
|
|
22
|
-
} from "@/components/ui/input-group";
|
|
23
|
-
import { Separator } from "@/components/ui/separator";
|
|
24
|
-
import { SidebarTrigger } from "@/components/ui/sidebar";
|
|
25
|
-
import { Skeleton } from "@/components/ui/skeleton";
|
|
26
|
-
|
|
27
|
-
export function InstallationPageHeader() {
|
|
28
|
-
return (
|
|
29
|
-
<header className="sticky top-0 z-10 flex shrink-0 flex-wrap items-center gap-x-2 gap-y-1 border-b bg-background px-3 py-3 sm:min-h-12 sm:flex-nowrap sm:bg-card/70 sm:px-4 sm:backdrop-blur-sm">
|
|
30
|
-
<SidebarTrigger className="-ml-1" />
|
|
31
|
-
<Link
|
|
32
|
-
className={buttonVariants({ size: "sm", variant: "ghost" })}
|
|
33
|
-
to="/analytics"
|
|
34
|
-
>
|
|
35
|
-
<ArrowLeft aria-hidden="true" data-icon="inline-start" />
|
|
36
|
-
Back to Analytics
|
|
37
|
-
</Link>
|
|
38
|
-
<Separator className="mx-1 hidden h-4 sm:block" orientation="vertical" />
|
|
39
|
-
<div className="basis-full pl-9 sm:basis-auto sm:pl-0">
|
|
40
|
-
<h1 className="text-sm font-medium">Installation history</h1>
|
|
41
|
-
<p className="text-xs text-muted-foreground">
|
|
42
|
-
Review the last known bundle and recorded changes.
|
|
43
|
-
</p>
|
|
44
|
-
</div>
|
|
45
|
-
</header>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function InstallationSearchPanel({
|
|
50
|
-
draftQuery,
|
|
51
|
-
onDraftQueryChange,
|
|
52
|
-
onSubmit,
|
|
53
|
-
onClear,
|
|
54
|
-
hasQuery,
|
|
55
|
-
}: {
|
|
56
|
-
readonly draftQuery: string;
|
|
57
|
-
readonly onDraftQueryChange: (value: string) => void;
|
|
58
|
-
readonly onSubmit: () => void;
|
|
59
|
-
readonly onClear: () => void;
|
|
60
|
-
readonly hasQuery: boolean;
|
|
61
|
-
}) {
|
|
62
|
-
return (
|
|
63
|
-
<Card>
|
|
64
|
-
<CardContent className="grid gap-4 p-4 lg:grid-cols-[minmax(12rem,0.45fr)_minmax(0,1.55fr)] lg:items-start">
|
|
65
|
-
<div className="flex min-w-0 flex-col gap-1.5">
|
|
66
|
-
<CardTitle className="text-sm font-medium">
|
|
67
|
-
<h2>Find an installation</h2>
|
|
68
|
-
</CardTitle>
|
|
69
|
-
<CardDescription>
|
|
70
|
-
Search by either identifier to inspect its bundle history.
|
|
71
|
-
</CardDescription>
|
|
72
|
-
</div>
|
|
73
|
-
<form
|
|
74
|
-
onSubmit={(event) => {
|
|
75
|
-
event.preventDefault();
|
|
76
|
-
onSubmit();
|
|
77
|
-
}}
|
|
78
|
-
role="search"
|
|
79
|
-
>
|
|
80
|
-
<FieldGroup>
|
|
81
|
-
<Field>
|
|
82
|
-
<FieldLabel htmlFor="installation-history-search">
|
|
83
|
-
User ID or install ID
|
|
84
|
-
</FieldLabel>
|
|
85
|
-
<div className="flex flex-col gap-2 sm:flex-row">
|
|
86
|
-
<InputGroup className="h-8">
|
|
87
|
-
<InputGroupAddon>
|
|
88
|
-
<Search aria-hidden="true" />
|
|
89
|
-
</InputGroupAddon>
|
|
90
|
-
<InputGroupInput
|
|
91
|
-
id="installation-history-search"
|
|
92
|
-
onChange={(event) => onDraftQueryChange(event.target.value)}
|
|
93
|
-
placeholder="Enter a user ID or install ID"
|
|
94
|
-
type="search"
|
|
95
|
-
value={draftQuery}
|
|
96
|
-
/>
|
|
97
|
-
{draftQuery.length > 0 || hasQuery ? (
|
|
98
|
-
<InputGroupAddon align="inline-end">
|
|
99
|
-
<InputGroupButton
|
|
100
|
-
aria-label="Clear search"
|
|
101
|
-
onClick={onClear}
|
|
102
|
-
size="icon-xs"
|
|
103
|
-
>
|
|
104
|
-
<X aria-hidden="true" />
|
|
105
|
-
</InputGroupButton>
|
|
106
|
-
</InputGroupAddon>
|
|
107
|
-
) : null}
|
|
108
|
-
</InputGroup>
|
|
109
|
-
<Button
|
|
110
|
-
className="w-full sm:w-auto"
|
|
111
|
-
disabled={draftQuery.trim().length === 0}
|
|
112
|
-
size="lg"
|
|
113
|
-
type="submit"
|
|
114
|
-
>
|
|
115
|
-
<Search aria-hidden="true" data-icon="inline-start" />
|
|
116
|
-
Search history
|
|
117
|
-
</Button>
|
|
118
|
-
</div>
|
|
119
|
-
<FieldDescription>
|
|
120
|
-
A user ID may return more than one installation. An install ID
|
|
121
|
-
opens one installation directly.
|
|
122
|
-
</FieldDescription>
|
|
123
|
-
</Field>
|
|
124
|
-
</FieldGroup>
|
|
125
|
-
</form>
|
|
126
|
-
</CardContent>
|
|
127
|
-
</Card>
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function InstallationResultsSkeleton() {
|
|
132
|
-
return (
|
|
133
|
-
<div className="grid items-stretch gap-6 lg:min-h-96 lg:grid-cols-[minmax(18rem,20rem)_minmax(0,1fr)]">
|
|
134
|
-
<Skeleton className="h-64 w-full lg:h-full" />
|
|
135
|
-
<Skeleton className="h-64 w-full lg:h-full" />
|
|
136
|
-
</div>
|
|
137
|
-
);
|
|
138
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { Button } from "@/components/ui/button";
|
|
2
|
-
|
|
3
|
-
export function InstallationPagination({
|
|
4
|
-
label,
|
|
5
|
-
limit,
|
|
6
|
-
offset,
|
|
7
|
-
pageLength,
|
|
8
|
-
total,
|
|
9
|
-
onOffsetChange,
|
|
10
|
-
}: {
|
|
11
|
-
readonly label: string;
|
|
12
|
-
readonly limit: number;
|
|
13
|
-
readonly offset: number;
|
|
14
|
-
readonly pageLength: number;
|
|
15
|
-
readonly total: number;
|
|
16
|
-
readonly onOffsetChange: (offset: number) => void;
|
|
17
|
-
}) {
|
|
18
|
-
return (
|
|
19
|
-
<nav
|
|
20
|
-
aria-label={`${label} pagination`}
|
|
21
|
-
className="flex items-center justify-between gap-4 border-t px-5 py-5"
|
|
22
|
-
>
|
|
23
|
-
<span className="text-xs text-muted-foreground">
|
|
24
|
-
{offset + 1}–{Math.min(offset + pageLength, total)} of {total}
|
|
25
|
-
</span>
|
|
26
|
-
<div className="flex gap-2">
|
|
27
|
-
<Button
|
|
28
|
-
disabled={offset === 0}
|
|
29
|
-
onClick={() => onOffsetChange(Math.max(0, offset - limit))}
|
|
30
|
-
size="sm"
|
|
31
|
-
type="button"
|
|
32
|
-
variant="outline"
|
|
33
|
-
>
|
|
34
|
-
Previous
|
|
35
|
-
</Button>
|
|
36
|
-
<Button
|
|
37
|
-
disabled={offset + pageLength >= total}
|
|
38
|
-
onClick={() => onOffsetChange(offset + limit)}
|
|
39
|
-
size="sm"
|
|
40
|
-
type="button"
|
|
41
|
-
variant="outline"
|
|
42
|
-
>
|
|
43
|
-
Next
|
|
44
|
-
</Button>
|
|
45
|
-
</div>
|
|
46
|
-
</nav>
|
|
47
|
-
);
|
|
48
|
-
}
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import { Link } from "@tanstack/react-router";
|
|
2
|
-
import { Search, TriangleAlert } from "lucide-react";
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
|
|
5
|
-
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
|
6
|
-
import { Button } from "@/components/ui/button";
|
|
7
|
-
import {
|
|
8
|
-
Card,
|
|
9
|
-
CardContent,
|
|
10
|
-
CardDescription,
|
|
11
|
-
CardHeader,
|
|
12
|
-
CardTitle,
|
|
13
|
-
} from "@/components/ui/card";
|
|
14
|
-
import {
|
|
15
|
-
InputGroup,
|
|
16
|
-
InputGroupAddon,
|
|
17
|
-
InputGroupButton,
|
|
18
|
-
InputGroupInput,
|
|
19
|
-
} from "@/components/ui/input-group";
|
|
20
|
-
import { Skeleton } from "@/components/ui/skeleton";
|
|
21
|
-
import {
|
|
22
|
-
Table,
|
|
23
|
-
TableBody,
|
|
24
|
-
TableCell,
|
|
25
|
-
TableHead,
|
|
26
|
-
TableHeader,
|
|
27
|
-
TableRow,
|
|
28
|
-
} from "@/components/ui/table";
|
|
29
|
-
import { isAnalyticsQueryEnabled } from "@/lib/analytics-api";
|
|
30
|
-
import {
|
|
31
|
-
type InstallationSearchRow,
|
|
32
|
-
useInstallationSearchQuery,
|
|
33
|
-
} from "@/lib/api";
|
|
34
|
-
|
|
35
|
-
import { useAnalyticsCapability } from "./AnalyticsCapabilityContext";
|
|
36
|
-
|
|
37
|
-
const SEARCH_LIMIT = 20;
|
|
38
|
-
|
|
39
|
-
const identityLabel = (row: InstallationSearchRow): string =>
|
|
40
|
-
row.userId ?? row.installId;
|
|
41
|
-
|
|
42
|
-
const platformLabel = (platform: InstallationSearchRow["platform"]): string =>
|
|
43
|
-
platform === "ios" ? "iOS" : "Android";
|
|
44
|
-
|
|
45
|
-
export function InstallationSearch({
|
|
46
|
-
initialQuery = "",
|
|
47
|
-
}: {
|
|
48
|
-
readonly initialQuery?: string;
|
|
49
|
-
}) {
|
|
50
|
-
const capability = useAnalyticsCapability();
|
|
51
|
-
const normalizedInitialQuery = initialQuery.trim();
|
|
52
|
-
const [draftQuery, setDraftQuery] = useState(normalizedInitialQuery);
|
|
53
|
-
const [submittedQuery, setSubmittedQuery] = useState(normalizedInitialQuery);
|
|
54
|
-
const [offset, setOffset] = useState(0);
|
|
55
|
-
const queryEnabled =
|
|
56
|
-
isAnalyticsQueryEnabled(capability) && submittedQuery.length > 0;
|
|
57
|
-
const { data, error, isLoading } = useInstallationSearchQuery(
|
|
58
|
-
{ query: submittedQuery, limit: SEARCH_LIMIT, offset },
|
|
59
|
-
queryEnabled,
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<Card>
|
|
64
|
-
<CardHeader>
|
|
65
|
-
<CardTitle className="text-sm font-medium">
|
|
66
|
-
<h2>Installation inspector</h2>
|
|
67
|
-
</CardTitle>
|
|
68
|
-
<CardDescription>
|
|
69
|
-
Search by user ID or install ID to inspect its last known bundle.
|
|
70
|
-
</CardDescription>
|
|
71
|
-
</CardHeader>
|
|
72
|
-
<CardContent className="flex flex-col gap-5">
|
|
73
|
-
<form
|
|
74
|
-
className="max-w-2xl"
|
|
75
|
-
onSubmit={(event) => {
|
|
76
|
-
event.preventDefault();
|
|
77
|
-
setSubmittedQuery(draftQuery.trim());
|
|
78
|
-
setOffset(0);
|
|
79
|
-
}}
|
|
80
|
-
role="search"
|
|
81
|
-
>
|
|
82
|
-
<label className="sr-only" htmlFor="analytics-installation-search">
|
|
83
|
-
User ID or install ID
|
|
84
|
-
</label>
|
|
85
|
-
<InputGroup className="h-9">
|
|
86
|
-
<InputGroupAddon>
|
|
87
|
-
<Search aria-hidden="true" />
|
|
88
|
-
</InputGroupAddon>
|
|
89
|
-
<InputGroupInput
|
|
90
|
-
id="analytics-installation-search"
|
|
91
|
-
aria-label="User ID or install ID"
|
|
92
|
-
disabled={isLoading}
|
|
93
|
-
onChange={(event) => setDraftQuery(event.target.value)}
|
|
94
|
-
placeholder="Enter a user ID or install ID"
|
|
95
|
-
type="search"
|
|
96
|
-
value={draftQuery}
|
|
97
|
-
/>
|
|
98
|
-
<InputGroupAddon align="inline-end">
|
|
99
|
-
<InputGroupButton
|
|
100
|
-
disabled={isLoading}
|
|
101
|
-
type="submit"
|
|
102
|
-
variant="default"
|
|
103
|
-
>
|
|
104
|
-
Search
|
|
105
|
-
</InputGroupButton>
|
|
106
|
-
</InputGroupAddon>
|
|
107
|
-
</InputGroup>
|
|
108
|
-
</form>
|
|
109
|
-
|
|
110
|
-
{!queryEnabled ? (
|
|
111
|
-
<p className="text-sm text-muted-foreground">
|
|
112
|
-
Enter a user ID or install ID to search received reports.
|
|
113
|
-
</p>
|
|
114
|
-
) : isLoading ? (
|
|
115
|
-
<div
|
|
116
|
-
aria-label="Searching installations"
|
|
117
|
-
className="flex flex-col gap-2"
|
|
118
|
-
>
|
|
119
|
-
<Skeleton className="h-10 w-full" />
|
|
120
|
-
<Skeleton className="h-10 w-full" />
|
|
121
|
-
</div>
|
|
122
|
-
) : error ? (
|
|
123
|
-
<Alert variant="destructive">
|
|
124
|
-
<TriangleAlert aria-hidden="true" />
|
|
125
|
-
<AlertTitle>Installation search unavailable</AlertTitle>
|
|
126
|
-
<AlertDescription>{error.message}</AlertDescription>
|
|
127
|
-
</Alert>
|
|
128
|
-
) : data && data.data.length > 0 ? (
|
|
129
|
-
<div className="flex flex-col gap-3">
|
|
130
|
-
<Table>
|
|
131
|
-
<TableHeader>
|
|
132
|
-
<TableRow className="hover:bg-transparent">
|
|
133
|
-
<TableHead>Installation</TableHead>
|
|
134
|
-
<TableHead>Last known bundle</TableHead>
|
|
135
|
-
<TableHead>Reported context</TableHead>
|
|
136
|
-
<TableHead className="text-right">Details</TableHead>
|
|
137
|
-
</TableRow>
|
|
138
|
-
</TableHeader>
|
|
139
|
-
<TableBody>
|
|
140
|
-
{data.data.map((row) => (
|
|
141
|
-
<InstallationResultRow
|
|
142
|
-
key={row.installId}
|
|
143
|
-
query={submittedQuery}
|
|
144
|
-
row={row}
|
|
145
|
-
/>
|
|
146
|
-
))}
|
|
147
|
-
</TableBody>
|
|
148
|
-
</Table>
|
|
149
|
-
<div className="flex items-center justify-between gap-3">
|
|
150
|
-
<p className="text-xs text-muted-foreground">
|
|
151
|
-
{offset + 1}–
|
|
152
|
-
{Math.min(offset + data.data.length, data.pagination.total)} of{" "}
|
|
153
|
-
{data.pagination.total}
|
|
154
|
-
</p>
|
|
155
|
-
<div className="flex gap-2">
|
|
156
|
-
<Button
|
|
157
|
-
disabled={offset === 0}
|
|
158
|
-
onClick={() => setOffset(Math.max(0, offset - SEARCH_LIMIT))}
|
|
159
|
-
size="sm"
|
|
160
|
-
type="button"
|
|
161
|
-
variant="outline"
|
|
162
|
-
>
|
|
163
|
-
Previous
|
|
164
|
-
</Button>
|
|
165
|
-
<Button
|
|
166
|
-
disabled={offset + data.data.length >= data.pagination.total}
|
|
167
|
-
onClick={() => setOffset(offset + SEARCH_LIMIT)}
|
|
168
|
-
size="sm"
|
|
169
|
-
type="button"
|
|
170
|
-
variant="outline"
|
|
171
|
-
>
|
|
172
|
-
Next
|
|
173
|
-
</Button>
|
|
174
|
-
</div>
|
|
175
|
-
</div>
|
|
176
|
-
</div>
|
|
177
|
-
) : (
|
|
178
|
-
<p className="text-sm text-muted-foreground">
|
|
179
|
-
No installations matched that search.
|
|
180
|
-
</p>
|
|
181
|
-
)}
|
|
182
|
-
</CardContent>
|
|
183
|
-
</Card>
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function InstallationResultRow({
|
|
188
|
-
query,
|
|
189
|
-
row,
|
|
190
|
-
}: {
|
|
191
|
-
readonly query: string;
|
|
192
|
-
readonly row: InstallationSearchRow;
|
|
193
|
-
}) {
|
|
194
|
-
return (
|
|
195
|
-
<TableRow>
|
|
196
|
-
<TableCell className="max-w-64 whitespace-normal">
|
|
197
|
-
<div className="flex flex-col gap-1">
|
|
198
|
-
<span className="font-medium">{identityLabel(row)}</span>
|
|
199
|
-
<code className="break-all text-muted-foreground">
|
|
200
|
-
{row.installId}
|
|
201
|
-
</code>
|
|
202
|
-
</div>
|
|
203
|
-
</TableCell>
|
|
204
|
-
<TableCell className="max-w-64 whitespace-normal">
|
|
205
|
-
<div className="flex flex-col gap-1">
|
|
206
|
-
<span className="text-muted-foreground">Last known bundle</span>
|
|
207
|
-
<code className="break-all">{row.lastKnownBundleId ?? "—"}</code>
|
|
208
|
-
</div>
|
|
209
|
-
</TableCell>
|
|
210
|
-
<TableCell>
|
|
211
|
-
{platformLabel(row.platform)} · {row.channel} · {row.appVersion}
|
|
212
|
-
</TableCell>
|
|
213
|
-
<TableCell className="text-right">
|
|
214
|
-
<Link
|
|
215
|
-
aria-label={`Open ${row.installId}`}
|
|
216
|
-
className="font-medium text-primary underline-offset-4 hover:underline focus-visible:rounded-sm focus-visible:outline-2"
|
|
217
|
-
search={{
|
|
218
|
-
query,
|
|
219
|
-
installId: row.installId,
|
|
220
|
-
searchOffset: 0,
|
|
221
|
-
historyOffset: 0,
|
|
222
|
-
}}
|
|
223
|
-
to="/installations"
|
|
224
|
-
>
|
|
225
|
-
Open
|
|
226
|
-
</Link>
|
|
227
|
-
</TableCell>
|
|
228
|
-
</TableRow>
|
|
229
|
-
);
|
|
230
|
-
}
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import type { ActiveInstallationWindow } from "@hot-updater/server";
|
|
2
|
-
import type { ReactNode } from "react";
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
Card,
|
|
6
|
-
CardContent,
|
|
7
|
-
CardDescription,
|
|
8
|
-
CardHeader,
|
|
9
|
-
CardTitle,
|
|
10
|
-
} from "@/components/ui/card";
|
|
11
|
-
import { Skeleton } from "@/components/ui/skeleton";
|
|
12
|
-
import type { BundleEventAnalytics } from "@/lib/api";
|
|
13
|
-
|
|
14
|
-
import { BundleActivityChart } from "../bundles/BundleActivityChart";
|
|
15
|
-
import { AnalyticsErrorAlert } from "./AnalyticsErrorAlert";
|
|
16
|
-
|
|
17
|
-
export type UpdateOutcomeState =
|
|
18
|
-
| { readonly status: "idle" }
|
|
19
|
-
| { readonly status: "loading"; readonly bundleId: string }
|
|
20
|
-
| {
|
|
21
|
-
readonly status: "error";
|
|
22
|
-
readonly bundleId: string;
|
|
23
|
-
readonly error: Error;
|
|
24
|
-
}
|
|
25
|
-
| {
|
|
26
|
-
readonly status: "success";
|
|
27
|
-
readonly bundleId: string;
|
|
28
|
-
readonly data: BundleEventAnalytics;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export function UpdateOutcomes({
|
|
32
|
-
bundleSelector,
|
|
33
|
-
configuredPercentage,
|
|
34
|
-
latestBundleInstallations,
|
|
35
|
-
reportingInstallations,
|
|
36
|
-
state,
|
|
37
|
-
window,
|
|
38
|
-
}: {
|
|
39
|
-
readonly bundleSelector: ReactNode;
|
|
40
|
-
readonly configuredPercentage: number | null;
|
|
41
|
-
readonly latestBundleInstallations: number;
|
|
42
|
-
readonly reportingInstallations: number;
|
|
43
|
-
readonly state: UpdateOutcomeState;
|
|
44
|
-
readonly window: ActiveInstallationWindow;
|
|
45
|
-
}) {
|
|
46
|
-
const latestBundleShare =
|
|
47
|
-
reportingInstallations === 0
|
|
48
|
-
? 0
|
|
49
|
-
: (latestBundleInstallations / reportingInstallations) * 100;
|
|
50
|
-
|
|
51
|
-
return (
|
|
52
|
-
<Card className="min-w-0 overflow-hidden shadow-sm">
|
|
53
|
-
<CardHeader className="gap-4 space-y-0 lg:flex-row lg:items-start lg:justify-between">
|
|
54
|
-
<div className="flex min-w-0 flex-col gap-1.5">
|
|
55
|
-
<CardTitle className="text-sm font-medium">
|
|
56
|
-
<h2 id="bundle-detail-heading">Selected bundle activity</h2>
|
|
57
|
-
</CardTitle>
|
|
58
|
-
<CardDescription>
|
|
59
|
-
Select a bundle to inspect its latest presence, applies, and
|
|
60
|
-
recoveries during this period.
|
|
61
|
-
</CardDescription>
|
|
62
|
-
</div>
|
|
63
|
-
{bundleSelector}
|
|
64
|
-
</CardHeader>
|
|
65
|
-
<CardContent className="pt-10">
|
|
66
|
-
{state.status === "idle" ? (
|
|
67
|
-
<p className="text-sm text-muted-foreground">
|
|
68
|
-
No active bundle is available.
|
|
69
|
-
</p>
|
|
70
|
-
) : state.status === "loading" ? (
|
|
71
|
-
<div
|
|
72
|
-
aria-label="Loading update outcomes"
|
|
73
|
-
className="flex flex-col gap-4"
|
|
74
|
-
>
|
|
75
|
-
<div className="grid grid-cols-2 gap-4 lg:grid-cols-4">
|
|
76
|
-
<Skeleton className="h-14 w-full" />
|
|
77
|
-
<Skeleton className="h-14 w-full" />
|
|
78
|
-
<Skeleton className="h-14 w-full" />
|
|
79
|
-
<Skeleton className="h-14 w-full" />
|
|
80
|
-
</div>
|
|
81
|
-
<Skeleton className="h-32 w-full" />
|
|
82
|
-
</div>
|
|
83
|
-
) : state.status === "error" ? (
|
|
84
|
-
<AnalyticsErrorAlert
|
|
85
|
-
error={state.error}
|
|
86
|
-
fallbackTitle="Bundle movement unavailable"
|
|
87
|
-
/>
|
|
88
|
-
) : (
|
|
89
|
-
<div className="flex min-w-0 flex-col gap-5">
|
|
90
|
-
<dl className="grid sm:grid-cols-2 lg:grid-cols-4 lg:divide-x lg:divide-border/70">
|
|
91
|
-
<div className="border-b pb-4 sm:pr-4 lg:border-b-0">
|
|
92
|
-
<dt className="text-xs text-muted-foreground">
|
|
93
|
-
Latest bundle share
|
|
94
|
-
</dt>
|
|
95
|
-
<dd className="mt-1 text-2xl font-semibold tracking-tight tabular-nums">
|
|
96
|
-
{Math.round(latestBundleShare)}%
|
|
97
|
-
</dd>
|
|
98
|
-
<dd className="mt-1 text-xs text-muted-foreground tabular-nums">
|
|
99
|
-
{latestBundleInstallations.toLocaleString()} of{" "}
|
|
100
|
-
{reportingInstallations.toLocaleString()} reporting installs
|
|
101
|
-
</dd>
|
|
102
|
-
</div>
|
|
103
|
-
<div className="border-b py-4 sm:pl-4 lg:border-b-0 lg:py-0 lg:pr-4">
|
|
104
|
-
<dt className="text-xs text-muted-foreground">Newly applied</dt>
|
|
105
|
-
<dd className="mt-1 text-2xl font-semibold tracking-tight tabular-nums">
|
|
106
|
-
{state.data.summary.installed.toLocaleString()}
|
|
107
|
-
</dd>
|
|
108
|
-
<dd className="mt-1 text-xs text-muted-foreground">
|
|
109
|
-
Distinct installations
|
|
110
|
-
</dd>
|
|
111
|
-
</div>
|
|
112
|
-
<div className="border-b py-4 sm:pr-4 lg:border-b-0 lg:py-0 lg:pl-4">
|
|
113
|
-
<dt className="text-xs text-muted-foreground">
|
|
114
|
-
Recovered away
|
|
115
|
-
</dt>
|
|
116
|
-
<dd className="mt-1 text-2xl font-semibold tracking-tight tabular-nums">
|
|
117
|
-
{state.data.summary.recovered.toLocaleString()}
|
|
118
|
-
</dd>
|
|
119
|
-
<dd className="mt-1 text-xs text-muted-foreground">
|
|
120
|
-
Distinct installations
|
|
121
|
-
</dd>
|
|
122
|
-
</div>
|
|
123
|
-
<div className="pt-4 sm:pl-4 lg:py-0">
|
|
124
|
-
<dt className="text-xs text-muted-foreground">
|
|
125
|
-
Configured rollout
|
|
126
|
-
</dt>
|
|
127
|
-
<dd className="mt-1 text-2xl font-semibold tracking-tight tabular-nums">
|
|
128
|
-
{configuredPercentage === null
|
|
129
|
-
? "—"
|
|
130
|
-
: `${configuredPercentage}%`}
|
|
131
|
-
</dd>
|
|
132
|
-
<dd className="mt-1 text-xs text-muted-foreground">
|
|
133
|
-
Eligibility setting
|
|
134
|
-
</dd>
|
|
135
|
-
</div>
|
|
136
|
-
</dl>
|
|
137
|
-
<BundleActivityChart
|
|
138
|
-
installed={state.data.series.installed}
|
|
139
|
-
recovered={state.data.series.recovered}
|
|
140
|
-
window={window}
|
|
141
|
-
/>
|
|
142
|
-
</div>
|
|
143
|
-
)}
|
|
144
|
-
</CardContent>
|
|
145
|
-
</Card>
|
|
146
|
-
);
|
|
147
|
-
}
|