@hot-updater/console 1.0.0-rc.4 → 1.0.0-rc.40
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/ConsoleFeatureUnavailable-BH7LLqAg.js +1 -0
- package/.output/public/assets/EventDetails-3DgVjwDn.js +1 -0
- package/.output/public/assets/InsightsInfo-NUuLFYqg.js +1 -0
- package/.output/public/assets/InsightsPageHeader-DqCp0vf7.js +1 -0
- package/.output/public/assets/NotFoundPage-CkgdpWvl.js +1 -0
- package/.output/public/assets/alert-dialog-DXxrSU0G.js +1 -0
- package/.output/public/assets/api-BiZP_i-u.js +1 -0
- package/.output/public/assets/api-keys-DQDqmKtq.js +1 -0
- package/.output/public/assets/api-keys-DTQ1HrkR.js +1 -0
- package/.output/public/assets/arrow-left-BPK7iqvj.js +1 -0
- package/.output/public/assets/card-DKr2pZMA.js +1 -0
- package/.output/public/assets/console-features-api-BhUIxeL-.js +1 -0
- package/.output/public/assets/dialog-DxjO7ofz.js +1 -0
- package/.output/public/assets/dist-BzWwzt98.js +1 -0
- package/.output/public/assets/empty-DtangHOp.js +1 -0
- package/.output/public/assets/field-bPoDrs6K.js +1 -0
- package/.output/public/assets/index-B7BSdp0k.js +11 -0
- package/.output/public/assets/input-group-_PD0i7wZ.js +1 -0
- package/.output/public/assets/insights-BBya_x8N.js +1 -0
- package/.output/public/assets/insights-CevVB_8E.js +2 -0
- package/.output/public/assets/insights-DMH-zhbe.js +42 -0
- package/.output/public/assets/insights-usage-rpc-pz2XQBi3.js +1 -0
- package/.output/public/assets/insights_.distribution-B9MOEnzZ.js +2 -0
- package/.output/public/assets/insights_.distribution-O73SKvoa.js +1 -0
- package/.output/public/assets/insights_.distribution-WTcKITzb.js +1 -0
- package/.output/public/assets/installations-CstW4tEL.js +1 -0
- package/.output/public/assets/installations-DtZj5jk2.js +1 -0
- package/.output/public/assets/installations-LJBhFDEM.js +2 -0
- package/.output/public/assets/loader-circle-iTGehfyV.js +1 -0
- package/.output/public/assets/mutation-B4EZNS91.js +1 -0
- package/.output/public/assets/preload-helper-D9SUwQT7.js +1 -0
- package/.output/public/assets/refresh-cw-3fSQ1FYY.js +1 -0
- package/.output/public/assets/routes-CJSxovHS.js +1 -0
- package/.output/public/assets/routes-DyZCr794.js +2 -0
- package/.output/public/assets/sidebar-RBvHzq8e.js +3 -0
- package/.output/public/assets/styles-DwA6mhnh.css +2 -0
- package/.output/public/assets/table-BYmouPt9.js +1 -0
- package/.output/public/assets/useCompositeListItem-BxJspDxS.js +1 -0
- package/.output/public/assets/valueToPercent-Ci2-fmWp.js +1 -0
- package/.output/server/{__23tanstack-start-server-fn-resolver-Dr2ixDgQ.mjs → __23tanstack-start-server-fn-resolver-CUp7Too4.mjs} +60 -52
- package/.output/server/_libs/@base-ui/react+[...].mjs +7219 -3692
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +3802 -4022
- package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +140 -97
- package/.output/server/_libs/lucide-react.mjs +114 -31
- package/.output/server/_libs/next-themes.mjs +1 -1
- package/.output/server/_libs/react-redux+[...].mjs +1 -1
- package/.output/server/_libs/recharts+[...].mjs +483 -901
- package/.output/server/_libs/sonner.mjs +1 -1
- package/.output/server/_libs/tanstack__history.mjs +68 -41
- package/.output/server/_libs/tanstack__query-core.mjs +1994 -310
- package/.output/server/_libs/tanstack__react-query.mjs +448 -21
- package/.output/server/_libs/verkit.mjs +99 -1
- package/.output/server/_ssr/ConsoleFeatureUnavailable-BxaV7ca0.mjs +58 -0
- package/.output/server/_ssr/EventDetails-B5HHZsCk.mjs +344 -0
- package/.output/server/_ssr/InsightsInfo-C7K_HKHZ.mjs +75 -0
- package/.output/server/_ssr/InsightsPageHeader-BPaYzzkg.mjs +183 -0
- package/.output/server/_ssr/NotFoundPage-CyBONH8d.mjs +37 -0
- package/.output/server/_ssr/{dialog-DUaIgpgd.mjs → alert-dialog-BsqgyNOZ.mjs} +6 -83
- package/.output/server/_ssr/api-keys-CY1d8bCZ.mjs +5 -0
- package/.output/server/_ssr/{api-keys-DJml_XoK.mjs → api-keys-D8R-eRJ-.mjs} +49 -10
- package/.output/server/_ssr/api-keys-rpc-CC1ERVtk.mjs +52 -0
- package/.output/server/_ssr/api-otC0m2Cl.mjs +203 -0
- package/.output/server/_ssr/api-rpc-C99iexLk.mjs +296 -0
- package/.output/server/_ssr/{auth-rpc-D_hv52-J.mjs → auth-rpc-PABg7kHk.mjs} +6 -6
- package/.output/server/_ssr/auth.server-8-IRXBc-.mjs +5712 -0
- package/.output/server/_ssr/bundleActivity-BXSJbaQY.mjs +18 -0
- package/.output/server/_ssr/card-ijGSudc_.mjs +49 -0
- package/.output/server/_ssr/config-nsIcsHh8.mjs +15 -0
- package/.output/server/_ssr/config.server-08RES1G-.mjs +36 -0
- package/.output/server/_ssr/console-access-D0bQJ9tx.mjs +17 -0
- package/.output/server/_ssr/console-features-AGV2b_l5.mjs +55 -0
- package/.output/server/_ssr/console-features-api-BRGAxAgc.mjs +43 -0
- package/.output/server/_ssr/console-features-rpc-845alOXy.mjs +19 -0
- package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-BdBf_ezN.mjs} +67 -56
- package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-B_y2lrwA.mjs} +2 -2
- package/.output/server/_ssr/{databaseJsonValue-d6I3nAS4.mjs → databaseJsonValue-Bhxciy7Z.mjs} +28 -5
- package/.output/server/_ssr/databaseRows-DwMAjaF8.mjs +74 -0
- package/.output/server/_ssr/dialog-BnfVyv4I.mjs +84 -0
- package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-Cxo9ghYv.mjs} +140 -6
- package/.output/server/_ssr/{downloadBundle-B3ert-pY.mjs → downloadBundle-D10vIrle.mjs} +10 -10
- package/.output/server/_ssr/empty-CnGZqF1a.mjs +49 -0
- package/.output/server/_ssr/{empty-plugin-adapters-B5QvGpEl.mjs → empty-plugin-adapters-CYdinE-i.mjs} +1 -1
- package/.output/server/_ssr/failureReports-_trbWCVo.mjs +33 -0
- package/.output/server/_ssr/{field-CqNUlqUq.mjs → field-CysUuRJx.mjs} +62 -4
- package/.output/server/_ssr/getBundleChildren-DLGwrnCV.mjs +22 -0
- package/.output/server/_ssr/{input-group-Bb8ueylQ.mjs → input-group-C44twsSf.mjs} +2 -2
- package/.output/server/_ssr/insights-Br6JnctH.mjs +5 -0
- package/.output/server/_ssr/insights-CgRV7dlP.mjs +2314 -0
- package/.output/server/_ssr/insights-CrrlG-bz.mjs +14 -0
- package/.output/server/_ssr/insights-api-MpLvDjmC.mjs +122 -0
- package/.output/server/_ssr/insights-errors-Cy03daYs.mjs +34 -0
- package/.output/server/_ssr/insights-failures-HHiiIbag.mjs +46 -0
- package/.output/server/_ssr/insights-failures-rpc-CGiDJbg-.mjs +36 -0
- package/.output/server/_ssr/{insights-recovery-DSFM-YTL.mjs → insights-recovery-Dpk6Mfb9.mjs} +9 -2
- package/.output/server/_ssr/insights-recovery-rpc-Bul_1cyL.mjs +32 -0
- package/.output/server/_ssr/{insights-rpc-DrIQCjpD.mjs → insights-rpc-D24DKKlx.mjs} +22 -14
- package/.output/server/_ssr/insights-usage-CLUWjUFz.mjs +29 -0
- package/.output/server/_ssr/insights-usage-rpc-iG8wRo4G.mjs +29 -0
- package/.output/server/_ssr/insights-usage-rpc-tVCHkzGH.mjs +41 -0
- package/.output/server/_ssr/insightsUsage-9X2sp3ja.mjs +56 -0
- package/.output/server/_ssr/insights_.distribution-Ckc_O360.mjs +16 -0
- package/.output/server/_ssr/insights_.distribution-DbO4X6X6.mjs +7 -0
- package/.output/server/_ssr/insights_.distribution-DsujaomV.mjs +347 -0
- package/.output/server/_ssr/{installations-YumqpNpa.mjs → installations-DV3sNoM5.mjs} +280 -364
- package/.output/server/_ssr/installations-DkJq_y8i.mjs +5 -0
- package/.output/server/_ssr/installations-F6vBSPUU.mjs +69 -0
- package/.output/server/_ssr/listReleases-BitHzOYB.mjs +79 -0
- package/.output/server/_ssr/release-adoption-NO2DVJwF.mjs +55 -0
- package/.output/server/_ssr/release-adoption-rpc-h_r3KQsi.mjs +41 -0
- package/.output/server/_ssr/releaseAdoption-CwFUzIZA.mjs +114 -0
- package/.output/server/_ssr/releaseManagement-B3e7I8J9.mjs +11 -0
- package/.output/server/_ssr/{router-iP95Vqi2.mjs → router-C_IshN2d.mjs} +134 -138
- package/.output/server/_ssr/{routes-ivsV2TDu.mjs → routes-BEHlOXKL.mjs} +190 -674
- package/.output/server/_ssr/routes-DeSps20R.mjs +391 -0
- package/.output/server/_ssr/runtime.server-C1SVch_-.mjs +1604 -0
- package/.output/server/_ssr/schema-BKY9SqaY.mjs +25 -0
- package/.output/server/_ssr/select-BL10bGzk.mjs +114 -0
- package/.output/server/_ssr/server-DIIIVgcN.mjs +8334 -0
- package/.output/server/_ssr/{createSsrRpc-NHF2PxuK.mjs → sidebar-DLus-ndm.mjs} +107 -47
- package/.output/server/_ssr/ssr.mjs +1 -1
- package/.output/server/_ssr/start-BXUOv_n8.mjs +4 -0
- package/.output/server/_ssr/{table-D1mL8fJu.mjs → table-U7InIZlH.mjs} +2 -2
- package/.output/server/_ssr/updateFailures-BLs3R7j0.mjs +40 -0
- package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-C8bwiZr8.mjs} +2 -2
- package/.output/server/_tanstack-start-manifest_v-VxUXUIhM.mjs +123 -0
- package/.output/server/index.mjs +240 -163
- package/LICENSE +13 -14
- package/README.md +82 -72
- package/lib/index.d.mts +23 -11
- package/lib/vite-internal.mjs +13 -3
- package/lib/vite.d.mts +2 -0
- package/package.json +14 -14
- package/src/components/AppSidebar.tsx +100 -71
- package/src/components/ConsoleAccessPage.tsx +5 -1
- package/src/components/ConsoleFeatureUnavailable.tsx +80 -0
- package/src/components/NotFoundPage.tsx +7 -7
- package/src/components/features/FeatureSlots.tsx +101 -0
- package/src/components/features/api-keys/apiKeysFeatureUi.tsx +15 -0
- package/src/components/features/bundles/BundleMetadata.tsx +8 -5
- package/src/components/features/bundles/RolloutCohortsDialog.tsx +1 -1
- package/src/components/features/channels/ChannelManagementDialog.tsx +2 -5
- package/src/components/features/insights/AppUsage.tsx +366 -0
- package/src/components/features/insights/BuiltinBundle.tsx +36 -0
- package/src/components/features/insights/ComparedBundlesChart.tsx +310 -0
- package/src/components/features/insights/EstimatedCount.tsx +18 -0
- package/src/components/features/insights/EventDetails.tsx +400 -39
- package/src/components/features/insights/EventHistoryCard.tsx +112 -26
- package/src/components/features/insights/EventHistoryList.tsx +2 -2
- package/src/components/features/insights/FailureReports.tsx +407 -0
- package/src/components/features/insights/InsightsControls.tsx +265 -88
- package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
- package/src/components/features/insights/InsightsInfo.tsx +48 -0
- package/src/components/features/insights/InsightsOverview.tsx +604 -355
- package/src/components/features/insights/InsightsPageHeader.tsx +35 -20
- package/src/components/features/insights/InsightsPeriodSelector.tsx +89 -0
- package/src/components/features/insights/InstallationHistoryCard.tsx +66 -11
- package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
- package/src/components/features/insights/ReleaseActivity.tsx +308 -0
- package/src/components/features/insights/ReportingDevicesSummary.tsx +48 -0
- package/src/components/features/insights/RollbackButton.tsx +118 -0
- package/src/components/features/insights/UpdateFailures.tsx +382 -0
- package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
- package/src/components/features/releases/ReleaseEditorSheet.tsx +26 -12
- package/src/components/ui/card.tsx +1 -1
- package/src/components/ui/scroll-area.tsx +52 -0
- package/src/components/ui/sidebar.tsx +22 -29
- package/src/components/ui/tabs.tsx +81 -0
- package/src/index.ts +26 -19
- package/src/lib/api-keys-api.ts +0 -28
- package/src/lib/api-keys-rpc.ts +27 -38
- package/src/lib/api-rpc.ts +189 -266
- package/src/lib/api.ts +19 -102
- package/src/lib/bundle-activity.ts +24 -4
- package/src/lib/console-access.ts +20 -0
- package/src/lib/console-features-api.ts +68 -0
- package/src/lib/console-features-rpc.ts +13 -0
- package/src/lib/console-features.ts +91 -0
- package/src/lib/insights-api.ts +56 -2
- package/src/lib/insights-errors.ts +64 -0
- package/src/lib/insights-failures-rpc.ts +36 -0
- package/src/lib/insights-failures.ts +130 -0
- package/src/lib/insights-recovery-rpc.ts +13 -17
- package/src/lib/insights-recovery.ts +10 -27
- package/src/lib/insights-retention.ts +21 -0
- package/src/lib/insights-rpc.ts +30 -12
- package/src/lib/insights-search.ts +93 -0
- package/src/lib/insights-usage-rpc.ts +29 -0
- package/src/lib/insights-usage.ts +76 -0
- package/src/lib/insights-view.ts +34 -5
- package/src/lib/release-adoption-api.ts +214 -0
- package/src/lib/release-adoption-rpc.ts +49 -0
- package/src/lib/release-adoption.ts +164 -0
- package/src/lib/scroll-restoration.ts +21 -0
- package/src/lib/server/adminInsights.ts +124 -0
- package/src/lib/server/auth.server.ts +13 -5
- package/src/lib/server/bundleActivity.ts +27 -28
- package/src/lib/server/config.server.ts +21 -74
- package/src/lib/server/console-feature-apis.ts +59 -0
- package/src/lib/server/console-runtime.server.ts +82 -38
- package/src/lib/server/downloadBundle.ts +16 -15
- package/src/lib/server/failureReports.ts +49 -0
- package/src/lib/server/getBundleChildren.ts +31 -122
- package/src/lib/server/insightsUsage.ts +63 -0
- package/src/lib/server/listReleases.ts +109 -44
- package/src/lib/server/releaseAdoption.ts +154 -0
- package/src/lib/server/releaseReachability.ts +8 -7
- package/src/lib/server/runtime.server.ts +120 -31
- package/src/lib/server/updateFailures.ts +65 -0
- package/src/routeTree.gen.ts +44 -44
- package/src/router.tsx +3 -2
- package/src/routes/-installations-search.ts +32 -8
- package/src/routes/-releases-search.ts +76 -29
- package/src/routes/__root.tsx +10 -4
- package/src/routes/api/bundles/$bundleId/download.ts +2 -5
- package/src/routes/api-keys.tsx +5 -2
- package/src/routes/index.tsx +210 -152
- package/src/routes/insights.tsx +133 -18
- package/src/routes/insights_.distribution.tsx +422 -0
- package/src/routes/installations.tsx +98 -41
- package/src/styles.css +9 -0
- package/src/vite-internal.ts +15 -3
- package/src/vite.ts +2 -0
- package/.output/public/assets/InsightsPageHeader-C-Xc2MB9.js +0 -1
- package/.output/public/assets/api-keys-C1sgC6ko.js +0 -1
- package/.output/public/assets/api-keys-api-LchP65vQ.js +0 -1
- package/.output/public/assets/badge-BuMDU0Rf.js +0 -1
- package/.output/public/assets/card-BEvx_QKh.js +0 -1
- package/.output/public/assets/chart-no-axes-combined-C0G0fQ3J.js +0 -1
- package/.output/public/assets/composite-CHTH1Xhh.js +0 -1
- package/.output/public/assets/createServerFn-DHc2erub.js +0 -3
- package/.output/public/assets/dialog-dyZ1SphZ.js +0 -1
- package/.output/public/assets/dist-Bh1gaDfB.js +0 -1
- package/.output/public/assets/field-79SVH26s.js +0 -1
- package/.output/public/assets/index-BNkxg4jz.js +0 -10
- package/.output/public/assets/input-group-BbHfaBzQ.js +0 -1
- package/.output/public/assets/insights-D_7uSKfc.js +0 -1
- package/.output/public/assets/installations-BzoHWuXw.js +0 -1
- package/.output/public/assets/installations-DvjbUqd1.js +0 -2
- package/.output/public/assets/lazyRouteComponent-BN0MS05o.js +0 -1
- package/.output/public/assets/link-BG06whwB.js +0 -1
- package/.output/public/assets/loader-circle-Bbp8LGVC.js +0 -1
- package/.output/public/assets/preload-helper-uTix4PVD.js +0 -1
- package/.output/public/assets/refresh-cw-DZgiAOMk.js +0 -1
- package/.output/public/assets/routes-BFE-oRIT.js +0 -1
- package/.output/public/assets/routes-DaPMY31P.js +0 -2
- package/.output/public/assets/shield-check-DoIiaaXU.js +0 -1
- package/.output/public/assets/signing-BTRhaF5D.js +0 -1
- package/.output/public/assets/styles-C7U0oVke.css +0 -2
- package/.output/public/assets/table-BBEzJ7Xl.js +0 -1
- package/.output/public/assets/useCompositeListItem-BkPDaqyj.js +0 -42
- package/.output/server/_ssr/InsightsPageHeader-C5Has8Ry.mjs +0 -60
- package/.output/server/_ssr/api-keys-api-CrCZyvnl.mjs +0 -60
- package/.output/server/_ssr/api-keys-rpc-CFaNf-q0.mjs +0 -136
- package/.output/server/_ssr/api-rpc-B61KL3Eo.mjs +0 -1399
- package/.output/server/_ssr/auth.server-DF_shCeq.mjs +0 -47
- package/.output/server/_ssr/badge-BgsCP9Dp.mjs +0 -28
- package/.output/server/_ssr/bundle-signing-rpc-BBipZnFs.mjs +0 -16
- package/.output/server/_ssr/bundle-signing.server-Co_zHdJx.mjs +0 -12
- package/.output/server/_ssr/bundleActivity-D3_fIda2.mjs +0 -27
- package/.output/server/_ssr/card-DrbxCPtG.mjs +0 -86
- package/.output/server/_ssr/chart-qxa-_30a.mjs +0 -162
- package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
- package/.output/server/_ssr/config.server-CBr6PDj5.mjs +0 -368
- package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
- package/.output/server/_ssr/deleteBundle-BAq3JuGr.mjs +0 -66
- package/.output/server/_ssr/getBundleChildren-COMt16m_.mjs +0 -50
- package/.output/server/_ssr/insights-B-DUbksX.mjs +0 -502
- package/.output/server/_ssr/insights-recovery-rpc-P71FH66n.mjs +0 -37
- package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +0 -303
- package/.output/server/_ssr/insightsRecovery-CqcosG7B.mjs +0 -149
- package/.output/server/_ssr/installations-BQrppti7.mjs +0 -29
- package/.output/server/_ssr/routes-BDabsmc7.mjs +0 -37
- package/.output/server/_ssr/runtime.server-jmZOKIQM.mjs +0 -438
- package/.output/server/_ssr/server-p7yrVBfD.mjs +0 -7468
- package/.output/server/_ssr/signing-CxCbnFCt.mjs +0 -134
- package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
- package/.output/server/_tanstack-start-manifest_v-Cwpu0C9Q.mjs +0 -100
- package/src/components/features/bundles/BundleActivityChart.tsx +0 -95
- package/src/components/features/bundles/BundleInsightsSummary.tsx +0 -121
- package/src/components/features/signing/BundleSigningPage.tsx +0 -139
- package/src/lib/bundle-signing-api.ts +0 -14
- package/src/lib/bundle-signing-rpc.ts +0 -16
- package/src/lib/server/bundle-signing.server.ts +0 -23
- package/src/lib/server/deleteBundle.ts +0 -145
- package/src/lib/server/insightsHistory.ts +0 -52
- package/src/lib/server/insightsRecovery.ts +0 -186
- package/src/routes/signing.tsx +0 -37
|
@@ -4,7 +4,13 @@ import type { ReactNode } from "react";
|
|
|
4
4
|
|
|
5
5
|
import { HashValueDisplay } from "@/components/HashValueDisplay";
|
|
6
6
|
import { Button, buttonVariants } from "@/components/ui/button";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
Card,
|
|
9
|
+
CardContent,
|
|
10
|
+
CardDescription,
|
|
11
|
+
CardHeader,
|
|
12
|
+
CardTitle,
|
|
13
|
+
} from "@/components/ui/card";
|
|
8
14
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
9
15
|
import {
|
|
10
16
|
Table,
|
|
@@ -14,23 +20,39 @@ import {
|
|
|
14
20
|
TableHeader,
|
|
15
21
|
TableRow,
|
|
16
22
|
} from "@/components/ui/table";
|
|
17
|
-
import
|
|
23
|
+
import {
|
|
24
|
+
EVENT_RANGES,
|
|
25
|
+
type EventRange,
|
|
26
|
+
eventRanges,
|
|
27
|
+
type InsightsEventRow,
|
|
28
|
+
type InsightsViewPage,
|
|
29
|
+
widerEventRange,
|
|
30
|
+
} from "@/lib/insights-view";
|
|
18
31
|
|
|
19
32
|
import {
|
|
20
33
|
EventBundleTransition,
|
|
21
34
|
EventTimestamp,
|
|
22
|
-
|
|
35
|
+
EventTypeDetails,
|
|
23
36
|
useInsightsTimeFormat,
|
|
24
37
|
} from "./EventDetails";
|
|
25
38
|
import { EventHistoryList } from "./EventHistoryList";
|
|
26
39
|
import { InsightsErrorAlert } from "./InsightsErrorAlert";
|
|
27
40
|
import { InsightsPagination } from "./InsightsPagination";
|
|
41
|
+
import { PeriodSelector } from "./InsightsPeriodSelector";
|
|
28
42
|
|
|
29
43
|
type EventsLocationState = {
|
|
30
44
|
readonly eventsBefore: number;
|
|
31
45
|
readonly eventsCursor?: string;
|
|
46
|
+
readonly eventsRange?: EventRange;
|
|
32
47
|
};
|
|
33
48
|
|
|
49
|
+
/** The ranges within the days raw events are kept, the shortest always. */
|
|
50
|
+
const rangesWithin = (days: number) =>
|
|
51
|
+
EVENT_RANGES.filter(
|
|
52
|
+
(value, position) =>
|
|
53
|
+
position === 0 || eventRanges[value].durationMs <= days * 86_400_000,
|
|
54
|
+
);
|
|
55
|
+
|
|
34
56
|
function EventIdentity({
|
|
35
57
|
event,
|
|
36
58
|
eventsLocation,
|
|
@@ -62,12 +84,17 @@ function EventIdentity({
|
|
|
62
84
|
query: event.installId || undefined,
|
|
63
85
|
installId: event.installId,
|
|
64
86
|
}}
|
|
65
|
-
|
|
87
|
+
state={(previous) => ({
|
|
88
|
+
insightsPagination: {
|
|
89
|
+
eventsBack: previous.insightsPagination?.eventsBack,
|
|
90
|
+
},
|
|
91
|
+
})}
|
|
92
|
+
aria-label={`View history for ${event.userId ?? "anonymous installation"} (${event.installId})`}
|
|
66
93
|
>
|
|
67
94
|
<span
|
|
68
95
|
className={touch ? "min-w-0 text-left wrap-anywhere" : "truncate"}
|
|
69
96
|
>
|
|
70
|
-
{event.userId ??
|
|
97
|
+
{event.userId ?? "Anonymous installation"}
|
|
71
98
|
</span>
|
|
72
99
|
<ArrowUpRight aria-hidden="true" data-icon="inline-end" />
|
|
73
100
|
</Link>
|
|
@@ -82,6 +109,7 @@ function EventIdentity({
|
|
|
82
109
|
|
|
83
110
|
export function EventHistoryCard({
|
|
84
111
|
children,
|
|
112
|
+
description,
|
|
85
113
|
error,
|
|
86
114
|
eventsLocation,
|
|
87
115
|
history,
|
|
@@ -89,11 +117,16 @@ export function EventHistoryCard({
|
|
|
89
117
|
isLoading,
|
|
90
118
|
onNext,
|
|
91
119
|
onPrevious,
|
|
120
|
+
onRangeChange,
|
|
92
121
|
onRefresh,
|
|
93
122
|
pageNumber,
|
|
123
|
+
range,
|
|
124
|
+
rawDays = 90,
|
|
94
125
|
title = "All events",
|
|
95
126
|
}: {
|
|
96
127
|
readonly children?: ReactNode;
|
|
128
|
+
/** One line under the title that says what the list holds. */
|
|
129
|
+
readonly description?: ReactNode;
|
|
97
130
|
readonly error: Error | null;
|
|
98
131
|
readonly eventsLocation: EventsLocationState;
|
|
99
132
|
readonly history: InsightsViewPage<InsightsEventRow> | undefined;
|
|
@@ -101,30 +134,68 @@ export function EventHistoryCard({
|
|
|
101
134
|
readonly isLoading: boolean;
|
|
102
135
|
readonly onNext: () => void;
|
|
103
136
|
readonly onPrevious: () => void;
|
|
137
|
+
/** Lets the list change its range; a list with a fixed window leaves it out. */
|
|
138
|
+
readonly onRangeChange?: (range: EventRange) => void;
|
|
104
139
|
readonly onRefresh: () => void;
|
|
105
140
|
readonly pageNumber: number;
|
|
141
|
+
/** The time range the list reads, named by its empty and end states. */
|
|
142
|
+
readonly range?: EventRange;
|
|
143
|
+
/** How long the server keeps raw events: the longest range offered. */
|
|
144
|
+
readonly rawDays?: number;
|
|
106
145
|
readonly title?: string;
|
|
107
146
|
}) {
|
|
108
147
|
const dateTimeFormat = useInsightsTimeFormat();
|
|
109
148
|
const hasPrevious = pageNumber > 1;
|
|
149
|
+
const ranges = rangesWithin(rawDays);
|
|
150
|
+
const next = range === undefined ? undefined : widerEventRange(range);
|
|
151
|
+
const wider = next !== undefined && ranges.includes(next) ? next : undefined;
|
|
152
|
+
const widen =
|
|
153
|
+
wider !== undefined && onRangeChange ? (
|
|
154
|
+
<Button
|
|
155
|
+
className="h-11 px-3 lg:h-8"
|
|
156
|
+
onClick={() => onRangeChange(wider)}
|
|
157
|
+
type="button"
|
|
158
|
+
variant="outline"
|
|
159
|
+
>
|
|
160
|
+
{`Show the last ${eventRanges[wider].period}`}
|
|
161
|
+
</Button>
|
|
162
|
+
) : null;
|
|
110
163
|
|
|
111
164
|
return (
|
|
112
165
|
<Card className="@container min-w-0 shadow-sm" aria-busy={isFetching}>
|
|
113
166
|
<CardHeader className="gap-4 p-4 sm:p-6">
|
|
114
|
-
<div className="flex flex-
|
|
115
|
-
<
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
className="
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
167
|
+
<div className="flex flex-col gap-1.5">
|
|
168
|
+
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
169
|
+
<CardTitle>
|
|
170
|
+
<h2>{title}</h2>
|
|
171
|
+
</CardTitle>
|
|
172
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
173
|
+
{range !== undefined && onRangeChange ? (
|
|
174
|
+
<PeriodSelector
|
|
175
|
+
label="Time range"
|
|
176
|
+
onPeriodChange={onRangeChange}
|
|
177
|
+
period={range}
|
|
178
|
+
periods={ranges.map((value) => ({
|
|
179
|
+
value,
|
|
180
|
+
name: `Last ${eventRanges[value].period}`,
|
|
181
|
+
}))}
|
|
182
|
+
/>
|
|
183
|
+
) : null}
|
|
184
|
+
<Button
|
|
185
|
+
className="h-11 lg:h-8"
|
|
186
|
+
disabled={isFetching}
|
|
187
|
+
onClick={onRefresh}
|
|
188
|
+
size="lg"
|
|
189
|
+
variant="outline"
|
|
190
|
+
>
|
|
191
|
+
<RefreshCw aria-hidden="true" data-icon="inline-start" />
|
|
192
|
+
Refresh
|
|
193
|
+
</Button>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
{description ? (
|
|
197
|
+
<CardDescription>{description}</CardDescription>
|
|
198
|
+
) : null}
|
|
128
199
|
</div>
|
|
129
200
|
{children}
|
|
130
201
|
</CardHeader>
|
|
@@ -189,7 +260,7 @@ export function EventHistoryCard({
|
|
|
189
260
|
/>
|
|
190
261
|
</TableCell>
|
|
191
262
|
<TableCell>
|
|
192
|
-
<
|
|
263
|
+
<EventTypeDetails event={event} />
|
|
193
264
|
</TableCell>
|
|
194
265
|
<TableCell>
|
|
195
266
|
<EventIdentity
|
|
@@ -218,14 +289,29 @@ export function EventHistoryCard({
|
|
|
218
289
|
</div>
|
|
219
290
|
</>
|
|
220
291
|
) : (
|
|
221
|
-
<div className="px-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
292
|
+
<div className="flex flex-wrap items-center justify-between gap-3 px-4 pb-4 sm:px-6 sm:pb-6">
|
|
293
|
+
<p className="text-sm text-muted-foreground">
|
|
294
|
+
{hasPrevious
|
|
295
|
+
? "No older events in this range."
|
|
296
|
+
: range !== undefined
|
|
297
|
+
? `No events in the last ${eventRanges[range].period}.`
|
|
298
|
+
: title === "All events"
|
|
299
|
+
? "No events recorded yet"
|
|
300
|
+
: "No matching reports in this period"}
|
|
301
|
+
</p>
|
|
302
|
+
{hasPrevious ? null : widen}
|
|
227
303
|
</div>
|
|
228
304
|
)}
|
|
305
|
+
{range !== undefined &&
|
|
306
|
+
history.nextCursor === null &&
|
|
307
|
+
(history.data.length > 0 || hasPrevious) ? (
|
|
308
|
+
<div className="flex flex-wrap items-center justify-between gap-3 border-t px-4 py-3 sm:px-6">
|
|
309
|
+
<p className="text-sm text-muted-foreground">
|
|
310
|
+
Start of range reached
|
|
311
|
+
</p>
|
|
312
|
+
{widen}
|
|
313
|
+
</div>
|
|
314
|
+
) : null}
|
|
229
315
|
{history.data.length > 0 || hasPrevious || history.nextCursor ? (
|
|
230
316
|
<InsightsPagination
|
|
231
317
|
hasPrevious={hasPrevious}
|
|
@@ -5,7 +5,7 @@ import type { InsightsEventRow } from "@/lib/insights-view";
|
|
|
5
5
|
import {
|
|
6
6
|
EventBundleTransition,
|
|
7
7
|
EventTimestamp,
|
|
8
|
-
|
|
8
|
+
EventTypeDetails,
|
|
9
9
|
} from "./EventDetails";
|
|
10
10
|
|
|
11
11
|
export function EventHistoryList<T extends InsightsEventRow>({
|
|
@@ -29,7 +29,7 @@ export function EventHistoryList<T extends InsightsEventRow>({
|
|
|
29
29
|
className="flex min-w-0 flex-col gap-3 px-4 py-4 sm:px-6"
|
|
30
30
|
>
|
|
31
31
|
<div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-1">
|
|
32
|
-
<
|
|
32
|
+
<EventTypeDetails event={event} />
|
|
33
33
|
<div className="text-xs tabular-nums">
|
|
34
34
|
<EventTimestamp
|
|
35
35
|
value={event.receivedAtMs}
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import { Link } from "@tanstack/react-router";
|
|
2
|
+
import { ArrowUpRight, Copy, RefreshCw } from "lucide-react";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { toast } from "sonner";
|
|
5
|
+
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import {
|
|
8
|
+
Empty,
|
|
9
|
+
EmptyDescription,
|
|
10
|
+
EmptyHeader,
|
|
11
|
+
EmptyTitle,
|
|
12
|
+
} from "@/components/ui/empty";
|
|
13
|
+
import {
|
|
14
|
+
Sheet,
|
|
15
|
+
SheetContent,
|
|
16
|
+
SheetDescription,
|
|
17
|
+
SheetHeader,
|
|
18
|
+
SheetTitle,
|
|
19
|
+
SheetTrigger,
|
|
20
|
+
} from "@/components/ui/sheet";
|
|
21
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
22
|
+
import {
|
|
23
|
+
Table,
|
|
24
|
+
TableBody,
|
|
25
|
+
TableCell,
|
|
26
|
+
TableHead,
|
|
27
|
+
TableHeader,
|
|
28
|
+
TableRow,
|
|
29
|
+
} from "@/components/ui/table";
|
|
30
|
+
import { useFailureReportsQuery } from "@/lib/insights-api";
|
|
31
|
+
import {
|
|
32
|
+
groupFailureReports,
|
|
33
|
+
type FailureGroup,
|
|
34
|
+
type FailureReportsInput,
|
|
35
|
+
type FailureReportsPage,
|
|
36
|
+
} from "@/lib/insights-errors";
|
|
37
|
+
import {
|
|
38
|
+
failureDetailParts,
|
|
39
|
+
failureReasonLabel,
|
|
40
|
+
failureStageLabel,
|
|
41
|
+
} from "@/lib/insights-failures";
|
|
42
|
+
import type { InsightsEventRow } from "@/lib/insights-view";
|
|
43
|
+
import { cn } from "@/lib/utils";
|
|
44
|
+
|
|
45
|
+
import {
|
|
46
|
+
EventBundleTransition,
|
|
47
|
+
EventTimestamp,
|
|
48
|
+
HttpResponseBody,
|
|
49
|
+
useInsightsTimeFormat,
|
|
50
|
+
} from "./EventDetails";
|
|
51
|
+
import { InsightsErrorAlert } from "./InsightsErrorAlert";
|
|
52
|
+
|
|
53
|
+
const messageOf = (event: InsightsEventRow) =>
|
|
54
|
+
event.failure?.errorMessage || "No error message reported";
|
|
55
|
+
|
|
56
|
+
function FailureDetail({ group }: { readonly group: FailureGroup }) {
|
|
57
|
+
const [selectedId, setSelectedId] = useState(group.reports[0]!.id);
|
|
58
|
+
const event =
|
|
59
|
+
group.reports.find(({ id }) => id === selectedId) ?? group.reports[0]!;
|
|
60
|
+
const failure = event.failure!;
|
|
61
|
+
const formatter = useInsightsTimeFormat();
|
|
62
|
+
const context = [
|
|
63
|
+
[
|
|
64
|
+
"App",
|
|
65
|
+
`${event.platform === "ios" ? "iOS" : "Android"} ${event.appVersion}`,
|
|
66
|
+
],
|
|
67
|
+
["Channel", event.channel],
|
|
68
|
+
["SDK", event.sdkVersion ?? "Not reported"],
|
|
69
|
+
["Stage", failureStageLabel(failure.stage)],
|
|
70
|
+
["Resource", failure.resource],
|
|
71
|
+
["HTTP status", failure.httpStatus?.toString()],
|
|
72
|
+
["Origin code", failure.originCode],
|
|
73
|
+
["Transport", failure.transport],
|
|
74
|
+
].filter(([, value]) => value !== undefined);
|
|
75
|
+
const copy = async () => {
|
|
76
|
+
try {
|
|
77
|
+
await navigator.clipboard.writeText(JSON.stringify(event, null, 2));
|
|
78
|
+
toast.success("Error report copied");
|
|
79
|
+
} catch {
|
|
80
|
+
toast.error("Could not copy the report");
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
return (
|
|
84
|
+
<SheetContent className="data-[side=right]:w-full data-[side=right]:sm:max-w-2xl">
|
|
85
|
+
<SheetHeader className="border-b pr-14">
|
|
86
|
+
<SheetTitle>Error details</SheetTitle>
|
|
87
|
+
<SheetDescription>
|
|
88
|
+
{group.reports.length.toLocaleString()} loaded reports ·{" "}
|
|
89
|
+
{group.installations.toLocaleString()} installations
|
|
90
|
+
</SheetDescription>
|
|
91
|
+
</SheetHeader>
|
|
92
|
+
<div className="flex min-h-0 flex-1 flex-col gap-6 overflow-y-auto p-6">
|
|
93
|
+
<section
|
|
94
|
+
aria-label="Original error"
|
|
95
|
+
className="flex min-w-0 flex-col gap-3"
|
|
96
|
+
>
|
|
97
|
+
<p className="text-xs text-muted-foreground">
|
|
98
|
+
{failureStageLabel(failure.stage)} ·{" "}
|
|
99
|
+
{failureReasonLabel(failure.reason)}
|
|
100
|
+
</p>
|
|
101
|
+
<h3
|
|
102
|
+
className={cn(
|
|
103
|
+
"text-sm leading-relaxed whitespace-pre-wrap wrap-anywhere",
|
|
104
|
+
failure.errorMessage ? "font-mono" : "text-muted-foreground",
|
|
105
|
+
)}
|
|
106
|
+
>
|
|
107
|
+
{messageOf(event)}
|
|
108
|
+
</h3>
|
|
109
|
+
{!failure.errorMessage ? (
|
|
110
|
+
<p className="text-sm text-muted-foreground">
|
|
111
|
+
This client did not send the original error. Upgrade the app’s
|
|
112
|
+
Insights plugin and server to collect details for future failures.
|
|
113
|
+
</p>
|
|
114
|
+
) : null}
|
|
115
|
+
<Button
|
|
116
|
+
className="w-fit"
|
|
117
|
+
variant="outline"
|
|
118
|
+
onClick={() => void copy()}
|
|
119
|
+
>
|
|
120
|
+
<Copy aria-hidden="true" data-icon="inline-start" /> Copy report
|
|
121
|
+
</Button>
|
|
122
|
+
</section>
|
|
123
|
+
{event.httpResponse ? (
|
|
124
|
+
<section
|
|
125
|
+
aria-label="Server response"
|
|
126
|
+
className="flex min-w-0 flex-col gap-3"
|
|
127
|
+
>
|
|
128
|
+
<h3 className="text-sm font-medium">
|
|
129
|
+
Server response · HTTP {event.httpResponse.status}
|
|
130
|
+
</h3>
|
|
131
|
+
<p className="text-xs text-muted-foreground">
|
|
132
|
+
Most recent response available when this report was created.
|
|
133
|
+
</p>
|
|
134
|
+
<p className="font-mono text-xs wrap-anywhere">
|
|
135
|
+
GET {event.httpResponse.path}
|
|
136
|
+
</p>
|
|
137
|
+
<div className="text-xs text-muted-foreground">
|
|
138
|
+
Response received{" "}
|
|
139
|
+
<EventTimestamp
|
|
140
|
+
value={event.httpResponse.receivedAtMs}
|
|
141
|
+
formatter={formatter}
|
|
142
|
+
/>
|
|
143
|
+
</div>
|
|
144
|
+
<HttpResponseBody response={event.httpResponse} />
|
|
145
|
+
</section>
|
|
146
|
+
) : null}
|
|
147
|
+
<section
|
|
148
|
+
aria-label="Stack trace"
|
|
149
|
+
className="flex min-w-0 flex-col gap-2"
|
|
150
|
+
>
|
|
151
|
+
<h3 className="text-sm font-medium">Stack trace</h3>
|
|
152
|
+
{failure.errorStack ? (
|
|
153
|
+
<pre className="rounded-md border bg-muted/30 p-4 font-mono text-xs leading-relaxed whitespace-pre-wrap wrap-anywhere">
|
|
154
|
+
{failure.errorStack}
|
|
155
|
+
</pre>
|
|
156
|
+
) : (
|
|
157
|
+
<p className="text-sm text-muted-foreground">
|
|
158
|
+
No stack trace was sent with this report.
|
|
159
|
+
</p>
|
|
160
|
+
)}
|
|
161
|
+
</section>
|
|
162
|
+
<section aria-label="Report context" className="flex flex-col gap-3">
|
|
163
|
+
<h3 className="text-sm font-medium">Report context</h3>
|
|
164
|
+
<div className="text-xs text-muted-foreground">
|
|
165
|
+
<EventTimestamp value={event.receivedAtMs} formatter={formatter} />
|
|
166
|
+
</div>
|
|
167
|
+
<dl className="grid grid-cols-2 gap-x-6 gap-y-4">
|
|
168
|
+
{context.map(([label, value]) => (
|
|
169
|
+
<div key={label} className="min-w-0">
|
|
170
|
+
<dt className="text-xs text-muted-foreground">{label}</dt>
|
|
171
|
+
<dd className="mt-1 text-sm wrap-anywhere">{value}</dd>
|
|
172
|
+
</div>
|
|
173
|
+
))}
|
|
174
|
+
</dl>
|
|
175
|
+
<EventBundleTransition event={event} />
|
|
176
|
+
<Button
|
|
177
|
+
variant="outline"
|
|
178
|
+
className="w-fit"
|
|
179
|
+
render={
|
|
180
|
+
<Link
|
|
181
|
+
to="/installations"
|
|
182
|
+
search={{ query: event.installId, installId: event.installId }}
|
|
183
|
+
/>
|
|
184
|
+
}
|
|
185
|
+
>
|
|
186
|
+
Installation history{" "}
|
|
187
|
+
<ArrowUpRight aria-hidden="true" data-icon="inline-end" />
|
|
188
|
+
</Button>
|
|
189
|
+
<p className="font-mono text-xs text-muted-foreground wrap-anywhere">
|
|
190
|
+
{event.installId}
|
|
191
|
+
</p>
|
|
192
|
+
</section>
|
|
193
|
+
<section aria-label="Occurrences" className="flex flex-col gap-2">
|
|
194
|
+
<h3 className="text-sm font-medium">Occurrences in loaded reports</h3>
|
|
195
|
+
<p className="text-xs text-muted-foreground">
|
|
196
|
+
Select a report to inspect its stack and context. Times use your
|
|
197
|
+
browser’s zone.
|
|
198
|
+
</p>
|
|
199
|
+
<div className="flex flex-col gap-1">
|
|
200
|
+
{group.reports.map((report) => (
|
|
201
|
+
<Button
|
|
202
|
+
key={report.id}
|
|
203
|
+
variant={report.id === event.id ? "secondary" : "ghost"}
|
|
204
|
+
aria-pressed={report.id === event.id}
|
|
205
|
+
className="h-auto min-h-11 justify-between gap-4 whitespace-normal"
|
|
206
|
+
onClick={() => setSelectedId(report.id)}
|
|
207
|
+
>
|
|
208
|
+
<span className="text-left tabular-nums">
|
|
209
|
+
{formatter.format(report.receivedAtMs)}
|
|
210
|
+
</span>
|
|
211
|
+
<span className="shrink-0">{report.appVersion}</span>
|
|
212
|
+
</Button>
|
|
213
|
+
))}
|
|
214
|
+
</div>
|
|
215
|
+
</section>
|
|
216
|
+
</div>
|
|
217
|
+
</SheetContent>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function FailureReportsList({
|
|
222
|
+
pages,
|
|
223
|
+
error,
|
|
224
|
+
isPending,
|
|
225
|
+
isFetching,
|
|
226
|
+
hasNextPage,
|
|
227
|
+
onLoadMore,
|
|
228
|
+
onRefresh,
|
|
229
|
+
}: {
|
|
230
|
+
readonly pages: readonly FailureReportsPage[];
|
|
231
|
+
readonly error: Error | null;
|
|
232
|
+
readonly isPending: boolean;
|
|
233
|
+
readonly isFetching: boolean;
|
|
234
|
+
readonly hasNextPage: boolean;
|
|
235
|
+
readonly onLoadMore: () => void;
|
|
236
|
+
readonly onRefresh: () => void;
|
|
237
|
+
}) {
|
|
238
|
+
const groups = groupFailureReports(pages.flatMap((page) => page.data));
|
|
239
|
+
const reportCount = groups.reduce(
|
|
240
|
+
(sum, group) => sum + group.reports.length,
|
|
241
|
+
0,
|
|
242
|
+
);
|
|
243
|
+
const scanned = pages.reduce((sum, page) => sum + page.scanned, 0);
|
|
244
|
+
const formatter = useInsightsTimeFormat();
|
|
245
|
+
return (
|
|
246
|
+
<section
|
|
247
|
+
aria-label="Errors to investigate"
|
|
248
|
+
className="flex min-w-0 flex-col gap-3"
|
|
249
|
+
aria-busy={isFetching}
|
|
250
|
+
>
|
|
251
|
+
<div className="flex items-center justify-between gap-4">
|
|
252
|
+
<div className="flex flex-col gap-1">
|
|
253
|
+
<h3 className="text-sm font-medium">Errors to investigate</h3>
|
|
254
|
+
<p className="text-xs text-muted-foreground">
|
|
255
|
+
Original messages, most reported first. Select an error for its
|
|
256
|
+
stack and affected app.
|
|
257
|
+
</p>
|
|
258
|
+
</div>
|
|
259
|
+
<Button
|
|
260
|
+
variant="ghost"
|
|
261
|
+
size="icon-lg"
|
|
262
|
+
aria-label="Refresh error reports"
|
|
263
|
+
disabled={isFetching}
|
|
264
|
+
onClick={onRefresh}
|
|
265
|
+
>
|
|
266
|
+
<RefreshCw aria-hidden="true" />
|
|
267
|
+
</Button>
|
|
268
|
+
</div>
|
|
269
|
+
{isPending ? (
|
|
270
|
+
<Skeleton className="h-40" aria-label="Loading error reports" />
|
|
271
|
+
) : (
|
|
272
|
+
<>
|
|
273
|
+
{error ? (
|
|
274
|
+
<InsightsErrorAlert
|
|
275
|
+
error={error}
|
|
276
|
+
fallbackTitle="Error reports unavailable"
|
|
277
|
+
/>
|
|
278
|
+
) : null}
|
|
279
|
+
{groups.length ? (
|
|
280
|
+
<Table aria-label="Error messages">
|
|
281
|
+
<TableHeader>
|
|
282
|
+
<TableRow>
|
|
283
|
+
<TableHead>Error</TableHead>
|
|
284
|
+
<TableHead className="text-right">Reports</TableHead>
|
|
285
|
+
<TableHead className="hidden text-right sm:table-cell">
|
|
286
|
+
Installations
|
|
287
|
+
</TableHead>
|
|
288
|
+
<TableHead className="hidden lg:table-cell">
|
|
289
|
+
Last seen
|
|
290
|
+
</TableHead>
|
|
291
|
+
</TableRow>
|
|
292
|
+
</TableHeader>
|
|
293
|
+
<TableBody>
|
|
294
|
+
{groups.map((group) => {
|
|
295
|
+
const latest = group.reports[0]!;
|
|
296
|
+
const failure = latest.failure!;
|
|
297
|
+
return (
|
|
298
|
+
<TableRow key={group.key}>
|
|
299
|
+
<TableCell className="max-w-md whitespace-normal">
|
|
300
|
+
<Sheet>
|
|
301
|
+
<SheetTrigger
|
|
302
|
+
render={
|
|
303
|
+
<Button
|
|
304
|
+
variant="link"
|
|
305
|
+
className="h-auto min-h-11 w-full justify-start whitespace-normal px-0 text-left"
|
|
306
|
+
/>
|
|
307
|
+
}
|
|
308
|
+
>
|
|
309
|
+
<span className="flex min-w-0 flex-col gap-1">
|
|
310
|
+
<span
|
|
311
|
+
className={cn(
|
|
312
|
+
"line-clamp-2 text-xs leading-relaxed wrap-anywhere",
|
|
313
|
+
latest.failure?.errorMessage
|
|
314
|
+
? "font-mono"
|
|
315
|
+
: "text-muted-foreground",
|
|
316
|
+
)}
|
|
317
|
+
>
|
|
318
|
+
{messageOf(latest)}
|
|
319
|
+
</span>
|
|
320
|
+
<span className="text-xs font-normal text-muted-foreground">
|
|
321
|
+
{[
|
|
322
|
+
failureStageLabel(failure.stage),
|
|
323
|
+
...failureDetailParts(failure),
|
|
324
|
+
].join(" · ")}
|
|
325
|
+
</span>
|
|
326
|
+
</span>
|
|
327
|
+
</SheetTrigger>
|
|
328
|
+
<FailureDetail group={group} />
|
|
329
|
+
</Sheet>
|
|
330
|
+
</TableCell>
|
|
331
|
+
<TableCell className="text-right font-medium tabular-nums">
|
|
332
|
+
{group.reports.length.toLocaleString()}
|
|
333
|
+
</TableCell>
|
|
334
|
+
<TableCell className="hidden text-right tabular-nums sm:table-cell">
|
|
335
|
+
{group.installations.toLocaleString()}
|
|
336
|
+
</TableCell>
|
|
337
|
+
<TableCell className="hidden text-xs text-muted-foreground tabular-nums lg:table-cell">
|
|
338
|
+
{formatter.format(latest.receivedAtMs)}
|
|
339
|
+
</TableCell>
|
|
340
|
+
</TableRow>
|
|
341
|
+
);
|
|
342
|
+
})}
|
|
343
|
+
</TableBody>
|
|
344
|
+
</Table>
|
|
345
|
+
) : !error ? (
|
|
346
|
+
<Empty>
|
|
347
|
+
<EmptyHeader>
|
|
348
|
+
<EmptyTitle>
|
|
349
|
+
{hasNextPage
|
|
350
|
+
? "No failures in the events loaded so far"
|
|
351
|
+
: "No error reports in this range"}
|
|
352
|
+
</EmptyTitle>
|
|
353
|
+
<EmptyDescription>
|
|
354
|
+
{hasNextPage
|
|
355
|
+
? "Load older events to continue investigating this period."
|
|
356
|
+
: "Reports appear here when a client sends an update failure. Raw history may be shorter than aggregate history."}
|
|
357
|
+
</EmptyDescription>
|
|
358
|
+
</EmptyHeader>
|
|
359
|
+
</Empty>
|
|
360
|
+
) : null}
|
|
361
|
+
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
362
|
+
<p className="text-xs text-muted-foreground" role="status">
|
|
363
|
+
{reportCount.toLocaleString()} failure reports from{" "}
|
|
364
|
+
{scanned.toLocaleString()} events reviewed.
|
|
365
|
+
{hasNextPage
|
|
366
|
+
? " More events remain; these counts are not period totals."
|
|
367
|
+
: error
|
|
368
|
+
? " Scan interrupted."
|
|
369
|
+
: " End of available range."}
|
|
370
|
+
{pages[0]
|
|
371
|
+
? ` Raw history since ${formatter.format(pages[0].sinceMs)}.`
|
|
372
|
+
: ""}
|
|
373
|
+
</p>
|
|
374
|
+
{hasNextPage ? (
|
|
375
|
+
<Button
|
|
376
|
+
variant="outline"
|
|
377
|
+
disabled={isFetching}
|
|
378
|
+
onClick={onLoadMore}
|
|
379
|
+
>
|
|
380
|
+
{isFetching ? "Loading…" : "Load older events"}
|
|
381
|
+
</Button>
|
|
382
|
+
) : null}
|
|
383
|
+
</div>
|
|
384
|
+
</>
|
|
385
|
+
)}
|
|
386
|
+
</section>
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function FailureReports({
|
|
391
|
+
input,
|
|
392
|
+
}: {
|
|
393
|
+
readonly input: FailureReportsInput;
|
|
394
|
+
}) {
|
|
395
|
+
const query = useFailureReportsQuery(input, true);
|
|
396
|
+
return (
|
|
397
|
+
<FailureReportsList
|
|
398
|
+
pages={query.data?.pages ?? []}
|
|
399
|
+
error={query.error}
|
|
400
|
+
isPending={query.isPending}
|
|
401
|
+
isFetching={query.isFetching}
|
|
402
|
+
hasNextPage={query.hasNextPage}
|
|
403
|
+
onLoadMore={() => void query.fetchNextPage()}
|
|
404
|
+
onRefresh={() => void query.refetch()}
|
|
405
|
+
/>
|
|
406
|
+
);
|
|
407
|
+
}
|