@hot-updater/console 1.0.0-rc.2 → 1.0.0-rc.20
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-B9VYGA3N.js +1 -0
- package/.output/public/assets/HashValueDisplay-D880zntR.js +1 -0
- package/.output/public/assets/InsightsInfo-By_sPjTE.js +1 -0
- package/.output/public/assets/InsightsPageHeader-9_kx_LL7.js +1 -0
- package/.output/public/assets/NotFoundPage-Dys8C0cW.js +1 -0
- package/.output/public/assets/api-CtarlV0l.js +1 -0
- package/.output/public/assets/api-keys-DFF_QqtN.js +1 -0
- package/.output/public/assets/api-keys-evfsQ4sN.js +1 -0
- package/.output/public/assets/arrow-left-BP9D-ta_.js +1 -0
- package/.output/public/assets/arrow-up-right-CDP9Tevw.js +1 -0
- package/.output/public/assets/card-DzCPSRsh.js +1 -0
- package/.output/public/assets/console-features-api-yv7rm3DX.js +1 -0
- package/.output/public/assets/dialog-BDvCy_QO.js +1 -0
- package/.output/public/assets/dist-Clb6cQ9l.js +1 -0
- package/.output/public/assets/empty-C5x9mn_s.js +1 -0
- package/.output/public/assets/field-Bou7shoO.js +1 -0
- package/.output/public/assets/index-0eg3l8aC.js +10 -0
- package/.output/public/assets/input-group-DVtWFUUF.js +1 -0
- package/.output/public/assets/insights-2MbdiSNv.js +1 -0
- package/.output/public/assets/insights-C6tmCZHk.js +2 -0
- package/.output/public/assets/insights-CadS_aIA.js +42 -0
- package/.output/public/assets/insights-recovery-rpc-wvdyM4LD.js +1 -0
- package/.output/public/assets/insights-search-KIF7mRtB.js +1 -0
- package/.output/public/assets/insights-usage-rpc-qUkNtrWr.js +1 -0
- package/.output/public/assets/insights_.distribution-BhBs6PNA.js +1 -0
- package/.output/public/assets/insights_.distribution-CItZw3-8.js +1 -0
- package/.output/public/assets/insights_.distribution-CJUH_wAS.js +2 -0
- package/.output/public/assets/installations-CH2H7JQt.js +2 -0
- package/.output/public/assets/installations-DMeMuwsW.js +1 -0
- package/.output/public/assets/installations-DnvxI9Dt.js +1 -0
- package/.output/public/assets/loader-circle-Ca7C9YY1.js +1 -0
- package/.output/public/assets/mutation-x9L28_CD.js +1 -0
- package/.output/public/assets/preload-helper-BN-UkjOE.js +1 -0
- package/.output/public/assets/refresh-cw-CVGqvy8Z.js +1 -0
- package/.output/public/assets/routes-BhK8DuZA.js +1 -0
- package/.output/public/assets/routes-veKtDW39.js +2 -0
- package/.output/public/assets/sidebar-B45aMqRm.js +3 -0
- package/.output/public/assets/styles-Dn0pK1EX.css +2 -0
- package/.output/public/assets/table-CwLnN6KX.js +1 -0
- package/.output/public/assets/useCompositeListItem-CF7vsfcA.js +1 -0
- package/.output/public/assets/useMutation-CN1ygYEr.js +1 -0
- package/.output/public/assets/useOpenInteractionType-AnAMWYIY.js +1 -0
- package/.output/public/assets/valueToPercent-C-t8FT0y.js +1 -0
- package/.output/server/{__23tanstack-start-server-fn-resolver-B7ioJhtE.mjs → __23tanstack-start-server-fn-resolver-CUQLz7hs.mjs} +52 -40
- 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 +49 -3
- package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +4 -4
- 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__react-query.mjs +8 -3
- package/.output/server/_libs/verkit.mjs +99 -1
- package/.output/server/_runtime.mjs +10 -1
- package/.output/server/_ssr/ConsoleFeatureUnavailable-DoxET2AT.mjs +58 -0
- package/.output/server/_ssr/{HashValueDisplay-DwpGLfrl.mjs → HashValueDisplay-BplboHEb.mjs} +3 -3
- package/.output/server/_ssr/InsightsInfo-o2YPd5Qc.mjs +37 -0
- package/.output/server/_ssr/InsightsPageHeader-NExCJL1I.mjs +154 -0
- package/.output/server/_ssr/NotFoundPage-Cd-A8ock.mjs +37 -0
- package/.output/server/_ssr/api-B1Ro0uys.mjs +202 -0
- package/.output/server/_ssr/api-C0G9MTMI.mjs +4205 -0
- package/.output/server/_ssr/{api-keys-CZeTPQys.mjs → api-keys-CT3HG7KT.mjs} +49 -9
- package/.output/server/_ssr/api-keys-Dvvy1sem.mjs +5 -0
- package/.output/server/_ssr/api-keys-rpc-b1qTTFfG.mjs +52 -0
- package/.output/server/_ssr/api-rpc-Cbns2MGx.mjs +338 -0
- package/.output/server/_ssr/{auth-rpc-DUaeOWKK.mjs → auth-rpc-DChS7m9y.mjs} +6 -6
- package/.output/server/_ssr/auth.server-Ccqz2iVv.mjs +37 -0
- package/.output/server/_ssr/bundleActivity-D17Pb_h5.mjs +19 -0
- package/.output/server/_ssr/card-DMbcvlPz.mjs +49 -0
- package/.output/server/_ssr/config-DMEJdhoW.mjs +13 -0
- package/.output/server/_ssr/config.server-DyypN6cQ.mjs +60 -0
- package/.output/server/_ssr/console-access-KpPv5lin.mjs +17 -0
- package/.output/server/_ssr/console-features-CsbyHYfK.mjs +55 -0
- package/.output/server/_ssr/console-features-api-B-KVuo4p.mjs +43 -0
- package/.output/server/_ssr/console-features-rpc-Djyo5740.mjs +19 -0
- package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-B_I3Ll4M.mjs} +28 -2
- package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-DuNm5mmo.mjs} +2 -2
- package/.output/server/_ssr/databaseRows-CXxf_VIB.mjs +132 -0
- package/.output/server/_ssr/{deleteBundle-BTGwBgzx.mjs → deleteBundle-Dcc21Lbd.mjs} +9 -19
- package/.output/server/_ssr/{dialog-DS-nw-pA.mjs → dialog-cOmLTUV2.mjs} +2 -2
- package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-B-r4u3Lg.mjs} +28 -5
- package/.output/server/_ssr/{downloadBundle-CNnYxYO2.mjs → downloadBundle-CexOFoZ4.mjs} +6 -7
- package/.output/server/_ssr/empty-BHoY_5jt.mjs +49 -0
- package/.output/server/_ssr/{table-CnZmon_Z.mjs → field-mjhcNEXD.mjs} +63 -51
- package/.output/server/_ssr/getBundleChildren-DbDXe9wB.mjs +22 -0
- package/.output/server/_ssr/{input-group-BpsnmvZw.mjs → input-group-96H30HWC.mjs} +2 -2
- package/.output/server/_ssr/insights-D25VDVaz.mjs +5 -0
- package/.output/server/_ssr/insights-D359GJFL.mjs +14 -0
- package/.output/server/_ssr/insights-DgFv1NsO.mjs +1238 -0
- package/.output/server/_ssr/insights-api-ChAKrbhG.mjs +105 -0
- package/.output/server/_ssr/insights-failures-WQ-ttjOx.mjs +44 -0
- package/.output/server/_ssr/insights-failures-rpc-BFsqlltN.mjs +21 -0
- package/.output/server/_ssr/insights-recovery-DkL6bxMW.mjs +28 -0
- package/.output/server/_ssr/insights-recovery-rpc-2P5m8F62.mjs +46 -0
- package/.output/server/_ssr/insights-recovery-rpc-Tj8vNC19.mjs +20 -0
- package/.output/server/_ssr/{insights-rpc-38DujK0G.mjs → insights-rpc-DrkVmV9o.mjs} +22 -14
- package/.output/server/_ssr/insights-search-BsalnNw-.mjs +25 -0
- package/.output/server/_ssr/insights-usage-1nVTQrLy.mjs +29 -0
- package/.output/server/_ssr/insights-usage-rpc-Ct_4xHhZ.mjs +29 -0
- package/.output/server/_ssr/insights-usage-rpc-DaYIRtFK.mjs +41 -0
- package/.output/server/_ssr/insightsRecovery-CpGucWN1.mjs +45 -0
- package/.output/server/_ssr/insightsUsage-JYpKZAF7.mjs +56 -0
- package/.output/server/_ssr/insights_.distribution-Bcu6uYs_.mjs +5 -0
- package/.output/server/_ssr/insights_.distribution-D3Csuyjw.mjs +14 -0
- package/.output/server/_ssr/insights_.distribution-jQZEF07J.mjs +335 -0
- package/.output/server/_ssr/installations-B8Vh54lH.mjs +69 -0
- package/.output/server/_ssr/installations-BI5kelHv.mjs +5 -0
- package/.output/server/_ssr/{installations-DiVKEF5T.mjs → installations-Cxikllmx.mjs} +517 -54
- package/.output/server/_ssr/listReleases-DqU9t1l3.mjs +79 -0
- package/.output/server/_ssr/releaseManagement-B3e7I8J9.mjs +11 -0
- package/.output/server/_ssr/{router-B9wG_9f8.mjs → router-CRkm-wHD.mjs} +117 -124
- package/.output/server/_ssr/routes-B3IJo_jL.mjs +292 -0
- package/.output/server/_ssr/{routes-D7Yh_jfj.mjs → routes-CNAi0Mmv.mjs} +176 -455
- package/.output/server/_ssr/runtime.server-NucbYjc7.mjs +1372 -0
- package/.output/server/_ssr/select-BkPAJFL7.mjs +107 -0
- package/.output/server/_ssr/{server-D_C2rMzf.mjs → server-DKJYsaCP.mjs} +6 -32
- package/.output/server/_ssr/{createSsrRpc-DjWNJ0uc.mjs → sidebar-Cw1VuC7d.mjs} +117 -63
- package/.output/server/_ssr/ssr.mjs +1 -1
- package/.output/server/_ssr/table-q0P9wz7x.mjs +53 -0
- package/.output/server/_ssr/updateFailures-DMU9f0pK.mjs +24 -0
- package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-BzSO-fr-.mjs} +2 -2
- package/.output/server/_tanstack-start-manifest_v-Bb0gKDbb.mjs +124 -0
- package/.output/server/index.mjs +273 -168
- package/README.md +71 -65
- package/lib/index.d.mts +13 -11
- package/lib/vite-internal.mjs +6 -3
- package/package.json +8 -8
- package/src/components/AppSidebar.tsx +91 -71
- package/src/components/ConsoleAccessPage.tsx +5 -1
- package/src/components/ConsoleFeatureUnavailable.tsx +82 -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 +7 -4
- 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 +24 -1
- 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 +12 -17
- package/src/lib/api-keys-api.ts +0 -28
- package/src/lib/api-keys-rpc.ts +27 -38
- package/src/lib/api-rpc.ts +197 -221
- package/src/lib/api.ts +11 -13
- 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 +16 -32
- package/src/lib/server/console-feature-apis.ts +59 -0
- package/src/lib/server/console-runtime.server.ts +6 -40
- package/src/lib/server/deleteBundle.ts +16 -25
- package/src/lib/server/downloadBundle.ts +7 -10
- 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 +4 -3
- package/src/lib/server/runtime.server.ts +132 -31
- package/src/lib/server/updateFailures.ts +31 -0
- package/src/routeTree.gen.ts +21 -21
- 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 +6 -3
- 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/databaseJsonValue-d6I3nAS4.mjs +0 -45
- package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
- 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/signing-Dz1WkZOf.mjs +0 -133
- 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/routes/signing.tsx +0 -37
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Hot Updater Console
|
|
1
|
+
# Hot Updater Console
|
|
2
2
|
|
|
3
3
|
Modern web-based management console for Hot Updater built with **TanStack Start** and **shadcn/ui**.
|
|
4
4
|
|
|
@@ -6,11 +6,11 @@ Modern web-based management console for Hot Updater built with **TanStack Start*
|
|
|
6
6
|
|
|
7
7
|
- **Bundle Management** - View, filter, and manage OTA update bundles
|
|
8
8
|
- **Real-time Filtering** - Filter by platform (iOS/Android) and channel
|
|
9
|
-
- **
|
|
9
|
+
- **Release Editor** - Edit delivery policy, rollout, and target cohorts
|
|
10
10
|
- **Rollout Control** - Adjust rollout percentage with visual slider
|
|
11
11
|
- **Rollout Statistics** - View deployment metrics and success rates
|
|
12
|
-
- **Channel Promotion** - Copy or move
|
|
13
|
-
- **
|
|
12
|
+
- **Channel Promotion** - Copy or move releases between channels
|
|
13
|
+
- **Rollback** - Disable a release and save; devices reconsider it on their next update check
|
|
14
14
|
- **Dark Mode** - Full dark mode support with system preference detection
|
|
15
15
|
- **Responsive Design** - Works seamlessly on desktop, tablet, and mobile
|
|
16
16
|
|
|
@@ -21,13 +21,11 @@ Modern web-based management console for Hot Updater built with **TanStack Start*
|
|
|
21
21
|
- **TanStack Start** - Full-stack React framework with SSR
|
|
22
22
|
- **TanStack Router** - File-based routing with type safety
|
|
23
23
|
- **TanStack Query** - Data fetching and caching
|
|
24
|
-
- **TanStack Form** - Form state management and validation
|
|
25
|
-
- **TanStack Table** - Powerful table with sorting and pagination
|
|
26
24
|
- **React 19** - Latest React features
|
|
27
25
|
|
|
28
26
|
### UI Components
|
|
29
27
|
|
|
30
|
-
- **shadcn/ui** - High-quality accessible components
|
|
28
|
+
- **shadcn/ui** - High-quality accessible components built on Base UI
|
|
31
29
|
- **Tailwind CSS v4** - Utility-first CSS with oklch color system
|
|
32
30
|
- **Lucide React** - Beautiful icon library
|
|
33
31
|
- **Sonner** - Toast notifications
|
|
@@ -40,27 +38,33 @@ Modern web-based management console for Hot Updater built with **TanStack Start*
|
|
|
40
38
|
## 📦 Installation
|
|
41
39
|
|
|
42
40
|
```bash
|
|
43
|
-
# Install dependencies (from monorepo root)
|
|
41
|
+
# Install and build workspace dependencies (from monorepo root)
|
|
44
42
|
pnpm install
|
|
43
|
+
pnpm -w build
|
|
45
44
|
```
|
|
46
45
|
|
|
47
46
|
## 🏃♂️ Development
|
|
48
47
|
|
|
49
48
|
```bash
|
|
50
|
-
#
|
|
51
|
-
|
|
49
|
+
# Run these commands from the monorepo root
|
|
50
|
+
# Start the local development server
|
|
51
|
+
pnpm --dir packages/console dev
|
|
52
52
|
|
|
53
|
-
# Build
|
|
54
|
-
pnpm build
|
|
55
|
-
|
|
56
|
-
# Preview production build
|
|
57
|
-
pnpm preview
|
|
53
|
+
# Build and preview the local console package
|
|
54
|
+
pnpm --dir packages/console build
|
|
55
|
+
pnpm --dir packages/console preview
|
|
58
56
|
|
|
59
57
|
# Type checking
|
|
60
|
-
pnpm test:type
|
|
58
|
+
pnpm --dir packages/console test:type
|
|
61
59
|
```
|
|
62
60
|
|
|
63
|
-
The
|
|
61
|
+
The development server is available at `http://localhost:3000`.
|
|
62
|
+
|
|
63
|
+
This package's Vite configuration uses the local console adapter, which grants
|
|
64
|
+
management access without sign-in in both development and built output. Keep
|
|
65
|
+
this build and preview local. For production hosting, follow the
|
|
66
|
+
[Console deployment guide](https://hot-updater.dev/docs/guides/console-deployment)
|
|
67
|
+
and configure its hosted template with a `ConsoleAuthAdapter`.
|
|
64
68
|
|
|
65
69
|
## 📁 Project Structure
|
|
66
70
|
|
|
@@ -68,26 +72,27 @@ The console will be available at `http://localhost:3000`.
|
|
|
68
72
|
src/
|
|
69
73
|
├── routes/
|
|
70
74
|
│ ├── __root.tsx # Root layout with providers
|
|
71
|
-
│ ├── index.tsx # Bundle list page
|
|
72
|
-
│
|
|
75
|
+
│ ├── index.tsx # Bundle/release list page
|
|
76
|
+
│ ├── -releases-search.ts # URL filter validation
|
|
77
|
+
│ └── api/ # Authentication and bundle downloads
|
|
73
78
|
├── components/
|
|
74
79
|
│ ├── ui/ # shadcn components
|
|
75
80
|
│ ├── features/
|
|
76
|
-
│ │
|
|
81
|
+
│ │ ├── bundles/ # Bundle metadata and rollout details
|
|
82
|
+
│ │ └── releases/ # Release policy editor
|
|
77
83
|
│ ├── PlatformIcon.tsx # iOS/Android icons
|
|
78
84
|
│ ├── BundleIdDisplay.tsx # Truncated bundle ID with tooltip
|
|
79
85
|
│ ├── RolloutPercentageBadge.tsx
|
|
80
86
|
│ ├── TimestampDisplay.tsx # UUIDv7 timestamp formatting
|
|
81
87
|
│ ├── ChannelBadge.tsx
|
|
82
88
|
│ └── EnabledStatusIcon.tsx
|
|
83
|
-
├── hooks/
|
|
84
|
-
│ └── useFilterParams.ts # URL-based filter state
|
|
85
89
|
├── lib/
|
|
86
90
|
│ ├── api.ts # React Query hooks
|
|
91
|
+
│ ├── api-rpc.ts # TanStack Start server functions
|
|
87
92
|
│ ├── constants.ts # Shared constants
|
|
88
93
|
│ ├── utils.ts # Utility functions
|
|
89
94
|
│ └── server/
|
|
90
|
-
│ ├──
|
|
95
|
+
│ ├── auth.server.ts # Management access checks
|
|
91
96
|
│ └── config.server.ts # Hot Updater config loader
|
|
92
97
|
└── styles.css # Global styles & theme variables
|
|
93
98
|
```
|
|
@@ -96,64 +101,65 @@ src/
|
|
|
96
101
|
|
|
97
102
|
### Bundle List Page
|
|
98
103
|
|
|
99
|
-
- **
|
|
100
|
-
- **
|
|
101
|
-
-
|
|
104
|
+
- **BundlesPage** - Release-backed list with server-side pagination (20 per page)
|
|
105
|
+
- **BundleFilterToolbar** - Platform, channel, and enabled-state filters
|
|
106
|
+
- Both live in `src/routes/index.tsx`.
|
|
102
107
|
|
|
103
|
-
###
|
|
108
|
+
### Release Editor Sheet
|
|
104
109
|
|
|
105
|
-
- **
|
|
106
|
-
- **BundleEditorForm** - TanStack Form with validation
|
|
110
|
+
- **ReleaseEditorSheet** - Slide-out editor for release policy, rollout, and promotion
|
|
107
111
|
- **BundleMetadata** - Read-only bundle information display
|
|
112
|
+
- Saving preflights the policy, then updates it with the expected release revision.
|
|
113
|
+
- To roll back a release, turn off **Enabled** and select **Save changes**.
|
|
114
|
+
On the next update check, devices select a previous eligible release or the
|
|
115
|
+
built-in app when none remains; the app still controls applying the result.
|
|
108
116
|
|
|
109
117
|
### Dialogs
|
|
110
118
|
|
|
111
|
-
- **
|
|
112
|
-
- **
|
|
113
|
-
- **
|
|
119
|
+
- **ReleaseEditorSheet** contains promotion and release-deletion dialogs.
|
|
120
|
+
- **RolloutCohortsDialog** displays rollout and targeted cohorts.
|
|
121
|
+
- **ChannelManagementDialog** creates and deletes channels.
|
|
114
122
|
|
|
115
123
|
## 🔌 API Integration
|
|
116
124
|
|
|
117
|
-
The console integrates with the configured Hot Updater storage and database
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
- `
|
|
122
|
-
- `
|
|
123
|
-
|
|
124
|
-
- `
|
|
125
|
-
- `
|
|
125
|
+
The console integrates with the configured Hot Updater storage and database
|
|
126
|
+
plugins through TanStack Start server functions in `src/lib/api-rpc.ts`.
|
|
127
|
+
Functions with input take a `{ data: input }` argument:
|
|
128
|
+
|
|
129
|
+
- `getConfig()` and `getChannels()` load configuration and channels.
|
|
130
|
+
- `getReleases({ data: { filter, limit, beforeReleaseId, afterReleaseId } })`
|
|
131
|
+
lists releases using the supported `ReleaseFilter` shapes.
|
|
132
|
+
- `getRelease({ data: { releaseId } })` reads a release.
|
|
133
|
+
- `preflightRelease({ data: { releaseId, expectedRevision, patch } })` validates
|
|
134
|
+
a `ReleasePolicyPatch`; `updateRelease` takes the same input to save it.
|
|
135
|
+
- `deleteRelease({ data: { releaseId, expectedRevision } })` deletes a release.
|
|
136
|
+
- `promoteRelease({ data: { releaseId, expectedRevision, targetChannel, action } })`
|
|
137
|
+
copies or moves a release; `action` is `"copy"` or `"move"`.
|
|
138
|
+
- `getBundles({ data: { platform, limit, after, before } })` lists bundle artifacts;
|
|
139
|
+
`getBundle({ data: { bundleId } })` reads one and
|
|
140
|
+
`deleteBundle({ data: { bundleId } })` deletes one.
|
|
141
|
+
|
|
142
|
+
Use one pagination cursor at a time.
|
|
126
143
|
|
|
127
144
|
## 🎯 Configuration
|
|
128
145
|
|
|
129
|
-
|
|
146
|
+
The checked-in [demo configuration](hot-updater.config.ts) seeds sample bundles
|
|
147
|
+
and releases through `createDatabaseCoreApi(database.withoutLatency()).deploy`.
|
|
148
|
+
For an empty local mock console, use this `hot-updater.config.ts`:
|
|
130
149
|
|
|
131
150
|
```typescript
|
|
132
|
-
import {
|
|
133
|
-
createMockDatabaseData,
|
|
134
|
-
mockDatabase,
|
|
135
|
-
mockStorage,
|
|
136
|
-
} from "@hot-updater/mock";
|
|
137
|
-
import { bundleToRow, type Bundle } from "@hot-updater/plugin-core";
|
|
138
|
-
|
|
139
|
-
const bundles: Bundle[] = [
|
|
140
|
-
// ... your bundles
|
|
141
|
-
];
|
|
142
|
-
|
|
143
|
-
const databaseData = createMockDatabaseData();
|
|
144
|
-
for (const bundle of bundles) {
|
|
145
|
-
databaseData.bundles.set(bundle.id, bundleToRow(bundle));
|
|
146
|
-
}
|
|
151
|
+
import { mockDatabase, mockStorage } from "@hot-updater/mock";
|
|
147
152
|
|
|
148
153
|
export default {
|
|
149
154
|
storage: mockStorage({}),
|
|
150
155
|
database: mockDatabase({
|
|
151
156
|
latency: { min: 500, max: 700 },
|
|
152
|
-
data: databaseData,
|
|
153
157
|
}),
|
|
154
158
|
};
|
|
155
159
|
```
|
|
156
160
|
|
|
161
|
+
Mock data is held in memory and resets when the configuration is reloaded.
|
|
162
|
+
|
|
157
163
|
## 🌈 Theming
|
|
158
164
|
|
|
159
165
|
The console uses Tailwind CSS v4 with oklch color space for accessible colors. Theme variables are defined in `src/styles.css`:
|
|
@@ -167,23 +173,23 @@ The console uses Tailwind CSS v4 with oklch color space for accessible colors. T
|
|
|
167
173
|
- Full TypeScript strict mode
|
|
168
174
|
- Type-safe server functions with TanStack Start
|
|
169
175
|
- Type-safe routing with TanStack Router
|
|
170
|
-
-
|
|
176
|
+
- Typed release-policy drafts and revision-aware mutations
|
|
171
177
|
|
|
172
178
|
## 📊 Data Flow
|
|
173
179
|
|
|
174
|
-
1. **URL State** → `
|
|
180
|
+
1. **URL State** → `Route.useSearch()` and `-releases-search.ts` manage release filters
|
|
175
181
|
2. **Server Functions** → TanStack Start server functions call the configured storage and database plugins
|
|
176
|
-
3. **React Query** → `
|
|
182
|
+
3. **React Query** → `useReleasesQuery()` and `useBundleQuery()` fetch and cache data
|
|
177
183
|
4. **UI Components** → Display data with shadcn components
|
|
178
|
-
5. **Mutations** → `
|
|
179
|
-
6. **Cache Invalidation** →
|
|
184
|
+
5. **Mutations** → `usePreflightReleaseMutation()` validates changes before `useUpdateReleaseMutation()` saves them
|
|
185
|
+
6. **Cache Invalidation** → Successful updates invalidate release, catalog, and channel queries
|
|
180
186
|
|
|
181
187
|
## 🚦 Development Guidelines
|
|
182
188
|
|
|
183
189
|
- **Server-only code** must use `.server.ts` extension
|
|
184
190
|
- **Client-side constants** live in `src/lib/constants.ts`
|
|
185
191
|
- **shadcn components** are customizable in `src/components/ui/`
|
|
186
|
-
- **
|
|
192
|
+
- **Release validation** checks the draft locally and preflights changes on the server
|
|
187
193
|
- **Toast notifications** use Sonner for success/error feedback
|
|
188
194
|
|
|
189
195
|
## 🐛 Troubleshooting
|
|
@@ -196,7 +202,7 @@ The console uses Tailwind CSS v4 with oklch color space for accessible colors. T
|
|
|
196
202
|
### Development Server
|
|
197
203
|
|
|
198
204
|
- Default port is 3000
|
|
199
|
-
- Change port: `pnpm dev --port 3001`
|
|
205
|
+
- Change port: `pnpm --dir packages/console dev --port 3001`
|
|
200
206
|
|
|
201
207
|
### Hot Updater Config
|
|
202
208
|
|
package/lib/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ConfigInput,
|
|
1
|
+
import { ConfigInput, ConfiguredDatabase } from "@hot-updater/plugin-core";
|
|
2
|
+
import { AnyHotUpdaterPlugin } from "@hot-updater/server/plugins";
|
|
2
3
|
//#region src/index.d.ts
|
|
3
4
|
type ConsoleAuthProvider = "google" | "github";
|
|
4
5
|
type ConsolePrincipal = Readonly<{
|
|
@@ -20,17 +21,18 @@ type ConsoleAuthAdapter = Readonly<{
|
|
|
20
21
|
getAccess(request: Request): Promise<ConsoleAccess>;
|
|
21
22
|
getProviders(request: Request): Promise<readonly ConsoleAuthProvider[]>;
|
|
22
23
|
}>;
|
|
23
|
-
type
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
type HotUpdaterConsoleConfig = Readonly<Omit<Pick<ConfigInput, "console" | "database" | "storage">, "database"> & {
|
|
25
|
+
database: ConfiguredDatabase;
|
|
26
|
+
/**
|
|
27
|
+
* The plugins your server runs, such as `[insights(), apiKeys()]`. The
|
|
28
|
+
* console runs them over `database` to read Insights and manage API
|
|
29
|
+
* keys, and shows only the features of the plugins listed. A
|
|
30
|
+
* `standaloneRepository` database needs none: the console reads its
|
|
31
|
+
* server's plugins from the admin `/version`.
|
|
32
|
+
*/
|
|
33
|
+
plugins?: readonly AnyHotUpdaterPlugin[];
|
|
32
34
|
}>;
|
|
33
35
|
type HotUpdaterConsoleConfigSource = HotUpdaterConsoleConfig | ((request: Request) => HotUpdaterConsoleConfig | Promise<HotUpdaterConsoleConfig>);
|
|
34
36
|
declare const defineConsoleConfig: <const TConfig extends HotUpdaterConsoleConfigSource>(config: TConfig) => TConfig;
|
|
35
37
|
//#endregion
|
|
36
|
-
export { ConsoleAccess, ConsoleAuthAdapter, ConsoleAuthProvider, ConsolePrincipal,
|
|
38
|
+
export { ConsoleAccess, ConsoleAuthAdapter, ConsoleAuthProvider, ConsolePrincipal, HotUpdaterConsoleConfig, HotUpdaterConsoleConfigSource, defineConsoleConfig };
|
package/lib/vite-internal.mjs
CHANGED
|
@@ -69,14 +69,17 @@ const createConsoleModulesPlugin = (mode) => {
|
|
|
69
69
|
if (id === resolvedVirtualPackageRouterModuleId) return `export { getRouter } from ${JSON.stringify(toImportPath(packageRouterFile))};`;
|
|
70
70
|
if (id === resolvedVirtualConfigModuleId) {
|
|
71
71
|
if (mode.type === "local") return [
|
|
72
|
-
"import { loadConfig } from \"@hot-updater/cli-tools\";",
|
|
72
|
+
"import { loadConfig, loadHotUpdaterPlugins } from \"@hot-updater/cli-tools\";",
|
|
73
73
|
"export default async () => {",
|
|
74
|
-
" const config = await
|
|
74
|
+
" const [config, plugins] = await Promise.all([",
|
|
75
|
+
" loadConfig(null),",
|
|
76
|
+
" loadHotUpdaterPlugins(),",
|
|
77
|
+
" ]);",
|
|
75
78
|
" return {",
|
|
76
79
|
" console: { gitUrl: config.console.gitUrl },",
|
|
77
80
|
" database: config.database,",
|
|
78
|
-
" signing: config.signing,",
|
|
79
81
|
" storage: config.storage,",
|
|
82
|
+
" ...(plugins === undefined ? {} : { plugins }),",
|
|
80
83
|
" };",
|
|
81
84
|
"};"
|
|
82
85
|
].join("\n");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/console",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-rc.
|
|
4
|
+
"version": "1.0.0-rc.20",
|
|
5
5
|
"files": [
|
|
6
6
|
".output",
|
|
7
7
|
"lib",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"@tanstack/react-router": "^1.167.0",
|
|
43
43
|
"@tanstack/react-router-devtools": "^1.166.7",
|
|
44
44
|
"@tanstack/react-router-ssr-query": "^1.166.7",
|
|
45
|
-
"@tanstack/react-start": "^1.
|
|
45
|
+
"@tanstack/react-start": "^1.168.25",
|
|
46
46
|
"@vitejs/plugin-react": "^6.0.1",
|
|
47
47
|
"class-variance-authority": "^0.7.1",
|
|
48
48
|
"clsx": "^2.1.1",
|
|
@@ -58,10 +58,10 @@
|
|
|
58
58
|
"tailwindcss": "^4.2.1",
|
|
59
59
|
"tw-animate-css": "^1.4.0",
|
|
60
60
|
"verkit": "0.4.0",
|
|
61
|
-
"@hot-updater/
|
|
62
|
-
"@hot-updater/server": "1.0.0-rc.
|
|
63
|
-
"@hot-updater/
|
|
64
|
-
"@hot-updater/
|
|
61
|
+
"@hot-updater/plugin-core": "1.0.0-rc.17",
|
|
62
|
+
"@hot-updater/server": "1.0.0-rc.18",
|
|
63
|
+
"@hot-updater/core": "1.0.0-rc.15",
|
|
64
|
+
"@hot-updater/bsdiff": "1.0.0-rc.14"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@tanstack/devtools-vite": "^0.6.0",
|
|
@@ -86,8 +86,8 @@
|
|
|
86
86
|
"vite-tsconfig-paths": "^6.1.1",
|
|
87
87
|
"vitest": "4.1.4",
|
|
88
88
|
"web-vitals": "^5.1.0",
|
|
89
|
-
"@hot-updater/cli-tools": "1.0.0-rc.
|
|
90
|
-
"@hot-updater/mock": "1.0.0-rc.
|
|
89
|
+
"@hot-updater/cli-tools": "1.0.0-rc.17",
|
|
90
|
+
"@hot-updater/mock": "1.0.0-rc.17"
|
|
91
91
|
},
|
|
92
92
|
"description": "React Native OTA solution for self-hosted",
|
|
93
93
|
"license": "MIT",
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { Link, useRouterState } from "@tanstack/react-router";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Moon,
|
|
6
|
-
Package,
|
|
7
|
-
ShieldCheck,
|
|
8
|
-
Sun,
|
|
9
|
-
} from "lucide-react";
|
|
2
|
+
import { LogOut, Moon, Package, Sun } from "lucide-react";
|
|
3
|
+
import { type ReactElement, type ReactNode, useState } from "react";
|
|
4
|
+
import { toast } from "sonner";
|
|
10
5
|
|
|
6
|
+
import { useFeatureUi } from "@/components/features/FeatureSlots";
|
|
11
7
|
import { HotUpdaterLogo } from "@/components/HotUpdaterLogo";
|
|
12
8
|
import { useTheme } from "@/components/ThemeProvider";
|
|
13
9
|
import {
|
|
@@ -21,26 +17,84 @@ import {
|
|
|
21
17
|
SidebarMenu,
|
|
22
18
|
SidebarMenuButton,
|
|
23
19
|
SidebarMenuItem,
|
|
20
|
+
useSidebar,
|
|
24
21
|
} from "@/components/ui/sidebar";
|
|
25
|
-
import { useApiKeyCapabilityQuery } from "@/lib/api-keys-api";
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
type NavigationItem = {
|
|
24
|
+
readonly label: string;
|
|
25
|
+
readonly icon: ReactNode;
|
|
26
|
+
readonly active: boolean;
|
|
27
|
+
readonly link: ReactElement;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function AppSidebar({ canSignOut = false }: { canSignOut?: boolean }) {
|
|
31
|
+
const { setOpenMobile } = useSidebar();
|
|
32
|
+
const closeMobileSidebar = () => setOpenMobile(false);
|
|
33
|
+
const [signingOut, setSigningOut] = useState(false);
|
|
34
|
+
const featureUi = useFeatureUi();
|
|
29
35
|
const { theme, setTheme } = useTheme();
|
|
30
36
|
const routerState = useRouterState();
|
|
31
37
|
const currentPath = routerState.location.pathname;
|
|
32
38
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
const navigation: readonly NavigationItem[] = [
|
|
40
|
+
{
|
|
41
|
+
label: "Bundles",
|
|
42
|
+
icon: <Package />,
|
|
43
|
+
active: currentPath === "/",
|
|
44
|
+
link: (
|
|
45
|
+
<Link
|
|
46
|
+
to="/"
|
|
47
|
+
onClick={closeMobileSidebar}
|
|
48
|
+
search={{
|
|
49
|
+
afterReleaseId: undefined,
|
|
50
|
+
beforeReleaseId: undefined,
|
|
51
|
+
channelId: undefined,
|
|
52
|
+
enabled: undefined,
|
|
53
|
+
releaseId: undefined,
|
|
54
|
+
platform: undefined,
|
|
55
|
+
bundleId: undefined,
|
|
56
|
+
scopeKey: undefined,
|
|
57
|
+
}}
|
|
58
|
+
/>
|
|
59
|
+
),
|
|
60
|
+
},
|
|
61
|
+
// Each feature that is on adds its item, as the feature registry lists it.
|
|
62
|
+
...featureUi.flatMap(({ features, label, navigation }) =>
|
|
63
|
+
navigation === undefined
|
|
64
|
+
? []
|
|
65
|
+
: [
|
|
66
|
+
{
|
|
67
|
+
label,
|
|
68
|
+
icon: navigation.icon,
|
|
69
|
+
active: navigation.paths.includes(currentPath),
|
|
70
|
+
link: navigation.link({ features, onClick: closeMobileSidebar }),
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
),
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
const signOut = async () => {
|
|
77
|
+
setSigningOut(true);
|
|
78
|
+
try {
|
|
79
|
+
const response = await fetch("/api/auth/sign-out", {
|
|
80
|
+
body: "{}",
|
|
81
|
+
headers: { "content-type": "application/json" },
|
|
82
|
+
method: "POST",
|
|
83
|
+
});
|
|
84
|
+
if (!response.ok) throw new Error("Sign-out failed. Please try again.");
|
|
85
|
+
window.location.reload();
|
|
86
|
+
} catch {
|
|
87
|
+
setSigningOut(false);
|
|
88
|
+
toast.error("Sign-out failed. Please try again.");
|
|
89
|
+
}
|
|
90
|
+
};
|
|
38
91
|
|
|
39
92
|
return (
|
|
40
93
|
<Sidebar collapsible="icon">
|
|
41
94
|
<SidebarHeader className="h-12 justify-center">
|
|
42
95
|
<Link
|
|
43
96
|
to="/"
|
|
97
|
+
onClick={closeMobileSidebar}
|
|
44
98
|
search={{
|
|
45
99
|
afterReleaseId: undefined,
|
|
46
100
|
beforeReleaseId: undefined,
|
|
@@ -48,9 +102,8 @@ export function AppSidebar() {
|
|
|
48
102
|
enabled: undefined,
|
|
49
103
|
releaseId: undefined,
|
|
50
104
|
platform: undefined,
|
|
51
|
-
targetAppVersion: undefined,
|
|
52
105
|
bundleId: undefined,
|
|
53
|
-
|
|
106
|
+
scopeKey: undefined,
|
|
54
107
|
}}
|
|
55
108
|
className="flex items-center gap-3 p-1 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:p-2"
|
|
56
109
|
>
|
|
@@ -71,63 +124,18 @@ export function AppSidebar() {
|
|
|
71
124
|
<SidebarGroupLabel>Navigation</SidebarGroupLabel>
|
|
72
125
|
<SidebarGroupContent>
|
|
73
126
|
<SidebarMenu>
|
|
74
|
-
|
|
75
|
-
<
|
|
76
|
-
isActive={isBundlesActive}
|
|
77
|
-
render={
|
|
78
|
-
<Link
|
|
79
|
-
to="/"
|
|
80
|
-
search={{
|
|
81
|
-
afterReleaseId: undefined,
|
|
82
|
-
beforeReleaseId: undefined,
|
|
83
|
-
channelId: undefined,
|
|
84
|
-
enabled: undefined,
|
|
85
|
-
releaseId: undefined,
|
|
86
|
-
platform: undefined,
|
|
87
|
-
targetAppVersion: undefined,
|
|
88
|
-
bundleId: undefined,
|
|
89
|
-
page: undefined,
|
|
90
|
-
}}
|
|
91
|
-
/>
|
|
92
|
-
}
|
|
93
|
-
tooltip="Bundles"
|
|
94
|
-
>
|
|
95
|
-
<Package />
|
|
96
|
-
<span>Bundles</span>
|
|
97
|
-
</SidebarMenuButton>
|
|
98
|
-
</SidebarMenuItem>
|
|
99
|
-
<SidebarMenuItem>
|
|
100
|
-
<SidebarMenuButton
|
|
101
|
-
isActive={isInsightsActive}
|
|
102
|
-
render={<Link to="/insights" />}
|
|
103
|
-
tooltip="Insights"
|
|
104
|
-
>
|
|
105
|
-
<ChartNoAxesCombined />
|
|
106
|
-
<span>Insights</span>
|
|
107
|
-
</SidebarMenuButton>
|
|
108
|
-
</SidebarMenuItem>
|
|
109
|
-
{apiKeyCapability.data?.apiKeys ? (
|
|
110
|
-
<SidebarMenuItem>
|
|
127
|
+
{navigation.map((item) => (
|
|
128
|
+
<SidebarMenuItem key={item.label}>
|
|
111
129
|
<SidebarMenuButton
|
|
112
|
-
isActive={
|
|
113
|
-
render={
|
|
114
|
-
tooltip=
|
|
130
|
+
isActive={item.active}
|
|
131
|
+
render={item.link}
|
|
132
|
+
tooltip={item.label}
|
|
115
133
|
>
|
|
116
|
-
|
|
117
|
-
<span>
|
|
134
|
+
{item.icon}
|
|
135
|
+
<span>{item.label}</span>
|
|
118
136
|
</SidebarMenuButton>
|
|
119
137
|
</SidebarMenuItem>
|
|
120
|
-
)
|
|
121
|
-
<SidebarMenuItem>
|
|
122
|
-
<SidebarMenuButton
|
|
123
|
-
isActive={isSigningActive}
|
|
124
|
-
render={<Link to="/signing" />}
|
|
125
|
-
tooltip="Bundle signing"
|
|
126
|
-
>
|
|
127
|
-
<ShieldCheck />
|
|
128
|
-
<span>Bundle signing</span>
|
|
129
|
-
</SidebarMenuButton>
|
|
130
|
-
</SidebarMenuItem>
|
|
138
|
+
))}
|
|
131
139
|
</SidebarMenu>
|
|
132
140
|
</SidebarGroupContent>
|
|
133
141
|
</SidebarGroup>
|
|
@@ -135,6 +143,18 @@ export function AppSidebar() {
|
|
|
135
143
|
|
|
136
144
|
<SidebarFooter>
|
|
137
145
|
<SidebarMenu>
|
|
146
|
+
{canSignOut ? (
|
|
147
|
+
<SidebarMenuItem>
|
|
148
|
+
<SidebarMenuButton
|
|
149
|
+
disabled={signingOut}
|
|
150
|
+
onClick={() => void signOut()}
|
|
151
|
+
tooltip="Sign out"
|
|
152
|
+
>
|
|
153
|
+
<LogOut />
|
|
154
|
+
<span>{signingOut ? "Signing out…" : "Sign out"}</span>
|
|
155
|
+
</SidebarMenuButton>
|
|
156
|
+
</SidebarMenuItem>
|
|
157
|
+
) : null}
|
|
138
158
|
<SidebarMenuItem>
|
|
139
159
|
<SidebarMenuButton
|
|
140
160
|
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
|
@@ -48,7 +48,11 @@ export function ConsoleAccessPage({
|
|
|
48
48
|
setSigningOut(true);
|
|
49
49
|
setError(null);
|
|
50
50
|
try {
|
|
51
|
-
const response = await fetch("/api/auth/sign-out", {
|
|
51
|
+
const response = await fetch("/api/auth/sign-out", {
|
|
52
|
+
body: "{}",
|
|
53
|
+
headers: { "content-type": "application/json" },
|
|
54
|
+
method: "POST",
|
|
55
|
+
});
|
|
52
56
|
if (!response.ok) {
|
|
53
57
|
throw new Error("Sign-out failed.");
|
|
54
58
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ArrowUpRight } from "lucide-react";
|
|
2
|
+
|
|
3
|
+
import { NotFoundPage } from "@/components/NotFoundPage";
|
|
4
|
+
import { buttonVariants } from "@/components/ui/button";
|
|
5
|
+
import {
|
|
6
|
+
Empty,
|
|
7
|
+
EmptyContent,
|
|
8
|
+
EmptyDescription,
|
|
9
|
+
EmptyHeader,
|
|
10
|
+
EmptyTitle,
|
|
11
|
+
} from "@/components/ui/empty";
|
|
12
|
+
import { SidebarTrigger } from "@/components/ui/sidebar";
|
|
13
|
+
import { consoleFeatures, unavailableReason } from "@/lib/console-features";
|
|
14
|
+
import {
|
|
15
|
+
notFoundFeature,
|
|
16
|
+
useConsoleFeatures,
|
|
17
|
+
} from "@/lib/console-features-api";
|
|
18
|
+
|
|
19
|
+
export const CONSOLE_DEPLOYMENT_GUIDE_URL =
|
|
20
|
+
"https://hot-updater.dev/docs/guides/console-deployment";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A feature route's not-found state: the plugin to add, or, for a feature a
|
|
24
|
+
* self-hosted server does not serve, the database config it needs.
|
|
25
|
+
*/
|
|
26
|
+
export function ConsoleFeatureUnavailable({
|
|
27
|
+
data,
|
|
28
|
+
}: {
|
|
29
|
+
readonly data?: unknown;
|
|
30
|
+
}) {
|
|
31
|
+
const feature = notFoundFeature(data);
|
|
32
|
+
const remote = useConsoleFeatures().data?.remote === true;
|
|
33
|
+
if (feature === undefined) return <NotFoundPage />;
|
|
34
|
+
|
|
35
|
+
const { label, plugin } = consoleFeatures[feature];
|
|
36
|
+
const pluginCall = <code className="font-mono">{`${plugin}()`}</code>;
|
|
37
|
+
return (
|
|
38
|
+
<div className="flex h-svh min-h-0 flex-col">
|
|
39
|
+
<header className="sticky top-0 z-10 flex shrink-0 items-center gap-3 border-b bg-background px-3 py-3 sm:min-h-12 sm:bg-card/70 sm:px-4 sm:backdrop-blur-sm">
|
|
40
|
+
<SidebarTrigger className="-ml-1" />
|
|
41
|
+
<h1 className="text-sm font-medium">{label}</h1>
|
|
42
|
+
</header>
|
|
43
|
+
<div className="flex min-h-0 min-w-0 flex-1 overflow-y-auto bg-muted/5 p-3 sm:p-6">
|
|
44
|
+
<Empty>
|
|
45
|
+
<EmptyHeader>
|
|
46
|
+
{unavailableReason(feature, { remote }) === "remote" ? (
|
|
47
|
+
<>
|
|
48
|
+
<EmptyTitle>Needs the server's database</EmptyTitle>
|
|
49
|
+
<EmptyDescription>
|
|
50
|
+
This console reaches a self-hosted server through its admin
|
|
51
|
+
API, which does not serve this page. Configure the console
|
|
52
|
+
with the database the server uses.
|
|
53
|
+
</EmptyDescription>
|
|
54
|
+
</>
|
|
55
|
+
) : (
|
|
56
|
+
<>
|
|
57
|
+
<EmptyTitle>{label} not installed</EmptyTitle>
|
|
58
|
+
<EmptyDescription>
|
|
59
|
+
Add {pluginCall} to <code className="font-mono">plugins</code>{" "}
|
|
60
|
+
{remote
|
|
61
|
+
? "where you create the server."
|
|
62
|
+
: "where you create the server and in the console config."}
|
|
63
|
+
</EmptyDescription>
|
|
64
|
+
</>
|
|
65
|
+
)}
|
|
66
|
+
</EmptyHeader>
|
|
67
|
+
<EmptyContent>
|
|
68
|
+
<a
|
|
69
|
+
className={buttonVariants({ variant: "outline" })}
|
|
70
|
+
href={CONSOLE_DEPLOYMENT_GUIDE_URL}
|
|
71
|
+
rel="noreferrer"
|
|
72
|
+
target="_blank"
|
|
73
|
+
>
|
|
74
|
+
Console setup guide
|
|
75
|
+
<ArrowUpRight aria-hidden="true" data-icon="inline-end" />
|
|
76
|
+
</a>
|
|
77
|
+
</EmptyContent>
|
|
78
|
+
</Empty>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|