Files
NapCatQQ/packages/napcat-webui-frontend/src/controllers/webui_manager.ts
手瓜一十雪 927797f3d5 Add SSL certificate management to WebUI config
Introduces backend API endpoints and frontend UI for managing SSL certificates, including viewing status, uploading, and deleting cert/key files. Adds a new SSL configuration tab in the dashboard, allowing users to enable HTTPS by providing PEM-formatted certificate and key, with changes taking effect after restart.
2026-01-30 14:28:47 +08:00

344 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import CryptoJS from 'crypto-js';
import { EventSourcePolyfill } from 'event-source-polyfill';
import { LogLevel } from '@/const/enum';
import { serverRequest } from '@/utils/request';
export interface Log {
level: LogLevel;
message: string;
}
export default class WebUIManager {
public static async checkWebUiLogined () {
const { data } =
await serverRequest.post<ServerResponse<boolean>>('/auth/check');
return data.data;
}
public static async loginWithToken (token: string) {
const sha256 = CryptoJS.SHA256(token + '.napcat').toString();
const { data } = await serverRequest.post<ServerResponse<AuthResponse>>(
'/auth/login',
{ hash: sha256 }
);
return data.data.Credential;
}
public static async changePassword (oldToken: string, newToken: string) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/auth/update_token',
{ oldToken, newToken }
);
return data.data;
}
public static async proxy<T> (url = '') {
const data = await serverRequest.get<ServerResponse<string>>(
'/base/proxy?url=' + encodeURIComponent(url)
);
data.data.data = JSON.parse(data.data.data);
return data.data as ServerResponse<T>;
}
public static async GetNapCatVersion () {
const { data } =
await serverRequest.get<ServerResponse<PackageInfo>>('/base/GetNapCatVersion');
return data.data;
}
public static async getLatestTag () {
const { data } =
await serverRequest.get<ServerResponse<string>>('/base/getLatestTag');
return data.data;
}
/**
* 版本信息接口
*/
static readonly VersionTypes = {
RELEASE: 'release',
PRERELEASE: 'prerelease',
ACTION: 'action',
} as const;
/**
* 获取所有可用的版本列表(支持分页、过滤和搜索)
* 懒加载:根据 type 参数只获取对应类型的版本
*/
public static async getAllReleases (options: {
page?: number;
pageSize?: number;
type?: 'release' | 'action' | 'all';
search?: string;
mirror?: string;
} = {}) {
const { page = 1, pageSize = 20, type = 'release', search = '', mirror } = options;
const { data } = await serverRequest.get<ServerResponse<{
versions: Array<{
tag: string;
type: 'release' | 'prerelease' | 'action';
artifactId?: number;
artifactName?: string;
createdAt?: string;
expiresAt?: string;
size?: number;
workflowRunId?: number;
headSha?: string;
}>;
pagination: {
page: number;
pageSize: number;
total: number;
totalPages: number;
};
mirror?: string;
}>>('/base/getAllReleases', {
params: { page, pageSize, type, search, mirror },
});
return data.data;
}
public static async getMirrors () {
const { data } =
await serverRequest.get<ServerResponse<{ mirrors: string[]; }>>('/base/getMirrors');
return data.data;
}
public static async UpdateNapCat (mirror?: string) {
const { data } = await serverRequest.post<ServerResponse<any>>(
'/UpdateNapCat/update',
{ mirror },
{ timeout: 120000 } // 2分钟超时
);
return data;
}
/**
* 更新到指定版本
* @param targetVersion 目标版本 tag如 "v4.9.9" 或 "action-123456"
* @param force 是否强制更新(允许降级)
* @param mirror 指定使用的镜像
*/
public static async UpdateNapCatToVersion (targetVersion: string, force: boolean = false, mirror?: string) {
const { data } = await serverRequest.post<ServerResponse<any>>(
'/UpdateNapCat/update',
{ targetVersion, force, mirror },
{ timeout: 120000 } // 2分钟超时
);
return data;
}
public static async getQQVersion () {
const { data } =
await serverRequest.get<ServerResponse<string>>('/base/QQVersion');
return data.data;
}
public static async getThemeConfig () {
const { data } =
await serverRequest.get<ServerResponse<ThemeConfig>>('/base/Theme');
return data.data;
}
public static async setThemeConfig (theme: ThemeConfig) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/base/SetTheme',
{ theme }
);
return data.data;
}
public static async restart () {
const { data } = await serverRequest.post<ServerResponse<any>>('/Process/Restart');
return data.data;
}
public static async getAllUsers (): Promise<any> {
const { data } = await serverRequest.get<ServerResponse<any>>('/QQLogin/GetAllUsers');
return data.data;
}
public static async getLogList () {
const { data } =
await serverRequest.get<ServerResponse<string[]>>('/Log/GetLogList');
return data.data;
}
public static async getLogContent (logName: string) {
const { data } = await serverRequest.get<ServerResponse<string>>(
`/Log/GetLog?id=${logName}`
);
return data.data;
}
public static getRealTimeLogs (writer: (data: Log[]) => void) {
const token = localStorage.getItem('token');
if (!token) {
throw new Error('未登录');
}
const _token = JSON.parse(token);
const eventSource = new EventSourcePolyfill('/api/Log/GetLogRealTime', {
headers: {
Authorization: `Bearer ${_token}`,
Accept: 'text/event-stream',
},
withCredentials: true,
});
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
data.message = data.message.replace(/\n/g, '\r\n');
writer([data]);
} catch (error) {
console.error(error);
}
};
eventSource.onerror = (error) => {
console.error('SSE连接出错:', error);
eventSource.close();
};
return eventSource;
}
public static getSystemStatus (writer: (data: SystemStatus) => void) {
const token = localStorage.getItem('token');
if (!token) {
throw new Error('未登录');
}
const _token = JSON.parse(token);
const eventSource = new EventSourcePolyfill(
'/api/base/GetSysStatusRealTime',
{
headers: {
Authorization: `Bearer ${_token}`,
Accept: 'text/event-stream',
},
withCredentials: true,
}
);
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data) as SystemStatus;
writer(data);
} catch (error) {
console.error(error);
}
};
eventSource.onerror = (error) => {
console.error('SSE连接出错:', error);
eventSource.close();
};
return eventSource;
}
// 获取WebUI基础配置
public static async getWebUIConfig () {
const { data } = await serverRequest.get<ServerResponse<WebUIConfig>>(
'/WebUIConfig/GetConfig'
);
return data.data;
}
// 更新WebUI基础配置
public static async updateWebUIConfig (config: Partial<WebUIConfig>) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/WebUIConfig/UpdateConfig',
config
);
return data.data;
}
// 获取是否禁用WebUI
public static async getDisableWebUI () {
const { data } = await serverRequest.get<ServerResponse<boolean>>(
'/WebUIConfig/GetDisableWebUI'
);
return data.data;
}
// 更新是否禁用WebUI
public static async updateDisableWebUI (disable: boolean) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/WebUIConfig/UpdateDisableWebUI',
{ disable }
);
return data.data;
}
// 获取当前客户端IP
public static async getClientIP () {
const { data } = await serverRequest.get<ServerResponse<{ ip: string; }>>(
'/WebUIConfig/GetClientIP'
);
return data.data;
}
// 获取SSL证书状态
public static async getSSLStatus () {
const { data } = await serverRequest.get<ServerResponse<{
enabled: boolean;
certExists: boolean;
keyExists: boolean;
certContent: string;
keyContent: string;
}>>('/WebUIConfig/GetSSLStatus');
return data.data;
}
// 保存SSL证书
public static async saveSSLCert (cert: string, key: string) {
const { data } = await serverRequest.post<ServerResponse<{ message: string; }>>(
'/WebUIConfig/UploadSSLCert',
{ cert, key }
);
return data.data;
}
// 删除SSL证书
public static async deleteSSLCert () {
const { data } = await serverRequest.post<ServerResponse<{ message: string; }>>(
'/WebUIConfig/DeleteSSLCert'
);
return data.data;
}
// Passkey相关方法
public static async generatePasskeyRegistrationOptions () {
const { data } = await serverRequest.post<ServerResponse<any>>(
'/auth/passkey/generate-registration-options'
);
return data.data;
}
public static async verifyPasskeyRegistration (response: any) {
const { data } = await serverRequest.post<ServerResponse<any>>(
'/auth/passkey/verify-registration',
{ response }
);
return data.data;
}
public static async generatePasskeyAuthenticationOptions () {
const { data } = await serverRequest.post<ServerResponse<any>>(
'/auth/passkey/generate-authentication-options'
);
return data.data;
}
public static async verifyPasskeyAuthentication (response: any) {
const { data } = await serverRequest.post<ServerResponse<any>>(
'/auth/passkey/verify-authentication',
{ response }
);
return data.data;
}
}