update
This commit is contained in:
@@ -2,6 +2,7 @@ import { Database } from 'sqlite3';
|
||||
import * as schemaSql from './schema';
|
||||
import * as appearanceRepository from '../repositories/appearance.repository';
|
||||
import * as terminalThemeRepository from '../repositories/terminal-theme.repository';
|
||||
import * as settingsRepository from '../repositories/settings.repository'; // <-- Import settings repository
|
||||
import { presetTerminalThemes } from '../config/preset-themes-definition';
|
||||
import { runDb } from './connection'; // Import runDb for init functions
|
||||
|
||||
@@ -16,20 +17,7 @@ export interface TableDefinition {
|
||||
|
||||
// --- Initialization Functions ---
|
||||
|
||||
/**
|
||||
* Initializes default settings in the settings table.
|
||||
*/
|
||||
const initSettingsTable = async (db: Database): Promise<void> => {
|
||||
const defaultSettings = [
|
||||
{ key: 'ipWhitelistEnabled', value: 'false' },
|
||||
{ key: 'ipWhitelist', value: '' }
|
||||
];
|
||||
for (const setting of defaultSettings) {
|
||||
// Use INSERT OR IGNORE to avoid errors if settings already exist
|
||||
await runDb(db, "INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", [setting.key, setting.value]);
|
||||
}
|
||||
console.log('[DB Init] 默认 settings 初始化检查完成。');
|
||||
};
|
||||
// Remove the old initSettingsTable function, as the logic is now in the repository
|
||||
|
||||
/**
|
||||
* Initializes preset terminal themes.
|
||||
@@ -66,7 +54,7 @@ export const tableDefinitions: TableDefinition[] = [
|
||||
{
|
||||
name: 'settings',
|
||||
sql: schemaSql.createSettingsTableSQL,
|
||||
init: initSettingsTable
|
||||
init: settingsRepository.ensureDefaultSettingsExist // <-- Use the function from the repository
|
||||
},
|
||||
{ name: 'audit_logs', sql: schemaSql.createAuditLogsTableSQL },
|
||||
{ name: 'api_keys', sql: schemaSql.createApiKeysTableSQL },
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// packages/backend/src/repositories/settings.repository.ts
|
||||
import { getDbInstance, runDb, getDb as getDbRow, allDb } from '../database/connection'; // Import new async helpers
|
||||
import { getDbInstance, runDb, getDb as getDbRow, allDb } from '../database/connection';
|
||||
import { SidebarConfig } from '../types/settings.types'; // <-- Correct import path
|
||||
import * as sqlite3 from 'sqlite3'; // Import sqlite3 for Database type hint
|
||||
|
||||
// Remove top-level db instance
|
||||
// const db = getDb();
|
||||
// Define keys for specific settings
|
||||
const SIDEBAR_CONFIG_KEY = 'sidebarConfig';
|
||||
|
||||
export interface Setting {
|
||||
key: string;
|
||||
@@ -93,3 +95,94 @@ export const settingsRepository = {
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// --- Specific Setting Getters/Setters ---
|
||||
|
||||
/**
|
||||
* 获取侧栏配置
|
||||
* @returns Promise<SidebarConfig> - Returns the parsed config or default
|
||||
*/
|
||||
export const getSidebarConfig = async (): Promise<SidebarConfig> => {
|
||||
const defaultValue: SidebarConfig = { left: [], right: [] };
|
||||
try {
|
||||
const jsonString = await settingsRepository.getSetting(SIDEBAR_CONFIG_KEY);
|
||||
if (jsonString) {
|
||||
try {
|
||||
const config = JSON.parse(jsonString);
|
||||
// Basic validation
|
||||
if (config && Array.isArray(config.left) && Array.isArray(config.right)) {
|
||||
// TODO: Add deeper validation if needed (e.g., check if items are valid PaneName)
|
||||
return config as SidebarConfig;
|
||||
}
|
||||
console.warn(`[SettingsRepo] Invalid sidebarConfig format found in DB: ${jsonString}. Returning default.`);
|
||||
} catch (parseError) {
|
||||
console.error(`[SettingsRepo] Failed to parse sidebarConfig JSON from DB: ${jsonString}`, parseError);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[SettingsRepo] Error fetching sidebar config setting (key: ${SIDEBAR_CONFIG_KEY}):`, error);
|
||||
}
|
||||
// Return default if not found, invalid, or error occurred
|
||||
return defaultValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置侧栏配置
|
||||
* @param config - The sidebar configuration object
|
||||
*/
|
||||
export const setSidebarConfig = async (config: SidebarConfig): Promise<void> => {
|
||||
try {
|
||||
// Basic validation before stringifying
|
||||
if (!config || typeof config !== 'object' || !Array.isArray(config.left) || !Array.isArray(config.right)) {
|
||||
throw new Error('Invalid sidebar config object provided.');
|
||||
}
|
||||
// TODO: Add deeper validation if needed (e.g., check PaneName validity)
|
||||
const jsonString = JSON.stringify(config);
|
||||
await settingsRepository.setSetting(SIDEBAR_CONFIG_KEY, jsonString);
|
||||
} catch (error) {
|
||||
console.error(`[SettingsRepo] Error setting sidebar config (key: ${SIDEBAR_CONFIG_KEY}):`, error);
|
||||
throw new Error('Failed to save sidebar configuration.');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// --- Initialization ---
|
||||
|
||||
/**
|
||||
* Ensures default settings exist in the settings table.
|
||||
* This function should be called during database initialization.
|
||||
* @param db - The active database instance
|
||||
*/
|
||||
export const ensureDefaultSettingsExist = async (db: sqlite3.Database): Promise<void> => {
|
||||
const defaultSettings: Record<string, string> = {
|
||||
language: 'en', // Default language
|
||||
ipWhitelistEnabled: 'false',
|
||||
ipWhitelist: '',
|
||||
maxLoginAttempts: '5',
|
||||
loginBanDuration: '300', // 5 minutes in seconds
|
||||
focusSwitcherSequence: JSON.stringify(["quickCommandsSearch", "commandHistorySearch", "fileManagerSearch", "commandInput", "terminalSearch"]), // Default focus sequence
|
||||
navBarVisible: 'true', // Default nav bar visibility
|
||||
layoutTree: 'null', // Default layout tree (null initially)
|
||||
autoCopyOnSelect: 'false', // Default auto copy setting
|
||||
showPopupFileEditor: 'false', // Default popup editor setting
|
||||
shareFileEditorTabs: 'true', // Default editor tab sharing
|
||||
dockerStatusIntervalSeconds: '5', // Default Docker refresh interval
|
||||
dockerDefaultExpand: 'false', // Default Docker expand state
|
||||
statusMonitorIntervalSeconds: '3', // Default Status Monitor interval
|
||||
[SIDEBAR_CONFIG_KEY]: JSON.stringify({ left: [], right: [] }), // Default sidebar config
|
||||
// Add other default settings here
|
||||
};
|
||||
const nowSeconds = Math.floor(Date.now() / 1000);
|
||||
const sqlInsertOrIgnore = `INSERT OR IGNORE INTO settings (key, value, created_at, updated_at) VALUES (?, ?, ?, ?)`;
|
||||
|
||||
console.log('[SettingsRepo] Ensuring default settings exist...');
|
||||
try {
|
||||
for (const [key, value] of Object.entries(defaultSettings)) {
|
||||
await runDb(db, sqlInsertOrIgnore, [key, value, nowSeconds, nowSeconds]);
|
||||
}
|
||||
console.log('[SettingsRepo] Default settings check complete.');
|
||||
} catch (err: any) {
|
||||
console.error(`[SettingsRepo] Error ensuring default settings:`, err.message);
|
||||
throw new Error(`Failed to ensure default settings: ${err.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { settingsRepository, Setting } from '../repositories/settings.repository';
|
||||
import { settingsRepository, Setting, getSidebarConfig as getSidebarConfigFromRepo, setSidebarConfig as setSidebarConfigInRepo } from '../repositories/settings.repository'; // Import specific repo functions
|
||||
import { SidebarConfig, PaneName, UpdateSidebarConfigDto } from '../types/settings.types'; // <-- Correct import path
|
||||
|
||||
// +++ 定义默认的焦点切换顺序 +++
|
||||
const DEFAULT_FOCUS_SEQUENCE = ["quickCommandsSearch", "commandHistorySearch", "fileManagerSearch", "commandInput", "terminalSearch"];
|
||||
@@ -291,5 +292,68 @@ export const settingsService = {
|
||||
console.error(`[Service] Error calling settingsRepository.setSetting for key ${STATUS_MONITOR_INTERVAL_SECONDS_KEY}:`, error);
|
||||
throw new Error('Failed to save status monitor interval setting.');
|
||||
}
|
||||
} // *** 最后的方法后面不需要逗号 ***
|
||||
};
|
||||
}, // *** 确保这里有逗号 ***
|
||||
|
||||
// --- Sidebar Config Specific Functions ---
|
||||
|
||||
/**
|
||||
* 获取侧栏配置
|
||||
* @returns Promise<SidebarConfig>
|
||||
*/
|
||||
async getSidebarConfig(): Promise<SidebarConfig> {
|
||||
console.log('[SettingsService] Getting sidebar config...');
|
||||
// Directly call the specific repository function
|
||||
const config = await getSidebarConfigFromRepo();
|
||||
console.log('[SettingsService] Returning sidebar config:', config);
|
||||
return config;
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置侧栏配置
|
||||
* @param configDto - The sidebar configuration object from DTO
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
async setSidebarConfig(configDto: UpdateSidebarConfigDto): Promise<void> {
|
||||
console.log('[SettingsService] Setting sidebar config:', configDto);
|
||||
|
||||
// --- Validation ---
|
||||
if (!configDto || typeof configDto !== 'object' || !Array.isArray(configDto.left) || !Array.isArray(configDto.right)) {
|
||||
throw new Error('无效的侧栏配置格式。必须包含 left 和 right 数组。');
|
||||
}
|
||||
|
||||
// Validate PaneName (using the type imported)
|
||||
const validPaneNames: Set<PaneName> = new Set([
|
||||
'connections', 'terminal', 'commandBar', 'fileManager',
|
||||
'editor', 'statusMonitor', 'commandHistory', 'quickCommands',
|
||||
'dockerManager'
|
||||
]);
|
||||
|
||||
const validatePaneArray = (arr: any[], side: string) => {
|
||||
if (!arr.every(item => typeof item === 'string' && validPaneNames.has(item as PaneName))) {
|
||||
const invalidItems = arr.filter(item => typeof item !== 'string' || !validPaneNames.has(item as PaneName));
|
||||
throw new Error(`侧栏配置 (${side}) 包含无效的面板名称: ${invalidItems.join(', ')}`);
|
||||
}
|
||||
};
|
||||
|
||||
validatePaneArray(configDto.left, 'left');
|
||||
validatePaneArray(configDto.right, 'right');
|
||||
|
||||
// Prevent duplicates (optional, uncomment if needed)
|
||||
// const allPanes = [...configDto.left, ...configDto.right];
|
||||
// const uniquePanes = new Set(allPanes);
|
||||
// if (allPanes.length !== uniquePanes.size) {
|
||||
// throw new Error('侧栏配置中不允许包含重复的面板。');
|
||||
// }
|
||||
|
||||
// Prepare the data in the exact SidebarConfig format expected by the repo
|
||||
const configToSave: SidebarConfig = {
|
||||
left: configDto.left,
|
||||
right: configDto.right,
|
||||
};
|
||||
|
||||
// Directly call the specific repository function
|
||||
await setSidebarConfigInRepo(configToSave);
|
||||
console.log('[SettingsService] Sidebar config successfully set.');
|
||||
} // <-- No comma after the last method in the object
|
||||
|
||||
}; // <-- End of settingsService object definition
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { settingsService } from '../services/settings.service';
|
||||
import { AuditLogService } from '../services/audit.service'; // 引入 AuditLogService
|
||||
import { ipBlacklistService } from '../services/ip-blacklist.service'; // 引入 IP 黑名单服务
|
||||
import { ipBlacklistService } from '../services/ip-blacklist.service';
|
||||
import { UpdateSidebarConfigDto } from '../types/settings.types'; // <-- Correct import path
|
||||
|
||||
const auditLogService = new AuditLogService(); // 实例化 AuditLogService
|
||||
const auditLogService = new AuditLogService();
|
||||
|
||||
export const settingsController = {
|
||||
/**
|
||||
@@ -291,5 +292,59 @@ export const settingsController = {
|
||||
console.error('[Controller] 设置终端选中自动复制时出错:', error);
|
||||
res.status(500).json({ message: '设置终端选中自动复制失败', error: error.message });
|
||||
}
|
||||
} // *** 最后的方法后面不需要逗号 ***
|
||||
};
|
||||
}, // *** 确保这里有逗号 ***
|
||||
|
||||
// --- Sidebar Config Controller Methods ---
|
||||
|
||||
/**
|
||||
* 获取侧栏配置
|
||||
*/
|
||||
async getSidebarConfig(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
console.log('[Controller] Received request to get sidebar config.');
|
||||
const config = await settingsService.getSidebarConfig();
|
||||
console.log('[Controller] Sending sidebar config to client:', config);
|
||||
res.json(config);
|
||||
} catch (error: any) {
|
||||
console.error('[Controller] 获取侧栏配置时出错:', error);
|
||||
res.status(500).json({ message: '获取侧栏配置失败', error: error.message });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置侧栏配置
|
||||
*/
|
||||
async setSidebarConfig(req: Request, res: Response): Promise<void> {
|
||||
console.log('[Controller] Received request to set sidebar config.');
|
||||
try {
|
||||
const configDto: UpdateSidebarConfigDto = req.body;
|
||||
console.log('[Controller] Request body:', configDto);
|
||||
|
||||
// --- DTO Validation (Basic) ---
|
||||
// More specific validation happens in the service layer
|
||||
if (!configDto || typeof configDto !== 'object' || !Array.isArray(configDto.left) || !Array.isArray(configDto.right)) {
|
||||
console.warn('[Controller] Invalid sidebar config format received:', configDto);
|
||||
res.status(400).json({ message: '无效的请求体,应为包含 left 和 right 数组的 JSON 对象' });
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[Controller] Calling settingsService.setSidebarConfig...');
|
||||
await settingsService.setSidebarConfig(configDto);
|
||||
console.log('[Controller] settingsService.setSidebarConfig completed successfully.');
|
||||
|
||||
// auditLogService.logAction('SIDEBAR_CONFIG_UPDATED'); // Optional: Add audit log
|
||||
|
||||
console.log('[Controller] Sending success response.');
|
||||
res.status(200).json({ message: '侧栏配置已成功更新' });
|
||||
} catch (error: any) {
|
||||
console.error('[Controller] 设置侧栏配置时出错:', error);
|
||||
// Handle specific validation errors from the service
|
||||
if (error.message.includes('无效的面板名称') || error.message.includes('无效的侧栏配置格式')) {
|
||||
res.status(400).json({ message: `设置侧栏配置失败: ${error.message}` });
|
||||
} else {
|
||||
res.status(500).json({ message: '设置侧栏配置失败', error: error.message });
|
||||
}
|
||||
}
|
||||
} // <-- No comma after the last method
|
||||
|
||||
}; // <-- End of settingsController object
|
||||
|
||||
@@ -43,4 +43,11 @@ router.get('/auto-copy-on-select', settingsController.getAutoCopyOnSelect);
|
||||
// PUT /api/v1/settings/auto-copy-on-select - 更新设置
|
||||
router.put('/auto-copy-on-select', settingsController.setAutoCopyOnSelect);
|
||||
|
||||
// +++ 新增:侧栏配置路由 +++
|
||||
// GET /api/v1/settings/sidebar - 获取侧栏配置
|
||||
router.get('/sidebar', settingsController.getSidebarConfig);
|
||||
// PUT /api/v1/settings/sidebar - 更新侧栏配置
|
||||
router.put('/sidebar', settingsController.setSidebarConfig);
|
||||
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { ITheme } from 'xterm';
|
||||
|
||||
// 定义所有可用面板的名称 (后端独立定义)
|
||||
export type PaneName = 'connections' | 'terminal' | 'commandBar' | 'fileManager' | 'editor' | 'statusMonitor' | 'commandHistory' | 'quickCommands' | 'dockerManager';
|
||||
|
||||
/**
|
||||
* 外观设置数据结构
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// packages/backend/src/types/settings.types.ts
|
||||
|
||||
// Define PaneName here as it's logically related to layout/sidebar settings
|
||||
export type PaneName = 'connections' | 'terminal' | 'commandBar' | 'fileManager' | 'editor' | 'statusMonitor' | 'commandHistory' | 'quickCommands' | 'dockerManager';
|
||||
|
||||
/**
|
||||
* 侧栏配置数据结构 (Managed by Settings Repository/Service)
|
||||
*/
|
||||
export interface SidebarConfig {
|
||||
left: PaneName[];
|
||||
right: PaneName[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于更新侧栏配置的 DTO
|
||||
*/
|
||||
export interface UpdateSidebarConfigDto extends SidebarConfig {} // Simple alias for now, can add validation later
|
||||
|
||||
// You can add other settings-related types here if needed
|
||||
Reference in New Issue
Block a user