123 lines
5.7 KiB
TypeScript
123 lines
5.7 KiB
TypeScript
// src/lib/monitor/settings-manager.ts
|
|
import { getDb } from '@/lib/db'
|
|
import crypto from 'crypto'
|
|
import type { MonitorConfig } from './types'
|
|
|
|
const ALGORITHM = 'aes-256-cbc'
|
|
|
|
function getEncryptionKey(): string {
|
|
const db = getDb()
|
|
const row = db.prepare("SELECT value FROM settings WHERE key = 'monitor.encryption_key'").get() as { value: string } | undefined
|
|
if (row) return row.value
|
|
// 首次启动:生成密钥
|
|
const newKey = crypto.randomBytes(32).toString('hex')
|
|
db.prepare("INSERT OR IGNORE INTO settings (key, value, category) VALUES ('monitor.encryption_key', ?, 'security')").run(newKey)
|
|
return newKey
|
|
}
|
|
|
|
export function encryptValue(plaintext: string): string {
|
|
if (!plaintext) return ''
|
|
const key = Buffer.from(getEncryptionKey(), 'hex')
|
|
const iv = crypto.randomBytes(16)
|
|
const cipher = crypto.createCipheriv(ALGORITHM, key, iv)
|
|
let encrypted = cipher.update(plaintext, 'utf8', 'hex')
|
|
encrypted += cipher.final('hex')
|
|
return iv.toString('hex') + ':' + encrypted
|
|
}
|
|
|
|
export function decryptValue(ciphertext: string): string {
|
|
if (!ciphertext) return ''
|
|
try {
|
|
const key = Buffer.from(getEncryptionKey(), 'hex')
|
|
const [ivHex, encrypted] = ciphertext.split(':')
|
|
const iv = Buffer.from(ivHex, 'hex')
|
|
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv)
|
|
let decrypted = decipher.update(encrypted, 'hex', 'utf8')
|
|
decrypted += decipher.final('utf8')
|
|
return decrypted
|
|
} catch {
|
|
console.error('[Settings] 密码解密失败,可能密钥不匹配')
|
|
return ''
|
|
}
|
|
}
|
|
|
|
export function getSetting(key: string): string | null {
|
|
const db = getDb()
|
|
const row = db.prepare('SELECT value FROM settings WHERE key = ?').get(key) as { value: string } | undefined
|
|
return row?.value ?? null
|
|
}
|
|
|
|
export function setSetting(key: string, value: string, category = 'general'): void {
|
|
const db = getDb()
|
|
db.prepare(
|
|
"INSERT INTO settings (key, value, category, updated_at) VALUES (?, ?, ?, datetime('now', '+8 hours')) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at"
|
|
).run(key, value, category)
|
|
}
|
|
|
|
const DEFAULT_CONFIG: MonitorConfig = {
|
|
monitor: { enabled: false, interval_seconds: 60, push_delay_ms: 2000, silent_start_hour: 0, silent_end_hour: 7 },
|
|
mail: { address: 'gxp@qx002575.com', imap_server: 'imaphz.qiye.163.com', imap_port: 993, smtp_server: 'smtphz.qiye.163.com', smtp_port: 465, password: '' },
|
|
filter: {
|
|
subject_keywords: ['服务器故障单'],
|
|
sender_email: 'tencent_dcops@tencent.com',
|
|
oem_repair_keywords: { base_keyword: '服务器故障单', type_keyword: 'OEM维修', exclude_keyword: 'OEM诊断' },
|
|
oem_diag_keywords: { base_keyword: '服务器故障单', type_keyword: 'OEM诊断', exclude_keyword: 'OEM维修' },
|
|
},
|
|
wechat: { webhook_url: '' },
|
|
}
|
|
|
|
export function getMonitorConfig(): MonitorConfig {
|
|
const config = JSON.parse(JSON.stringify(DEFAULT_CONFIG))
|
|
const mappings: Record<string, (v: string) => void> = {
|
|
'monitor.enabled': (v) => { config.monitor.enabled = v === 'true' },
|
|
'monitor.interval_seconds': (v) => { config.monitor.interval_seconds = parseInt(v) || 60 },
|
|
'monitor.push_delay_ms': (v) => { config.monitor.push_delay_ms = parseInt(v) || 2000 },
|
|
'monitor.silent_start_hour': (v) => { config.monitor.silent_start_hour = parseInt(v) || 0 },
|
|
'monitor.silent_end_hour': (v) => { config.monitor.silent_end_hour = parseInt(v) || 7 },
|
|
'mail.address': (v) => { config.mail.address = v },
|
|
'mail.imap_server': (v) => { config.mail.imap_server = v },
|
|
'mail.imap_port': (v) => { config.mail.imap_port = parseInt(v) || 993 },
|
|
'mail.smtp_server': (v) => { config.mail.smtp_server = v },
|
|
'mail.smtp_port': (v) => { config.mail.smtp_port = parseInt(v) || 465 },
|
|
'mail.password': (v) => { config.mail.password = v ? decryptValue(v) : '' },
|
|
'filter.subject_keywords': (v) => { try { config.filter.subject_keywords = JSON.parse(v) } catch {} },
|
|
'filter.sender_email': (v) => { config.filter.sender_email = v },
|
|
'filter.oem_repair_keywords': (v) => { try { config.filter.oem_repair_keywords = JSON.parse(v) } catch {} },
|
|
'filter.oem_diag_keywords': (v) => { try { config.filter.oem_diag_keywords = JSON.parse(v) } catch {} },
|
|
'wechat.webhook_url': (v) => { config.wechat.webhook_url = v },
|
|
}
|
|
const db = getDb()
|
|
const rows = db.prepare("SELECT key, value FROM settings WHERE key LIKE 'monitor.%' OR key LIKE 'mail.%' OR key LIKE 'filter.%' OR key LIKE 'wechat.%'").all() as { key: string; value: string }[]
|
|
for (const row of rows) {
|
|
mappings[row.key]?.(row.value)
|
|
}
|
|
return config
|
|
}
|
|
|
|
export function updateMonitorConfig(updates: Record<string, unknown>): void {
|
|
const encryptedFields = new Set(['mail.password'])
|
|
for (const [key, value] of Object.entries(updates)) {
|
|
if (key === 'monitor.encryption_key') continue // 不允许修改密钥
|
|
let stringValue: string
|
|
if (typeof value === 'boolean') stringValue = String(value)
|
|
else if (typeof value === 'number') stringValue = String(value)
|
|
else if (typeof value === 'object') stringValue = JSON.stringify(value)
|
|
else stringValue = String(value ?? '')
|
|
if (encryptedFields.has(key) && stringValue && !stringValue.match(/^[0-9a-f]{32}:/)) {
|
|
stringValue = encryptValue(stringValue)
|
|
}
|
|
const category = key.split('.')[0]
|
|
setSetting(key, stringValue, category)
|
|
}
|
|
}
|
|
|
|
export function getMaskedConfig(): MonitorConfig {
|
|
const config = getMonitorConfig()
|
|
if (config.mail.password) config.mail.password = '••••••••'
|
|
if (config.wechat.webhook_url) {
|
|
const keyMatch = config.wechat.webhook_url.match(/key=([0-9a-f-]+)/)
|
|
if (keyMatch) config.wechat.webhook_url = config.wechat.webhook_url.replace(keyMatch[1], '••••••••')
|
|
}
|
|
return config
|
|
}
|