Commit 882cdbd3 by Apple\Apple

🎛️ feat(dashboard): add per-panel enable switches & conditional backend payload

Backend:
• ConsoleSetting
  - Introduce `ApiInfoEnabled`, `UptimeKumaEnabled`, `AnnouncementsEnabled`, `FAQEnabled` (default true).
• misc.GetStatus
  - Refactor to build response map dynamically.
  - Return the four *_enabled flags.
  - Only append `api_info`, `announcements`, `faq` when their respective flags are true.

Frontend:
• Detail page
  - Remove all `self_use_mode_enabled` checks.
  - Render API, Announcement, FAQ and Uptime panels based on the new *_enabled flags.
• Dashboard → Settings
  - Added `Switch` controls in:
    · SettingsAPIInfo.js
    · SettingsAnnouncements.js
    · SettingsFAQ.js
    · SettingsUptimeKuma.js
  - Each switch persists its state via `/api/option` to the corresponding
    `console_setting.<panel>_enabled` key and reflects current status on load.
  - DashboardSetting.js now initialises and refreshes the four *_enabled keys so
    child components receive accurate panel states.

Fixes:
• Switches previously defaulted to “on” because *_enabled keys were missing.
  They are now included, ensuring correct visual state when panels are disabled.

No breaking changes; existing functionality remains untouched aside from the
new per-panel visibility control.
parent bccb87fe
...@@ -38,52 +38,71 @@ func TestStatus(c *gin.Context) { ...@@ -38,52 +38,71 @@ func TestStatus(c *gin.Context) {
func GetStatus(c *gin.Context) { func GetStatus(c *gin.Context) {
cs := console_setting.GetConsoleSetting()
data := gin.H{
"version": common.Version,
"start_time": common.StartTime,
"email_verification": common.EmailVerificationEnabled,
"github_oauth": common.GitHubOAuthEnabled,
"github_client_id": common.GitHubClientId,
"linuxdo_oauth": common.LinuxDOOAuthEnabled,
"linuxdo_client_id": common.LinuxDOClientId,
"telegram_oauth": common.TelegramOAuthEnabled,
"telegram_bot_name": common.TelegramBotName,
"system_name": common.SystemName,
"logo": common.Logo,
"footer_html": common.Footer,
"wechat_qrcode": common.WeChatAccountQRCodeImageURL,
"wechat_login": common.WeChatAuthEnabled,
"server_address": setting.ServerAddress,
"price": setting.Price,
"min_topup": setting.MinTopUp,
"turnstile_check": common.TurnstileCheckEnabled,
"turnstile_site_key": common.TurnstileSiteKey,
"top_up_link": common.TopUpLink,
"docs_link": operation_setting.GetGeneralSetting().DocsLink,
"quota_per_unit": common.QuotaPerUnit,
"display_in_currency": common.DisplayInCurrencyEnabled,
"enable_batch_update": common.BatchUpdateEnabled,
"enable_drawing": common.DrawingEnabled,
"enable_task": common.TaskEnabled,
"enable_data_export": common.DataExportEnabled,
"data_export_default_time": common.DataExportDefaultTime,
"default_collapse_sidebar": common.DefaultCollapseSidebar,
"enable_online_topup": setting.PayAddress != "" && setting.EpayId != "" && setting.EpayKey != "",
"mj_notify_enabled": setting.MjNotifyEnabled,
"chats": setting.Chats,
"demo_site_enabled": operation_setting.DemoSiteEnabled,
"self_use_mode_enabled": operation_setting.SelfUseModeEnabled,
// 面板启用开关
"api_info_enabled": cs.ApiInfoEnabled,
"uptime_kuma_enabled": cs.UptimeKumaEnabled,
"announcements_enabled": cs.AnnouncementsEnabled,
"faq_enabled": cs.FAQEnabled,
"oidc_enabled": system_setting.GetOIDCSettings().Enabled,
"oidc_client_id": system_setting.GetOIDCSettings().ClientId,
"oidc_authorization_endpoint": system_setting.GetOIDCSettings().AuthorizationEndpoint,
"setup": constant.Setup,
}
// 根据启用状态注入可选内容
if cs.ApiInfoEnabled {
data["api_info"] = console_setting.GetApiInfo()
}
if cs.AnnouncementsEnabled {
data["announcements"] = console_setting.GetAnnouncements()
}
if cs.FAQEnabled {
data["faq"] = console_setting.GetFAQ()
}
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
"message": "", "message": "",
"data": gin.H{ "data": data,
"version": common.Version,
"start_time": common.StartTime,
"email_verification": common.EmailVerificationEnabled,
"github_oauth": common.GitHubOAuthEnabled,
"github_client_id": common.GitHubClientId,
"linuxdo_oauth": common.LinuxDOOAuthEnabled,
"linuxdo_client_id": common.LinuxDOClientId,
"telegram_oauth": common.TelegramOAuthEnabled,
"telegram_bot_name": common.TelegramBotName,
"system_name": common.SystemName,
"logo": common.Logo,
"footer_html": common.Footer,
"wechat_qrcode": common.WeChatAccountQRCodeImageURL,
"wechat_login": common.WeChatAuthEnabled,
"server_address": setting.ServerAddress,
"price": setting.Price,
"min_topup": setting.MinTopUp,
"turnstile_check": common.TurnstileCheckEnabled,
"turnstile_site_key": common.TurnstileSiteKey,
"top_up_link": common.TopUpLink,
"docs_link": operation_setting.GetGeneralSetting().DocsLink,
"quota_per_unit": common.QuotaPerUnit,
"display_in_currency": common.DisplayInCurrencyEnabled,
"enable_batch_update": common.BatchUpdateEnabled,
"enable_drawing": common.DrawingEnabled,
"enable_task": common.TaskEnabled,
"enable_data_export": common.DataExportEnabled,
"data_export_default_time": common.DataExportDefaultTime,
"default_collapse_sidebar": common.DefaultCollapseSidebar,
"enable_online_topup": setting.PayAddress != "" && setting.EpayId != "" && setting.EpayKey != "",
"mj_notify_enabled": setting.MjNotifyEnabled,
"chats": setting.Chats,
"demo_site_enabled": operation_setting.DemoSiteEnabled,
"self_use_mode_enabled": operation_setting.SelfUseModeEnabled,
"oidc_enabled": system_setting.GetOIDCSettings().Enabled,
"oidc_client_id": system_setting.GetOIDCSettings().ClientId,
"oidc_authorization_endpoint": system_setting.GetOIDCSettings().AuthorizationEndpoint,
"setup": constant.Setup,
"api_info": console_setting.GetApiInfo(),
"announcements": console_setting.GetAnnouncements(),
"faq": console_setting.GetFAQ(),
},
}) })
return return
} }
......
...@@ -8,6 +8,10 @@ type ConsoleSetting struct { ...@@ -8,6 +8,10 @@ type ConsoleSetting struct {
UptimeKumaSlug string `json:"uptime_kuma_slug"` // Uptime Kuma Status Page Slug UptimeKumaSlug string `json:"uptime_kuma_slug"` // Uptime Kuma Status Page Slug
Announcements string `json:"announcements"` // 系统公告 (JSON 数组字符串) Announcements string `json:"announcements"` // 系统公告 (JSON 数组字符串)
FAQ string `json:"faq"` // 常见问题 (JSON 数组字符串) FAQ string `json:"faq"` // 常见问题 (JSON 数组字符串)
ApiInfoEnabled bool `json:"api_info_enabled"` // 是否启用 API 信息面板
UptimeKumaEnabled bool `json:"uptime_kuma_enabled"` // 是否启用 Uptime Kuma 面板
AnnouncementsEnabled bool `json:"announcements_enabled"` // 是否启用系统公告面板
FAQEnabled bool `json:"faq_enabled"` // 是否启用常见问答面板
} }
// 默认配置 // 默认配置
...@@ -17,6 +21,10 @@ var defaultConsoleSetting = ConsoleSetting{ ...@@ -17,6 +21,10 @@ var defaultConsoleSetting = ConsoleSetting{
UptimeKumaSlug: "", UptimeKumaSlug: "",
Announcements: "", Announcements: "",
FAQ: "", FAQ: "",
ApiInfoEnabled: true,
UptimeKumaEnabled: true,
AnnouncementsEnabled: true,
FAQEnabled: true,
} }
// 全局实例 // 全局实例
......
...@@ -13,6 +13,10 @@ const DashboardSetting = () => { ...@@ -13,6 +13,10 @@ const DashboardSetting = () => {
'console_setting.faq': '', 'console_setting.faq': '',
'console_setting.uptime_kuma_url': '', 'console_setting.uptime_kuma_url': '',
'console_setting.uptime_kuma_slug': '', 'console_setting.uptime_kuma_slug': '',
'console_setting.api_info_enabled': '',
'console_setting.announcements_enabled': '',
'console_setting.faq_enabled': '',
'console_setting.uptime_kuma_enabled': '',
// 用于迁移检测的旧键,下个版本会删除 // 用于迁移检测的旧键,下个版本会删除
ApiInfo: '', ApiInfo: '',
......
...@@ -9,7 +9,8 @@ import { ...@@ -9,7 +9,8 @@ import {
Divider, Divider,
Avatar, Avatar,
Modal, Modal,
Tag Tag,
Switch
} from '@douyinfe/semi-ui'; } from '@douyinfe/semi-ui';
import { import {
IllustrationNoResult, IllustrationNoResult,
...@@ -48,6 +49,9 @@ const SettingsAPIInfo = ({ options, refresh }) => { ...@@ -48,6 +49,9 @@ const SettingsAPIInfo = ({ options, refresh }) => {
const [pageSize, setPageSize] = useState(10); const [pageSize, setPageSize] = useState(10);
const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [selectedRowKeys, setSelectedRowKeys] = useState([]);
// 面板启用状态 state
const [panelEnabled, setPanelEnabled] = useState(true);
const colorOptions = [ const colorOptions = [
{ value: 'blue', label: 'blue' }, { value: 'blue', label: 'blue' },
{ value: 'green', label: 'green' }, { value: 'green', label: 'green' },
...@@ -191,6 +195,30 @@ const SettingsAPIInfo = ({ options, refresh }) => { ...@@ -191,6 +195,30 @@ const SettingsAPIInfo = ({ options, refresh }) => {
} }
}, [options['console_setting.api_info'], options.ApiInfo]); }, [options['console_setting.api_info'], options.ApiInfo]);
useEffect(() => {
const enabledStr = options['console_setting.api_info_enabled'];
setPanelEnabled(enabledStr === undefined ? true : enabledStr === 'true' || enabledStr === true);
}, [options['console_setting.api_info_enabled']]);
const handleToggleEnabled = async (checked) => {
const newValue = checked ? 'true' : 'false';
try {
const res = await API.put('/api/option/', {
key: 'console_setting.api_info_enabled',
value: newValue,
});
if (res.data.success) {
setPanelEnabled(checked);
showSuccess(t('设置已保存'));
refresh?.();
} else {
showError(res.data.message);
}
} catch (err) {
showError(err.message);
}
};
const columns = [ const columns = [
{ {
title: 'ID', title: 'ID',
...@@ -325,6 +353,15 @@ const SettingsAPIInfo = ({ options, refresh }) => { ...@@ -325,6 +353,15 @@ const SettingsAPIInfo = ({ options, refresh }) => {
{t('保存设置')} {t('保存设置')}
</Button> </Button>
</div> </div>
{/* 启用开关 */}
<div className="order-1 md:order-2 flex items-center gap-2">
<Switch
checked={panelEnabled}
onChange={handleToggleEnabled}
/>
<Text>{panelEnabled ? t('已启用') : t('已禁用')}</Text>
</div>
</div> </div>
</div> </div>
); );
......
...@@ -8,7 +8,8 @@ import { ...@@ -8,7 +8,8 @@ import {
Empty, Empty,
Divider, Divider,
Modal, Modal,
Tag Tag,
Switch
} from '@douyinfe/semi-ui'; } from '@douyinfe/semi-ui';
import { import {
IllustrationNoResult, IllustrationNoResult,
...@@ -47,6 +48,9 @@ const SettingsAnnouncements = ({ options, refresh }) => { ...@@ -47,6 +48,9 @@ const SettingsAnnouncements = ({ options, refresh }) => {
const [pageSize, setPageSize] = useState(10); const [pageSize, setPageSize] = useState(10);
const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [selectedRowKeys, setSelectedRowKeys] = useState([]);
// 面板启用状态
const [panelEnabled, setPanelEnabled] = useState(true);
const typeOptions = [ const typeOptions = [
{ value: 'default', label: t('默认') }, { value: 'default', label: t('默认') },
{ value: 'ongoing', label: t('进行中') }, { value: 'ongoing', label: t('进行中') },
...@@ -294,6 +298,30 @@ const SettingsAnnouncements = ({ options, refresh }) => { ...@@ -294,6 +298,30 @@ const SettingsAnnouncements = ({ options, refresh }) => {
} }
}, [options['console_setting.announcements'], options.Announcements]); }, [options['console_setting.announcements'], options.Announcements]);
useEffect(() => {
const enabledStr = options['console_setting.announcements_enabled'];
setPanelEnabled(enabledStr === undefined ? true : enabledStr === 'true' || enabledStr === true);
}, [options['console_setting.announcements_enabled']]);
const handleToggleEnabled = async (checked) => {
const newValue = checked ? 'true' : 'false';
try {
const res = await API.put('/api/option/', {
key: 'console_setting.announcements_enabled',
value: newValue,
});
if (res.data.success) {
setPanelEnabled(checked);
showSuccess(t('设置已保存'));
refresh?.();
} else {
showError(res.data.message);
}
} catch (err) {
showError(err.message);
}
};
const handleBatchDelete = () => { const handleBatchDelete = () => {
if (selectedRowKeys.length === 0) { if (selectedRowKeys.length === 0) {
showError('请先选择要删除的系统公告'); showError('请先选择要删除的系统公告');
...@@ -350,6 +378,12 @@ const SettingsAnnouncements = ({ options, refresh }) => { ...@@ -350,6 +378,12 @@ const SettingsAnnouncements = ({ options, refresh }) => {
{t('保存设置')} {t('保存设置')}
</Button> </Button>
</div> </div>
{/* 启用开关 */}
<div className="order-1 md:order-2 flex items-center gap-2">
<Switch checked={panelEnabled} onChange={handleToggleEnabled} />
<Text>{panelEnabled ? t('已启用') : t('已禁用')}</Text>
</div>
</div> </div>
</div> </div>
); );
......
...@@ -7,7 +7,8 @@ import { ...@@ -7,7 +7,8 @@ import {
Typography, Typography,
Empty, Empty,
Divider, Divider,
Modal Modal,
Switch
} from '@douyinfe/semi-ui'; } from '@douyinfe/semi-ui';
import { import {
IllustrationNoResult, IllustrationNoResult,
...@@ -44,6 +45,9 @@ const SettingsFAQ = ({ options, refresh }) => { ...@@ -44,6 +45,9 @@ const SettingsFAQ = ({ options, refresh }) => {
const [pageSize, setPageSize] = useState(10); const [pageSize, setPageSize] = useState(10);
const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [selectedRowKeys, setSelectedRowKeys] = useState([]);
// 面板启用状态
const [panelEnabled, setPanelEnabled] = useState(true);
const columns = [ const columns = [
{ {
title: t('问题标题'), title: t('问题标题'),
...@@ -231,6 +235,30 @@ const SettingsFAQ = ({ options, refresh }) => { ...@@ -231,6 +235,30 @@ const SettingsFAQ = ({ options, refresh }) => {
} }
}, [options['console_setting.faq']]); }, [options['console_setting.faq']]);
useEffect(() => {
const enabledStr = options['console_setting.faq_enabled'];
setPanelEnabled(enabledStr === undefined ? true : enabledStr === 'true' || enabledStr === true);
}, [options['console_setting.faq_enabled']]);
const handleToggleEnabled = async (checked) => {
const newValue = checked ? 'true' : 'false';
try {
const res = await API.put('/api/option/', {
key: 'console_setting.faq_enabled',
value: newValue,
});
if (res.data.success) {
setPanelEnabled(checked);
showSuccess(t('设置已保存'));
refresh?.();
} else {
showError(res.data.message);
}
} catch (err) {
showError(err.message);
}
};
const handleBatchDelete = () => { const handleBatchDelete = () => {
if (selectedRowKeys.length === 0) { if (selectedRowKeys.length === 0) {
showError('请先选择要删除的常见问答'); showError('请先选择要删除的常见问答');
...@@ -287,6 +315,12 @@ const SettingsFAQ = ({ options, refresh }) => { ...@@ -287,6 +315,12 @@ const SettingsFAQ = ({ options, refresh }) => {
{t('保存设置')} {t('保存设置')}
</Button> </Button>
</div> </div>
{/* 启用开关 */}
<div className="order-1 md:order-2 flex items-center gap-2">
<Switch checked={panelEnabled} onChange={handleToggleEnabled} />
<Text>{panelEnabled ? t('已启用') : t('已禁用')}</Text>
</div>
</div> </div>
</div> </div>
); );
......
...@@ -5,6 +5,7 @@ import { ...@@ -5,6 +5,7 @@ import {
Typography, Typography,
Row, Row,
Col, Col,
Switch,
} from '@douyinfe/semi-ui'; } from '@douyinfe/semi-ui';
import { import {
Save, Save,
...@@ -19,6 +20,7 @@ const SettingsUptimeKuma = ({ options, refresh }) => { ...@@ -19,6 +20,7 @@ const SettingsUptimeKuma = ({ options, refresh }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [panelEnabled, setPanelEnabled] = useState(true);
const formApiRef = useRef(null); const formApiRef = useRef(null);
const initValues = useMemo(() => ({ const initValues = useMemo(() => ({
...@@ -32,6 +34,11 @@ const SettingsUptimeKuma = ({ options, refresh }) => { ...@@ -32,6 +34,11 @@ const SettingsUptimeKuma = ({ options, refresh }) => {
} }
}, [initValues]); }, [initValues]);
useEffect(() => {
const enabledStr = options?.['console_setting.uptime_kuma_enabled'];
setPanelEnabled(enabledStr === undefined ? true : enabledStr === 'true' || enabledStr === true);
}, [options?.['console_setting.uptime_kuma_enabled']]);
const handleSave = async () => { const handleSave = async () => {
const api = formApiRef.current; const api = formApiRef.current;
if (!api) { if (!api) {
...@@ -75,6 +82,25 @@ const SettingsUptimeKuma = ({ options, refresh }) => { ...@@ -75,6 +82,25 @@ const SettingsUptimeKuma = ({ options, refresh }) => {
} }
}; };
const handleToggleEnabled = async (checked) => {
const newValue = checked ? 'true' : 'false';
try {
const res = await API.put('/api/option/', {
key: 'console_setting.uptime_kuma_enabled',
value: newValue,
});
if (res.data.success) {
setPanelEnabled(checked);
showSuccess(t('设置已保存'));
refresh?.();
} else {
showError(res.data.message);
}
} catch (err) {
showError(err.message);
}
};
const isValidUrl = useCallback((string) => { const isValidUrl = useCallback((string) => {
try { try {
new URL(string); new URL(string);
...@@ -103,7 +129,7 @@ const SettingsUptimeKuma = ({ options, refresh }) => { ...@@ -103,7 +129,7 @@ const SettingsUptimeKuma = ({ options, refresh }) => {
</Text> </Text>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2 items-center">
<Button <Button
icon={<Save size={14} />} icon={<Save size={14} />}
theme='solid' theme='solid'
...@@ -114,6 +140,9 @@ const SettingsUptimeKuma = ({ options, refresh }) => { ...@@ -114,6 +140,9 @@ const SettingsUptimeKuma = ({ options, refresh }) => {
> >
{t('保存设置')} {t('保存设置')}
</Button> </Button>
<Switch checked={panelEnabled} onChange={handleToggleEnabled} />
<Text>{panelEnabled ? t('已启用') : t('已禁用')}</Text>
</div> </div>
</div> </div>
</div> </div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment