Files
Termix/src/ui/desktop/apps/admin/tabs/GeneralSettingsTab.tsx
T
e9e30cd318 v2.0.0 (#621)
* Guacd, Docker-Compose, RDP (#475)

* fix select edit host but not update view (#438)

* fix: Checksum issue with chocolatey

* fix: Remove homebrew old stuff

* Add Korean translation (#439)

Co-authored-by: 송준우 <2484@coreit.co.kr>

* feat: Automate flatpak

* fix: Add imagemagik to electron builder to resolve build error

* fix: Build error with runtime repo flag

* fix: Flatpak runtime error and install freedesktop ver warning

* fix: Flatpak runtime error and install freedesktop ver warning

* feat: Re-add homebrew cask and move scripts to backend

* fix: No sandbox flag issue

* fix: Change name for electron macos cask output

* fix: Sandbox error with Linux

* fix: Remove comming soon for app stores in readme

* Adding Comment at the end of the public_key on the host on deploy (#440)

* Add termix.rb Cask file

* Update Termix to version 1.9.0 with new checksum

* Update README to remove 'coming soon' notes

* -Add New Interface for Credential DB
-Add Credential Name as a comment into the server authorized_key file

---------

Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>

* Sudo auto fill password (#441)

* Add termix.rb Cask file

* Update Termix to version 1.9.0 with new checksum

* Update README to remove 'coming soon' notes

* Feature Sudo password auto-fill;

* Fix locale json shema;

---------

Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>

* Added Italian Language; (#445)

* Add termix.rb Cask file

* Update Termix to version 1.9.0 with new checksum

* Update README to remove 'coming soon' notes

* Added Italian Language;

---------

Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>

* Auto collapse snippet folders (#448)

* Add termix.rb Cask file

* Update Termix to version 1.9.0 with new checksum

* Update README to remove 'coming soon' notes

* feat: Add collapsable snippets (customizable in user profile)

* Translations (#447)

* Add termix.rb Cask file

* Update Termix to version 1.9.0 with new checksum

* Update README to remove 'coming soon' notes

* Added Italian Language;

* Fix translations;

Removed duplicate keys, synchronised other languages using English as the source, translated added keys, fixed inaccurate translations.

---------

Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>

* Remove PTY-level keepalive (#449)

* Add termix.rb Cask file

* Update Termix to version 1.9.0 with new checksum

* Update README to remove 'coming soon' notes

* Remove PTY-level keepalive to prevent unwanted terminal output; use SSH-level keepalive instead

---------

Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>

* feat: add Guacamole support for RDP, VNC, and Telnet connections

- Implemented WebSocket support for Guacamole in Nginx configuration.
- Added REST API endpoints for generating connection tokens and checking guacd status.
- Created Guacamole server using guacamole-lite for handling connections.
- Developed frontend components for testing RDP/VNC connections and displaying the remote session.
- Updated package dependencies to include guacamole-common-js and guacamole-lite.
- Enhanced logging for Guacamole operations.

* feat: enhance Guacamole support with RDP and VNC connection settings and UI updates

* feat: Seperate server stats and tunnel management (improved both UI's) then started initial docker implementation

* fix: finalize adding docker to db

* fix: merge syntax errors

* feat: implement mouse coordinate adjustment based on scale factor in GuacamoleDisplay

* feat: add TypeScript definitions for guacamole-common-js module

* feat: enhance Mouse.State constructor to accept optional parameters and object destructuring

* feat: Add support for RDP and VNC connections in SSH host management

- Introduced connectionType field to differentiate between SSH, RDP, VNC, and Telnet in host data structures.
- Updated backend routes to handle RDP/VNC specific fields: domain, security, and ignoreCert.
- Enhanced the HostManagerEditor to include RDP/VNC specific settings and authentication options.
- Implemented token retrieval for RDP/VNC connections using Guacamole API.
- Updated UI components to reflect connection type changes and provide appropriate connection buttons.
- Removed the GuacamoleTestDialog component as its functionality is integrated into the HostManagerEditor.
- Adjusted the TopNavbar and Host components to accommodate new connection types and their respective actions.

* feat: Enhance Guacamole integration with extended configuration options

- Added detailed Guacamole configuration interface for RDP/VNC/Telnet connections, including display, audio, performance, and session settings.
- Implemented logging for token requests and received options for better debugging.
- Updated HostManagerEditor to support new Guacamole configuration fields with validation and default values.
- Integrated Guacamole configuration parsing in HostManagerViewer and Host components.
- Enhanced API requests to include extended Guacamole configuration parameters in the token request.
- Refactored code to convert camelCase configuration keys to kebab-case for compatibility with Guacamole API.

* feat: merge guacd into 2.0.0 and improve UI for host manager and made general bug fixes

---------

Co-authored-by: Tran Trung Kien <kientt13.7@gmail.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: junu <bigdwarf_@naver.com>
Co-authored-by: 송준우 <2484@coreit.co.kr>
Co-authored-by: SlimGary <trash.slim@gmail.com>
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: Nunzio Marfè <nunzio.marfe@protonmail.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat: rename api routes and files

* feat: improve guacd ui/backend

* feat: improve guacd ui/backend

* fix: state persistance issues causing refresh

* feat: improge guacd connections, fixed telnet not opening, and improved general guacd integration

* feat: continue improving integration also with bug fixes

* Merge 2.0.0 with 2.0.0 that includes bug fixes (#620)

* Guacd, Docker-Compose, RDP (#475)

* fix select edit host but not update view (#438)

* fix: Checksum issue with chocolatey

* fix: Remove homebrew old stuff

* Add Korean translation (#439)

Co-authored-by: 송준우 <2484@coreit.co.kr>

* feat: Automate flatpak

* fix: Add imagemagik to electron builder to resolve build error

* fix: Build error with runtime repo flag

* fix: Flatpak runtime error and install freedesktop ver warning

* fix: Flatpak runtime error and install freedesktop ver warning

* feat: Re-add homebrew cask and move scripts to backend

* fix: No sandbox flag issue

* fix: Change name for electron macos cask output

* fix: Sandbox error with Linux

* fix: Remove comming soon for app stores in readme

* Adding Comment at the end of the public_key on the host on deploy (#440)

* Add termix.rb Cask file

* Update Termix to version 1.9.0 with new checksum

* Update README to remove 'coming soon' notes

* -Add New Interface for Credential DB
-Add Credential Name as a comment into the server authorized_key file

---------

Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>

* Sudo auto fill password (#441)

* Add termix.rb Cask file

* Update Termix to version 1.9.0 with new checksum

* Update README to remove 'coming soon' notes

* Feature Sudo password auto-fill;

* Fix locale json shema;

---------

Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>

* Added Italian Language; (#445)

* Add termix.rb Cask file

* Update Termix to version 1.9.0 with new checksum

* Update README to remove 'coming soon' notes

* Added Italian Language;

---------

Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>

* Auto collapse snippet folders (#448)

* Add termix.rb Cask file

* Update Termix to version 1.9.0 with new checksum

* Update README to remove 'coming soon' notes

* feat: Add collapsable snippets (customizable in user profile)

* Translations (#447)

* Add termix.rb Cask file

* Update Termix to version 1.9.0 with new checksum

* Update README to remove 'coming soon' notes

* Added Italian Language;

* Fix translations;

Removed duplicate keys, synchronised other languages using English as the source, translated added keys, fixed inaccurate translations.

---------

Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>

* Remove PTY-level keepalive (#449)

* Add termix.rb Cask file

* Update Termix to version 1.9.0 with new checksum

* Update README to remove 'coming soon' notes

* Remove PTY-level keepalive to prevent unwanted terminal output; use SSH-level keepalive instead

---------

Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>

* feat: add Guacamole support for RDP, VNC, and Telnet connections

- Implemented WebSocket support for Guacamole in Nginx configuration.
- Added REST API endpoints for generating connection tokens and checking guacd status.
- Created Guacamole server using guacamole-lite for handling connections.
- Developed frontend components for testing RDP/VNC connections and displaying the remote session.
- Updated package dependencies to include guacamole-common-js and guacamole-lite.
- Enhanced logging for Guacamole operations.

* feat: enhance Guacamole support with RDP and VNC connection settings and UI updates

* feat: Seperate server stats and tunnel management (improved both UI's) then started initial docker implementation

* fix: finalize adding docker to db

* fix: merge syntax errors

* feat: implement mouse coordinate adjustment based on scale factor in GuacamoleDisplay

* feat: add TypeScript definitions for guacamole-common-js module

* feat: enhance Mouse.State constructor to accept optional parameters and object destructuring

* feat: Add support for RDP and VNC connections in SSH host management

- Introduced connectionType field to differentiate between SSH, RDP, VNC, and Telnet in host data structures.
- Updated backend routes to handle RDP/VNC specific fields: domain, security, and ignoreCert.
- Enhanced the HostManagerEditor to include RDP/VNC specific settings and authentication options.
- Implemented token retrieval for RDP/VNC connections using Guacamole API.
- Updated UI components to reflect connection type changes and provide appropriate connection buttons.
- Removed the GuacamoleTestDialog component as its functionality is integrated into the HostManagerEditor.
- Adjusted the TopNavbar and Host components to accommodate new connection types and their respective actions.

* feat: Enhance Guacamole integration with extended configuration options

- Added detailed Guacamole configuration interface for RDP/VNC/Telnet connections, including display, audio, performance, and session settings.
- Implemented logging for token requests and received options for better debugging.
- Updated HostManagerEditor to support new Guacamole configuration fields with validation and default values.
- Integrated Guacamole configuration parsing in HostManagerViewer and Host components.
- Enhanced API requests to include extended Guacamole configuration parameters in the token request.
- Refactored code to convert camelCase configuration keys to kebab-case for compatibility with Guacamole API.

* feat: merge guacd into 2.0.0 and improve UI for host manager and made general bug fixes

---------

Co-authored-by: Tran Trung Kien <kientt13.7@gmail.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: junu <bigdwarf_@naver.com>
Co-authored-by: 송준우 <2484@coreit.co.kr>
Co-authored-by: SlimGary <trash.slim@gmail.com>
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: Nunzio Marfè <nunzio.marfe@protonmail.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat: rename api routes and files

* feat: improve guacd ui/backend

* feat: improve guacd ui/backend

* fix: state persistance issues causing refresh

* feat: improge guacd connections, fixed telnet not opening, and improved general guacd integration

* feat: continue improving integration also with bug fixes

---------

Co-authored-by: Wesley Reid <starhound@lostsouls.org>
Co-authored-by: Tran Trung Kien <kientt13.7@gmail.com>
Co-authored-by: junu <bigdwarf_@naver.com>
Co-authored-by: 송준우 <2484@coreit.co.kr>
Co-authored-by: SlimGary <trash.slim@gmail.com>
Co-authored-by: Nunzio Marfè <nunzio.marfe@protonmail.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat: allow customizing guacd backened url

* fix: ssh route mistmatching and guacamole url not changing

* chore: increment ver

* feat: change default to work with default compose, added splits creen support, updated readmes

* fix: linux app not starting due to better sqlite isuses, improved copy/paste system so no context menu, added oidc remember me toggle, improved OS detection for sessions, flatpak invalid key, and sharing hosts with other users errors

* fix: global settings not setting

* chore: update compose

* feat: improve the global status input

* chore: cleanup files

* chore: update export/improt with new host fields

* fix: file manager and docker not loading properly

---------

Co-authored-by: Wesley Reid <starhound@lostsouls.org>
Co-authored-by: Tran Trung Kien <kientt13.7@gmail.com>
Co-authored-by: junu <bigdwarf_@naver.com>
Co-authored-by: 송준우 <2484@coreit.co.kr>
Co-authored-by: SlimGary <trash.slim@gmail.com>
Co-authored-by: Nunzio Marfè <nunzio.marfe@protonmail.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-14 20:05:05 -05:00

416 lines
13 KiB
TypeScript

import React from "react";
import { Checkbox } from "@/components/ui/checkbox.tsx";
import { Input } from "@/components/ui/input.tsx";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select.tsx";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { useConfirmation } from "@/hooks/use-confirmation.ts";
import {
updateRegistrationAllowed,
updatePasswordLoginAllowed,
updatePasswordResetAllowed,
getGlobalMonitoringSettings,
updateGlobalMonitoringSettings,
getGuacamoleSettings,
updateGuacamoleSettings,
} from "@/ui/main-axios.ts";
import { Button } from "@/components/ui/button.tsx";
interface GeneralSettingsTabProps {
allowRegistration: boolean;
setAllowRegistration: (value: boolean) => void;
allowPasswordLogin: boolean;
setAllowPasswordLogin: (value: boolean) => void;
allowPasswordReset: boolean;
setAllowPasswordReset: (value: boolean) => void;
oidcConfig: {
client_id: string;
client_secret: string;
issuer_url: string;
authorization_url: string;
token_url: string;
};
}
export function GeneralSettingsTab({
allowRegistration,
setAllowRegistration,
allowPasswordLogin,
setAllowPasswordLogin,
allowPasswordReset,
setAllowPasswordReset,
oidcConfig,
}: GeneralSettingsTabProps): React.ReactElement {
const { t } = useTranslation();
const { confirmWithToast } = useConfirmation();
const [regLoading, setRegLoading] = React.useState(false);
const [passwordLoginLoading, setPasswordLoginLoading] = React.useState(false);
const [passwordResetLoading, setPasswordResetLoading] = React.useState(false);
const [statusInterval, setStatusInterval] = React.useState(60);
const [metricsInterval, setMetricsInterval] = React.useState(30);
const [statusInputValue, setStatusInputValue] = React.useState("60");
const [metricsInputValue, setMetricsInputValue] = React.useState("30");
const [statusUnit, setStatusUnit] = React.useState<"seconds" | "minutes">(
"seconds",
);
const [metricsUnit, setMetricsUnit] = React.useState<"seconds" | "minutes">(
"seconds",
);
const [monitoringLoading, setMonitoringLoading] = React.useState(false);
const [guacEnabled, setGuacEnabled] = React.useState(true);
const [guacUrl, setGuacUrl] = React.useState("guacd:4822");
const [guacLoading, setGuacLoading] = React.useState(false);
React.useEffect(() => {
getGuacamoleSettings()
.then((data) => {
setGuacEnabled(data.enabled);
setGuacUrl(data.url);
})
.catch(() => {
toast.error(t("admin.failedToLoadGuacamoleSettings"));
});
}, [t]);
const saveGuacDebounce = React.useRef<NodeJS.Timeout | null>(null);
const saveGuacSettings = React.useCallback(
(newEnabled: boolean, newUrl: string) => {
if (saveGuacDebounce.current) {
clearTimeout(saveGuacDebounce.current);
}
saveGuacDebounce.current = setTimeout(async () => {
setGuacLoading(true);
try {
await updateGuacamoleSettings({ enabled: newEnabled, url: newUrl });
toast.success(t("admin.guacamoleSettingsSaved"));
} catch {
toast.error(t("admin.failedToSaveGuacamoleSettings"));
} finally {
setGuacLoading(false);
}
}, 800);
},
[t],
);
React.useEffect(() => {
getGlobalMonitoringSettings()
.then((data) => {
setStatusInterval(data.statusCheckInterval);
setMetricsInterval(data.metricsInterval);
setStatusInputValue(String(data.statusCheckInterval));
setMetricsInputValue(String(data.metricsInterval));
})
.catch(() => {
// Use defaults silently
});
}, []);
const saveMonitoringSettings = React.useCallback(
async (newStatus: number, newMetrics: number) => {
setMonitoringLoading(true);
try {
await updateGlobalMonitoringSettings({
statusCheckInterval: newStatus,
metricsInterval: newMetrics,
});
toast.success(t("admin.globalSettingsSaved"));
} catch (error) {
const errorMessage =
error instanceof Error
? error.message
: t("admin.failedToSaveGlobalSettings");
toast.error(errorMessage);
} finally {
setMonitoringLoading(false);
}
},
[t],
);
const handleStatusBlur = () => {
const num = parseInt(statusInputValue) || 0;
const seconds = statusUnit === "minutes" ? num * 60 : num;
const clamped = Math.max(5, Math.min(3600, seconds));
setStatusInterval(clamped);
setStatusInputValue(
statusUnit === "minutes"
? String(Math.round(clamped / 60))
: String(clamped),
);
saveMonitoringSettings(clamped, metricsInterval);
};
const handleMetricsBlur = () => {
const num = parseInt(metricsInputValue) || 0;
const seconds = metricsUnit === "minutes" ? num * 60 : num;
const clamped = Math.max(5, Math.min(3600, seconds));
setMetricsInterval(clamped);
setMetricsInputValue(
metricsUnit === "minutes"
? String(Math.round(clamped / 60))
: String(clamped),
);
saveMonitoringSettings(statusInterval, clamped);
};
const handleToggleRegistration = async (checked: boolean) => {
setRegLoading(true);
try {
await updateRegistrationAllowed(checked);
setAllowRegistration(checked);
} finally {
setRegLoading(false);
}
};
const handleTogglePasswordLogin = async (checked: boolean) => {
if (!checked) {
const hasOIDCConfigured =
oidcConfig.client_id &&
oidcConfig.client_secret &&
oidcConfig.issuer_url &&
oidcConfig.authorization_url &&
oidcConfig.token_url;
if (!hasOIDCConfigured) {
toast.error(t("admin.cannotDisablePasswordLoginWithoutOIDC"), {
duration: 5000,
});
return;
}
confirmWithToast(
t("admin.confirmDisablePasswordLogin"),
async () => {
setPasswordLoginLoading(true);
try {
await updatePasswordLoginAllowed(checked);
setAllowPasswordLogin(checked);
if (allowRegistration) {
await updateRegistrationAllowed(false);
setAllowRegistration(false);
toast.success(t("admin.passwordLoginAndRegistrationDisabled"));
} else {
toast.success(t("admin.passwordLoginDisabled"));
}
} catch {
toast.error(t("admin.failedToUpdatePasswordLoginStatus"));
} finally {
setPasswordLoginLoading(false);
}
},
"destructive",
);
return;
}
setPasswordLoginLoading(true);
try {
await updatePasswordLoginAllowed(checked);
setAllowPasswordLogin(checked);
} finally {
setPasswordLoginLoading(false);
}
};
const handleTogglePasswordReset = async (checked: boolean) => {
setPasswordResetLoading(true);
try {
await updatePasswordResetAllowed(checked);
setAllowPasswordReset(checked);
} finally {
setPasswordResetLoading(false);
}
};
return (
<div className="space-y-6">
<div className="rounded-lg border-2 border-border bg-card p-4 space-y-4">
<h3 className="text-lg font-semibold">{t("admin.userRegistration")}</h3>
<label className="flex items-center gap-2">
<Checkbox
checked={allowRegistration}
onCheckedChange={handleToggleRegistration}
disabled={regLoading || !allowPasswordLogin}
/>
{t("admin.allowNewAccountRegistration")}
{!allowPasswordLogin && (
<span className="text-xs text-muted-foreground">
({t("admin.requiresPasswordLogin")})
</span>
)}
</label>
<label className="flex items-center gap-2">
<Checkbox
checked={allowPasswordLogin}
onCheckedChange={handleTogglePasswordLogin}
disabled={passwordLoginLoading}
/>
{t("admin.allowPasswordLogin")}
</label>
<label className="flex items-center gap-2">
<Checkbox
checked={allowPasswordReset}
onCheckedChange={handleTogglePasswordReset}
disabled={passwordResetLoading || !allowPasswordLogin}
/>
{t("admin.allowPasswordReset")}
{!allowPasswordLogin && (
<span className="text-xs text-muted-foreground">
({t("admin.requiresPasswordLogin")})
</span>
)}
</label>
</div>
<div className="rounded-lg border-2 border-border bg-card p-4 space-y-4">
<h3 className="text-lg font-semibold">
{t("admin.monitoringDefaults")}
</h3>
<p className="text-sm text-muted-foreground">
{t("admin.monitoringDefaultsDesc")}
</p>
<div className="space-y-3">
<div>
<label className="text-sm font-medium">
{t("admin.globalStatusCheckInterval")}
</label>
<div className="flex gap-2 mt-1">
<Input
type="number"
value={statusInputValue}
onChange={(e) => setStatusInputValue(e.target.value)}
onBlur={handleStatusBlur}
disabled={monitoringLoading}
className="flex-1"
/>
<Select
value={statusUnit}
onValueChange={(value: "seconds" | "minutes") => {
setStatusUnit(value);
setStatusInputValue(
value === "minutes"
? String(Math.round(statusInterval / 60))
: String(statusInterval),
);
}}
>
<SelectTrigger className="w-[120px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="seconds">
{t("hosts.intervalSeconds")}
</SelectItem>
<SelectItem value="minutes">
{t("hosts.intervalMinutes")}
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div>
<label className="text-sm font-medium">
{t("admin.globalMetricsInterval")}
</label>
<div className="flex gap-2 mt-1">
<Input
type="number"
value={metricsInputValue}
onChange={(e) => setMetricsInputValue(e.target.value)}
onBlur={handleMetricsBlur}
disabled={monitoringLoading}
className="flex-1"
/>
<Select
value={metricsUnit}
onValueChange={(value: "seconds" | "minutes") => {
setMetricsUnit(value);
setMetricsInputValue(
value === "minutes"
? String(Math.round(metricsInterval / 60))
: String(metricsInterval),
);
}}
>
<SelectTrigger className="w-[120px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="seconds">
{t("hosts.intervalSeconds")}
</SelectItem>
<SelectItem value="minutes">
{t("hosts.intervalMinutes")}
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
</div>
<div className="rounded-lg border-2 border-border bg-card p-4 space-y-4">
<h3 className="text-lg font-semibold">
{t("admin.guacamoleIntegration")}
</h3>
<p className="text-sm text-muted-foreground">
{t("admin.guacamoleIntegrationDesc")}
</p>
<Button
variant="outline"
size="sm"
className="h-8 px-3 text-xs"
onClick={() =>
window.open("https://docs.termix.site/remote-desktop", "_blank")
}
>
{t("common.documentation")}
</Button>
<label className="flex items-center gap-2">
<Checkbox
checked={guacEnabled}
onCheckedChange={(checked) => {
const val = checked === true;
setGuacEnabled(val);
saveGuacSettings(val, guacUrl);
}}
disabled={guacLoading}
/>
{t("admin.enableGuacamole")}
</label>
{guacEnabled && (
<div>
<label className="text-sm font-medium">{t("admin.guacdUrl")}</label>
<Input
className="mt-1"
value={guacUrl}
placeholder={t("admin.guacdUrlPlaceholder")}
disabled={guacLoading}
onChange={(e) => {
setGuacUrl(e.target.value);
saveGuacSettings(guacEnabled, e.target.value);
}}
/>
<p className="text-xs text-muted-foreground mt-1">
{t("admin.guacdUrlNote")}
</p>
</div>
)}
</div>
</div>
);
}