@hot-updater/console 1.0.0-rc.0 → 1.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/HashValueDisplay-CB0Eskit.js +1 -0
  3. package/.output/public/assets/InsightsPageHeader-Car5LNiZ.js +1 -0
  4. package/.output/public/assets/api-keys-BzKocqRy.js +1 -0
  5. package/.output/public/assets/api-keys-api-BHcODB7q.js +1 -0
  6. package/.output/public/assets/card-Dhr_eFYj.js +1 -0
  7. package/.output/public/assets/{chart-no-axes-combined-FaYMGmQS.js → chart-no-axes-combined-Dy4WJv2U.js} +1 -1
  8. package/.output/public/assets/composite-Bp98A0Ld.js +1 -0
  9. package/.output/public/assets/createServerFn-CA8fs7QD.js +3 -0
  10. package/.output/public/assets/dialog-CUTrcPlB.js +1 -0
  11. package/.output/public/assets/{dist-DspNKfMj.js → dist-C_NydL0e.js} +1 -1
  12. package/.output/public/assets/index-DdAz-CXv.js +10 -0
  13. package/.output/public/assets/input-group-cSOG4wh_.js +1 -0
  14. package/.output/public/assets/insights-C0PBSZ-n.js +1 -0
  15. package/.output/public/assets/installations-CE33wgbY.js +2 -0
  16. package/.output/public/assets/installations-CStZja8x.js +1 -0
  17. package/.output/public/assets/lazyRouteComponent-Ccp3bwBY.js +1 -0
  18. package/.output/public/assets/link-CI8idAUi.js +1 -0
  19. package/.output/public/assets/loader-circle-BoxLrsOu.js +1 -0
  20. package/.output/public/assets/refresh-cw-Bqc72UZG.js +1 -0
  21. package/.output/public/assets/{routes-DcQnVajO.js → routes-DGVXdzpw.js} +2 -2
  22. package/.output/public/assets/routes-DTlwOr8I.js +1 -0
  23. package/.output/public/assets/{shield-check-DE_hVuHu.js → shield-check-uK3MHWJZ.js} +1 -1
  24. package/.output/public/assets/signing-CMf4aqDO.js +1 -0
  25. package/.output/public/assets/styles-BCvbtKsm.css +2 -0
  26. package/.output/public/assets/table-2vcV89QB.js +1 -0
  27. package/.output/public/assets/useCompositeListItem-CHLITE_4.js +1 -0
  28. package/.output/public/assets/x-DCd0j8c6.js +1 -0
  29. package/.output/server/{__23tanstack-start-server-fn-resolver-DBP9TQ7K.mjs → __23tanstack-start-server-fn-resolver-B7ioJhtE.mjs} +44 -52
  30. package/.output/server/_libs/@base-ui/react+[...].mjs +1906 -4040
  31. package/.output/server/_libs/@tanstack/react-router+[...].mjs +52 -4
  32. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +2 -2
  33. package/.output/server/_libs/lucide-react.mjs +36 -3
  34. package/.output/server/_libs/next-themes.mjs +2 -2
  35. package/.output/server/_libs/sonner.mjs +2 -2
  36. package/.output/server/_libs/tanstack__react-query.mjs +2 -2
  37. package/.output/server/_libs/verkit.mjs +20 -15
  38. package/.output/server/_runtime.mjs +1 -10
  39. package/.output/server/_ssr/HashValueDisplay-DwpGLfrl.mjs +55 -0
  40. package/.output/server/_ssr/InsightsPageHeader-Df4Brj8u.mjs +484 -0
  41. package/.output/server/_ssr/{api-keys-BnM_o2nS.mjs → api-keys-CZeTPQys.mjs} +11 -13
  42. package/.output/server/_ssr/{api-keys-api-CIywsSF5.mjs → api-keys-api-BUPQVSpk.mjs} +2 -2
  43. package/.output/server/_ssr/{api-keys-rpc-BSp1wkIV.mjs → api-keys-rpc-Cs7tS5_j.mjs} +5 -5
  44. package/.output/server/_ssr/{api-rpc-cG5wd_tO.mjs → api-rpc-tAupfGQm.mjs} +73 -142
  45. package/.output/server/_ssr/{auth-rpc-BrJ6obHJ.mjs → auth-rpc-DUaeOWKK.mjs} +4 -4
  46. package/.output/server/_ssr/{auth.server-DwKoZoQM.mjs → auth.server-DW71WzsD.mjs} +2 -5
  47. package/.output/server/_ssr/{bundle-signing-rpc-COoQhW8E.mjs → bundle-signing-rpc-BhnBw0-d.mjs} +2 -2
  48. package/.output/server/_ssr/bundle-signing.server-C4NW5mWx.mjs +12 -0
  49. package/.output/server/_ssr/{alert-Dim3MGY5.mjs → card-BRiPmi0C.mjs} +61 -39
  50. package/.output/server/_ssr/{config.server-DEiLhKmy.mjs → config.server-afDuAwaG.mjs} +15 -8
  51. package/.output/server/_ssr/{createSsrRpc-9P0S0lJ3.mjs → createSsrRpc-DjWNJ0uc.mjs} +6 -6
  52. package/.output/server/_ssr/databaseJsonValue-d6I3nAS4.mjs +45 -0
  53. package/.output/server/_ssr/{databaseRows-CwbNwYSl.mjs → databaseRows-5bCRTxm-.mjs} +4 -44
  54. package/.output/server/_ssr/{deleteBundle-Cu_XOMt0.mjs → deleteBundle-BTGwBgzx.mjs} +2 -2
  55. package/.output/server/_ssr/{dialog-CQuRMNZ4.mjs → dialog-DS-nw-pA.mjs} +2 -2
  56. package/.output/server/_ssr/{dist-BVSLTpoP.mjs → dist-CyzPaI5v.mjs} +2 -2
  57. package/.output/server/_ssr/{getBundleChildren-BcRMFoDF.mjs → getBundleChildren-C0vh4ZvE.mjs} +2 -2
  58. package/.output/server/_ssr/input-group-BpsnmvZw.mjs +70 -0
  59. package/.output/server/_ssr/insights-BdS364Xu.mjs +460 -0
  60. package/.output/server/_ssr/insights-rpc-38DujK0G.mjs +57 -0
  61. package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +303 -0
  62. package/.output/server/_ssr/installations-CtA59MQG.mjs +29 -0
  63. package/.output/server/_ssr/installations-DiVKEF5T.mjs +627 -0
  64. package/.output/server/_ssr/{router-C-KUBfOi.mjs → router-B9wG_9f8.mjs} +37 -46
  65. package/.output/server/_ssr/{routes-Z7TwC6nC.mjs → routes-CKrrS2q2.mjs} +3 -3
  66. package/.output/server/_ssr/{routes-BM4ht9jb.mjs → routes-D7Yh_jfj.mjs} +402 -390
  67. package/.output/server/_ssr/runtime.server-DacU1TBV.mjs +438 -0
  68. package/.output/server/_ssr/{server-BjzmvXnO.mjs → server-D_C2rMzf.mjs} +5 -5
  69. package/.output/server/_ssr/{signing-k8GxyBDF.mjs → signing-Dz1WkZOf.mjs} +16 -85
  70. package/.output/server/_ssr/ssr.mjs +1 -1
  71. package/.output/server/_ssr/{field-DRIXKjZG.mjs → table-CnZmon_Z.mjs} +51 -5
  72. package/.output/server/_ssr/{uuidv7-BbJ-9D_I.mjs → uuidv7-D0QFkPL_.mjs} +3 -3
  73. package/.output/server/_tanstack-start-manifest_v-2MjTyxil.mjs +97 -0
  74. package/.output/server/index.mjs +168 -203
  75. package/lib/index.d.mts +4 -4
  76. package/lib/vite-internal.mjs +1 -1
  77. package/lib/vite.d.mts +0 -1
  78. package/package.json +8 -8
  79. package/src/components/AppSidebar.tsx +12 -16
  80. package/src/components/ConsoleAccessPage.tsx +1 -1
  81. package/src/components/HashValueDisplay.tsx +11 -2
  82. package/src/components/features/api-keys/ApiKeysPage.tsx +1 -1
  83. package/src/components/features/bundles/BundleChildrenPanel.tsx +7 -6
  84. package/src/components/features/bundles/BundleMetadata.tsx +1 -20
  85. package/src/components/features/channels/ChannelManagementDialog.tsx +1 -1
  86. package/src/components/features/insights/EventDetails.tsx +151 -0
  87. package/src/components/features/insights/EventHistoryCard.tsx +245 -0
  88. package/src/components/features/insights/EventHistoryList.tsx +63 -0
  89. package/src/components/features/insights/InsightsBundleReports.tsx +51 -0
  90. package/src/components/features/insights/InsightsControls.tsx +121 -0
  91. package/src/components/features/{analytics/AnalyticsErrorAlert.tsx → insights/InsightsErrorAlert.tsx} +6 -6
  92. package/src/components/features/insights/InsightsOverview.tsx +203 -0
  93. package/src/components/features/insights/InsightsPageHeader.tsx +48 -0
  94. package/src/components/features/insights/InsightsPagination.tsx +52 -0
  95. package/src/components/features/insights/InstallationHistoryCard.tsx +250 -0
  96. package/src/components/features/insights/InstallationMatchesCard.tsx +178 -0
  97. package/src/components/features/insights/InstallationPageHeader.tsx +90 -0
  98. package/src/components/features/releases/ReleaseEditorSheet.tsx +24 -24
  99. package/src/components/features/signing/BundleSigningPage.tsx +18 -126
  100. package/src/components/ui/badge.tsx +2 -0
  101. package/src/components/ui/combobox.tsx +1 -0
  102. package/src/components/ui/skeleton.tsx +4 -1
  103. package/src/components/ui/tooltip.tsx +1 -1
  104. package/src/index.ts +7 -3
  105. package/src/lib/api-rpc.ts +19 -90
  106. package/src/lib/api.ts +0 -99
  107. package/src/lib/insights-api.ts +100 -0
  108. package/src/lib/insights-rpc.ts +88 -0
  109. package/src/lib/insights-view.ts +15 -0
  110. package/src/lib/public-bundle-error.ts +19 -0
  111. package/src/lib/server/bundle-signing.server.ts +5 -82
  112. package/src/lib/server/console-runtime.server.ts +0 -9
  113. package/src/lib/server/releaseReachability.ts +1 -4
  114. package/src/lib/server/runtime.server.ts +6 -142
  115. package/src/routeTree.gen.ts +21 -21
  116. package/src/router.tsx +3 -0
  117. package/src/routes/-installations-search.ts +25 -12
  118. package/src/routes/__root.tsx +6 -16
  119. package/src/routes/api-keys.tsx +1 -1
  120. package/src/routes/index.tsx +21 -81
  121. package/src/routes/insights.tsx +84 -0
  122. package/src/routes/installations.tsx +294 -142
  123. package/src/styles.css +6 -0
  124. package/.output/public/assets/AnalyticsCapabilityContext-BCke57-7.js +0 -1
  125. package/.output/public/assets/BundleActivityChart-CnsBe_1J.js +0 -42
  126. package/.output/public/assets/BundleIdDisplay-Dky75zLU.js +0 -1
  127. package/.output/public/assets/alert-C-1KvDW8.js +0 -1
  128. package/.output/public/assets/analytics-Bqz0c-BT.js +0 -1
  129. package/.output/public/assets/analytics-api-CJdrTLLO.js +0 -1
  130. package/.output/public/assets/api-B_Rh-uzC.js +0 -1
  131. package/.output/public/assets/api-keys-BurCFhzH.js +0 -1
  132. package/.output/public/assets/api-keys-api-BEDJdQg3.js +0 -1
  133. package/.output/public/assets/badge-Bf0fZ-Gx.js +0 -1
  134. package/.output/public/assets/createServerFn-gM2-LFBy.js +0 -3
  135. package/.output/public/assets/dialog-BS60BRr2.js +0 -1
  136. package/.output/public/assets/download-O-yNBc9H.js +0 -1
  137. package/.output/public/assets/field-Buq148-A.js +0 -1
  138. package/.output/public/assets/index-kFhd2S78.js +0 -10
  139. package/.output/public/assets/installations-BrQ-od41.js +0 -2
  140. package/.output/public/assets/installations-CGlZ3ROz.js +0 -1
  141. package/.output/public/assets/lazyRouteComponent-rPzgA7S-.js +0 -1
  142. package/.output/public/assets/loader-circle-DCYF-wJl.js +0 -1
  143. package/.output/public/assets/refresh-cw-n8XYbPPj.js +0 -1
  144. package/.output/public/assets/routes-CrdokJ80.js +0 -1
  145. package/.output/public/assets/search-DLhbWw3W.js +0 -1
  146. package/.output/public/assets/signing-HRexfjsH.js +0 -1
  147. package/.output/public/assets/styles-eB4EtvFS.css +0 -2
  148. package/.output/public/assets/table-D8CLieHX.js +0 -1
  149. package/.output/public/assets/textarea-YJLTVAoq.js +0 -1
  150. package/.output/public/assets/useMutation-JsGVjQMY.js +0 -1
  151. package/.output/public/assets/useOpenInteractionType-4cRD5xO0.js +0 -1
  152. package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +0 -1554
  153. package/.output/server/_libs/d3-array.mjs +0 -231
  154. package/.output/server/_libs/d3-color.mjs +0 -330
  155. package/.output/server/_libs/d3-format.mjs +0 -252
  156. package/.output/server/_libs/d3-interpolate.mjs +0 -207
  157. package/.output/server/_libs/d3-path.mjs +0 -77
  158. package/.output/server/_libs/d3-scale+[...].mjs +0 -1907
  159. package/.output/server/_libs/d3-shape.mjs +0 -818
  160. package/.output/server/_libs/decimal.js-light.mjs +0 -884
  161. package/.output/server/_libs/es-toolkit.mjs +0 -1134
  162. package/.output/server/_libs/eventemitter3.mjs +0 -236
  163. package/.output/server/_libs/immer.mjs +0 -532
  164. package/.output/server/_libs/react-redux+[...].mjs +0 -205
  165. package/.output/server/_libs/recharts+[...].mjs +0 -14117
  166. package/.output/server/_ssr/AnalyticsCapabilityContext-ui4DX8GO.mjs +0 -15
  167. package/.output/server/_ssr/BundleActivityChart-BiB9ZORH.mjs +0 -288
  168. package/.output/server/_ssr/BundleIdDisplay-Czk6iR6p.mjs +0 -13
  169. package/.output/server/_ssr/analytics-DtMTn8fn.mjs +0 -858
  170. package/.output/server/_ssr/analytics-api-Cg1M5v_H.mjs +0 -71
  171. package/.output/server/_ssr/analytics-input-fRRRn0uA.mjs +0 -96
  172. package/.output/server/_ssr/analytics-rpc-DY5uXvHT.mjs +0 -155
  173. package/.output/server/_ssr/api-BTSv1tZb.mjs +0 -282
  174. package/.output/server/_ssr/badge-DCxbEeof.mjs +0 -26
  175. package/.output/server/_ssr/bundle-signing.server-D5u8KkMp.mjs +0 -51
  176. package/.output/server/_ssr/installations-B-azgAvX.mjs +0 -19
  177. package/.output/server/_ssr/installations-Ctvzp2JH.mjs +0 -535
  178. package/.output/server/_ssr/runtime.server-awTLiiXW.mjs +0 -527
  179. package/.output/server/_ssr/table-v1yCvYo7.mjs +0 -53
  180. package/.output/server/_ssr/textarea-DYHnskhb.mjs +0 -14
  181. package/.output/server/_tanstack-start-manifest_v-CH4StzJu.mjs +0 -108
  182. package/src/components/features/analytics/ActivityChart.tsx +0 -150
  183. package/src/components/features/analytics/AnalyticsCapabilityContext.tsx +0 -24
  184. package/src/components/features/analytics/AnalyticsControls.tsx +0 -115
  185. package/src/components/features/analytics/AnalyticsOverview.tsx +0 -208
  186. package/src/components/features/analytics/BundleDistribution.tsx +0 -133
  187. package/src/components/features/analytics/BundleSelector.tsx +0 -114
  188. package/src/components/features/analytics/InstallationHistoryCard.tsx +0 -248
  189. package/src/components/features/analytics/InstallationMatchesCard.tsx +0 -132
  190. package/src/components/features/analytics/InstallationPageHeader.tsx +0 -138
  191. package/src/components/features/analytics/InstallationPagination.tsx +0 -48
  192. package/src/components/features/analytics/InstallationSearch.tsx +0 -230
  193. package/src/components/features/analytics/UpdateOutcomes.tsx +0 -147
  194. package/src/components/features/bundles/BundleActivityChart.tsx +0 -193
  195. package/src/components/features/bundles/BundleAnalyticsSummary.tsx +0 -123
  196. package/src/lib/analytics-api.ts +0 -145
  197. package/src/lib/analytics-error.ts +0 -19
  198. package/src/lib/analytics-input.ts +0 -187
  199. package/src/lib/analytics-overview.ts +0 -170
  200. package/src/lib/analytics-rpc.ts +0 -218
  201. package/src/lib/server/releaseActivity.ts +0 -27
  202. package/src/routes/analytics.tsx +0 -139
  203. /package/.output/public/assets/{preload-helper-TKOBYGYD.js → preload-helper-CQ36nxok.js} +0 -0
