@rapidmx/web-client 0.17.2 → 0.18.0
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/apps/admin/diagnostics/index.tsx +15 -0
- package/apps/shared/components/admin/diagnostics/Badge.tsx +37 -0
- package/apps/shared/components/admin/diagnostics/ComponentsTable.tsx +131 -0
- package/apps/shared/components/admin/diagnostics/DiagnosticsManager.tsx +173 -0
- package/apps/shared/components/admin/diagnostics/HostCard.tsx +108 -0
- package/apps/shared/components/admin/diagnostics/InstalledPlugins.tsx +136 -0
- package/apps/shared/components/admin/diagnostics/KubernetesNotice.tsx +25 -0
- package/apps/shared/components/admin/diagnostics/LogStore.ts +75 -0
- package/apps/shared/components/admin/diagnostics/LogViewer.tsx +87 -0
- package/apps/shared/components/admin/diagnostics/LogsPanel.tsx +235 -0
- package/apps/shared/components/admin/diagnostics/MetricTile.tsx +43 -0
- package/apps/shared/components/admin/diagnostics/NamespaceCard.tsx +95 -0
- package/apps/shared/components/admin/diagnostics/PackagesTable.tsx +107 -0
- package/apps/shared/components/admin/diagnostics/ProcessCard.tsx +70 -0
- package/apps/shared/components/admin/diagnostics/PvcTable.tsx +79 -0
- package/apps/shared/components/admin/diagnostics/RuntimePanel.tsx +110 -0
- package/apps/shared/components/admin/diagnostics/ServerVersionCard.tsx +37 -0
- package/apps/shared/components/admin/diagnostics/Sparkline.tsx +79 -0
- package/apps/shared/components/admin/diagnostics/SystemPanel.tsx +81 -0
- package/apps/shared/components/admin/diagnostics/UsageMeter.tsx +73 -0
- package/apps/shared/components/admin/diagnostics/VersionsPanel.tsx +36 -0
- package/apps/shared/components/admin/diagnostics/diagnosticsApi.ts +197 -0
- package/apps/shared/components/admin/diagnostics/download.ts +28 -0
- package/apps/shared/components/admin/diagnostics/format.ts +106 -0
- package/apps/shared/components/admin/diagnostics/logClient.ts +231 -0
- package/apps/shared/components/admin/diagnostics/logLines.ts +125 -0
- package/apps/shared/components/admin/diagnostics/metricsHistory.ts +28 -0
- package/apps/shared/components/admin/diagnostics/useDiagnosticsResource.ts +44 -0
- package/apps/shared/components/admin/diagnostics/useLogStream.ts +122 -0
- package/apps/shared/components/admin/diagnostics/useMetricsPolling.ts +84 -0
- package/apps/shared/components/admin/layout/AdminShell.tsx +3 -0
- package/dist/apps/admin/diagnostics/index.d.ts +3 -0
- package/dist/apps/admin/diagnostics/index.js +6 -0
- package/dist/apps/shared/components/admin/diagnostics/Badge.d.ts +10 -0
- package/dist/apps/shared/components/admin/diagnostics/Badge.js +15 -0
- package/dist/apps/shared/components/admin/diagnostics/ComponentsTable.d.ts +15 -0
- package/dist/apps/shared/components/admin/diagnostics/ComponentsTable.js +35 -0
- package/dist/apps/shared/components/admin/diagnostics/DiagnosticsManager.d.ts +17 -0
- package/dist/apps/shared/components/admin/diagnostics/DiagnosticsManager.js +111 -0
- package/dist/apps/shared/components/admin/diagnostics/HostCard.d.ts +13 -0
- package/dist/apps/shared/components/admin/diagnostics/HostCard.js +33 -0
- package/dist/apps/shared/components/admin/diagnostics/InstalledPlugins.d.ts +26 -0
- package/dist/apps/shared/components/admin/diagnostics/InstalledPlugins.js +60 -0
- package/dist/apps/shared/components/admin/diagnostics/KubernetesNotice.d.ts +8 -0
- package/dist/apps/shared/components/admin/diagnostics/KubernetesNotice.js +9 -0
- package/dist/apps/shared/components/admin/diagnostics/LogStore.d.ts +37 -0
- package/dist/apps/shared/components/admin/diagnostics/LogStore.js +50 -0
- package/dist/apps/shared/components/admin/diagnostics/LogViewer.d.ts +24 -0
- package/dist/apps/shared/components/admin/diagnostics/LogViewer.js +47 -0
- package/dist/apps/shared/components/admin/diagnostics/LogsPanel.d.ts +13 -0
- package/dist/apps/shared/components/admin/diagnostics/LogsPanel.js +63 -0
- package/dist/apps/shared/components/admin/diagnostics/MetricTile.d.ts +21 -0
- package/dist/apps/shared/components/admin/diagnostics/MetricTile.js +7 -0
- package/dist/apps/shared/components/admin/diagnostics/NamespaceCard.d.ts +13 -0
- package/dist/apps/shared/components/admin/diagnostics/NamespaceCard.js +15 -0
- package/dist/apps/shared/components/admin/diagnostics/PackagesTable.d.ts +8 -0
- package/dist/apps/shared/components/admin/diagnostics/PackagesTable.js +31 -0
- package/dist/apps/shared/components/admin/diagnostics/ProcessCard.d.ts +8 -0
- package/dist/apps/shared/components/admin/diagnostics/ProcessCard.js +18 -0
- package/dist/apps/shared/components/admin/diagnostics/PvcTable.d.ts +6 -0
- package/dist/apps/shared/components/admin/diagnostics/PvcTable.js +18 -0
- package/dist/apps/shared/components/admin/diagnostics/RuntimePanel.d.ts +7 -0
- package/dist/apps/shared/components/admin/diagnostics/RuntimePanel.js +28 -0
- package/dist/apps/shared/components/admin/diagnostics/ServerVersionCard.d.ts +6 -0
- package/dist/apps/shared/components/admin/diagnostics/ServerVersionCard.js +17 -0
- package/dist/apps/shared/components/admin/diagnostics/Sparkline.d.ts +22 -0
- package/dist/apps/shared/components/admin/diagnostics/Sparkline.js +34 -0
- package/dist/apps/shared/components/admin/diagnostics/SystemPanel.d.ts +6 -0
- package/dist/apps/shared/components/admin/diagnostics/SystemPanel.js +26 -0
- package/dist/apps/shared/components/admin/diagnostics/UsageMeter.d.ts +26 -0
- package/dist/apps/shared/components/admin/diagnostics/UsageMeter.js +25 -0
- package/dist/apps/shared/components/admin/diagnostics/VersionsPanel.d.ts +10 -0
- package/dist/apps/shared/components/admin/diagnostics/VersionsPanel.js +11 -0
- package/dist/apps/shared/components/admin/diagnostics/diagnosticsApi.d.ts +168 -0
- package/dist/apps/shared/components/admin/diagnostics/diagnosticsApi.js +17 -0
- package/dist/apps/shared/components/admin/diagnostics/download.d.ts +9 -0
- package/dist/apps/shared/components/admin/diagnostics/download.js +23 -0
- package/dist/apps/shared/components/admin/diagnostics/format.d.ts +24 -0
- package/dist/apps/shared/components/admin/diagnostics/format.js +91 -0
- package/dist/apps/shared/components/admin/diagnostics/logClient.d.ts +85 -0
- package/dist/apps/shared/components/admin/diagnostics/logClient.js +173 -0
- package/dist/apps/shared/components/admin/diagnostics/logLines.d.ts +47 -0
- package/dist/apps/shared/components/admin/diagnostics/logLines.js +86 -0
- package/dist/apps/shared/components/admin/diagnostics/metricsHistory.d.ts +9 -0
- package/dist/apps/shared/components/admin/diagnostics/metricsHistory.js +19 -0
- package/dist/apps/shared/components/admin/diagnostics/useDiagnosticsResource.d.ts +12 -0
- package/dist/apps/shared/components/admin/diagnostics/useDiagnosticsResource.js +30 -0
- package/dist/apps/shared/components/admin/diagnostics/useLogStream.d.ts +37 -0
- package/dist/apps/shared/components/admin/diagnostics/useLogStream.js +79 -0
- package/dist/apps/shared/components/admin/diagnostics/useMetricsPolling.d.ts +20 -0
- package/dist/apps/shared/components/admin/diagnostics/useMetricsPolling.js +69 -0
- package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
- package/dist/apps/shared/components/admin/layout/AdminShell.js +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
6
|
+
import { LogClient } from "./logClient.js";
|
|
7
|
+
import { LogStore } from "./LogStore.js";
|
|
8
|
+
/** Lines arrive in bursts; the view is redrawn at most this often, however many came. */
|
|
9
|
+
export const LOG_FLUSH_MS = 100;
|
|
10
|
+
function snapshotOf(store) {
|
|
11
|
+
const capture = store.capture;
|
|
12
|
+
return {
|
|
13
|
+
entries: store.entries.slice(),
|
|
14
|
+
dropped: store.dropped,
|
|
15
|
+
capture: capture && {
|
|
16
|
+
active: capture.active,
|
|
17
|
+
startedAt: capture.startedAt,
|
|
18
|
+
stoppedAt: capture.stoppedAt,
|
|
19
|
+
count: capture.entries.length,
|
|
20
|
+
missed: capture.missed,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The live log stream of the server for the log view: a `LogClient`, and a `LogStore` holding the lines and any capture. Nothing
|
|
26
|
+
* connects until `start()`. The stream is closed, and the redraw timer cleared, when the component unmounts.
|
|
27
|
+
*/
|
|
28
|
+
export function useLogStream(options = {}) {
|
|
29
|
+
const [client] = useState(() => new LogClient(options));
|
|
30
|
+
const [store] = useState(() => new LogStore());
|
|
31
|
+
const [connection, setConnection] = useState({
|
|
32
|
+
status: client.status,
|
|
33
|
+
error: undefined,
|
|
34
|
+
});
|
|
35
|
+
const [snapshot, setSnapshot] = useState(() => snapshotOf(store));
|
|
36
|
+
const timer = useRef(undefined);
|
|
37
|
+
const flush = useCallback(() => {
|
|
38
|
+
clearTimeout(timer.current);
|
|
39
|
+
timer.current = undefined;
|
|
40
|
+
setSnapshot(snapshotOf(store));
|
|
41
|
+
}, [store]);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const stopEntries = client.onEntry((entry) => {
|
|
44
|
+
store.add(entry);
|
|
45
|
+
timer.current ?? (timer.current = setTimeout(flush, LOG_FLUSH_MS));
|
|
46
|
+
});
|
|
47
|
+
const stopStatus = client.onStatus((status, error) => setConnection({ status, error }));
|
|
48
|
+
return () => {
|
|
49
|
+
stopEntries();
|
|
50
|
+
stopStatus();
|
|
51
|
+
client.stop();
|
|
52
|
+
clearTimeout(timer.current);
|
|
53
|
+
timer.current = undefined;
|
|
54
|
+
};
|
|
55
|
+
}, [client, store, flush]);
|
|
56
|
+
return {
|
|
57
|
+
status: connection.status,
|
|
58
|
+
error: connection.error,
|
|
59
|
+
running: connection.status === "connecting" || connection.status === "live" || connection.status === "reconnecting",
|
|
60
|
+
entries: snapshot.entries,
|
|
61
|
+
dropped: snapshot.dropped,
|
|
62
|
+
capture: snapshot.capture,
|
|
63
|
+
start: () => client.start(),
|
|
64
|
+
stop: () => client.stop(),
|
|
65
|
+
clear: () => {
|
|
66
|
+
store.clear();
|
|
67
|
+
flush();
|
|
68
|
+
},
|
|
69
|
+
startCapture: () => {
|
|
70
|
+
store.startCapture(Date.now());
|
|
71
|
+
flush();
|
|
72
|
+
},
|
|
73
|
+
stopCapture: () => {
|
|
74
|
+
store.stopCapture(Date.now());
|
|
75
|
+
flush();
|
|
76
|
+
},
|
|
77
|
+
captureEntries: () => store.capture?.entries ?? [],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DiagnosticsMetrics } from "./diagnosticsApi.js";
|
|
2
|
+
export interface MetricsPolling {
|
|
3
|
+
/** The last `METRICS_HISTORY_SIZE` samples, oldest first. */
|
|
4
|
+
history: DiagnosticsMetrics[];
|
|
5
|
+
/** Why the last request failed, until one succeeds. */
|
|
6
|
+
error: string | undefined;
|
|
7
|
+
/** Whether nothing has been answered yet. */
|
|
8
|
+
loading: boolean;
|
|
9
|
+
/** Whether polling is stopped by the viewer (Pause), or because the server refused the viewer. */
|
|
10
|
+
paused: boolean;
|
|
11
|
+
setPaused: (paused: boolean) => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Samples `getDiagnosticsMetrics()` every `METRICS_POLL_INTERVAL_MS` while `enabled` and not paused, keeping the last
|
|
15
|
+
* `METRICS_HISTORY_SIZE`. A request is only ever made when the previous one has answered, none is made while the page is
|
|
16
|
+
* hidden (one is made at once when it is shown again), and polling stops when the component unmounts. A refusal (401/403)
|
|
17
|
+
* pauses polling, since asking again would only be refused again; Resume tries once more. Any other failure is shown
|
|
18
|
+
* and polling carries on, so a blip on the server does not need the viewer to do anything.
|
|
19
|
+
*/
|
|
20
|
+
export declare function useMetricsPolling(enabled: boolean): MetricsPolling;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { useEffect, useState } from "react";
|
|
6
|
+
import { getDiagnosticsMetrics } from "./diagnosticsApi.js";
|
|
7
|
+
import { describeError, isPermissionError } from "./format.js";
|
|
8
|
+
import { appendSample, METRICS_POLL_INTERVAL_MS } from "./metricsHistory.js";
|
|
9
|
+
/**
|
|
10
|
+
* Samples `getDiagnosticsMetrics()` every `METRICS_POLL_INTERVAL_MS` while `enabled` and not paused, keeping the last
|
|
11
|
+
* `METRICS_HISTORY_SIZE`. A request is only ever made when the previous one has answered, none is made while the page is
|
|
12
|
+
* hidden (one is made at once when it is shown again), and polling stops when the component unmounts. A refusal (401/403)
|
|
13
|
+
* pauses polling, since asking again would only be refused again; Resume tries once more. Any other failure is shown
|
|
14
|
+
* and polling carries on, so a blip on the server does not need the viewer to do anything.
|
|
15
|
+
*/
|
|
16
|
+
export function useMetricsPolling(enabled) {
|
|
17
|
+
const [history, setHistory] = useState([]);
|
|
18
|
+
const [error, setError] = useState();
|
|
19
|
+
const [loading, setLoading] = useState(true);
|
|
20
|
+
const [paused, setPaused] = useState(false);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!enabled || paused) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
let cancelled = false;
|
|
26
|
+
let inFlight = false;
|
|
27
|
+
async function sample() {
|
|
28
|
+
if (inFlight || document.hidden) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
inFlight = true;
|
|
32
|
+
try {
|
|
33
|
+
const next = await getDiagnosticsMetrics();
|
|
34
|
+
if (!cancelled) {
|
|
35
|
+
setHistory((previous) => appendSample(previous, next));
|
|
36
|
+
setError(undefined);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
if (!cancelled) {
|
|
41
|
+
setError(describeError(err, "Could not read the server's metrics."));
|
|
42
|
+
if (isPermissionError(err)) {
|
|
43
|
+
setPaused(true);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
inFlight = false;
|
|
49
|
+
if (!cancelled) {
|
|
50
|
+
setLoading(false);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
void sample();
|
|
55
|
+
const timer = setInterval(() => void sample(), METRICS_POLL_INTERVAL_MS);
|
|
56
|
+
const onVisibilityChange = () => {
|
|
57
|
+
if (!document.hidden) {
|
|
58
|
+
void sample();
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
62
|
+
return () => {
|
|
63
|
+
cancelled = true;
|
|
64
|
+
clearInterval(timer);
|
|
65
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
66
|
+
};
|
|
67
|
+
}, [enabled, paused]);
|
|
68
|
+
return { history, error, loading, paused, setPaused };
|
|
69
|
+
}
|
|
@@ -2,7 +2,7 @@ import "../../../styles/app.css";
|
|
|
2
2
|
import React, { PropsWithChildren } from "react";
|
|
3
3
|
import type { NavItem } from "@rapidmx/react-shared/components/navigation/BottomTabBar.js";
|
|
4
4
|
import { PluginNav, PluginNavProps } from "../../../plugins/pluginNav.js";
|
|
5
|
-
export type AdminSection = "mailboxes" | "quarantine" | "ingestQueue" | "domains" | "auditLog" | "distributionLists" | "transportRules" | "escrowScopes" | "retentionPolicy" | "encryptionPolicy" | "mailboxPolicy" | "setup" | "dataRequests" | "plugins" | "branding" | "signingCertificates";
|
|
5
|
+
export type AdminSection = "mailboxes" | "quarantine" | "ingestQueue" | "domains" | "auditLog" | "distributionLists" | "transportRules" | "escrowScopes" | "retentionPolicy" | "encryptionPolicy" | "mailboxPolicy" | "setup" | "dataRequests" | "plugins" | "diagnostics" | "branding" | "signingCertificates";
|
|
6
6
|
/** A core `AdminSection`, or a plugin's `adminNav` item id (see `PluginNav`). */
|
|
7
7
|
export type AdminShellActive = AdminSection | (string & {});
|
|
8
8
|
export interface AdminShellProps extends PluginNavProps {
|
|
@@ -6,7 +6,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
import "../../../styles/app.css";
|
|
7
7
|
import { useEffect, useState } from "react";
|
|
8
8
|
import { useRouter } from "@rapidrest/react/client";
|
|
9
|
-
import { HiOutlineClipboardDocumentList, HiOutlineClock, HiOutlineDocumentArrowDown, HiOutlineDocumentCheck, HiOutlineGlobeAlt, HiOutlineInboxStack, HiOutlineKey, HiOutlineLockClosed, HiOutlinePaintBrush, HiOutlinePuzzlePiece, HiOutlineQueueList, HiOutlineRocketLaunch, HiOutlineShieldCheck, HiOutlineShieldExclamation, HiOutlineUserGroup, HiOutlineWrenchScrewdriver, HiOutlineBars3, } from "react-icons/hi2";
|
|
9
|
+
import { HiOutlineClipboardDocumentList, HiOutlineClock, HiOutlineCpuChip, HiOutlineDocumentArrowDown, HiOutlineDocumentCheck, HiOutlineGlobeAlt, HiOutlineInboxStack, HiOutlineKey, HiOutlineLockClosed, HiOutlinePaintBrush, HiOutlinePuzzlePiece, HiOutlineQueueList, HiOutlineRocketLaunch, HiOutlineShieldCheck, HiOutlineShieldExclamation, HiOutlineUserGroup, HiOutlineWrenchScrewdriver, HiOutlineBars3, } from "react-icons/hi2";
|
|
10
10
|
import { apiFetch, ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
11
11
|
import { getSetupStatus } from "@rapidmx/react-shared/admin/setupApi.js";
|
|
12
12
|
import { useRedirectIfUnauthenticated } from "@rapidmx/react-shared/auth/session.js";
|
|
@@ -78,6 +78,7 @@ const NAV_ITEMS = [
|
|
|
78
78
|
icon: HiOutlineDocumentCheck,
|
|
79
79
|
},
|
|
80
80
|
{ id: "plugins", href: "/admin/plugins", label: "Plugins", icon: HiOutlinePuzzlePiece },
|
|
81
|
+
{ id: "diagnostics", href: "/admin/diagnostics", label: "Diagnostics", icon: HiOutlineCpuChip },
|
|
81
82
|
{ id: "branding", href: "/admin/branding", label: "Branding", icon: HiOutlinePaintBrush },
|
|
82
83
|
];
|
|
83
84
|
/** Mailbox-scoped sections — not part of the global nav rail/tab bar (see `NAV_ITEMS` above), but
|
package/package.json
CHANGED