release-2.4.1 (#921)

* chore(deps-dev): bump the dev-minor-updates group across 1 directory with 12 updates (#910)

Bumps the dev-minor-updates group with 12 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@radix-ui/react-select](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/select) | `2.2.6` | `2.3.1` |
| [@radix-ui/react-slider](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/slider) | `1.3.6` | `1.4.1` |
| [@radix-ui/react-slot](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/slot) | `1.2.5` | `1.3.0` |
| [@radix-ui/react-switch](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/switch) | `1.2.6` | `1.3.1` |
| [cytoscape](https://github.com/cytoscape/cytoscape.js) | `3.33.4` | `3.34.0` |
| [electron](https://github.com/electron/electron) | `42.2.0` | `42.4.1` |
| [electron-builder](https://github.com/electron-userland/electron-builder/tree/HEAD/packages/electron-builder) | `26.8.1` | `26.15.3` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.16.0` | `1.20.0` |
| [radix-ui](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/radix-ui) | `1.4.3` | `1.6.0` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.76.1` | `7.79.0` |
| [sharp](https://github.com/lovell/sharp) | `0.34.5` | `0.35.1` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.60.0` | `8.61.1` |



Updates `@radix-ui/react-select` from 2.2.6 to 2.3.1
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/select/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/select)

Updates `@radix-ui/react-slider` from 1.3.6 to 1.4.1
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/slider/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/slider)

Updates `@radix-ui/react-slot` from 1.2.5 to 1.3.0
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/slot/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/slot)

Updates `@radix-ui/react-switch` from 1.2.6 to 1.3.1
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/switch/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/switch)