@@ -1,189 +1,341 @@
1
- import { createFileRoute } from "@tanstack/react-router";
2
- import { useEffect, useMemo, useState } from "react";
1
+ import {
2
+ createFileRoute,
3
+ useElementScrollRestoration,
4
+ } from "@tanstack/react-router";
5
+ import { ArrowLeft } from "lucide-react";
6
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
3
7
 
4
- import { useAnalyticsCapability } from "@/components/features/analytics/AnalyticsCapabilityContext";
5
- import { InstallationHistoryCard } from "@/components/features/analytics/InstallationHistoryCard";
6
- import { InstallationMatchesCard } from "@/components/features/analytics/InstallationMatchesCard";
8
+ import { EventHistoryCard } from "@/components/features/insights/EventHistoryCard";
9
+ import { InsightsPageHeader } from "@/components/features/insights/InsightsPageHeader";
10
+ import { InstallationHistoryCard } from "@/components/features/insights/InstallationHistoryCard";
11
+ import { InstallationMatchesCard } from "@/components/features/insights/InstallationMatchesCard";
7
12
  import {
8
- InstallationPageHeader,
9
13
  InstallationResultsSkeleton,
10
14
  InstallationSearchPanel,
11
- } from "@/components/features/analytics/InstallationPageHeader";
12
- import {
13
- ensureAnalyticsRouteAccess,
14
- isAnalyticsQueryEnabled,
15
- } from "@/lib/analytics-api";
15
+ } from "@/components/features/insights/InstallationPageHeader";
16
+ import { Button } from "@/components/ui/button";
17
+ import { Card, CardContent } from "@/components/ui/card";
16
18
  import {
17
- type InstallationSearchRow,
18
- useInstallationHistoryQuery,
19
- useInstallationSearchQuery,
20
- } from "@/lib/api";
19
+ useInsightsEventsQuery,
20
+ useInsightsInstallationEventsQuery,
21
+ useInsightsInstallationQuery,
22
+ useInsightsInstallationsQuery,
23
+ } from "@/lib/insights-api";
21
24
 
