@hot-updater/console 1.0.0-rc.2 → 1.0.0-rc.21
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-D0LPL1Gy.js +1 -0
- package/.output/public/assets/HashValueDisplay-BmuB4FW-.js +1 -0
- package/.output/public/assets/InsightsInfo--v4SC07c.js +1 -0
- package/.output/public/assets/InsightsPageHeader-dU83CAQY.js +1 -0
- package/.output/public/assets/NotFoundPage-B5mhGG2a.js +1 -0
- package/.output/public/assets/api-D-vOVlQ4.js +1 -0
- package/.output/public/assets/api-keys-Csh8ARKU.js +1 -0
- package/.output/public/assets/api-keys-OMPUoA_A.js +1 -0
- package/.output/public/assets/arrow-left-Bwq9X1eS.js +1 -0
- package/.output/public/assets/arrow-up-right-BkOVn4if.js +1 -0
- package/.output/public/assets/card-C_7d6O9a.js +1 -0
- package/.output/public/assets/console-features-api-C1jRZTGw.js +1 -0
- package/.output/public/assets/dialog-DE8wFN_P.js +1 -0
- package/.output/public/assets/dist-C7ENGzpS.js +1 -0
- package/.output/public/assets/empty-CVd5NlQD.js +1 -0
- package/.output/public/assets/field-C06q1iC9.js +1 -0
- package/.output/public/assets/index-BeWlQtO6.js +11 -0
- package/.output/public/assets/input-group-Bi61biIA.js +1 -0
- package/.output/public/assets/insights-BjnAtC0f.js +2 -0
- package/.output/public/assets/insights-D86m11yH.js +42 -0
- package/.output/public/assets/insights-DpYstMoY.js +1 -0
- package/.output/public/assets/insights-recovery-rpc-BIm5_ALc.js +1 -0
- package/.output/public/assets/insights-search-KIF7mRtB.js +1 -0
- package/.output/public/assets/insights-usage-rpc-VHj8vyBt.js +1 -0
- package/.output/public/assets/insights_.distribution-ADcrX48c.js +2 -0
- package/.output/public/assets/insights_.distribution-CuuJUN6Y.js +1 -0
- package/.output/public/assets/insights_.distribution-DJRjuXsO.js +1 -0
- package/.output/public/assets/installations-BSBBzqWs.js +2 -0
- package/.output/public/assets/installations-DqIE6WcO.js +1 -0
- package/.output/public/assets/installations-UsELbDvw.js +1 -0
- package/.output/public/assets/loader-circle-CnCBxzHp.js +1 -0
- package/.output/public/assets/mutation-Csp7xUNh.js +1 -0
- package/.output/public/assets/preload-helper-B-cOmpLH.js +1 -0
- package/.output/public/assets/refresh-cw-FF1NtzlQ.js +1 -0
- package/.output/public/assets/routes-CoN9XHP_.js +2 -0
- package/.output/public/assets/routes-DT1jB0Zi.js +1 -0
- package/.output/public/assets/sidebar-D8mJRF15.js +3 -0
- package/.output/public/assets/styles-Bwt7v6U5.css +2 -0
- package/.output/public/assets/table-WBgfvgXg.js +1 -0
- package/.output/public/assets/useCompositeListItem-CloBmDwn.js +1 -0
- package/.output/public/assets/useMutation-DO8cvsTx.js +1 -0
- package/.output/public/assets/useOpenInteractionType-ReGr9GzJ.js +1 -0
- package/.output/public/assets/valueToPercent-BvPAi4bP.js +1 -0
- package/.output/server/{__23tanstack-start-server-fn-resolver-B7ioJhtE.mjs → __23tanstack-start-server-fn-resolver-DMRD4e4f.mjs} +51 -47
- package/.output/server/_libs/@base-ui/react+[...].mjs +7044 -3622
- package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +1554 -0
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +3803 -4023
- package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +141 -98
- package/.output/server/_libs/d3-array.mjs +231 -0
- package/.output/server/_libs/d3-color.mjs +330 -0
- package/.output/server/_libs/d3-format.mjs +252 -0
- package/.output/server/_libs/d3-interpolate.mjs +207 -0
- package/.output/server/_libs/d3-path.mjs +77 -0
- package/.output/server/_libs/d3-scale+[...].mjs +1907 -0
- package/.output/server/_libs/d3-shape.mjs +818 -0
- package/.output/server/_libs/decimal.js-light.mjs +884 -0
- package/.output/server/_libs/es-toolkit.mjs +1159 -0
- package/.output/server/_libs/eventemitter3.mjs +236 -0
- package/.output/server/_libs/immer.mjs +532 -0
- package/.output/server/_libs/lucide-react.mjs +46 -32
- package/.output/server/_libs/next-themes.mjs +2 -2
- package/.output/server/_libs/react-redux+[...].mjs +205 -0
- package/.output/server/_libs/recharts+[...].mjs +14350 -0
- package/.output/server/_libs/sonner.mjs +2 -2
- package/.output/server/_libs/tanstack__history.mjs +68 -41
- package/.output/server/_libs/tanstack__query-core.mjs +1618 -309
- package/.output/server/_libs/tanstack__react-query.mjs +262 -22
- package/.output/server/_libs/verkit.mjs +99 -1
- package/.output/server/_runtime.mjs +10 -1
- package/.output/server/_ssr/ConsoleFeatureUnavailable-Du7jNPNS.mjs +58 -0
- package/.output/server/_ssr/{HashValueDisplay-DwpGLfrl.mjs → HashValueDisplay-GQvFkXoi.mjs} +3 -3
- package/.output/server/_ssr/InsightsInfo-Do7QSaFR.mjs +37 -0
- package/.output/server/_ssr/InsightsPageHeader-DSUWUWRc.mjs +154 -0
- package/.output/server/_ssr/NotFoundPage-DsF4Vrhg.mjs +37 -0
- package/.output/server/_ssr/api-BEiVs0H7.mjs +203 -0
- package/.output/server/_ssr/api-keys-BrvBWGRf.mjs +5 -0
- package/.output/server/_ssr/{api-keys-CZeTPQys.mjs → api-keys-K5v32nf7.mjs} +49 -9
- package/.output/server/_ssr/api-keys-rpc-B96bBllm.mjs +52 -0
- package/.output/server/_ssr/api-rpc-DcUe280B.mjs +296 -0
- package/.output/server/_ssr/{auth-rpc-DUaeOWKK.mjs → auth-rpc-C68W56DP.mjs} +6 -6
- package/.output/server/_ssr/auth.server-DM1J6NwR.mjs +5712 -0
- package/.output/server/_ssr/bundleActivity-CpuY9CTP.mjs +19 -0
- package/.output/server/_ssr/card-CPAEKx0w.mjs +49 -0
- package/.output/server/_ssr/config-nsIcsHh8.mjs +15 -0
- package/.output/server/_ssr/config.server-DDCZk3Sq.mjs +36 -0
- package/.output/server/_ssr/console-access-Da-2Qd7q.mjs +17 -0
- package/.output/server/_ssr/console-features-Dzu2iUGD.mjs +55 -0
- package/.output/server/_ssr/console-features-api-xAsSgFa_.mjs +43 -0
- package/.output/server/_ssr/console-features-rpc-qARGpB24.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-DS-nw-pA.mjs → dialog-q__BJ7mQ.mjs} +2 -2
- package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-Cxo9ghYv.mjs} +140 -6
- package/.output/server/_ssr/{downloadBundle-CNnYxYO2.mjs → downloadBundle-BW9I-pMQ.mjs} +10 -10
- package/.output/server/_ssr/empty-DcBTAooP.mjs +49 -0
- package/.output/server/_ssr/{empty-plugin-adapters-B5QvGpEl.mjs → empty-plugin-adapters-CYdinE-i.mjs} +1 -1
- package/.output/server/_ssr/{table-CnZmon_Z.mjs → field-QCDBVpXA.mjs} +63 -51
- package/.output/server/_ssr/getBundleChildren-DIAGCF4a.mjs +22 -0
- package/.output/server/_ssr/{input-group-BpsnmvZw.mjs → input-group-hCk2nUoU.mjs} +2 -2
- package/.output/server/_ssr/insights-BfBO0RFr.mjs +1238 -0
- package/.output/server/_ssr/insights-DsAU3-TB.mjs +14 -0
- package/.output/server/_ssr/insights-RIfg447W.mjs +5 -0
- package/.output/server/_ssr/insights-api-CDn-QQHU.mjs +105 -0
- package/.output/server/_ssr/insights-failures-BBGMhuUr.mjs +44 -0
- package/.output/server/_ssr/insights-failures-rpc-rA2dctK2.mjs +21 -0
- package/.output/server/_ssr/insights-recovery-Dpk6Mfb9.mjs +28 -0
- package/.output/server/_ssr/insights-recovery-rpc-BCaF-xSd.mjs +46 -0
- package/.output/server/_ssr/insights-recovery-rpc-CcgYyUHk.mjs +20 -0
- package/.output/server/_ssr/{insights-rpc-38DujK0G.mjs → insights-rpc-keycmwzw.mjs} +22 -14
- package/.output/server/_ssr/insights-search-BRg5VsNe.mjs +25 -0
- package/.output/server/_ssr/insights-usage-cbw9n2Fq.mjs +29 -0
- package/.output/server/_ssr/insights-usage-rpc-BKgV1QDf.mjs +29 -0
- package/.output/server/_ssr/insights-usage-rpc-D-JYachy.mjs +41 -0
- package/.output/server/_ssr/insightsRecovery-B7oMc8d8.mjs +45 -0
- package/.output/server/_ssr/insightsUsage-Oa7kk5ek.mjs +56 -0
- package/.output/server/_ssr/insights_.distribution-BIqOqRW0.mjs +335 -0
- package/.output/server/_ssr/insights_.distribution-CuUrDa7P.mjs +5 -0
- package/.output/server/_ssr/insights_.distribution-RT_6x_ud.mjs +14 -0
- package/.output/server/_ssr/{installations-DiVKEF5T.mjs → installations-BO5CjX2z.mjs} +517 -54
- package/.output/server/_ssr/installations-BjaHiIfh.mjs +5 -0
- package/.output/server/_ssr/installations-Dt5luA-N.mjs +69 -0
- package/.output/server/_ssr/listReleases-BitHzOYB.mjs +79 -0
- package/.output/server/_ssr/releaseManagement-B3e7I8J9.mjs +11 -0
- package/.output/server/_ssr/{router-B9wG_9f8.mjs → router-DfVAixxe.mjs} +135 -139
- package/.output/server/_ssr/{routes-D7Yh_jfj.mjs → routes-C6LstNp8.mjs} +176 -455
- package/.output/server/_ssr/routes-Q3Uhmwwz.mjs +292 -0
- package/.output/server/_ssr/runtime.server-fL0o6I2b.mjs +1459 -0
- package/.output/server/_ssr/schema-BKY9SqaY.mjs +25 -0
- package/.output/server/_ssr/select-CUlInmjk.mjs +107 -0
- package/.output/server/_ssr/server-C43wq9ft.mjs +8330 -0
- package/.output/server/_ssr/{createSsrRpc-DjWNJ0uc.mjs → sidebar-DKbx2ach.mjs} +117 -63
- package/.output/server/_ssr/ssr.mjs +1 -1
- package/.output/server/_ssr/start-BXUOv_n8.mjs +4 -0
- package/.output/server/_ssr/table-NGWV8B-S.mjs +53 -0
- package/.output/server/_ssr/updateFailures-Jwe2Baab.mjs +24 -0
- package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-C8bwiZr8.mjs} +2 -2
- package/.output/server/_tanstack-start-manifest_v-WQ5qGn8n.mjs +124 -0
- package/.output/server/index.mjs +267 -162
- 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 +99 -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 +370 -0
- package/src/components/features/insights/EstimatedCount.tsx +18 -0
- package/src/components/features/insights/EventDetails.tsx +117 -37
- package/src/components/features/insights/EventHistoryCard.tsx +112 -26
- package/src/components/features/insights/EventHistoryList.tsx +2 -2
- package/src/components/features/insights/InsightsControls.tsx +265 -102
- 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 +176 -173
- 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 +52 -10
- package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
- package/src/components/features/insights/ReleaseActivity.tsx +188 -0
- package/src/components/features/insights/ReportingDevicesSummary.tsx +65 -0
- package/src/components/features/insights/UpdateFailures.tsx +319 -0
- package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
- package/src/components/features/releases/ReleaseEditorSheet.tsx +30 -7
- package/src/components/ui/card.tsx +1 -1
- package/src/components/ui/empty.tsx +101 -0
- 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 +31 -0
- 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 +37 -1
- package/src/lib/insights-failures-rpc.ts +21 -0
- package/src/lib/insights-failures.ts +129 -0
- package/src/lib/insights-recovery-rpc.ts +51 -0
- package/src/lib/insights-recovery.ts +68 -0
- package/src/lib/insights-retention.ts +21 -0
- package/src/lib/insights-rpc.ts +30 -12
- package/src/lib/insights-search.ts +58 -0
- package/src/lib/insights-usage-rpc.ts +29 -0
- package/src/lib/insights-usage.ts +74 -0
- package/src/lib/insights-view.ts +38 -2
- 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 +37 -0
- 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/getBundleChildren.ts +31 -122
- package/src/lib/server/insightsRecovery.ts +55 -0
- package/src/lib/server/insightsUsage.ts +63 -0
- package/src/lib/server/listReleases.ts +109 -44
- package/src/lib/server/releaseReachability.ts +8 -7
- package/src/lib/server/runtime.server.ts +120 -31
- package/src/lib/server/updateFailures.ts +31 -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 +167 -104
- package/src/routes/insights.tsx +100 -59
- package/src/routes/insights_.distribution.tsx +398 -0
- package/src/routes/installations.tsx +98 -41
- package/src/vite-internal.ts +15 -3
- package/src/vite.ts +2 -0
- package/.output/public/assets/HashValueDisplay-CB0Eskit.js +0 -1
- package/.output/public/assets/InsightsPageHeader-Car5LNiZ.js +0 -1
- package/.output/public/assets/api-keys-BzKocqRy.js +0 -1
- package/.output/public/assets/api-keys-api-BHcODB7q.js +0 -1
- package/.output/public/assets/card-Dhr_eFYj.js +0 -1
- package/.output/public/assets/chart-no-axes-combined-Dy4WJv2U.js +0 -1
- package/.output/public/assets/composite-Bp98A0Ld.js +0 -1
- package/.output/public/assets/createServerFn-CA8fs7QD.js +0 -3
- package/.output/public/assets/dialog-CUTrcPlB.js +0 -1
- package/.output/public/assets/dist-C_NydL0e.js +0 -1
- package/.output/public/assets/index-DdAz-CXv.js +0 -10
- package/.output/public/assets/input-group-cSOG4wh_.js +0 -1
- package/.output/public/assets/insights-C0PBSZ-n.js +0 -1
- package/.output/public/assets/installations-CE33wgbY.js +0 -2
- package/.output/public/assets/installations-CStZja8x.js +0 -1
- package/.output/public/assets/lazyRouteComponent-Ccp3bwBY.js +0 -1
- package/.output/public/assets/link-CI8idAUi.js +0 -1
- package/.output/public/assets/loader-circle-BoxLrsOu.js +0 -1
- package/.output/public/assets/preload-helper-CQ36nxok.js +0 -1
- package/.output/public/assets/refresh-cw-Bqc72UZG.js +0 -1
- package/.output/public/assets/routes-DGVXdzpw.js +0 -2
- package/.output/public/assets/routes-DTlwOr8I.js +0 -1
- package/.output/public/assets/shield-check-uK3MHWJZ.js +0 -1
- package/.output/public/assets/signing-CMf4aqDO.js +0 -1
- package/.output/public/assets/styles-BCvbtKsm.css +0 -2
- package/.output/public/assets/table-2vcV89QB.js +0 -1
- package/.output/public/assets/useCompositeListItem-CHLITE_4.js +0 -1
- package/.output/public/assets/x-DCd0j8c6.js +0 -1
- package/.output/server/_ssr/InsightsPageHeader-Df4Brj8u.mjs +0 -484
- package/.output/server/_ssr/api-keys-api-BUPQVSpk.mjs +0 -60
- package/.output/server/_ssr/api-keys-rpc-Cs7tS5_j.mjs +0 -136
- package/.output/server/_ssr/api-rpc-tAupfGQm.mjs +0 -1399
- package/.output/server/_ssr/auth.server-DW71WzsD.mjs +0 -47
- package/.output/server/_ssr/bundle-signing-rpc-BhnBw0-d.mjs +0 -16
- package/.output/server/_ssr/bundle-signing.server-C4NW5mWx.mjs +0 -12
- package/.output/server/_ssr/card-BRiPmi0C.mjs +0 -108
- package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
- package/.output/server/_ssr/config.server-afDuAwaG.mjs +0 -368
- package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
- package/.output/server/_ssr/deleteBundle-BTGwBgzx.mjs +0 -66
- package/.output/server/_ssr/getBundleChildren-C0vh4ZvE.mjs +0 -50
- package/.output/server/_ssr/insights-BdS364Xu.mjs +0 -460
- package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +0 -303
- package/.output/server/_ssr/installations-CtA59MQG.mjs +0 -29
- package/.output/server/_ssr/routes-CKrrS2q2.mjs +0 -37
- package/.output/server/_ssr/runtime.server-DacU1TBV.mjs +0 -438
- package/.output/server/_ssr/server-D_C2rMzf.mjs +0 -7468
- package/.output/server/_ssr/signing-Dz1WkZOf.mjs +0 -133
- package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
- package/.output/server/_tanstack-start-manifest_v-2MjTyxil.mjs +0 -97
- package/src/components/features/insights/InsightsBundleReports.tsx +0 -51
- 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/routes/signing.tsx +0 -37
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { ChevronDown, RotateCw } from "lucide-react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
import {
|
|
6
|
+
Card,
|
|
7
|
+
CardContent,
|
|
8
|
+
CardDescription,
|
|
9
|
+
CardFooter,
|
|
10
|
+
CardHeader,
|
|
11
|
+
CardTitle,
|
|
12
|
+
} from "@/components/ui/card";
|
|
13
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
14
|
+
import {
|
|
15
|
+
Table,
|
|
16
|
+
TableBody,
|
|
17
|
+
TableCell,
|
|
18
|
+
TableHead,
|
|
19
|
+
TableHeader,
|
|
20
|
+
TableRow,
|
|
21
|
+
} from "@/components/ui/table";
|
|
22
|
+
import {
|
|
23
|
+
checkFailureRate,
|
|
24
|
+
failureDetailParts,
|
|
25
|
+
failureRate,
|
|
26
|
+
failureReasonLabel,
|
|
27
|
+
failureStageLabel,
|
|
28
|
+
formatRate,
|
|
29
|
+
type InsightsFailureBreakdown,
|
|
30
|
+
patchFallbackRate,
|
|
31
|
+
type UpdateFailuresReport,
|
|
32
|
+
} from "@/lib/insights-failures";
|
|
33
|
+
|
|
34
|
+
import { EstimatedCount } from "./EstimatedCount";
|
|
35
|
+
import { InsightsErrorAlert } from "./InsightsErrorAlert";
|
|
36
|
+
import { InsightsInfo } from "./InsightsInfo";
|
|
37
|
+
|
|
38
|
+
const share = (events: number, total: number) =>
|
|
39
|
+
total === 0 ? "—" : `${((events / total) * 100).toFixed(1)}%`;
|
|
40
|
+
|
|
41
|
+
function Metric({
|
|
42
|
+
label,
|
|
43
|
+
info,
|
|
44
|
+
children,
|
|
45
|
+
}: {
|
|
46
|
+
readonly label: string;
|
|
47
|
+
readonly info?: ReactNode;
|
|
48
|
+
readonly children: ReactNode;
|
|
49
|
+
}) {
|
|
50
|
+
return (
|
|
51
|
+
<div className="flex flex-col gap-1">
|
|
52
|
+
<dt className="flex items-center gap-1 text-sm text-muted-foreground">
|
|
53
|
+
{label}
|
|
54
|
+
{info ? (
|
|
55
|
+
<InsightsInfo label={`About ${label.toLowerCase()}`}>
|
|
56
|
+
{info}
|
|
57
|
+
</InsightsInfo>
|
|
58
|
+
) : null}
|
|
59
|
+
</dt>
|
|
60
|
+
<dd className="text-3xl font-semibold tracking-tight tabular-nums">
|
|
61
|
+
{children}
|
|
62
|
+
</dd>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** One stage and reason, and what else its clients knew, most first. */
|
|
68
|
+
function FailureRow({
|
|
69
|
+
failure,
|
|
70
|
+
total,
|
|
71
|
+
}: {
|
|
72
|
+
readonly failure: InsightsFailureBreakdown;
|
|
73
|
+
readonly total: number;
|
|
74
|
+
}) {
|
|
75
|
+
const label = `${failureStageLabel(failure.stage)} · ${failureReasonLabel(failure.reason)}`;
|
|
76
|
+
return (
|
|
77
|
+
<TableRow className="[&>td]:px-4 [&>td]:py-3 [&>td]:align-top sm:[&>td]:px-6">
|
|
78
|
+
<TableCell className="whitespace-normal">
|
|
79
|
+
<details className="group/failure">
|
|
80
|
+
<summary className="flex cursor-pointer list-none items-center gap-1 rounded-md outline-none focus-visible:ring-2 focus-visible:ring-ring/30 [&::-webkit-details-marker]:hidden">
|
|
81
|
+
<span className="font-medium">{label}</span>
|
|
82
|
+
<ChevronDown
|
|
83
|
+
aria-hidden="true"
|
|
84
|
+
className="size-3 text-muted-foreground group-open/failure:rotate-180"
|
|
85
|
+
/>
|
|
86
|
+
</summary>
|
|
87
|
+
<ul
|
|
88
|
+
aria-label={`${label} details`}
|
|
89
|
+
className="mt-2 flex flex-col gap-1 text-xs text-muted-foreground"
|
|
90
|
+
>
|
|
91
|
+
{failure.details.map((detail) => {
|
|
92
|
+
const parts = failureDetailParts(detail);
|
|
93
|
+
return (
|
|
94
|
+
<li
|
|
95
|
+
key={JSON.stringify(detail)}
|
|
96
|
+
className="flex justify-between gap-4"
|
|
97
|
+
>
|
|
98
|
+
<span className="wrap-anywhere">
|
|
99
|
+
{parts.length === 0
|
|
100
|
+
? "No details reported"
|
|
101
|
+
: parts.join(" · ")}
|
|
102
|
+
</span>
|
|
103
|
+
<span className="tabular-nums">
|
|
104
|
+
{detail.events.toLocaleString()}
|
|
105
|
+
</span>
|
|
106
|
+
</li>
|
|
107
|
+
);
|
|
108
|
+
})}
|
|
109
|
+
</ul>
|
|
110
|
+
</details>
|
|
111
|
+
</TableCell>
|
|
112
|
+
<TableCell className="text-right tabular-nums">
|
|
113
|
+
{failure.events.toLocaleString()}
|
|
114
|
+
</TableCell>
|
|
115
|
+
<TableCell className="text-right tabular-nums text-muted-foreground">
|
|
116
|
+
{share(failure.events, total)}
|
|
117
|
+
</TableCell>
|
|
118
|
+
</TableRow>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function Recoveries({
|
|
123
|
+
recoveries,
|
|
124
|
+
}: {
|
|
125
|
+
readonly recoveries: NonNullable<UpdateFailuresReport["recoveries"]>;
|
|
126
|
+
}) {
|
|
127
|
+
const known = recoveries.byExitReason.reduce(
|
|
128
|
+
(sum, { events }) => sum + events,
|
|
129
|
+
0,
|
|
130
|
+
);
|
|
131
|
+
const unreported = Math.max(0, recoveries.failedLaunches - known);
|
|
132
|
+
const reasons = [
|
|
133
|
+
...recoveries.byExitReason.map(({ exitReason, events }) => ({
|
|
134
|
+
name: exitReason,
|
|
135
|
+
code: true,
|
|
136
|
+
events,
|
|
137
|
+
})),
|
|
138
|
+
...(unreported > 0
|
|
139
|
+
? [{ name: "Not reported", code: false, events: unreported }]
|
|
140
|
+
: []),
|
|
141
|
+
];
|
|
142
|
+
return (
|
|
143
|
+
<section
|
|
144
|
+
aria-labelledby="recoveries-by-exit-reason"
|
|
145
|
+
className="flex flex-col gap-2"
|
|
146
|
+
>
|
|
147
|
+
<h3
|
|
148
|
+
className="flex items-center gap-1 text-sm font-medium"
|
|
149
|
+
id="recoveries-by-exit-reason"
|
|
150
|
+
>
|
|
151
|
+
Recoveries by exit reason
|
|
152
|
+
<InsightsInfo label="About exit reasons">
|
|
153
|
+
Why the crashed process exited, as Android 11 and later report it: a
|
|
154
|
+
crash, an ANR, or the system or the user closing the app. iOS and
|
|
155
|
+
older Android report none.
|
|
156
|
+
</InsightsInfo>
|
|
157
|
+
</h3>
|
|
158
|
+
{reasons.length === 0 ? (
|
|
159
|
+
<p className="text-sm text-muted-foreground">
|
|
160
|
+
No recoveries in this period.
|
|
161
|
+
</p>
|
|
162
|
+
) : (
|
|
163
|
+
<ul className="flex flex-col gap-1 text-sm">
|
|
164
|
+
{reasons.map(({ name, code, events }) => (
|
|
165
|
+
<li key={name} className="flex justify-between gap-4">
|
|
166
|
+
<span className={code ? "font-mono text-xs" : undefined}>
|
|
167
|
+
{name}
|
|
168
|
+
</span>
|
|
169
|
+
<span className="tabular-nums">
|
|
170
|
+
{events.toLocaleString()}
|
|
171
|
+
<span className="ml-2 text-muted-foreground">
|
|
172
|
+
{share(events, recoveries.failedLaunches)}
|
|
173
|
+
</span>
|
|
174
|
+
</span>
|
|
175
|
+
</li>
|
|
176
|
+
))}
|
|
177
|
+
</ul>
|
|
178
|
+
)}
|
|
179
|
+
</section>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function FailuresReport({ report }: { readonly report: UpdateFailuresReport }) {
|
|
184
|
+
const breakdown = report.breakdown ?? [];
|
|
185
|
+
const total = breakdown.reduce((sum, { events }) => sum + events, 0);
|
|
186
|
+
return (
|
|
187
|
+
<div className="flex flex-col gap-8">
|
|
188
|
+
<dl className="grid grid-cols-2 gap-5 lg:grid-cols-4">
|
|
189
|
+
<Metric label="Failed updates">
|
|
190
|
+
{report.failedUpdates.toLocaleString()}
|
|
191
|
+
</Metric>
|
|
192
|
+
<Metric
|
|
193
|
+
label="Attempt failure rate"
|
|
194
|
+
info="The failure rate of update attempts: failed download and install reports divided by those plus download reports."
|
|
195
|
+
>
|
|
196
|
+
{formatRate(failureRate(report))}
|
|
197
|
+
</Metric>
|
|
198
|
+
<Metric
|
|
199
|
+
label="Failed installations"
|
|
200
|
+
info="Distinct installations with a failed download or install, estimated: typically within about 3%. A client reports each failure at most once a UTC day."
|
|
201
|
+
>
|
|
202
|
+
<EstimatedCount value={report.failedInstallations} />
|
|
203
|
+
</Metric>
|
|
204
|
+
<Metric
|
|
205
|
+
label="Patch fallback rate"
|
|
206
|
+
info="Of the downloads that tried a patch, the share that fell back to the changed files or the full archive."
|
|
207
|
+
>
|
|
208
|
+
{formatRate(patchFallbackRate(report))}
|
|
209
|
+
</Metric>
|
|
210
|
+
{report.checks ? (
|
|
211
|
+
<>
|
|
212
|
+
<Metric label="Failed checks">
|
|
213
|
+
{report.checks.failures.toLocaleString()}
|
|
214
|
+
</Metric>
|
|
215
|
+
<Metric
|
|
216
|
+
label="Check failure rate"
|
|
217
|
+
info="Installations whose update check failed, divided by the channel's active installations; both estimated. A check the device could not send while offline is not reported."
|
|
218
|
+
>
|
|
219
|
+
{formatRate(checkFailureRate(report.checks))}
|
|
220
|
+
</Metric>
|
|
221
|
+
</>
|
|
222
|
+
) : null}
|
|
223
|
+
</dl>
|
|
224
|
+
<section
|
|
225
|
+
aria-labelledby="failures-by-stage"
|
|
226
|
+
className="flex flex-col gap-2"
|
|
227
|
+
>
|
|
228
|
+
<h3 className="text-sm font-medium" id="failures-by-stage">
|
|
229
|
+
Failures by stage and reason
|
|
230
|
+
</h3>
|
|
231
|
+
{breakdown.length === 0 ? (
|
|
232
|
+
<p className="text-sm text-muted-foreground">
|
|
233
|
+
No update failures in this period.
|
|
234
|
+
</p>
|
|
235
|
+
) : (
|
|
236
|
+
<Table>
|
|
237
|
+
<TableHeader>
|
|
238
|
+
<TableRow className="[&>th]:px-4 sm:[&>th]:px-6">
|
|
239
|
+
<TableHead>Stage · reason</TableHead>
|
|
240
|
+
<TableHead className="w-24 text-right">Reports</TableHead>
|
|
241
|
+
<TableHead className="w-24 text-right">Share</TableHead>
|
|
242
|
+
</TableRow>
|
|
243
|
+
</TableHeader>
|
|
244
|
+
<TableBody>
|
|
245
|
+
{breakdown.map((failure) => (
|
|
246
|
+
<FailureRow
|
|
247
|
+
key={`${failure.stage} ${failure.reason}`}
|
|
248
|
+
failure={failure}
|
|
249
|
+
total={total}
|
|
250
|
+
/>
|
|
251
|
+
))}
|
|
252
|
+
</TableBody>
|
|
253
|
+
</Table>
|
|
254
|
+
)}
|
|
255
|
+
</section>
|
|
256
|
+
{report.recoveries ? <Recoveries recoveries={report.recoveries} /> : null}
|
|
257
|
+
</div>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Update failures for Release health's scope and period: counts, rates, the
|
|
263
|
+
* stage and reason breakdown with what else the clients knew, and why
|
|
264
|
+
* crashed processes exited.
|
|
265
|
+
*/
|
|
266
|
+
export function UpdateFailures({
|
|
267
|
+
query,
|
|
268
|
+
onRefresh,
|
|
269
|
+
}: {
|
|
270
|
+
readonly query: {
|
|
271
|
+
readonly data: UpdateFailuresReport | undefined;
|
|
272
|
+
readonly error: Error | null;
|
|
273
|
+
readonly isPending: boolean;
|
|
274
|
+
readonly isFetching: boolean;
|
|
275
|
+
};
|
|
276
|
+
readonly onRefresh: () => void;
|
|
277
|
+
}) {
|
|
278
|
+
return (
|
|
279
|
+
<section aria-label="Update failures">
|
|
280
|
+
<Card className="min-w-0 overflow-hidden shadow-sm">
|
|
281
|
+
<CardHeader className="flex flex-col gap-1.5 p-6">
|
|
282
|
+
<div className="flex flex-wrap items-center justify-between gap-4">
|
|
283
|
+
<CardTitle>Update failures</CardTitle>
|
|
284
|
+
<Button
|
|
285
|
+
aria-label="Refresh update failures"
|
|
286
|
+
variant="ghost"
|
|
287
|
+
size="icon-lg"
|
|
288
|
+
disabled={query.isFetching}
|
|
289
|
+
onClick={onRefresh}
|
|
290
|
+
>
|
|
291
|
+
<RotateCw aria-hidden="true" />
|
|
292
|
+
</Button>
|
|
293
|
+
</div>
|
|
294
|
+
<CardDescription>
|
|
295
|
+
Checks, downloads, and installs that failed, for Release health's
|
|
296
|
+
scope and period.
|
|
297
|
+
</CardDescription>
|
|
298
|
+
</CardHeader>
|
|
299
|
+
<CardContent className="px-6 pb-6">
|
|
300
|
+
{query.isPending ? (
|
|
301
|
+
<Skeleton aria-label="Loading update failures" className="h-64" />
|
|
302
|
+
) : query.error ? (
|
|
303
|
+
<InsightsErrorAlert
|
|
304
|
+
error={query.error}
|
|
305
|
+
fallbackTitle="Update failures unavailable"
|
|
306
|
+
/>
|
|
307
|
+
) : query.data ? (
|
|
308
|
+
<FailuresReport report={query.data} />
|
|
309
|
+
) : null}
|
|
310
|
+
</CardContent>
|
|
311
|
+
{query.data?.coverage.kind === "partial" ? (
|
|
312
|
+
<CardFooter className="border-t px-6 py-5">
|
|
313
|
+
<span className="text-xs text-muted-foreground">Partial</span>
|
|
314
|
+
</CardFooter>
|
|
315
|
+
) : null}
|
|
316
|
+
</Card>
|
|
317
|
+
</section>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Link } from "@tanstack/react-router";
|
|
2
|
+
import { ChartNoAxesCombined } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import type { ConsoleFeatureUiRegistry } from "../FeatureSlots";
|
|
5
|
+
import {
|
|
6
|
+
BundleInsightsSummary,
|
|
7
|
+
ReleaseFailuresSection,
|
|
8
|
+
releaseActivityColumn,
|
|
9
|
+
} from "./ReleaseActivity";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* What the Insights features add: their navigation item, release activity on
|
|
13
|
+
* the Bundles page and in the release editor, and a release's download
|
|
14
|
+
* failures in the release editor, which a self-hosted server serves too.
|
|
15
|
+
*/
|
|
16
|
+
export const insightsFeatureUi = {
|
|
17
|
+
insights: {
|
|
18
|
+
navigation: {
|
|
19
|
+
icon: <ChartNoAxesCombined />,
|
|
20
|
+
paths: ["/insights", "/insights/distribution", "/installations"],
|
|
21
|
+
// A self-hosted server's admin API serves events, not the overview.
|
|
22
|
+
link: ({ features, onClick }) =>
|
|
23
|
+
features.insightsAnalytics ? (
|
|
24
|
+
<Link to="/insights" onClick={onClick} />
|
|
25
|
+
) : (
|
|
26
|
+
<Link to="/installations" onClick={onClick} />
|
|
27
|
+
),
|
|
28
|
+
},
|
|
29
|
+
releaseSection: ({ release }) => <ReleaseFailuresSection input={release} />,
|
|
30
|
+
},
|
|
31
|
+
insightsAnalytics: {
|
|
32
|
+
releaseColumn: releaseActivityColumn,
|
|
33
|
+
releaseSection: ({ release }) => <BundleInsightsSummary input={release} />,
|
|
34
|
+
},
|
|
35
|
+
} satisfies ConsoleFeatureUiRegistry;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getPatchBaseBundleId,
|
|
3
|
-
INVALID_COHORT_ERROR_MESSAGE,
|
|
4
|
-
isValidCohort,
|
|
5
|
-
normalizeCohortValue,
|
|
6
|
-
} from "@hot-updater/core";
|
|
7
1
|
import type {
|
|
8
2
|
ChannelRow,
|
|
9
3
|
ReleasePolicyPatch,
|
|
10
4
|
ReleaseRow,
|
|
11
5
|
} from "@hot-updater/plugin-core";
|
|
6
|
+
import {
|
|
7
|
+
getPatchBaseBundleId,
|
|
8
|
+
INVALID_COHORT_ERROR_MESSAGE,
|
|
9
|
+
isValidCohort,
|
|
10
|
+
normalizeCohortValue,
|
|
11
|
+
} from "@hot-updater/protocol";
|
|
12
12
|
import { AlertTriangle, Download, Plus, X } from "lucide-react";
|
|
13
13
|
import { useEffect, useState } from "react";
|
|
14
14
|
import { toast } from "sonner";
|
|
@@ -17,6 +17,7 @@ import { normalizeRange } from "verkit";
|
|
|
17
17
|
import { BundleIdDisplay } from "@/components/BundleIdDisplay";
|
|
18
18
|
import { BundleMetadata } from "@/components/features/bundles/BundleMetadata";
|
|
19
19
|
import { RolloutCohortsDialog } from "@/components/features/bundles/RolloutCohortsDialog";
|
|
20
|
+
import { ReleaseSections } from "@/components/features/FeatureSlots";
|
|
20
21
|
import { PlatformIcon } from "@/components/PlatformIcon";
|
|
21
22
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
|
22
23
|
import {
|
|
@@ -203,11 +204,14 @@ function RolloutPercentageInput({
|
|
|
203
204
|
export function ReleaseEditorSheet({
|
|
204
205
|
channels,
|
|
205
206
|
onOpenChange,
|
|
207
|
+
onShowScope,
|
|
206
208
|
open,
|
|
207
209
|
releaseId,
|
|
208
210
|
}: {
|
|
209
211
|
channels: readonly ChannelRow[];
|
|
210
212
|
onOpenChange: (open: boolean) => void;
|
|
213
|
+
/** Lists the bundles of this release's catalog scope: its channel, platform, and target kind. */
|
|
214
|
+
onShowScope?: (scopeKey: string) => void;
|
|
211
215
|
open: boolean;
|
|
212
216
|
releaseId: string;
|
|
213
217
|
}) {
|
|
@@ -408,6 +412,15 @@ export function ReleaseEditorSheet({
|
|
|
408
412
|
|
|
409
413
|
{release && draft ? (
|
|
410
414
|
<div className="flex flex-col gap-6 px-4 pb-4 sm:px-6 sm:pb-6">
|
|
415
|
+
{channelName ? (
|
|
416
|
+
<ReleaseSections
|
|
417
|
+
release={{
|
|
418
|
+
platform: release.platform,
|
|
419
|
+
channel: channelName,
|
|
420
|
+
releaseId: release.id,
|
|
421
|
+
}}
|
|
422
|
+
/>
|
|
423
|
+
) : null}
|
|
411
424
|
<section
|
|
412
425
|
aria-labelledby="delivery-settings-heading"
|
|
413
426
|
className="flex flex-col gap-4"
|
|
@@ -614,7 +627,7 @@ export function ReleaseEditorSheet({
|
|
|
614
627
|
variant="outline"
|
|
615
628
|
>
|
|
616
629
|
<Download data-icon="inline-start" />
|
|
617
|
-
Download
|
|
630
|
+
Download manifest
|
|
618
631
|
</Button>
|
|
619
632
|
) : null}
|
|
620
633
|
<Button
|
|
@@ -688,6 +701,16 @@ export function ReleaseEditorSheet({
|
|
|
688
701
|
<dd className="mt-1 break-all font-mono text-foreground">
|
|
689
702
|
{release.scope_key}
|
|
690
703
|
</dd>
|
|
704
|
+
{onShowScope ? (
|
|
705
|
+
<Button
|
|
706
|
+
className="mt-1 h-auto px-0 text-xs"
|
|
707
|
+
onClick={() => onShowScope(release.scope_key)}
|
|
708
|
+
size="sm"
|
|
709
|
+
variant="link"
|
|
710
|
+
>
|
|
711
|
+
Show bundles for this target
|
|
712
|
+
</Button>
|
|
713
|
+
) : null}
|
|
691
714
|
</div>
|
|
692
715
|
</dl>
|
|
693
716
|
{diagnostics.isError ? (
|
|
@@ -19,7 +19,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
19
19
|
return (
|
|
20
20
|
<div
|
|
21
21
|
data-slot="card-header"
|
|
22
|
-
className={cn("flex flex-col
|
|
22
|
+
className={cn("flex flex-col gap-1.5 p-6", className)}
|
|
23
23
|
{...props}
|
|
24
24
|
/>
|
|
25
25
|
);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
function Empty({ className, ...props }: React.ComponentProps<"div">) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
data-slot="empty"
|
|
9
|
+
className={cn(
|
|
10
|
+
"flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-xl border-dashed p-6 text-center text-balance",
|
|
11
|
+
className,
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
data-slot="empty-header"
|
|
22
|
+
className={cn("flex max-w-sm flex-col items-center gap-1", className)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const emptyMediaVariants = cva(
|
|
29
|
+
"mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
30
|
+
{
|
|
31
|
+
variants: {
|
|
32
|
+
variant: {
|
|
33
|
+
default: "bg-transparent",
|
|
34
|
+
icon: "flex size-8 shrink-0 items-center justify-center rounded-md bg-muted text-foreground [&_svg:not([class*='size-'])]:size-4",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
defaultVariants: {
|
|
38
|
+
variant: "default",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
function EmptyMedia({
|
|
44
|
+
className,
|
|
45
|
+
variant = "default",
|
|
46
|
+
...props
|
|
47
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
data-slot="empty-icon"
|
|
51
|
+
data-variant={variant}
|
|
52
|
+
className={cn(emptyMediaVariants({ variant, className }))}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
data-slot="empty-title"
|
|
62
|
+
className={cn("text-sm font-medium tracking-tight", className)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
data-slot="empty-description"
|
|
72
|
+
className={cn(
|
|
73
|
+
"text-xs/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
74
|
+
className,
|
|
75
|
+
)}
|
|
76
|
+
{...props}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
82
|
+
return (
|
|
83
|
+
<div
|
|
84
|
+
data-slot="empty-content"
|
|
85
|
+
className={cn(
|
|
86
|
+
"flex w-full max-w-sm min-w-0 flex-col items-center gap-2 text-xs/relaxed text-balance",
|
|
87
|
+
className,
|
|
88
|
+
)}
|
|
89
|
+
{...props}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
Empty,
|
|
96
|
+
EmptyHeader,
|
|
97
|
+
EmptyTitle,
|
|
98
|
+
EmptyDescription,
|
|
99
|
+
EmptyContent,
|
|
100
|
+
EmptyMedia,
|
|
101
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
function ScrollArea({
|
|
6
|
+
className,
|
|
7
|
+
children,
|
|
8
|
+
...props
|
|
9
|
+
}: ScrollAreaPrimitive.Root.Props) {
|
|
10
|
+
return (
|
|
11
|
+
<ScrollAreaPrimitive.Root
|
|
12
|
+
data-slot="scroll-area"
|
|
13
|
+
className={cn("relative", className)}
|
|
14
|
+
{...props}
|
|
15
|
+
>
|
|
16
|
+
<ScrollAreaPrimitive.Viewport
|
|
17
|
+
data-slot="scroll-area-viewport"
|
|
18
|
+
className="size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1"
|
|
19
|
+
>
|
|
20
|
+
{children}
|
|
21
|
+
</ScrollAreaPrimitive.Viewport>
|
|
22
|
+
<ScrollBar />
|
|
23
|
+
<ScrollAreaPrimitive.Corner />
|
|
24
|
+
</ScrollAreaPrimitive.Root>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function ScrollBar({
|
|
29
|
+
className,
|
|
30
|
+
orientation = "vertical",
|
|
31
|
+
...props
|
|
32
|
+
}: ScrollAreaPrimitive.Scrollbar.Props) {
|
|
33
|
+
return (
|
|
34
|
+
<ScrollAreaPrimitive.Scrollbar
|
|
35
|
+
data-slot="scroll-area-scrollbar"
|
|
36
|
+
data-orientation={orientation}
|
|
37
|
+
orientation={orientation}
|
|
38
|
+
className={cn(
|
|
39
|
+
"flex touch-none p-px transition-colors select-none data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent",
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
<ScrollAreaPrimitive.Thumb
|
|
45
|
+
data-slot="scroll-area-thumb"
|
|
46
|
+
className="relative flex-1 rounded-full bg-border"
|
|
47
|
+
/>
|
|
48
|
+
</ScrollAreaPrimitive.Scrollbar>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { ScrollArea, ScrollBar };
|
|
@@ -9,6 +9,13 @@ import * as React from "react";
|
|
|
9
9
|
import { Button } from "@/components/ui/button";
|
|
10
10
|
import { Input } from "@/components/ui/input";
|
|
11
11
|
import { Separator } from "@/components/ui/separator";
|
|
12
|
+
import {
|
|
13
|
+
Sheet,
|
|
14
|
+
SheetContent,
|
|
15
|
+
SheetDescription,
|
|
16
|
+
SheetHeader,
|
|
17
|
+
SheetTitle,
|
|
18
|
+
} from "@/components/ui/sheet";
|
|
12
19
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
13
20
|
import {
|
|
14
21
|
Tooltip,
|
|
@@ -220,44 +227,30 @@ function Sidebar({
|
|
|
220
227
|
}
|
|
221
228
|
|
|
222
229
|
if (isMobile) {
|
|
223
|
-
if (!openMobile) {
|
|
224
|
-
return null;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
230
|
return (
|
|
228
|
-
<
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
>
|
|
235
|
-
<button
|
|
236
|
-
type="button"
|
|
237
|
-
aria-label="Close sidebar"
|
|
238
|
-
className="absolute inset-0 bg-sidebar/55 supports-backdrop-filter:backdrop-blur-sm"
|
|
239
|
-
onClick={() => setOpenMobile(false)}
|
|
240
|
-
/>
|
|
241
|
-
<dialog
|
|
242
|
-
open
|
|
243
|
-
aria-modal="true"
|
|
244
|
-
aria-label="Sidebar"
|
|
245
|
-
className={cn(
|
|
246
|
-
"bg-sidebar text-sidebar-foreground absolute inset-y-0 z-10 flex w-(--sidebar-width) flex-col shadow-lg",
|
|
247
|
-
side === "left" ? "left-0" : "right-0",
|
|
248
|
-
)}
|
|
231
|
+
<Sheet open={openMobile} onOpenChange={setOpenMobile}>
|
|
232
|
+
<SheetContent
|
|
233
|
+
data-sidebar="sidebar"
|
|
234
|
+
data-slot="sidebar"
|
|
235
|
+
data-mobile="true"
|
|
236
|
+
className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) max-w-[calc(100vw-2rem)] p-0"
|
|
249
237
|
style={
|
|
250
238
|
{
|
|
251
239
|
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
|
|
252
240
|
} as React.CSSProperties
|
|
253
241
|
}
|
|
242
|
+
side={side}
|
|
243
|
+
{...props}
|
|
254
244
|
>
|
|
255
|
-
<
|
|
256
|
-
|
|
245
|
+
<SheetHeader className="sr-only">
|
|
246
|
+
<SheetTitle>Sidebar</SheetTitle>
|
|
247
|
+
<SheetDescription>Console navigation.</SheetDescription>
|
|
248
|
+
</SheetHeader>
|
|
249
|
+
<div className="flex size-full min-h-0 flex-col pt-[env(safe-area-inset-top)] pb-[env(safe-area-inset-bottom)]">
|
|
257
250
|
{children}
|
|
258
251
|
</div>
|
|
259
|
-
</
|
|
260
|
-
</
|
|
252
|
+
</SheetContent>
|
|
253
|
+
</Sheet>
|
|
261
254
|
);
|
|
262
255
|
}
|
|
263
256
|
|