feat: surface admin settings attention states
This commit is contained in:
+241
-37
@@ -56,6 +56,19 @@ const CHECKIN_INTERVAL_OPTIONS = Array.from({ length: 24 }, (_, index) => {
|
||||
type DbDialect = 'sqlite' | 'mysql' | 'postgres';
|
||||
type RouteCooldownUnit = typeof ROUTE_COOLDOWN_UNIT_OPTIONS[number]['value'];
|
||||
type SettingsPillTone = 'neutral' | 'primary' | 'danger' | 'warning';
|
||||
type SettingsNavItem = {
|
||||
id: string;
|
||||
label: string;
|
||||
badge: string;
|
||||
tone: SettingsPillTone;
|
||||
};
|
||||
type SettingsAttentionItem = {
|
||||
key: string;
|
||||
title: string;
|
||||
detail: string;
|
||||
href: string;
|
||||
tone: SettingsPillTone;
|
||||
};
|
||||
type PayloadRulesEditorSectionKey = PayloadRuleAction;
|
||||
type PayloadRulesEditorDrafts = Record<PayloadRulesEditorSectionKey, string>;
|
||||
|
||||
@@ -638,6 +651,38 @@ export default function Settings() {
|
||||
const settingsAnchorCardStyle: React.CSSProperties = {
|
||||
scrollMarginTop: 96,
|
||||
};
|
||||
const settingsAttentionPanelStyle: React.CSSProperties = {
|
||||
maxWidth: 1120,
|
||||
display: 'grid',
|
||||
gap: 12,
|
||||
padding: '14px 16px',
|
||||
borderRadius: 'var(--radius-md)',
|
||||
border: '1px solid var(--color-border-light)',
|
||||
background: 'color-mix(in srgb, var(--color-bg-card) 94%, var(--color-bg))',
|
||||
boxShadow: 'var(--shadow-card)',
|
||||
marginBottom: 16,
|
||||
};
|
||||
const settingsAttentionHeaderStyle: React.CSSProperties = {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
flexWrap: 'wrap',
|
||||
};
|
||||
const settingsAttentionGridStyle: React.CSSProperties = {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: isMobile ? '1fr' : 'repeat(2, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
};
|
||||
const settingsAttentionItemStyle: React.CSSProperties = {
|
||||
display: 'grid',
|
||||
gap: 4,
|
||||
padding: '12px 14px',
|
||||
borderRadius: 'var(--radius-sm)',
|
||||
border: '1px solid var(--color-border-light)',
|
||||
background: 'color-mix(in srgb, var(--color-bg) 78%, var(--color-bg-card))',
|
||||
textDecoration: 'none',
|
||||
};
|
||||
|
||||
const getSettingsPillStyle = (tone: SettingsPillTone): React.CSSProperties => {
|
||||
const toneStyles: Record<SettingsPillTone, React.CSSProperties> = {
|
||||
@@ -707,20 +752,67 @@ export default function Settings() {
|
||||
blockedBrands.length > 0 ? `${blockedBrands.length} 个品牌屏蔽` : '',
|
||||
allowedModels.length > 0 ? `${allowedModels.length} 个模型白名单` : '',
|
||||
].filter(Boolean).join(' / ') || '全部模型开放';
|
||||
const settingsSections = [
|
||||
{ id: 'settings-access', label: '管理员令牌', badge: maskedToken ? '已配置' : '检查' },
|
||||
{ id: 'settings-schedule', label: '定时任务', badge: runtime.checkinScheduleMode === 'interval' ? `${runtime.checkinIntervalHours}h` : 'Cron' },
|
||||
{ id: 'settings-proxy', label: '代理与失败判定', badge: systemProxySummary },
|
||||
{ id: 'settings-payload', label: 'Payload 规则', badge: configuredPayloadRuleCount > 0 ? `${configuredPayloadRuleCount}` : '0' },
|
||||
{ id: 'settings-transport', label: '上游传输', badge: runtime.codexUpstreamWebsocketEnabled ? 'WS' : 'HTTP' },
|
||||
{ id: 'settings-probe', label: '批量测活', badge: modelAvailabilityProbeStatusLabel },
|
||||
{ id: 'settings-downstream-token', label: '下游令牌', badge: runtime.proxyTokenMasked ? '已配置' : '未设置' },
|
||||
{ id: 'settings-routing', label: '路由策略', badge: activeRoutingProfileLabel },
|
||||
{ id: 'settings-governance', label: '模型治理', badge: blockedBrands.length + allowedModels.length > 0 ? '已配置' : '开放' },
|
||||
{ id: 'settings-database', label: '数据库', badge: runtimeDatabaseSummary },
|
||||
{ id: 'settings-maintenance', label: '维护与危险操作', badge: '谨慎' },
|
||||
{ id: 'settings-security', label: '会话与安全', badge: securitySummary },
|
||||
const settingsSections: SettingsNavItem[] = [
|
||||
{ id: 'settings-access', label: '管理员令牌', badge: maskedToken ? '已配置' : '检查', tone: maskedToken ? 'neutral' : 'warning' },
|
||||
{ id: 'settings-schedule', label: '定时任务', badge: runtime.checkinScheduleMode === 'interval' ? `${runtime.checkinIntervalHours}h` : 'Cron', tone: 'neutral' },
|
||||
{ id: 'settings-proxy', label: '代理与失败判定', badge: systemProxySummary, tone: systemProxySummary === '已配置' ? 'primary' : 'neutral' },
|
||||
{ id: 'settings-payload', label: 'Payload 规则', badge: configuredPayloadRuleCount > 0 ? `${configuredPayloadRuleCount}` : '0', tone: payloadAdvancedDirty ? 'warning' : configuredPayloadRuleCount > 0 ? 'primary' : 'neutral' },
|
||||
{ id: 'settings-transport', label: '上游传输', badge: runtime.codexUpstreamWebsocketEnabled ? 'WS' : 'HTTP', tone: runtime.codexUpstreamWebsocketEnabled ? 'primary' : 'neutral' },
|
||||
{ id: 'settings-probe', label: '批量测活', badge: modelAvailabilityProbeStatusLabel, tone: modelAvailabilityProbeStatusTone },
|
||||
{ id: 'settings-downstream-token', label: '下游令牌', badge: runtime.proxyTokenMasked ? '已配置' : '未设置', tone: runtime.proxyTokenMasked ? 'primary' : 'warning' },
|
||||
{ id: 'settings-routing', label: '路由策略', badge: activeRoutingProfileLabel, tone: activeRoutingProfile === 'balanced' ? 'neutral' : 'primary' },
|
||||
{ id: 'settings-governance', label: '模型治理', badge: blockedBrands.length + allowedModels.length > 0 ? '已配置' : '开放', tone: blockedBrands.length + allowedModels.length > 0 ? 'primary' : 'neutral' },
|
||||
{ id: 'settings-database', label: '数据库', badge: runtimeDatabaseSummary, tone: runtimeDatabaseState?.restartRequired ? 'warning' : 'neutral' },
|
||||
{ id: 'settings-maintenance', label: '维护与危险操作', badge: '谨慎', tone: 'danger' },
|
||||
{ id: 'settings-security', label: '会话与安全', badge: securitySummary, tone: runtime.adminIpAllowlist?.length ? 'primary' : 'neutral' },
|
||||
];
|
||||
const settingsAttentionItems: SettingsAttentionItem[] = [
|
||||
runtimeDatabaseState?.restartRequired
|
||||
? {
|
||||
key: 'database-restart',
|
||||
title: '数据库配置待重启',
|
||||
detail: '运行数据库和已保存配置不一致,重启后才会切换到新连接。',
|
||||
href: '#settings-database',
|
||||
tone: 'warning',
|
||||
}
|
||||
: null,
|
||||
payloadAdvancedDirty
|
||||
? {
|
||||
key: 'payload-dirty',
|
||||
title: 'Payload 高级 JSON 待同步',
|
||||
detail: '高级编辑器有未同步或未保存内容,离开页面前建议保存。',
|
||||
href: '#settings-payload',
|
||||
tone: 'warning',
|
||||
}
|
||||
: null,
|
||||
modelAvailabilityProbeDirty
|
||||
? {
|
||||
key: 'probe-dirty',
|
||||
title: '批量测活开关待保存',
|
||||
detail: '测活状态已修改但尚未保存,当前运行状态不会立刻改变。',
|
||||
href: '#settings-probe',
|
||||
tone: 'warning',
|
||||
}
|
||||
: null,
|
||||
savedModelAvailabilityProbeEnabled
|
||||
? {
|
||||
key: 'probe-enabled',
|
||||
title: '批量测活处于开启状态',
|
||||
detail: '请确认你的上游中转站允许主动探测,避免触发风控。',
|
||||
href: '#settings-probe',
|
||||
tone: 'danger',
|
||||
}
|
||||
: null,
|
||||
!runtime.proxyTokenMasked
|
||||
? {
|
||||
key: 'downstream-token',
|
||||
title: '下游访问令牌未设置',
|
||||
detail: '下游客户端调用代理接口前,需要先配置 PROXY_TOKEN。',
|
||||
href: '#settings-downstream-token',
|
||||
tone: 'warning',
|
||||
}
|
||||
: null,
|
||||
].filter((item): item is SettingsAttentionItem => item !== null);
|
||||
|
||||
const syncPayloadRuleDraftsFromObject = (value: unknown) => {
|
||||
setPayloadRuleDrafts(normalizePayloadRulesForEditor(value));
|
||||
@@ -1449,6 +1541,43 @@ export default function Settings() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={settingsAttentionPanelStyle} aria-label="运行关注事项">
|
||||
<div style={settingsAttentionHeaderStyle}>
|
||||
<div>
|
||||
<div style={settingsModernTitleStyle}>运行关注事项</div>
|
||||
<div style={settingsModernDescriptionStyle}>
|
||||
自动汇总需要管理员确认的设置状态;没有异常时保持安静。
|
||||
</div>
|
||||
</div>
|
||||
<span style={getSettingsPillStyle(settingsAttentionItems.length > 0 ? 'warning' : 'primary')}>
|
||||
{settingsAttentionItems.length > 0 ? `${settingsAttentionItems.length} 项待确认` : '当前配置平稳'}
|
||||
</span>
|
||||
</div>
|
||||
{settingsAttentionItems.length > 0 ? (
|
||||
<div style={settingsAttentionGridStyle}>
|
||||
{settingsAttentionItems.map((item) => (
|
||||
<a key={item.key} href={item.href} style={settingsAttentionItemStyle}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
|
||||
<span style={{ fontSize: 13, fontWeight: 700, color: 'var(--color-text-primary)' }}>
|
||||
{item.title}
|
||||
</span>
|
||||
<span style={getSettingsPillStyle(item.tone)}>
|
||||
{item.tone === 'danger' ? '高风险' : item.tone === 'warning' ? '待处理' : '提示'}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ fontSize: 12, lineHeight: 1.7, color: 'var(--color-text-muted)' }}>
|
||||
{item.detail}
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ ...settingsModernFieldHintStyle, marginTop: 0 }}>
|
||||
暂无待处理事项。可以继续检查下方分区,或直接保持当前运行配置。
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={settingsShellStyle}>
|
||||
<aside style={settingsNavStyle} aria-label="设置分区导航">
|
||||
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--color-text-muted)', padding: '2px 10px 6px' }}>
|
||||
@@ -1457,14 +1586,24 @@ export default function Settings() {
|
||||
{settingsSections.map((section) => (
|
||||
<a key={section.id} href={`#${section.id}`} style={settingsNavLinkStyle}>
|
||||
<span>{section.label}</span>
|
||||
<span style={getSettingsPillStyle(section.badge === '谨慎' ? 'danger' : 'neutral')}>{section.badge}</span>
|
||||
<span style={getSettingsPillStyle(section.tone)}>{section.badge}</span>
|
||||
</a>
|
||||
))}
|
||||
</aside>
|
||||
|
||||
<div style={settingsContentStyle}>
|
||||
<div id="settings-access" className="card animate-slide-up stagger-1" style={{ ...settingsModernCardStyle, ...settingsAnchorCardStyle }}>
|
||||
<div style={{ fontWeight: 600, fontSize: 14, marginBottom: 12 }}>管理员登录令牌</div>
|
||||
<div style={settingsModernHeaderStyle}>
|
||||
<div style={settingsModernTitleBlockStyle}>
|
||||
<div style={settingsModernTitleStyle}>管理员登录令牌</div>
|
||||
<div style={settingsModernDescriptionStyle}>
|
||||
用于管理后台登录。修改后建议同步更新部署侧保存的管理员入口说明。
|
||||
</div>
|
||||
</div>
|
||||
<span style={getSettingsPillStyle(maskedToken ? 'primary' : 'warning')}>
|
||||
{maskedToken ? '已配置' : '待检查'}
|
||||
</span>
|
||||
</div>
|
||||
<code style={{ display: 'block', padding: '10px 14px', background: 'var(--color-bg)', borderRadius: 'var(--radius-sm)', fontSize: 13, fontFamily: 'var(--font-mono)', color: 'var(--color-text-secondary)', border: '1px solid var(--color-border-light)', marginBottom: 12 }}>
|
||||
{maskedToken || '****'}
|
||||
</code>
|
||||
@@ -1479,7 +1618,17 @@ export default function Settings() {
|
||||
</div>
|
||||
|
||||
<div id="settings-schedule" className="card animate-slide-up stagger-2" style={{ ...settingsModernCardStyle, ...settingsAnchorCardStyle }}>
|
||||
<div style={{ fontWeight: 600, fontSize: 14, marginBottom: 12 }}>定时任务</div>
|
||||
<div style={settingsModernHeaderStyle}>
|
||||
<div style={settingsModernTitleBlockStyle}>
|
||||
<div style={settingsModernTitleStyle}>定时任务</div>
|
||||
<div style={settingsModernDescriptionStyle}>
|
||||
控制账号签到、余额刷新和日志清理节奏,适合按站点规模调小或调大频率。
|
||||
</div>
|
||||
</div>
|
||||
<span style={getSettingsPillStyle('neutral')}>
|
||||
{runtime.checkinScheduleMode === 'interval' ? `${runtime.checkinIntervalHours} 小时签到` : 'Cron 签到'}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '180px 180px auto', gap: 12, alignItems: 'end', marginBottom: 12 }}>
|
||||
<div>
|
||||
<div style={{ fontSize: 12, color: 'var(--color-text-muted)', marginBottom: 6 }}>签到方式</div>
|
||||
@@ -1600,9 +1749,16 @@ export default function Settings() {
|
||||
</div>
|
||||
|
||||
<div id="settings-proxy" className="card animate-slide-up stagger-3" style={{ ...settingsModernCardStyle, ...settingsAnchorCardStyle }}>
|
||||
<div style={{ fontWeight: 600, fontSize: 14, marginBottom: 8 }}>系统代理</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--color-text-muted)', marginBottom: 12 }}>
|
||||
配置一个全局出站代理地址,站点页可按站点决定是否启用系统代理。
|
||||
<div style={settingsModernHeaderStyle}>
|
||||
<div style={settingsModernTitleBlockStyle}>
|
||||
<div style={settingsModernTitleStyle}>系统代理</div>
|
||||
<div style={settingsModernDescriptionStyle}>
|
||||
配置一个全局出站代理地址,站点页可按站点决定是否启用系统代理。
|
||||
</div>
|
||||
</div>
|
||||
<span style={getSettingsPillStyle(runtime.systemProxyUrl.trim() ? 'primary' : 'neutral')}>
|
||||
{systemProxySummary}
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
value={runtime.systemProxyUrl}
|
||||
@@ -1642,9 +1798,16 @@ export default function Settings() {
|
||||
</div>
|
||||
|
||||
<div className="card animate-slide-up stagger-4" style={settingsModernCardStyle}>
|
||||
<div style={{ fontWeight: 600, fontSize: 14, marginBottom: 8 }}>代理失败判定</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--color-text-muted)', marginBottom: 12 }}>
|
||||
命中任一关键词或空内容时判定失败,可触发重试。
|
||||
<div style={settingsModernHeaderStyle}>
|
||||
<div style={settingsModernTitleBlockStyle}>
|
||||
<div style={settingsModernTitleStyle}>代理失败判定</div>
|
||||
<div style={settingsModernDescriptionStyle}>
|
||||
命中任一关键词或空内容时判定失败,可触发重试。
|
||||
</div>
|
||||
</div>
|
||||
<span style={getSettingsPillStyle(runtime.proxyEmptyContentFailEnabled || proxyErrorKeywordsText.trim() ? 'primary' : 'neutral')}>
|
||||
{runtime.proxyEmptyContentFailEnabled || proxyErrorKeywordsText.trim() ? '已配置' : '默认'}
|
||||
</span>
|
||||
</div>
|
||||
<textarea
|
||||
value={proxyErrorKeywordsText}
|
||||
@@ -2193,9 +2356,21 @@ export default function Settings() {
|
||||
</div>
|
||||
|
||||
<div id="settings-routing" className="card animate-slide-up stagger-5" style={{ ...settingsModernCardStyle, ...settingsAnchorCardStyle }}>
|
||||
<div style={{ fontWeight: 600, fontSize: 14, marginBottom: 10 }}>路由策略</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--color-text-muted)', marginBottom: 12 }}>
|
||||
先选择预设策略,只有需要精调时再展开高级参数。
|
||||
<div style={settingsModernHeaderStyle}>
|
||||
<div style={settingsModernTitleBlockStyle}>
|
||||
<div style={settingsModernTitleStyle}>路由策略</div>
|
||||
<div style={settingsModernDescriptionStyle}>
|
||||
先选择预设策略,只有需要精调时再展开高级参数。
|
||||
</div>
|
||||
</div>
|
||||
<div style={settingsModernPillRowStyle}>
|
||||
<span style={getSettingsPillStyle(activeRoutingProfile === 'balanced' ? 'neutral' : 'primary')}>
|
||||
{activeRoutingProfileLabel}
|
||||
</span>
|
||||
<span style={getSettingsPillStyle(runtime.disableCrossProtocolFallback ? 'warning' : 'neutral')}>
|
||||
{runtime.disableCrossProtocolFallback ? '禁止跨协议' : '允许兼容切换'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12, maxWidth: 280 }}>
|
||||
<div style={{ fontSize: 12, color: 'var(--color-text-muted)', marginBottom: 6 }}>
|
||||
@@ -2390,9 +2565,16 @@ export default function Settings() {
|
||||
|
||||
{/* Global Brand Filter */}
|
||||
<div id="settings-governance" className="card animate-slide-up stagger-6" style={{ ...settingsModernCardStyle, ...settingsAnchorCardStyle }}>
|
||||
<div style={{ fontWeight: 600, fontSize: 14, marginBottom: 8 }}>全局品牌屏蔽</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--color-text-muted)', marginBottom: 12, lineHeight: 1.6 }}>
|
||||
屏蔽选定品牌后,路由重建时将自动跳过匹配该品牌的所有模型。点击品牌切换屏蔽状态,保存后自动触发路由重建。
|
||||
<div style={settingsModernHeaderStyle}>
|
||||
<div style={settingsModernTitleBlockStyle}>
|
||||
<div style={settingsModernTitleStyle}>全局品牌屏蔽</div>
|
||||
<div style={settingsModernDescriptionStyle}>
|
||||
屏蔽选定品牌后,路由重建时将自动跳过匹配该品牌的所有模型。点击品牌切换屏蔽状态,保存后自动触发路由重建。
|
||||
</div>
|
||||
</div>
|
||||
<span style={getSettingsPillStyle(blockedBrands.length > 0 ? 'warning' : 'neutral')}>
|
||||
{blockedBrands.length > 0 ? `已屏蔽 ${blockedBrands.length}` : '未屏蔽'}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 12 }}>
|
||||
{(allBrandNames || []).map((brand) => {
|
||||
@@ -2439,9 +2621,16 @@ export default function Settings() {
|
||||
|
||||
{/* Global Allowed Models Whitelist */}
|
||||
<div className="card animate-slide-up stagger-7" style={settingsModernCardStyle}>
|
||||
<div style={{ fontWeight: 600, fontSize: 14, marginBottom: 8 }}>全局模型白名单</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--color-text-muted)', marginBottom: 12, lineHeight: 1.6 }}>
|
||||
配置白名单后,路由重建和候选生成将只针对白名单中的模型。留空表示允许所有模型(向后兼容)。保存后自动触发路由重建。
|
||||
<div style={settingsModernHeaderStyle}>
|
||||
<div style={settingsModernTitleBlockStyle}>
|
||||
<div style={settingsModernTitleStyle}>全局模型白名单</div>
|
||||
<div style={settingsModernDescriptionStyle}>
|
||||
配置白名单后,路由重建和候选生成将只针对白名单中的模型。留空表示允许所有模型(向后兼容)。保存后自动触发路由重建。
|
||||
</div>
|
||||
</div>
|
||||
<span style={getSettingsPillStyle(allowedModels.length > 0 ? 'primary' : 'neutral')}>
|
||||
{allowedModels.length > 0 ? `${allowedModels.length} 个模型` : '全部开放'}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
|
||||
@@ -2549,9 +2738,21 @@ export default function Settings() {
|
||||
</div>
|
||||
|
||||
<div id="settings-database" className="card animate-slide-up stagger-8" style={{ ...settingsModernCardStyle, ...settingsAnchorCardStyle }}>
|
||||
<div style={{ fontWeight: 600, fontSize: 14, marginBottom: 10 }}>数据库迁移(SQLite / MySQL / PostgreSQL)</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--color-text-muted)', marginBottom: 12 }}>
|
||||
可先测试连接,再迁移数据;迁移完成后可保存为运行数据库配置(重启容器后生效)。
|
||||
<div style={settingsModernHeaderStyle}>
|
||||
<div style={settingsModernTitleBlockStyle}>
|
||||
<div style={settingsModernTitleStyle}>数据库迁移(SQLite / MySQL / PostgreSQL)</div>
|
||||
<div style={settingsModernDescriptionStyle}>
|
||||
可先测试连接,再迁移数据;迁移完成后可保存为运行数据库配置(重启容器后生效)。
|
||||
</div>
|
||||
</div>
|
||||
<div style={settingsModernPillRowStyle}>
|
||||
<span style={getSettingsPillStyle(runtimeDatabaseState?.restartRequired ? 'warning' : 'neutral')}>
|
||||
{runtimeDatabaseSummary}
|
||||
</span>
|
||||
<span style={getSettingsPillStyle(migrationOverwrite ? 'warning' : 'neutral')}>
|
||||
{migrationOverwrite ? '允许覆盖' : '不覆盖'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '180px 1fr', gap: 10, marginBottom: 10, alignItems: 'center' }}>
|
||||
@@ -2696,9 +2897,12 @@ export default function Settings() {
|
||||
</div>
|
||||
|
||||
{runtimeDatabaseState && (
|
||||
<div style={{ border: '1px solid var(--color-border-light)', borderRadius: 'var(--radius-sm)', padding: 10, fontSize: 12, color: 'var(--color-text-secondary)', lineHeight: 1.8, marginBottom: migrationSummary ? 12 : 0 }}>
|
||||
<div>当前运行:{runtimeDatabaseState.active.dialect}({runtimeDatabaseState.active.connection || '未配置'}){runtimeDatabaseState.active.ssl && '(已启用 SSL)'}</div>
|
||||
<div>
|
||||
<div style={{ ...settingsModernFieldCardStyle, marginBottom: migrationSummary ? 12 : 0 }}>
|
||||
<div style={settingsModernFieldLabelStyle}>当前数据库状态</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--color-text-secondary)', lineHeight: 1.8 }}>
|
||||
当前运行:{runtimeDatabaseState.active.dialect}({runtimeDatabaseState.active.connection || '未配置'}){runtimeDatabaseState.active.ssl && '(已启用 SSL)'}
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--color-text-secondary)', lineHeight: 1.8 }}>
|
||||
已保存待生效:
|
||||
{runtimeDatabaseState.saved
|
||||
? ` ${runtimeDatabaseState.saved.dialect}(${runtimeDatabaseState.saved.connection})${runtimeDatabaseState.saved.ssl ? '(已启用 SSL)' : ''}`
|
||||
|
||||
Reference in New Issue
Block a user