@camstack/ui-library 1.2.303 → 1.2.305
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/dist/composites/grouped-tile-picker.d.ts +31 -0
- package/dist/composites/index.d.ts +1 -0
- package/dist/composites/log-history-paging.d.ts +62 -0
- package/dist/composites/log-stream.d.ts +6 -10
- package/dist/composites/scope-picker.d.ts +1 -1
- package/dist/generated/system-hooks.d.ts +4 -10
- package/dist/hooks/use-log-history.d.ts +26 -0
- package/dist/index.cjs +367 -92
- package/dist/index.js +366 -89
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -10754,6 +10754,8 @@ var useDeviceHistoryListNodeSeries = trpc.deviceHistory.listNodeSeries.useQuery;
|
|
|
10754
10754
|
var useDeviceHistoryQuery = trpc.deviceHistory.query.useQuery;
|
|
10755
10755
|
/** Generated alias around `trpc.deviceHistory.queryNode.useQuery`. */
|
|
10756
10756
|
var useDeviceHistoryQueryNode = trpc.deviceHistory.queryNode.useQuery;
|
|
10757
|
+
/** Generated alias around `trpc.deviceHistory.queryMany.useQuery`. */
|
|
10758
|
+
var useDeviceHistoryQueryMany = trpc.deviceHistory.queryMany.useQuery;
|
|
10757
10759
|
/** Generated alias around `trpc.deviceHistory.declareSeries.useMutation`. */
|
|
10758
10760
|
var useDeviceHistoryDeclareSeries = trpc.deviceHistory.declareSeries.useMutation;
|
|
10759
10761
|
/** Generated alias around `trpc.deviceHistory.record.useMutation`. */
|
|
@@ -11202,8 +11204,8 @@ var useLlmMigrateRawSecrets = trpc.llm.migrateRawSecrets.useMutation;
|
|
|
11202
11204
|
var useLlmDeleteProfile = trpc.llm.deleteProfile.useMutation;
|
|
11203
11205
|
/** Generated alias around `trpc.llm.testProfile.useMutation`. */
|
|
11204
11206
|
var useLlmTestProfile = trpc.llm.testProfile.useMutation;
|
|
11205
|
-
/** Generated alias around `trpc.llm.listModels.
|
|
11206
|
-
var useLlmListModels = trpc.llm.listModels.
|
|
11207
|
+
/** Generated alias around `trpc.llm.listModels.useMutation`. */
|
|
11208
|
+
var useLlmListModels = trpc.llm.listModels.useMutation;
|
|
11207
11209
|
/** Generated alias around `trpc.llm.getDefaults.useQuery`. */
|
|
11208
11210
|
var useLlmGetDefaults = trpc.llm.getDefaults.useQuery;
|
|
11209
11211
|
/** Generated alias around `trpc.llm.setDefault.useMutation`. */
|
|
@@ -12584,14 +12586,6 @@ var useUserManagementValidateCredentials = trpc.userManagement.validateCredentia
|
|
|
12584
12586
|
var useUserManagementListApiKeys = trpc.userManagement.listApiKeys.useQuery;
|
|
12585
12587
|
/** Generated alias around `trpc.userManagement.revokeApiKey.useMutation`. */
|
|
12586
12588
|
var useUserManagementRevokeApiKey = trpc.userManagement.revokeApiKey.useMutation;
|
|
12587
|
-
/** Generated alias around `trpc.userManagement.createScopedToken.useMutation`. */
|
|
12588
|
-
var useUserManagementCreateScopedToken = trpc.userManagement.createScopedToken.useMutation;
|
|
12589
|
-
/** Generated alias around `trpc.userManagement.revokeScopedToken.useMutation`. */
|
|
12590
|
-
var useUserManagementRevokeScopedToken = trpc.userManagement.revokeScopedToken.useMutation;
|
|
12591
|
-
/** Generated alias around `trpc.userManagement.validateScopedToken.useQuery`. */
|
|
12592
|
-
var useUserManagementValidateScopedToken = trpc.userManagement.validateScopedToken.useQuery;
|
|
12593
|
-
/** Generated alias around `trpc.userManagement.listScopedTokens.useQuery`. */
|
|
12594
|
-
var useUserManagementListScopedTokens = trpc.userManagement.listScopedTokens.useQuery;
|
|
12595
12589
|
/** Generated alias around `trpc.userManagement.setupTotp.useMutation`. */
|
|
12596
12590
|
var useUserManagementSetupTotp = trpc.userManagement.setupTotp.useMutation;
|
|
12597
12591
|
/** Generated alias around `trpc.userManagement.confirmTotp.useMutation`. */
|
|
@@ -39840,6 +39834,202 @@ function useLiveBuffer(max) {
|
|
|
39840
39834
|
reset
|
|
39841
39835
|
]);
|
|
39842
39836
|
}
|
|
39837
|
+
//#endregion
|
|
39838
|
+
//#region src/hooks/use-debounced-string.ts
|
|
39839
|
+
/**
|
|
39840
|
+
* useDebouncedString — return `value` after `delay` ms have passed
|
|
39841
|
+
* without further changes. Resets the timer on every change.
|
|
39842
|
+
*
|
|
39843
|
+
* Useful for search inputs that should NOT push to URL state on every
|
|
39844
|
+
* keystroke. The DOM input stays controlled by an external `value`
|
|
39845
|
+
* (caller's `useState`); this hook produces a debounced derivation.
|
|
39846
|
+
*/
|
|
39847
|
+
function useDebouncedString(value, delayMs) {
|
|
39848
|
+
const [debounced, setDebounced] = (0, react$1.useState)(value);
|
|
39849
|
+
(0, react$1.useEffect)(() => {
|
|
39850
|
+
const t = setTimeout(() => setDebounced(value), delayMs);
|
|
39851
|
+
return () => clearTimeout(t);
|
|
39852
|
+
}, [value, delayMs]);
|
|
39853
|
+
return debounced;
|
|
39854
|
+
}
|
|
39855
|
+
//#endregion
|
|
39856
|
+
//#region src/composites/log-history-paging.ts
|
|
39857
|
+
/**
|
|
39858
|
+
* Accumulate slices until `limit` entries, `done`, or an abort. An abort keeps
|
|
39859
|
+
* what was already collected and the cursor reached, so "Load older" resumes
|
|
39860
|
+
* where the scan stopped instead of starting again.
|
|
39861
|
+
*/
|
|
39862
|
+
async function collectPage(options) {
|
|
39863
|
+
const { fetchSlice, limit, signal, onProgress } = options;
|
|
39864
|
+
const entries = [];
|
|
39865
|
+
let cursor = options.before;
|
|
39866
|
+
let scannedBytes = 0;
|
|
39867
|
+
let source;
|
|
39868
|
+
for (;;) {
|
|
39869
|
+
if (signal.aborted) return {
|
|
39870
|
+
entries,
|
|
39871
|
+
nextBefore: cursor ?? null,
|
|
39872
|
+
done: false,
|
|
39873
|
+
cancelled: true,
|
|
39874
|
+
scannedBytes,
|
|
39875
|
+
source
|
|
39876
|
+
};
|
|
39877
|
+
let page;
|
|
39878
|
+
try {
|
|
39879
|
+
page = await fetchSlice(cursor, Math.max(1, limit - entries.length), signal);
|
|
39880
|
+
} catch (error) {
|
|
39881
|
+
if (signal.aborted) return {
|
|
39882
|
+
entries,
|
|
39883
|
+
nextBefore: cursor ?? null,
|
|
39884
|
+
done: false,
|
|
39885
|
+
cancelled: true,
|
|
39886
|
+
scannedBytes,
|
|
39887
|
+
source
|
|
39888
|
+
};
|
|
39889
|
+
throw error;
|
|
39890
|
+
}
|
|
39891
|
+
entries.push(...page.entries);
|
|
39892
|
+
scannedBytes += page.scannedBytes;
|
|
39893
|
+
source = page.source;
|
|
39894
|
+
onProgress?.(scannedBytes);
|
|
39895
|
+
const exhausted = page.done || page.nextBefore === null;
|
|
39896
|
+
if (page.cancelled === true) return {
|
|
39897
|
+
entries,
|
|
39898
|
+
nextBefore: page.nextBefore,
|
|
39899
|
+
done: exhausted,
|
|
39900
|
+
cancelled: true,
|
|
39901
|
+
scannedBytes,
|
|
39902
|
+
source
|
|
39903
|
+
};
|
|
39904
|
+
if (exhausted || entries.length >= limit) return {
|
|
39905
|
+
entries,
|
|
39906
|
+
nextBefore: page.nextBefore,
|
|
39907
|
+
done: exhausted,
|
|
39908
|
+
cancelled: false,
|
|
39909
|
+
scannedBytes,
|
|
39910
|
+
source
|
|
39911
|
+
};
|
|
39912
|
+
cursor = page.nextBefore ?? void 0;
|
|
39913
|
+
}
|
|
39914
|
+
}
|
|
39915
|
+
/** Stable identity of an entry: the server id, else the legacy composite key. */
|
|
39916
|
+
function logEntryKey(entry) {
|
|
39917
|
+
if (entry.id !== void 0 && entry.id !== "") return `id:${entry.id}`;
|
|
39918
|
+
return `${entry.timestamp}|${entry.level}|${entry.scope ?? ""}|${entry.message}`;
|
|
39919
|
+
}
|
|
39920
|
+
/**
|
|
39921
|
+
* History (newest first, as paged) merged with live entries (arrival order),
|
|
39922
|
+
* deduped by key — a live entry wins (richer tags/meta) — and returned
|
|
39923
|
+
* chronologically, newest LAST. Ties keep insertion order.
|
|
39924
|
+
*/
|
|
39925
|
+
function mergeLogEntries(history, live) {
|
|
39926
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
39927
|
+
for (const entry of history) byKey.set(logEntryKey(entry), entry);
|
|
39928
|
+
for (const entry of live) byKey.set(logEntryKey(entry), entry);
|
|
39929
|
+
return [...byKey.values()].toSorted((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
39930
|
+
}
|
|
39931
|
+
//#endregion
|
|
39932
|
+
//#region src/hooks/use-log-history.ts
|
|
39933
|
+
/**
|
|
39934
|
+
* useLogHistory — pages the server-served log history (D898).
|
|
39935
|
+
*
|
|
39936
|
+
* The first page and every older page go through `collectPage`, which keeps
|
|
39937
|
+
* calling `logs.query` with `before: nextBefore` while the server answers a
|
|
39938
|
+
* short, not-done slice. Changing the filters aborts whatever is in flight and
|
|
39939
|
+
* restarts from the first page; `cancel()` aborts the scan but keeps what was
|
|
39940
|
+
* found and the cursor reached, so "Load older" resumes from there.
|
|
39941
|
+
*/
|
|
39942
|
+
var INITIAL = {
|
|
39943
|
+
entries: [],
|
|
39944
|
+
nextBefore: null,
|
|
39945
|
+
done: false,
|
|
39946
|
+
status: "loading",
|
|
39947
|
+
scannedBytes: 0,
|
|
39948
|
+
source: void 0,
|
|
39949
|
+
error: null
|
|
39950
|
+
};
|
|
39951
|
+
function useLogHistory(query, limit) {
|
|
39952
|
+
const utils = trpc.useUtils();
|
|
39953
|
+
const [state, setState] = (0, react$1.useState)(INITIAL);
|
|
39954
|
+
const stateRef = (0, react$1.useRef)(state);
|
|
39955
|
+
stateRef.current = state;
|
|
39956
|
+
const queryRef = (0, react$1.useRef)(query);
|
|
39957
|
+
queryRef.current = query;
|
|
39958
|
+
const abortRef = (0, react$1.useRef)(null);
|
|
39959
|
+
const queryKey = JSON.stringify(query);
|
|
39960
|
+
const run = (0, react$1.useCallback)(async (before, reset) => {
|
|
39961
|
+
abortRef.current?.abort();
|
|
39962
|
+
const controller = new AbortController();
|
|
39963
|
+
abortRef.current = controller;
|
|
39964
|
+
setState((prev) => ({
|
|
39965
|
+
...reset ? INITIAL : prev,
|
|
39966
|
+
status: "loading",
|
|
39967
|
+
scannedBytes: 0,
|
|
39968
|
+
error: null
|
|
39969
|
+
}));
|
|
39970
|
+
const fetchSlice = (cursor, remaining, signal) => utils.client.logs.query.query({
|
|
39971
|
+
...queryRef.current,
|
|
39972
|
+
limit: remaining,
|
|
39973
|
+
...cursor !== void 0 ? { before: cursor } : {}
|
|
39974
|
+
}, { signal });
|
|
39975
|
+
try {
|
|
39976
|
+
const out = await collectPage({
|
|
39977
|
+
fetchSlice,
|
|
39978
|
+
limit,
|
|
39979
|
+
...before !== void 0 ? { before } : {},
|
|
39980
|
+
signal: controller.signal,
|
|
39981
|
+
onProgress: (scannedBytes) => {
|
|
39982
|
+
if (abortRef.current === controller) setState((prev) => ({
|
|
39983
|
+
...prev,
|
|
39984
|
+
scannedBytes
|
|
39985
|
+
}));
|
|
39986
|
+
}
|
|
39987
|
+
});
|
|
39988
|
+
if (abortRef.current !== controller) return;
|
|
39989
|
+
setState((prev) => ({
|
|
39990
|
+
entries: reset ? out.entries : [...prev.entries, ...out.entries],
|
|
39991
|
+
nextBefore: out.nextBefore,
|
|
39992
|
+
done: out.done,
|
|
39993
|
+
status: "idle",
|
|
39994
|
+
scannedBytes: out.scannedBytes,
|
|
39995
|
+
source: out.source ?? prev.source,
|
|
39996
|
+
error: null
|
|
39997
|
+
}));
|
|
39998
|
+
} catch (error) {
|
|
39999
|
+
if (abortRef.current !== controller) return;
|
|
40000
|
+
setState((prev) => ({
|
|
40001
|
+
...prev,
|
|
40002
|
+
status: "idle",
|
|
40003
|
+
error: error instanceof Error ? error.message : String(error)
|
|
40004
|
+
}));
|
|
40005
|
+
}
|
|
40006
|
+
}, [utils, limit]);
|
|
40007
|
+
(0, react$1.useEffect)(() => {
|
|
40008
|
+
run(void 0, true);
|
|
40009
|
+
return () => {
|
|
40010
|
+
abortRef.current?.abort();
|
|
40011
|
+
abortRef.current = null;
|
|
40012
|
+
};
|
|
40013
|
+
}, [run, queryKey]);
|
|
40014
|
+
const loadOlder = (0, react$1.useCallback)(() => {
|
|
40015
|
+
const current = stateRef.current;
|
|
40016
|
+
if (current.status === "loading" || current.done) return;
|
|
40017
|
+
run(current.nextBefore ?? void 0, false);
|
|
40018
|
+
}, [run]);
|
|
40019
|
+
const cancel = (0, react$1.useCallback)(() => {
|
|
40020
|
+
abortRef.current?.abort();
|
|
40021
|
+
}, []);
|
|
40022
|
+
return {
|
|
40023
|
+
entries: state.entries,
|
|
40024
|
+
status: state.status,
|
|
40025
|
+
done: state.done,
|
|
40026
|
+
scannedBytes: state.scannedBytes,
|
|
40027
|
+
source: state.source,
|
|
40028
|
+
error: state.error,
|
|
40029
|
+
loadOlder,
|
|
40030
|
+
cancel
|
|
40031
|
+
};
|
|
40032
|
+
}
|
|
39843
40033
|
/**
|
|
39844
40034
|
* True when the viewport is scrolled to (or within `threshold` px of) the
|
|
39845
40035
|
* bottom of its content.
|
|
@@ -39896,12 +40086,6 @@ var LEVEL_STYLES = {
|
|
|
39896
40086
|
color: "text-gray-500"
|
|
39897
40087
|
}
|
|
39898
40088
|
};
|
|
39899
|
-
var LEVEL_SEVERITY = {
|
|
39900
|
-
debug: 0,
|
|
39901
|
-
info: 1,
|
|
39902
|
-
warn: 2,
|
|
39903
|
-
error: 3
|
|
39904
|
-
};
|
|
39905
40089
|
var LEVEL_OPTIONS = [
|
|
39906
40090
|
"debug",
|
|
39907
40091
|
"info",
|
|
@@ -39910,15 +40094,18 @@ var LEVEL_OPTIONS = [
|
|
|
39910
40094
|
];
|
|
39911
40095
|
/** Max live entries kept in the ring buffer. */
|
|
39912
40096
|
var MAX_LIVE_ENTRIES$1 = 500;
|
|
39913
|
-
|
|
39914
|
-
|
|
39915
|
-
|
|
39916
|
-
|
|
39917
|
-
|
|
40097
|
+
/** Text filter debounce before it goes to the server. */
|
|
40098
|
+
var TEXT_DEBOUNCE_MS = 300;
|
|
40099
|
+
/** Distance from the top (px) that asks for the next older page. */
|
|
40100
|
+
var TOP_LOAD_THRESHOLD_PX = 24;
|
|
40101
|
+
var BYTES_PER_MB = 1024 * 1024;
|
|
40102
|
+
function LogStream({ agentId: propsAgentId, addonId: propsAddonId, deviceId: propsDeviceId, containerDeviceId: propsContainerDeviceId, integrationId: propsIntegrationId, requestId: propsRequestId, level: initialLevel, maxHeight = "max-h-96", showScope = false, showDeviceChip = false, showFilters = true, showSourceSelect, limit = 100, liveBuffer: externalBuffer, onClose, className }) {
|
|
39918
40103
|
const t = useUiText();
|
|
40104
|
+
const sourceSelectOn = showSourceSelect ?? showFilters;
|
|
39919
40105
|
const [localAddonId, setLocalAddonId] = (0, react$1.useState)("");
|
|
39920
40106
|
const [localDeviceId, setLocalDeviceId] = (0, react$1.useState)("");
|
|
39921
40107
|
const [levelFilter, setLevelFilter] = (0, react$1.useState)(initialLevel ?? "info");
|
|
40108
|
+
const [sourceChoice, setSourceChoice] = (0, react$1.useState)(void 0);
|
|
39922
40109
|
const agentId = propsAgentId;
|
|
39923
40110
|
const addonId = propsAddonId ?? (localAddonId || void 0);
|
|
39924
40111
|
const deviceId = propsDeviceId ?? (localDeviceId ? Number(localDeviceId) : void 0);
|
|
@@ -39963,48 +40150,63 @@ function LogStream({ agentId: propsAgentId, addonId: propsAddonId, deviceId: pro
|
|
|
39963
40150
|
setExpandAll(false);
|
|
39964
40151
|
}
|
|
39965
40152
|
}, [scopeKey, buffer]);
|
|
39966
|
-
const
|
|
39967
|
-
|
|
39968
|
-
|
|
39969
|
-
|
|
39970
|
-
|
|
40153
|
+
const debouncedText = useDebouncedString(searchText, TEXT_DEBOUNCE_MS).trim();
|
|
40154
|
+
const sourcesQuery = trpc.logs.sources.useQuery(void 0, { staleTime: 6e4 });
|
|
40155
|
+
const readableSources = (0, react$1.useMemo)(() => (sourcesQuery.data ?? []).filter((source) => source.readable), [sourcesQuery.data]);
|
|
40156
|
+
const history = useLogHistory((0, react$1.useMemo)(() => ({
|
|
40157
|
+
...sourceChoice ? { source: sourceChoice } : {},
|
|
40158
|
+
level: levelFilter,
|
|
40159
|
+
...debouncedText ? { text: debouncedText } : {},
|
|
40160
|
+
...tags ? { tags } : {}
|
|
40161
|
+
}), [
|
|
40162
|
+
sourceChoice,
|
|
40163
|
+
levelFilter,
|
|
40164
|
+
debouncedText,
|
|
40165
|
+
tags
|
|
40166
|
+
]), limit);
|
|
40167
|
+
trpc.logs.subscribe.useSubscription({
|
|
40168
|
+
level: levelFilter,
|
|
40169
|
+
...debouncedText ? { text: debouncedText } : {},
|
|
40170
|
+
...tags ? { tags } : {}
|
|
40171
|
+
}, { onData: (entry) => {
|
|
39971
40172
|
buffer.append(entry);
|
|
39972
40173
|
} });
|
|
40174
|
+
const filterKey = `${sourceChoice ?? ""}|${levelFilter}|${debouncedText}`;
|
|
40175
|
+
const prevFilterRef = (0, react$1.useRef)(filterKey);
|
|
40176
|
+
(0, react$1.useEffect)(() => {
|
|
40177
|
+
if (prevFilterRef.current === filterKey) return;
|
|
40178
|
+
prevFilterRef.current = filterKey;
|
|
40179
|
+
buffer.reset();
|
|
40180
|
+
setFollow(true);
|
|
40181
|
+
}, [filterKey, buffer]);
|
|
39973
40182
|
const allLogs = (0, react$1.useMemo)(() => {
|
|
39974
|
-
const
|
|
39975
|
-
|
|
39976
|
-
level: log.level,
|
|
39977
|
-
message: log.message,
|
|
39978
|
-
scope: log.scope,
|
|
39979
|
-
tags: log.tags,
|
|
39980
|
-
meta: log.meta
|
|
39981
|
-
}));
|
|
39982
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
39983
|
-
for (const log of initial) byKey.set(`${log.timestamp}|${log.level}|${log.scope ?? ""}|${log.message}`, log);
|
|
39984
|
-
for (const log of liveLogs) byKey.set(`${log.timestamp}|${log.level}|${log.scope ?? ""}|${log.message}`, log);
|
|
39985
|
-
const merged = [...byKey.values()].toSorted((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
39986
|
-
const levelFiltered = (clearedAt > 0 ? merged.filter((log) => new Date(log.timestamp).getTime() > clearedAt) : merged).filter((log) => passesLevel(log.level, levelFilter));
|
|
39987
|
-
const needle = searchText.trim().toLowerCase();
|
|
39988
|
-
if (!needle) return levelFiltered;
|
|
39989
|
-
return levelFiltered.filter((log) => {
|
|
39990
|
-
if (log.message.toLowerCase().includes(needle)) return true;
|
|
39991
|
-
if (log.scope && log.scope.toLowerCase().includes(needle)) return true;
|
|
39992
|
-
if (log.tags) {
|
|
39993
|
-
for (const v of Object.values(log.tags)) if (v !== void 0 && String(v).toLowerCase().includes(needle)) return true;
|
|
39994
|
-
}
|
|
39995
|
-
if (log.meta) for (const v of Object.values(log.meta)) {
|
|
39996
|
-
if (v === void 0 || v === null) continue;
|
|
39997
|
-
if (formatMetaValue(v).toLowerCase().includes(needle)) return true;
|
|
39998
|
-
}
|
|
39999
|
-
return false;
|
|
40000
|
-
});
|
|
40183
|
+
const merged = mergeLogEntries(history.entries, liveLogs);
|
|
40184
|
+
return clearedAt > 0 ? merged.filter((log) => new Date(log.timestamp).getTime() > clearedAt) : merged;
|
|
40001
40185
|
}, [
|
|
40002
|
-
|
|
40186
|
+
history.entries,
|
|
40003
40187
|
liveLogs,
|
|
40004
|
-
|
|
40005
|
-
clearedAt,
|
|
40006
|
-
searchText
|
|
40188
|
+
clearedAt
|
|
40007
40189
|
]);
|
|
40190
|
+
const prependAnchorRef = (0, react$1.useRef)(null);
|
|
40191
|
+
const requestOlder = (0, react$1.useCallback)(() => {
|
|
40192
|
+
if (history.status === "loading" || history.done) return;
|
|
40193
|
+
const el = scrollRef.current;
|
|
40194
|
+
if (el) prependAnchorRef.current = {
|
|
40195
|
+
height: el.scrollHeight,
|
|
40196
|
+
top: el.scrollTop
|
|
40197
|
+
};
|
|
40198
|
+
history.loadOlder();
|
|
40199
|
+
}, [history]);
|
|
40200
|
+
(0, react$1.useLayoutEffect)(() => {
|
|
40201
|
+
const anchor = prependAnchorRef.current;
|
|
40202
|
+
const el = scrollRef.current;
|
|
40203
|
+
if (!anchor || !el || history.status !== "idle") return;
|
|
40204
|
+
prependAnchorRef.current = null;
|
|
40205
|
+
const next = anchor.top + (el.scrollHeight - anchor.height);
|
|
40206
|
+
if (Math.abs(next - el.scrollTop) < 1) return;
|
|
40207
|
+
programmaticScrollRef.current = true;
|
|
40208
|
+
el.scrollTop = next;
|
|
40209
|
+
}, [history.status, history.entries.length]);
|
|
40008
40210
|
const scrollToBottom = (0, react$1.useCallback)((behavior) => {
|
|
40009
40211
|
const el = scrollRef.current;
|
|
40010
40212
|
if (!el) return;
|
|
@@ -40015,15 +40217,16 @@ function LogStream({ agentId: propsAgentId, addonId: propsAddonId, deviceId: pro
|
|
|
40015
40217
|
});
|
|
40016
40218
|
}, []);
|
|
40017
40219
|
const [hasNewWhilePaused, setHasNewWhilePaused] = (0, react$1.useState)(false);
|
|
40018
|
-
const
|
|
40220
|
+
const lastKey = allLogs.length > 0 ? logEntryKey(allLogs[allLogs.length - 1]) : "";
|
|
40221
|
+
const prevLastKeyRef = (0, react$1.useRef)(lastKey);
|
|
40019
40222
|
(0, react$1.useLayoutEffect)(() => {
|
|
40020
|
-
const
|
|
40021
|
-
|
|
40022
|
-
if (!
|
|
40223
|
+
const newestChanged = lastKey !== "" && lastKey !== prevLastKeyRef.current;
|
|
40224
|
+
prevLastKeyRef.current = lastKey;
|
|
40225
|
+
if (!newestChanged) return;
|
|
40023
40226
|
if (follow) scrollToBottom();
|
|
40024
40227
|
else setHasNewWhilePaused(true);
|
|
40025
40228
|
}, [
|
|
40026
|
-
|
|
40229
|
+
lastKey,
|
|
40027
40230
|
follow,
|
|
40028
40231
|
scrollToBottom
|
|
40029
40232
|
]);
|
|
@@ -40043,8 +40246,9 @@ function LogStream({ agentId: propsAgentId, addonId: propsAddonId, deviceId: pro
|
|
|
40043
40246
|
if (!el) return;
|
|
40044
40247
|
const pinned = isPinnedToBottom(el, 40);
|
|
40045
40248
|
setFollow((prev) => prev === pinned ? prev : pinned);
|
|
40046
|
-
|
|
40047
|
-
|
|
40249
|
+
if (el.scrollTop <= TOP_LOAD_THRESHOLD_PX) requestOlder();
|
|
40250
|
+
}, [requestOlder]);
|
|
40251
|
+
const expandableKeys = (0, react$1.useMemo)(() => allLogs.filter((log) => log.tags && Object.keys(log.tags).length > 0 || log.meta && Object.keys(log.meta).length > 0).map(logEntryKey), [allLogs]);
|
|
40048
40252
|
const toggleExpandAll = (0, react$1.useCallback)(() => {
|
|
40049
40253
|
setExpandAll((prev) => {
|
|
40050
40254
|
if (prev) setExpandedRows(/* @__PURE__ */ new Set());
|
|
@@ -40091,7 +40295,7 @@ function LogStream({ agentId: propsAgentId, addonId: propsAddonId, deviceId: pro
|
|
|
40091
40295
|
setTimeout(() => setCopiedRowKey((prev) => prev === key ? null : prev), 1200);
|
|
40092
40296
|
});
|
|
40093
40297
|
}, []);
|
|
40094
|
-
const rowKey =
|
|
40298
|
+
const rowKey = logEntryKey;
|
|
40095
40299
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
40096
40300
|
className: cn("h-full min-h-0 flex flex-col", className),
|
|
40097
40301
|
onClick: (e) => e.stopPropagation(),
|
|
@@ -40126,6 +40330,23 @@ function LogStream({ agentId: propsAgentId, addonId: propsAddonId, deviceId: pro
|
|
|
40126
40330
|
children: [l, "+"]
|
|
40127
40331
|
}, l))
|
|
40128
40332
|
}),
|
|
40333
|
+
sourceSelectOn && readableSources.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
40334
|
+
className: "w-36",
|
|
40335
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Select, {
|
|
40336
|
+
"aria-label": t("uiLibrary.logStream.source", "Log source"),
|
|
40337
|
+
className: "h-5 text-[10px]",
|
|
40338
|
+
value: sourceChoice ?? readableSources.find((x) => x.isDefault)?.addonId ?? "",
|
|
40339
|
+
onChange: (e) => setSourceChoice(e.target.value),
|
|
40340
|
+
options: readableSources.map((x) => ({
|
|
40341
|
+
value: x.addonId,
|
|
40342
|
+
label: x.addonId
|
|
40343
|
+
}))
|
|
40344
|
+
})
|
|
40345
|
+
}),
|
|
40346
|
+
sourceSelectOn && readableSources.length <= 1 && history.source && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
40347
|
+
className: "text-[9px] text-foreground-subtle",
|
|
40348
|
+
children: t("uiLibrary.logStream.sourceName", "Source: {{name}}", { name: history.source })
|
|
40349
|
+
}),
|
|
40129
40350
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarButton, {
|
|
40130
40351
|
icon: ArrowDownToLine,
|
|
40131
40352
|
label: follow ? t("uiLibrary.logStream.following", "Following") : t("uiLibrary.logStream.paused", "Paused"),
|
|
@@ -40224,11 +40445,16 @@ function LogStream({ agentId: propsAgentId, addonId: propsAddonId, deviceId: pro
|
|
|
40224
40445
|
onScroll: handleScroll,
|
|
40225
40446
|
className: cn("flex-1 min-h-0 overflow-auto font-mono text-[10px]", maxHeight),
|
|
40226
40447
|
children: [
|
|
40227
|
-
|
|
40228
|
-
|
|
40229
|
-
|
|
40448
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(HistoryEdge, {
|
|
40449
|
+
status: history.status,
|
|
40450
|
+
done: history.done,
|
|
40451
|
+
scannedBytes: history.scannedBytes,
|
|
40452
|
+
hasRows: allLogs.length > 0,
|
|
40453
|
+
error: history.error,
|
|
40454
|
+
onLoadOlder: requestOlder,
|
|
40455
|
+
onCancel: history.cancel
|
|
40230
40456
|
}),
|
|
40231
|
-
|
|
40457
|
+
history.status === "idle" && history.done && allLogs.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
40232
40458
|
className: "py-4 text-center text-foreground-subtle",
|
|
40233
40459
|
children: t("uiLibrary.logStream.noLogs", "No logs found")
|
|
40234
40460
|
}),
|
|
@@ -40352,6 +40578,49 @@ function DeviceChip({ deviceId, deviceName }) {
|
|
|
40352
40578
|
children: label
|
|
40353
40579
|
});
|
|
40354
40580
|
}
|
|
40581
|
+
/**
|
|
40582
|
+
* The top of the list: the state of the older-history scan. A scan that is
|
|
40583
|
+
* still looking (short page, source not done) says how much it has read and
|
|
40584
|
+
* can be cancelled; the end of the source is stated quietly.
|
|
40585
|
+
*/
|
|
40586
|
+
function HistoryEdge({ status, done, scannedBytes, hasRows, error, onLoadOlder, onCancel }) {
|
|
40587
|
+
const t = useUiText();
|
|
40588
|
+
if (status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
40589
|
+
className: "flex items-center justify-center gap-2 py-2 text-foreground-subtle",
|
|
40590
|
+
children: [
|
|
40591
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }),
|
|
40592
|
+
scannedBytes > 0 ? t("uiLibrary.logStream.searching", "Searching older logs… ({{mb}} MB scanned)", { mb: (scannedBytes / BYTES_PER_MB).toFixed(1) }) : t("uiLibrary.logStream.loading", "Loading logs..."),
|
|
40593
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
40594
|
+
variant: "outline",
|
|
40595
|
+
size: "sm",
|
|
40596
|
+
onClick: onCancel,
|
|
40597
|
+
children: t("uiLibrary.logStream.cancel", "Cancel")
|
|
40598
|
+
})
|
|
40599
|
+
]
|
|
40600
|
+
});
|
|
40601
|
+
if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
40602
|
+
className: "flex items-center justify-center gap-2 py-2 text-danger",
|
|
40603
|
+
children: [error, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
40604
|
+
variant: "outline",
|
|
40605
|
+
size: "sm",
|
|
40606
|
+
onClick: onLoadOlder,
|
|
40607
|
+
children: t("uiLibrary.logStream.retry", "Retry")
|
|
40608
|
+
})]
|
|
40609
|
+
});
|
|
40610
|
+
if (done) return hasRows ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
40611
|
+
className: "py-2 text-center text-foreground-subtle",
|
|
40612
|
+
children: t("uiLibrary.logStream.beginning", "Beginning of logs")
|
|
40613
|
+
}) : null;
|
|
40614
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
40615
|
+
className: "flex justify-center py-1.5",
|
|
40616
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
40617
|
+
variant: "outline",
|
|
40618
|
+
size: "sm",
|
|
40619
|
+
onClick: onLoadOlder,
|
|
40620
|
+
children: t("uiLibrary.logStream.loadOlder", "Load older")
|
|
40621
|
+
})
|
|
40622
|
+
});
|
|
40623
|
+
}
|
|
40355
40624
|
function ScopeBadge$2({ label, value }) {
|
|
40356
40625
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
40357
40626
|
className: "text-[9px] px-1.5 py-0.5 rounded-full bg-primary/10 text-primary font-medium",
|
|
@@ -52395,24 +52664,6 @@ function DeviceBatchActionsBar(props) {
|
|
|
52395
52664
|
})] });
|
|
52396
52665
|
}
|
|
52397
52666
|
//#endregion
|
|
52398
|
-
//#region src/hooks/use-debounced-string.ts
|
|
52399
|
-
/**
|
|
52400
|
-
* useDebouncedString — return `value` after `delay` ms have passed
|
|
52401
|
-
* without further changes. Resets the timer on every change.
|
|
52402
|
-
*
|
|
52403
|
-
* Useful for search inputs that should NOT push to URL state on every
|
|
52404
|
-
* keystroke. The DOM input stays controlled by an external `value`
|
|
52405
|
-
* (caller's `useState`); this hook produces a debounced derivation.
|
|
52406
|
-
*/
|
|
52407
|
-
function useDebouncedString(value, delayMs) {
|
|
52408
|
-
const [debounced, setDebounced] = (0, react$1.useState)(value);
|
|
52409
|
-
(0, react$1.useEffect)(() => {
|
|
52410
|
-
const t = setTimeout(() => setDebounced(value), delayMs);
|
|
52411
|
-
return () => clearTimeout(t);
|
|
52412
|
-
}, [value, delayMs]);
|
|
52413
|
-
return debounced;
|
|
52414
|
-
}
|
|
52415
|
-
//#endregion
|
|
52416
52667
|
//#region src/hooks/use-persisted-page-size.ts
|
|
52417
52668
|
/**
|
|
52418
52669
|
* usePersistedPageSize — a "rows per page" choice remembered per viewer.
|
|
@@ -58052,6 +58303,32 @@ function FormField$1({ label, description, error, required, children, orientatio
|
|
|
58052
58303
|
});
|
|
58053
58304
|
}
|
|
58054
58305
|
//#endregion
|
|
58306
|
+
//#region src/composites/grouped-tile-picker.tsx
|
|
58307
|
+
function GroupedTilePicker({ groups, onPick, emptyMessage }) {
|
|
58308
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
58309
|
+
className: "space-y-4",
|
|
58310
|
+
children: [groups.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
58311
|
+
className: "text-xs text-foreground-subtle",
|
|
58312
|
+
children: emptyMessage
|
|
58313
|
+
}), groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
58314
|
+
className: "mb-1.5 text-[10px] font-semibold uppercase tracking-wider text-foreground-subtle",
|
|
58315
|
+
children: group.label
|
|
58316
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
58317
|
+
className: "grid grid-cols-2 gap-2 sm:grid-cols-3",
|
|
58318
|
+
children: group.tiles.map((tile) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
58319
|
+
type: "button",
|
|
58320
|
+
"data-testid": tile.testId,
|
|
58321
|
+
onClick: () => onPick(tile.key),
|
|
58322
|
+
className: "flex flex-col items-center gap-1.5 rounded-lg border border-border bg-surface px-3 py-3 text-xs transition-colors hover:border-primary/50 hover:bg-surface-hover",
|
|
58323
|
+
children: [tile.icon, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
58324
|
+
className: "text-center font-medium text-foreground",
|
|
58325
|
+
children: tile.label
|
|
58326
|
+
})]
|
|
58327
|
+
}, tile.key))
|
|
58328
|
+
})] }, group.id))]
|
|
58329
|
+
});
|
|
58330
|
+
}
|
|
58331
|
+
//#endregion
|
|
58055
58332
|
//#region src/composites/hls-quality.ts
|
|
58056
58333
|
/**
|
|
58057
58334
|
* Pure helpers for the HLS playback quality selector.
|
|
@@ -60392,7 +60669,7 @@ function ResponseLog({ data, label = "Response", className = "" }) {
|
|
|
60392
60669
|
* Two consumers: the user-scope editor on `/system/users` (admin grants
|
|
60393
60670
|
* a baseline scope set to a non-admin user) and the scoped-token create
|
|
60394
60671
|
* modal on `/system/api-keys` (a caller carves out a subset of THEIR
|
|
60395
|
-
* scopes for a
|
|
60672
|
+
* scopes for a token).
|
|
60396
60673
|
*
|
|
60397
60674
|
* Row shapes per scope `type`:
|
|
60398
60675
|
* - category → target: 'device' | 'system'
|
|
@@ -64863,6 +65140,7 @@ exports.Grid2x2 = Grid2x2;
|
|
|
64863
65140
|
exports.GripHorizontal = GripHorizontal;
|
|
64864
65141
|
exports.GripTrack = GripTrack;
|
|
64865
65142
|
exports.GroupedModelSelector = GroupedModelSelector;
|
|
65143
|
+
exports.GroupedTilePicker = GroupedTilePicker;
|
|
64866
65144
|
exports.HOST_WIDGETS = HOST_WIDGETS;
|
|
64867
65145
|
exports.HUB_SESSION_RUN_MS = HUB_SESSION_RUN_MS;
|
|
64868
65146
|
exports.Hammer = Hammer;
|
|
@@ -65684,6 +65962,7 @@ exports.useDeviceHistoryGetStatus = useDeviceHistoryGetStatus;
|
|
|
65684
65962
|
exports.useDeviceHistoryListNodeSeries = useDeviceHistoryListNodeSeries;
|
|
65685
65963
|
exports.useDeviceHistoryListSeries = useDeviceHistoryListSeries;
|
|
65686
65964
|
exports.useDeviceHistoryQuery = useDeviceHistoryQuery;
|
|
65965
|
+
exports.useDeviceHistoryQueryMany = useDeviceHistoryQueryMany;
|
|
65687
65966
|
exports.useDeviceHistoryQueryNode = useDeviceHistoryQueryNode;
|
|
65688
65967
|
exports.useDeviceHistoryRecord = useDeviceHistoryRecord;
|
|
65689
65968
|
exports.useDeviceHistoryRecordBuckets = useDeviceHistoryRecordBuckets;
|
|
@@ -66639,7 +66918,6 @@ exports.useUpdateGetStatus = useUpdateGetStatus;
|
|
|
66639
66918
|
exports.useUpdateInstallUpdate = useUpdateInstallUpdate;
|
|
66640
66919
|
exports.useUrlQueryParam = useUrlQueryParam;
|
|
66641
66920
|
exports.useUserManagementConfirmTotp = useUserManagementConfirmTotp;
|
|
66642
|
-
exports.useUserManagementCreateScopedToken = useUserManagementCreateScopedToken;
|
|
66643
66921
|
exports.useUserManagementCreateUser = useUserManagementCreateUser;
|
|
66644
66922
|
exports.useUserManagementDeleteUser = useUserManagementDeleteUser;
|
|
66645
66923
|
exports.useUserManagementDisableTotp = useUserManagementDisableTotp;
|
|
@@ -66648,7 +66926,6 @@ exports.useUserManagementGetTotpStatus = useUserManagementGetTotpStatus;
|
|
|
66648
66926
|
exports.useUserManagementListApiKeys = useUserManagementListApiKeys;
|
|
66649
66927
|
exports.useUserManagementListExternalIdentities = useUserManagementListExternalIdentities;
|
|
66650
66928
|
exports.useUserManagementListOauthSessions = useUserManagementListOauthSessions;
|
|
66651
|
-
exports.useUserManagementListScopedTokens = useUserManagementListScopedTokens;
|
|
66652
66929
|
exports.useUserManagementListUsers = useUserManagementListUsers;
|
|
66653
66930
|
exports.useUserManagementMigrateRawSecrets = useUserManagementMigrateRawSecrets;
|
|
66654
66931
|
exports.useUserManagementOauthExchangeCode = useUserManagementOauthExchangeCode;
|
|
@@ -66658,13 +66935,11 @@ exports.useUserManagementOauthVerifyAccessToken = useUserManagementOauthVerifyAc
|
|
|
66658
66935
|
exports.useUserManagementResetPassword = useUserManagementResetPassword;
|
|
66659
66936
|
exports.useUserManagementRevokeApiKey = useUserManagementRevokeApiKey;
|
|
66660
66937
|
exports.useUserManagementRevokeOauthSession = useUserManagementRevokeOauthSession;
|
|
66661
|
-
exports.useUserManagementRevokeScopedToken = useUserManagementRevokeScopedToken;
|
|
66662
66938
|
exports.useUserManagementSetUserScopes = useUserManagementSetUserScopes;
|
|
66663
66939
|
exports.useUserManagementSetupTotp = useUserManagementSetupTotp;
|
|
66664
66940
|
exports.useUserManagementUnlinkExternalIdentity = useUserManagementUnlinkExternalIdentity;
|
|
66665
66941
|
exports.useUserManagementUpdateUser = useUserManagementUpdateUser;
|
|
66666
66942
|
exports.useUserManagementValidateCredentials = useUserManagementValidateCredentials;
|
|
66667
|
-
exports.useUserManagementValidateScopedToken = useUserManagementValidateScopedToken;
|
|
66668
66943
|
exports.useUserManagementVerifyTotp = useUserManagementVerifyTotp;
|
|
66669
66944
|
exports.useVacuumControlGetStatus = useVacuumControlGetStatus;
|
|
66670
66945
|
exports.useVacuumControlLocate = useVacuumControlLocate;
|