Files
OpenFlare/frontend/app/(main)/admin/settings/components/openflare-ops-utils.ts
T
ryan 5a8722ff07 feat(frontend): 控制台 next-intl 中英双语
接入无 URL 前缀的 zh-CN/en,顶栏与外观设置可切换语言;选择写入 cookie 后刷新生效。
2026-08-16 16:49:16 +08:00

242 lines
8.2 KiB
TypeScript

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<Record<string, string>>((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<string, string>,
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, string | number>,
) => 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)),
},
];
}