mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-30 17:38:10 +08:00
Add configurable browser tab title for multi-instance QingLong deployments (#3041)
This commit is contained in:
@@ -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(
|
route.put(
|
||||||
'/config/global-ssh-key',
|
'/config/global-ssh-key',
|
||||||
celebrate({
|
celebrate({
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export enum AuthDataType {
|
|||||||
|
|
||||||
export interface SystemConfigInfo {
|
export interface SystemConfigInfo {
|
||||||
lang?: string;
|
lang?: string;
|
||||||
|
panelTitle?: string;
|
||||||
logRemoveFrequency?: number;
|
logRemoveFrequency?: number;
|
||||||
cronConcurrency?: number;
|
cronConcurrency?: number;
|
||||||
dependenceProxy?: string;
|
dependenceProxy?: string;
|
||||||
|
|||||||
+13
-3
@@ -548,24 +548,34 @@ export default class SystemService {
|
|||||||
return { code: 200, data: { lang } };
|
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) {
|
public async updateGlobalSshKey(info: SystemModelInfo) {
|
||||||
const oDoc = await this.getSystemConfig();
|
const oDoc = await this.getSystemConfig();
|
||||||
const result = await this.updateAuthDb({
|
const result = await this.updateAuthDb({
|
||||||
...oDoc,
|
...oDoc,
|
||||||
info: { ...oDoc.info, ...info },
|
info: { ...oDoc.info, ...info },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Apply the global SSH key
|
// Apply the global SSH key
|
||||||
const SshKeyService = require('./sshKey').default;
|
const SshKeyService = require('./sshKey').default;
|
||||||
const Container = require('typedi').Container;
|
const Container = require('typedi').Container;
|
||||||
const sshKeyService = Container.get(SshKeyService);
|
const sshKeyService = Container.get(SshKeyService);
|
||||||
|
|
||||||
if (info.globalSshKey) {
|
if (info.globalSshKey) {
|
||||||
await sshKeyService.addGlobalSSHKey(info.globalSshKey, 'global');
|
await sshKeyService.addGlobalSSHKey(info.globalSshKey, 'global');
|
||||||
} else {
|
} else {
|
||||||
await sshKeyService.removeGlobalSSHKey('global');
|
await sshKeyService.removeGlobalSSHKey('global');
|
||||||
}
|
}
|
||||||
|
|
||||||
return { code: 200, data: result };
|
return { code: 200, data: result };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+43
-12
@@ -22,9 +22,11 @@ import WebSocketManager from '../utils/websocket';
|
|||||||
export interface SharedContext {
|
export interface SharedContext {
|
||||||
headerStyle: React.CSSProperties;
|
headerStyle: React.CSSProperties;
|
||||||
isPhone: boolean;
|
isPhone: boolean;
|
||||||
|
siteTitle: string;
|
||||||
theme: 'vs' | 'vs-dark';
|
theme: 'vs' | 'vs-dark';
|
||||||
user: any;
|
user: any;
|
||||||
reloadUser: (needLoading?: boolean) => void;
|
reloadUser: (needLoading?: boolean) => void;
|
||||||
|
reloadSystemConfig: () => void;
|
||||||
reloadTheme: () => void;
|
reloadTheme: () => void;
|
||||||
systemInfo: TSystemInfo;
|
systemInfo: TSystemInfo;
|
||||||
}
|
}
|
||||||
@@ -45,6 +47,9 @@ export default function () {
|
|||||||
const [user, setUser] = useState<any>({});
|
const [user, setUser] = useState<any>({});
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
const [systemInfo, setSystemInfo] = useState<TSystemInfo>();
|
const [systemInfo, setSystemInfo] = useState<TSystemInfo>();
|
||||||
|
const [siteTitle, setSiteTitle] = useState(
|
||||||
|
() => localStorage.getItem('qinglong_panel_title')?.trim() || intl.get('青龙'),
|
||||||
|
);
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
const [initLoading, setInitLoading] = useState<boolean>(true);
|
const [initLoading, setInitLoading] = useState<boolean>(true);
|
||||||
const {
|
const {
|
||||||
@@ -124,20 +129,18 @@ export default function () {
|
|||||||
getUser(needLoading);
|
getUser(needLoading);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const reloadSystemConfig = () => {
|
||||||
if (systemInfo && systemInfo.isInitialized && !user) {
|
|
||||||
getUser();
|
|
||||||
}
|
|
||||||
}, [location.pathname]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getHealthStatus();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}system/config`)
|
.get(`${config.apiPrefix}system/config`)
|
||||||
.then(({ data }: any) => {
|
.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) {
|
if (!data?.info?.lang) {
|
||||||
const browserLang =
|
const browserLang =
|
||||||
localStorage.getItem('lang') ||
|
localStorage.getItem('lang') ||
|
||||||
@@ -149,8 +152,31 @@ export default function () {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.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(() => {
|
useEffect(() => {
|
||||||
if (theme === 'vs-dark') {
|
if (theme === 'vs-dark') {
|
||||||
document.body.setAttribute('data-dark', 'true');
|
document.body.setAttribute('data-dark', 'true');
|
||||||
@@ -229,7 +255,10 @@ export default function () {
|
|||||||
theme,
|
theme,
|
||||||
user,
|
user,
|
||||||
reloadUser,
|
reloadUser,
|
||||||
|
reloadSystemConfig,
|
||||||
reloadTheme,
|
reloadTheme,
|
||||||
|
siteTitle,
|
||||||
|
systemInfo,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -310,7 +339,7 @@ export default function () {
|
|||||||
const title =
|
const title =
|
||||||
(config.documentTitleMap as any)[location.pathname] ||
|
(config.documentTitleMap as any)[location.pathname] ||
|
||||||
intl.get('未找到');
|
intl.get('未找到');
|
||||||
return `${title} - ${intl.get('青龙')}`;
|
return `${title} - ${siteTitle}`;
|
||||||
}}
|
}}
|
||||||
onCollapse={setCollapsed}
|
onCollapse={setCollapsed}
|
||||||
collapsed={collapsed}
|
collapsed={collapsed}
|
||||||
@@ -372,7 +401,9 @@ export default function () {
|
|||||||
theme,
|
theme,
|
||||||
user,
|
user,
|
||||||
reloadUser,
|
reloadUser,
|
||||||
|
reloadSystemConfig,
|
||||||
reloadTheme,
|
reloadTheme,
|
||||||
|
siteTitle,
|
||||||
systemInfo,
|
systemInfo,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -581,7 +581,10 @@
|
|||||||
"链接": "Link",
|
"链接": "Link",
|
||||||
"错误": "Error",
|
"错误": "Error",
|
||||||
"错误日志": "Error Log",
|
"错误日志": "Error Log",
|
||||||
|
"留空使用默认值“青龙”": "Leave blank to use the default title \"Qinglong\"",
|
||||||
"队列中": "In Queue",
|
"队列中": "In Queue",
|
||||||
|
"自定义面板的站点标题,留空使用默认值“青龙”": "Customize the panel site title. Leave blank to use the default title \"Qinglong\"",
|
||||||
|
"面板标题": "Panel Title",
|
||||||
"青龙": "Qinglong",
|
"青龙": "Qinglong",
|
||||||
"项": "items",
|
"项": "items",
|
||||||
"顺序": "Order",
|
"顺序": "Order",
|
||||||
|
|||||||
@@ -581,7 +581,10 @@
|
|||||||
"链接": "链接",
|
"链接": "链接",
|
||||||
"错误": "错误",
|
"错误": "错误",
|
||||||
"错误日志": "错误日志",
|
"错误日志": "错误日志",
|
||||||
|
"留空使用默认值“青龙”": "留空使用默认值“青龙”",
|
||||||
"队列中": "队列中",
|
"队列中": "队列中",
|
||||||
|
"自定义面板的站点标题,留空使用默认值“青龙”": "自定义面板的站点标题,留空使用默认值“青龙”",
|
||||||
|
"面板标题": "面板标题",
|
||||||
"青龙": "青龙",
|
"青龙": "青龙",
|
||||||
"项": "项",
|
"项": "项",
|
||||||
"顺序": "顺序",
|
"顺序": "顺序",
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ const Setting = () => {
|
|||||||
user,
|
user,
|
||||||
theme,
|
theme,
|
||||||
reloadUser,
|
reloadUser,
|
||||||
|
reloadSystemConfig,
|
||||||
reloadTheme,
|
reloadTheme,
|
||||||
systemInfo,
|
systemInfo,
|
||||||
} = useOutletContext<SharedContext>();
|
} = useOutletContext<SharedContext>();
|
||||||
@@ -353,7 +354,11 @@ const Setting = () => {
|
|||||||
key: 'other',
|
key: 'other',
|
||||||
label: intl.get('其他设置'),
|
label: intl.get('其他设置'),
|
||||||
children: (
|
children: (
|
||||||
<Other reloadTheme={reloadTheme} systemInfo={systemInfo} />
|
<Other
|
||||||
|
reloadSystemConfig={reloadSystemConfig}
|
||||||
|
reloadTheme={reloadTheme}
|
||||||
|
systemInfo={systemInfo}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { disableBody } from '@/utils';
|
|||||||
import { TIMEZONES } from '@/utils/const';
|
import { TIMEZONES } from '@/utils/const';
|
||||||
|
|
||||||
const dataMap = {
|
const dataMap = {
|
||||||
|
'panel-title': 'panelTitle',
|
||||||
'log-remove-frequency': 'logRemoveFrequency',
|
'log-remove-frequency': 'logRemoveFrequency',
|
||||||
'cron-concurrency': 'cronConcurrency',
|
'cron-concurrency': 'cronConcurrency',
|
||||||
timezone: 'timezone',
|
timezone: 'timezone',
|
||||||
@@ -48,10 +49,15 @@ const exportModules = [
|
|||||||
|
|
||||||
const Other = ({
|
const Other = ({
|
||||||
systemInfo,
|
systemInfo,
|
||||||
|
reloadSystemConfig,
|
||||||
reloadTheme,
|
reloadTheme,
|
||||||
}: Pick<SharedContext, 'reloadTheme' | 'systemInfo'>) => {
|
}: Pick<
|
||||||
|
SharedContext,
|
||||||
|
'reloadSystemConfig' | 'reloadTheme' | 'systemInfo'
|
||||||
|
>) => {
|
||||||
const defaultTheme = localStorage.getItem('qinglong_dark_theme') || 'auto';
|
const defaultTheme = localStorage.getItem('qinglong_dark_theme') || 'auto';
|
||||||
const [systemConfig, setSystemConfig] = useState<{
|
const [systemConfig, setSystemConfig] = useState<{
|
||||||
|
panelTitle?: string | null;
|
||||||
logRemoveFrequency?: number | null;
|
logRemoveFrequency?: number | null;
|
||||||
cronConcurrency?: number | null;
|
cronConcurrency?: number | null;
|
||||||
timezone?: string | null;
|
timezone?: string | null;
|
||||||
@@ -120,6 +126,9 @@ const Other = ({
|
|||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
message.success(intl.get('更新成功'));
|
message.success(intl.get('更新成功'));
|
||||||
|
if (path === 'panel-title') {
|
||||||
|
reloadSystemConfig();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
@@ -236,6 +245,35 @@ const Other = ({
|
|||||||
</Radio.Button>
|
</Radio.Button>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={intl.get('面板标题')}
|
||||||
|
name="panelTitle"
|
||||||
|
tooltip={intl.get('自定义面板的站点标题,留空使用默认值“青龙”')}
|
||||||
|
>
|
||||||
|
<Input.Group compact>
|
||||||
|
<Input
|
||||||
|
style={{ width: 180 }}
|
||||||
|
maxLength={100}
|
||||||
|
value={systemConfig?.panelTitle || ''}
|
||||||
|
placeholder={intl.get('留空使用默认值“青龙”')}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSystemConfig({
|
||||||
|
...systemConfig,
|
||||||
|
panelTitle: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
updateSystemConfig('panel-title');
|
||||||
|
}}
|
||||||
|
style={{ width: 84 }}
|
||||||
|
>
|
||||||
|
{intl.get('确认')}
|
||||||
|
</Button>
|
||||||
|
</Input.Group>
|
||||||
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={intl.get('日志删除频率')}
|
label={intl.get('日志删除频率')}
|
||||||
name="frequency"
|
name="frequency"
|
||||||
|
|||||||
Reference in New Issue
Block a user