CF-Server-Monitor
Host a server-metrics dashboard and alerting control plane on Cloudflare.
CF-Server-Monitor is a self-hosted Netdata Cloud alternative built on Cloudflare (D1, Durable Objects, Workers). Free tier eligible within limits. Inspect the source and license in the linked repository.
Source & license
Upstream license: MIT
License TL;DR
You can use it, change it, self-host it and sell it. Keep the original copyright and license notice with copies of the code. You don’t have to publish your changes. The authors don’t promise it will work.
Explain MIT in plain English →Summary of the main license. Separate packages and assets can have different terms.
Inspect repository ↗Read this project’s actual license ↗Repository owner
See the upstream repository for the original creator and contributors.
Maintain this project? Maintainer verification →Cloudflare hosting
Free tier eligible within limits
The reviewed Cloudflare deployment is eligible for Free-plan allowances for the stated small workload and feature scope. Usage limits, CPU, required account setup and separate services apply.
Hosting requirements
- This assessment covers only the Cloudflare dashboard/control plane; existing monitored hosts and their agents are required and billed separately.
- Use SQLite-backed MetricsBroadcaster as declared; persistent WebSocket duration and agent reporting frequency must stay within Durable Object allowances.
- Reduce reporting frequency and retention as needed to stay within Worker and D1 quotas; no verified server-count guarantee.
- Workers Free dynamic requests are shared across this account (100,000/day), with 10 ms CPU per invocation; workload fit is conditional and has not been measured.
- D1 Free allowance: 5 million rows read/day, 100,000 rows written/day and 5 GB total storage; unindexed scans and history retention consume quota.
- Only SQLite Durable Objects qualify for Workers Free. Keep DO requests below 100,000/day, active duration below 13,000 GB-s/day and SQLite storage/operations inside the captured allowances.
Sources checked 01/10/2026
Repository snapshot: dfb9bf1. Hosting eligibility reflects the deployment documentation and listed assumptions.
- netdata-cloud ↗
· [主题开发](theme-develop.md) </div> ## 项目简介 CF-Server-Monitor 是一个部署在 Cloudflare Workers 上的服务器监控系统。服务器端安装 Agent 后会单向上报指标到 Worker,数据写入 D1,并通过 Durable Objects + WebSocket 推送到前端,实现免费托管、低维护的实时监控。 支持主流 Linux 发行版、Alpine Linux、OpenWrt、macOS、群晖 DSM、飞牛 fnOS、Windows 等系统,并提供 Docker 镜像部署方式。 高安全性:Agent 仅单向上报指标,不提供 WebSSH、远程命令下发或主控通道;支持非 root 运行,可降低监控组件被利用后的影响范围。 ## 目录 - [对比优势](#对比优势) - [特性](#特性) - [系统架构](#系统架构) - [版本说明](#版本说明) - [快速部署](#快速部署) - [首次使用](#首次使用) - [Agent 参数与安全建议](#agent-参数与安全建议) - [配置说明](#配置说明) - [通知与告警](#通知与告警) - [安全建议](#安全建议) - [主题与外观](#主题与外观) - [升级与维护](#升级
- workers ↗
# Cloudflare Workers 配置,线上已由deploy.yml接管,这里仅供本地测试 name = "cf-server-monitor" main = "src/index.js" compatibility_date = "2024-12-01" compatibility_flags = ["nodejs_compat"] keep_vars = true # 定时任务配置(UTC) # - 每分钟:执行离线节点、资源告警 # - 每小时:合并执行其他定时任务(表轮换、旧表清理、按配置时区/小时触发服务器到期检测) # - 注意同步修改deploy和index.js scheduled函数里面的值 [triggers] crons = ["*/1 * * * *", "0 * * * *"] [assets] directory = "./dist" binding = "ASSETS" [[d1_databases]] binding = "DB" database_name = "server-monitor-db" # Durable Objects:实时指标广播中心 # 注意:首次部署前需要执行 `wrangler durable-obje
- d1 ↗
* * *", "0 * * * *"] [assets] directory = "./dist" binding = "ASSETS" [[d1_databases]] binding = "DB" database_name = "server-monitor-db" # Durable Objects:实时指标广播中心 # 注意:首次部署前需要执行 `wrangler durable-object create MetricsBroadcaster --class MetricsBroadcaster` # 或者在部署后,wrangler 会自动创建对应的 DO namespace。 [[durable_objects.bindings]] name = "METRICS_BROADCASTER" class_name = "MetricsBroadcaster" [[migrations]] tag = "v1" new_sqlite_classes = ["MetricsBroadcaster"] # 开发服务器配置 [dev] port = 8787 local_protocol = "https"
- durable-objects ↗
s MetricsBroadcaster` # 或者在部署后,wrangler 会自动创建对应的 DO namespace。 [[durable_objects.bindings]] name = "METRICS_BROADCASTER" class_name = "MetricsBroadcaster" [[migrations]] tag = "v1" new_sqlite_classes = ["MetricsBroadcaster"] # 开发服务器配置 [dev] port = 8787 local_protocol = "https"
- free-tier-eligible ↗
# Cloudflare Workers 配置,线上已由deploy.yml接管,这里仅供本地测试 name = "cf-server-monitor" main = "src/index.js" compatibility_date = "2024-12-01" compatibility_flags = ["nodejs_compat"] keep_vars = true # 定时任务配置(UTC) # - 每分钟:执行离线节点、资源告警 # - 每小时:合并执行其他定时任务(表轮换、旧表清理、按配置时区/小时触发服务器到期检测) # - 注意同步修改deploy和index.js scheduled函数里面的值 [triggers] crons = ["*/1 * * * *", "0 * * * *"] [assets] directory = "./dist" binding = "ASSETS" [[d1_databases]] binding = "DB" database_name = "server-monitor-db" # Durable Objects:实时指标广播中心 # 注意:首次部署前
- free-tier-eligible ↗
ount Manager. | | Requests<sup>1, 2, 3, 4</sup> | Duration | CPU time | | --- | --- | --- | --- | | **Free** | 100,000 per day | No charge for duration | 10 milliseconds of CPU time per invocation | | **Standard** | 10 million included per month <br> +$0.30 per additional million | No charge or limit for duration | 30 million CPU milliseconds included per month<br> +$0.02 per additional million CPU milliseconds<br><br> Max of [5 minutes of CPU time](https://developers.cloudflare.com/workers/platform/limits/#account-plan-limits) per invocation (default: 30 second
- free-tier-eligible ↗
rs Paid](https://developers.cloudflare.com/workers/platform/pricing/#workers) | | --- | --- | --- | | Rows read | 5 million / day | First 25 billion / month included + $0.001 / million rows | | Rows written | 100,000 / day | First 50 million / month included + $1.00 / million rows | | Storage (per GB stored) | 5 GB (total) | First 5 GB included + $0.75 / GB-mo | Track your D1 usage To accurately track your usage, use the [meta object](https://developers.cloudflare.com/d1/worker-api/return-object/), [GraphQL Analytics API](https://developers.cloudflare.com/d1/obs
- free-tier-eligible ↗
ute and storage. Note Durable Objects are available both on Workers Free and Workers Paid plans. - **Workers Free plan**: Only Durable Objects with [SQLite storage backend](https://developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/#create-sqlite-backed-durable-object-class) are available. - **Workers Paid plan**: Durable Objects with the SQLite storage backend are available. The [key-value storage backend](https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/#storage-backends) is only avail
- MIT ↗
MIT License Copyright (c) 2026 huilang-me Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this
- architecture ↗
# Cloudflare Workers 配置,线上已由deploy.yml接管,这里仅供本地测试 name = "cf-server-monitor" main = "src/index.js" compatibility_date = "2024-12-01" compatibility_flags = ["nodejs_compat"] keep_vars = true # 定时任务配置(UTC) # - 每分钟:执行离线节点、资源告警 # - 每小时:合并执行其他定时任务(表轮换、旧表清理、按配置时区/小时触发服务器到期检测) # - 注意同步修改deploy和index.js scheduled函数里面的值 [triggers] crons = ["*/1 * * * *", "0 * * * *"] [assets] directory = "./dist" binding = "ASSETS" [[d1_databases]] binding = "DB" database_name = "server-monitor-db" # Durable Objects:实时指标广播中心 # 注意:首次部署前需要执行 `wrangler durable-obje
What it can replace
Compare the workflow you need. These mappings describe overlap; full feature parity requires a separate comparison.
Editorial workflow alternative: Dashboarding and alerts for metrics reported by agents on existing servers; remote management and complete observability parity not asserted.
See supporting source ↗How it works
The shape of CF-Server-Monitor on Cloudflare, and how it stacks up against the rented tools it replaces.
Architecture
Diagram of deployment declarations at the reviewed commit. Each app has its own entrypoint; declared resources do not prove runtime calls. Follow file and line sources below.
View upstream source ↗Configuration and workflow sources
Reviewed commit dfb9bf19c234. Files were read as data; upstream applications and CI jobs were not executed.
Partial source coverage: 13 files outside collection bounds; 0 collection or parsing issues. Dynamic imports and generated entrypoints may need manual review.
Deployment configuration · 1 files
Cloudflare Workers · compatibility 2024-12-01
cf-server-monitor · default
Entrypoint: src/index.js
Static assets: ./dist
Cron triggers (UTC): */1 * * * * · 0 * * * *
DB→ D1METRICS_BROADCASTER→ Durable Objects · class MetricsBroadcasterASSETS→ Static assets
Named environments are separate deployments. Bindings are shown only where declared. Configured routes are URL patterns, not verified application endpoints.
Runtime source · handlers, binding usage and workflow steps
Observed TypeScript/JavaScript declarations from Worker entrypoints and resolved relative imports. Calls and workflow steps may run conditionally; their listed order is not a proven end-to-end request flow. Router declarations may be mounted under a prefix or may not be registered. This shows code wiring, not a successful deployment or runtime test. Dynamic wiring, aliases and generated code may not resolve.
- L186 · fetch handler exported · references DEBUG, API_SECRET, DB, METRICS_BROADCASTER · calls setDebug, getCorsAllowedOrigins, createBadRequestResponse, applyCors, createOptionsResponse, Response.redirect, target.toString, path.startsWith, serveFrontend, loadSettings, themeAssetResponse.headers.get, cleanThemeAssetResponse, initDatabase, bypassTurnstilePaths.includes, request.headers.get, isTurnstileBypassed, loadSiteSettings, isTurnstileVerified, verifyTurnstileToken, createErrorResponse, handleUpdate, handleUpdateWebSocketUpgrade, createSuccessResponse, env.METRICS_BROADCASTER.idFromName, env.METRICS_BROADCASTER.get, stub.fetch, ensureSiteSettings, loadAppearanceOptions, Math.floor, Date.now, encryptTurnstileData, checkAuth, getRemoteVersion, isGithubOAuthReady, Number, normalizeFrontendWsTimeoutMinutes, normalizeLongHistoryPoints, request.json, simpleAuthResponse, handleGithubOAuthStartApi, handleGithubOAuthCallback, handleTheme, JSON.stringify, isValidThemeOptions, saveThemeOptions, handleServerAPI, ensureFullSettings, handleServersAPI, handleWebSocketUpgrade, url.searchParams.get, parseFloat, HISTORY_ALL_QUERY_COLUMNS.join, fetchHistoryData, handleAdminAPI, updateDatabase, clearHistory, route.handler, finalHeaders.set
- L493 · scheduled handler exported · references DB, DEBUG · calls debug, now.getUTCDay, now.getUTCHours, now.getUTCMinutes, checkOfflineNodes, checkResourceAlerts, weeklyCleanup, checkExpiringServers, now.getTime
- L33 · cleanThemeAssetResponse calls (conditional paths may differ): headers.delete
- L43 · getEncryptionKey calls (conditional paths may differ): crypto.subtle.digest, encode, crypto.subtle.importKey, slice
- L57 · encryptTurnstileData calls (conditional paths may differ): getEncryptionKey, crypto.getRandomValues, encoder.encode, JSON.stringify, crypto.subtle.encrypt, combined.set, btoa, String.fromCharCode
- L73 · decryptTurnstileData calls (conditional paths may differ): getEncryptionKey, map, split, atob, c.charCodeAt, decoded.slice, crypto.subtle.decrypt, JSON.parse, encoder.decode, debug
- L92 · isTurnstileVerified calls (conditional paths may differ): request.headers.get, decryptTurnstileData, Date.now
- L105 · fetchHistoryData calls (conditional paths may differ): createBadRequestResponse, ALLOWED_HOURS.includes, loadSiteSettings, checkAuth, simpleAuthResponse, createUnauthorizedResponse, getServerDetail, createNotFoundResponse, Math.min, getCacheDuration, Number, normalizeLongHistoryPoints, getMetricsHistoryCache, Date.now, Array.isArray, cached.data.map, createSuccessResponse, getMetricsHistory, String, test
- L268 · ensureSiteSettings calls (conditional paths may differ): loadSiteSettings
- L275 · ensureFullSettings calls (conditional paths may differ): loadSettings
Environment references: env.TURNSTILE_SECRET_KEY · env.API_SECRET · env.DB · env.DEBUG · env.METRICS_BROADCASTER
- L31 · pruneDashboardLatencyHistoryCache calls (conditional paths may differ): Date.now, dashboardLatencyHistoryCache.delete, next, dashboardLatencyHistoryCache.keys
- L45 · clearDashboardLatencyHistoryCache calls (conditional paths may differ): dashboardLatencyHistoryCache.clear
- L49 · initDatabase calls (conditional paths may differ): debug, first, db.prepare, run, saveSiteOptions, ensureServerOptimization, createHistoryTableSql, console.error
- L130 · clearHistory calls (conditional paths may differ): debug, run, db.prepare, initDatabase, clearAllCaches, clearDashboardLatencyHistoryCache, console.error
- L171 · getMetricsHistory calls (conditional paths may differ): Date.now, getCacheDuration, Math.min, Number, normalizeLongHistoryPoints, getMetricsHistoryCache, debug, getServerHistoryInfo, Math.max, nowDate.getUTCDay, Date.UTC, nowDate.getUTCFullYear, nowDate.getUTCMonth, nowDate.getUTCDate, thisSunday.getTime, first, db.prepare, getHistoryIdRange, filter, map
- L332 · normalizeLatencyHistoryValue calls (conditional paths may differ): isDisabledProbeMetric, Number, Number.isFinite, Math.max, Math.min, Math.round
- L345 · buildLatencyHistoryPoint calls (conditional paths may differ): Number, Number.isFinite, Object.prototype.hasOwnProperty.call, normalizeLatencyHistoryValue, Object.keys
- L361 · normalizeDashboardLatencyRows calls (conditional paths may differ): buildLatencyHistoryPoint, ping.push, loss.push, ping.sort, loss.sort
- L378 · parseDashboardLatencySample calls (conditional paths may differ): JSON.parse
- L387 · normalizeDashboardLatencyWindow calls (conditional paths may differ): Array.from, parseDashboardLatencySample, Number, normalizeDashboardLatencyRows
- L405 · getDashboardLatencyHistory calls (conditional paths may differ): Array.isArray, filter, serverList.map, trim, String, Number.isFinite, Number, Date.now, pruneDashboardLatencyHistoryCache, dashboardLatencyHistoryCache.get, result.set, serversToFetch.push, Number.isInteger, Math.floor, DASHBOARD_LATENCY_COLUMNS.join, nowDate.getUTCDay, Date.UTC, nowDate.getUTCFullYear, nowDate.getUTCMonth, nowDate.getUTCDate
- L509 · weeklyCleanup calls (conditional paths may differ): debug, run, db.prepare, first, initDatabase, console.error
- L543 · saveMetricsHistory calls (conditional paths may differ): buildHistoryId, Number, Number.isFinite, Date.now, isDisabledProbeMetric, parseInt, Number.isNaN, Math.max, Math.min, flattenDiskMetrics, run, bind, db.prepare, HISTORY_INSERT_COLUMNS.join, join, HISTORY_INSERT_COLUMNS.map, parseFloat, parsePing, parseLoss, Array.isArray
- L648 · getLatestMetrics calls (conditional paths may differ): getServerHistoryInfo, getHistoryIdRange, debug, first, bind, db.prepare, normalizeProbeMetricRow, console.error
- L673 · getLatestMetricsForAllServers calls (conditional paths may differ): Date.now, getLatestMetricsCache, getAllServers, Promise.all, servers.map, then, getLatestMetrics, entries.filter, setLatestMetricsCache, console.error
- L41 · getZonedDateParts calls (conditional paths may differ): Date.now, Number.isNaN, date.getTime, normalizeNotificationTimezone, Object.fromEntries, map, formatter.formatToParts
- L69 · formatNotificationTime calls (conditional paths may differ): Date.now, getZonedDateParts, padStart, String, Number, pad
- L77 · formatLastReportTime calls (conditional paths may differ): formatNotificationTime
- L83 · isExpireNotificationTimeDue calls (conditional paths may differ): Date.now, getZonedDateParts, Number, normalizeExpireNotificationTime
- L89 · getZonedDateSerial calls (conditional paths may differ): getZonedDateParts, Math.floor, Date.UTC, Number
- L95 · parseDateSerial calls (conditional paths may differ): match, trim, String, Number, Date.UTC, date.getUTCFullYear, date.getUTCMonth, date.getUTCDate, Math.floor, date.getTime
- L112 · formatMegabitsPerSecond calls (conditional paths may differ): Number, Number.isFinite, mbps.toFixed
- L119 · formatPercent calls (conditional paths may differ): Number, Number.isFinite, number.toFixed
- L125 · formatResourceMetric calls (conditional paths may differ): formatPercent, formatMegabitsPerSecond
- L155 · formatResourceMetricValue calls (conditional paths may differ): formatPercent, formatMegabitsPerSecond
- L162 · formatRecoveredResourceMetric calls (conditional paths may differ): getResourceMetricLabel, formatResourceMetricValue
- L172 · parseResourceAlertState calls (conditional paths may differ): JSON.parse, String
- L186 · hasResourceAlertStateEntries calls (conditional paths may differ): Object.keys
- L190 · getD1Changes calls (conditional paths may differ): Number, Number.isFinite
- L195 · clearResourceAlertState calls (conditional paths may differ): run, bind, db.prepare, getD1Changes
- L203 · saveResourceAlertState calls (conditional paths may differ): hasResourceAlertStateEntries, run, bind, db.prepare, JSON.stringify, clearResourceAlertState
- L223 · getResourceAlertStateTimestamp calls (conditional paths may differ): Number, Number.isFinite
- L229 · getStoredResourceAlertMetrics calls (conditional paths may differ): map
- L238 · canRecoverResourceAlert calls (conditional paths may differ): Array.isArray, metrics.every, Number, Number.isFinite
- L247 · getResourceAlertRuleIntervalMs calls (conditional paths may differ): Number, Number.isFinite, Math.max
- L253 · formatCurrentTime calls (conditional paths may differ): formatNotificationTime, Date.now
- L261 · getResourceAlertRuleName calls (conditional paths may differ): trim, String
- L265 · getResourceAlertRuleServerIds calls (conditional paths may differ): filter, servers.map, String, Array.isArray, trim, allowed.has, seen.has, seen.add, ids.push
- L283 · formatConciseResourceMetric calls (conditional paths may differ): getResourceMetricLabel, formatResourceMetricValue
- L289 · buildGroupedResourceAlertEntries calls (conditional paths may differ): Array.isArray, trim, String, groups.get, groups.set, formatConciseResourceMetric, group.seen.has, group.seen.add, group.metrics.push, map, filter, Array.from, groups.values, group.metrics.join
- L321 · appendResourceAlertNotificationChunks calls (conditional paths may differ): Array.isArray, join, chunkEntries.map, payloads.push, candidateEntries.map, flush, chunkEntries.push, chunkClients.push
- L358 · buildResourceAlertNotificationPayloads calls (conditional paths may differ): appendResourceAlertNotificationChunks, buildGroupedResourceAlertEntries
- L379 · evaluateResourceAlertRules calls (conditional paths may differ): Array.isArray, requests.push, serverIds.slice, requests.slice, stub.fetch, JSON.stringify, batch.map, Number, getResourceAlertRuleThresholds, console.warn, response.json, trim, String, resultMap.get, existing.alerts.push, existing.evaluatedServerIds.push, filter, item.evaluatedServerIds.map, existing.evaluations.push, item.evaluations.filter
- L447 · fetchWithRetry calls (conditional paths may differ): fetch, setTimeout
- L467 · stripMarkdown calls (conditional paths may differ): trim, replace, String
- L474 · inferNotificationEvent calls (conditional paths may differ): find, split, String, line.trim, stripMarkdown
- L479 · escapeJsonStringFragment calls (conditional paths may differ): slice, JSON.stringify, String
- L483 · renderTemplate calls (conditional paths may differ): String, source.replace, escapeJsonStringFragment
- L491 · normalizeNotificationClients calls (conditional paths may differ): Array.isArray, split, String, filter, source.map, trim, Array.from
- L502 · inferNotificationEmoji calls (conditional paths may differ): String, test
- L510 · buildNotificationContext calls (conditional paths may differ): formatCurrentTime, normalizeNotificationClients, Number.isFinite, Number, inferNotificationEvent, inferNotificationEmoji, clients.join, String
- L529 · formatNotificationMessage calls (conditional paths may differ): normalizeNotificationTemplate, renderTemplate, String
- L534 · parseWebhookHeaders calls (conditional paths may differ): trim, renderTemplate, normalizeNotificationWebhookHeaders, raw.startsWith, JSON.parse, Array.isArray, Object.entries, String, test, raw.split, line.indexOf, line.slice
- L562 · buildWebhookQueryParams calls (conditional paths may differ): normalizeNotificationWebhookBody, renderTemplate, JSON.parse, Array.isArray, map, Object.entries, String, Array.from, params.entries
- L576 · buildWebhookUrl calls (conditional paths may differ): renderTemplate, trim, String, buildWebhookQueryParams, url.searchParams.set, url.toString
Environment references: env.DB · env.METRICS_BROADCASTER
- L12 · updateDatabase calls (conditional paths may differ): debug, addServerColumns, results.push, cleanupServerExtraColumns, addHistoryColumns, cleanupStaleSettings, dropMetricsAggregatedTable, run, db.prepare
- L55 · addServerColumns calls (conditional paths may differ): all, db.prepare, columns.map, existingCols.includes, Object.entries, run, normalizePrice, normalizeBillingCycle, detectBillingCycle, normalizeCurrency, detectCurrencySymbol, bind, debug
- L130 · cleanupServerExtraColumns calls (conditional paths may differ): all, db.prepare, columns.map, extraCols.filter, existingCols.includes, run, debug, colsToDrop.join
- L154 · addHistoryColumns calls (conditional paths may differ): first, db.prepare, tables.push, all, historyColumns.map, Object.entries, existingHistoryCols.includes, run, debug
- L184 · dropMetricsAggregatedTable calls (conditional paths may differ): debug, all, db.prepare, run
- L204 · cleanupStaleSettings calls (conditional paths may differ): debug, join, concat, stalePrefixes.map, staleExact.map, run, bind, db.prepare
- L21 · toUsageNumber calls (conditional paths may differ): Number, Number.isFinite
- L26 · isDurableObjectsHibernationInvocationType calls (conditional paths may differ): replace, toLowerCase, String, type.includes
- L36 · normalizeServerRegion calls (conditional paths may differ): slice, replace, toUpperCase, trim, String
- L40 · normalizeServerBillingData calls (conditional paths may differ): normalizeBillingCycle, detectBillingCycle, normalizeBooleanFlag, normalizePrice, normalizeCurrency, detectCurrencySymbol, renewExpireDateIfNeeded
- L57 · isValidUUID calls (conditional paths may differ): test
- L61 · isValidName calls (conditional paths may differ): name.trim
- L65 · isMissingColumnError calls (conditional paths may differ): String, test
- L70 · handleServerMutationError calls (conditional paths may differ): isMissingColumnError, console.warn, addServerColumns, createBadRequestResponse, String
- L81 · sanitizeCspDomains calls (conditional paths may differ): join, filter, map, input.split, s.trim, arr.indexOf
- L92 · normalizeCspOrigin calls (conditional paths may differ): trim, String, test
- L106 · normalizePingNodeFields calls (conditional paths may differ): validatePingNode
- L124 · normalizeNetworkInterfaceField calls (conditional paths may differ): validateNetworkInterfaces
- L132 · hasAppearanceInput calls (conditional paths may differ): some, APPEARANCE_FIELDS.filter
- L139 · extractBearerToken calls (conditional paths may differ): request.headers.get, split, authHeader.trim
- L145 · buildThemePreviewUrl calls (conditional paths may differ): previewUrl.searchParams.set, previewUrl.toString
- L151 · buildThemePreviewAuthCookie calls (conditional paths may differ): encodeURIComponent
- L161 · createSuccessResponseWithCookies calls (conditional paths may differ): headers.append, JSON.stringify
- L169 · normalizeThemeUrl calls (conditional paths may differ): trim, String, filter, url.pathname.split, test, parts.some, parts.join
- L199 · getThemeRawIndexUrl calls (conditional paths may differ): normalizeThemeUrl, filter, url.pathname.split, join, map, parts.slice, encodeURIComponent
- L214 · validateThemeUrlAvailable calls (conditional paths may differ): getThemeRawIndexUrl, fetch
- L231 · deleteServer calls (conditional paths may differ): db.prepare, stmt1.all, run, bind, stmt2.all
- L251 · getUtcTodayRange calls (conditional paths may differ): Date.UTC, now.getUTCFullYear, now.getUTCMonth, now.getUTCDate, start.getTime, slice, start.toISOString, end.toISOString
- L264 · getUtcYesterdayRange calls (conditional paths may differ): Date.UTC, now.getUTCFullYear, now.getUTCMonth, now.getUTCDate, todayStart.getTime, slice, start.toISOString, end.toISOString
- L278 · cloudflareGraphql calls (conditional paths may differ): fetch, JSON.stringify, response.json, join, data.errors.map
- L295 · estimateDurableObjectsWebSocketBillableRequests calls (conditional paths may differ): toUsageNumber, Math.ceil
- L301 · estimateDurableObjectsBillableRequests calls (conditional paths may differ): estimateDurableObjectsWebSocketBillableRequests, toUsageNumber, Math.ceil
- L315 · summarizeDurableObjectsUsage calls (conditional paths may differ): toUsageNumber, isDurableObjectsHibernationInvocationType, estimateDurableObjectsBillableRequests
- L344 · fetchCloudflareUsage calls (conditional paths may differ): cloudflareGraphql, groups.reduce, Number, reduce, summarizeDurableObjectsUsage
- L418 · getD1DailyUsage calls (conditional paths may differ): getUtcTodayRange, getUtcYesterdayRange, Promise.all, fetchCloudflareUsage
- L464 · handleLoginAction calls (conditional paths may differ): createBadRequestResponse, request.headers.get, verifyTurnstileToken, createErrorResponse, btoa, validateCredentials, createUnauthorizedResponse, hashPassword, saveSiteOptions, console.error, generateToken, createSuccessResponse, buildAuthCookie
- L523 · handleLogoutAction calls (conditional paths may differ): createSuccessResponseWithCookies, buildClearAuthCookie, buildClearThemePreviewAuthCookie
- L532 · handleClearThemePreviewAuthAction calls (conditional paths may differ): createSuccessResponseWithCookies, buildClearThemePreviewAuthCookie
- L546 · sanitizeAdminSettings calls (conditional paths may differ): Boolean, trim, String
- L555 · handleGetSettingsAction calls (conditional paths may differ): loadFullSettings, createSuccessResponse, sanitizeAdminSettings
- L564 · handleStartThemePreviewAction calls (conditional paths may differ): normalizeThemeUrl, createBadRequestResponse, validateThemeUrlAvailable, extractBearerToken, simpleAuthResponse, createSuccessResponse, buildThemePreviewUrl, buildThemePreviewAuthCookie
- L586 · handleSaveThemeOptionsAction calls (conditional paths may differ): isValidThemeOptions, createBadRequestResponse, saveThemeOptions, createSuccessResponse
- L604 · handleListAction calls (conditional paths may differ): getAllServers, getLatestMetricsForAllServers, Date.now, servers.map, latestMetricsMap.get, mergeMetricsIntoServer, parseFloat, toFixed, createSuccessResponse
- L668 · handleD1UsageAction calls (conditional paths may differ): Object.prototype.hasOwnProperty.call, getD1DailyUsage, trim, String, createSuccessResponse, createBadRequestResponse
- L686 · handleSendTestNotificationAction calls (conditional paths may differ): normalizeBooleanSetting, trim, String, createBadRequestResponse, isSmtpNotificationTarget, tg_bot_token.trim, sendNotification, normalizeNotificationWebhookMethod, normalizeNotificationWebhookFormat, normalizeNotificationWebhookHeaders, normalizeNotificationWebhookBody, normalizeNotificationTemplate, normalizeNotificationTimezone, normalizeExpireNotificationTime, console.warn, createSuccessResponse
- L751 · handleAdminAPI calls (conditional paths may differ): request.json, publicActionHandler, checkAuth, simpleAuthResponse, authenticatedActionHandler, trim, String, createBadRequestResponse, normalizeThemeUrl, validateThemeUrlAvailable, settings.turnstile_site_key.trim, settings.turnstile_secret_key.trim, normalizeBooleanSetting, normalizeTgNotify, normalizeExpireReminder, normalizeResourceAlertRules, isSmtpNotificationTarget, normalizePingNodeFields, Array.isArray, hasAppearanceInput
Environment references: env.DB · env.API_SECRET
- L23 · loadFrontendFiles calls (conditional paths may differ): env.ASSETS.fetch, res.text, console.log, console.error
- L55 · escapeHtml calls (conditional paths may differ): replace, String
- L64 · insertBeforeHeadClose calls (conditional paths may differ): test, html.replace
- L71 · injectTitle calls (conditional paths may differ): escapeHtml, test, html.replace, insertBeforeHeadClose
- L79 · injectFavicon calls (conditional paths may differ): trim, String, html.replace, escapeHtml, insertBeforeHeadClose
- L88 · getEnvApiBases calls (conditional paths may differ): parseCspOrigins
- L92 · injectAppearanceSettings calls (conditional paths may differ): stripCspMeta, injectTitle, injectFavicon, getEnvApiBases, injectApiBase, parseCspOrigins, buildApiDomainsWithWs, buildCspHeader, insertBeforeHeadClose, test, modifiedHtml.replace, buildBackgroundStyle
- L133 · getContentType calls (conditional paths may differ): toLowerCase, split, String, cleanPath.endsWith
- L154 · normalizeThemeUrl calls (conditional paths may differ): trim, String, filter, url.pathname.split, test, parts.some, parts.join
- L182 · parseThemeUrl calls (conditional paths may differ): normalizeThemeUrl, filter, url.pathname.split, parts.slice, join, map, encodeURIComponent
- L204 · isCommitRef calls (conditional paths may differ): test
- L208 · getThemeWorkerCacheTtl calls (conditional paths may differ): isCommitRef
- L212 · getThemeAssetBrowserCacheControl calls (conditional paths may differ): isCommitRef
- L219 · getCookie calls (conditional paths may differ): request.headers.get, cookie.split, split, part.trim, valueParts.join
- L230 · getPreviewThemeUrlFromCookie calls (conditional paths may differ): getCookie, normalizeThemeUrl, decodeURIComponent
- L240 · buildPreviewCookie calls (conditional paths may differ): encodeURIComponent
- L250 · checkPreviewAuth calls (conditional paths may differ): getCookie, toLowerCase, String, decodeURIComponent, request.headers.get, checkAuth
- L271 · getPreviewThemeUrlFromQuery calls (conditional paths may differ): url.searchParams.has, normalizeThemeUrl, url.searchParams.get
- L276 · normalizeAssetPath calls (conditional paths may differ): pathname.slice, decodeURIComponent, decoded.includes, filter, decoded.split, parts.some, join, parts.map, encodeURIComponent
- L291 · normalizeThemeAssetUrls calls (conditional paths may differ): html.replace
- L295 · stripBrowserCacheHeaders calls (conditional paths may differ): headers.delete
- L309 · fetchWithCache calls (conditional paths may differ): cache.match, stripBrowserCacheHeaders, fetch, headers.set, originResponse.headers.get, catch, cache.put, response.clone
- L352 · serveThemeAsset calls (conditional paths may differ): parseThemeUrl, normalizeAssetPath, getContentType, fetchWithCache, getThemeWorkerCacheTtl, headers.set, getThemeAssetBrowserCacheControl
- L387 · loadThemeIndex calls (conditional paths may differ): parseThemeUrl, fetchWithCache, getThemeWorkerCacheTtl, normalizeThemeAssetUrls, response.text
- L402 · buildHtmlResponse calls (conditional paths may differ): injectAppearanceSettings, headers.append, buildPreviewCookie, getPreviewThemeUrlFromCookie, buildClearPreviewCookie
- L430 · buildPreviewUnauthorizedResponse calls (conditional paths may differ): buildClearPreviewCookie, headers.set
- L447 · resolveThemeUrlForAsset calls (conditional paths may differ): getPreviewThemeUrlFromQuery, getPreviewThemeUrlFromCookie, normalizeThemeUrl
- L465 · shouldUseBuiltinFrontend calls (conditional paths may differ): path.startsWith
- L469 · serveFrontend calls (conditional paths may differ): loadSettings, path.startsWith, resolveThemeUrlForAsset, checkPreviewAuth, buildPreviewUnauthorizedResponse, serveThemeAsset, getPreviewThemeUrlFromQuery, normalizeThemeUrl, shouldUseBuiltinFrontend, loadThemeIndex, buildHtmlResponse, buildThemeIndexErrorResponse, loadFrontendFiles
Environment references: env.ASSETS · env.API_BASE · env.DB
- L41 · buildPayloadForBroadcast calls (conditional paths may differ): mergeMetricsIntoServer, Date.now, coerceNumericMetricFields
- L67 · normalizeTimestamp calls (conditional paths may differ): Date.now, Number, Number.isFinite
- L73 · normalizeAgentVersion calls (conditional paths may differ): slice, replace, trim, String
- L81 · logUpdateBadRequest calls (conditional paths may differ): console.warn
- L85 · normalizeCorrectionValue calls (conditional paths may differ): isValidTrafficCorrection, Number
- L90 · normalizeMetricSamples calls (conditional paths may differ): Date.now, Array.isArray, filter, rawSamples.map, normalizeTimestamp, samples.push, samples.sort, samples.slice
- L124 · hasOwnMetric calls (conditional paths may differ): Object.prototype.hasOwnProperty.call
- L128 · isPlainMetricObject calls (conditional paths may differ): Array.isArray
- L132 · toFiniteHistoryMetricNumber calls (conditional paths may differ): Number, Number.isFinite
- L138 · getHistoryMetricSourceValue calls (conditional paths may differ): isPlainMetricObject, hasOwnMetric
- L153 · getSampleMetricSource calls (conditional paths may differ): isPlainMetricObject
- L162 · mergeHistoryMetricAggregates calls (conditional paths may differ): createEmptyHistoryMetricAggregate, isPlainMetricObject, Object.entries, toFiniteHistoryMetricNumber, hasOwnMetric, Number, Number.isFinite
- L196 · addHistoryMetricAggregateSource calls (conditional paths may differ): isPlainMetricObject, Object.entries, toFiniteHistoryMetricNumber, getHistoryMetricSourceValue, hasOwnMetric
- L217 · collectHistoryMetricAggregates calls (conditional paths may differ): mergeHistoryMetricAggregates, Array.isArray, addHistoryMetricAggregateSource, getSampleMetricSource
- L225 · setHistoryMetricResultValue calls (conditional paths may differ): isPlainMetricObject
- L238 · applyHistoryMetricAggregates calls (conditional paths may differ): mergeHistoryMetricAggregates, Object.entries, setHistoryMetricResultValue, Number.isFinite
- L254 · getHistoryMetrics calls (conditional paths may differ): applyHistoryMetricAggregates, getReportMetrics, collectHistoryMetricAggregates
- L261 · buildSamplePayloadForBroadcast calls (conditional paths may differ): Date.now, BROADCAST_DELETE_FIELDS.forEach, coerceNumericMetricFields
- L269 · toBroadcastSamples calls (conditional paths may differ): samples.map, buildSamplePayloadForBroadcast, buildPayloadForBroadcast, Object.assign, BROADCAST_DELETE_FIELDS.forEach
- L291 · queueBroadcastSamples calls (conditional paths may differ): Array.isArray, batchQueue.get, existing.samples.concat, batchQueue.set, merged.slice
- L300 · getCachedFrontendSubscriberCount calls (conditional paths may differ): Date.now, env.METRICS_BROADCASTER.idFromName, env.METRICS_BROADCASTER.get, stub.fetch, response.json, Math.max, Number, console.warn
- L325 · hasResourceAlertNotificationTarget calls (conditional paths may differ): normalizeBooleanSetting, trim, String
- L332 · getRealtimeBatchIntent calls (conditional paths may differ): loadSiteSettings, hasResourceAlertNotificationTarget, getResourceAlertConfig, console.warn, hasRecentFrontendRealtimeActivity, getCachedFrontendSubscriberCount
- L369 · getBatchFlushDelayMs calls (conditional paths may differ): Date.now, hasRecentFrontendRealtimeActivity, loadSiteSettings, hasResourceAlertNotificationTarget, getResourceAlertConfig, console.warn
- L384 · buildAgentWssStateHeaders calls (conditional paths may differ): Date.now, getWssReportScheduleState
- L392 · createAgentWssScheduleInactiveResponse calls (conditional paths may differ): JSON.stringify, buildAgentWssStateHeaders
- L408 · createAgentWssDisabledResponse calls (conditional paths may differ): JSON.stringify
- L425 · _flushBatch calls (conditional paths may differ): Array.isArray, updates.push, Object.assign, BROADCAST_DELETE_FIELDS.forEach, getRealtimeBatchIntent, env.METRICS_BROADCASTER.idFromName, env.METRICS_BROADCASTER.get, stub.fetch, JSON.stringify, console.warn
- L465 · _ensureBatchFlush calls (conditional paths may differ): Date.now, hasRecentFrontendRealtimeActivity, clearTimeout, setTimeout, finally, _flushBatch, resolve, then, getBatchFlushDelayMs, Math.max, Number, currentResolve
- L507 · handleUpdate calls (conditional paths may differ): request.json, createUnauthorizedResponse, normalizeAgentVersion, request.headers.get, getServerDetail, createNotFoundResponse, Object.prototype.hasOwnProperty.call, normalizeCorrectionValue, createBadRequestResponse, run, bind, env.DB.prepare, clearServerDetailCache, scheduleAgentConfigChanged, ensureServerOptimization, logUpdateBadRequest, normalizeMetricSamples, Array.isArray, getReportMetrics, getHistoryMetrics
- L649 · isWebSocketUpgradeRequest calls (conditional paths may differ): request.headers.get, upgradeHeader.toLowerCase
- L654 · forwardWebSocketUpgrade calls (conditional paths may differ): JSON.stringify, isWebSocketUpgradeRequest, env.METRICS_BROADCASTER.idFromName, env.METRICS_BROADCASTER.get, headers.set, headers.get, stub.fetch, console.error
- L692 · handleWebSocketUpgrade calls (conditional paths may differ): loadSiteSettings, checkWebSocketAuth, JSON.stringify, forwardWebSocketUpgrade, markFrontendRealtimeActive
- L708 · handleUpdateWebSocketUpgrade calls (conditional paths may differ): loadSiteSettings, isWssReportConfigured, createAgentWssDisabledResponse, getWssReportScheduleState, createAgentWssScheduleInactiveResponse, forwardWebSocketUpgrade
Environment references: env.METRICS_BROADCASTER · env.DB · env.API_SECRET
- L22 · omitNullLossProbeFields calls (conditional paths may differ): Array.isArray
- L38 · toPublicIpReachability calls (conditional paths may differ): toLowerCase, trim, String
- L43 · normalizePublicIpFields calls (conditional paths may differ): Object.prototype.hasOwnProperty.call, toPublicIpReachability, Array.isArray, normalizePublicIpFields
- L58 · withoutPrivateServerFields calls (conditional paths may differ): normalizePublicIpFields
- L66 · normalizeLatestReportSample calls (conditional paths may differ): coerceNumericMetricFields, normalizePublicIpFields
- L76 · normalizeLatestReportUpdate calls (conditional paths may differ): Array.isArray, filter, update.samples.map
- L90 · attachLatencyHistoryToServers calls (conditional paths may differ): String, createEmptyLatencyWindow
- L98 · getDurableRealtimeState calls (conditional paths may differ): Array.isArray, env.METRICS_BROADCASTER.idFromName, env.METRICS_BROADCASTER.get, serverIds.slice, stub.fetch, JSON.stringify, response.json, updates.push, console.warn
- L126 · mergeLatestReportUpdates calls (conditional paths may differ): Array.isArray, merged.set, String, merged.get, getLatestReportSampleTimestamp, Date.now, filter, map, serverIds.map, normalizeLatestReportUpdate, Math.max, Number
- L154 · getRealtimeStateForServers calls (conditional paths may differ): Array.from, filter, map, Array.isArray, trim, String, getDurableRealtimeState, cacheLatestReportUpdate, mergeLatestReportUpdates, getWorkerLatestReportUpdates
- L181 · handleServerAPI calls (conditional paths may differ): checkAuth, simpleAuthResponse, markFrontendRealtimeActive, url.searchParams.get, createBadRequestResponse, getServerDetail, createNotFoundResponse, Promise.all, getLatestMetrics, getRealtimeStateForServers, mergeMetricsIntoServer, Number, normalizeLongHistoryPoints, createSuccessResponse, omitNullLossProbeFields, withoutPrivateServerFields
- L210 · handleServersAPI calls (conditional paths may differ): checkAuth, simpleAuthResponse, markFrontendRealtimeActive, map, getAllServers, filter, results.map, Promise.all, getLatestMetricsForAllServers, getRealtimeStateForServers, getDashboardLatencyHistory, Promise.resolve, attachLatencyHistoryToServers, Date.now, latestMetricsMap.get, mergeMetricsIntoServer, normalizePublicIpFields, parseFloat, toUpperCase, createSuccessResponse
Environment references: env.METRICS_BROADCASTER · env.DB
- L23 · handleTheme calls (conditional paths may differ): Math.floor, Date.now, fetch, res.json, normalizeThemeStore
- L14 · normalizeGithubUserId calls (conditional paths may differ): trim, String, test
- L19 · hasGithubOAuthCredentials calls (conditional paths may differ): Boolean, trim, String
- L24 · isGithubOAuthConfigured calls (conditional paths may differ): isEnabled, hasGithubOAuthCredentials
- L28 · isGithubOAuthReady calls (conditional paths may differ): isGithubOAuthConfigured, Boolean, normalizeGithubUserId
- L32 · getCookieValue calls (conditional paths may differ): request.headers.get, cookie.split, part.trim, item.startsWith, decodeURIComponent, item.slice
- L47 · stateMatches calls (conditional paths may differ): actual.charCodeAt, expected.charCodeAt
- L56 · getCallbackUrl calls (conditional paths may differ): toString
- L60 · buildStateCookie calls (conditional paths may differ): encodeURIComponent
- L70 · redirectResponse calls (conditional paths may differ): headers.append
- L82 · redirectToAdmin calls (conditional paths may differ): target.searchParams.set, Object.entries, String, redirectResponse, target.toString
- L93 · createGithubAuthorizeRequest calls (conditional paths may differ): replace, crypto.randomUUID, authorizeUrl.searchParams.set, trim, String, getCallbackUrl, authorizeUrl.toString, buildStateCookie
- L106 · handleGithubOAuthStartApi calls (conditional paths may differ): hasGithubOAuthCredentials, isGithubOAuthReady, JSON.stringify, createGithubAuthorizeRequest
- L129 · handleGithubOAuthCallback calls (conditional paths may differ): buildClearStateCookie, url.searchParams.get, getCookieValue, stateMatches, redirectToAdmin, state.startsWith, hasGithubOAuthCredentials, isGithubOAuthConfigured, normalizeGithubUserId, checkAuth, fetch, toString, trim, String, getCallbackUrl, catch, tokenResponse.json, userResponse.json, Number.isSafeInteger, run
Environment references: env.DB
- L131 · normalizeLongHistoryPoints calls (conditional paths may differ): Number, String, LONG_HISTORY_POINT_OPTIONS.includes
- L140 · normalizeFrontendWsTimeoutMinutes calls (conditional paths may differ): Number, String, Number.isInteger
- L149 · normalizeTgNotify calls (conditional paths may differ): String, Number, Number.isInteger
- L172 · getTgNotifyMinutes calls (conditional paths may differ): Number, normalizeTgNotify
- L176 · normalizeExpireReminder calls (conditional paths may differ): String, Number, Number.isInteger
- L196 · getExpireReminderDays calls (conditional paths may differ): Number, normalizeExpireReminder
- L201 · normalizeTrafficAlertThreshold calls (conditional paths may differ): String, normalizePct
- L205 · normalizeNotificationTimezone calls (conditional paths may differ): trim, String, format
- L217 · normalizeExpireNotificationTime calls (conditional paths may differ): trim, String, raw.match, Number, Number.isInteger
- L227 · normalizeNotificationWebhookMethod calls (conditional paths may differ): toUpperCase, trim, String, NOTIFICATION_WEBHOOK_METHODS.includes
- L232 · normalizeNotificationWebhookFormat calls (conditional paths may differ): toLowerCase, trim, String, NOTIFICATION_WEBHOOK_FORMATS.includes
- L237 · normalizeNotificationWebhookHeaders calls (conditional paths may differ): slice, String
- L241 · normalizeNotificationWebhookBody calls (conditional paths may differ): trim, String, LEGACY_DEFAULT_NOTIFICATION_WEBHOOK_BODIES.includes, slice
- L249 · normalizeNotificationTemplate calls (conditional paths may differ): trim, String, LEGACY_DEFAULT_NOTIFICATION_TEMPLATES.includes, slice
- L257 · normalizeResourceAlertWindowMinutes calls (conditional paths may differ): Number, Number.isInteger, String
- L268 · normalizeResourceAlertIntervalMinutes calls (conditional paths may differ): normalizeResourceAlertWindowMinutes, String
- L273 · normalizeResourceAlertPercent calls (conditional paths may differ): Number, Number.isFinite, String, Math.round
- L280 · normalizeResourceAlertMbps calls (conditional paths may differ): Number, Number.isFinite, String, Math.round
- L287 · normalizeResourceAlertMode calls (conditional paths may differ): toLowerCase, trim, String
- L294 · normalizeResourceAlertMetric calls (conditional paths may differ): trim, String, RESOURCE_ALERT_METRICS.includes
- L299 · parseResourceAlertRulesValue calls (conditional paths may differ): Array.isArray, value.trim, JSON.parse
- L312 · hasExplicitResourceAlertRulesValue calls (conditional paths may differ): Array.isArray, value.trim
- L317 · normalizeResourceAlertRuleId calls (conditional paths may differ): slice, replace, trim, String
- L322 · normalizeResourceAlertRuleName calls (conditional paths may differ): slice, trim, String
- L335 · normalizeResourceAlertServers calls (conditional paths may differ): Array.isArray, trim, String, test, seen.has, seen.add, servers.push, servers.slice
- L356 · normalizeResourceAlertThreshold calls (conditional paths may differ): getDefaultResourceAlertThreshold, normalizeResourceAlertMbps, normalizeResourceAlertPercent, Number
- L364 · normalizeResourceAlertRule calls (conditional paths may differ): Array.isArray, normalizeResourceAlertMetric, normalizeResourceAlertIntervalMinutes, normalizeResourceAlertRuleId, normalizeResourceAlertRuleName, normalizeResourceAlertThreshold, normalizeResourceAlertServers, normalizeResourceAlertMode
- L382 · normalizeResourceAlertRules calls (conditional paths may differ): hasExplicitResourceAlertRulesValue, parseResourceAlertRulesValue, map, filter, source.slice, normalizeResourceAlertRule, seenIds.has, id.slice, Math.max, rule.id.slice, seenIds.add
- L410 · getResourceAlertRuleThresholds calls (conditional paths may differ): normalizeResourceAlertMetric, Number, normalizeResourceAlertThreshold
- L423 · getResourceAlertConfig calls (conditional paths may differ): normalizeResourceAlertRules
- L433 · generateRandomSecret calls (conditional paths may differ): crypto.getRandomValues, padStart, byte.toString
- L447 · tryParseJSON calls (conditional paths may differ): JSON.parse
- L465 · normalizeDisplayMode calls (conditional paths may differ): toLowerCase, trim, String
- L472 · normalizePreferredTheme calls (conditional paths may differ): toLowerCase, trim, String
- L478 · normalizeDefaultLanguage calls (conditional paths may differ): toLowerCase, trim, String
- L484 · normalizeBooleanSetting calls (conditional paths may differ): toLowerCase, trim, String, includes
- L497 · normalizeWssReportHours calls (conditional paths may differ): JSON.parse, filter, map, source.split, item.trim, Array.isArray, sort, Array.from, source.map, test, hour.trim, Number, Number.isInteger
- L522 · isWssReportConfigured calls (conditional paths may differ): normalizeBooleanSetting
- L526 · getWssReportScheduleState calls (conditional paths may differ): Date.now, Number.isNaN, date.getTime, isWssReportConfigured, normalizeWssReportHours, safeDate.getUTCHours, hours.includes
- L567 · isWssReportEnabled calls (conditional paths may differ): Date.now, getWssReportScheduleState
- L8 · generateKeyFromSecret calls (conditional paths may differ): encoder.encode, crypto.subtle.importKey
- L14 · signJwt calls (conditional paths may differ): replace, btoa, JSON.stringify, generateKeyFromSecret, encoder.encode, crypto.subtle.sign, String.fromCharCode
- L31 · verifyJwt calls (conditional paths may differ): token.split, generateKeyFromSecret, encoder.encode, map, split, atob, c.charCodeAt, crypto.subtle.verify, JSON.parse, Date.now, console.error
- L67 · getJwtSecret calls (conditional paths may differ): isValidJwtSecret, substring, fallback.padEnd
- L76 · getCookieValue calls (conditional paths may differ): cookie.split, part.trim, item.startsWith, decodeURIComponent, item.slice
- L91 · extractBearerToken calls (conditional paths may differ): split, authHeader.trim
- L97 · verifyToken calls (conditional paths may differ): getJwtSecret, verifyJwt, console.error
- L110 · generateToken calls (conditional paths may differ): Math.floor, Date.now, getJwtSecret, signJwt
- L121 · checkAuth calls (conditional paths may differ): verifyToken, extractBearerToken, getCookieValue
- L128 · checkWebSocketAuth calls (conditional paths may differ): checkAuth, verifyToken, url.searchParams.get
- L148 · buildAuthCookie calls (conditional paths may differ): encodeURIComponent
- L158 · validateCredentials calls (conditional paths may differ): request.headers.get, split, authHeader.trim, atob, decoded.indexOf, decoded.slice, verifyPasswordHash, console.error
- L219 · simpleAuthResponse calls (conditional paths may differ): JSON.stringify
Environment references: env.API_SECRET · env.API_USER_NAME
- L40 · filterServersByHidden calls (conditional paths may differ): servers.filter
- L48 · getAllServers calls (conditional paths may differ): Date.now, debug, filterServersByHidden, all, db.prepare
- L67 · clearServersListCache calls (conditional paths may differ): serverDetailCache.clear
- L72 · clearServerDetailCache calls (conditional paths may differ): serverDetailCache.clear
- L78 · patchServerDetailCache calls (conditional paths may differ): serverDetailCache.get, Object.assign
- L83 · getServerDetail calls (conditional paths may differ): Date.now, serverDetailCache.get, debug, serverDetailCache.delete, first, bind, db.prepare, serverDetailCache.set
- L122 · checkServerExists calls (conditional paths may differ): getServerDetail
- L135 · setLatestMetricsCache calls (conditional paths may differ): Date.now
- L145 · getCacheKey calls (conditional paths may differ): join, sort, columns.split
- L151 · getMetricsHistoryCache calls (conditional paths may differ): getCacheKey, metricsHistoryCache.get
- L156 · setMetricsHistoryCache calls (conditional paths may differ): getCacheKey, metricsHistoryCache.set, Date.now
- L161 · clearMetricsHistoryCache calls (conditional paths may differ): metricsHistoryCache.keys, key.startsWith, metricsHistoryCache.delete
- L169 · clearAllCaches calls (conditional paths may differ): clearServersListCache, clearLatestMetricsCache, metricsHistoryCache.clear, clearSiteSettingsCache, clearAppearanceSettingsCache
- L10 · createErrorResponse calls (conditional paths may differ): console.error, JSON.stringify
- L33 · createSuccessResponse calls (conditional paths may differ): JSON.stringify
- L41 · createUnauthorizedResponse calls (conditional paths may differ): JSON.stringify
- L51 · createBadRequestResponse calls (conditional paths may differ): JSON.stringify
- L61 · createNotFoundResponse calls (conditional paths may differ): JSON.stringify
- L14 · verifyTurnstileToken calls (conditional paths may differ): setTimeout, controller.abort, fetch, JSON.stringify, response.json, console.error, clearTimeout
- L53 · bytesToHex calls (conditional paths may differ): join, map, Array.from, padStart, b.toString
- L59 · hexToBytes calls (conditional paths may differ): test, parseInt, hex.slice
- L71 · timingSafeEqualBytes calls (conditional paths may differ): crypto.subtle.timingSafeEqual, Math.max
- L88 · derivePbkdf2Hash calls (conditional paths may differ): crypto.subtle.importKey, encoder.encode, crypto.subtle.deriveBits
- L112 · parsePbkdf2Hash calls (conditional paths may differ): split, storedHash.trim, Number, hexToBytes, Number.isInteger
- L140 · isLegacyMd5Hash calls (conditional paths may differ): test, storedHash.trim
- L144 · hashPassword calls (conditional paths may differ): crypto.getRandomValues, derivePbkdf2Hash, bytesToHex
- L150 · verifyPasswordHash calls (conditional paths may differ): parsePbkdf2Hash, derivePbkdf2Hash, timingSafeEqualBytes, isLegacyMd5Hash, md5Hash, hexToBytes, toLowerCase, storedHash.trim
- L195 · md5Hash calls (conditional paths may differ): Array.from, encode, bytes.push, Math.floor, rotateLeft, join, map, padStart, toString
- L1 · parseAllowedOrigins calls (conditional paths may differ): corsAllowedOrigins.trim, filter, map, corsAllowedOrigins.split, o.trim
- L11 · getCorsAllowedOrigins calls (conditional paths may differ): parseAllowedOrigins
- L15 · isOriginAllowed calls (conditional paths may differ): allowedOrigins.includes
- L22 · createCorsHeaders calls (conditional paths may differ): isOriginAllowed, headers.set
- L34 · createOptionsResponse calls (conditional paths may differ): request.headers.get, createCorsHeaders, headers.set
- L57 · applyCors calls (conditional paths may differ): request.headers.get, isOriginAllowed, newHeaders.set, newHeaders.get, vary.includes
Environment references: env.CORS_ALLOWED_ORIGINS
- L12 · getRemoteVersion calls (conditional paths may differ): Date.now, finally, fetchRemoteVersion
- L31 · fetchRemoteVersion calls (conditional paths may differ): Promise.allSettled, fetchWithTimeout, versionRes.value.json, releaseRes.value.json, release.tag_name.trim, Date.now
- L72 · fetchWithTimeout calls (conditional paths may differ): setTimeout, controller.abort, fetch, clearTimeout
- L154 · createHistoryTableSql calls (conditional paths may differ): join, HISTORY_TABLE_COLUMNS.map
- L74 · getAlertCutoffMinute calls (conditional paths may differ): Math.floor, Math.max
- L79 · parseAllowedOrigins calls (conditional paths may differ): corsAllowedOrigins.trim, filter, map, corsAllowedOrigins.split, o.trim
- L89 · toFiniteNumber calls (conditional paths may differ): Number, Number.isFinite
- L94 · normalizeMetricTimestamp calls (conditional paths may differ): Date.now, Number, Number.isFinite
- L100 · toPublicIpReachability calls (conditional paths may differ): toLowerCase, trim, String
- L105 · normalizeConfigSchema calls (conditional paths may differ): trim, String, Number, Number.isInteger
- L112 · normalizeConfigMd5 calls (conditional paths may differ): toLowerCase, trim, String
- L124 · maskPublicIpFields calls (conditional paths may differ): Object.prototype.hasOwnProperty.call, ensureMaskedCopy, toPublicIpReachability, Array.isArray, maskPublicIpFields
- L151 · maskPublicIpSample calls (conditional paths may differ): maskPublicIpFields
- L165 · maskPublicIpUpdate calls (conditional paths may differ): Array.isArray, update.samples.map
- L173 · normalizeResourceAlertSample calls (conditional paths may differ): Array.isArray, normalizeMetricTimestamp, toFiniteNumber, Math.max, Math.floor
- L211 · normalizeThresholds calls (conditional paths may differ): Number, Number.isFinite, normalize
- L227 · normalizeResourceAlertMode calls (conditional paths may differ): toLowerCase, trim, String
- L233 · getMetricValue calls (conditional paths may differ): Number.isFinite
- L238 · summarizeMetric calls (conditional paths may differ): filter, samples.map, getMetricValue, values.reduce, Math.min, Math.max
- L252 · getResourceAlertSampleSpan calls (conditional paths may differ): Array.isArray, Math.max
- L257 · getResourceAlertLatestTolerance calls (conditional paths may differ): Array.isArray, getResourceAlertSampleSpan, Math.max
- L263 · hasSufficientResourceAlertSamples calls (conditional paths may differ): Array.isArray, Math.ceil, Math.max, getResourceAlertSampleSpan
Environment references: env.DB · env.API_SECRET · env.CORS_ALLOWED_ORIGINS
- L24 · toFiniteMetricNumber calls (conditional paths may differ): Number, Number.isFinite
- L30 · hasOwnMetric calls (conditional paths may differ): Object.prototype.hasOwnProperty.call
- L34 · isPlainMetricObject calls (conditional paths may differ): Array.isArray
- L38 · hasDiskMetricsPayload calls (conditional paths may differ): isPlainMetricObject, hasOwnMetric, DISK_IO_METRIC_FIELDS.some, toFiniteMetricNumber
- L60 · normalizeDiskMetrics calls (conditional paths may differ): isPlainMetricObject, hasOwnMetric, Object.fromEntries, DISK_IO_METRIC_FIELDS.map, toFiniteMetricNumber
- L76 · createEmptyDiskMetricColumns calls (conditional paths may differ): Object.fromEntries, DISK_IO_METRIC_FIELDS.map
- L83 · flattenDiskMetrics calls (conditional paths may differ): hasDiskMetricsPayload, createEmptyDiskMetricColumns, normalizeDiskMetrics, Object.fromEntries, DISK_IO_METRIC_FIELDS.map
- L95 · attachDiskMetricsObject calls (conditional paths may differ): hasDiskMetricsPayload, normalizeDiskMetrics
- L115 · coerceNumericMetricFields calls (conditional paths may differ): Object.prototype.hasOwnProperty.call, Number, Number.isFinite, hasDiskMetricsPayload, normalizeDiskMetrics
- L151 · normalizeProbeMetric calls (conditional paths may differ): isDisabledProbeMetric
- L155 · normalizeProbeMetricRow calls (conditional paths may differ): Object.prototype.hasOwnProperty.call, normalizeProbeMetric
- L167 · mergeMetricsIntoServer calls (conditional paths may differ): normalizeProbeMetric, hasDiskMetricsPayload, normalizeDiskMetrics
- L10 · ensureServerOptimization calls (conditional paths may differ): getSettingByKey, all, db.prepare, columns.map, existingColumns.has, run, debug, clearServersListCache, saveSiteOptions, Number, bind
- L84 · getNextServerHistoryPartitionId calls (conditional paths may differ): getAllServers, filter, servers.map, Number, Number.isInteger, usedIds.has, debug
- L99 · padHistoryTimePart calls (conditional paths may differ): padStart, String
- L104 · normalizeHistoryTimestamp calls (conditional paths may differ): Date.now, Number, Number.isFinite
- L110 · formatHistoryTimeKey calls (conditional paths may differ): normalizeHistoryTimestamp, date.getUTCFullYear, debug, Number, join, padHistoryTimePart, date.getUTCMonth, date.getUTCDate, date.getUTCHours, date.getUTCMinutes, date.getUTCSeconds
- L130 · normalizeHistoryPartitionId calls (conditional paths may differ): Number, Number.isInteger
- L138 · buildHistoryId calls (conditional paths may differ): normalizeHistoryPartitionId, formatHistoryTimeKey
- L146 · getServerHistoryInfo calls (conditional paths may differ): find, getAllServers, debug, normalizeHistoryPartitionId, normalizeHistoryTimestamp
- L162 · getHistoryIdRange calls (conditional paths may differ): normalizeHistoryPartitionId, formatHistoryTimeKey
- L18 · buildSparseHistoryQuery calls (conditional paths may differ): filter, map, columns.split, column.trim, join, flatMap, toUpperCase, String, buildSampleJsonExpression, bindValues.push
- L12 · normalizeTrafficLimitGb calls (conditional paths may differ): parseFloat, Number.isFinite
- L18 · normalizePct calls (conditional paths may differ): parseInt, Number.isFinite, Math.max, Math.min
- L26 · normalizePctOrNull calls (conditional paths may differ): parseInt, Number.isFinite, Math.max, Math.min
- L34 · getTrafficUsageBytes calls (conditional paths may differ): parseFloat, Math.max
Build and deployment pipeline · 3 GitHub Actions workflows
Repository CI declarations, separate from runtime request processing. Job dependencies and conditions are shown as written; long commands are shortened with an ellipsis; a workflow file does not prove a recent successful run.
Triggers: push, workflow_dispatch
check · no job dependencies declared
- Shell command
if [ -n "$API_BASE" ] || [ "$EVENT_NAME" = "workflow_dispatch" ]; then echo "should_deploy=true" >> $GITHUB_OUTPUT else echo "should_deploy=false" >> $GITHUB_OUTPUT fi
build · after check
Condition: needs.check.outputs.should_deploy == 'true'
- actions/checkout@v4
actions/checkout@v4 - actions/setup-node@v4
actions/setup-node@v4 - Shell command
npm install - Build
npm run build:github-page - actions/upload-pages-artifact@v3
actions/upload-pages-artifact@v3
deploy · after build
Condition: needs.build.result == 'success'
- actions/deploy-pages@v4
actions/deploy-pages@v4
Triggers: push, workflow_dispatch
check · no job dependencies declared
- Shell command
if [ -n "$CF_ACCOUNT_ID" ] || [ "$EVENT_NAME" = "workflow_dispatch" ]; then echo "should_deploy=true" >> $GITHUB_OUTPUT else echo "should_deploy=false" >> $GITHUB_OUTPUT fi
Deploy · after check
Condition: needs.check.outputs.should_deploy == 'true'
- 📥 Checkout code
actions/checkout@v4 - 🔧 Setup Node.js
actions/setup-node@v4 - 📦 Install dependencies
npm install - 🏗️ Build frontend assets
npm run build:frontend - 📝 Create wrangler.toml
cat > wrangler.toml << 'WEOF' name = "server-monitor-pro" main = "src/index.js" compatibility_date = "2024-12-01" compatibility_flags = ["nodejs_compat"] # 定时任务配置(UTC) # - 每分钟:执行离线节点检测 # - 每小时:合并执行其他定时任务(每月1号数据清理、每月8号清理旧表、每天12点服务器到期检测) [triggers] crons = ["*/1 * * * *", "0 * * * *"] [assets] directory = "./dist" binding = "ASSETS" [[d1_databases]] binding = "DB" database_name = "server-monitor-db" database_id = "D1_PLACEHOLDER" # Durable Objects:实时指标广播中心 [[durable_objects.bindings]] name = "MET… - 🚀 Deploy to Cloudflare Workers
cloudflare/wrangler-action@v3Wrangler command: deploy - ✅ Deployment Success
echo "============================================" echo " 🎉 部署成功!" echo "============================================"Condition: success() - ❌ Deployment Failed
echo "============================================" echo " 💥 部署失败!请检查 Actions 日志" echo "============================================"Condition: failure()
Triggers: schedule, workflow_dispatch
Sync latest commits from upstream repo · no job dependencies declared
Condition: ${{ github.event.repository.fork }}
- Checkout target repo
actions/checkout@v3 - Sync upstream changes
aormsby/Fork-Sync-With-Upstream-action@v3.4 - Sync check
echo "[Error] 由于上游仓库的 workflow 文件变更,导致 GitHub 自动暂停了本次自动更新,请前往仓库页面手动执行 Sync Fork 操作。" echo "[Error] Due to a change in the workflow file of the upstream repository, GitHub has automatically suspended the scheduled automatic update. You need to manually sync your fork." exit 1Condition: failure()
deploy: npm run build:frontend && wrangler deploybuild: node scripts/build.jsbuild:frontend: node scripts/build.jsbuild:github-page: node scripts/build-github-page.js
Repository README
View original on GitHub ↗Full upstream document by @huilang-me · README.md · snapshot dfb9bf1
CF-Server-Monitor
基于 Cloudflare Workers、D1 和 Durable Objects 的轻量级多服务器监控面板。
项目简介
CF-Server-Monitor 是一个部署在 Cloudflare Workers 上的服务器监控系统。服务器端安装 Agent 后会单向上报指标到 Worker,数据写入 D1,并通过 Durable Objects + WebSocket 推送到前端,实现免费托管、低维护的实时监控。
支持主流 Linux 发行版、Alpine Linux、OpenWrt、macOS、群晖 DSM、飞牛 fnOS、Windows 等系统,并提供 Docker 镜像部署方式。
高安全性:Agent 仅单向上报指标,不提供 WebSSH、远程命令下发或主控通道;支持非 root 运行,可降低监控组件被利用后的影响范围。
目录
对比优势
相比传统主控式探针,CF-Server-Monitor 更适合低成本、低维护和安全优先的监控场景:
- 免费托管:面板、API、数据库和实时推送都运行在 Cloudflare 上,按免费额度友好设计;默认 60 秒上报间隔可支持约 60 台服务器,改为 120 秒后理论上可翻倍。
- 单向上报更安全:没有 WebSSH、没有远程命令下发、没有主控通道;Agent 只向 Worker 上报指标。
- 功能覆盖完整:实时指标、历史图表、地图展示、离线通知、资源告警、到期提醒、主题商店、多语言和移动端适配都已内置。
- 参数动态下发:后台可修改 Ping 节点、采集间隔、HTTP 上报间隔、WSS 上报间隔、统计网卡、流量重置日、上下行流量校正等参数,Agent 后续自动拉取并生效;Worker 地址、
API_SECRET和自动更新开关变更需要重新执行安装命令。 - 支持非 root 安装:支持
systemd --user的 Linux 可使用普通用户安装,探针文件写入~/.cf-probe/。 - 上报时间自动校准:Go Agent 会使用 Worker 响应中的 HTTP
Date头校正样本时间和boot_time,降低服务器本地时间错误对历史图表的影响;不会修改系统时间。
特性
| 模块 | 能力 |
|---|---|
| 实时监控 | CPU、GPU、内存、交换分区、磁盘、磁盘 IO、网络、连接数、进程数、负载、运行时间 |
| 历史数据 | 7 天历史图表、长时段采样、实时网速、月流量统计与校正 |
| 网络质量 | 电信、联通、移动、BGP 节点延迟与丢包率追踪;三网详情开启时首页从 D1 最近 2 小时抽样最多 20 个真实点并缓存 5 分钟 |
| 多视图前台 | 条形图、环形图、表格、地图视图,支持桌面端和移动端 |
| 管理后台 | 服务器增删改查、拖拽排序、隐藏服务器、导入导出、批量删除、数据库维护 |
| 多系统 Agent | 主流 Linux、Alpine Linux、OpenWrt、群晖 DSM、飞牛 fnOS、FreeBSD、macOS、Windows,支持 Docker 镜像部署;默认 Go 版本,保留 Shell/PowerShell 版本 |
| 实时推送 | Durable Objects + WebSocket,Agent 上报后前端即时刷新 |
| 告警通知 | 离线告警、恢复通知、到期提醒、资源负载告警 |
| 多语言 | 前端内置中文和英文切换;文档提供中文与英文入口 |
| 多站点 | 支持 GitHub Pages 静态前台和多个 Worker API 聚合展示 |
| 小组件 | 提供 iOS Scriptable 小组件脚本,适合移动端快速查看 |
| 安全控制 | API Secret、管理员密码、JWT、Turnstile、CORS、CSP 白名单 |
| 主题生态 | 内置主题、Mikus 模式、主题商店、第三方主题反代与预览 |
| 额度友好 | 按月表轮换、历史查询采样、缓存与限流设计,适配 Cloudflare 免费额度 |
系统架构
flowchart LR
Agent["Server Agent<br/>Go / Shell / PowerShell"] -->|"POST /update"| Worker["Cloudflare Worker"]
Worker --> D1["Cloudflare D1<br/>servers / settings / history"]
Worker <--> DO["Durable Object<br/>WebSocket broadcast"]
Worker --> Assets["Vue Dashboard<br/>Admin Panel"]
Browser["Browser / Mobile / Widget"] <--> Worker
核心数据流:
- 在管理后台添加服务器,复制安装命令。
- 目标服务器安装 Agent,按上报间隔向 Worker 发送指标。
- Worker 校验
API_SECRET,写入 D1,并通过 Durable Object 广播实时数据。 - 前台大盘、详情页、管理后台和 iOS 小组件读取同一套 API。
近期变化:
2.8.6:Added GitHub login, WSS frontend subscription with 250ms batch reporting, added SMTP notification channel, added monthly traffic threshold alert, removed legacy database compatibility, added Docker installation method.2.8.5:支持自定义 Ping 节点名称;增加ICMP模式;优化WSS响应逻辑;API接口优化;原皮前端优化;新增4个ping节点。2.8.4:新增 Agent WSS 上报和 WSS 开启时段,提升实时数据推送及时性,并允许非目标时段自动改用 POST 降低 Do 时长消耗;该能力要求 Agent 升级到v1.0.10+。新增账户Do用量展示,优化无前端订阅时的 Do 实时广播请求,降低空闲额度消耗。通知设置新增自定义 Webhook 渠道, 新增前端wss超时配置。2.8.3:新增磁盘 IO 统计,默认 Agent 切换为 Go 版本,新增服务器延迟与丢包率实时窗口。2.8.2:引入 Go Agent 支持。2.8.1:优化长时间历史查询 D1 读行,增加资源负载通知和主题商店接口优化。2.8.0:新增主题商店,支持一键切换第三方主题。2.7.x:重构数据库写入、月表轮换、通知模块、安全策略、服务器计费字段、导入导出和多项后台能力。
Go Agent 的完整更新记录见 cfsm-agent releases。
快速部署
前置要求
- Cloudflare 账户
- GitHub 账户
- 一个足够复杂的
API_SECRET,同时作为 Agent 上报密钥和初始后台密码
建议优先使用「Cloudflare Workers 连接 GitHub 仓库」或「GitHub Actions 自动部署」。一键部署适合快速体验,但后续同步更新不够方便。
方式一:Cloudflare Workers 连接 GitHub 仓库
这是推荐方式,适合希望通过 Cloudflare 控制台自动构建和重新部署的用户。
- Fork 本仓库到自己的 GitHub 账号。
- 进入 Cloudflare Dashboard,打开 Workers & Pages。
- 创建 Worker,并选择从 GitHub 仓库导入本项目。
- 构建命令填写
npm run build:frontend。 - 部署命令使用
npx wrangler deploy。 - 部署完成后,在 Worker 的 Variables and Secrets 中添加
API_SECRET。
图文教程:https://huilang.me/cf-server-monitor-setup/
方式二:GitHub Actions 自动部署
适合希望把部署流程完全托管在 GitHub Actions 的用户。
- Fork 本仓库。
- 在 Cloudflare 创建 D1 数据库,名称建议为
server-monitor-db。 - 复制 Cloudflare Account ID。
- 创建 Cloudflare API Token,至少需要 Workers 编辑和 D1 部署相关权限。
- 在 Fork 后的 GitHub 仓库中进入 Settings -> Secrets and variables -> Actions。
- 添加以下 Secrets。
| Secret | 必填 | 说明 |
|---|---|---|
CF_API_TOKEN |
是 | Cloudflare API Token |
CF_ACCOUNT_ID |
是 | Cloudflare Account ID |
D1_DATABASE_ID |
是 | D1 数据库 ID |
API_SECRET |
是 | Agent 上报密钥和初始后台密码 |
CORS_ALLOWED_ORIGINS |
否 | 允许跨域访问 API 的来源,多个用英文逗号分隔 |
推送到 main 分支会自动部署,也可以在 Actions 页面手动运行 Deploy to Cloudflare Workers 工作流。
方式三:一键部署
部署时请确认:
- Build command 使用
npm run build:frontend API_SECRET改为随机强密码,不要继续使用默认值- 登录后台后尽快修改管理员用户名和密码
一键部署不方便长期同步上游更新,正式使用更建议迁移到方式一或方式二。
首次使用
登录后台
部署成功后访问:
https://你的 Worker 域名/admin#/admin
默认凭据:
| 项目 | 默认值 |
|---|---|
| 用户名 | admin |
| 密码 | API_SECRET |
登录后建议立即修改后台用户名和密码。后台登录密码可以和 API_SECRET 分离;服务器 Agent 上报仍使用 Cloudflare 环境变量中的 API_SECRET。
添加服务器
- 进入
/admin#/admin。 - 在服务器管理中填写服务器名称。
- 点击添加服务器。
- 点击复制按钮,选择目标系统和 Agent 版本。
- 在目标服务器上执行复制出的安装命令。
建议优先使用后台生成的命令,因为它会自动带上服务器 ID、Worker URL、Secret、上报间隔、网络质量节点和网卡等参数。
Agent 参数与安全建议
V2.8.3 起默认使用独立项目 cfsm-agent,安装后服务名为 cf-probe。新增服务器后建议直接从管理后台复制安装命令,后台会按目标系统、服务器 ID、Worker 地址和当前参数生成完整命令。
完整安装路径、日志查看、状态检查和升级行为见 https://github.com/huilang-me/cfsm-agent。
常用参数
| 参数 | 说明 | 默认值 |
|---|---|---|
-id |
服务器唯一 ID | 必填 |
-secret |
Agent 上报密钥,需要等于 API_SECRET |
必填 |
-url |
Worker 上报地址 | 必填 |
-collect_interval |
本机采集间隔;0 表示不额外采样 |
0 |
-interval |
上报间隔,单位秒 | 60 |
-ct / -cu / -cm / -bd |
自定义网络质量测试节点,支持 host[:port] |
内置节点 |
-reset_day |
月流量重置日 | 1 |
-rx_correction |
下行月流量校正,单位 GB | 空 |
-tx_correction |
上行月流量校正,单位 GB | 空 |
-collect_interval 控制本机额外采集频率,-interval 控制上报频率。上报越频繁,Workers 请求和 D1 写入越多。
非 root 安装(推荐)
支持 systemd --user 的 Linux 环境建议使用非 root 安装。后台安装命令可选择“当前用户”或“专用 cfsm 用户”:当前用户会直接执行安装,专用用户则会自动创建并切换到 cfsm。文件保存在该用户的 ~/.cf-probe/。
普通用户安装后,如需退出登录仍保持服务运行,请由 root 执行:
loginctl enable-linger 用户名
卸载时请选择原来的安装用户。OpenWrt、Alpine/OpenRC、Synology DSM 等不支持 systemd --user 的环境,请使用对应系统命令。
Docker 部署
后台复制安装命令的目标系统支持选择 Docker,会生成如下容器命令(服务器 ID、Secret、Worker 地址已按当前服务器自动填充,镜像标签默认 latest,可在 Agent 版本栏自定义):
docker run -d --name cf-probe --restart=unless-stopped --network=host \
-v cf-probe-data:/data \
-e SERVER_ID=<服务器ID> -e SECRET='<API_SECRET>' -e WORKER_URL=https://<你的后台地址>/update \
ghcr.io/huilang-me/cfsm-agent:latest
采集间隔、上报间隔、Ping 节点、网卡、流量重置日、上下行校正等运行参数由 Agent 按 SERVER_ID 从后台动态拉取,无需在命令中重复指定;更换镜像标签即回退/升级到指定版本。卸载容器:
docker rm -f cf-probe && docker volume rm cf-probe-data
配置说明
Worker 环境变量
| 变量 | 必填 | 说明 |
|---|---|---|
API_SECRET |
是 | Agent 上报密钥;也是首次登录后台的默认密码 |
API_BASE |
否 | 前端请求的 Worker API 地址,多个用英文逗号分隔;用于多 Worker 聚合或前后端分离 |
CORS_ALLOWED_ORIGINS |
否 | 允许跨域访问 API 的来源,多个用英文逗号分隔 |
GitHub Pages 静态前台
项目支持把前台构建到 GitHub Pages,并通过远程 Worker API 聚合数据。相关工作流为 .github/workflows/deploy-github-page.yml。
| Secret | 说明 |
|---|---|
API_BASE |
Worker API 地址,多个用英文逗号分隔 |
TITLE |
静态前台标题 |
BACKGROUND_IMAGE |
背景图地址 |
CSP_STATIC |
额外静态资源 CSP 白名单 |
CSP_API |
额外 API / WebSocket CSP 白名单 |
构建命令:
npm run build:github-page
后台可配置项
| 分类 | 主要内容 |
|---|---|
| 站点设置 | 标题、背景、favicon、默认展示模式、默认外观、默认语言、三网详情、公开访问策略 |
| 服务器参数 | HTTP/WSS 上报间隔、采集间隔、Ping 节点、网卡、月流量、价格、到期时间、自动续费 |
| 安全设置 | 管理员账号密码、JWT Secret、Turnstile |
| 通知设置 | 离线告警、到期提醒、资源负载告警、测试通知 |
| 外观设置 | 自定义 CSS、<head>、CSP 白名单、Mikus 模式 |
| 数据库管理 | 升级数据库、清空历史数据 |
| Cloudflare 用量 | 查询 D1 行读写和 Workers 请求量 |
iOS Scriptable 小组件
项目提供 scripts/ios-scriptable-widget.js,可在 iPhone 桌面显示单台服务器状态。
使用方式:
- 在 iPhone 安装 Scriptable。
- 新建脚本,并放入
scripts/ios-scriptable-widget.js内容。 - 修改脚本顶部的
CONFIG.baseURL为你的站点地址。 - 添加 Scriptable 小组件并选择该脚本。
- 在小组件 Parameter 中填写服务器 ID,也可以写成
id:SERVER_ID。
小组件会显示在线状态、CPU、内存、磁盘、月流量、实时上下行速率和更新时间。iOS 会按系统策略决定实际刷新频率。
通知与告警
在管理后台 -> 全局设置 -> 通知 中配置。通知分为“内置渠道”、“SMTP 邮件”和“自定义 Webhook”三种渠道;选择自定义 Webhook 后,后端只会发送 Webhook,不会再调用内置渠道。
内置渠道
内置渠道通过 Bot Token 内容自动识别平台。
| 平台 | Bot Token 填写方式 | Chat ID |
|---|---|---|
| Telegram | BotFather 创建的 Bot Token | 用户、群组或频道 ID |
| 企业微信 | 群机器人 Webhook URL | 留空 |
| 飞书 | 群机器人 Webhook URL | 留空 |
| 钉钉 | 自定义机器人 Webhook URL | 留空 |
| OneBot / QQ | onebot:http://host/send_private_msg?... 或 send_group_msg |
用户 ID 或群 ID |
| Bark | https://api.day.app/xxxx/ 或 bark:https://example.com/xxxx/ |
留空 |
| Server 酱 | https://sctapi.ftqq.com/<SendKey>.send 或 server:https://example.com/<SendKey>.send |
留空 |
| WxPusher | https://wxpusher.zjiecode.com/api/send/message/[SPT_xxx]/Hello |
留空 |
| Gotify | https://gotify.example.com/message?token=xxx |
留空 |
SMTP 邮件
选择“SMTP 邮件”渠道后,在设置面板填写 SMTP 服务器、端口、加密方式、用户名、密码、发件人和收件人即可,无需手动拼接配置。后端会将其序列化为 smtp:// 前缀协议存入 tg_bot_token 字段,并通过 cloudflare-smtp(基于 cloudflare:sockets)直连邮件服务器发送纯文本邮件。
配置格式(前端自动生成,手动填写内置渠道 Bot Token 时亦可使用):
smtp://<用户名>:<密码>@<host>:<port>?from=<发件人>&to=<收件人1,收件人2>&secure=<auto|tls|starttls>
注意事项:
- Cloudflare Workers 永久封禁 25 端口出站,只能使用
465(隐式 TLS)或587(STARTTLS);secure缺省时按端口自动选择。 - 用户名、密码中的特殊字符需 URL 编码(例如
@写作%40),前端表单会自动处理。 - QQ 邮箱、163 邮箱等需使用 SMTP 授权码而非登录密码;发件人留空时默认等于用户名。
- 当前仅发送纯文本邮件(不支持 HTML、附件、抄送),发送失败会按
NOTIFICATION_MAX_RETRIES自动重试。 - 能否成功送达还取决于邮件服务商对 Cloudflare 出口 IP 及 SPF / DKIM 的校验策略。
自定义 Webhook
自定义 Webhook 支持 GET 和 POST:
POST:可选择JSON、x-www-form-urlencoded或Text。默认 JSON 请求体为title和content两个参数。GET:使用同一个参数配置追加到 URL query;参数可写 JSON 对象,也可写title={{emoji}} {{event}}&content={{notification}}这种 QueryString。- 请求头支持 JSON 对象或
Header: value多行文本。 - 发送测试通知会按当前通知模板渲染后发送,适合保存前验证平台格式。
默认 Webhook 参数:
{
"title": "{{emoji}} {{event}}",
"content": "{{notification}}"
}
默认通知模板:
{{emoji}}【CF Server Monitor】{{event}}
{{message}}
{{time}}
可用模板变量:
| 变量 | 说明 |
|---|---|
{{emoji}} |
事件图标:恢复/测试为 ✅,离线/告警为 ❌,到期/混合状态为 ⚠️ |
{{event}} |
事件名称,例如“节点离线告警”“资源负载恢复” |
{{client}} / {{clients}} |
本次通知涉及的服务器名称,多个服务器用逗号连接 |
{{count}} |
本次通知涉及的服务器数量;默认模板不显示,但可自定义加入 |
{{message}} |
通知详情列表 |
{{time}} |
按通知时区格式化的发送时间 |
{{notification}} |
应用通知模板后的完整内容,通常用于 Webhook 的 content |
{{title}} |
固定标题 💌 Cloudflare Server Monitor |
支持的告警类型:
- 离线告警:节点离线达到设定阈值后通知,恢复后发送恢复通知。
- 到期提醒:服务器到期前 1 到 7 天内,按通知时区和到期通知时间每天提醒,也可关闭。
- 资源负载告警:按 CPU、内存、磁盘、上下行速率等指标配置规则。
配置后请先点击发送测试通知,再保存配置。
安全建议
API Secret
- 使用随机强密码,不要包含容易被 Shell 或 URL 转义影响的特殊字符。
- 修改
API_SECRET后,需要重新部署 Worker,并在所有服务器上重新安装或更新 Agent 命令。 - 后台登录密码可以独立修改,建议不要长期和
API_SECRET保持一致。
JWT 与 WebSocket 认证
- 管理员登录成功后会签发 7 天有效期的 JWT;前端会用于后续管理请求,并设置
cfsm_authHttpOnly Cookie。 - 私有站点(
is_public !== 'true')会对/api/servers、/api/server、/api/history/all和/api/ws做登录校验;未授权的 WebSocket 不会转发到 Durable Object。 /api/ws支持三种 JWT 认证来源:Authorization: Bearer <token>、Cookie: cfsm_auth=<token>、查询参数token/auth_token/ws_token。- 浏览器原生 WebSocket 不能自定义
AuthorizationHeader,内置前端同域连接走cfsm_authCookie,跨域连接才在 URL 中追加token=<jwt>查询参数。 - 查询参数 token 可能出现在访问日志中,请只通过 HTTPS 使用,并避免把带 token 的 WebSocket URL 分享给他人。
- 后台可配置“前端 WSS 超时(分钟)”:默认
0,表示不因连接时长主动断开;设为正整数后,内置前端到时会断开实时订阅并弹窗让用户选择关闭或继续。
Turnstile
可在后台启用 Cloudflare Turnstile,用于降低公开 API 和登录入口被刷的风险。多站点模式下,如果多个站点都启用 Turnstile,请保持 Site Key 一致。
GitHub 登录
- 在 GitHub
Settings → Developer settings → OAuth Apps中创建 OAuth App。 - 在 CFSM 后台的“管理员登录设置”中填写 Client ID 和 Client Secret,保存配置。
- 将后台显示的
Authorization callback URL原样填入 GitHub OAuth App。 - 保持管理员密码登录状态,点击“绑定 GitHub 账号”并完成授权。系统会自动保存该账号不可变的 GitHub 数字 ID,此后仅该账号能够使用 GitHub 登录。
GitHub OAuth 配置和绑定结果与其他站点配置一样保存在 D1 的 site_options 中,不需要升级数据库结构。Client Secret 不会通过后台设置读取接口返回;再次保存时留空即可保留原值。重新绑定必须处于管理员登录状态,建议保留账号密码登录作为应急入口。
CORS
默认建议仅同源访问。如果需要独立前台或多站点聚合,在 CORS_ALLOWED_ORIGINS 中加入可信来源。
CSP
项目默认启用较保守的 Content Security Policy。第三方背景图、外部 CSS/JS、字体、图片和 WebSocket/API 域名需要加入后台 CSP 白名单后才会加载。
内置允许的常见来源包括 Cloudflare Turnstile、Cloudflare Analytics、Google Fonts、GitHub Raw 和若干公开 API。添加第三方脚本前请先确认来源可信。
主题与外观
项目内置默认主题,并支持:
- 深色 / 浅色显示
- 前台中文 / 英文切换
- 自定义背景图、favicon、CSS、
<head> - Mikus 模式
- 主题商店
- 第三方主题 GitHub tree 地址
- 管理员预览主题
第三方主题只反代主题仓库中的 index.html 与 assets/,管理后台仍使用内置主题。开发自定义主题请参考 theme-develop.md。
升级与维护
升级 Worker
如果通过 Fork 部署,同步上游仓库即可触发重新部署:
- 手动同步:GitHub 仓库页面点击 Sync fork -> Update branch。
- 自动同步:启用
Upstream Sync工作流,默认每天 UTC 00:00 检查上游更新。
一键部署方式建议重新部署到同一个项目,或迁移到 Fork + GitHub / Cloudflare 自动部署模式。
升级 Agent
Go 版本 Agent 会保留原配置,直接执行安装命令即可升级。
Linux / OpenWrt / Synology DSM / FreeBSD / macOS:
curl -fsSL https://raw.githubusercontent.com/huilang-me/cfsm-agent/main/install.sh | sh -s -- install
Windows 管理员 PowerShell:
$script = "$env:TEMP\install-cf-probe.ps1"
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/huilang-me/cfsm-agent/main/install.ps1" -OutFile $script -UseBasicParsing
PowerShell -ExecutionPolicy Bypass -File $script install
卸载 Agent
Go 版本:
curl -fsSL https://raw.githubusercontent.com/huilang-me/cfsm-agent/main/install.sh | sh -s -- uninstall
旧 Shell / PowerShell 版本已不再维护,下面命令仅用于清理历史 Shell / PowerShell 安装。请按当初安装时使用的系统脚本选择对应卸载命令;如果当前运行的是 Go Agent,请使用上面的 Go 卸载命令。
| 旧版系统脚本 | 卸载命令 |
|---|---|
| Linux / systemd | curl -sL https://你的 Worker 域名/install.sh | bash -s uninstall |
| Alpine / OpenRC | curl -sL https://你的 Worker 域名/install-alpine.sh | sh -s uninstall |
| OpenWrt / procd | curl -sL https://你的 Worker 域名/install-openwrt.sh | sh -s uninstall |
| macOS | curl -sL https://你的 Worker 域名/install-mac.sh | sudo bash -s uninstall |
| Synology DSM | curl -sL https://你的 Worker 域名/install-synology.sh | bash -s uninstall |
Windows 旧 PowerShell 版本:
irm https://你的 Worker 域名/cf-server-monitor.ps1 -OutFile cf-server-monitor.ps1
powershell -ExecutionPolicy Bypass -File .\cf-server-monitor.ps1 uninstall
Go 版本和旧 Shell / PowerShell 版本卸载脚本只清理各自安装的服务和文件。如果曾经从 Shell / PowerShell 切换到 Go,或反向切换过,请分别执行对应版本的卸载命令,避免残留旧服务、定时任务或配置文件。
数据库维护
后台 -> Database Management 提供:
- 升级数据库:补齐新版本字段和索引,不删除现有数据。
- 清空历史数据:删除历史监控记录,保留服务器列表和站点设置。
从旧版本升级到包含 GPU、磁盘 IO、丢包率或新历史结构的版本后,如果页面提示数据库字段缺失,请先执行升级数据库,再升级 Agent。
定时任务
wrangler.toml 中包含两个 Cron:
| Cron | 说明 |
|---|---|
*/1 * * * * |
每分钟检测离线节点、资源告警 |
0 * * * * |
每小时执行合并任务,包括月表轮换、旧表清理,并按通知时区/到期通知小时执行到期检测 |
本地开发
环境要求
- Node.js 18+
- npm
- Wrangler 4
常用命令
# 安装依赖
npm install
# 首次创建 D1 数据库
npx wrangler d1 create server-monitor-db
# 启动本地 Worker,默认 https://localhost:8787
npm run dev
# 单独启动前端 Vite,默认 http://localhost:5173
npm run dev:frontend
# 构建前端
npm run build:frontend
# 部署到 Cloudflare Workers
npm run deploy
# 测试历史查询
npm run test:history-query
# 测试 Agent 配置下发
npm run test:agent-config
本地 .env 至少需要:
API_SECRET=123456
测试数据
node test/generate-sql.js
wrangler d1 execute server-monitor-db --file=test/mock-data.sql
更多本地测试说明见 test/README.md。
定时任务
https://localhost:8787/cdn-cgi/handler/scheduled?cron=*/1+*+*+*+* // 每分钟执行一次(离线检测)
https://localhost:8787/cdn-cgi/handler/scheduled?cron=0+*+*+*+* // 每小时执行一次(合并任务)
https://localhost:8787/cdn-cgi/handler/scheduled?cron=0+0+*+*+0 // 每周执行一次(测试使用)
https://localhost:8787/cdn-cgi/handler/scheduled?cron=0+12+*+*+* // 每天12点执行一次(测试使用)
API 检查
node test/api-check.js
node test/api-check.js --base-url=http://localhost:8787 --api-secret=123456
node test/api-check.js --help
完整接口说明见 API.md。
项目结构
CF-Server-Monitor/
├── public/ # 安装脚本、系统图标、旗帜、静态资源
├── scripts/ # 构建脚本、GitHub Pages 构建、iOS 小组件
├── src/
│ ├── database/ # D1 表结构、索引、迁移
│ ├── durable/ # Durable Object 实时广播
│ ├── frontend/ # Vue 3 前台与管理后台
│ ├── handlers/ # Worker 路由处理
│ ├── middleware/ # 鉴权中间件
│ ├── services/ # 通知服务
│ └── utils/ # 缓存、CORS、CSP、指标处理、版本检查
├── test/ # 本地测试和模拟数据工具
├── API.md # REST / WebSocket API 文档
├── theme-develop.md # 第三方主题开发文档
├── wrangler.toml # 本地 Wrangler 配置
└── version.json # Worker / Agent 版本
常见问题
部署后返回 API_SECRET is required
说明 Worker 没有读取到环境变量。请在 Cloudflare Workers & Pages 的 Variables and Secrets 中删除旧值后重新添加 API_SECRET,保存并等待重新部署完成。
目前Cloudflare版本原因,可能需要先改动值保存一次,再改回去触发重新部署。
Agent 没有上报数据
先确认服务器可以访问 Worker URL。如需开启调试,可在安装参数后追加 -debug=1。不同系统的日志查看命令不固定,请参考 cfsm-agent 文档 中对应系统的状态与日志说明。
排查完成后移除 debug 参数并重新安装,避免日志持续增大。
如何更换 API_SECRET
在 Cloudflare 中修改 API_SECRET,重新部署 Worker,然后在所有服务器上重新复制并执行安装命令。使用 GitHub Actions 部署时,也要同步更新 GitHub Secret。
D1 免费额度够用吗
默认 60 秒上报间隔按约 60 台服务器设计;改为 120 秒后可以进一步降低写入。读取主要来自前端访问和历史查询,项目已通过缓存、采样和登录限制降低消耗。实际额度以 Cloudflare 控制台显示为准。
背景图没有显示
通常是 CSP 拦截了第三方图片资源。进入管理后台 -> 外观设置 -> CSP 静态文件域名白名单,添加背景图所在域名的 origin,例如背景图地址是 https://cdn.example.com/path/bg.webp,只填写 https://cdn.example.com。
如果背景图地址会跳转或经过 CDN 重定向,需要打开浏览器开发者工具查看最终加载的图片地址,并把最终地址对应的域名加入白名单。修改后保存配置并刷新页面。
忘记后台密码
进入 Cloudflare D1 数据库 server-monitor-db,打开 setting 表,编辑 site_options 的 password 字段。旧版 MD5 兼容值 e10adc3949ba59abbe56e057f20f883e 对应密码 123456,保存后可用该密码登录,再到后台重新设置强密码。
国内服务器无法上报
建议绑定自定义域名到 Worker。无法绑定时,可临时通过 hosts 指向可用的 Cloudflare CDN IP:
echo <CF_CDN_IP> <你的探针域名> | sudo tee -a /etc/hosts
Ping 结果异常或全是 1
检查服务器是否启用了代理。OpenWrt / 软路由环境中,部分代理插件可能影响延迟测试,可关闭代理或更换透明代理方案后再测试。
地图中的地区显示说明
前端会并列展示港澳台和国家/地区信息。地图基于中华人民共和国自然资源部标准地图制作,审图号:GS(2023)2767 号。
界面预览
深色风格
浅色风格
相关文档
- API.md:REST API、WebSocket、鉴权、错误码和数据结构
- https://github.com/huilang-me/cfsm-agent:Go 版本 Agent 配置、升级、日志与排障
- theme-develop.md:第三方主题开发
- test/README.md:本地模拟数据和测试流程
社区
- Telegram 群组:https://t.me/cfServerMonitor
- 在线演示:https://demo.huilang.me/
支持项目
如果这个项目对你有帮助,欢迎通过以下方式支持后续维护。
- 微信赞赏:扫码支持
致谢
- CF-Server-Monitor-Pro
- Cloudflare Workers
- Vue 3
- Vite
- Chart.js
- Leaflet
- 感谢 NodeSeek 和 LINUX DO 社区的支持与推广
许可证
MIT License
Frequently asked about CF-Server-Monitor
What is CF-Server-Monitor?+
CF-Server-Monitor is a self-hosted Netdata Cloud alternative built on the Cloudflare developer platform. Host a server-metrics dashboard and alerting control plane on Cloudflare.
What does CF-Server-Monitor replace?+
CF-Server-Monitor is listed as an alternative to Netdata Cloud. Compare the features and tradeoffs before migrating.
What Cloudflare primitives does CF-Server-Monitor use?+
CF-Server-Monitor is built on D1, Durable Objects, Workers.
How much does CF-Server-Monitor cost to run?+
The reviewed Cloudflare deployment is eligible for Free-plan allowances for the stated small workload and feature scope. Usage limits, CPU, required account setup and separate services apply. This assessment covers only the Cloudflare dashboard/control plane; existing monitored hosts and their agents are required and billed separately. Use SQLite-backed MetricsBroadcaster as declared; persistent WebSocket duration and agent reporting frequency must stay within Durable Object allowances. Reduce reporting frequency and retention as needed to stay within Worker and D1 quotas; no verified server-count guarantee. Workers Free dynamic requests are shared across this account (100,000/day), with 10 ms CPU per invocation; workload fit is conditional and has not been measured. D1 Free allowance: 5 million rows read/day, 100,000 rows written/day and 5 GB total storage; unindexed scans and history retention consume quota. Only SQLite Durable Objects qualify for Workers Free. Keep DO requests below 100,000/day, active duration below 13,000 GB-s/day and SQLite storage/operations inside the captured allowances. Check current Cloudflare pricing before deploying.
Is CF-Server-Monitor open source?+
The upstream repository declares the MIT license. Read its terms at https://raw.githubusercontent.com/huilang-me/CF-Server-Monitor/dfb9bf19c23479398c3835c5f96328364e194c85/LICENSE. Source code and contributor credit are available at https://github.com/huilang-me/CF-Server-Monitor.

Discussion · 0
sign in to comment →