@rapidmx/web-client 0.37.0 → 0.38.1
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/configuration-encryption/index.tsx +17 -0
- package/apps/admin/domains/[uid].tsx +17 -4
- package/apps/admin/integrations/dns-providers/[uid].tsx +205 -0
- package/apps/admin/integrations/dns-providers/index.tsx +164 -0
- package/apps/admin/integrations/dns-providers/new/index.tsx +55 -0
- package/apps/admin/integrations/dns-resolver/index.tsx +16 -0
- package/apps/admin/integrations/giphy/index.tsx +16 -0
- package/apps/admin/integrations/index.tsx +31 -0
- package/apps/admin/integrations/plugin-registry/index.tsx +16 -0
- package/apps/admin/integrations/signing-ca/index.tsx +16 -0
- package/apps/admin/integrations/stun/index.tsx +16 -0
- package/apps/shared/components/admin/configEncryption/ConfigKeysManager.tsx +529 -0
- package/apps/shared/components/admin/configEncryption/keyringParts.ts +37 -0
- package/apps/shared/components/admin/diagnostics/DiagnosticsManager.tsx +20 -4
- package/apps/shared/components/admin/diagnostics/InformationPanel.tsx +23 -6
- package/apps/shared/components/admin/diagnostics/KeyringCard.tsx +144 -0
- package/apps/shared/components/admin/diagnostics/SettingsTable.tsx +21 -6
- package/apps/shared/components/admin/diagnostics/diagnosticsApi.ts +15 -3
- package/apps/shared/components/admin/integrations/DnsProviderForm.tsx +354 -0
- package/apps/shared/components/admin/integrations/DnsProviderHosts.tsx +197 -0
- package/apps/shared/components/admin/integrations/DnsProviderOperations.tsx +115 -0
- package/apps/shared/components/admin/integrations/DnsResolverForm.tsx +61 -0
- package/apps/shared/components/admin/integrations/GiphyForm.tsx +59 -0
- package/apps/shared/components/admin/integrations/IntegrationSectionForm.tsx +234 -0
- package/apps/shared/components/admin/integrations/IntegrationSectionPage.tsx +40 -0
- package/apps/shared/components/admin/integrations/IntegrationsLayout.tsx +72 -0
- package/apps/shared/components/admin/integrations/PluginRegistryForm.tsx +230 -0
- package/apps/shared/components/admin/integrations/SecretField.tsx +91 -0
- package/apps/shared/components/admin/integrations/SigningCaForm.tsx +95 -0
- package/apps/shared/components/admin/integrations/StunForm.tsx +63 -0
- package/apps/shared/components/admin/integrations/dnsProviderParts.tsx +72 -0
- package/apps/shared/components/admin/layout/AdminShell.tsx +12 -1
- package/apps/shared/components/admin/settings/CopyableValue.tsx +16 -0
- package/apps/shared/components/admin/settings/DomainApexTxt.tsx +319 -0
- package/apps/shared/components/admin/settings/DomainDkimRotation.tsx +408 -0
- package/apps/shared/components/admin/settings/DomainDnsProvider.tsx +147 -0
- package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +174 -17
- package/apps/shared/components/admin/setup/SetupWizard.tsx +39 -6
- package/dist/apps/admin/configuration-encryption/index.d.ts +4 -0
- package/dist/apps/admin/configuration-encryption/index.js +7 -0
- package/dist/apps/admin/domains/[uid].js +11 -4
- package/dist/apps/admin/integrations/dns-providers/[uid].d.ts +7 -0
- package/dist/apps/admin/integrations/dns-providers/[uid].js +117 -0
- package/dist/apps/admin/integrations/dns-providers/index.d.ts +3 -0
- package/dist/apps/admin/integrations/dns-providers/index.js +68 -0
- package/dist/apps/admin/integrations/dns-providers/new/index.d.ts +3 -0
- package/dist/apps/admin/integrations/dns-providers/new/index.js +31 -0
- package/dist/apps/admin/integrations/dns-resolver/index.d.ts +3 -0
- package/dist/apps/admin/integrations/dns-resolver/index.js +6 -0
- package/dist/apps/admin/integrations/giphy/index.d.ts +3 -0
- package/dist/apps/admin/integrations/giphy/index.js +6 -0
- package/dist/apps/admin/integrations/index.d.ts +4 -0
- package/dist/apps/admin/integrations/index.js +7 -0
- package/dist/apps/admin/integrations/plugin-registry/index.d.ts +3 -0
- package/dist/apps/admin/integrations/plugin-registry/index.js +6 -0
- package/dist/apps/admin/integrations/signing-ca/index.d.ts +3 -0
- package/dist/apps/admin/integrations/signing-ca/index.js +6 -0
- package/dist/apps/admin/integrations/stun/index.d.ts +3 -0
- package/dist/apps/admin/integrations/stun/index.js +6 -0
- package/dist/apps/shared/components/admin/configEncryption/ConfigKeysManager.d.ts +9 -0
- package/dist/apps/shared/components/admin/configEncryption/ConfigKeysManager.js +187 -0
- package/dist/apps/shared/components/admin/configEncryption/keyringParts.d.ts +10 -0
- package/dist/apps/shared/components/admin/configEncryption/keyringParts.js +26 -0
- package/dist/apps/shared/components/admin/diagnostics/DiagnosticsManager.js +20 -4
- package/dist/apps/shared/components/admin/diagnostics/InformationPanel.d.ts +8 -4
- package/dist/apps/shared/components/admin/diagnostics/InformationPanel.js +7 -5
- package/dist/apps/shared/components/admin/diagnostics/KeyringCard.d.ts +18 -0
- package/dist/apps/shared/components/admin/diagnostics/KeyringCard.js +51 -0
- package/dist/apps/shared/components/admin/diagnostics/SettingsTable.d.ts +9 -1
- package/dist/apps/shared/components/admin/diagnostics/SettingsTable.js +10 -3
- package/dist/apps/shared/components/admin/diagnostics/diagnosticsApi.d.ts +13 -3
- package/dist/apps/shared/components/admin/integrations/DnsProviderForm.d.ts +17 -0
- package/dist/apps/shared/components/admin/integrations/DnsProviderForm.js +147 -0
- package/dist/apps/shared/components/admin/integrations/DnsProviderHosts.d.ts +10 -0
- package/dist/apps/shared/components/admin/integrations/DnsProviderHosts.js +101 -0
- package/dist/apps/shared/components/admin/integrations/DnsProviderOperations.d.ts +8 -0
- package/dist/apps/shared/components/admin/integrations/DnsProviderOperations.js +60 -0
- package/dist/apps/shared/components/admin/integrations/DnsResolverForm.d.ts +7 -0
- package/dist/apps/shared/components/admin/integrations/DnsResolverForm.js +15 -0
- package/dist/apps/shared/components/admin/integrations/GiphyForm.d.ts +7 -0
- package/dist/apps/shared/components/admin/integrations/GiphyForm.js +11 -0
- package/dist/apps/shared/components/admin/integrations/IntegrationSectionForm.d.ts +54 -0
- package/dist/apps/shared/components/admin/integrations/IntegrationSectionForm.js +131 -0
- package/dist/apps/shared/components/admin/integrations/IntegrationSectionPage.d.ts +12 -0
- package/dist/apps/shared/components/admin/integrations/IntegrationSectionPage.js +21 -0
- package/dist/apps/shared/components/admin/integrations/IntegrationsLayout.d.ts +14 -0
- package/dist/apps/shared/components/admin/integrations/IntegrationsLayout.js +46 -0
- package/dist/apps/shared/components/admin/integrations/PluginRegistryForm.d.ts +7 -0
- package/dist/apps/shared/components/admin/integrations/PluginRegistryForm.js +90 -0
- package/dist/apps/shared/components/admin/integrations/SecretField.d.ts +25 -0
- package/dist/apps/shared/components/admin/integrations/SecretField.js +25 -0
- package/dist/apps/shared/components/admin/integrations/SigningCaForm.d.ts +7 -0
- package/dist/apps/shared/components/admin/integrations/SigningCaForm.js +25 -0
- package/dist/apps/shared/components/admin/integrations/StunForm.d.ts +9 -0
- package/dist/apps/shared/components/admin/integrations/StunForm.js +26 -0
- package/dist/apps/shared/components/admin/integrations/dnsProviderParts.d.ts +28 -0
- package/dist/apps/shared/components/admin/integrations/dnsProviderParts.js +43 -0
- package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
- package/dist/apps/shared/components/admin/layout/AdminShell.js +8 -1
- package/dist/apps/shared/components/admin/settings/CopyableValue.d.ts +6 -0
- package/dist/apps/shared/components/admin/settings/CopyableValue.js +6 -0
- package/dist/apps/shared/components/admin/settings/DomainApexTxt.d.ts +13 -0
- package/dist/apps/shared/components/admin/settings/DomainApexTxt.js +148 -0
- package/dist/apps/shared/components/admin/settings/DomainDkimRotation.d.ts +15 -0
- package/dist/apps/shared/components/admin/settings/DomainDkimRotation.js +200 -0
- package/dist/apps/shared/components/admin/settings/DomainDnsProvider.d.ts +12 -0
- package/dist/apps/shared/components/admin/settings/DomainDnsProvider.js +70 -0
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.d.ts +3 -3
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +61 -8
- package/dist/apps/shared/components/admin/setup/SetupWizard.js +9 -1
- package/dist/lib/admin/configKeysApi.d.ts +96 -0
- package/dist/lib/admin/configKeysApi.js +34 -0
- package/dist/lib/admin/dnsProvidersApi.d.ts +172 -0
- package/dist/lib/admin/dnsProvidersApi.js +70 -0
- package/dist/lib/admin/domainsApi.d.ts +116 -1
- package/dist/lib/admin/domainsApi.js +33 -0
- package/dist/lib/admin/integrationsApi.d.ts +75 -0
- package/dist/lib/admin/integrationsApi.js +31 -0
- package/lib/admin/configKeysApi.ts +117 -0
- package/lib/admin/dnsProvidersApi.ts +241 -0
- package/lib/admin/domainsApi.ts +145 -1
- package/lib/admin/integrationsApi.ts +107 -0
- package/package.json +1 -1
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
///////////////////////////////////////////////////////////////////////////////
|
|
5
5
|
import React from "react";
|
|
6
6
|
import Alert from "../ActionAlert.js";
|
|
7
|
+
import type { ConfigKeys } from "../../../../../lib/admin/configKeysApi.js";
|
|
7
8
|
import type { DiagnosticsInformation, DiagnosticsVersions } from "./diagnosticsApi.js";
|
|
8
9
|
import type { DiagnosticsResource } from "./useDiagnosticsResource.js";
|
|
9
10
|
import ComponentsTable from "./ComponentsTable.js";
|
|
10
11
|
import InstalledPlugins, { InstalledPluginsData } from "./InstalledPlugins.js";
|
|
12
|
+
import KeyringCard from "./KeyringCard.js";
|
|
11
13
|
import PackagesTable from "./PackagesTable.js";
|
|
12
14
|
import ServerVersionCard from "./ServerVersionCard.js";
|
|
13
15
|
import SettingsTable from "./SettingsTable.js";
|
|
@@ -16,14 +18,17 @@ export interface InformationPanelProps {
|
|
|
16
18
|
versions: DiagnosticsResource<DiagnosticsVersions>;
|
|
17
19
|
plugins: DiagnosticsResource<InstalledPluginsData>;
|
|
18
20
|
information: DiagnosticsResource<DiagnosticsInformation>;
|
|
21
|
+
/** `null` when the server has no keyring route. */
|
|
22
|
+
keyring: DiagnosticsResource<ConfigKeys | null>;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
/**
|
|
22
|
-
* The Information tab: the server, the install's other containers, the installed plugins, the server's environment
|
|
23
|
-
* configuration (secrets withheld by the server),
|
|
24
|
-
*
|
|
26
|
+
* The Information tab: the server, the install's other containers (DNS publishing's among them), the installed plugins, the server's environment
|
|
27
|
+
* variables and configuration (secrets withheld by the server), what the Integrations pages saved as a source of its own, the configuration keyring and
|
|
28
|
+
* every installed package. The environment and configuration come from their own request, and so does the keyring, so a server that predates them
|
|
29
|
+
* leaves the rest of the tab working and says so in their place.
|
|
25
30
|
*/
|
|
26
|
-
export default function InformationPanel({ versions, plugins, information }: InformationPanelProps) {
|
|
31
|
+
export default function InformationPanel({ versions, plugins, information, keyring }: InformationPanelProps) {
|
|
27
32
|
const { data, error, loading } = versions;
|
|
28
33
|
return (
|
|
29
34
|
<div className="space-y-6">
|
|
@@ -47,14 +52,26 @@ export default function InformationPanel({ versions, plugins, information }: Inf
|
|
|
47
52
|
<SettingsTable
|
|
48
53
|
id="diagnostics-configuration"
|
|
49
54
|
title="Configuration"
|
|
50
|
-
description="The effective settings the server runs with: the environment, saved plugin settings and defaults combined. Settings on the server's list of hidden settings, such as passwords, tokens and keys, are withheld by the server and never sent, and so are the credentials in URLs."
|
|
55
|
+
description="The effective settings the server runs with: the environment, saved plugin settings, what the Integrations pages saved and defaults combined. Settings on the server's list of hidden settings, such as passwords, tokens and keys, are withheld by the server and never sent, and so are the credentials in URLs."
|
|
51
56
|
settings={information.data.configuration ?? []}
|
|
52
57
|
/>
|
|
58
|
+
{information.data.integrations && (
|
|
59
|
+
<SettingsTable
|
|
60
|
+
id="diagnostics-integrations"
|
|
61
|
+
title="Integrations"
|
|
62
|
+
description="Saved in the Integrations pages of the admin console and applied without a restart. Each of these overrides the deployment's own value of the same name above. A secret shows only that it is set."
|
|
63
|
+
settings={information.data.integrations}
|
|
64
|
+
secrets="set"
|
|
65
|
+
emptyText="Nothing is saved in the Integrations pages."
|
|
66
|
+
/>
|
|
67
|
+
)}
|
|
53
68
|
</>
|
|
54
69
|
)}
|
|
55
|
-
<PackagesTable packages={data.packages ?? []} />
|
|
56
70
|
</>
|
|
57
71
|
)}
|
|
72
|
+
{/* Its own request, so it shows (or says why it can't) whether or not the versions could be read. */}
|
|
73
|
+
<KeyringCard keyring={keyring} />
|
|
74
|
+
{data && <PackagesTable packages={data.packages ?? []} />}
|
|
58
75
|
</div>
|
|
59
76
|
);
|
|
60
77
|
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import React, { ReactNode } from "react";
|
|
6
|
+
import { ConfigKeyRotationPhase, ConfigKeys, getConfigKeys } from "../../../../../lib/admin/configKeysApi.js";
|
|
7
|
+
import { ApiRequestError } from "../../../../../lib/util/api.js";
|
|
8
|
+
import Alert from "../ActionAlert.js";
|
|
9
|
+
import { activeKidOf, ageText, isRunning } from "../configEncryption/keyringParts.js";
|
|
10
|
+
import Badge from "./Badge.js";
|
|
11
|
+
import { formatDateTime, NO_VALUE } from "./format.js";
|
|
12
|
+
import type { DiagnosticsResource } from "./useDiagnosticsResource.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Reads the configuration keyring's state with the configuration keys client: the same answer the Configuration Encryption page shows, so Diagnostics needs no
|
|
16
|
+
* route of its own. A server without the route (404) answers `null`, which the card words, so that it doesn't fail the page.
|
|
17
|
+
*/
|
|
18
|
+
export async function loadKeyring(): Promise<ConfigKeys | null> {
|
|
19
|
+
try {
|
|
20
|
+
return await getConfigKeys();
|
|
21
|
+
} catch (err) {
|
|
22
|
+
if (err instanceof ApiRequestError && err.status === 404) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
throw err;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Where a running rotation is, in words. */
|
|
30
|
+
const PHASES: Record<ConfigKeyRotationPhase, string> = {
|
|
31
|
+
adding: "adding the new key",
|
|
32
|
+
activating: "waiting for the server copies to load the new key",
|
|
33
|
+
reencrypting: "re-encrypting the saved values",
|
|
34
|
+
retiring: "removing the old key",
|
|
35
|
+
done: "done",
|
|
36
|
+
failed: "failed",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const HEADINGS = ["Key ID", "Created", "Age", "Values encrypted", "State"];
|
|
40
|
+
|
|
41
|
+
export interface KeyringCardProps {
|
|
42
|
+
keyring: DiagnosticsResource<ConfigKeys | null>;
|
|
43
|
+
/** The time the ages are counted to. Defaults to now. For a test. */
|
|
44
|
+
now?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function Note({ role = "note", children }: { role?: "note" | "alert"; children: ReactNode }) {
|
|
48
|
+
return (
|
|
49
|
+
<div role={role} className="mt-3 rounded-sm bg-warning px-3.5 py-3 text-sm text-warning-contrast">
|
|
50
|
+
{children}
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The configuration keyring (spec 5.6, 14): each key by its id, when it was made, its age and how many saved values it encrypts, which one encrypts now, and a
|
|
57
|
+
* rotation that is running or failed. Never any key material: the server doesn't send it. Managing the keys is the Configuration Encryption page's.
|
|
58
|
+
*/
|
|
59
|
+
export default function KeyringCard({ keyring, now = Date.now() }: KeyringCardProps) {
|
|
60
|
+
const { data, error, loading } = keyring;
|
|
61
|
+
return (
|
|
62
|
+
<section aria-labelledby="diagnostics-keyring-heading" className="rounded-md border border-border bg-surface p-4">
|
|
63
|
+
<h2 id="diagnostics-keyring-heading" className="mb-1 text-base font-bold uppercase tracking-wide">
|
|
64
|
+
Configuration keyring
|
|
65
|
+
</h2>
|
|
66
|
+
<p className="mb-3 text-xs text-text-muted">
|
|
67
|
+
The keys that encrypt the credentials, API keys and tokens saved in the console. Only each key’s id, age and how many values it
|
|
68
|
+
encrypts are shown.
|
|
69
|
+
</p>
|
|
70
|
+
{error && <Alert>{error}</Alert>}
|
|
71
|
+
{!data && loading && <p className="text-sm text-text-muted">Loading…</p>}
|
|
72
|
+
{data === null && <p className="text-sm text-text-muted">This server doesn’t offer configuration encryption yet.</p>}
|
|
73
|
+
{data && <Keys keys={data} now={now} />}
|
|
74
|
+
</section>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function Keys({ keys, now }: { keys: ConfigKeys; now: number }) {
|
|
79
|
+
const list = keys.keys ?? [];
|
|
80
|
+
const activeKid = activeKidOf({ ...keys, keys: list });
|
|
81
|
+
const rotation = keys.rotation;
|
|
82
|
+
const reencryption = keys.reencryption;
|
|
83
|
+
const unreadable = reencryption?.unreadable?.length ?? 0;
|
|
84
|
+
return (
|
|
85
|
+
<>
|
|
86
|
+
{list.length === 0 ? (
|
|
87
|
+
<p className="text-sm text-text-muted">The keyring holds no keys.</p>
|
|
88
|
+
) : (
|
|
89
|
+
<div className="overflow-x-auto">
|
|
90
|
+
<table className="w-full border-collapse text-sm">
|
|
91
|
+
<thead>
|
|
92
|
+
<tr>
|
|
93
|
+
{HEADINGS.map((heading) => (
|
|
94
|
+
<th key={heading} className="border-b border-border px-2.5 py-2 text-left text-xs uppercase tracking-wide text-text-muted">
|
|
95
|
+
{heading}
|
|
96
|
+
</th>
|
|
97
|
+
))}
|
|
98
|
+
</tr>
|
|
99
|
+
</thead>
|
|
100
|
+
<tbody>
|
|
101
|
+
{list.map((key) => (
|
|
102
|
+
<tr key={key.kid}>
|
|
103
|
+
<td className="break-all border-b border-border px-2.5 py-2 font-mono">{key.kid}</td>
|
|
104
|
+
<td className="border-b border-border px-2.5 py-2">{formatDateTime(key.createdAt)}</td>
|
|
105
|
+
<td className="border-b border-border px-2.5 py-2">{ageText(key.createdAt, now)}</td>
|
|
106
|
+
<td className="border-b border-border px-2.5 py-2 tabular-nums">{key.envelopes ?? "Not counted yet"}</td>
|
|
107
|
+
<td className="border-b border-border px-2.5 py-2">
|
|
108
|
+
{key.kid === activeKid ? (
|
|
109
|
+
<Badge tone="success">Active</Badge>
|
|
110
|
+
) : isRunning(rotation) && key.kid === rotation.kid ? (
|
|
111
|
+
<Badge>Waiting to be activated</Badge>
|
|
112
|
+
) : (
|
|
113
|
+
NO_VALUE
|
|
114
|
+
)}
|
|
115
|
+
</td>
|
|
116
|
+
</tr>
|
|
117
|
+
))}
|
|
118
|
+
</tbody>
|
|
119
|
+
</table>
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
<p className="mt-2 text-xs text-text-muted">
|
|
123
|
+
{keys.countedAt ? `Values were last counted ${formatDateTime(keys.countedAt)}.` : "Values have not been counted yet."}
|
|
124
|
+
</p>
|
|
125
|
+
{isRunning(rotation) && (
|
|
126
|
+
<Note>
|
|
127
|
+
A key rotation is in progress: {PHASES[rotation.phase] ?? rotation.phase}. Follow it on the{" "}
|
|
128
|
+
<a href="/admin/configuration-encryption" className="underline">
|
|
129
|
+
Configuration Encryption
|
|
130
|
+
</a>{" "}
|
|
131
|
+
page.
|
|
132
|
+
</Note>
|
|
133
|
+
)}
|
|
134
|
+
{rotation?.phase === "failed" && <Note>The last key rotation failed: {rotation.lastError ?? "no reason was recorded."}</Note>}
|
|
135
|
+
{unreadable > 0 && (
|
|
136
|
+
<Note role="alert">
|
|
137
|
+
{reencryption?.finishedAt ? `The re-encryption pass that finished ${formatDateTime(reencryption.finishedAt)}` : "The last re-encryption pass"} could not read{" "}
|
|
138
|
+
{unreadable === 1 ? "1 saved value" : `${unreadable} saved values`} with the keys in this keyring. Restore the keyring they were saved with, or enter them again;
|
|
139
|
+
only a later pass updates this, so a value entered again stays counted until then.
|
|
140
|
+
</Note>
|
|
141
|
+
)}
|
|
142
|
+
</>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
@@ -13,6 +13,12 @@ export const SETTINGS_PAGE_SIZE = 100;
|
|
|
13
13
|
/** What stands where the server withheld a value. Drawn here from `redacted`: the server never sends the value (or any text for it). */
|
|
14
14
|
export const HIDDEN_VALUE = "•••• (hidden)";
|
|
15
15
|
|
|
16
|
+
/** What stands where a secret of the integrations layer is saved: whether it is set is all that is shown of it. */
|
|
17
|
+
export const SET_VALUE = "•••• (set)";
|
|
18
|
+
|
|
19
|
+
/** What stands where a saved secret could not be decrypted, so it is not in use. */
|
|
20
|
+
export const UNREADABLE_VALUE = "Set, but can't be read: enter it again";
|
|
21
|
+
|
|
16
22
|
const INPUT_CLASS =
|
|
17
23
|
"w-full text-sm py-2 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
18
24
|
|
|
@@ -24,13 +30,17 @@ export interface SettingsTableProps {
|
|
|
24
30
|
/** Also the prefix of the heading's element id. */
|
|
25
31
|
id: string;
|
|
26
32
|
settings: DiagnosticsSetting[];
|
|
33
|
+
/** What a `redacted` value is: `hidden` (default) where the server withholds it, `set` where it is a saved secret and the setting says only that it is set. */
|
|
34
|
+
secrets?: "hidden" | "set";
|
|
35
|
+
/** What an empty list says. Defaults to "Nothing is set." */
|
|
36
|
+
emptyText?: string;
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
/**
|
|
30
40
|
* A filterable, paged list of settings (name and value). A value the server withheld is shown as "•••• (hidden)", never as
|
|
31
41
|
* anything it could have held. The filter matches names, and the values that are shown: a hidden value can't be searched for.
|
|
32
42
|
*/
|
|
33
|
-
export default function SettingsTable({ title, description, id, settings }: SettingsTableProps) {
|
|
43
|
+
export default function SettingsTable({ title, description, id, settings, secrets = "hidden", emptyText = "Nothing is set." }: SettingsTableProps) {
|
|
34
44
|
const [query, setQuery] = useState("");
|
|
35
45
|
const [limit, setLimit] = useState(SETTINGS_PAGE_SIZE);
|
|
36
46
|
|
|
@@ -39,7 +49,9 @@ export default function SettingsTable({ title, description, id, settings }: Sett
|
|
|
39
49
|
return settings.filter((item) => needle === "" || `${item.name} ${item.redacted ? "" : (item.value ?? "")}`.toLowerCase().includes(needle));
|
|
40
50
|
}, [settings, query]);
|
|
41
51
|
const shown = matching.slice(0, limit);
|
|
42
|
-
const
|
|
52
|
+
const unreadableCount = settings.filter((item) => item.unreadable).length;
|
|
53
|
+
const hiddenCount = settings.filter((item) => item.redacted && !item.unreadable).length;
|
|
54
|
+
const hiddenText = secrets === "set" ? `${hiddenCount} ${hiddenCount === 1 ? "secret" : "secrets"} set` : `${hiddenCount} with a hidden value`;
|
|
43
55
|
|
|
44
56
|
return (
|
|
45
57
|
<section aria-labelledby={`${id}-heading`} className="rounded-md border border-border bg-surface p-4">
|
|
@@ -62,10 +74,11 @@ export default function SettingsTable({ title, description, id, settings }: Sett
|
|
|
62
74
|
</div>
|
|
63
75
|
<p role="status" className="mb-2 text-xs text-text-muted">
|
|
64
76
|
{(matching.length === settings.length ? `${settings.length} settings` : `${matching.length} of ${settings.length} settings match`) +
|
|
65
|
-
(hiddenCount > 0 ? `, ${
|
|
77
|
+
(hiddenCount > 0 ? `, ${hiddenText}` : "") +
|
|
78
|
+
(unreadableCount > 0 ? `, ${unreadableCount} that can't be read` : "")}
|
|
66
79
|
</p>
|
|
67
80
|
{matching.length === 0 ? (
|
|
68
|
-
<p className="text-sm text-text-muted">{settings.length === 0 ?
|
|
81
|
+
<p className="text-sm text-text-muted">{settings.length === 0 ? emptyText : "No settings match."}</p>
|
|
69
82
|
) : (
|
|
70
83
|
<div className="overflow-x-auto">
|
|
71
84
|
<table className="w-full text-sm border-collapse">
|
|
@@ -89,8 +102,10 @@ export default function SettingsTable({ title, description, id, settings }: Sett
|
|
|
89
102
|
<CopyIconButton value={item.name} label={`Copy the name of ${item.name}`} />
|
|
90
103
|
</td>
|
|
91
104
|
<td className="py-2 px-2.5 border-b border-border font-mono break-all">
|
|
92
|
-
{item.
|
|
93
|
-
<span className="text-
|
|
105
|
+
{item.unreadable ? (
|
|
106
|
+
<span className="text-danger">{UNREADABLE_VALUE}</span>
|
|
107
|
+
) : item.redacted ? (
|
|
108
|
+
<span className="text-text-muted">{secrets === "set" ? SET_VALUE : HIDDEN_VALUE}</span>
|
|
94
109
|
) : (
|
|
95
110
|
<>
|
|
96
111
|
{item.value}
|
|
@@ -15,7 +15,10 @@ import { apiFetch } from "../../../../../lib/util/api.js";
|
|
|
15
15
|
* machine figures are those of the node running the server, read from inside its own container.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* The components of a RapidMX install that run beside the server itself, in the order the server lists them. `dns-controller` (one pod) and
|
|
20
|
+
* `external-dns` (one pod per DNS provider config) are DNS publishing's and come after the eight others, only from a server that runs it.
|
|
21
|
+
*/
|
|
19
22
|
export type DiagnosticsComponentName =
|
|
20
23
|
| "postfix"
|
|
21
24
|
| "postfix-bridge"
|
|
@@ -24,7 +27,9 @@ export type DiagnosticsComponentName =
|
|
|
24
27
|
| "redis"
|
|
25
28
|
| "coturn"
|
|
26
29
|
| "rspamd"
|
|
27
|
-
| "clamav"
|
|
30
|
+
| "clamav"
|
|
31
|
+
| "dns-controller"
|
|
32
|
+
| "external-dns";
|
|
28
33
|
|
|
29
34
|
export interface DiagnosticsContainer {
|
|
30
35
|
name: string;
|
|
@@ -70,7 +75,7 @@ export interface DiagnosticsVersions {
|
|
|
70
75
|
};
|
|
71
76
|
/** Sorted by name. */
|
|
72
77
|
packages: { name: string; version: string; direct: boolean }[];
|
|
73
|
-
/**
|
|
78
|
+
/** The eight others, in the order of `DiagnosticsComponentName`, and the two of DNS publishing when the server runs it. */
|
|
74
79
|
components: DiagnosticsComponent[];
|
|
75
80
|
kubernetes: { available: boolean; reason?: string };
|
|
76
81
|
}
|
|
@@ -83,12 +88,19 @@ export interface DiagnosticsSetting {
|
|
|
83
88
|
name: string;
|
|
84
89
|
value?: string;
|
|
85
90
|
redacted: boolean;
|
|
91
|
+
/** A saved secret the server holds but could not decrypt (a key missing from its keyring), so it is not in use: set with `redacted`, and the administrator enters it again. */
|
|
92
|
+
unreadable?: boolean;
|
|
86
93
|
}
|
|
87
94
|
|
|
88
95
|
/** The server's environment variables and effective configuration, each sorted by name. */
|
|
89
96
|
export interface DiagnosticsInformation {
|
|
90
97
|
environment: DiagnosticsSetting[];
|
|
91
98
|
configuration: DiagnosticsSetting[];
|
|
99
|
+
/**
|
|
100
|
+
* The integrations config layer as a source of its own: what is saved on the console's Integrations pages and applied without a restart, a secret
|
|
101
|
+
* redacted. Left out by a server that predates it (nothing is shown), an empty list when nothing is saved there.
|
|
102
|
+
*/
|
|
103
|
+
integrations?: DiagnosticsSetting[];
|
|
92
104
|
}
|
|
93
105
|
|
|
94
106
|
/** A node that runs some of this install's pods, derived from the pods: nothing else about the node is readable. */
|