mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-09-05 23:57:19 +08:00
feat: update SystemSettingsPage to remove AuthSettingsTab and enhance AppSettingsTab with registration settings
This commit is contained in:
@@ -2,21 +2,20 @@ import { Alert, message, Tabs, Space } from 'antd';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import PageCard from '../../components/PageCard';
|
import PageCard from '../../components/PageCard';
|
||||||
import { getAllConfig, setConfig } from '../../api/config';
|
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 { useTheme } from '../../contexts/ThemeContext';
|
||||||
import '../../styles/settings-tabs.css';
|
import '../../styles/settings-tabs.css';
|
||||||
import { useI18n } from '../../i18n';
|
import { useI18n } from '../../i18n';
|
||||||
import AppearanceSettingsTab from './components/AppearanceSettingsTab';
|
import AppearanceSettingsTab from './components/AppearanceSettingsTab';
|
||||||
import AppSettingsTab from './components/AppSettingsTab';
|
import AppSettingsTab from './components/AppSettingsTab';
|
||||||
import AuthSettingsTab from './components/AuthSettingsTab';
|
|
||||||
import AiSettingsTab from './components/AiSettingsTab';
|
import AiSettingsTab from './components/AiSettingsTab';
|
||||||
import VectorDbSettingsTab from './components/VectorDbSettingsTab';
|
import VectorDbSettingsTab from './components/VectorDbSettingsTab';
|
||||||
import EmailSettingsTab from './components/EmailSettingsTab';
|
import EmailSettingsTab from './components/EmailSettingsTab';
|
||||||
import ProtocolMappingsTab from './components/ProtocolMappingsTab';
|
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 DEFAULT_TAB: TabKey = 'appearance';
|
||||||
|
|
||||||
const isValidTab = (key?: string): key is TabKey => !!key && (TAB_KEYS as string[]).includes(key);
|
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',
|
key: 'email',
|
||||||
label: (
|
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';
|
import { useI18n } from '../../../i18n';
|
||||||
|
|
||||||
interface AppConfigKey {
|
interface AppConfigKey {
|
||||||
@@ -21,14 +23,56 @@ export default function AppSettingsTab({
|
|||||||
configKeys,
|
configKeys,
|
||||||
}: AppSettingsTabProps) {
|
}: AppSettingsTabProps) {
|
||||||
const { t } = useI18n();
|
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 (
|
return (
|
||||||
<Form
|
<Form
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
initialValues={{
|
initialValues={initialValues}
|
||||||
...Object.fromEntries(configKeys.map(({ key, default: def }) => [key, config[key] ?? def ?? ''])),
|
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 }}
|
style={{ marginTop: 24 }}
|
||||||
key={JSON.stringify(config)}
|
key={JSON.stringify(config)}
|
||||||
>
|
>
|
||||||
@@ -37,6 +81,45 @@ export default function AppSettingsTab({
|
|||||||
<Input size="large" />
|
<Input size="large" />
|
||||||
</Form.Item>
|
</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>
|
<Form.Item>
|
||||||
<Button type="primary" htmlType="submit" loading={loading} block>
|
<Button type="primary" htmlType="submit" loading={loading} block>
|
||||||
{t('Save')}
|
{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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user