Updates `cytoscape` from 3.33.4 to 3.34.0
- [Release notes](https://github.com/cytoscape/cytoscape.js/releases)
- [Commits](https://github.com/cytoscape/cytoscape.js/compare/v3.33.4...v3.34.0)

Updates `electron` from 42.2.0 to 42.4.1
- [Release notes](https://github.com/electron/electron/releases)
- [Commits](https://github.com/electron/electron/compare/v42.2.0...v42.4.1)

Updates `electron-builder` from 26.8.1 to 26.15.3
- [Release notes](https://github.com/electron-userland/electron-builder/releases)
- [Changelog](https://github.com/electron-userland/electron-builder/blob/master/packages/electron-builder/CHANGELOG.md)
- [Commits](https://github.com/electron-userland/electron-builder/commits/electron-builder@26.15.3/packages/electron-builder)

Updates `lucide-react` from 1.16.0 to 1.20.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.20.0/packages/lucide-react)

Updates `radix-ui` from 1.4.3 to 1.6.0
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/radix-ui/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/radix-ui)

Updates `react-hook-form` from 7.76.1 to 7.79.0
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.76.1...v7.79.0)

Updates `sharp` from 0.34.5 to 0.35.1
- [Release notes](https://github.com/lovell/sharp/releases)
- [Commits](https://github.com/lovell/sharp/compare/v0.34.5...v0.35.1)

Updates `typescript-eslint` from 8.60.0 to 8.61.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.61.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@radix-ui/react-select"
  dependency-version: 2.3.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: "@radix-ui/react-slider"
  dependency-version: 1.4.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: "@radix-ui/react-slot"
  dependency-version: 1.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: "@radix-ui/react-switch"
  dependency-version: 1.3.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: cytoscape
  dependency-version: 3.34.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: electron
  dependency-version: 42.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: electron-builder
  dependency-version: 26.15.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: lucide-react
  dependency-version: 1.18.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: radix-ui
  dependency-version: 1.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: react-hook-form
  dependency-version: 7.79.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: sharp
  dependency-version: 0.35.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: typescript-eslint
  dependency-version: 8.61.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump node in /docker in the docker-major-updates group (#914)

Bumps the docker-major-updates group in /docker with 1 update: node.


Updates `node` from 24-slim to 26-slim

---
updated-dependencies:
- dependency-name: node
  dependency-version: 26-slim
  dependency-type: direct:production
  dependency-group: docker-major-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* ci(deps): bump the github-actions group with 2 updates (#915)

Bumps the github-actions group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [actions/setup-node](https://github.com/actions/setup-node).


Updates `actions/checkout` from 5 to 6
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

Updates `actions/setup-node` from 4 to 6
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump the prod-minor-updates group across 1 directory with 5 updates (#916)

Bumps the prod-minor-updates group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [axios](https://github.com/axios/axios) | `1.17.0` | `1.18.0` |
| [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) | `12.10.0` | `12.11.1` |
| [body-parser](https://github.com/expressjs/body-parser) | `2.2.2` | `2.3.0` |
| [multer](https://github.com/expressjs/multer) | `2.1.1` | `2.2.0` |
| [undici](https://github.com/nodejs/undici) | `8.4.0` | `8.5.0` |



Updates `axios` from 1.17.0 to 1.18.0
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.17.0...v1.18.0)

Updates `better-sqlite3` from 12.10.0 to 12.11.1
- [Release notes](https://github.com/WiseLibs/better-sqlite3/releases)
- [Commits](https://github.com/WiseLibs/better-sqlite3/compare/v12.10.0...v12.11.1)

Updates `body-parser` from 2.2.2 to 2.3.0
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/v2.2.2...v2.3.0)

Updates `multer` from 2.1.1 to 2.2.0
- [Release notes](https://github.com/expressjs/multer/releases)
- [Changelog](https://github.com/expressjs/multer/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expressjs/multer/compare/v2.1.1...v2.2.0)

Updates `undici` from 8.4.0 to 8.5.0
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v8.4.0...v8.5.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: better-sqlite3
  dependency-version: 12.11.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: body-parser
  dependency-version: 2.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: multer
  dependency-version: 2.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: undici
  dependency-version: 8.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat: add unlink button for dual auth

* fix: general bug fixes

* fix: general qol/small feature additions

* fix: 100mb max upload size

* fix: terminal syntax not handling carriage returns or shell prompt lines properly

* feat: continued general improvements

* fix: terminal outputting success right after folder path

* chore: update release notes

* feat: continued fixes and improvements

* chore: lint, format, and bump version to 2.4.1

* chore: sync Crowdin translations for 2.4.1

* fix: rebase dev branch onto main before Crowdin download

* fix: use merge instead of rebase to sync main before Crowdin

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
Luke Gustafson
2026-06-21 15:55:41 -05:00
committed by GitHub
co-authored by dependabot[bot]
parent 8072b4ede9
commit d1a8dcf3c6
165 changed files with 87244 additions and 60766 deletions
+159 -4
View File
@@ -9,7 +9,7 @@ import {
deleteUser,
revokeAllUserSessions,
createRole,
registerUser,
adminCreateUser,
makeUserAdmin,
removeAdminStatus,
getRegistrationAllowed,
@@ -28,6 +28,8 @@ import {
updateGuacamoleSettings,
getOidcAutoProvision,
updateOidcAutoProvision,
getOidcSilentLoginDefault,
updateOidcSilentLoginDefault,
getCommandHistoryEnabled,
updateCommandHistoryEnabled,
isElectron,
@@ -36,7 +38,16 @@ import {
import {
getTailscaleSettings,
updateTailscaleSettings,
getHostDefaults,
updateHostDefaults,
type HostDefaults,
} from "@/api/settings-api";
import {
getAcmeSslSettings,
updateAcmeSslSettings,
requestAcmeCertificate,
type AcmeSettings,
} from "@/api/acme-ssl-api";
import {
getAdminSSOProviders,
updateSSOProvider,
@@ -58,7 +69,9 @@ import { getBasePath } from "@/lib/base-path";
import {
AdminDatabaseSection,
AdminGeneralSettingsSection,
AdminHostDefaultsSection,
AdminSSOSection,
AdminSSLSection,
} from "./AdminSettingsSections";
import { SSOProviderDialog } from "./SSOProviderDialog";
import { AdminApiKeysSection } from "./AdminApiKeysSection";
@@ -67,6 +80,7 @@ import {
AdminCreateUserDialog,
AdminEditUserDialog,
AdminLinkAccountDialog,
AdminUnlinkAccountDialog,
} from "./AdminUserDialogs";
type ApiErrorLike = {
@@ -97,9 +111,11 @@ export function AdminSettingsPanel() {
const [logLevel, setLogLevel] = useState("info");
const [tailscaleApiKey, setTailscaleApiKey] = useState("");
const [commandHistoryEnabled, setCommandHistoryEnabled] = useState(true);
const [hostDefaults, setHostDefaults] = useState<HostDefaults>({});
// SSO / auto-provision state
const [oidcAutoProvision, setOidcAutoProvision] = useState(false);
const [oidcSilentLoginDefault, setOidcSilentLoginDefault] = useState(false);
const [ssoProviders, setSsoProviders] = useState<SSOProvider[]>([]);
const [ssoDialogOpen, setSsoDialogOpen] = useState(false);
const [ssoDialogProvider, setSsoDialogProvider] =
@@ -127,6 +143,13 @@ export function AdminSettingsPanel() {
isOidc: boolean;
} | null>(null);
// Unlink account dialog
const [unlinkAccountOpen, setUnlinkAccountOpen] = useState(false);
const [unlinkAccountTarget, setUnlinkAccountTarget] = useState<{
id: string;
username: string;
} | null>(null);
// Create role form
const [showCreateRole, setShowCreateRole] = useState(false);
const [newRoleName, setNewRoleName] = useState("");
@@ -147,6 +170,22 @@ export function AdminSettingsPanel() {
const [exportLoading, setExportLoading] = useState(false);
const [importLoading, setImportLoading] = useState(false);
// ACME SSL state
const defaultAcmeSettings: AcmeSettings = {
enabled: false,
domain: "",
email: "",
challengeType: "http-webroot",
cloudflareToken: "",
lastIssuedAt: null,
certStatus: "none",
certExpiresAt: null,
};
const [acmeSettings, setAcmeSettings] =
useState<AcmeSettings>(defaultAcmeSettings);
const [cloudflareTokenDraft, setCloudflareTokenDraft] = useState("");
const [acmeRequesting, setAcmeRequesting] = useState(false);
const [users, setUsers] = useState<AdminUser[]>([]);
const [sessions, setSessions] = useState<AdminSession[]>([]);
const [roles, setRoles] = useState<Role[]>([]);
@@ -217,6 +256,7 @@ export function AdminSettingsPanel() {
level,
guac,
oidcProv,
oidcSilent,
tailscale,
cmdHistory,
] = await Promise.allSettled([
@@ -228,6 +268,7 @@ export function AdminSettingsPanel() {
getLogLevel(),
getGuacamoleSettings(),
getOidcAutoProvision(),
getOidcSilentLoginDefault(),
getTailscaleSettings(),
getCommandHistoryEnabled(),
]);
@@ -237,6 +278,8 @@ export function AdminSettingsPanel() {
setAllowPasswordLogin(pwLogin.value.allowed);
if (oidcProv.status === "fulfilled")
setOidcAutoProvision(oidcProv.value.enabled);
if (oidcSilent.status === "fulfilled")
setOidcSilentLoginDefault(oidcSilent.value.enabled);
if (pwReset.status === "fulfilled") setAllowPasswordReset(pwReset.value);
if (timeout.status === "fulfilled")
setSessionTimeout(String(timeout.value.timeoutHours));
@@ -258,6 +301,14 @@ export function AdminSettingsPanel() {
} catch {
// non-fatal
}
getHostDefaults()
.then((d) => setHostDefaults(d))
.catch(() => {});
getAcmeSslSettings()
.then((s) => setAcmeSettings(s))
.catch(() => {});
}
async function loadSSOProviders() {
@@ -273,6 +324,15 @@ export function AdminSettingsPanel() {
setOpenSection((prev) => (prev === id ? null : id));
}
async function handleSaveHostDefaults() {
try {
await updateHostDefaults(hostDefaults);
toast.success(t("admin.hostDefaultsSaved"));
} catch {
toast.error(t("admin.hostDefaultsSaveFailed"));
}
}
async function handleToggleRegistration() {
const newVal = !allowRegistration;
setAllowRegistration(newVal);
@@ -289,9 +349,10 @@ export function AdminSettingsPanel() {
setAllowPasswordLogin(newVal);
try {
await updatePasswordLoginAllowed(newVal);
} catch {
} catch (e) {
setAllowPasswordLogin(!newVal);
toast.error(t("admin.updatePasswordLoginFailed"));
const msg = (e as ApiErrorLike).response?.data?.error;
toast.error(msg || t("admin.updatePasswordLoginFailed"));
}
}
@@ -306,6 +367,17 @@ export function AdminSettingsPanel() {
}
}
async function handleToggleOidcSilentLoginDefault() {
const newVal = !oidcSilentLoginDefault;
setOidcSilentLoginDefault(newVal);
try {
await updateOidcSilentLoginDefault(newVal);
} catch {
setOidcSilentLoginDefault(!newVal);
toast.error(t("admin.updateOidcSilentLoginDefaultFailed"));
}
}
async function handleTogglePasswordReset() {
const newVal = !allowPasswordReset;
setAllowPasswordReset(newVal);
@@ -435,6 +507,50 @@ export function AdminSettingsPanel() {
}
}
async function handleSaveAcmeSettings() {
try {
const payload: Parameters<typeof updateAcmeSslSettings>[0] = {
enabled: acmeSettings.enabled,
domain: acmeSettings.domain,
email: acmeSettings.email,
challengeType: acmeSettings.challengeType,
...(cloudflareTokenDraft && { cloudflareToken: cloudflareTokenDraft }),
};
const updated = await updateAcmeSslSettings(payload);
setAcmeSettings(updated);
setCloudflareTokenDraft("");
toast.success(t("admin.sslSaved"));
} catch {
toast.error(t("admin.sslSaveFailed"));
}
}
async function handleRequestAcmeCertificate() {
if (!acmeSettings.domain || !acmeSettings.email) {
toast.error(t("admin.sslRequiresDomain"));
return;
}
setAcmeRequesting(true);
try {
if (cloudflareTokenDraft) {
await updateAcmeSslSettings({
domain: acmeSettings.domain,
email: acmeSettings.email,
challengeType: acmeSettings.challengeType,
cloudflareToken: cloudflareTokenDraft,
});
setCloudflareTokenDraft("");
}
const result = await requestAcmeCertificate();
setAcmeSettings(result);
toast.success(t("admin.sslRequestCertSuccess"));
} catch (e) {
toast.error(apiErrorMessage(e, t("admin.sslRequestCertFailed")));
} finally {
setAcmeRequesting(false);
}
}
function handleProviderSaved(saved: SSOProvider) {
setSsoProviders((prev) => {
const idx = prev.findIndex((p) => p.id === saved.id);
@@ -458,7 +574,7 @@ export function AdminSettingsPanel() {
}
setCreateUserLoading(true);
try {
await registerUser(newUsername.trim(), newPassword);
await adminCreateUser(newUsername.trim(), newPassword);
toast.success(t("admin.createUserSuccess", { username: newUsername }));
setCreateUserOpen(false);
setNewUsername("");
@@ -699,6 +815,8 @@ export function AdminSettingsPanel() {
handleTogglePasswordLogin={handleTogglePasswordLogin}
oidcAutoProvision={oidcAutoProvision}
handleToggleOidcAutoProvision={handleToggleOidcAutoProvision}
oidcSilentLoginDefault={oidcSilentLoginDefault}
handleToggleOidcSilentLoginDefault={handleToggleOidcSilentLoginDefault}
allowPasswordReset={allowPasswordReset}
handleTogglePasswordReset={handleTogglePasswordReset}
commandHistoryEnabled={commandHistoryEnabled}
@@ -751,6 +869,8 @@ export function AdminSettingsPanel() {
setEditUserOpen={setEditUserOpen}
setLinkAccountTarget={setLinkAccountTarget}
setLinkAccountOpen={setLinkAccountOpen}
setUnlinkAccountTarget={setUnlinkAccountTarget}
setUnlinkAccountOpen={setUnlinkAccountOpen}
/>
<AdminSessionsSection
@@ -778,6 +898,14 @@ export function AdminSettingsPanel() {
createRoleLoading={createRoleLoading}
/>
<AdminHostDefaultsSection
open={openSection === "host-defaults"}
onToggle={() => toggle("host-defaults")}
defaults={hostDefaults}
setDefaults={setHostDefaults}
handleSaveDefaults={handleSaveHostDefaults}
/>
<AdminDatabaseSection
open={openSection === "database"}
onToggle={() => toggle("database")}
@@ -789,6 +917,18 @@ export function AdminSettingsPanel() {
handleImportDatabase={handleImportDatabase}
/>
<AdminSSLSection
open={openSection === "ssl"}
onToggle={() => toggle("ssl")}
settings={acmeSettings}
setSettings={setAcmeSettings}
cloudflareTokenDraft={cloudflareTokenDraft}
setCloudflareTokenDraft={setCloudflareTokenDraft}
requesting={acmeRequesting}
handleSave={handleSaveAcmeSettings}
handleRequest={handleRequestAcmeCertificate}
/>
<AdminApiKeysSection
open={openSection === "api-keys"}
onToggle={() => toggle("api-keys")}
@@ -850,6 +990,21 @@ export function AdminSettingsPanel() {
setUsers={setUsers}
users={users}
/>
<AdminUnlinkAccountDialog
open={unlinkAccountOpen}
onOpenChange={setUnlinkAccountOpen}
unlinkAccountTarget={unlinkAccountTarget}
onSuccess={(userId) =>
setUsers((prev) =>
prev.map((u) =>
u.id === userId
? { ...u, isOidc: false, passwordHash: undefined }
: u,
),
)
}
/>
</div>
);
}