mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 22:55:24 +00:00
feat: give plugins their own settings and one settings screen
This commit is contained in:
1 parent
cc15559ea1
commit
49f0286a14
66 files changed
+36497
-451
No files matched your search
@@ -64,11 +64,6 @@ import {
|
||||
type TerminalImageStorageTestResult,
|
||||
type BrandingSettings,
|
||||
} from "@/api/settings-api";
|
||||
import {
|
||||
getTailscaleSettings,
|
||||
updateTailscaleSettings,
|
||||
} from "../../../plugins/tailscale/src/frontend/tailscale-api";
|
||||
import { AdminTailscaleSection } from "../../../plugins/tailscale/src/frontend/AdminTailscaleSection";
|
||||
import {
|
||||
getSessionSharingGloballyEnabled,
|
||||
updateSessionSharingGloballyEnabled,
|
||||
@@ -185,8 +180,6 @@ export function AdminSettingsPanel({
|
||||
const [guacEnabled, setGuacEnabled] = useState(false);
|
||||
const [guacUrl, setGuacUrl] = useState("guacd:4822");
|
||||
const [logLevel, setLogLevel] = useState("info");
|
||||
const [tailscaleApiKey, setTailscaleApiKey] = useState("");
|
||||
const [tailscaleApiBaseUrl, setTailscaleApiBaseUrl] = useState("");
|
||||
const [commandHistoryEnabled, setCommandHistoryEnabled] = useState(true);
|
||||
const [analyticsEnabled, setAnalyticsEnabled] = useState(true);
|
||||
const [analyticsLocked, setAnalyticsLocked] = useState(false);
|
||||
@@ -413,7 +406,6 @@ export function AdminSettingsPanel({
|
||||
guac,
|
||||
oidcProv,
|
||||
oidcSilent,
|
||||
tailscale,
|
||||
cmdHistory,
|
||||
analytics,
|
||||
sessionSharingEnabled,
|
||||
@@ -435,7 +427,6 @@ export function AdminSettingsPanel({
|
||||
getGuacamoleSettings(),
|
||||
getOidcAutoProvision(),
|
||||
getOidcSilentLoginDefault(),
|
||||
getTailscaleSettings(),
|
||||
getCommandHistoryEnabled(),
|
||||
getAnalyticsEnabled(),
|
||||
getSessionSharingGloballyEnabled(),
|
||||
@@ -474,10 +465,6 @@ export function AdminSettingsPanel({
|
||||
setGuacEnabled(guac.value.enabled);
|
||||
setGuacUrl(guac.value.url || "guacd:4822");
|
||||
}
|
||||
if (tailscale.status === "fulfilled") {
|
||||
setTailscaleApiKey(tailscale.value.apiKey ?? "");
|
||||
setTailscaleApiBaseUrl(tailscale.value.apiBaseUrl ?? "");
|
||||
}
|
||||
if (cmdHistory.status === "fulfilled") {
|
||||
setCommandHistoryEnabled(cmdHistory.value.enabled);
|
||||
}
|
||||
@@ -886,15 +873,6 @@ export function AdminSettingsPanel({
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSaveTailscaleApiKey() {
|
||||
try {
|
||||
await updateTailscaleSettings(tailscaleApiKey, tailscaleApiBaseUrl);
|
||||
toast.success(t("admin.tailscaleSettingsSaved"));
|
||||
} catch {
|
||||
toast.error(t("admin.tailscaleSettingsSaveFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSaveLogLevel(level: string) {
|
||||
setLogLevel(level);
|
||||
try {
|
||||
@@ -1341,16 +1319,6 @@ export function AdminSettingsPanel({
|
||||
handleSaveLogLevel={handleSaveLogLevel}
|
||||
/>
|
||||
|
||||
<AdminTailscaleSection
|
||||
open={openSections.has("tailscale")}
|
||||
onToggle={() => toggle("tailscale")}
|
||||
tailscaleApiKey={tailscaleApiKey}
|
||||
setTailscaleApiKey={setTailscaleApiKey}
|
||||
tailscaleApiBaseUrl={tailscaleApiBaseUrl}
|
||||
setTailscaleApiBaseUrl={setTailscaleApiBaseUrl}
|
||||
handleSaveTailscaleApiKey={handleSaveTailscaleApiKey}
|
||||
/>
|
||||
|
||||
<AdminSSOSection
|
||||
open={openSections.has("sso")}
|
||||
onToggle={() => toggle("sso")}
|
||||
|
||||
@@ -67,6 +67,7 @@ import {
|
||||
import type { Host, VaultProfile } from "@/types/ui-types";
|
||||
import type { SSHHost, TunnelStatus } from "@/types";
|
||||
import { useTabsSafe } from "@/shell/TabContext";
|
||||
import { updatePluginHostSettings } from "@/api/plugins-api";
|
||||
import {
|
||||
buildHostEditorPayload,
|
||||
createHostEditorForm,
|
||||
@@ -333,6 +334,29 @@ export function HostEditor({
|
||||
.finally(() => setTailscaleLoading(false));
|
||||
}, [form.authType]);
|
||||
|
||||
/**
|
||||
* Writes each plugin's host-scope values through its own route.
|
||||
*
|
||||
* One request per plugin rather than one per field, and a plugin that
|
||||
* rejects a value is reported without failing the host save that already
|
||||
* succeeded.
|
||||
*/
|
||||
const savePluginHostSettings = async (
|
||||
hostId: number,
|
||||
values: Record<string, Record<string, unknown>> | undefined,
|
||||
) => {
|
||||
if (!values || !Number.isInteger(hostId)) return;
|
||||
|
||||
for (const [pluginId, fields] of Object.entries(values)) {
|
||||
if (!fields || Object.keys(fields).length === 0) continue;
|
||||
try {
|
||||
await updatePluginHostSettings(pluginId, hostId, fields);
|
||||
} catch {
|
||||
toast.error(t("settings.pluginSettingsSaveFailed"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
setSaving(true);
|
||||
try {
|
||||
@@ -350,6 +374,10 @@ export function HostEditor({
|
||||
? await updateSSHHost(Number(host.id), data)
|
||||
: await createSSHHost(data);
|
||||
}
|
||||
// After the host: a new one has no id to scope settings to until it
|
||||
// exists. A failure here must not claim the host itself failed to save.
|
||||
await savePluginHostSettings(Number(saved.id), form.pluginSettings);
|
||||
|
||||
toast.success(host ? t("hosts.hostUpdated") : t("hosts.hostCreated"));
|
||||
setPreviewTerminalTheme(null);
|
||||
onSave(saved);
|
||||
|
||||
@@ -342,6 +342,13 @@ export function createHostEditorForm(
|
||||
excludedMounts: [] as string[],
|
||||
monitoredMounts: [] as Array<{ path: string; label?: string }>,
|
||||
},
|
||||
|
||||
// Host-scope plugin settings, keyed by plugin id. Loaded with the host and
|
||||
// saved through the plugin's own scoped route, not in the host payload.
|
||||
pluginSettings: (host?.pluginSettings ?? {}) as Record<
|
||||
string,
|
||||
Record<string, unknown>
|
||||
>,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user