Co-authored-by: Claude Haiku 4.5 <[email protected]>
This commit is contained in:
Anthony Stirling
2026-03-02 13:56:39 +00:00
committed by GitHub
co-authored by Claude Haiku 4.5
parent 8b25db37ad
commit 012bd1af92
21 changed files with 407 additions and 66 deletions
@@ -10,7 +10,6 @@ import {
Loader,
Group,
TextInput,
PasswordInput,
Select,
Badge,
Table,
@@ -29,6 +28,7 @@ import { useAdminSettings } from "@app/hooks/useAdminSettings";
import PendingBadge from "@app/components/shared/config/PendingBadge";
import { useLoginRequired } from "@app/hooks/useLoginRequired";
import LoginRequiredBanner from "@app/components/shared/config/LoginRequiredBanner";
import EditableSecretField from "@app/components/shared/EditableSecretField";
import apiClient from "@app/services/apiClient";
import LocalIcon from "@app/components/shared/LocalIcon";
import databaseManagementService, { DatabaseBackupFile } from "@app/services/databaseManagementService";
@@ -515,17 +515,15 @@ export default function AdminDatabaseSection() {
</div>
<div>
<PasswordInput
label={
<Group gap="xs">
<span>{t("admin.settings.database.password.label", "Password")}</span>
<PendingBadge show={isFieldPending("password")} />
</Group>
}
<Group gap="xs" align="center" mb={4}>
<span style={{ fontWeight: 500, fontSize: "0.875rem" }}>{t("admin.settings.database.password.label", "Password")}</span>
<PendingBadge show={isFieldPending("password")} />
</Group>
<EditableSecretField
description={t("admin.settings.database.password.description", "Database authentication password")}
value={settings?.password || ""}
onChange={(e) => setSettings({ ...settings, password: e.target.value })}
placeholder="••••••••"
onChange={(value) => setSettings({ ...settings, password: value })}
placeholder="Enter database password"
disabled={!loginEnabled}
/>
</div>
@@ -1,12 +1,13 @@
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { TextInput, NumberInput, Switch, Button, Stack, Paper, Text, Loader, Group, PasswordInput, Anchor } from '@mantine/core';
import { TextInput, NumberInput, Switch, Button, Stack, Paper, Text, Loader, Group, Anchor } from '@mantine/core';
import { alert } from '@app/components/toast';
import RestartConfirmationModal from '@app/components/shared/config/RestartConfirmationModal';
import { useRestartServer } from '@app/components/shared/config/useRestartServer';
import { useAdminSettings } from '@app/hooks/useAdminSettings';
import PendingBadge from '@app/components/shared/config/PendingBadge';
import EditableSecretField from '@app/components/shared/EditableSecretField';
import apiClient from '@app/services/apiClient';
interface MailSettingsData {
@@ -174,16 +175,15 @@ export default function AdminMailSection() {
</div>
<div>
<PasswordInput
label={
<Group gap="xs">
<span>{t('admin.settings.mail.password.label', 'SMTP Password')}</span>
<PendingBadge show={isFieldPending('password')} />
</Group>
}
<Group gap="xs" align="center" mb={4}>
<span style={{ fontWeight: 500, fontSize: '0.875rem' }}>{t('admin.settings.mail.password.label', 'SMTP Password')}</span>
<PendingBadge show={isFieldPending('password')} />
</Group>
<EditableSecretField
description={t('admin.settings.mail.password.description', 'SMTP authentication password')}
value={settings.password || ''}
onChange={(e) => setSettings({ ...settings, password: e.target.value })}
onChange={(value) => setSettings({ ...settings, password: value })}
placeholder="Enter SMTP password"
/>
</div>
@@ -18,11 +18,29 @@ export default function ApiKeys() {
const copy = async (text: string, tag: string) => {
try {
await navigator.clipboard.writeText(text);
setCopied(tag);
setTimeout(() => setCopied(null), 1600);
// Try modern Clipboard API first (requires HTTPS)
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
setCopied(tag);
setTimeout(() => setCopied(null), 1600);
} else {
// Fallback for HTTP: use old execCommand method
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
if (document.execCommand('copy')) {
setCopied(tag);
setTimeout(() => setCopied(null), 1600);
}
document.body.removeChild(textarea);
}
} catch (e) {
console.error(e);
console.error('Failed to copy:', e);
}
};