22
- import { validateInstallationsSearch } from "./-installations-search";
25
+ import {
26
+ getInsightsScrollRestorationKey,
27
+ validateInstallationsSearch,
28
+ } from "./-installations-search";
23
29
 
24
30
  const SEARCH_LIMIT = 20;
25
- const HISTORY_LIMIT = 50;
31
+ const EVENT_LIMIT = 50;
32
+ const freshBefore = () => Date.now();
33
+ const pushCursor = (stack: readonly string[], cursor: string | undefined) => [
34
+ ...stack,
35
+ cursor ?? "",
36
+ ];
37
+ const popCursor = (stack: readonly string[]) => ({
38
+ cursor: stack?.at(-1) || undefined,
39
+ stack: stack.slice(0, -1),
40
+ });
26
41
 
27
42
  export const Route = createFileRoute("/installations")({
28
- beforeLoad: ({ context }) => ensureAnalyticsRouteAccess(context.queryClient),
29
43
  component: InstallationsPage,
30
44
  validateSearch: validateInstallationsSearch,
31
45
  });
32
46
 
33
47
  function InstallationsPage() {
34
- const capability = useAnalyticsCapability();
35
- const analyticsQueriesEnabled = isAnalyticsQueryEnabled(capability);
36
48
  const search = Route.useSearch();
37
49
  const navigate = Route.useNavigate();
38
50
  const [draftQuery, setDraftQuery] = useState(search.query ?? "");
51
+ const [initialEventsBefore] = useState(freshBefore);
52
+ const [initialHistoryBefore] = useState(freshBefore);
53
+ const [eventsBack, setEventsBack] = useState<readonly string[]>([]);
54
+ const [searchBack, setSearchBack] = useState<readonly string[]>([]);
55
+ const [historyBack, setHistoryBack] = useState<readonly string[]>([]);
56
+ const query = search.query?.trim() ?? "";
57
+ const hasSearchQuery = query.length > 0;
58
+ const hasSelection = search.installId !== undefined;
59
+ const hasLookup = hasSearchQuery || hasSelection;
60
+ const eventsBefore = search.eventsBefore ?? initialEventsBefore;
61
+ const historyBefore = search.historyBefore ?? initialHistoryBefore;
62
+
63
+ const updateSearch = (next: Partial<typeof search>, replace = false) => {
64
+ void navigate({
65
+ to: "/installations",
66
+ search: { ...search, ...next },
67
+ replace,
68
+ });
69
+ };
39
70
 
40
71
  useEffect(() => {
41
72
  setDraftQuery(search.query ?? "");
42
73
  }, [search.query]);
43
74
 
44
- const query = search.query?.trim() ?? "";
45
- const {
46
- data: results,
47
- error: searchError,
48
- isLoading: isSearchLoading,
49
- } = useInstallationSearchQuery(
75
+ useEffect(() => {
76
+ if (search.eventsBefore === undefined) {
77
+ updateSearch({ eventsBefore }, true);
78
+ }
79
+ }, [eventsBefore, search.eventsBefore]);
80
+
81
+ useEffect(() => {
82
+ if (hasSelection && search.historyBefore === undefined) {
83
+ updateSearch({ historyBefore }, true);
84
+ }
85
+ }, [hasSelection, historyBefore, search.historyBefore]);
86
+
87
+ const events = useInsightsEventsQuery(
50
88
  {
51
- query,
89
+ beforeReceivedAtMs: eventsBefore,
90
+ cursor: search.eventsCursor,
91
+ limit: EVENT_LIMIT,
92
+ },
93
+ !hasLookup,
94
+ );
95
+ const matches = useInsightsInstallationsQuery(
96
+ {
97
+ cursor: search.searchCursor,
98
+ identity: query,
52
99
  limit: SEARCH_LIMIT,
53
- offset: search.searchOffset,
54
100
  },
55
- analyticsQueriesEnabled,
101
+ hasSearchQuery,
56
102
  );
57
- const selectedInstallId = search.installId ?? "";
58
- const firstMatchingInstallId = results?.data[0]?.installId;
59
103
 
60
104
  useEffect(() => {
61
- if (selectedInstallId || !firstMatchingInstallId) return;
62
- void navigate({
63
- to: "/installations",
64
- search: {
65
- query: search.query,
66
- installId: firstMatchingInstallId,
67
- searchOffset: search.searchOffset,
68
- historyOffset: 0,
105
+ const firstInstallId = matches.data?.data[0]?.installId;
106
+ if (!hasSearchQuery || hasSelection || firstInstallId === undefined) return;
107
+ setHistoryBack([]);
108
+ updateSearch(
109
+ {
110
+ historyBefore: freshBefore(),
111
+ historyCursor: undefined,
112
+ installId: firstInstallId,
69
113
  },
70
- replace: true,
71
- });
72
- }, [
73
- firstMatchingInstallId,
74
- navigate,
75
- search.query,
76
- search.searchOffset,
77
- selectedInstallId,
78
- ]);
114
+ true,
115
+ );
116
+ }, [hasSearchQuery, hasSelection, matches.data?.data]);
79
117
 
80
- const {
81
- data: history,
82
- error: historyError,
83
- isLoading: isHistoryLoading,
84
- } = useInstallationHistoryQuery(
118
+ const selectedInstallation = useInsightsInstallationQuery(
119
+ search.installId ?? "",
120
+ hasSelection,
121
+ );
122
+ const history = useInsightsInstallationEventsQuery(
85
123
  {
86
- installId: selectedInstallId,
87
- limit: HISTORY_LIMIT,
88
- offset: search.historyOffset,
124
+ beforeReceivedAtMs: historyBefore,
125
+ cursor: search.historyCursor,
126
+ installId: search.installId ?? "",
127
+ limit: EVENT_LIMIT,
89
128
  },
90
- analyticsQueriesEnabled,
129
+ hasSelection,
91
130
  );
92
- const selectedInstallation = useMemo(
93
- () =>
94
- results?.data.find(
95
- (event: InstallationSearchRow) => event.installId === selectedInstallId,
96
- ) ?? null,
97
- [results?.data, selectedInstallId],
98
- );
99
- const selectedEvent = selectedInstallation ?? history?.data[0];
131
+ const selectedEvent =
132
+ selectedInstallation.data ??
133
+ matches.data?.data.find(
134
+ ({ installId }) => installId === search.installId,
135
+ ) ??
136
+ history.data?.data[0];
100
137
 
101
- const updateSearch = (
102
- nextSearch: {
103
- query?: string;
104
- installId?: string;
105
- searchOffset?: number;
106
- historyOffset?: number;
107
- },
108
- replace = false,
109
- ) => {
110
- void navigate({
111
- to: "/installations",
112
- search: {
113
- query: nextSearch.query,
114
- installId: nextSearch.installId,
115
- searchOffset: nextSearch.searchOffset ?? 0,
116
- historyOffset: nextSearch.historyOffset ?? 0,
117
- },
118
- replace,
138
+ const scrollRestorationId = hasLookup
139
+ ? `installation-history-${search.historyCursor ?? "first"}`
140
+ : `all-events-${search.eventsCursor ?? "first"}`;
141
+ const scrollRef = useRef<HTMLDivElement>(null);
142
+ const scrollEntry = useElementScrollRestoration({
143
+ id: scrollRestorationId,
144
+ getKey: getInsightsScrollRestorationKey,
145
+ });
146
+ useLayoutEffect(() => {
147
+ if (!hasLookup && !events.isLoading && scrollRef.current) {
148
+ scrollRef.current.scrollTop = scrollEntry?.scrollY ?? 0;
149
+ }
150
+ }, [events.isLoading, hasLookup, scrollEntry?.scrollY, scrollRestorationId]);
151
+
152
+ const clearLookup = () => {
153
+ setDraftQuery("");
154
+ setHistoryBack([]);
155
+ setSearchBack([]);
156
+ updateSearch({
157
+ historyBefore: undefined,
158
+ historyCursor: undefined,
159
+ installId: undefined,
160
+ query: undefined,
161
+ searchCursor: undefined,
119
162
  });
120
163
  };
121
-
122
- const hasQuery = query.length > 0 || selectedInstallId.length > 0;
164
+ const installationLookup = (
165
+ <InstallationSearchPanel
166
+ draftQuery={draftQuery}
167
+ onClear={clearLookup}
168
+ onDraftQueryChange={setDraftQuery}
169
+ onSubmit={() => {
170
+ const nextQuery = draftQuery.trim();
171
+ if (!nextQuery) {
172
+ clearLookup();
173
+ return;
174
+ }
175
+ setHistoryBack([]);
176
+ setSearchBack([]);
177
+ updateSearch({
178
+ historyBefore: undefined,
179
+ historyCursor: undefined,
180
+ installId: undefined,
181
+ query: nextQuery,
182
+ searchCursor: undefined,
183
+ });
184
+ }}
185
+ />
186
+ );
123
187
 
124
188
  return (
125
189
  <div className="flex h-svh min-h-0 flex-col">
126
- <InstallationPageHeader />
127
- <div className="min-h-0 min-w-0 flex-1 overflow-y-auto bg-muted/5 p-3 sm:p-6">
128
- <div className="mx-auto flex w-full max-w-7xl flex-col gap-6">
129
- <InstallationSearchPanel
130
- draftQuery={draftQuery}
131
- hasQuery={hasQuery}
132
- onClear={() => {
133
- setDraftQuery("");
134
- updateSearch({ query: undefined, installId: undefined });
135
- }}
136
- onDraftQueryChange={setDraftQuery}
137
- onSubmit={() => {
138
- const nextQuery = draftQuery.trim();
139
- updateSearch({
140
- query: nextQuery || undefined,
141
- installId: undefined,
142
- });
143
- }}
144
- />
145
- {!hasQuery ? null : isSearchLoading ? (
190
+ <InsightsPageHeader view="events" />
191
+ <div
192
+ key={scrollRestorationId}
193
+ ref={scrollRef}
194
+ data-scroll-restoration-id={scrollRestorationId}
195
+ id="insights-events-scroll"
196
+ className="min-h-0 min-w-0 flex-1 overflow-y-auto bg-muted/5 p-3 sm:p-6"
197
+ >
198
+ <div className="mx-auto flex w-full max-w-7xl flex-col gap-4 sm:gap-6">
199
+ {hasLookup ? (
200
+ <Card className="shadow-sm">
201
+ <CardContent className="flex flex-col gap-3 p-4 sm:p-6">
202
+ <Button
203
+ className="-ml-2 h-11 self-start lg:h-8"
204
+ onClick={clearLookup}
205
+ variant="ghost"
206
+ >
207
+ <ArrowLeft aria-hidden="true" data-icon="inline-start" />
208
+ Back to all events
209
+ </Button>
210
+ {installationLookup}
211
+ </CardContent>
212
+ </Card>
213
+ ) : null}
214
+ {!hasLookup ? (
215
+ <EventHistoryCard
216
+ error={events.error}
217
+ eventsLocation={{
218
+ eventsBefore,
219
+ eventsCursor: search.eventsCursor,
220
+ }}
221
+ history={events.data}
222
+ isFetching={events.isFetching}
223
+ isLoading={events.isLoading}
224
+ onNext={() => {
225
+ if (!events.data?.nextCursor) return;
226
+ setEventsBack(pushCursor(eventsBack, search.eventsCursor));
227
+ updateSearch(
228
+ {
229
+ eventsCursor: events.data.nextCursor,
230
+ },
231
+ true,
232
+ );
233
+ }}
234
+ onPrevious={() => {
235
+ const previous = popCursor(eventsBack);
236
+ setEventsBack(previous.stack);
237
+ updateSearch(
238
+ {
239
+ eventsCursor: previous.cursor,
240
+ },
241
+ true,
242
+ );
243
+ }}
244
+ onRefresh={() => {
245
+ setEventsBack([]);
246
+ updateSearch(
247
+ {
248
+ eventsBefore: freshBefore(),
249
+ eventsCursor: undefined,
250
+ },
251
+ true,
252
+ );
253
+ }}
254
+ pageNumber={eventsBack.length + 1}
255
+ >
256
+ {installationLookup}
257
+ </EventHistoryCard>
258
+ ) : matches.isLoading && hasSearchQuery ? (
146
259
  <InstallationResultsSkeleton />
147
260
  ) : (
148
- <div className="grid min-h-0 min-w-0 items-stretch gap-6 lg:min-h-96 lg:grid-cols-[minmax(18rem,20rem)_minmax(0,1fr)]">
149
- <InstallationMatchesCard
150
- error={searchError}
151
- limit={SEARCH_LIMIT}
152
- offset={search.searchOffset}
153
- results={results}
154
- selectedInstallId={selectedInstallId}
155
- onOffsetChange={(searchOffset) =>
156
- updateSearch({
157
- query: search.query,
158
- installId: undefined,
159
- searchOffset,
160
- historyOffset: 0,
161
- })
162
- }
163
- onSelect={(installId) =>
164
- updateSearch({
165
- query: search.query,
166
- installId,
167
- searchOffset: search.searchOffset,
168
- })
169
- }
170
- />
261
+ <div
262
+ className={`grid min-h-0 min-w-0 items-stretch gap-4 sm:gap-6 ${hasSearchQuery ? "lg:min-h-96 lg:grid-cols-[minmax(18rem,20rem)_minmax(0,1fr)]" : "lg:grid-cols-1"}`}
263
+ >
264
+ {hasSearchQuery ? (
265
+ <InstallationMatchesCard
266
+ error={matches.error}
267
+ onNext={() => {
268
+ if (!matches.data?.nextCursor) return;
269
+ setSearchBack(pushCursor(searchBack, search.searchCursor));
270
+ updateSearch(
271
+ {
272
+ installId: undefined,
273
+ searchCursor: matches.data.nextCursor,
274
+ },
275
+ true,
276
+ );
277
+ }}
278
+ onPrevious={() => {
279
+ const previous = popCursor(searchBack);
280
+ setSearchBack(previous.stack);
281
+ updateSearch(
282
+ {
283
+ installId: undefined,
284
+ searchCursor: previous.cursor,
285
+ },
286
+ true,
287
+ );
288
+ }}
289
+ onSelect={(installId) => {
290
+ setHistoryBack([]);
291
+ updateSearch({
292
+ historyBefore: freshBefore(),
293
+ historyCursor: undefined,
294
+ installId,
295
+ });
296
+ }}
297
+ pageNumber={searchBack.length + 1}
298
+ results={matches.data}
299
+ selectedInstallId={search.installId}
300
+ />
301
+ ) : null}
171
302
  <InstallationHistoryCard
172
- error={historyError}
173
- history={history}
174
- isLoading={isHistoryLoading}
175
- limit={HISTORY_LIMIT}
176
- offset={search.historyOffset}
303
+ error={history.error ?? selectedInstallation.error}
304
+ history={history.data}
305
+ isLoading={history.isLoading || selectedInstallation.isLoading}
306
+ onNext={() => {
307
+ if (!history.data?.nextCursor) return;
308
+ setHistoryBack(pushCursor(historyBack, search.historyCursor));
309
+ updateSearch(
310
+ {
311
+ historyCursor: history.data.nextCursor,
312
+ },
313
+ true,
314
+ );
315
+ }}
316
+ onPrevious={() => {
317
+ const previous = popCursor(historyBack);
318
+ setHistoryBack(previous.stack);
319
+ updateSearch(
320
+ {
321
+ historyCursor: previous.cursor,
322
+ },
323
+ true,
324
+ );
325
+ }}
326
+ onRefresh={() => {
327
+ setHistoryBack([]);
328
+ updateSearch(
329
+ {
330
+ historyBefore: freshBefore(),
331
+ historyCursor: undefined,
332
+ },
333
+ true,
334
+ );
335
+ }}
336
+ pageNumber={historyBack.length + 1}
177
337
  selectedEvent={selectedEvent}
178
- selectedInstallId={selectedInstallId}
179
- onOffsetChange={(historyOffset) =>
180
- updateSearch({
181
- query: search.query,
182
- installId: selectedInstallId,
183
- searchOffset: search.searchOffset,
184
- historyOffset,
185
- })
186
- }
338
+ selectedInstallId={search.installId}
187
339
  />
188
340
  </div>
189
341
  )}
package/src/styles.css CHANGED
@@ -38,6 +38,8 @@ code {
38
38
  --accent: oklch(0.646 0.222 41.116);
39
39
  --accent-foreground: oklch(0.98 0.016 73.684);
40
40
  --destructive: oklch(0.577 0.245 27.325);
41
+ --success: oklch(0.448 0.119 151.328);
42
+ --warning: oklch(0.473 0.137 46.201);
41
43
  --border: oklch(0.923 0.003 48.717);
42
44
  --input: oklch(0.923 0.003 48.717);
43
45
  --ring: oklch(0.709 0.01 56.259);
@@ -74,6 +76,8 @@ code {
74
76
  --accent: oklch(0.705 0.213 47.604);
75
77
  --accent-foreground: oklch(0.98 0.016 73.684);
76
78
  --destructive: oklch(0.704 0.191 22.216);
79
+ --success: oklch(0.792 0.209 151.711);
80
+ --warning: oklch(0.828 0.189 84.429);
77
81
  --border: oklch(1 0 0 / 10%);
78
82
  --input: oklch(1 0 0 / 15%);
79
83
  --ring: oklch(0.553 0.013 58.071);
@@ -111,6 +115,8 @@ code {
111
115
  --color-input: var(--input);
112
116
  --color-border: var(--border);
113
117
  --color-destructive: var(--destructive);
118
+ --color-success: var(--success);
119
+ --color-warning: var(--warning);
114
120
  --color-accent-foreground: var(--accent-foreground);
115
121
  --color-accent: var(--accent);
116
122
  --color-muted-foreground: var(--muted-foreground);
@@ -1 +0,0 @@
1
- import{Lr as e,Pr as t,ir as n}from"./createServerFn-gM2-LFBy.js";var r=e(t(),1),i=r.use,a=typeof window<`u`?r.useLayoutEffect:r.useEffect;function o(e){let t=r.useRef({value:e,prev:null}),n=t.current.value;return e!==n&&(t.current={value:e,prev:n}),t.current.prev}function s(e,t,n={},i={}){r.useEffect(()=>{if(!e.current||i.disabled||typeof IntersectionObserver!=`function`)return;let r=new IntersectionObserver(([e])=>{t(e)},n);return r.observe(e.current),()=>{r.disconnect()}},[t,n,i.disabled,e])}function c(e){let t=r.useRef(null);return r.useImperativeHandle(e,()=>t.current,[]),t}var l=r.createContext(null);function u(e){return r.useContext(l)}function d(e){let t=u();return r.useCallback(n=>t.navigate({...n,from:n.from??e?.from}),[e?.from,t])}var f=n(),p=(0,r.createContext)({status:`unresolved`});function m({children:e,value:t}){return(0,f.jsx)(p.Provider,{value:t,children:e})}var h=()=>(0,r.useContext)(p);export{l as a,s as c,u as i,a as l,h as n,i as o,d as r,c as s,m as t,o as u};