feat: add gateway management features and settings

- Implemented new API routes for handling logs and settings related to the gateway.
- Added a new Gateway section in the General Settings panel to manage gateway status and auto-start options.
- Introduced a state management hook for gateway settings, including status, logs, and auto-start functionality.
- Updated configuration service to include gateway auto-start setting.
- Enhanced internationalization support for new gateway-related messages.
- Refactored existing settings store to accommodate new gateway settings.
- Cleaned up code and improved logging functionality.
This commit is contained in:
duanshuwen
2026-04-20 22:22:11 +08:00
parent f7f4ffaee9
commit dfef9c90a5
18 changed files with 1139 additions and 15 deletions

View File

@@ -6,7 +6,11 @@ import { debounce } from '@runtime/lib/utils'
import logManager from '@electron/service/logger'
import { getUserDataDir } from '@electron/utils/paths'
const DEFAULT_CONFIG: IConfig = {
type AppConfig = IConfig & {
[CONFIG_KEYS.GATEWAY_AUTO_START]: boolean;
};
const DEFAULT_CONFIG: AppConfig = {
[CONFIG_KEYS.THEME_MODE]: 'system',
[CONFIG_KEYS.PRIMARY_COLOR]: '#BB5BE7',
[CONFIG_KEYS.LANGUAGE]: 'zh',
@@ -17,14 +21,15 @@ const DEFAULT_CONFIG: IConfig = {
[CONFIG_KEYS.SELECTED_CHANNELS]: [],
[CONFIG_KEYS.IMAGE_CACHE]: [],
[CONFIG_KEYS.TASK_LIST]: [],
[CONFIG_KEYS.GATEWAY_AUTO_START]: true,
}
export class ConfigService {
private static _instance: ConfigService;
private _store: any;
private _defaultConfig: IConfig = DEFAULT_CONFIG;
private _defaultConfig: AppConfig = DEFAULT_CONFIG;
private _listeners: Array<(config: IConfig) => void> = [];
private _listeners: Array<(config: AppConfig) => void> = [];
private constructor() {
@@ -34,7 +39,7 @@ export class ConfigService {
public async init() {
if (this._store) return;
const { default: Store } = await import('electron-store');
this._store = new Store<IConfig>({
this._store = new Store<AppConfig>({
name: 'config',
cwd: getUserDataDir(),
defaults: DEFAULT_CONFIG,
@@ -74,7 +79,7 @@ export class ConfigService {
this._listeners.forEach(listener => listener({ ...this._store.store }));
}
public getConfig(): IConfig {
public getConfig(): AppConfig {
if (!this._store) {
return { ...this._defaultConfig };
}
@@ -98,7 +103,7 @@ export class ConfigService {
autoSave && this._notifyListeners();
}
public update(updates: Partial<IConfig>, autoSave: boolean = true): void {
public update(updates: Partial<AppConfig>, autoSave: boolean = true): void {
this._ensureStore();
(Object.keys(updates) as ConfigKeys[]).forEach((key) => {
this._store.set(key, updates[key]);
@@ -115,7 +120,7 @@ export class ConfigService {
this._notifyListeners();
}
public onConfigChange(listener: ((config: IConfig) => void)): () => void {
public onConfigChange(listener: ((config: AppConfig) => void)): () => void {
this._listeners.push(listener);
return () => this._listeners = this._listeners.filter(l => l !== listener);

View File

@@ -13,6 +13,7 @@ const unlinkAsync = promisify(fs.unlink);
class LogService {
private static _instance: LogService;
private readonly logDirPath: string;
// 日志保留天数默认7天
private LOG_RETENTION_DAYS = 7;
@@ -21,7 +22,8 @@ class LogService {
private readonly CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1000;
private constructor() {
const logPath = path.join(getUserDataDir(), 'logs');
this.logDirPath = path.join(getUserDataDir(), 'logs');
const logPath = this.logDirPath;
// c:users/{username}/AppData/Roaming/{appName}/logs
// 创建日志目录
@@ -175,6 +177,54 @@ class LogService {
this.info(`User Operation: ${operation} by ${userId}, Details: ${JSON.stringify(details)}`);
}
public async readRecentLogText(tailLines: number = 200): Promise<string> {
const safeTailLines = Number.isFinite(tailLines) ? Math.max(1, Math.floor(tailLines)) : 200;
const filePath = this._getCurrentLogFilePath();
if (!fs.existsSync(filePath)) {
return '';
}
const file = await fs.promises.open(filePath, 'r');
try {
const fileStat = await file.stat();
if (fileStat.size === 0) {
return '';
}
const chunkSize = 64 * 1024;
let position = fileStat.size;
let content = '';
let lineCount = 0;
while (position > 0 && lineCount <= safeTailLines) {
const bytesToRead = Math.min(chunkSize, position);
position -= bytesToRead;
const buffer = Buffer.allocUnsafe(bytesToRead);
await file.read(buffer, 0, bytesToRead, position);
content = `${buffer.toString('utf-8')}${content}`;
lineCount = content.split('\n').length - 1;
}
const lines = content.split('\n');
if (lines.length <= safeTailLines) {
return content;
}
return lines.slice(-safeTailLines).join('\n');
} finally {
await file.close();
}
}
private _getCurrentLogFilePath(): string {
const today = new Date();
const formattedDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
return path.join(this.logDirPath, `${formattedDate}.log`);
}
}
export const logManager = LogService.getInstance();