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; pages_max_package_size_mb: string; pages_max_history_count: string; }; export const defaultOpenFlareOpsFields: OpenFlareOpsFields = { agent_heartbeat_interval: '3000', agent_websocket_upgrade_enabled: true, node_offline_threshold: '60000', 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', pages_max_package_size_mb: '100', pages_max_history_count: '20', }; 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 ?? '3000', agent_websocket_upgrade_enabled: toBoolean( optionMap.agent_websocket_upgrade_enabled, true, ), node_offline_threshold: optionMap.node_offline_threshold ?? '60000', 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', pages_max_package_size_mb: optionMap.pages_max_package_size_mb ?? '100', pages_max_history_count: optionMap.pages_max_history_count ?? '20', }; } export type OpsMessage = ( key: string, values?: Record, ) => string; export function formatDurationLabel(value: string, t: OpsMessage) { const milliseconds = Number.parseInt(value, 10); if (Number.isNaN(milliseconds)) return value; if (milliseconds >= 60000) { return t('duration.minutes', { count: milliseconds / 60000 }); } return t('duration.seconds', { count: 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, t: OpsMessage) { const heartbeat = Number.parseInt(fields.agent_heartbeat_interval, 10); const offline = Number.parseInt(fields.node_offline_threshold, 10); if (Number.isNaN(heartbeat) || heartbeat < 1000) { throw new Error(t('errors.heartbeatMin')); } if (Number.isNaN(offline) || offline < 10000) { throw new Error(t('errors.offlineMin')); } if (offline < heartbeat * 3) { throw new Error(t('errors.offlineRatio')); } } export function validateUptimeKumaFields( fields: OpenFlareOpsFields, t: OpsMessage, ) { 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(t('errors.kumaUrl')); if (!fields.uptime_kuma_username.trim()) throw new Error(t('errors.kumaUser')); } if (Number.isNaN(syncInt) || syncInt <= 0) throw new Error(t('errors.syncInterval')); if (Number.isNaN(interval) || interval <= 0) throw new Error(t('errors.interval')); if (Number.isNaN(retry) || retry < 0) throw new Error(t('errors.retry')); if (Number.isNaN(retryInt) || retryInt <= 0) throw new Error(t('errors.retryInterval')); if (Number.isNaN(timeout) || timeout <= 0) throw new Error(t('errors.timeout')); } export function agentOptionEntries( fields: OpenFlareOpsFields, t: OpsMessage, ): OptionItem[] { validateAgentFields(fields, t); 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, t: OpsMessage, ): OptionItem[] { validateUptimeKumaFields(fields, t); 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 validatePagesFields(fields: OpenFlareOpsFields, t: OpsMessage) { const packageSize = Number.parseInt(fields.pages_max_package_size_mb, 10); const historyCount = Number.parseInt(fields.pages_max_history_count, 10); if (Number.isNaN(packageSize) || packageSize < 1 || packageSize > 2048) { throw new Error(t('errors.pagesSize')); } if (Number.isNaN(historyCount) || historyCount < 0) { throw new Error(t('errors.pagesHistory')); } } export function pagesOptionEntries( fields: OpenFlareOpsFields, t: OpsMessage, ): OptionItem[] { validatePagesFields(fields, t); return [ { key: 'pages_max_package_size_mb', value: String(Number.parseInt(fields.pages_max_package_size_mb, 10)), }, { key: 'pages_max_history_count', value: String(Number.parseInt(fields.pages_max_history_count, 10)), }, ]; }