feat: update SystemSettingsPage to remove AuthSettingsTab and enhance AppSettingsTab with registration settings

This commit is contained in:
shiyu
2026-02-09 12:46:46 +08:00
parent 451e8555d5
commit 97a3c58f0f
3 changed files with 90 additions and 136 deletions
@@ -2,21 +2,20 @@ import { Alert, message, Tabs, Space } from 'antd';
import { useEffect, useState } from 'react';
import PageCard from '../../components/PageCard';
import { getAllConfig, setConfig } from '../../api/config';
import { AppstoreOutlined, RobotOutlined, DatabaseOutlined, SkinOutlined, MailOutlined, CloudSyncOutlined, UserOutlined } from '@ant-design/icons';
import { AppstoreOutlined, RobotOutlined, DatabaseOutlined, SkinOutlined, MailOutlined, CloudSyncOutlined } from '@ant-design/icons';
import { useTheme } from '../../contexts/ThemeContext';
import '../../styles/settings-tabs.css';
import { useI18n } from '../../i18n';
import AppearanceSettingsTab from './components/AppearanceSettingsTab';
import AppSettingsTab from './components/AppSettingsTab';
import AuthSettingsTab from './components/AuthSettingsTab';
import AiSettingsTab from './components/AiSettingsTab';
import VectorDbSettingsTab from './components/VectorDbSettingsTab';
import EmailSettingsTab from './components/EmailSettingsTab';
import ProtocolMappingsTab from './components/ProtocolMappingsTab';
type TabKey = 'appearance' | 'app' | 'auth' | 'email' | 'ai' | 'vector-db' | 'mappings';
type TabKey = 'appearance' | 'app' | 'email' | 'ai' | 'vector-db' | 'mappings';
const TAB_KEYS: TabKey[] = ['appearance', 'app', 'auth', 'email', 'ai', 'vector-db', 'mappings'];
const TAB_KEYS: TabKey[] = ['appearance', 'app', 'email', 'ai', 'vector-db', 'mappings'];
const DEFAULT_TAB: TabKey = 'appearance';
const isValidTab = (key?: string): key is TabKey => !!key && (TAB_KEYS as string[]).includes(key);
@@ -169,22 +168,6 @@ export default function SystemSettingsPage({ tabKey, onTabNavigate }: SystemSett
/>
),
},
{
key: 'auth',
label: (
<span>
<UserOutlined style={{ marginRight: 8 }} />
{t('Registration Settings')}
</span>
),
children: (
<AuthSettingsTab
config={config}
loading={loading}
onSave={handleSave}
/>
),
},
{
key: 'email',
label: (
@@ -1,4 +1,6 @@
import { Form, Input, Button } from 'antd';
import { Alert, Button, Divider, Form, Input, Select, Switch, message } from 'antd';
import { useEffect, useMemo, useState } from 'react';
import { rolesApi, type RoleInfo } from '../../../api/roles';
import { useI18n } from '../../../i18n';
interface AppConfigKey {
@@ -21,14 +23,56 @@ export default function AppSettingsTab({
configKeys,
}: AppSettingsTabProps) {
const { t } = useI18n();
const [rolesLoading, setRolesLoading] = useState(false);
const [roles, setRoles] = useState<RoleInfo[]>([]);
useEffect(() => {
let mounted = true;
async function loadRoles() {
setRolesLoading(true);
try {
const list = await rolesApi.list();
if (!mounted) return;
setRoles(list);
} catch (e: any) {
message.error(e.message || t('Load failed'));
} finally {
if (mounted) setRolesLoading(false);
}
}
loadRoles();
return () => {
mounted = false;
};
}, [t]);
const initialValues = useMemo(() => {
const allowRegister = (config.AUTH_ALLOW_REGISTER || '').trim().toLowerCase() === 'true';
const roleIdRaw = (config.AUTH_DEFAULT_REGISTER_ROLE_ID || '').trim();
const roleId = roleIdRaw ? Number(roleIdRaw) : undefined;
return {
...Object.fromEntries(configKeys.map(({ key, default: def }) => [key, config[key] ?? def ?? ''])),
AUTH_ALLOW_REGISTER: allowRegister,
AUTH_DEFAULT_REGISTER_ROLE_ID: Number.isFinite(roleId) ? roleId : undefined,
};
}, [config, configKeys]);
return (
<Form
layout="vertical"
initialValues={{
...Object.fromEntries(configKeys.map(({ key, default: def }) => [key, config[key] ?? def ?? ''])),
initialValues={initialValues}
onFinish={async (vals) => {
const payload: Record<string, unknown> = {};
for (const { key } of configKeys) {
payload[key] = vals[key];
}
const allow = !!vals.AUTH_ALLOW_REGISTER;
payload.AUTH_ALLOW_REGISTER = allow ? 'true' : 'false';
if (allow) {
payload.AUTH_DEFAULT_REGISTER_ROLE_ID = String(vals.AUTH_DEFAULT_REGISTER_ROLE_ID);
}
await onSave(payload);
}}
onFinish={onSave}
style={{ marginTop: 24 }}
key={JSON.stringify(config)}
>
@@ -37,6 +81,45 @@ export default function AppSettingsTab({
<Input size="large" />
</Form.Item>
))}
<Divider orientation="left">{t('Registration Settings')}</Divider>
<Alert
type="info"
showIcon
message={t('Enabling registration allows new users to sign up and assigns them the default role')}
style={{ marginBottom: 16 }}
/>
<Form.Item
name="AUTH_ALLOW_REGISTER"
label={t('Enable Registration')}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item dependencies={['AUTH_ALLOW_REGISTER']} noStyle>
{({ getFieldValue }) => {
const enabled = !!getFieldValue('AUTH_ALLOW_REGISTER');
return (
<Form.Item
name="AUTH_DEFAULT_REGISTER_ROLE_ID"
label={t('Default Role for New Registrations')}
rules={enabled ? [{ required: true, message: t('Please select default role') }] : []}
>
<Select
size="large"
loading={rolesLoading}
disabled={!enabled || rolesLoading}
placeholder={t('Select roles')}
options={roles.map((r) => ({ value: r.id, label: r.name }))}
/>
</Form.Item>
);
}}
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" loading={loading} block>
{t('Save')}
@@ -1,112 +0,0 @@
import { Alert, Button, Form, Select, Switch, message } from 'antd';
import { useEffect, useMemo, useState } from 'react';
import { rolesApi, type RoleInfo } from '../../../api/roles';
import { useI18n } from '../../../i18n';
interface AuthSettingsTabProps {
config: Record<string, string>;
loading: boolean;
onSave: (values: Record<string, unknown>) => Promise<void>;
}
export default function AuthSettingsTab({
config,
loading,
onSave,
}: AuthSettingsTabProps) {
const { t } = useI18n();
const [rolesLoading, setRolesLoading] = useState(false);
const [roles, setRoles] = useState<RoleInfo[]>([]);
useEffect(() => {
let mounted = true;
async function loadRoles() {
setRolesLoading(true);
try {
const list = await rolesApi.list();
if (!mounted) return;
setRoles(list);
} catch (e: any) {
message.error(e.message || t('Load failed'));
} finally {
if (mounted) setRolesLoading(false);
}
}
loadRoles();
return () => {
mounted = false;
};
}, [t]);
const initialValues = useMemo(() => {
const allowRegister = (config.AUTH_ALLOW_REGISTER || '').trim().toLowerCase() === 'true';
const roleIdRaw = (config.AUTH_DEFAULT_REGISTER_ROLE_ID || '').trim();
const roleId = roleIdRaw ? Number(roleIdRaw) : undefined;
return {
AUTH_ALLOW_REGISTER: allowRegister,
AUTH_DEFAULT_REGISTER_ROLE_ID: Number.isFinite(roleId) ? roleId : undefined,
};
}, [config]);
return (
<Form
layout="vertical"
initialValues={initialValues}
onFinish={async (vals) => {
const allow = !!vals.AUTH_ALLOW_REGISTER;
const payload: Record<string, unknown> = {
AUTH_ALLOW_REGISTER: allow ? 'true' : 'false',
};
if (allow) {
payload.AUTH_DEFAULT_REGISTER_ROLE_ID = String(vals.AUTH_DEFAULT_REGISTER_ROLE_ID);
}
await onSave(payload);
}}
style={{ marginTop: 24 }}
key={'auth-settings-' + (config.AUTH_ALLOW_REGISTER ?? '') + '-' + (config.AUTH_DEFAULT_REGISTER_ROLE_ID ?? '')}
>
<Alert
type="info"
showIcon
message={t('Enabling registration allows new users to sign up and assigns them the default role')}
style={{ marginBottom: 16 }}
/>
<Form.Item
name="AUTH_ALLOW_REGISTER"
label={t('Enable Registration')}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item dependencies={['AUTH_ALLOW_REGISTER']} noStyle>
{({ getFieldValue }) => {
const enabled = !!getFieldValue('AUTH_ALLOW_REGISTER');
return (
<Form.Item
name="AUTH_DEFAULT_REGISTER_ROLE_ID"
label={t('Default Role for New Registrations')}
rules={enabled ? [{ required: true, message: t('Please select default role') }] : []}
>
<Select
size="large"
loading={rolesLoading}
disabled={!enabled || rolesLoading}
placeholder={t('Select roles')}
options={roles.map((r) => ({ value: r.id, label: r.name }))}
/>
</Form.Item>
);
}}
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" loading={loading} block>
{t('Save')}
</Button>
</Form.Item>
</Form>
);
}