From 4c05377c870268387bbb3a29e226ea50e6556071 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Sat, 14 Jun 2025 01:39:23 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=9B=EF=B8=8F=20feat(dashboard):=20add?= =?UTF-8?q?=20per-panel=20enable=20switches=20&=20conditional=20backend=20?= =?UTF-8?q?payload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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._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. --- controller/misc.go | 105 +++-- setting/console_setting/config.go | 8 + .../components/settings/DashboardSetting.js | 4 + web/src/pages/Detail/index.js | 417 +++++++++--------- .../Setting/Dashboard/SettingsAPIInfo.js | 39 +- .../Dashboard/SettingsAnnouncements.js | 36 +- .../pages/Setting/Dashboard/SettingsFAQ.js | 36 +- .../Setting/Dashboard/SettingsUptimeKuma.js | 31 +- 8 files changed, 428 insertions(+), 248 deletions(-) diff --git a/controller/misc.go b/controller/misc.go index ccbdecb5..33a41302 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -38,52 +38,71 @@ func TestStatus(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{ "success": true, "message": "", - "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, - "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(), - }, + "data": data, }) return } diff --git a/setting/console_setting/config.go b/setting/console_setting/config.go index 1379380e..063130fc 100644 --- a/setting/console_setting/config.go +++ b/setting/console_setting/config.go @@ -8,6 +8,10 @@ type ConsoleSetting struct { UptimeKumaSlug string `json:"uptime_kuma_slug"` // Uptime Kuma Status Page Slug Announcements string `json:"announcements"` // 系统公告 (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{ UptimeKumaSlug: "", Announcements: "", FAQ: "", + ApiInfoEnabled: true, + UptimeKumaEnabled: true, + AnnouncementsEnabled: true, + FAQEnabled: true, } // 全局实例 diff --git a/web/src/components/settings/DashboardSetting.js b/web/src/components/settings/DashboardSetting.js index 7021c7ca..0546ca21 100644 --- a/web/src/components/settings/DashboardSetting.js +++ b/web/src/components/settings/DashboardSetting.js @@ -13,6 +13,10 @@ const DashboardSetting = () => { 'console_setting.faq': '', 'console_setting.uptime_kuma_url': '', 'console_setting.uptime_kuma_slug': '', + 'console_setting.api_info_enabled': '', + 'console_setting.announcements_enabled': '', + 'console_setting.faq_enabled': '', + 'console_setting.uptime_kuma_enabled': '', // 用于迁移检测的旧键,下个版本会删除 ApiInfo: '', diff --git a/web/src/pages/Detail/index.js b/web/src/pages/Detail/index.js index f1d23871..747c1cf6 100644 --- a/web/src/pages/Detail/index.js +++ b/web/src/pages/Detail/index.js @@ -90,6 +90,15 @@ const Detail = (props) => { let now = new Date(); const isAdminUser = isAdmin(); + // ========== Panel enable flags ========== + const apiInfoEnabled = statusState?.status?.api_info_enabled ?? true; + const announcementsEnabled = statusState?.status?.announcements_enabled ?? true; + const faqEnabled = statusState?.status?.faq_enabled ?? true; + const uptimeEnabled = statusState?.status?.uptime_kuma_enabled ?? true; + + const hasApiInfoPanel = apiInfoEnabled; + const hasInfoPanels = announcementsEnabled || faqEnabled || uptimeEnabled; + // ========== Helper Functions ========== const getDefaultTime = useCallback(() => { return localStorage.getItem('data_export_default_time') || 'hour'; @@ -1015,10 +1024,10 @@ const Detail = (props) => {
-
+
@@ -1061,7 +1070,7 @@ const Detail = (props) => {
- {!statusState?.status?.self_use_mode_enabled && ( + {hasApiInfoPanel && ( {
{/* 系统公告和常见问答卡片 */} - {!statusState?.status?.self_use_mode_enabled && ( + {hasInfoPanels && (
{/* 公告卡片 */} - -
- - {t('系统公告')} - - {t('显示最新20条')} - -
- {/* 图例 */} -
- {announcementLegendData.map((legend, index) => ( -
-
- {legend.label} -
- ))} -
-
- } - > -
-
handleCardScroll(announcementScrollRef, setShowAnnouncementScrollHint)} - > - {announcementData.length > 0 ? ( - - ) : ( -
- } - darkModeImage={} - title={t('暂无系统公告')} - description={t('请联系管理员在系统设置中配置公告信息')} - style={{ padding: '12px' }} - /> + {announcementsEnabled && ( + +
+ + {t('系统公告')} + + {t('显示最新20条')} +
- )} -
-
-
- - - {/* 常见问答卡片 */} - - - {t('常见问答')} -
- } - > -
-
handleCardScroll(faqScrollRef, setShowFaqScrollHint)} - > - {faqData.length > 0 ? ( - } - collapseIcon={} - > - {faqData.map((item, index) => ( - -

{item.answer}

-
- ))} -
- ) : ( -
- } - darkModeImage={} - title={t('暂无常见问答')} - description={t('请联系管理员在系统设置中配置常见问答')} - style={{ padding: '12px' }} - /> -
- )} -
-
-
- - - {/* 服务可用性卡片 */} - -
- - {t('服务可用性')} -
- } - onClick={loadUptimeData} - loading={uptimeLoading} - size="small" - theme="borderless" - className="text-gray-500 hover:text-blue-500 hover:bg-blue-50 !rounded-full" - /> -
- } - footer={uptimeData.length > 0 ? ( - -
- {uptimeLegendData.map((legend, index) => ( -
-
- {legend.label} -
- ))} -
- - ) : null} - footerStyle={uptimeData.length > 0 ? { padding: '0px' } : undefined} - > -
- -
handleCardScroll(uptimeScrollRef, setShowUptimeScrollHint)} - > - {uptimeData.length > 0 ? ( - uptimeData.map((monitor, idx) => ( -
-
-
-
- {monitor.name} -
- {((monitor.uptime || 0) * 100).toFixed(2)}% -
-
- {getUptimeStatusText(monitor.status)} -
- -
-
+ {/* 图例 */} +
+ {announcementLegendData.map((legend, index) => ( +
+
+ {legend.label}
- )) + ))} +
+
+ } + > +
+
handleCardScroll(announcementScrollRef, setShowAnnouncementScrollHint)} + > + {announcementData.length > 0 ? ( + ) : (
} darkModeImage={} - title={t('暂无监控数据')} - description={t('请联系管理员在系统设置中配置Uptime')} + title={t('暂无系统公告')} + description={t('请联系管理员在系统设置中配置公告信息')} style={{ padding: '12px' }} />
)}
- -
-
- +
+
+ + )} + + {/* 常见问答卡片 */} + {faqEnabled && ( + + + {t('常见问答')} +
+ } + > +
+
handleCardScroll(faqScrollRef, setShowFaqScrollHint)} + > + {faqData.length > 0 ? ( + } + collapseIcon={} + > + {faqData.map((item, index) => ( + +

{item.answer}

+
+ ))} +
+ ) : ( +
+ } + darkModeImage={} + title={t('暂无常见问答')} + description={t('请联系管理员在系统设置中配置常见问答')} + style={{ padding: '12px' }} + /> +
+ )} +
+
+
+ + )} + + {/* 服务可用性卡片 */} + {uptimeEnabled && ( + +
+ + {t('服务可用性')} +
+ } + onClick={loadUptimeData} + loading={uptimeLoading} + size="small" + theme="borderless" + className="text-gray-500 hover:text-blue-500 hover:bg-blue-50 !rounded-full" + /> +
+ } + footer={uptimeData.length > 0 ? ( + +
+ {uptimeLegendData.map((legend, index) => ( +
+
+ {legend.label} +
+ ))} +
+ + ) : null} + footerStyle={uptimeData.length > 0 ? { padding: '0px' } : undefined} + > +
+ +
handleCardScroll(uptimeScrollRef, setShowUptimeScrollHint)} + > + {uptimeData.length > 0 ? ( + uptimeData.map((monitor, idx) => ( +
+
+
+
+ {monitor.name} +
+ {((monitor.uptime || 0) * 100).toFixed(2)}% +
+
+ {getUptimeStatusText(monitor.status)} +
+ +
+
+
+ )) + ) : ( +
+ } + darkModeImage={} + title={t('暂无监控数据')} + description={t('请联系管理员在系统设置中配置Uptime')} + style={{ padding: '12px' }} + /> +
+ )} +
+ +
+
+ + )}
)} diff --git a/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js b/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js index 7cb7275b..6a80b358 100644 --- a/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js +++ b/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js @@ -9,7 +9,8 @@ import { Divider, Avatar, Modal, - Tag + Tag, + Switch } from '@douyinfe/semi-ui'; import { IllustrationNoResult, @@ -48,6 +49,9 @@ const SettingsAPIInfo = ({ options, refresh }) => { const [pageSize, setPageSize] = useState(10); const [selectedRowKeys, setSelectedRowKeys] = useState([]); + // 面板启用状态 state + const [panelEnabled, setPanelEnabled] = useState(true); + const colorOptions = [ { value: 'blue', label: 'blue' }, { value: 'green', label: 'green' }, @@ -191,6 +195,30 @@ const SettingsAPIInfo = ({ options, refresh }) => { } }, [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 = [ { title: 'ID', @@ -325,6 +353,15 @@ const SettingsAPIInfo = ({ options, refresh }) => { {t('保存设置')}
+ + {/* 启用开关 */} +
+ + {panelEnabled ? t('已启用') : t('已禁用')} +
); diff --git a/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js b/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js index caa632e1..f1b99f43 100644 --- a/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js +++ b/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js @@ -8,7 +8,8 @@ import { Empty, Divider, Modal, - Tag + Tag, + Switch } from '@douyinfe/semi-ui'; import { IllustrationNoResult, @@ -47,6 +48,9 @@ const SettingsAnnouncements = ({ options, refresh }) => { const [pageSize, setPageSize] = useState(10); const [selectedRowKeys, setSelectedRowKeys] = useState([]); + // 面板启用状态 + const [panelEnabled, setPanelEnabled] = useState(true); + const typeOptions = [ { value: 'default', label: t('默认') }, { value: 'ongoing', label: t('进行中') }, @@ -294,6 +298,30 @@ const SettingsAnnouncements = ({ options, refresh }) => { } }, [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 = () => { if (selectedRowKeys.length === 0) { showError('请先选择要删除的系统公告'); @@ -350,6 +378,12 @@ const SettingsAnnouncements = ({ options, refresh }) => { {t('保存设置')}
+ + {/* 启用开关 */} +
+ + {panelEnabled ? t('已启用') : t('已禁用')} +
); diff --git a/web/src/pages/Setting/Dashboard/SettingsFAQ.js b/web/src/pages/Setting/Dashboard/SettingsFAQ.js index d1211899..0e029e13 100644 --- a/web/src/pages/Setting/Dashboard/SettingsFAQ.js +++ b/web/src/pages/Setting/Dashboard/SettingsFAQ.js @@ -7,7 +7,8 @@ import { Typography, Empty, Divider, - Modal + Modal, + Switch } from '@douyinfe/semi-ui'; import { IllustrationNoResult, @@ -44,6 +45,9 @@ const SettingsFAQ = ({ options, refresh }) => { const [pageSize, setPageSize] = useState(10); const [selectedRowKeys, setSelectedRowKeys] = useState([]); + // 面板启用状态 + const [panelEnabled, setPanelEnabled] = useState(true); + const columns = [ { title: t('问题标题'), @@ -231,6 +235,30 @@ const SettingsFAQ = ({ options, refresh }) => { } }, [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 = () => { if (selectedRowKeys.length === 0) { showError('请先选择要删除的常见问答'); @@ -287,6 +315,12 @@ const SettingsFAQ = ({ options, refresh }) => { {t('保存设置')}
+ + {/* 启用开关 */} +
+ + {panelEnabled ? t('已启用') : t('已禁用')} +
); diff --git a/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js b/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js index 58f8fedf..d489b683 100644 --- a/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js +++ b/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js @@ -5,6 +5,7 @@ import { Typography, Row, Col, + Switch, } from '@douyinfe/semi-ui'; import { Save, @@ -19,6 +20,7 @@ const SettingsUptimeKuma = ({ options, refresh }) => { const { t } = useTranslation(); const [loading, setLoading] = useState(false); + const [panelEnabled, setPanelEnabled] = useState(true); const formApiRef = useRef(null); const initValues = useMemo(() => ({ @@ -32,6 +34,11 @@ const SettingsUptimeKuma = ({ options, refresh }) => { } }, [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 api = formApiRef.current; if (!api) { @@ -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) => { try { new URL(string); @@ -103,7 +129,7 @@ const SettingsUptimeKuma = ({ options, refresh }) => {
-
+
+ + + {panelEnabled ? t('已启用') : t('已禁用')}