import type { OptionItem } from '@/lib/services/openflare'; export type OpenFlareOpsFields = { agent_heartbeat_interval: string; agent_websocket_upgrade_enabled: boolean; node_offline_threshold: string; agent_update_repo: string; geoip_provider: string; server_address: string; uptime_kuma_enabled: boolean; uptime_kuma_url: string; uptime_kuma_username: string; uptime_kuma_password: string; uptime_kuma_monitor_scope: string; uptime_kuma_selected_sites: string; uptime_kuma_sync_interval: string; uptime_kuma_interval: string; uptime_kuma_retry: string; uptime_kuma_retry_interval: string; uptime_kuma_timeout: string; database_auto_cleanup_enabled: boolean; database_auto_cleanup_retention_days: string; }; export const defaultOpenFlareOpsFields: OpenFlareOpsFields = { agent_heartbeat_interval: '10000', agent_websocket_upgrade_enabled: true, node_offline_threshold: '120000', agent_update_repo: 'Rain-kl/OpenFlare', geoip_provider: 'ipinfo', server_address: '', uptime_kuma_enabled: false, uptime_kuma_url: '', uptime_kuma_username: '', uptime_kuma_password: '', uptime_kuma_monitor_scope: 'all', uptime_kuma_selected_sites: '', uptime_kuma_sync_interval: '5', uptime_kuma_interval: '60', uptime_kuma_retry: '0', uptime_kuma_retry_interval: '60', uptime_kuma_timeout: '48', database_auto_cleanup_enabled: false, database_auto_cleanup_retention_days: '30', }; export const INSTALLER_SCRIPT_URL = 'https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh'; export function optionsToMap(options: OptionItem[]) { return options.reduce>((accumulator, option) => { accumulator[option.key] = option.value; return accumulator; }, {}); } function toBoolean(value: string | undefined, fallback: boolean) { if (value === undefined) return fallback; return value === 'true'; } export function mapOptionsToOpsFields( optionMap: Record, serverAddress = '', ): OpenFlareOpsFields { return { agent_heartbeat_interval: optionMap.agent_heartbeat_interval ?? '10000', agent_websocket_upgrade_enabled: toBoolean( optionMap.agent_websocket_upgrade_enabled, true, ), node_offline_threshold: optionMap.node_offline_threshold ?? '120000', agent_update_repo: optionMap.agent_update_repo ?? 'Rain-kl/OpenFlare', geoip_provider: optionMap.geoip_provider ?? 'ipinfo', server_address: optionMap.server_address || serverAddress, uptime_kuma_enabled: toBoolean(optionMap.uptime_kuma_enabled, false), uptime_kuma_url: optionMap.uptime_kuma_url ?? '', uptime_kuma_username: optionMap.uptime_kuma_username ?? '', uptime_kuma_password: '', uptime_kuma_monitor_scope: optionMap.uptime_kuma_monitor_scope ?? 'all', uptime_kuma_selected_sites: optionMap.uptime_kuma_selected_sites ?? '', uptime_kuma_sync_interval: optionMap.uptime_kuma_sync_interval ?? '5', uptime_kuma_interval: optionMap.uptime_kuma_interval ?? '60', uptime_kuma_retry: optionMap.uptime_kuma_retry ?? '0', uptime_kuma_retry_interval: optionMap.uptime_kuma_retry_interval ?? '60', uptime_kuma_timeout: optionMap.uptime_kuma_timeout ?? '48', database_auto_cleanup_enabled: toBoolean( optionMap.database_auto_cleanup_enabled, false, ), database_auto_cleanup_retention_days: optionMap.database_auto_cleanup_retention_days ?? '30', }; } export function formatDurationLabel(value: string) { const milliseconds = Number.parseInt(value, 10); if (Number.isNaN(milliseconds)) return value; if (milliseconds >= 60000) return `${milliseconds / 60000} 分钟`; return `${milliseconds / 1000} 秒`; } export function normalizeServerUrl(value: string) { return value.trim().replace(/\/+$/, ''); } export function getBrowserOrigin() { if (typeof window === 'undefined') return ''; return normalizeServerUrl(window.location.origin); } export function buildDiscoveryCommand( serverUrl: string, discoveryToken: string, ) { return [ `curl -fsSL ${INSTALLER_SCRIPT_URL} | bash -s -- \\`, ` --server-url ${normalizeServerUrl(serverUrl)} \\`, ` --discovery-token ${discoveryToken}`, ].join('\n'); } export function validateAgentFields(fields: OpenFlareOpsFields) { const heartbeat = Number.parseInt(fields.agent_heartbeat_interval, 10); const offline = Number.parseInt(fields.node_offline_threshold, 10); if (Number.isNaN(heartbeat) || heartbeat < 5000) { throw new Error('心跳间隔不能小于 5000 毫秒。'); } if (Number.isNaN(offline) || offline < 10000) { throw new Error('离线阈值不能小于 10000 毫秒。'); } } export function validateUptimeKumaFields(fields: OpenFlareOpsFields) { const syncInt = Number.parseInt(fields.uptime_kuma_sync_interval, 10); const interval = Number.parseInt(fields.uptime_kuma_interval, 10); const retry = Number.parseInt(fields.uptime_kuma_retry, 10); const retryInt = Number.parseInt(fields.uptime_kuma_retry_interval, 10); const timeout = Number.parseInt(fields.uptime_kuma_timeout, 10); if (fields.uptime_kuma_enabled) { if (!fields.uptime_kuma_url.trim()) throw new Error('请输入 Uptime Kuma 地址。'); if (!fields.uptime_kuma_username.trim()) throw new Error('请输入 Uptime Kuma 用户名。'); } if (Number.isNaN(syncInt) || syncInt <= 0) throw new Error('同步间隔必须为正整数。'); if (Number.isNaN(interval) || interval <= 0) throw new Error('心跳间隔必须为正整数。'); if (Number.isNaN(retry) || retry < 0) throw new Error('重试次数必须为非负整数。'); if (Number.isNaN(retryInt) || retryInt <= 0) throw new Error('心跳重试间隔必须为正整数。'); if (Number.isNaN(timeout) || timeout <= 0) throw new Error('请求超时必须为正整数。'); } export function validateDatabaseAutoCleanup(fields: OpenFlareOpsFields) { const retentionDays = Number.parseInt( fields.database_auto_cleanup_retention_days, 10, ); if (Number.isNaN(retentionDays) || retentionDays < 1) { throw new Error('自动清理保留天数至少为 1 天。'); } } export function agentOptionEntries(fields: OpenFlareOpsFields): OptionItem[] { validateAgentFields(fields); return [ { key: 'agent_heartbeat_interval', value: fields.agent_heartbeat_interval }, { key: 'agent_websocket_upgrade_enabled', value: String(fields.agent_websocket_upgrade_enabled), }, { key: 'node_offline_threshold', value: fields.node_offline_threshold }, { key: 'agent_update_repo', value: fields.agent_update_repo.trim() }, { key: 'geoip_provider', value: fields.geoip_provider }, ]; } export function uptimeKumaOptionEntries( fields: OpenFlareOpsFields, ): OptionItem[] { validateUptimeKumaFields(fields); return [ { key: 'uptime_kuma_enabled', value: String(fields.uptime_kuma_enabled) }, { key: 'uptime_kuma_url', value: fields.uptime_kuma_url.trim() }, { key: 'uptime_kuma_username', value: fields.uptime_kuma_username.trim() }, { key: 'uptime_kuma_password', value: fields.uptime_kuma_password }, { key: 'uptime_kuma_monitor_scope', value: fields.uptime_kuma_monitor_scope, }, { key: 'uptime_kuma_selected_sites', value: fields.uptime_kuma_selected_sites, }, { key: 'uptime_kuma_sync_interval', value: fields.uptime_kuma_sync_interval, }, { key: 'uptime_kuma_interval', value: fields.uptime_kuma_interval }, { key: 'uptime_kuma_retry', value: fields.uptime_kuma_retry }, { key: 'uptime_kuma_retry_interval', value: fields.uptime_kuma_retry_interval, }, { key: 'uptime_kuma_timeout', value: fields.uptime_kuma_timeout }, ]; } export function databaseAutoCleanupEntries( fields: OpenFlareOpsFields, ): OptionItem[] { validateDatabaseAutoCleanup(fields); return [ { key: 'database_auto_cleanup_enabled', value: String(fields.database_auto_cleanup_enabled), }, { key: 'database_auto_cleanup_retention_days', value: fields.database_auto_cleanup_retention_days, }, ]; }