diff --git a/back/api/system.ts b/back/api/system.ts index 7c89bc3a..d11af688 100644 --- a/back/api/system.ts +++ b/back/api/system.ts @@ -442,6 +442,24 @@ export default (app: Router) => { }, ); + route.put( + '/config/panel-title', + celebrate({ + body: Joi.object({ + panelTitle: Joi.string().max(100).allow('').allow(null), + }), + }), + async (req: Request, res: Response, next: NextFunction) => { + try { + const systemService = Container.get(SystemService); + const result = await systemService.updatePanelTitle(req.body); + res.send(result); + } catch (e) { + return next(e); + } + }, + ); + route.put( '/config/global-ssh-key', celebrate({ diff --git a/back/data/system.ts b/back/data/system.ts index 6343c5e1..3f5cfce6 100644 --- a/back/data/system.ts +++ b/back/data/system.ts @@ -32,6 +32,7 @@ export enum AuthDataType { export interface SystemConfigInfo { lang?: string; + panelTitle?: string; logRemoveFrequency?: number; cronConcurrency?: number; dependenceProxy?: string; diff --git a/back/services/system.ts b/back/services/system.ts index 0bd6aef8..e6848170 100644 --- a/back/services/system.ts +++ b/back/services/system.ts @@ -548,24 +548,34 @@ export default class SystemService { return { code: 200, data: { lang } }; } + public async updatePanelTitle(info: SystemModelInfo) { + const oDoc = await this.getSystemConfig(); + const panelTitle = info.panelTitle?.trim() || ''; + await this.updateAuthDb({ + ...oDoc, + info: { ...oDoc.info, panelTitle }, + }); + return { code: 200, data: { panelTitle } }; + } + public async updateGlobalSshKey(info: SystemModelInfo) { const oDoc = await this.getSystemConfig(); const result = await this.updateAuthDb({ ...oDoc, info: { ...oDoc.info, ...info }, }); - + // Apply the global SSH key const SshKeyService = require('./sshKey').default; const Container = require('typedi').Container; const sshKeyService = Container.get(SshKeyService); - + if (info.globalSshKey) { await sshKeyService.addGlobalSSHKey(info.globalSshKey, 'global'); } else { await sshKeyService.removeGlobalSSHKey('global'); } - + return { code: 200, data: result }; } diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index 940d5ddb..f8e5c1ec 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -22,9 +22,11 @@ import WebSocketManager from '../utils/websocket'; export interface SharedContext { headerStyle: React.CSSProperties; isPhone: boolean; + siteTitle: string; theme: 'vs' | 'vs-dark'; user: any; reloadUser: (needLoading?: boolean) => void; + reloadSystemConfig: () => void; reloadTheme: () => void; systemInfo: TSystemInfo; } @@ -45,6 +47,9 @@ export default function () { const [user, setUser] = useState({}); const [loading, setLoading] = useState(true); const [systemInfo, setSystemInfo] = useState(); + const [siteTitle, setSiteTitle] = useState( + () => localStorage.getItem('qinglong_panel_title')?.trim() || intl.get('青龙'), + ); const [collapsed, setCollapsed] = useState(false); const [initLoading, setInitLoading] = useState(true); const { @@ -124,20 +129,18 @@ export default function () { getUser(needLoading); }; - useEffect(() => { - if (systemInfo && systemInfo.isInitialized && !user) { - getUser(); - } - }, [location.pathname]); - - useEffect(() => { - getHealthStatus(); - }, []); - - useEffect(() => { + const reloadSystemConfig = () => { request .get(`${config.apiPrefix}system/config`) .then(({ data }: any) => { + const panelTitle = data?.info?.panelTitle?.trim(); + if (panelTitle) { + setSiteTitle(panelTitle); + localStorage.setItem('qinglong_panel_title', panelTitle); + } else { + setSiteTitle(intl.get('青龙')); + localStorage.removeItem('qinglong_panel_title'); + } if (!data?.info?.lang) { const browserLang = localStorage.getItem('lang') || @@ -149,8 +152,31 @@ export default function () { } }) .catch(() => {}); + }; + + useEffect(() => { + if (systemInfo && systemInfo.isInitialized && !user) { + getUser(); + } + }, [location.pathname]); + + useEffect(() => { + getHealthStatus(); }, []); + useEffect(() => { + reloadSystemConfig(); + }, []); + + useEffect(() => { + if (!['/login', '/initialization', '/error'].includes(location.pathname)) { + return; + } + const title = + (config.documentTitleMap as any)[location.pathname] || intl.get('未找到'); + document.title = `${title} - ${siteTitle}`; + }, [location.pathname, siteTitle]); + useEffect(() => { if (theme === 'vs-dark') { document.body.setAttribute('data-dark', 'true'); @@ -229,7 +255,10 @@ export default function () { theme, user, reloadUser, + reloadSystemConfig, reloadTheme, + siteTitle, + systemInfo, }} /> ); @@ -310,7 +339,7 @@ export default function () { const title = (config.documentTitleMap as any)[location.pathname] || intl.get('未找到'); - return `${title} - ${intl.get('青龙')}`; + return `${title} - ${siteTitle}`; }} onCollapse={setCollapsed} collapsed={collapsed} @@ -372,7 +401,9 @@ export default function () { theme, user, reloadUser, + reloadSystemConfig, reloadTheme, + siteTitle, systemInfo, }} /> diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 813eecc6..3d97a587 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -581,7 +581,10 @@ "链接": "Link", "错误": "Error", "错误日志": "Error Log", + "留空使用默认值“青龙”": "Leave blank to use the default title \"Qinglong\"", "队列中": "In Queue", + "自定义面板的站点标题,留空使用默认值“青龙”": "Customize the panel site title. Leave blank to use the default title \"Qinglong\"", + "面板标题": "Panel Title", "青龙": "Qinglong", "项": "items", "顺序": "Order", diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json index 37e642f8..12ef0f0b 100644 --- a/src/locales/zh-CN.json +++ b/src/locales/zh-CN.json @@ -581,7 +581,10 @@ "链接": "链接", "错误": "错误", "错误日志": "错误日志", + "留空使用默认值“青龙”": "留空使用默认值“青龙”", "队列中": "队列中", + "自定义面板的站点标题,留空使用默认值“青龙”": "自定义面板的站点标题,留空使用默认值“青龙”", + "面板标题": "面板标题", "青龙": "青龙", "项": "项", "顺序": "顺序", diff --git a/src/pages/setting/index.tsx b/src/pages/setting/index.tsx index a3242536..dc0f17f9 100644 --- a/src/pages/setting/index.tsx +++ b/src/pages/setting/index.tsx @@ -46,6 +46,7 @@ const Setting = () => { user, theme, reloadUser, + reloadSystemConfig, reloadTheme, systemInfo, } = useOutletContext(); @@ -353,7 +354,11 @@ const Setting = () => { key: 'other', label: intl.get('其他设置'), children: ( - + ), }, { diff --git a/src/pages/setting/other.tsx b/src/pages/setting/other.tsx index 60ed959e..d698e34e 100644 --- a/src/pages/setting/other.tsx +++ b/src/pages/setting/other.tsx @@ -27,6 +27,7 @@ import { disableBody } from '@/utils'; import { TIMEZONES } from '@/utils/const'; const dataMap = { + 'panel-title': 'panelTitle', 'log-remove-frequency': 'logRemoveFrequency', 'cron-concurrency': 'cronConcurrency', timezone: 'timezone', @@ -48,10 +49,15 @@ const exportModules = [ const Other = ({ systemInfo, + reloadSystemConfig, reloadTheme, -}: Pick) => { +}: Pick< + SharedContext, + 'reloadSystemConfig' | 'reloadTheme' | 'systemInfo' +>) => { const defaultTheme = localStorage.getItem('qinglong_dark_theme') || 'auto'; const [systemConfig, setSystemConfig] = useState<{ + panelTitle?: string | null; logRemoveFrequency?: number | null; cronConcurrency?: number | null; timezone?: string | null; @@ -120,6 +126,9 @@ const Other = ({ .then(({ code, data }) => { if (code === 200) { message.success(intl.get('更新成功')); + if (path === 'panel-title') { + reloadSystemConfig(); + } } }) .catch((error: any) => { @@ -236,6 +245,35 @@ const Other = ({ + + + { + setSystemConfig({ + ...systemConfig, + panelTitle: e.target.value, + }); + }} + /> + + +