diff --git a/.gemini b/.gemini deleted file mode 120000 index 9b617313..00000000 --- a/.gemini +++ /dev/null @@ -1 +0,0 @@ -.agent \ No newline at end of file diff --git a/cmd/agent/main.go b/cmd/agent/main.go index dec1cb3a..6070a3bd 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -1,3 +1,4 @@ +// Command agent runs the OpenFlare edge agent daemon. package main import ( @@ -95,8 +96,8 @@ func main() { heartbeatService := heartbeat.New(client) updateService := updater.New() runner := &agent.Runner{ - Config: cfg, - StateStore: stateStore, + Config: cfg, + StateStore: stateStore, HeartbeatCycle: &heartbeat.Cycle{ Config: cfg, StateStore: stateStore, @@ -112,7 +113,6 @@ func main() { } ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) - defer stop() geoIPUpdater := &geoipupdate.Updater{ MMDBPath: cfg.MMDBPath, DownloadURL: cfg.MMDBDownloadURL, @@ -123,7 +123,9 @@ func main() { if err = runner.Run(ctx); err != nil && err != context.Canceled { slog.Error("agent process exited with error", "error", err) + stop() os.Exit(1) } + stop() slog.Info("agent process stopped") } diff --git a/cmd/flared/main.go b/cmd/flared/main.go index bfc4d9df..679d1132 100644 --- a/cmd/flared/main.go +++ b/cmd/flared/main.go @@ -1,3 +1,4 @@ +// Command flared runs the OpenFlare tunnel client daemon. package main import ( @@ -41,7 +42,7 @@ func main() { frpcManager := frpc.NewManager(cfg) _ = frpcManager.LoadState() - slog.Info("detected frpc version", "version", frpcManager.GetVersion()) + slog.Info("detected frpc version", "version", frpcManager.GetVersion(context.Background())) httpClient := httpclient.New(cfg.ServerURL, cfg.InitialAuthToken(), cfg.RequestTimeout.Duration()) wsClient := wsclient.New(cfg.ServerURL, cfg.InitialAuthToken(), cfg.RequestTimeout.Duration()) @@ -52,20 +53,21 @@ func main() { runner := &flared.Runner{ Config: cfg, FrpcManager: frpcManager, - HttpClient: httpClient, + HTTPClient: httpClient, WebSocketService: wsClient, HeartbeatService: heartbeatService, SyncService: syncService, } ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) - defer stop() slog.Info("flared process started") if err := runner.Run(ctx); err != nil && err != context.Canceled { slog.Error("flared process exited with error", "error", err) + stop() os.Exit(1) } + stop() slog.Info("flared process stopped") } diff --git a/cmd/relay/main.go b/cmd/relay/main.go index 1bc24321..083aba2a 100644 --- a/cmd/relay/main.go +++ b/cmd/relay/main.go @@ -1,3 +1,4 @@ +// Command relay runs the OpenFlare relay node daemon. package main import ( @@ -44,7 +45,7 @@ func main() { frpsManager := frps.NewManager(cfg.FrpsPath, cfg.DataDir, cfg.InitialAuthToken()) - slog.Info("detected frps version", "version", frpsManager.GetVersion()) + slog.Info("detected frps version", "version", frpsManager.GetVersion(context.Background())) httpClient := httpclient.New(cfg.ServerURL, cfg.InitialAuthToken(), cfg.RequestTimeout.Duration()) wsClient := wsclient.New(cfg.ServerURL, cfg.InitialAuthToken(), cfg.RequestTimeout.Duration()) @@ -53,19 +54,20 @@ func main() { Config: cfg, StateStore: stateStore, FrpsManager: frpsManager, - HttpClient: httpClient, + HTTPClient: httpClient, WebSocketService: wsClient, HeartbeatService: heartbeat.New(httpClient, frpsManager, cfg, stateStore), } ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) - defer stop() slog.Info("relay process started") if err := runner.Run(ctx); err != nil && err != context.Canceled { slog.Error("relay process exited with error", "error", err) + stop() os.Exit(1) } + stop() slog.Info("relay process stopped") } diff --git a/data/agent/etc/nginx/conf.d/openflare_routes.conf b/data/agent/etc/nginx/conf.d/openflare_routes.conf deleted file mode 100644 index 7f9a5cda..00000000 --- a/data/agent/etc/nginx/conf.d/openflare_routes.conf +++ /dev/null @@ -1,25 +0,0 @@ -# This file is generated by OpenFlare. Do not edit manually. -upstream backend_local_1 { - server flare.arctel.net:443 max_fails=3 fail_timeout=10s; - keepalive 128; -} - -server { - listen 80; - server_name a.com; - set $openflare_waf_site "local"; - access_by_lua_file /data/etc/nginx/lua/waf/check.lua; - location / { - proxy_set_header Host $host; - proxy_ssl_server_name on; - proxy_ssl_name "flare.arctel.net"; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_http_version 1.1; - proxy_set_header Connection $connection_upgrade; - proxy_set_header Upgrade $http_upgrade; - proxy_pass https://backend_local_1; - } -} - diff --git a/data/agent/etc/nginx/lua/init.lua b/data/agent/etc/nginx/lua/init.lua deleted file mode 100644 index cdb98000..00000000 --- a/data/agent/etc/nginx/lua/init.lua +++ /dev/null @@ -1,6 +0,0 @@ -local dict = ngx.shared.openflare_observability -if not dict then - return -end - -return diff --git a/data/agent/etc/nginx/lua/log.lua b/data/agent/etc/nginx/lua/log.lua deleted file mode 100644 index 20daf1a1..00000000 --- a/data/agent/etc/nginx/lua/log.lua +++ /dev/null @@ -1,82 +0,0 @@ -local dict = ngx.shared.openflare_observability -if not dict then - return -end - -local request_uri = tostring(ngx.var.uri or "") -if request_uri == "/openflare/observability" or request_uri == "/openflare/stub_status" then - return -end - -local ttl = 7200 -local now = ngx.time() -local window_size = 60 -local window_start = now - (now % window_size) - -local function ensure_counter(key) - dict:add(key, 0, ttl) -end - -local function incr(key, delta) - ensure_counter(key) - local value, err = dict:incr(key, delta) - if not value and err == "not found" then - dict:set(key, delta, ttl) - end -end - -local function remember_value(list_key, marker_key, value) - if value == "" then - return - end - if not dict:add(marker_key, 1, ttl) then - return - end - local existing = dict:get(list_key) - if not existing or existing == "" then - dict:set(list_key, value, ttl) - return - end - dict:set(list_key, existing .. "\n" .. value, ttl) -end - -local window_prefix = tostring(window_start) -incr("request_count:" .. window_prefix, 1) - -local status = tostring(ngx.status or 0) -if status ~= "0" then - incr("status:" .. window_prefix .. ":" .. status, 1) - remember_value( - "status_keys:" .. window_prefix, - "status_marker:" .. window_prefix .. ":" .. status, - status - ) - if tonumber(status) and tonumber(status) >= 500 then - incr("error_count:" .. window_prefix, 1) - end -end - -local host = tostring(ngx.var.host or "") -if host ~= "" then - incr("domain:" .. window_prefix .. ":" .. host, 1) - remember_value( - "domain_keys:" .. window_prefix, - "domain_marker:" .. window_prefix .. ":" .. host, - host - ) -end - -local remote_addr = tostring(ngx.var.binary_remote_addr or ngx.var.remote_addr or "") -if remote_addr ~= "" and dict:add("visitor:" .. window_prefix .. ":" .. remote_addr, 1, ttl) then - incr("unique_visitor_count:" .. window_prefix, 1) -end - -local request_length = tonumber(ngx.var.request_length) or 0 -if request_length > 0 then - incr("openresty_rx_bytes:" .. window_prefix, request_length) -end - -local bytes_sent = tonumber(ngx.var.bytes_sent) or tonumber(ngx.var.body_bytes_sent) or 0 -if bytes_sent > 0 then - incr("openresty_tx_bytes:" .. window_prefix, bytes_sent) -end diff --git a/data/agent/etc/nginx/lua/observability/init.lua b/data/agent/etc/nginx/lua/observability/init.lua deleted file mode 100644 index cdb98000..00000000 --- a/data/agent/etc/nginx/lua/observability/init.lua +++ /dev/null @@ -1,6 +0,0 @@ -local dict = ngx.shared.openflare_observability -if not dict then - return -end - -return diff --git a/data/agent/etc/nginx/lua/observability/log.lua b/data/agent/etc/nginx/lua/observability/log.lua deleted file mode 100644 index 20daf1a1..00000000 --- a/data/agent/etc/nginx/lua/observability/log.lua +++ /dev/null @@ -1,82 +0,0 @@ -local dict = ngx.shared.openflare_observability -if not dict then - return -end - -local request_uri = tostring(ngx.var.uri or "") -if request_uri == "/openflare/observability" or request_uri == "/openflare/stub_status" then - return -end - -local ttl = 7200 -local now = ngx.time() -local window_size = 60 -local window_start = now - (now % window_size) - -local function ensure_counter(key) - dict:add(key, 0, ttl) -end - -local function incr(key, delta) - ensure_counter(key) - local value, err = dict:incr(key, delta) - if not value and err == "not found" then - dict:set(key, delta, ttl) - end -end - -local function remember_value(list_key, marker_key, value) - if value == "" then - return - end - if not dict:add(marker_key, 1, ttl) then - return - end - local existing = dict:get(list_key) - if not existing or existing == "" then - dict:set(list_key, value, ttl) - return - end - dict:set(list_key, existing .. "\n" .. value, ttl) -end - -local window_prefix = tostring(window_start) -incr("request_count:" .. window_prefix, 1) - -local status = tostring(ngx.status or 0) -if status ~= "0" then - incr("status:" .. window_prefix .. ":" .. status, 1) - remember_value( - "status_keys:" .. window_prefix, - "status_marker:" .. window_prefix .. ":" .. status, - status - ) - if tonumber(status) and tonumber(status) >= 500 then - incr("error_count:" .. window_prefix, 1) - end -end - -local host = tostring(ngx.var.host or "") -if host ~= "" then - incr("domain:" .. window_prefix .. ":" .. host, 1) - remember_value( - "domain_keys:" .. window_prefix, - "domain_marker:" .. window_prefix .. ":" .. host, - host - ) -end - -local remote_addr = tostring(ngx.var.binary_remote_addr or ngx.var.remote_addr or "") -if remote_addr ~= "" and dict:add("visitor:" .. window_prefix .. ":" .. remote_addr, 1, ttl) then - incr("unique_visitor_count:" .. window_prefix, 1) -end - -local request_length = tonumber(ngx.var.request_length) or 0 -if request_length > 0 then - incr("openresty_rx_bytes:" .. window_prefix, request_length) -end - -local bytes_sent = tonumber(ngx.var.bytes_sent) or tonumber(ngx.var.body_bytes_sent) or 0 -if bytes_sent > 0 then - incr("openresty_tx_bytes:" .. window_prefix, bytes_sent) -end diff --git a/data/agent/etc/nginx/lua/observability/read.lua b/data/agent/etc/nginx/lua/observability/read.lua deleted file mode 100644 index fe15ecc4..00000000 --- a/data/agent/etc/nginx/lua/observability/read.lua +++ /dev/null @@ -1,45 +0,0 @@ -local cjson = require "cjson.safe" - -local dict = ngx.shared.openflare_observability -if not dict then - ngx.status = ngx.HTTP_SERVICE_UNAVAILABLE - ngx.say(cjson.encode({ message = "shared dict unavailable" })) - return -end - -local now = ngx.time() -local window_size = 60 -local window_start = now - (now % window_size) -local current_window = tostring(window_start) - -local function read_counter(key) - return tonumber(dict:get(key) or 0) or 0 -end - -local function read_map(window_id, prefix, list_key) - local result = {} - local raw = dict:get(list_key .. ":" .. window_id) - if not raw or raw == "" then - return result - end - for value in string.gmatch(raw, "[^\n]+") do - result[value] = read_counter(prefix .. ":" .. window_id .. ":" .. value) - end - return result -end - -local payload = { - window_started_at_unix = window_start, - window_ended_at_unix = now, - request_count = read_counter("request_count:" .. current_window), - error_count = read_counter("error_count:" .. current_window), - unique_visitor_count = read_counter("unique_visitor_count:" .. current_window), - status_codes = read_map(current_window, "status", "status_keys"), - top_domains = read_map(current_window, "domain", "domain_keys"), - source_countries = {}, - openresty_rx_bytes = read_counter("openresty_rx_bytes:" .. current_window), - openresty_tx_bytes = read_counter("openresty_tx_bytes:" .. current_window) -} - -ngx.header.content_type = "application/json" -ngx.say(cjson.encode(payload)) diff --git a/data/agent/etc/nginx/lua/pow/challenge.lua b/data/agent/etc/nginx/lua/pow/challenge.lua deleted file mode 100644 index c793c6f1..00000000 --- a/data/agent/etc/nginx/lua/pow/challenge.lua +++ /dev/null @@ -1,134 +0,0 @@ -local cjson = require "cjson.safe" - -local pow_config_dict = ngx.shared.openflare_pow_config -local pow_challenges = ngx.shared.openflare_pow_challenges - -local function generate_entropy() - local pieces = { - tostring(ngx.now()), - tostring(ngx.worker.pid()), - tostring(math.random()), - ngx.var.remote_addr or "", - ngx.var.http_user_agent or "", - ngx.var.request_id or "", - } - return table.concat(pieces, ":") -end - -local args = ngx.req.get_uri_args() -local host = args["host"] or ngx.var.host or "" -local redir = args["redir"] or "" - -local site = ngx.var.openflare_waf_site or "" -if site == "" then - site = host -end - -local config_raw = pow_config_dict:get(site) -if not config_raw then - ngx.status = 403 - ngx.say("PoW not configured for this site") - return -end - -local ok, route_config = pcall(cjson.decode, config_raw) -if not ok or not route_config or not route_config.enabled then - ngx.status = 403 - ngx.say("PoW not enabled for this site") - return -end - -local config = route_config.config or {} -local difficulty = config.difficulty or 4 -local algorithm = config.algorithm or "fast" -local challenge_ttl = config.challenge_ttl or 300 -local session_ttl = config.session_ttl or 600 - --- Generate challenge data without depending on ngx.random_bytes, which is not --- available in every OpenResty runtime build. -local entropy = generate_entropy() -local challenge_id = ngx.md5(entropy .. ":id") -local challenge_data = ngx.md5(entropy .. ":data-a") .. ngx.md5(entropy .. ":data-b") - --- Store challenge -local challenge_info = cjson.encode({ - data = challenge_data, - difficulty = difficulty, - host = host, - redir = redir, - session_ttl = session_ttl -}) -pow_challenges:set(challenge_id, challenge_info, challenge_ttl) - -local static_prefix = "/.within.website/x/cmd/anubis/static/" -local accept_lang = ngx.var.http_accept_language or "" -local lang = "en" -if string.find(accept_lang, "zh") then - lang = "zh-CN" -end - -local t_title = "Making sure you're not a bot!" -local t_status = "Loading..." -local t_protected = "This site is protected by a Proof-of-Work challenge. Your browser will solve a small puzzle before the upstream response is shown." -local t_why = "Why am I seeing this?" -local t_why_desc = "OpenFlare is asking your browser to complete a lightweight computation to distinguish normal browser traffic from automated abuse. This should finish automatically." -local t_noscript = "JavaScript is required to pass this verification. Please enable JavaScript and reload." - -if lang == "zh-CN" then - t_title = "正在确认你是不是机器人!" - t_status = "加载中..." - t_protected = "本网站受工作量证明(Proof-of-Work)挑战保护。在显示源站响应之前,您的浏览器将解决一个微型谜题。" - t_why = "为什么我会看到这个?" - t_why_desc = "OpenFlare 正在要求您的浏览器完成一项轻量级计算,以区分正常的浏览器流量和自动化的恶意请求。这应该会自动完成。" - t_noscript = "很遗憾,您必须启用 JavaScript 才能通过这项验证。请开启 JavaScript 并刷新页面。" -end - -ngx.header.content_type = "text/html; charset=utf-8" -ngx.say([[ - - - - - -]] .. t_title .. [[ - - - - - - - - -
-

]] .. t_title .. [[

-
- -

]] .. t_status .. [[

-

]] .. t_protected .. [[

-
-
-]] .. t_why .. [[ -

]] .. t_why_desc .. [[

-
- -
-
- - -]]) diff --git a/data/agent/etc/nginx/lua/pow/check.lua b/data/agent/etc/nginx/lua/pow/check.lua deleted file mode 100644 index 02c45284..00000000 --- a/data/agent/etc/nginx/lua/pow/check.lua +++ /dev/null @@ -1,10 +0,0 @@ -local source = debug.getinfo(1, "S").source or "" -if string.sub(source, 1, 1) == "@" then - local script_path = string.sub(source, 2) - local base_dir = string.match(script_path, "^(.*)/pow/[^/]+%.lua$") - if base_dir and base_dir ~= "" and not string.find(package.path, base_dir, 1, true) then - package.path = base_dir .. "/?.lua;" .. base_dir .. "/?/init.lua;" .. package.path - end -end - -return require("pow.runtime").check() diff --git a/data/agent/etc/nginx/lua/pow/policy.lua b/data/agent/etc/nginx/lua/pow/policy.lua deleted file mode 100644 index 9da25854..00000000 --- a/data/agent/etc/nginx/lua/pow/policy.lua +++ /dev/null @@ -1,94 +0,0 @@ -local M = {} - -local function match_ip(remote_ip, ips) - if not ips or #ips == 0 then return false end - for _, ip in ipairs(ips) do - if ip == remote_ip then - return true - end - end - return false -end - -local function match_cidr(remote_ip, cidrs) - if not cidrs or #cidrs == 0 then return false end - for _, cidr in ipairs(cidrs) do - local m, err = ngx.re.match(cidr, "^(\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{1,3})/(\\\\d{1,2})$") - if m then - local mask_bits = tonumber(m[2]) - if mask_bits and mask_bits >= 0 and mask_bits <= 32 then - local function ip_to_num(ip_str) - local parts = {} - for part in string.gmatch(ip_str, "%d+") do - parts[#parts+1] = tonumber(part) or 0 - end - if #parts ~= 4 then return 0 end - return parts[1]*16777216 + parts[2]*65536 + parts[3]*256 + parts[4] - end - local remote_num = ip_to_num(remote_ip) - local net_num = ip_to_num(m[1]) - if mask_bits == 0 then - return true - end - local mask = math.floor(2^(32 - mask_bits)) - mask = 4294967296 - mask - if bit.band(remote_num, mask) == bit.band(net_num, mask) then - return true - end - end - end - end - return false -end - -local function match_path(uri, patterns) - if not patterns or #patterns == 0 then return false end - for _, pattern in ipairs(patterns) do - local ok, match = pcall(ngx.re.match, uri, "^" .. ngx.re.gsub(pattern, "([%^%$%(%)%%%.%[%]%+%-%?])", function(c) - if c == "*" then return ".*" end - return "%" .. c - end) .. "$", "i") - if ok and match then - return true - end - end - return false -end - -local function match_path_regex(uri, patterns) - if not patterns or #patterns == 0 then return false end - for _, pattern in ipairs(patterns) do - local ok, match = pcall(ngx.re.match, uri, pattern) - if ok and match then - return true - end - end - return false -end - -local function match_ua(ua, patterns) - if not patterns or #patterns == 0 then return false end - for _, pattern in ipairs(patterns) do - if ua and string.find(ua, pattern, 1, true) then - return true - end - end - return false -end - -function M.match_any(remote_ip, ua, uri, list) - if not list then return false end - if match_ip(remote_ip, list.ips) then return true end - if match_cidr(remote_ip, list.ip_cidrs) then return true end - if match_path(uri, list.paths) then return true end - if match_path_regex(uri, list.path_regexes) then return true end - if match_ua(ua, list.user_agents) then return true end - return false -end - -function M.has_entries(list) - if not list then return false end - return (#(list.ips or {}) + #(list.ip_cidrs or {}) + #(list.paths or {}) + #(list.path_regexes or {}) + #(list.user_agents or {})) > 0 -end - -return M diff --git a/data/agent/etc/nginx/lua/pow/runtime.lua b/data/agent/etc/nginx/lua/pow/runtime.lua deleted file mode 100644 index 20817c7e..00000000 --- a/data/agent/etc/nginx/lua/pow/runtime.lua +++ /dev/null @@ -1,182 +0,0 @@ -local _M = {} - -function _M.check() -local source = debug.getinfo(1, "S").source or "" -if string.sub(source, 1, 1) == "@" then - local script_path = string.sub(source, 2) - local base_dir = string.match(script_path, "^(.*)/pow/[^/]+%.lua$") - if base_dir and base_dir ~= "" and not string.find(package.path, base_dir, 1, true) then - package.path = base_dir .. "/?.lua;" .. base_dir .. "/?/init.lua;" .. package.path - end -end - -local cjson = require "cjson.safe" -local policy = require "pow.policy" - -local pow_config_dict = ngx.shared.openflare_pow_config -local pow_sessions = ngx.shared.openflare_pow_sessions - -local function session_cookie(value, ttl) - local cookie = "__openflare_pow=" .. value .. "; Path=/; HttpOnly; SameSite=Lax; Max-Age=" .. tostring(ttl) - if ngx.var.scheme == "https" then - cookie = cookie .. "; Secure" - end - return cookie -end - --- Lazy-load pow_config from file; reload when content changes -local function load_pow_config() - local config_paths = { - "/data/etc/openflare/waf_config.json", - "/etc/nginx/openflare-lua/waf_config.json", - "/usr/local/openresty/nginx/conf/waf_config.json" - } - for _, config_path in ipairs(config_paths) do - local f = io.open(config_path, "r") - if f then - local content = f:read("*a") - f:close() - local current_hash = ngx.md5(content or "") - - if current_hash == pow_config_dict:get("_config_hash") then - return - end - - -- Clear old domain/site entries - local old_keys = pow_config_dict:get("_domain_keys") - if old_keys then - for domain in string.gmatch(old_keys, "[^\n]+") do - pow_config_dict:delete(domain) - end - end - - local domain_keys = {} - if content and content ~= "" and content ~= "{}" then - local ok, decoded = pcall(cjson.decode, content) - if ok and decoded and decoded.rule_groups and decoded.site_rule_groups then - -- Build rule groups map (group ID -> PoWConfig) - local groups = {} - for _, group in ipairs(decoded.rule_groups) do - if group.pow_enabled then - groups[tostring(group.id)] = group.pow_config - end - end - -- Build site name to pow_config map - for site, group_ids in pairs(decoded.site_rule_groups) do - local pow_config = nil - -- Check custom group IDs first - for _, id in ipairs(group_ids) do - pow_config = groups[tostring(id)] - if pow_config then - break - end - end - -- If not found, check global group IDs - if not pow_config then - for _, group in ipairs(decoded.rule_groups) do - if group.is_global and group.pow_enabled then - pow_config = group.pow_config - break - end - end - end - if pow_config then - pow_config_dict:set(site, cjson.encode({enabled = true, config = pow_config}), 0) - domain_keys[#domain_keys+1] = site - end - end - end - end - - pow_config_dict:set("_domain_keys", table.concat(domain_keys, "\n"), 0) - pow_config_dict:set("_config_hash", current_hash, 0) - return - end - end -end - -load_pow_config() - -local host = ngx.var.host -if not host or host == "" then - return -end - -local site = ngx.var.openflare_waf_site or "" -if site == "" then - site = host -end - -local config_raw = pow_config_dict:get(site) -if not config_raw then - return -end - -local ok, route_config = pcall(cjson.decode, config_raw) -if not ok or not route_config then - return -end - -if not route_config.enabled then - return -end - -local config = route_config.config or {} -local session_ttl = config.session_ttl or 600 -local uri = ngx.var.uri or "" -local ua = ngx.var.http_user_agent or "" -local remote_ip = ngx.var.remote_addr or "" - --- Check whitelist: if matched, skip PoW -local whitelist = config.whitelist or {} -if policy.match_any(remote_ip, ua, uri, whitelist) then - return -end - --- Check blacklist: if matched, require PoW -local blacklist = config.blacklist or {} -local has_blacklist = policy.has_entries(blacklist) -local need_pow = false -if has_blacklist then - need_pow = policy.match_any(remote_ip, ua, uri, blacklist) -else - -- No blacklist means all non-whitelisted need PoW - need_pow = true -end - -if not need_pow then - return -end - --- Check valid session cookie -local cookie_val = ngx.var["cookie___openflare_pow"] -if cookie_val and cookie_val ~= "" then - local session_key = host .. ":" .. cookie_val - local session_data = pow_sessions:get(session_key) - if session_data then - pow_sessions:set(session_key, "1", session_ttl) - ngx.header["Set-Cookie"] = session_cookie(cookie_val, session_ttl) - return - end -end - --- If requesting the challenge API endpoints, let them through (handled by content_by_lua) -local anubis_api_prefix = "/.within.website/x/cmd/anubis/api/" -local anubis_static_prefix = "/.within.website/x/cmd/anubis/static/" -if string.sub(uri, 1, #anubis_api_prefix) == anubis_api_prefix then - return -end -if string.sub(uri, 1, #anubis_static_prefix) == anubis_static_prefix then - return -end - --- Render the challenge page through an internal redirect so the browser stays --- on the originally requested URL instead of seeing a 302 hop. -ngx.req.set_uri_args({ - redir = ngx.var.scheme .. "://" .. host .. uri .. (ngx.var.args and ("?" .. ngx.var.args) or ""), - host = host -}) -return ngx.exec("/.within.website/x/cmd/anubis/api/make-challenge") -end - -return _M diff --git a/data/agent/etc/nginx/lua/pow/static/css/static/geist.woff2 b/data/agent/etc/nginx/lua/pow/static/css/static/geist.woff2 deleted file mode 100644 index 6fd61c44..00000000 Binary files a/data/agent/etc/nginx/lua/pow/static/css/static/geist.woff2 and /dev/null differ diff --git a/data/agent/etc/nginx/lua/pow/static/css/static/iosevka-curly.woff2 b/data/agent/etc/nginx/lua/pow/static/css/static/iosevka-curly.woff2 deleted file mode 100644 index df9df40c..00000000 Binary files a/data/agent/etc/nginx/lua/pow/static/css/static/iosevka-curly.woff2 and /dev/null differ diff --git a/data/agent/etc/nginx/lua/pow/static/css/static/podkova.css b/data/agent/etc/nginx/lua/pow/static/css/static/podkova.css deleted file mode 100644 index efa1fd7a..00000000 --- a/data/agent/etc/nginx/lua/pow/static/css/static/podkova.css +++ /dev/null @@ -1,7 +0,0 @@ -@font-face { - font-family: "Podkova"; - font-style: normal; - font-weight: 400 800; - font-display: swap; - src: url("podkova.woff2") format("woff2"); -} diff --git a/data/agent/etc/nginx/lua/pow/static/css/static/podkova.woff2 b/data/agent/etc/nginx/lua/pow/static/css/static/podkova.woff2 deleted file mode 100644 index 508f97d3..00000000 Binary files a/data/agent/etc/nginx/lua/pow/static/css/static/podkova.woff2 and /dev/null differ diff --git a/data/agent/etc/nginx/lua/pow/static/css/xess.css b/data/agent/etc/nginx/lua/pow/static/css/xess.css deleted file mode 100644 index 67a0c4ba..00000000 --- a/data/agent/etc/nginx/lua/pow/static/css/xess.css +++ /dev/null @@ -1,149 +0,0 @@ -:root { - --body-sans-font: Geist, sans-serif; - --body-preformatted-font: Iosevka Curly Iaso, monospace; - --body-title-font: Podkova, serif; - - --background: #1d2021; - --text: #f9f5d7; - --text-selection: #d3869b; - --preformatted-background: #3c3836; - --link-foreground: #b16286; - --link-background: #282828; - --blockquote-border-left: 1px solid #bdae93; - - --progress-bar-outline: #b16286 solid 4px; - --progress-bar-fill: #b16286; -} -@media (prefers-color-scheme: light) { - :root { - --background: #f9f5d7; - --text: #1d2021; - --text-selection: #d3869b; - --preformatted-background: #ebdbb2; - --link-foreground: #b16286; - --link-background: #fbf1c7; - --blockquote-border-left: 1px solid #655c54; - } -} - -@font-face { - font-family: "Geist"; - font-style: normal; - font-weight: 100 900; - font-display: swap; - src: url("./static/geist.woff2") format("woff2"); -} - -@font-face { - font-family: "Podkova"; - font-style: normal; - font-weight: 400 800; - font-display: swap; - src: url("./static/podkova.woff2") format("woff2"); -} - -@font-face { - font-family: "Iosevka Curly"; - font-style: monospace; - font-display: swap; - src: url("./static/iosevka-curly.woff2") format("woff2"); -} - -main { - font-family: var(--body-sans-font); - max-width: 50rem; - padding: 2rem; - margin: auto; -} - -::selection { - background: var(--text-selection); -} - -body { - background: var(--background); - color: var(--text); -} - -body, -html { - height: 100%; - display: flex; - justify-content: center; - align-items: center; - margin-left: auto; - margin-right: auto; -} - -.centered-div { - text-align: center; -} - -#status { - font-variant-numeric: tabular-nums; -} - -.centered-div { - text-align: center; -} - -#status { - font-variant-numeric: tabular-nums; -} - -#progress { - display: none; - width: min(20rem, 90%); - height: 2rem; - border-radius: 1rem; - overflow: hidden; - margin: 1rem 0 2rem; - outline-offset: 2px; - outline: var(--progress-bar-outline); -} - -.bar-inner { - background-color: var(--progress-bar-fill); - height: 100%; - width: 0; - transition: width 0.25s ease-in; -} - -@media (prefers-reduced-motion: no-preference) { - .bar-inner { - transition: width 0.25s ease-in; - } -} - -pre { - background-color: var(--preformatted-background); - padding: 1em; - border: 0; - font-family: var(--body-preformatted-font); -} - -a, -a:active, -a:visited { - color: var(--link-foreground); - background-color: var(--link-background); -} - -h1, -h2, -h3, -h4, -h5 { - margin-bottom: 0.1rem; - font-family: var(--body-title-font); -} - -blockquote { - border-left: var(--blockquote-border-left); - margin: 0.5em 10px; - padding: 0.5em 10px; -} - -footer { - text-align: center; -} diff --git a/data/agent/etc/nginx/lua/pow/static/img/happy.webp b/data/agent/etc/nginx/lua/pow/static/img/happy.webp deleted file mode 100644 index 40b979da..00000000 Binary files a/data/agent/etc/nginx/lua/pow/static/img/happy.webp and /dev/null differ diff --git a/data/agent/etc/nginx/lua/pow/static/img/pensive.webp b/data/agent/etc/nginx/lua/pow/static/img/pensive.webp deleted file mode 100644 index e26faefe..00000000 Binary files a/data/agent/etc/nginx/lua/pow/static/img/pensive.webp and /dev/null differ diff --git a/data/agent/etc/nginx/lua/pow/static/img/reject.webp b/data/agent/etc/nginx/lua/pow/static/img/reject.webp deleted file mode 100644 index 17bc9398..00000000 Binary files a/data/agent/etc/nginx/lua/pow/static/img/reject.webp and /dev/null differ diff --git a/data/agent/etc/nginx/lua/pow/static/js/algorithms/index.mjs b/data/agent/etc/nginx/lua/pow/static/js/algorithms/index.mjs deleted file mode 100644 index 24a47324..00000000 --- a/data/agent/etc/nginx/lua/pow/static/js/algorithms/index.mjs +++ /dev/null @@ -1,32 +0,0 @@ -/* -@licstart The following is the entire license notice for the -JavaScript code in this page. - -Copyright (c) 2025 Xe Iaso - -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 permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -Includes code from https://github.com/aws/aws-sdk-js-crypto-helpers which is -used under the terms of the Apache 2 license. - -@licend The above is the entire license notice -for the JavaScript code in this page. -*/ -(()=>{var k=()=>navigator.hardwareConcurrency!==void 0?navigator.hardwareConcurrency:1;function n(c,b,w=5,e=null,g,u=Math.trunc(Math.max(k()/2,1))){console.debug("fast algo");let s="purejs";return window.isSecureContext&&(s="webcrypto"),(navigator.userAgent.includes("Firefox")||navigator.userAgent.includes("Goanna"))&&(console.log("Firefox detected, using pure-JS fallback"),s="purejs"),new Promise((p,l)=>{let m=`${c.basePrefix}/.within.website/x/cmd/anubis/static/js/worker/sha256-${s}.mjs?cacheBuster=${c.version}`,f=[],d=!1,a=()=>{console.log("PoW aborted"),i(),l(new DOMException("Aborted","AbortError"))},i=()=>{d||(d=!0,f.forEach(r=>r.terminate()),e?.removeEventListener("abort",a))};if(e!=null){if(e.aborted)return a();e.addEventListener("abort",a,{once:!0})}for(let r=0;r{typeof o.data=="number"?g?.(o.data):(i(),p(o.data))},t.onerror=o=>{i(),l(o)},t.postMessage({data:b,difficulty:w,nonce:r,threads:u}),f.push(t)}})}var P={fast:n,slow:n};})(); -//# sourceMappingURL=index.mjs.map diff --git a/data/agent/etc/nginx/lua/pow/static/js/main.mjs b/data/agent/etc/nginx/lua/pow/static/js/main.mjs deleted file mode 100644 index 7f496602..00000000 --- a/data/agent/etc/nginx/lua/pow/static/js/main.mjs +++ /dev/null @@ -1,32 +0,0 @@ -/* -@licstart The following is the entire license notice for the -JavaScript code in this page. - -Copyright (c) 2025 Xe Iaso - -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 permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -Includes code from https://github.com/aws/aws-sdk-js-crypto-helpers which is -used under the terms of the Apache 2 license. - -@licend The above is the entire license notice -for the JavaScript code in this page. -*/ -(()=>{var I=()=>navigator.hardwareConcurrency!==void 0?navigator.hardwareConcurrency:1;function _(e,n,s=5,o=null,i,u=Math.trunc(Math.max(I()/2,1))){console.debug("fast algo");let a="purejs";return window.isSecureContext&&(a="webcrypto"),(navigator.userAgent.includes("Firefox")||navigator.userAgent.includes("Goanna"))&&(console.log("Firefox detected, using pure-JS fallback"),a="purejs"),new Promise((E,x)=>{let M=`${e.basePrefix}/.within.website/x/cmd/anubis/static/js/worker/sha256-${a}.mjs?cacheBuster=${e.version}`,p=[],d=!1,b=()=>{console.log("PoW aborted"),h(),x(new DOMException("Aborted","AbortError"))},h=()=>{d||(d=!0,p.forEach(c=>c.terminate()),o?.removeEventListener("abort",b))};if(o!=null){if(o.aborted)return b();o.addEventListener("abort",b,{once:!0})}for(let c=0;c{typeof m.data=="number"?i?.(m.data):(h(),E(m.data))},g.onerror=m=>{h(),x(m)},g.postMessage({data:n,difficulty:s,nonce:c,threads:u}),p.push(g)}})}var j={fast:_,slow:_};var v=(e="",n={})=>{let s=new URL(e,window.location.href);return Object.entries(n).forEach(([o,i])=>s.searchParams.set(o,i)),s.toString()},L=e=>{let n=document.getElementById(e);return n===null?null:JSON.parse(n.textContent)},k=(e,n,s)=>v(`${s}/.within.website/x/cmd/anubis/static/img/${e}.webp`,{cacheBuster:n});var W=async()=>document.documentElement.lang,S=async e=>{let n=L("anubis_base_prefix");if(n!==null)try{return await(await fetch(`${n}/.within.website/x/cmd/anubis/static/locales/${e}.json`)).json()}catch(s){if(console.warn(`Failed to load translations for ${e}, falling back to English`),e!=="en")return await S("en");throw s}},C=()=>{let e=L("anubis_public_url");if(e!==null)return e&&window.location.href.startsWith(e)?new URLSearchParams(window.location.search).get("redir"):window.location.href},$={},D,A=async()=>{D=await W(),$=await S(D)},r=e=>$[`js_${e}`]||$[e]||e;(async()=>{await A();let e=[{name:"Web Workers",msg:r("web_workers_error"),value:window.Worker},{name:"Cookies",msg:r("cookies_error"),value:navigator.cookieEnabled}],n=document.getElementById("status"),s=document.getElementById("image"),o=document.getElementById("title"),i=document.getElementById("progress"),u=L("anubis_version"),a=L("anubis_base_prefix"),E=document.querySelector("details"),x=!1;E&&E.addEventListener("toggle",()=>{E.open&&(x=!0)});let M=({titleMsg:l,statusMsg:f,imageSrc:w})=>{o.innerHTML=l,n.innerHTML=f,s.src=w,i.style.display="none"};n.innerHTML=r("calculating");for(let{value:l,name:f,msg:w}of e)if(!l){M({titleMsg:`${r("missing_feature")} ${f}`,statusMsg:w,imageSrc:k("reject",u,a)});return}let{challenge:p,rules:d}=L("anubis_challenge"),b=j[d.algorithm];if(!b){M({titleMsg:r("challenge_error"),statusMsg:r("challenge_error_msg"),imageSrc:k("reject",u,a)});return}n.innerHTML=`${r("calculating_difficulty")} ${d.difficulty}, `,i.style.display="inline-block";let h=document.createTextNode(`${r("speed")} 0kH/s`);n.appendChild(h);let c=0,g=!1,m=Math.pow(16,-d.difficulty);try{let l=Date.now(),{hash:f,nonce:w}=await b({basePrefix:a,version:u},p.randomData,d.difficulty,null,t=>{let y=Date.now()-l;y-c>1e3&&(c=y,h.data=`${r("speed")} ${(t/y).toFixed(3)}kH/s`);let T=Math.pow(1-m,t),P=(1-Math.pow(T,2))*100;i["aria-valuenow"]=P,i.firstElementChild!==null&&(i.firstElementChild.style.width=`${P}%`),T<.1&&!g&&(n.append(document.createElement("br"),document.createTextNode(r("verification_longer"))),g=!0)}),H=Date.now();if(console.log({hash:f,nonce:w}),x){let y=function(){let T=C();window.location.replace(v(`${a}/.within.website/x/cmd/anubis/api/pass-challenge`,{id:p.id,response:f,nonce:w,redir:T,elapsedTime:H-l}))},t=document.getElementById("progress");t.style.display="flex",t.style.alignItems="center",t.style.justifyContent="center",t.style.height="2rem",t.style.borderRadius="1rem",t.style.cursor="pointer",t.style.background="#b16286",t.style.color="white",t.style.fontWeight="bold",t.style.outline="4px solid #b16286",t.style.outlineOffset="2px",t.style.width="min(20rem, 90%)",t.style.margin="1rem auto 2rem",t.innerHTML=r("finished_reading"),t.onclick=y,setTimeout(y,3e4)}else{let t=C();window.location.replace(v(`${a}/.within.website/x/cmd/anubis/api/pass-challenge`,{id:p.id,response:f,nonce:w,redir:t,elapsedTime:H-l}))}}catch(l){M({titleMsg:r("calculation_error"),statusMsg:`${r("calculation_error_msg")} ${l.message}`,imageSrc:k("reject",u,a)})}})();})(); -//# sourceMappingURL=main.mjs.map diff --git a/data/agent/etc/nginx/lua/pow/static/js/worker/sha256-purejs.mjs b/data/agent/etc/nginx/lua/pow/static/js/worker/sha256-purejs.mjs deleted file mode 100644 index e2c2d196..00000000 --- a/data/agent/etc/nginx/lua/pow/static/js/worker/sha256-purejs.mjs +++ /dev/null @@ -1,32 +0,0 @@ -/* -@licstart The following is the entire license notice for the -JavaScript code in this page. - -Copyright (c) 2025 Xe Iaso - -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 permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -Includes code from https://github.com/aws/aws-sdk-js-crypto-helpers which is -used under the terms of the Apache 2 license. - -@licend The above is the entire license notice -for the JavaScript code in this page. -*/ -(()=>{function _(o,i,e,n){function t(r){return r instanceof e?r:new e(function(a){a(r)})}return new(e||(e=Promise))(function(r,a){function p(c){try{f(n.next(c))}catch(l){a(l)}}function u(c){try{f(n.throw(c))}catch(l){a(l)}}function f(c){c.done?r(c.value):t(c.value).then(p,u)}f((n=n.apply(o,i||[])).next())})}function v(o,i){var e={label:0,sent:function(){if(r[0]&1)throw r[1];return r[1]},trys:[],ops:[]},n,t,r,a=Object.create((typeof Iterator=="function"?Iterator:Object).prototype);return a.next=p(0),a.throw=p(1),a.return=p(2),typeof Symbol=="function"&&(a[Symbol.iterator]=function(){return this}),a;function p(f){return function(c){return u([f,c])}}function u(f){if(n)throw new TypeError("Generator is already executing.");for(;a&&(a=0,f[0]&&(e=0)),e;)try{if(n=1,t&&(r=f[0]&2?t.return:f[0]?t.throw||((r=t.return)&&r.call(t),0):t.next)&&!(r=r.call(t,f[1])).done)return r;switch(t=0,r&&(f=[f[0]&2,r.value]),f[0]){case 0:case 1:r=f;break;case 4:return e.label++,{value:f[1],done:!1};case 5:e.label++,t=f[1],f=[0];continue;case 7:f=e.ops.pop(),e.trys.pop();continue;default:if(r=e.trys,!(r=r.length>0&&r[r.length-1])&&(f[0]===6||f[0]===2)){e=0;continue}if(f[0]===3&&(!r||f[1]>r[0]&&f[1]S)throw new Error("Cannot hash more than 2^53 - 1 bits");for(;n>0;)this.buffer[this.bufferLength++]=i[e++],n--,this.bufferLength===h&&(this.hashBuffer(),this.bufferLength=0)},o.prototype.digest=function(){if(!this.finished){var i=this.bytesHashed*8,e=new DataView(this.buffer.buffer,this.buffer.byteOffset,this.buffer.byteLength),n=this.bufferLength;if(e.setUint8(this.bufferLength++,128),n%h>=h-8){for(var t=this.bufferLength;t>>24&255,r[t*4+1]=this.state[t]>>>16&255,r[t*4+2]=this.state[t]>>>8&255,r[t*4+3]=this.state[t]>>>0&255;return r},o.prototype.hashBuffer=function(){for(var i=this,e=i.buffer,n=i.state,t=n[0],r=n[1],a=n[2],p=n[3],u=n[4],f=n[5],c=n[6],l=n[7],s=0;s>>17|y<<15)^(y>>>19|y<<13)^y>>>10;y=this.temp[s-15];var T=(y>>>7|y<<25)^(y>>>18|y<<14)^y>>>3;this.temp[s]=(A+this.temp[s-7]|0)+(T+this.temp[s-16]|0)}var m=(((u>>>6|u<<26)^(u>>>11|u<<21)^(u>>>25|u<<7))+(u&f^~u&c)|0)+(l+(E[s]+this.temp[s]|0)|0)|0,P=((t>>>2|t<<30)^(t>>>13|t<<19)^(t>>>22|t<<10))+(t&r^t&a^r&a)|0;l=c,c=f,f=u,u=p+m|0,p=a,a=r,r=t,t=m+P|0}n[0]+=t,n[1]+=r,n[2]+=a,n[3]+=p,n[4]+=u,n[5]+=f,n[6]+=c,n[7]+=l},o})();var d=o=>new TextEncoder().encode(o);var L=typeof Buffer<"u"&&Buffer.from?function(o){return Buffer.from(o,"utf8")}:d;function x(o){return o instanceof Uint8Array?o:typeof o=="string"?L(o):ArrayBuffer.isView(o)?new Uint8Array(o.buffer,o.byteOffset,o.byteLength/Uint8Array.BYTES_PER_ELEMENT):new Uint8Array(o)}function w(o){return typeof o=="string"?o.length===0:o.byteLength===0}var j=(function(){function o(i){this.secret=i,this.hash=new b,this.reset()}return o.prototype.update=function(i){if(!(w(i)||this.error))try{this.hash.update(x(i))}catch(e){this.error=e}},o.prototype.digestSync=function(){if(this.error)throw this.error;return this.outer?(this.outer.finished||this.outer.update(this.hash.digest()),this.outer.digest()):this.hash.digest()},o.prototype.digest=function(){return _(this,void 0,void 0,function(){return v(this,function(i){return[2,this.digestSync()]})})},o.prototype.reset=function(){if(this.hash=new b,this.secret){this.outer=new b;var i=U(this.secret),e=new Uint8Array(h);e.set(i);for(var n=0;nh){var e=new b;e.update(i),i=e.digest()}var n=new Uint8Array(h);return n.set(i),n}var B=o=>{let i=new j;return i.update(o),i.digest()};function I(o){return Array.from(o).map(i=>i.toString(16).padStart(2,"0")).join("")}addEventListener("message",async({data:o})=>{let{data:i,difficulty:e,threads:n}=o,t=o.nonce,r=t===0,a=0,p=Math.floor(e/2),u=e%2!==0;for(;;){let f=await B(i+t),c=new Uint8Array(f),l=!0;for(let s=0;s>4!==0&&(l=!1),l){let s=I(c);postMessage({hash:s,data:i,difficulty:e,nonce:t});return}t+=n,a++,t%1!==0&&(t=Math.trunc(t)),r&&(a&1023)===0&&postMessage(t)}});})(); -//# sourceMappingURL=sha256-purejs.mjs.map diff --git a/data/agent/etc/nginx/lua/pow/static/js/worker/sha256-webcrypto.mjs b/data/agent/etc/nginx/lua/pow/static/js/worker/sha256-webcrypto.mjs deleted file mode 100644 index 7d32193b..00000000 --- a/data/agent/etc/nginx/lua/pow/static/js/worker/sha256-webcrypto.mjs +++ /dev/null @@ -1,32 +0,0 @@ -/* -@licstart The following is the entire license notice for the -JavaScript code in this page. - -Copyright (c) 2025 Xe Iaso - -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 permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -Includes code from https://github.com/aws/aws-sdk-js-crypto-helpers which is -used under the terms of the Apache 2 license. - -@licend The above is the entire license notice -for the JavaScript code in this page. -*/ -(()=>{var h=new TextEncoder,y=async e=>{let s=h.encode(e);return await crypto.subtle.digest("SHA-256",s)},g=e=>e.reduce((s,a)=>s+a.toString(16).padStart(2,"0"),"");addEventListener("message",async({data:e})=>{let{data:s,difficulty:a,threads:d}=e,t=e.nonce,f=t===0,o=0,c=Math.floor(a/2),l=a%2!==0;for(;;){let u=await y(s+t),i=new Uint8Array(u),r=!0;for(let n=0;n>4!==0&&(r=!1),r){let n=g(i);postMessage({hash:n,data:s,difficulty:a,nonce:t});return}t+=d,o++,t%1!==0&&(t=Math.trunc(t)),f&&(o&1023)===0&&postMessage(t)}});})(); -//# sourceMappingURL=sha256-webcrypto.mjs.map diff --git a/data/agent/etc/nginx/lua/pow/static/locales/en.json b/data/agent/etc/nginx/lua/pow/static/locales/en.json deleted file mode 100644 index 4dffb969..00000000 --- a/data/agent/etc/nginx/lua/pow/static/locales/en.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "loading": "Loading...", - "why_am_i_seeing": "Why am I seeing this?", - "protected_by": "Protected by", - "protected_from": "From", - "made_with": "Made with ❤️ in 🇨🇦", - "mascot_design": "Mascot design by", - "ai_companies_explanation": "You are seeing this because the administrator of this website has set up Anubis to protect the server against the scourge of AI companies aggressively scraping websites. This can and does cause downtime for the websites, which makes their resources inaccessible for everyone.", - "anubis_compromise": "Anubis is a compromise. Anubis uses a Proof-of-Work scheme in the vein of Hashcash, a proposed proof-of-work scheme for reducing email spam. The idea is that at individual scales the additional load is ignorable, but at mass scraper levels it adds up and makes scraping much more expensive.", - "hack_purpose": "Ultimately, this is a placeholder solution so that more time can be spent on fingerprinting and identifying headless browsers (EG: via how they do font rendering) so that the challenge proof of work page doesn't need to be presented to users that are much more likely to be legitimate.", - "simplified_explanation": "This is a measure against bots and malicious requests similar to a CAPTCHA. However, instead of having to do work yourself, your browser is given a calculation task that it has to solve to ensure that it is a valid client. This concept is called Proof of Work. The task is calculated in a few seconds and you are granted access to the website. Thank you for your understanding and patience.", - "jshelter_note": "Please note that Anubis requires the use of modern JavaScript features that plugins like JShelter will disable. Please disable JShelter or other such plugins for this domain.", - "version_info": "This website is running Anubis version", - "try_again": "Try again", - "go_home": "Go home", - "contact_webmaster": "or if you believe you should not be blocked, please contact the webmaster at", - "connection_security": "Please wait a moment while we ensure the security of your connection.", - "javascript_required": "Sadly, you must enable JavaScript to get past this challenge. This is required because AI companies have changed the social contract around how website hosting works. A no-JS solution is a work-in-progress.", - "benchmark_requires_js": "Running the benchmark tool requires JavaScript to be enabled.", - "difficulty": "Difficulty:", - "algorithm": "Algorithm:", - "compare": "Compare:", - "time": "Time", - "iters": "Iters", - "time_a": "Time A", - "iters_a": "Iters A", - "time_b": "Time B", - "iters_b": "Iters B", - "static_check_endpoint": "This is just a check endpoint for your reverse proxy to use.", - "authorization_required": "Authorization required", - "cookies_disabled": "Your browser is configured to disable cookies. Anubis requires cookies for the legitimate interest of making sure you are a valid client. Please enable cookies for this domain", - "access_denied": "Access Denied: error code", - "dronebl_entry": "DroneBL reported an entry", - "see_dronebl_lookup": "see", - "internal_server_error": "Internal Server Error: administrator has misconfigured Anubis. Please contact the administrator and ask them to look for the logs around", - "invalid_redirect": "Invalid redirect", - "redirect_not_parseable": "Redirect URL not parseable", - "redirect_domain_not_allowed": "Redirect domain not allowed", - "missing_required_forwarded_headers": "Missing required X-Forwarded-* headers", - "failed_to_sign_jwt": "failed to sign JWT", - "invalid_invocation": "Invalid invocation of MakeChallenge", - "client_error_browser": "Client Error: Please ensure your browser is up to date and try again later.", - "oh_noes": "Oh noes!", - "benchmarking_anubis": "Benchmarking Anubis!", - "you_are_not_a_bot": "You are not a bot!", - "making_sure_not_bot": "Making sure you're not a bot!", - "celphase": "CELPHASE", - "js_web_crypto_error": "Your browser doesn't have a functioning web.crypto element. Are you viewing this over a secure context?", - "js_web_workers_error": "Your browser doesn't support web workers (Anubis uses this to avoid freezing your browser). Do you have a plugin like JShelter installed?", - "js_cookies_error": "Your browser doesn't store cookies. Anubis uses cookies to determine which clients have passed challenges by storing a signed token in a cookie. Please enable storing cookies for this domain. The names of the cookies Anubis stores may vary without notice. Cookie names and values are not part of the public API.", - "js_context_not_secure": "Your context is not secure!", - "js_context_not_secure_msg": "Try connecting over HTTPS or let the admin know to set up HTTPS. For more information, see MDN.", - "js_calculating": "Calculating...", - "js_missing_feature": "Missing feature", - "js_challenge_error": "Challenge error!", - "js_challenge_error_msg": "Failed to resolve check algorithm. You may want to reload the page.", - "js_calculating_difficulty": "Calculating...
Difficulty:", - "js_speed": "Speed:", - "js_verification_longer": "Verification is taking longer than expected. Please do not refresh the page.", - "js_success": "Success!", - "js_done_took": "Done! Took", - "js_iterations": "iterations", - "js_finished_reading": "I've finished reading, continue →", - "js_calculation_error": "Calculation error!", - "js_calculation_error_msg": "Failed to calculate challenge:" -} diff --git a/data/agent/etc/nginx/lua/pow/static/locales/zh-CN.json b/data/agent/etc/nginx/lua/pow/static/locales/zh-CN.json deleted file mode 100644 index 19529607..00000000 --- a/data/agent/etc/nginx/lua/pow/static/locales/zh-CN.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "loading": "加载中...", - "why_am_i_seeing": "为什么我会看到这个?", - "protected_by": "本网站由", - "protected_from": "保护,来自", - "made_with": "在 🇨🇦 用 ❤️ 制作", - "mascot_design": "吉祥物由", - "ai_companies_explanation": "您会看到这个画面,是因为网站管理员启用了 Anubis 来保护服务器,避免 AI 公司大量爬取网站内容。这类行为会导致网站崩溃,让所有用户都无法正常访问资源。", - "anubis_compromise": "Anubis 是一种折中做法。它采用了类似 Hashcash 的工作量证明机制(Proof-of-Work),该机制最初是为了减少垃圾邮件而提出。其核心概念是:对个别用户而言,额外的计算负担可以忽略,但对大规模爬虫来说,累积起来的成本将大幅增加,从而让爬取行为变得更困难。", - "hack_purpose": "最终,这是一个占位符解决方案,以便将更多时间用于指纹识别和识别无头浏览器(例如:通过它们如何进行字体渲染),从而无需向更可能是合法用户的用户呈现挑战工作量证明页面。", - "jshelter_note": "请注意,Anubis 需要使用现代 JavaScript 功能,而像 JShelter 这类插件可能会阻挡这些功能。请为此域名停用 JShelter 或类似的插件。", - "version_info": "这个网站正在运行的 Anubis 版本为", - "try_again": "再试一次", - "go_home": "返回首页", - "contact_webmaster": "或者您觉得您不应该被封锁,请联系网站管理员于", - "connection_security": "请稍等,我们需要在继续之前检查您的连接安全性。", - "javascript_required": "很遗憾,您必须启用 JavaScript 才能通过这项验证。这是因为 AI 公司已经改变了网站托管的社会契约,因此我们必须采取这样的保护机制。无需 JavaScript 的解决方案仍在开发中。", - "benchmark_requires_js": "运行基准测试工具需要启用 JavaScript。", - "difficulty": "难度:", - "algorithm": "算法:", - "compare": "比较:", - "time": "时间", - "iters": "迭代", - "time_a": "时间 A", - "iters_a": "迭代 A", - "time_b": "时间 B", - "iters_b": "迭代 B", - "static_check_endpoint": "这是提供给您的反向代理服务器使用的检查端点。", - "authorization_required": "需要认证", - "cookies_disabled": "您的浏览器目前已禁用 Cookie,为了确认您是合法用户,Anubis 需要启用 Cookie。 请您为此域名启用 Cookie", - "access_denied": "拒绝访问:错误代码", - "dronebl_entry": "DroneBL 报告了一条记录", - "see_dronebl_lookup": "见", - "internal_server_error": "内部服务器错误:管理员错误地配置了 Anubis。 请联系管理员要求他们检查日志", - "invalid_redirect": "无效的重定向", - "redirect_not_parseable": "重定向 URL 无法解析", - "redirect_domain_not_allowed": "重定向的域名并不允许", - "failed_to_sign_jwt": "签署 JWT 失败", - "invalid_invocation": "无效的 MakeChallenge 调用", - "client_error_browser": "客户端错误:请确保您的浏览器是最新版本并稍候再试。", - "oh_noes": "哎呀糟糕了!", - "benchmarking_anubis": "正在进行 Anubis 性能测试!", - "you_are_not_a_bot": "你不是机器人!", - "making_sure_not_bot": "正在确认你是不是机器人!", - "celphase": "CELPHASE 设计", - "js_web_crypto_error": "您的浏览器无法正常使用 web.crypto 组件。您是否通过安全连接(HTTPS)查看此网站?", - "js_web_workers_error": "您的浏览器并不支持 Web workers (Anubis 使用这个来避免冻结您的浏览器 )您有安装像是 JShelter 之类的插件吗?", - "js_cookies_error": "您的浏览器无法存储 Cookie。 Anubis 会使用 Cookie 存储签署的凭证,以判断用户是否已通过验证。请为此域名启用 Cookie 存储功能。 请注意,Anubis 存储的 Cookie 名称可能会变动,且其名称与内容不属于公开 API 的一部分。", - "js_context_not_secure": "您的内容并不安全", - "js_context_not_secure_msg": "请尝试使用 HTTPS 连接,或联系网站管理员设置 HTTPS。更多信息请参见 MDN。", - "js_calculating": "计算中...", - "js_missing_feature": "缺少功能", - "js_challenge_error": "挑战错误!", - "js_challenge_error_msg": "解决检查算法失败。 您可能会想要刷新页面。", - "js_calculating_difficulty": "计算中...
难度:", - "js_speed": "速度:", - "js_verification_longer": "验证所花的时间高于预期。 请不要刷新页面。", - "js_success": "成功!", - "js_done_took": "完成! 花费", - "js_iterations": "迭代", - "js_finished_reading": "我读完了,继续 →", - "js_calculation_error": "计算错误!", - "js_calculation_error_msg": "计算挑战失败:", - "missing_required_forwarded_headers": "缺少必要的 X-Forwarded-* 头", - "simplified_explanation": "这是一种类似于验证码的措施,用于防止机器人和恶意请求。但是,您无需自己动手,您的浏览器会收到一个计算任务,必须解决该任务以确保它是有效的客户端。这个概念称为工作量证明。该任务在几秒钟内计算完毕,您将被授予访问网站的权限。感谢您的理解和耐心。" -} diff --git a/data/agent/etc/nginx/lua/pow/verify.lua b/data/agent/etc/nginx/lua/pow/verify.lua deleted file mode 100644 index 0f824b57..00000000 --- a/data/agent/etc/nginx/lua/pow/verify.lua +++ /dev/null @@ -1,101 +0,0 @@ -local cjson = require "cjson.safe" - -local pow_challenges = ngx.shared.openflare_pow_challenges -local pow_sessions = ngx.shared.openflare_pow_sessions - -local args = ngx.req.get_uri_args() -local challenge_id = args["id"] or "" -local response = args["response"] or "" -local nonce_str = args["nonce"] or "" -local redir = args["redir"] or "" -local elapsed = args["elapsedTime"] or "" - -if challenge_id == "" or response == "" or nonce_str == "" then - ngx.status = 400 - ngx.header.content_type = "application/json" - ngx.say(cjson.encode({error = "missing parameters"})) - return -end - -local nonce = tonumber(nonce_str) -if not nonce then - ngx.status = 400 - ngx.header.content_type = "application/json" - ngx.say(cjson.encode({error = "invalid nonce"})) - return -end - --- Get stored challenge -local challenge_raw = pow_challenges:get(challenge_id) -if not challenge_raw then - ngx.status = 410 - ngx.header.content_type = "application/json" - ngx.say(cjson.encode({error = "challenge expired or not found"})) - return -end - -local ok, challenge_info = pcall(cjson.decode, challenge_raw) -if not ok or not challenge_info then - ngx.status = 500 - ngx.header.content_type = "application/json" - ngx.say(cjson.encode({error = "invalid challenge data"})) - return -end - -local challenge_data = challenge_info.data or "" -local difficulty = challenge_info.difficulty or 4 -local host = challenge_info.host or ngx.var.host or "" -local session_ttl = challenge_info.session_ttl or 600 - --- Compute SHA-256(challenge_data + nonce) -local calc_string = challenge_data .. tostring(math.floor(nonce)) -local calculated = ngx.sha1_bin ~= nil and "" or "" - --- Use resty.sha256 for proper SHA-256 -local sha256 = require "resty.sha256" -local str = require "resty.string" -local hasher = sha256:new() -hasher:update(calc_string) -local hash_bytes = hasher:final() -local hash_hex = str.to_hex(hash_bytes) - --- Verify hash matches response -if hash_hex ~= string.lower(response) then - ngx.status = 403 - ngx.header.content_type = "application/json" - ngx.say(cjson.encode({error = "hash mismatch"})) - return -end - --- Verify difficulty (leading zeros in hex) -local prefix = string.rep("0", difficulty) -if string.sub(hash_hex, 1, difficulty) ~= prefix then - ngx.status = 403 - ngx.header.content_type = "application/json" - ngx.say(cjson.encode({error = "insufficient difficulty"})) - return -end - --- Invalidate challenge (prevent replay) -pow_challenges:delete(challenge_id) - --- Generate session token -local session_token = str.to_hex(ngx.sha1_bin(challenge_id .. ngx.now() .. tostring(ngx.worker.pid()))) - --- Store session -pow_sessions:set(host .. ":" .. session_token, "1", session_ttl) - --- Set cookie. Secure cookies are not sent over HTTP, so only add Secure when --- the current request itself is HTTPS. -local cookie = "__openflare_pow=" .. session_token .. "; Path=/; HttpOnly; SameSite=Lax; Max-Age=" .. tostring(session_ttl) -if ngx.var.scheme == "https" then - cookie = cookie .. "; Secure" -end -ngx.header["Set-Cookie"] = cookie - -if redir ~= "" then - return ngx.redirect(redir) -end - -ngx.header.content_type = "application/json" -ngx.say(cjson.encode({ok = true})) diff --git a/data/agent/etc/nginx/lua/read.lua b/data/agent/etc/nginx/lua/read.lua deleted file mode 100644 index fe15ecc4..00000000 --- a/data/agent/etc/nginx/lua/read.lua +++ /dev/null @@ -1,45 +0,0 @@ -local cjson = require "cjson.safe" - -local dict = ngx.shared.openflare_observability -if not dict then - ngx.status = ngx.HTTP_SERVICE_UNAVAILABLE - ngx.say(cjson.encode({ message = "shared dict unavailable" })) - return -end - -local now = ngx.time() -local window_size = 60 -local window_start = now - (now % window_size) -local current_window = tostring(window_start) - -local function read_counter(key) - return tonumber(dict:get(key) or 0) or 0 -end - -local function read_map(window_id, prefix, list_key) - local result = {} - local raw = dict:get(list_key .. ":" .. window_id) - if not raw or raw == "" then - return result - end - for value in string.gmatch(raw, "[^\n]+") do - result[value] = read_counter(prefix .. ":" .. window_id .. ":" .. value) - end - return result -end - -local payload = { - window_started_at_unix = window_start, - window_ended_at_unix = now, - request_count = read_counter("request_count:" .. current_window), - error_count = read_counter("error_count:" .. current_window), - unique_visitor_count = read_counter("unique_visitor_count:" .. current_window), - status_codes = read_map(current_window, "status", "status_keys"), - top_domains = read_map(current_window, "domain", "domain_keys"), - source_countries = {}, - openresty_rx_bytes = read_counter("openresty_rx_bytes:" .. current_window), - openresty_tx_bytes = read_counter("openresty_tx_bytes:" .. current_window) -} - -ngx.header.content_type = "application/json" -ngx.say(cjson.encode(payload)) diff --git a/data/agent/etc/nginx/lua/waf/check.lua b/data/agent/etc/nginx/lua/waf/check.lua deleted file mode 100644 index 5f694592..00000000 --- a/data/agent/etc/nginx/lua/waf/check.lua +++ /dev/null @@ -1,10 +0,0 @@ -local source = debug.getinfo(1, "S").source or "" -if string.sub(source, 1, 1) == "@" then - local script_path = string.sub(source, 2) - local base_dir = string.match(script_path, "^(.*)/waf/[^/]+%.lua$") - if base_dir and base_dir ~= "" and not string.find(package.path, base_dir, 1, true) then - package.path = base_dir .. "/?.lua;" .. base_dir .. "/?/init.lua;" .. package.path - end -end - -return require("waf.runtime").check() diff --git a/data/agent/etc/nginx/lua/waf/runtime.lua b/data/agent/etc/nginx/lua/waf/runtime.lua deleted file mode 100644 index fd7c411e..00000000 --- a/data/agent/etc/nginx/lua/waf/runtime.lua +++ /dev/null @@ -1,286 +0,0 @@ -local _M = {} - -function _M.check() -local cjson = require "cjson.safe" - -local config_dict = ngx.shared.openflare_waf_config - -local function read_file(path) - local f = io.open(path, "r") - if not f then - return nil - end - local content = f:read("*a") - f:close() - return content -end - -local function load_config() - local paths = { - "/data/etc/openflare/waf_config.json", - "/etc/nginx/openflare-lua/waf_config.json", - "/usr/local/openresty/nginx/conf/waf_config.json" - } - for _, path in ipairs(paths) do - local content = read_file(path) - if content and content ~= "" then - local hash = ngx.md5(content) - if config_dict:get("_config_hash") == hash then - local cached = config_dict:get("_config_json") - if cached then - local decoded = cjson.decode(cached) - if decoded then - return decoded - end - end - end - local decoded = cjson.decode(content) - if decoded then - config_dict:set("_config_hash", hash, 0) - config_dict:set("_config_json", content, 0) - return decoded - end - end - end - return nil -end - -local function load_ip_groups() - local paths = { - "/data/etc/openflare/waf_ip_groups.json", - "/etc/nginx/openflare-lua/waf_ip_groups.json", - "/usr/local/openresty/nginx/conf/waf_ip_groups.json" - } - for _, path in ipairs(paths) do - local content = read_file(path) - if content and content ~= "" then - local hash = ngx.md5(content) - if config_dict:get("_ip_groups_hash") == hash then - local cached = config_dict:get("_ip_groups_json") - if cached then - local decoded = cjson.decode(cached) - if decoded then - return decoded - end - end - end - local decoded = cjson.decode(content) - if decoded then - config_dict:set("_ip_groups_hash", hash, 0) - config_dict:set("_ip_groups_json", content, 0) - return decoded - end - end - end - return { groups = {} } -end - -local function list_contains(items, value) - if not items or type(items) ~= "table" or not value or value == "" then - return false - end - for _, item in ipairs(items) do - if item == value then - return true - end - end - return false -end - -local function table_has_items(items) - return type(items) == "table" and #items > 0 -end - -local function parse_ipv4(value) - local a, b, c, d = string.match(value or "", "^(%d+)%.(%d+)%.(%d+)%.(%d+)$") - if not a then - return nil - end - a, b, c, d = tonumber(a), tonumber(b), tonumber(c), tonumber(d) - if a > 255 or b > 255 or c > 255 or d > 255 then - return nil - end - return ((a * 256 + b) * 256 + c) * 256 + d -end - -local function ipv4_in_cidr(ip, cidr) - local base, bits = string.match(cidr or "", "^([^/]+)/(%d+)$") - if not base then - return false - end - bits = tonumber(bits) - if not bits or bits < 0 or bits > 32 then - return false - end - local ip_num = parse_ipv4(ip) - local base_num = parse_ipv4(base) - if not ip_num or not base_num then - return false - end - if bits == 0 then - return true - end - local mask = 4294967295 - (2 ^ (32 - bits) - 1) - return (ip_num - (ip_num % (2 ^ (32 - bits)))) == (base_num - (base_num % (2 ^ (32 - bits)))) -end - -local function ip_matches(items, ip) - if not items or type(items) ~= "table" or not ip or ip == "" then - return false - end - for _, item in ipairs(items) do - if item == ip then - return true - end - if string.find(item, "/", 1, true) and ipv4_in_cidr(ip, item) then - return true - end - end - return false -end - -local function ip_matches_group_ids(group_ids, ip, ip_groups_config) - if not group_ids or type(group_ids) ~= "table" or not ip or ip == "" then - return false - end - local groups = (ip_groups_config or {}).groups or {} - for _, id in ipairs(group_ids) do - local group = groups[tostring(id)] - if group and group.enabled and ip_matches(group.ip_list, ip) then - return true - end - end - return false -end - -local function lookup_country(ip) - local ok, maxminddb = pcall(require, "resty.maxminddb") - if not ok or not maxminddb then - return nil - end - local paths = { - "/data/etc/openflare/GeoLite2-Country.mmdb", - "/etc/openflare/GeoLite2-Country.mmdb", - "/usr/local/share/openflare/GeoLite2-Country.mmdb" - } - for _, path in ipairs(paths) do - local opened = pcall(maxminddb.init, path) - if opened then - local res, err = maxminddb.lookup(ip) - if res and res.country and res.country.iso_code then - return string.upper(res.country.iso_code) - end - end - end - return nil -end - -local function group_by_id(config) - local result = {} - for _, group in ipairs(config.rule_groups or {}) do - result[tostring(group.id)] = group - end - return result -end - -local function active_groups(config, groups) - local site = ngx.var.openflare_waf_site or "" - local ids = (config.site_rule_groups or {})[site] - local result = {} - for _, group in ipairs(config.rule_groups or {}) do - if group.is_global then - result[#result + 1] = group - end - end - if ids then - local by_id = group_by_id(config) - for _, id in ipairs(ids) do - local group = by_id[tostring(id)] - if group and not group.is_global then - result[#result + 1] = group - end - end - end - return result -end - -local function exit_with_group(group) - ngx.ctx.openflare_waf_blocked = true - ngx.status = tonumber(group.block_status_code) or 418 - local body = group.block_response_body or "" - if body ~= "" then - ngx.header["Content-Type"] = "text/html; charset=utf-8" - ngx.say(body) - end - return ngx.exit(ngx.status) -end - -local function first_allowlist_group(groups) - for _, group in ipairs(groups) do - if table_has_items(group.ip_whitelist) - or table_has_items(group.ip_whitelist_group_ids) - or table_has_items(group.country_whitelist) then - return group - end - end - return nil -end - -local config = load_config() -if not config then - if config_dict:add("_missing_config_logged", true, 60) then - ngx.log(ngx.WARN, "openflare waf config is missing or invalid; requests will be allowed") - end - return -end - -local ip = ngx.var.remote_addr or "" -local groups = active_groups(config) -local ip_groups_config = load_ip_groups() -if #groups == 0 then - if config_dict:add("_empty_groups_logged", true, 60) then - ngx.log(ngx.WARN, "openflare waf has no active rule group for site: ", ngx.var.openflare_waf_site or "") - end - return -end - -for _, group in ipairs(groups) do - if ip_matches(group.ip_whitelist, ip) or ip_matches_group_ids(group.ip_whitelist_group_ids, ip, ip_groups_config) then - return - end -end - -local country = nil -for _, group in ipairs(groups) do - if type(group.country_whitelist) == "table" and #group.country_whitelist > 0 then - country = country or lookup_country(ip) - if list_contains(group.country_whitelist, country) then - return - end - end -end - -local allowlist_group = first_allowlist_group(groups) -if allowlist_group then - return exit_with_group(allowlist_group) -end - -for _, group in ipairs(groups) do - if ip_matches(group.ip_blacklist, ip) or ip_matches_group_ids(group.ip_blacklist_group_ids, ip, ip_groups_config) then - return exit_with_group(group) - end -end - -for _, group in ipairs(groups) do - if type(group.country_blacklist) == "table" and #group.country_blacklist > 0 then - country = country or lookup_country(ip) - if list_contains(group.country_blacklist, country) then - return exit_with_group(group) - end - end -end - -return "ok" -end - -return _M diff --git a/data/agent/etc/nginx/mime.types b/data/agent/etc/nginx/mime.types deleted file mode 100644 index 8a2348ab..00000000 --- a/data/agent/etc/nginx/mime.types +++ /dev/null @@ -1,95 +0,0 @@ - -types { - text/html html htm shtml; - text/css css; - text/xml xml; - image/gif gif; - image/jpeg jpeg jpg; - application/javascript js; - application/atom+xml atom; - application/rss+xml rss; - - text/mathml mml; - text/plain txt; - text/vnd.sun.j2me.app-descriptor jad; - text/vnd.wap.wml wml; - text/x-component htc; - - image/png png; - image/svg+xml svg svgz; - image/tiff tif tiff; - image/vnd.wap.wbmp wbmp; - image/webp webp; - image/x-icon ico; - image/x-jng jng; - image/x-ms-bmp bmp; - - application/font-woff woff; - application/java-archive jar war ear; - application/json json; - application/mac-binhex40 hqx; - application/msword doc; - application/pdf pdf; - application/postscript ps eps ai; - application/rtf rtf; - application/vnd.apple.mpegurl m3u8; - application/vnd.google-earth.kml+xml kml; - application/vnd.google-earth.kmz kmz; - application/vnd.ms-excel xls; - application/vnd.ms-fontobject eot; - application/vnd.ms-powerpoint ppt; - application/vnd.oasis.opendocument.graphics odg; - application/vnd.oasis.opendocument.presentation odp; - application/vnd.oasis.opendocument.spreadsheet ods; - application/vnd.oasis.opendocument.text odt; - application/vnd.openxmlformats-officedocument.presentationml.presentation - pptx; - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet - xlsx; - application/vnd.openxmlformats-officedocument.wordprocessingml.document - docx; - application/vnd.wap.wmlc wmlc; - application/x-7z-compressed 7z; - application/x-cocoa cco; - application/x-java-archive-diff jardiff; - application/x-java-jnlp-file jnlp; - application/x-makeself run; - application/x-perl pl pm; - application/x-pilot prc pdb; - application/x-rar-compressed rar; - application/x-redhat-package-manager rpm; - application/x-sea sea; - application/x-shockwave-flash swf; - application/x-stuffit sit; - application/x-tcl tcl tk; - application/x-x509-ca-cert der pem crt; - application/x-xpinstall xpi; - application/xhtml+xml xhtml; - application/xspf+xml xspf; - application/zip zip; - - application/octet-stream bin exe dll; - application/octet-stream deb; - application/octet-stream dmg; - application/octet-stream iso img; - application/octet-stream msi msp msm; - - audio/midi mid midi kar; - audio/mpeg mp3; - audio/ogg ogg; - audio/x-m4a m4a; - audio/x-realaudio ra; - - video/3gpp 3gpp 3gp; - video/mp2t ts; - video/mp4 mp4; - video/mpeg mpeg mpg; - video/quicktime mov; - video/webm webm; - video/x-flv flv; - video/x-m4v m4v; - video/x-mng mng; - video/x-ms-asf asx asf; - video/x-ms-wmv wmv; - video/x-msvideo avi; -} diff --git a/data/agent/etc/nginx/nginx.conf b/data/agent/etc/nginx/nginx.conf deleted file mode 100644 index 8012468f..00000000 --- a/data/agent/etc/nginx/nginx.conf +++ /dev/null @@ -1,84 +0,0 @@ -# This file is generated by OpenFlare. Do not edit manually. -worker_processes auto; -worker_rlimit_nofile 65535; -pid logs/nginx.pid; -error_log /data/var/log/openflare/error.log warn; - -events { - worker_connections 4096; - use epoll; - multi_accept on; -} - -http { - include mime.types; - default_type application/octet-stream; - map $http_upgrade $connection_upgrade { - default upgrade; - '' ""; - } - - server { - listen 80 default_server; - server_name _; - - return 421; - } - - server { - listen 443 ssl default_server; - listen 443 quic reuseport default_server; - server_name _; - - ssl_reject_handshake on; - } - log_format openflare_json escape=json '{"ts":"$time_iso8601","host":"$host","path":"$request_uri","remote_addr":"$remote_addr","status":$status,"request_time":$request_time,"bytes_sent":$body_bytes_sent,"request_length":$request_length}'; - access_log /data/var/log/openflare/access.log openflare_json; - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 20; - keepalive_requests 1000; - client_header_timeout 15; - client_body_timeout 15; - client_max_body_size 64m; - large_client_header_buffers 4 16k; - send_timeout 30; - proxy_connect_timeout 3; - proxy_send_timeout 60; - proxy_read_timeout 60; - proxy_request_buffering off; - proxy_buffering on; - proxy_buffers 16 16k; - proxy_buffer_size 8k; - proxy_busy_buffers_size 64k; - gzip on; - gzip_min_length 1024; - gzip_comp_level 5; - limit_conn_zone $server_name zone=openflare_conn_per_server:10m; - limit_conn_zone $binary_remote_addr zone=openflare_conn_per_ip:10m; - lua_shared_dict openflare_observability 10m; - lua_shared_dict openflare_pow_challenges 10m; - lua_shared_dict openflare_pow_sessions 10m; - lua_shared_dict openflare_pow_config 1m; - lua_shared_dict openflare_waf_config 1m; - init_worker_by_lua_file /data/etc/nginx/lua/observability/init.lua; - log_by_lua_file /data/etc/nginx/lua/observability/log.lua; - - server { - listen 127.0.0.1:18081; - server_name openflare-observability; - access_log off; - - location = /openflare/stub_status { - stub_status; - } - - location = /openflare/observability { - default_type application/json; - content_by_lua_file /data/etc/nginx/lua/observability/read.lua; - } - } - - include /data/etc/nginx/conf.d/openflare_routes.conf; -} diff --git a/data/agent/etc/openflare/openresty_config.json b/data/agent/etc/openflare/openresty_config.json deleted file mode 100644 index 8a4cf72a..00000000 --- a/data/agent/etc/openflare/openresty_config.json +++ /dev/null @@ -1 +0,0 @@ -{"routes":[{"id":1,"site_name":"local","domain":"a.com","domains":["a.com"],"origin_url":"https://flare.arctel.net:443","upstreams":["https://flare.arctel.net:443"],"enabled":true,"enable_https":false,"redirect_http":false,"cache_enabled":false,"upstream_type":"direct"}],"openresty_config":{"default_server_return_status":421,"worker_processes":"auto","worker_connections":4096,"worker_rlimit_nofile":65535,"events_use":"epoll","events_multi_accept_enabled":true,"keepalive_timeout":20,"keepalive_requests":1000,"client_header_timeout":15,"client_body_timeout":15,"client_max_body_size":"64m","large_client_header_buffers":"4 16k","send_timeout":30,"proxy_connect_timeout":3,"proxy_send_timeout":60,"proxy_read_timeout":60,"websocket_enabled":true,"http3_enabled":true,"proxy_request_buffering":false,"proxy_buffering_enabled":true,"proxy_buffers":"16 16k","proxy_buffer_size":"8k","proxy_busy_buffers_size":"64k","gzip_enabled":true,"gzip_min_length":1024,"gzip_comp_level":5,"cache_enabled":false,"cache_levels":"1:2","cache_inactive":"30m","cache_max_size":"1g","cache_key_template":"$scheme$host$request_uri","cache_lock_enabled":true,"cache_lock_timeout":"5s","cache_use_stale":"error timeout updating http_500 http_502 http_503 http_504","main_config_template":"# This file is generated by OpenFlare. Do not edit manually.\nworker_processes {{OpenRestyWorkerProcesses}};\nworker_rlimit_nofile {{OpenRestyWorkerRlimitNofile}};\npid logs/nginx.pid;\nerror_log {{OpenRestyErrorLogPath}} warn;\n\nevents {\n worker_connections {{OpenRestyWorkerConnections}};\n{{OpenRestyEventsUseDirective}}{{OpenRestyEventsMultiAcceptDirective}}}\n\nhttp {\n include mime.types;\n default_type application/octet-stream;\n{{OpenRestyConnectionUpgradeMap}}{{OpenRestyDefaultServerBlock}} log_format openflare_json escape=json '{\"ts\":\"$time_iso8601\",\"host\":\"$host\",\"path\":\"$request_uri\",\"remote_addr\":\"$remote_addr\",\"status\":$status,\"request_time\":$request_time,\"bytes_sent\":$body_bytes_sent,\"request_length\":$request_length}';\n access_log {{OpenRestyAccessLogPath}} openflare_json;\n sendfile on;\n tcp_nopush on;\n tcp_nodelay on;\n keepalive_timeout {{OpenRestyKeepaliveTimeout}};\n keepalive_requests {{OpenRestyKeepaliveRequests}};\n client_header_timeout {{OpenRestyClientHeaderTimeout}};\n client_body_timeout {{OpenRestyClientBodyTimeout}};\n client_max_body_size {{OpenRestyClientMaxBodySize}};\n large_client_header_buffers {{OpenRestyLargeClientHeaderBuffers}};\n send_timeout {{OpenRestySendTimeout}};\n proxy_connect_timeout {{OpenRestyProxyConnectTimeout}};\n proxy_send_timeout {{OpenRestyProxySendTimeout}};\n proxy_read_timeout {{OpenRestyProxyReadTimeout}};\n proxy_request_buffering {{OpenRestyProxyRequestBuffering}};\n proxy_buffering {{OpenRestyProxyBuffering}};\n proxy_buffers {{OpenRestyProxyBuffers}};\n proxy_buffer_size {{OpenRestyProxyBufferSize}};\n proxy_busy_buffers_size {{OpenRestyProxyBusyBuffersSize}};\n gzip {{OpenRestyGzip}};\n gzip_min_length {{OpenRestyGzipMinLength}};\n gzip_comp_level {{OpenRestyGzipCompLevel}};\n{{OpenRestyResolverDirective}}{{OpenRestyCacheBlock}} include {{OpenRestyRouteConfigInclude}};\n}\n"},"waf":{"rule_groups":[{"id":1,"name":"全局规则组","enabled":true,"is_global":true,"block_status_code":418,"pow_config":{"difficulty":4,"algorithm":"fast","session_ttl":600,"challenge_ttl":300,"whitelist":{"ips":[],"ip_cidrs":[],"paths":[],"path_regexes":[],"user_agents":[]},"blacklist":{"ips":[],"ip_cidrs":[],"paths":[],"path_regexes":[],"user_agents":[]}}}],"bindings":[]}} \ No newline at end of file diff --git a/data/agent/etc/openflare/waf_config.json b/data/agent/etc/openflare/waf_config.json deleted file mode 100644 index b2aa4559..00000000 --- a/data/agent/etc/openflare/waf_config.json +++ /dev/null @@ -1 +0,0 @@ -{"default_block_status_code":418,"rule_groups":[{"id":1,"name":"全局规则组","is_global":true,"block_status_code":418,"block_response_body":"","ip_whitelist":[],"ip_blacklist":[],"country_whitelist":null,"country_blacklist":null,"region_whitelist":null,"region_blacklist":null,"pow_enabled":false}],"site_rule_groups":{}} \ No newline at end of file diff --git a/data/agent/etc/openflare/waf_ip_groups.json b/data/agent/etc/openflare/waf_ip_groups.json deleted file mode 100644 index 6b18fc04..00000000 --- a/data/agent/etc/openflare/waf_ip_groups.json +++ /dev/null @@ -1 +0,0 @@ -{"groups":{"1":{"id":1,"name":"firehol","type":"subscription","enabled":false,"ip_list":[],"checksum":"af25d137052579a74485ec670bb3b85bece83acda3ad89975f6346f181369427"},"2":{"id":2,"name":"自定义 IP 组","type":"automatic","enabled":true,"ip_list":[],"checksum":"7ade1acdb67895a13d1c36797f879d4dcf424f0e420f6b2b2750e446aaa1523d"},"3":{"id":3,"name":"手动组","type":"manual","enabled":true,"ip_list":["172.18.8.20","192.168.0.1"],"checksum":"e1c484f8facfcda5630abfad3fa1340491a07647ba73901a21cc9949387ba799"}}} \ No newline at end of file diff --git a/data/agent/var/lib/openflare/agent-state.json b/data/agent/var/lib/openflare/agent-state.json deleted file mode 100644 index 4456f040..00000000 --- a/data/agent/var/lib/openflare/agent-state.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "node_id": "node-4a2bfffe5a8c8dad", - "current_version": "20260618-004", - "current_checksum": "cfdda2c756f0e3263b5bb67aea898ff28586682547fb41c78cde82bf3ffb68d6", - "blocked_version": "", - "blocked_checksum": "", - "blocked_reason": "", - "last_error": "", - "openresty_status": "healthy", - "openresty_message": "", - "last_profile_fingerprint": "848303a6e044aec69efa18333711c484d711fbff229c72a3c2bc93fd742bc340", - "last_cpu_stat_total": 91359492, - "last_cpu_stat_idle": 91057341, - "last_metric_at_unix": 1781849048, - "access_log_offset": 9522 -} \ No newline at end of file diff --git a/data/agent/var/lib/openflare/observability-buffer.json b/data/agent/var/lib/openflare/observability-buffer.json deleted file mode 100644 index de8b6a4a..00000000 --- a/data/agent/var/lib/openflare/observability-buffer.json +++ /dev/null @@ -1,482 +0,0 @@ -[ - { - "window_started_at_unix": 1781848200, - "snapshot": { - "captured_at_unix": 1781848258, - "cpu_usage_percent": 0.3127736769673464, - "memory_used_bytes": 683499520, - "memory_total_bytes": 12601327616, - "storage_used_bytes": 113875708018688, - "storage_total_bytes": 126562507685888, - "disk_read_bytes": 178618413568, - "disk_write_bytes": 8236818432, - "network_rx_bytes": 992072, - "network_tx_bytes": 3133491 - }, - "openresty_observation": { - "captured_at_unix": 1781848258, - "openresty_rx_bytes": 0, - "openresty_tx_bytes": 0, - "openresty_connections": 1 - }, - "traffic_report": { - "window_started_at_unix": 1781848200, - "window_ended_at_unix": 1781848258, - "request_count": 0, - "error_count": 0, - "unique_visitor_count": 0, - "status_codes": {}, - "top_domains": {}, - "source_countries": {} - }, - "queued_at_unix": 1781848258 - }, - { - "window_started_at_unix": 1781848260, - "snapshot": { - "captured_at_unix": 1781848318, - "cpu_usage_percent": 0.36268134067033514, - "memory_used_bytes": 683479040, - "memory_total_bytes": 12601327616, - "storage_used_bytes": 113878183706624, - "storage_total_bytes": 126562507685888, - "disk_read_bytes": 178736337408, - "disk_write_bytes": 8237965312, - "network_rx_bytes": 992522, - "network_tx_bytes": 3134061 - }, - "openresty_observation": { - "captured_at_unix": 1781848318, - "openresty_rx_bytes": 0, - "openresty_tx_bytes": 0, - "openresty_connections": 1 - }, - "traffic_report": { - "window_started_at_unix": 1781848260, - "window_ended_at_unix": 1781848318, - "request_count": 0, - "error_count": 0, - "unique_visitor_count": 0, - "status_codes": {}, - "top_domains": {}, - "source_countries": {} - }, - "queued_at_unix": 1781848318 - }, - { - "window_started_at_unix": 1781848320, - "snapshot": { - "captured_at_unix": 1781848378, - "cpu_usage_percent": 0.38808212318477714, - "memory_used_bytes": 689451008, - "memory_total_bytes": 12601327616, - "storage_used_bytes": 113856253788160, - "storage_total_bytes": 126562507685888, - "disk_read_bytes": 178832536064, - "disk_write_bytes": 8238546944, - "network_rx_bytes": 993014, - "network_tx_bytes": 3134673 - }, - "openresty_observation": { - "captured_at_unix": 1781848378, - "openresty_rx_bytes": 0, - "openresty_tx_bytes": 0, - "openresty_connections": 1 - }, - "traffic_report": { - "window_started_at_unix": 1781848320, - "window_ended_at_unix": 1781848378, - "request_count": 0, - "error_count": 0, - "unique_visitor_count": 0, - "status_codes": {}, - "top_domains": {}, - "source_countries": {} - }, - "queued_at_unix": 1781848378 - }, - { - "window_started_at_unix": 1781848380, - "snapshot": { - "captured_at_unix": 1781848438, - "cpu_usage_percent": 0.36263598849568585, - "memory_used_bytes": 681353216, - "memory_total_bytes": 12601327616, - "storage_used_bytes": 113861555388416, - "storage_total_bytes": 126562507685888, - "disk_read_bytes": 178939171328, - "disk_write_bytes": 8239661056, - "network_rx_bytes": 993464, - "network_tx_bytes": 3135243 - }, - "openresty_observation": { - "captured_at_unix": 1781848438, - "openresty_rx_bytes": 0, - "openresty_tx_bytes": 0, - "openresty_connections": 1 - }, - "traffic_report": { - "window_started_at_unix": 1781848380, - "window_ended_at_unix": 1781848438, - "request_count": 0, - "error_count": 0, - "unique_visitor_count": 0, - "status_codes": {}, - "top_domains": {}, - "source_countries": {} - }, - "queued_at_unix": 1781848438 - }, - { - "window_started_at_unix": 1781848440, - "snapshot": { - "captured_at_unix": 1781848498, - "cpu_usage_percent": 0.23752969121140144, - "memory_used_bytes": 694992896, - "memory_total_bytes": 12601327616, - "storage_used_bytes": 113869457457152, - "storage_total_bytes": 126562507685888, - "disk_read_bytes": 178986156544, - "disk_write_bytes": 8240242688, - "network_rx_bytes": 993956, - "network_tx_bytes": 3135855 - }, - "openresty_observation": { - "captured_at_unix": 1781848498, - "openresty_rx_bytes": 0, - "openresty_tx_bytes": 0, - "openresty_connections": 1 - }, - "traffic_report": { - "window_started_at_unix": 1781848440, - "window_ended_at_unix": 1781848498, - "request_count": 0, - "error_count": 0, - "unique_visitor_count": 0, - "status_codes": {}, - "top_domains": {}, - "source_countries": {} - }, - "queued_at_unix": 1781848498 - }, - { - "window_started_at_unix": 1781848500, - "snapshot": { - "captured_at_unix": 1781848558, - "cpu_usage_percent": 0.37537537537537535, - "memory_used_bytes": 678383616, - "memory_total_bytes": 12601327616, - "storage_used_bytes": 113875264471040, - "storage_total_bytes": 126562507685888, - "disk_read_bytes": 179164385792, - "disk_write_bytes": 8241356800, - "network_rx_bytes": 994406, - "network_tx_bytes": 3136425 - }, - "openresty_observation": { - "captured_at_unix": 1781848558, - "openresty_rx_bytes": 0, - "openresty_tx_bytes": 0, - "openresty_connections": 1 - }, - "traffic_report": { - "window_started_at_unix": 1781848500, - "window_ended_at_unix": 1781848558, - "request_count": 0, - "error_count": 0, - "unique_visitor_count": 0, - "status_codes": {}, - "top_domains": {}, - "source_countries": {} - }, - "queued_at_unix": 1781848558 - }, - { - "window_started_at_unix": 1781848560, - "snapshot": { - "captured_at_unix": 1781848618, - "cpu_usage_percent": 0.2625328166020752, - "memory_used_bytes": 680759296, - "memory_total_bytes": 12601327616, - "storage_used_bytes": 113879888691200, - "storage_total_bytes": 126562507685888, - "disk_read_bytes": 179171258880, - "disk_write_bytes": 8242077696, - "network_rx_bytes": 994898, - "network_tx_bytes": 3137037 - }, - "openresty_observation": { - "captured_at_unix": 1781848618, - "openresty_rx_bytes": 0, - "openresty_tx_bytes": 0, - "openresty_connections": 1 - }, - "traffic_report": { - "window_started_at_unix": 1781848560, - "window_ended_at_unix": 1781848618, - "request_count": 0, - "error_count": 0, - "unique_visitor_count": 0, - "status_codes": {}, - "top_domains": {}, - "source_countries": {} - }, - "queued_at_unix": 1781848618 - }, - { - "window_started_at_unix": 1781848620, - "snapshot": { - "captured_at_unix": 1781848678, - "cpu_usage_percent": 0.42537220067559117, - "memory_used_bytes": 682201088, - "memory_total_bytes": 12601327616, - "storage_used_bytes": 113884578971648, - "storage_total_bytes": 126562507685888, - "disk_read_bytes": 179309863424, - "disk_write_bytes": 8243191808, - "network_rx_bytes": 995348, - "network_tx_bytes": 3137607 - }, - "openresty_observation": { - "captured_at_unix": 1781848678, - "openresty_rx_bytes": 0, - "openresty_tx_bytes": 0, - "openresty_connections": 1 - }, - "traffic_report": { - "window_started_at_unix": 1781848620, - "window_ended_at_unix": 1781848678, - "request_count": 0, - "error_count": 0, - "unique_visitor_count": 0, - "status_codes": {}, - "top_domains": {}, - "source_countries": {} - }, - "queued_at_unix": 1781848678 - }, - { - "window_started_at_unix": 1781848680, - "snapshot": { - "captured_at_unix": 1781848738, - "cpu_usage_percent": 0.3254882323485228, - "memory_used_bytes": 683511808, - "memory_total_bytes": 12601327616, - "storage_used_bytes": 113863895810048, - "storage_total_bytes": 126562507685888, - "disk_read_bytes": 179363787264, - "disk_write_bytes": 8243838976, - "network_rx_bytes": 995798, - "network_tx_bytes": 3138177 - }, - "openresty_observation": { - "captured_at_unix": 1781848738, - "openresty_rx_bytes": 0, - "openresty_tx_bytes": 0, - "openresty_connections": 1 - }, - "traffic_report": { - "window_started_at_unix": 1781848680, - "window_ended_at_unix": 1781848738, - "request_count": 0, - "error_count": 0, - "unique_visitor_count": 0, - "status_codes": {}, - "top_domains": {}, - "source_countries": {} - }, - "queued_at_unix": 1781848738 - }, - { - "window_started_at_unix": 1781848740, - "snapshot": { - "captured_at_unix": 1781848798, - "cpu_usage_percent": 0.3253253253253253, - "memory_used_bytes": 683442176, - "memory_total_bytes": 12601327616, - "storage_used_bytes": 113874683559936, - "storage_total_bytes": 126562507685888, - "disk_read_bytes": 179498406400, - "disk_write_bytes": 8244953088, - "network_rx_bytes": 996248, - "network_tx_bytes": 3138747 - }, - "openresty_observation": { - "captured_at_unix": 1781848798, - "openresty_rx_bytes": 0, - "openresty_tx_bytes": 0, - "openresty_connections": 1 - }, - "traffic_report": { - "window_started_at_unix": 1781848740, - "window_ended_at_unix": 1781848798, - "request_count": 0, - "error_count": 0, - "unique_visitor_count": 0, - "status_codes": {}, - "top_domains": {}, - "source_countries": {} - }, - "queued_at_unix": 1781848798 - }, - { - "window_started_at_unix": 1781848800, - "snapshot": { - "captured_at_unix": 1781848858, - "cpu_usage_percent": 0.2251688766574931, - "memory_used_bytes": 683982848, - "memory_total_bytes": 12601327616, - "storage_used_bytes": 113881640861696, - "storage_total_bytes": 126562507685888, - "disk_read_bytes": 179556286976, - "disk_write_bytes": 8245567488, - "network_rx_bytes": 996698, - "network_tx_bytes": 3139317 - }, - "openresty_observation": { - "captured_at_unix": 1781848858, - "openresty_rx_bytes": 0, - "openresty_tx_bytes": 0, - "openresty_connections": 1 - }, - "traffic_report": { - "window_started_at_unix": 1781848800, - "window_ended_at_unix": 1781848858, - "request_count": 0, - "error_count": 0, - "unique_visitor_count": 0, - "status_codes": {}, - "top_domains": {}, - "source_countries": {} - }, - "queued_at_unix": 1781848858 - }, - { - "window_started_at_unix": 1781848860, - "snapshot": { - "captured_at_unix": 1781848918, - "cpu_usage_percent": 0.44999999999999996, - "memory_used_bytes": 675201024, - "memory_total_bytes": 12601327616, - "storage_used_bytes": 113889617379328, - "storage_total_bytes": 126562507685888, - "disk_read_bytes": 179728159232, - "disk_write_bytes": 8246779904, - "network_rx_bytes": 997148, - "network_tx_bytes": 3139887 - }, - "openresty_observation": { - "captured_at_unix": 1781848918, - "openresty_rx_bytes": 0, - "openresty_tx_bytes": 0, - "openresty_connections": 1 - }, - "traffic_report": { - "window_started_at_unix": 1781848860, - "window_ended_at_unix": 1781848918, - "request_count": 0, - "error_count": 0, - "unique_visitor_count": 0, - "status_codes": {}, - "top_domains": {}, - "source_countries": {} - }, - "queued_at_unix": 1781848918 - }, - { - "window_started_at_unix": 1781848920, - "snapshot": { - "captured_at_unix": 1781848978, - "cpu_usage_percent": 0.287464066991626, - "memory_used_bytes": 681017344, - "memory_total_bytes": 12601327616, - "storage_used_bytes": 113892144447488, - "storage_total_bytes": 126562507685888, - "disk_read_bytes": 179760898560, - "disk_write_bytes": 8247394304, - "network_rx_bytes": 997598, - "network_tx_bytes": 3140457 - }, - "openresty_observation": { - "captured_at_unix": 1781848978, - "openresty_rx_bytes": 0, - "openresty_tx_bytes": 0, - "openresty_connections": 1 - }, - "traffic_report": { - "window_started_at_unix": 1781848920, - "window_ended_at_unix": 1781848978, - "request_count": 0, - "error_count": 0, - "unique_visitor_count": 0, - "status_codes": {}, - "top_domains": {}, - "source_countries": {} - }, - "queued_at_unix": 1781848978 - }, - { - "window_started_at_unix": 1781848980, - "snapshot": { - "captured_at_unix": 1781849038, - "cpu_usage_percent": 0.37537537537537535, - "memory_used_bytes": 685330432, - "memory_total_bytes": 12601327616, - "storage_used_bytes": 113921559101440, - "storage_total_bytes": 126562507685888, - "disk_read_bytes": 179892072960, - "disk_write_bytes": 8248508416, - "network_rx_bytes": 998090, - "network_tx_bytes": 3141069 - }, - "openresty_observation": { - "captured_at_unix": 1781849038, - "openresty_rx_bytes": 0, - "openresty_tx_bytes": 0, - "openresty_connections": 1 - }, - "traffic_report": { - "window_started_at_unix": 1781848980, - "window_ended_at_unix": 1781849038, - "request_count": 0, - "error_count": 0, - "unique_visitor_count": 0, - "status_codes": {}, - "top_domains": {}, - "source_countries": {} - }, - "queued_at_unix": 1781849038 - }, - { - "window_started_at_unix": 1781849040, - "snapshot": { - "captured_at_unix": 1781849048, - "cpu_usage_percent": 0.2875718929732433, - "memory_used_bytes": 711155712, - "memory_total_bytes": 12601327616, - "storage_used_bytes": 113921939734528, - "storage_total_bytes": 126562507685888, - "disk_read_bytes": 179897475584, - "disk_write_bytes": 8248524800, - "network_rx_bytes": 998144, - "network_tx_bytes": 3141143 - }, - "openresty_observation": { - "captured_at_unix": 1781849048, - "openresty_rx_bytes": 0, - "openresty_tx_bytes": 0, - "openresty_connections": 1 - }, - "traffic_report": { - "window_started_at_unix": 1781849040, - "window_ended_at_unix": 1781849048, - "request_count": 0, - "error_count": 0, - "unique_visitor_count": 0, - "status_codes": {}, - "top_domains": {}, - "source_countries": {} - }, - "queued_at_unix": 1781849048 - } -] \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/.gitignore b/data/agent/var/lib/openflare/pages/deployments/1/current/.gitignore deleted file mode 100644 index e43b0f98..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/current/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.DS_Store diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/.openflare-pages.json b/data/agent/var/lib/openflare/pages/deployments/1/current/.openflare-pages.json deleted file mode 100644 index 660e9c7f..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/current/.openflare-pages.json +++ /dev/null @@ -1 +0,0 @@ -{"deployment_id":1,"checksum":"4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6"} \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/License.md b/data/agent/var/lib/openflare/pages/deployments/1/current/License.md deleted file mode 100644 index b93a9207..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/current/License.md +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 OpenSpeedTest™ - -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 permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/README.md b/data/agent/var/lib/openflare/pages/deployments/1/current/README.md deleted file mode 100644 index 17d89eeb..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/current/README.md +++ /dev/null @@ -1,506 +0,0 @@ -# **[SpeedTest by OpenSpeedTest™](https://openspeedtest.com?Run&ref=Github)** - Free & Open-Source HTML5 Network Performance Estimation Tool. - - - -SpeedTest by OpenSpeedTest™ is a Free and Open-Source HTML5 Network Performance Estimation Tool Written in Vanilla Javascript and only uses built-in Web APIs like `XMLHttpRequest` `(XHR)`, `HTML`, `CSS`, `JS`, & `SVG`. No Third-Party frameworks or libraries are Required. All we need is a static web server like `NGINX`. I started this project in 2011 and moved to OpenSpeedTest.com dedicated Project/Domain Name in 2013. - ---- -Just launched: OpenPacketLoss. Check it out! - -While OpenSpeedTest measures bandwidth, this tool helps you detect packet loss directly from your browser. - -**Try it:** https://openpacketloss.com -**Source:** https://github.com/openpacketloss - ---- - - -[![Download OpenSpeedTest-Server V2.1](https://github.com/openspeedtest/v2-Test/raw/main/images/10G-S.gif)](https://go.openspeedtest.com/Server "Download OpenSpeedTest-Server V2.1") - Download from the Microsoft Store Download from the Mac App Store Download from the App Store Download from the Google Play Store Download from the Snap Store Download from the Docker Hub Download from the Helm Store Download from GitHub - -**No client-side software or plugin is required. You can run a network speed test from any device with a [Web Browser that is IE10 or newer.](https://www.youtube.com/watch?v=9f-OM_WQ7Bw&list=PLt-deStxFJOMEAs2O1lJhscMNzcg9E3Po&index=1)** - - Video Tutorial by Roberto Jorge Tech - Video Tutorial by LanPad - -## Why OpenSpeedTest - - - -### Secure by Design. - - - -OpenSpeedTest contains Only `STATIC` Files like `HTML`,`CSS` & `JS`. - -So you don't need to worry about Security Updates or Hidden Exploits that may compromise your secure environments. - - - -### Lightweight, High Performance. - - - -OpenSpeedTest is written in Vanilla JavaScript. No Third-Party frameworks or libraries were used. SpeedTest script file size is under 8kB gzip. The unexpected side effect of using Vanilla JavaScript is High Performance. - - - -### Run a speed test from Any Device. - - - -OpenSpeedTest will run on Any Web Browser that is IE10 or newer. - - - -### Ready for Any Display Size and Resolution. - - - -OpenSpeedTest User interface is written in SVG. - - - -# Create Your Own SpeedTest Server. - -### Server Requirements : - -`Nginx`, `Apache`, `IIS`, `Express`, or Any Web server that supports `HTTP/1.1` or newer. - -- Accept, `GET`, `POST`, `HEAD` & `OPTIONS`, Response `200 OK`. - -- Accept, `POST` to Static Files, Response `200 OK`. - -- `client_max_body_size`, 35 Megabytes or more. - -- Timeout greater than `60 seconds`. - -- Disable `Access logs` for Increasing server performance. - -- Improve `Time to First Byte` (TTFB) - -- Warning! If you run it behind a **[Reverse Proxy](https://github.com/openspeedtest/Speed-Test/issues/4#issuecomment-1229157193)**, you should increase the `post-body content length` to 35 megabytes. -- Supports `HTTP2` & `HTTP3`. -- `HTTP1.1` is recommended for achieving maximum performance. -- **[You Should Follow our Nginx Config.](https://github.com/openspeedtest/Nginx-Configuration)** - - - -# Or, You can use OpenSpeedTest-Server. - -OpenSpeedTest-Server is available for Windows, Mac, Linux, Android, iOS & Docker. -[![Download OpenSpeedTest-Server V2.1](https://open.cachefly.net/assets/images/OSTV2-SS.png)](https://go.openspeedtest.com/Server "Download OpenSpeedTest-Server V2.1") -#### Fully Optimized and ready to use applications. - Download from the Microsoft Store Download from the Mac App Store Download from the App Store Download from the Google Play Store Download from the Snap Store Download from the Docker Hub Download from the Helm Store Download from GitHub - - -### New features: - - - -1. Stress Test. (Continuous Speed Test) - - - -To enable the stress test. Pass `Stress` or `S` keyword as a URL parameter. - - - -```` - -http://192.168.1.5?Stress=Low - -```` - -After the `STRESS` or `S` keyword, you can specify the number of seconds you need to run the StressTest in seconds, or preset values such as `Low`, `Medium`, `High`, `VeryHigh`, `Extreme`, `Day`, and `Year`. Will run a speed test for `300`,`600`,`900`,`1800`,`3600`,`86400`,`31557600` seconds, respectively. Also, you can feed the first letter of each parameter and its values. - - -```` - -http://192.168.1.5?S=L - -```` - -`S=L` is the same as passing `Stress=low` - -Or you can specify the number of seconds eg:`5000` directly without any preset keywords. - - - -```` - -http://192.168.1.5?Stress=5000 - -```` - - - -2. Run a speed test automatically - - - -Run a speed test automatically on page load. - -```` - -http://192.168.1.5?Run - -```` - -Run a speed test automatically after a few seconds. - -```` - -http://192.168.1.5?Run=10 or http://192.168.1.5?R=10 - -```` - - - -You can pass multiple keywords, and it's not `Case-Sensitive`. - - - -```` - -http://192.168.1.5?Run&Stress=300 OR http://192.168.1.5?R&S=300 - -```` - -This will start a speed test immediately and run for `300 seconds` in each direction. That is 300 seconds for download and `300 seconds` for upload. - - - -3. Save results to a Database - - - -Edit `Index.html` - -```` - -var saveData = true; - -var saveDataURL = "//yourDatabase.Server.com:4500/save?data="; - -```` - -4. Add multiple servers. The app will choose one with the least latency automatically. - - - -Edit `Index.html` - -```` - -var openSpeedTestServerList = [ - -{"ServerName":"Home-Earth", "Download":"/downloading", "Upload":"/upload", "ServerIcon":"DefaultIcon"}, - -{"ServerName":"Home-Mars", "Download":"/downloading", "Upload":"/upload", "ServerIcon":"DefaultIcon"}, - -{"ServerName":"Home-Moon", "Download":"/downloading", "Upload":"/upload", "ServerIcon":"DefaultIcon"} - -]; - -```` - -5. Disable or change Overhead Compensation factor. - - - -```` - -http://192.168.1.5?clean - -```` - -Overhead Compensation factor, This is browser based test, Many Unknowns. Currently 4%. That is within the margin of error. - -You can pass `Clean` or `C` as a URL Parameter and reset Overhead Compensation factor to Zero or set any value between 0 and 4. 1 = 1% to 4 = 4%. - -`Clean` will not accept values above 4, so Compensation is limited to maximum 4%. - - - -6. Change the default limit of 6 parallel HTTP connections to the Server. - - - -```` - -http://192.168.1.5?XHR=3 OR http://192.168.1.5?X=3 - -```` - -Allow the user to Change the default limit of 6 parallel HTTP connections to the Server. `XHR` will Accept values above 1 and max 32 - -pass `XHR` or `X` as a URL Parameter. - - - -7. Select a different server to run a speed test. - - - -```` - -http://192.168.1.5?Host=http://192.168.55.1:90 OR http://192.168.1.5?h=http://192.168.55.1:90 - -```` - -Pass `Host` or `H` as a URL Parameter. - -`HOST` will Accept only valid HTTP URLs like `http://192.168.1.10:3000` or `https://yourHost.com`. - - - -8. Select and run one test at a time, `DOWNLOAD`, `UPLOAD`, or `PING`. - - - -```` - -http://192.168.1.5?Test=Upload OR http://192.168.1.5?T=U - -```` - -`TEST` Allow the user to select and run one test at a time, Download, Upload, or Ping. - -Pass `Test` or `T` as a URL Parameter. - - - -9. Set a PingTimeout dynamically by passing `Out` or `O` as a URL Parameter - - - -```` - -http://192.168.1.5?Out=7000 OR http://192.168.1.5?O=7000 - -```` - -If Server not responded within 5 Seconds for any requests we send ('pingSamples' times) - -We will show `Network Error`, You can change the limit here. - -In milliseconds, if you need to set `6 seconds`. Change the value to `6000`. - - - -10. Set the Number of ping samples by adding `Ping` or `P` as a URL Parameter - - - -```` - -http://192.168.1.5?Ping=500 OR http://192.168.1.5?P=500 - -```` - -More samples = more accurate representation. `Ping = 500` will send `501` requests to server to find the accurate ping value. -Take a look at index.html, you can set a custom ping sample size, threads, upload data size etc. - -## Self-hosted (On-Premise) / (Docker Image/Source Code) -### For Headless large-scale deployments. -You have two options here. If you need a custom deployment, use our source code along with a web server of your choice. I prefer Nginx, and you can find my [Nginx Configuration](https://github.com/openspeedtest/Nginx-Configuration) here. Or you can use our docker image. You can deploy it on your LAN/WAN with or without an active internet connection. - -**This is docker implementation using nginxinc/nginx-unprivileged:stable-alpine. uses significantly fewer resources.** - -- NGINX Docker image that runs NGINX as a non root, unprivileged user. - - ### Docker install instructions: - - Install Docker and run the following command! - -````bash - -sudo docker run --restart=unless-stopped --name openspeedtest -d -p 3000:3000 -p 3001:3001 openspeedtest/latest - -```` -#### Or use docker-compose.yml -```` -version: '3.3' -services: - speedtest: - restart: unless-stopped - container_name: openspeedtest - ports: - - '3000:3000' - - '3001:3001' - image: openspeedtest/latest -```` -- Warning! If you run it behind a **[Reverse Proxy](https://github.com/openspeedtest/Speed-Test/issues/4#issuecomment-1229157193)**, you should increase the `post-body content length` to 35 megabytes. - -- **[Follow our Nginx Config.](https://github.com/openspeedtest/Nginx-Configuration)** - -Now open your browser and direct it to: - -A: For **HTTP** use: `http://YOUR-SERVER-IP:3000` - -B: For **HTTPS** use: `https://YOUR-SERVER-IP:3001` - -#### Container-Port for http is 3000 -If you need to run this image on a different port for `HTTP`, Eg: change to `80` = `-p 80:3000` -#### Container-Port for https is 3001 -If you need to run this image on a different port for `HTTPS`, Eg: change to `443` = `-p 443:3001` - -### Setup Free LetsEncrypt SSL with Automatic Certificate Renewal -***Requirements*** -- PUBLIC IPV4 and/or IPV6 address. -- A domain name that resolves to speed test server's IP address. -- Email ID - -The following command will generate a Let's Encrypt certificate for your domain name and configure a cron job to automatically renew the certificate. - -```` -docker run -e ENABLE_LETSENCRYPT=True -e DOMAIN_NAME=speedtest.yourdomain.com -e USER_EMAIL=you@yourdomain.pro --restart=unless-stopped --name openspeedtest -d -p 80:3000 -p 443:3001 openspeedtest/latest -```` -#### Or use docker-compose.yml -```` -version: '3.3' -services: - speedtest: - environment: - - ENABLE_LETSENCRYPT=True - - DOMAIN_NAME=speedtest.yourdomain.com - - USER_EMAIL=you@yourdomain.pro - restart: unless-stopped - container_name: openspeedtest - ports: - - '80:3000' - - '443:3001' - image: openspeedtest/latest -```` - -### How to Use Your Own Secure Sockets Layer (SSL) Certificate, Self-Signed or Paid? -***Requirements*** -- Folder with your Certificate, Self-Signed or Paid. -- Rename .cet file and .key file to `nginx.crt` & `nginx.key` - - The folder needs to contain: - -- `nginx.crt` - -- `nginx.key` - - -```` -sudo docker run --restart=unless-stopped --name openspeedtest -d -p 3000:3000 -p 3001:3001 openspeedtest/latest -```` - -To mount a folder with your own SSL certificate to this Docker container, append the following line to the above command: - - -````bash - --v /${PATH-TO-YOUR-OWN-SSL-CERTIFICATE}:/etc/ssl/ - -```` - -I am adding a folder with nginx.crt and nginx.key from my desktop by using the following command. - -````bash - -sudo docker run -v /Users/vishnu/Desktop/docker/:/etc/ssl/ --restart=unless-stopped --name openspeedtest -d -p 3000:3000 -p 3001:3001 openspeedtest/latest - -```` -#### Or use docker-compose.yml -```` -version: '3.3' -services: - speedtest: - volumes: - - '/Users/vishnu/Desktop/docker/:/etc/ssl/' - restart: unless-stopped - container_name: openspeedtest - ports: - - '3000:3000' - - '3001:3001' - image: openspeedtest/latest -```` -## Advanced Configuration Options - -- Container Port Configuration - -To enable port changes, set the `CHANGE_CONTAINER_PORTS` environment variable to `"True"` and provide appropriate values for the following variables. - -`CHANGE_CONTAINER_PORTS=True` - -`HTTP_PORT=3000` - -`HTTPS_PORT=3001` - -- Set User - -`SET_USER=101` - -- Only Allow `CORS Request` from listed domains. - -`ALLOW_ONLY=domain1.com;domain2.com;domain3.com` - -- `SET_SERVER_NAME` Display the server name on the UI. - -`SET_SERVER_NAME=HOME-NAS` - - -Docker images run better on Linux Platforms, including your NAS. But if you install docker on macOS or Windows, you may see poor performance. I asked this on Docker forums, and they told me macOS and Windows support is for Development purposes only. For Production, you need to use any Linux Platform. - -The same Story goes for Windows NGINX. Nginx uses only one worker even if you specify n number of worker processes. They will show in Task Manager, but the system will only use one. I got this information directly from the Nginx website. - - - - - -## Why do you need to Create Your Own SpeedTest Server? - -You can run OpenSpeedTest Server in your Home Lab, Office Server or Cloud Server. So that you or employees who work from home can run a speed test to your office and make sure they can run everything smoothly. - - - -**Choosing between ISP1 & ISP2.** - - - -Sometimes your ISP2 is Faster than ISP1 when you test your speed on popular speed test sites. But when you connect to your Home/Office/Cloud, that slower connection may perform better. The only way to find out is to run a speed test against your infrastructure. - - - -**Troubleshooting network issues.** - - - -It is common even when your Internet connection is working fine, but some of the devices in your network may experience trouble getting decent connectivity to the internet. The issue might be the wrong VLAN ID or Faulty Switch. If you run a Local network speed test, you can find and fix these issues easily. - - - -**Before you add a repeater.** - - - -Most repeaters will reduce your network speed by 50%, so if you put it far away, it will perform worst, and if you put it too close, you will not get enough coverage if you run a Local Network speed test. Depending on the application requirements, you can decide exactly where you need to put your repeater. - - - -**Browsing experience.** - - - -Many useful browser extensions are out there that we all know and love. But some of them are really slowing you down for **few seconds per page you visit**. You may see good performance when you test your network performance via File Transfer or Command-line utilities, but you may experience poor performance when browsing the internet. This is due to a bad browser configuration that including unwanted extensions installed. From my experience, only keep the one you are going to use every single day. Extension that you may use once in a while should be removed or disabled for maximum performance. If you see poor performance, try OpenSpeedTest from Private Window or Incognito Window. **This tool can be used to check the browser performance and impact of Extensions on your browsing experience.** - - - -**No client-side software or plugin is required. You can run a network speed test from any device with a [Web Browser that is IE10 or newer.](https://www.youtube.com/watch?v=9f-OM_WQ7Bw&list=PLt-deStxFJOMEAs2O1lJhscMNzcg9E3Po&index=1)** - -- Like this Project? Please **Donate NOW & Keep us Alive** -> https://go.openspeedtest.com/Donate - -MIT License - -Copyright (c) 2013 - 2023 OpenSpeedTest™ - -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 permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/css/app.css b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/css/app.css deleted file mode 100644 index 39fa9393..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/css/app.css +++ /dev/null @@ -1,382 +0,0 @@ -/* roboto-regular - latin */ -@font-face { - font-family: "Roboto"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("..fonts/roboto-v30-latin-regular.eot"); /* IE9 Compat Modes */ - src: local(""), - url("../fonts/roboto-v30-latin-regular.eot?#iefix") - format("embedded-opentype"), - /* IE6-IE8 */ url("../fonts/roboto-v30-latin-regular.woff2") - format("woff2"), - /* Super Modern Browsers */ - url("../fonts/roboto-v30-latin-regular.woff") format("woff"), - /* Modern Browsers */ url("../fonts/roboto-v30-latin-regular.ttf") - format("truetype"), - /* Safari, Android, iOS */ - url("../fonts/roboto-v30-latin-regular.svg#Roboto") format("svg"); /* Legacy iOS */ -} -/* roboto-500 - latin */ -@font-face { - font-family: "Roboto"; - font-style: normal; - font-weight: 500; - font-display: swap; - src: url("assets/fonts/roboto-v30-latin-500.eot"); /* IE9 Compat Modes */ - src: local(""), - url("assets/fonts/roboto-v30-latin-500.eot?#iefix") - format("embedded-opentype"), - /* IE6-IE8 */ url("../fonts/roboto-v30-latin-500.woff2") - format("woff2"), - /* Super Modern Browsers */ url("../fonts/roboto-v30-latin-500.woff") - format("woff"), - /* Modern Browsers */ url("../fonts/roboto-v30-latin-500.ttf") - format("truetype"), - /* Safari, Android, iOS */ - url("../fonts/roboto-v30-latin-500.svg#Roboto") format("svg"); /* Legacy iOS */ -} - -body { - margin: 0px; - padding: 0px; - display: block; -} - -::-webkit-scrollbar { - display: none; -} -html { - -ms-overflow-style: none; - scrollbar-width: none; -} - -.Credits { -color: rgb(125 119 119); -text-align: center; -font-size:14px; -font-family: Roboto-Medium, Roboto; -font-weight: 500; -} -.Credits a { - text-decoration: none; - color: rgb(113, 113, 113); -} -.Credits a:hover { -color: #14b0fe; -} - -.ConnectError { - display: none; - -} - -.openSpeedtestApp { - - height: 100vh; - width: 100vw; - display: none; - overflow: hidden; -} -.main-Gaugebg { - fill: none; - stroke: rgb(231, 231, 232); - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 22px; - stroke-dasharray: 681; -} -.main-GaugeBlue { - fill: none; - stroke: url(#gradient); - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 22px; - stroke-dasharray: 681; - stroke-opacity: 0; -} -.main-GaugeWhite { - fill: none; - stroke: rgb(255, 255, 255); - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 15px; - stroke-dasharray: 0, 681; - stroke-dashoffset: 1; - stroke-opacity: 0; -} -.oDo-Meter { - font-size: 16.633283615112305px; - fill: gray; - font-family: Roboto-Medium, Roboto; - font-weight: 500; -} -.oDoLive-Speed { - font-size: 28px; - fill: #201e1e; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} - -.oDoLive-Status { - font-size: 10px; - fill: #d2d1d2; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} -.uiBg { - fill: #d2d1d2; -} -.progressbg { - stroke: rgb(231, 231, 232); - stroke-width: 8px; - stroke-linecap: round; - stroke-linejoin: round; - stroke-dasharray: 400; - stroke-dashoffset: 0; -} -.Cards { - fill: #f2f2f2; -} -.Symbol { - fill: url(#gradient); -} -.rtext { - font-size: 12px; - fill: #333; - font-family: Roboto-Medium, Roboto; - font-weight: 500; -} -.rtextnum { - font-size: 23px; - fill: #201e1e; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} -.rtextmbms { - font-size: 12px; - fill: #5f5f5f; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} -.jitter-Mob { - font-size: 9px; - fill: #5f5f5f; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} -.startButton { - fill: url(#RadialGradient1); - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - -webkit-tap-highlight-color: transparent; - cursor: pointer; - pointer-events: visible; -} -.buttonTxt { - font-size: 40px; - fill: #ffffff; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} - -.intro-Progress { - stroke: #56c4fb; - stroke-width: 8px; - stroke-linecap: round; - stroke-linejoin: round; - stroke-dasharray: 400; - stroke-dashoffset: 0; -} -.progressElmstart { - stroke: #56c4fb; - stroke-width: 8px; - stroke-linecap: round; - stroke-linejoin: round; - stroke-dasharray: 400; - stroke-dashoffset: 0; - display: block; -} -.Startsettings { - fill: url(#RadialGradient1); - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - -webkit-tap-highlight-color: transparent; - cursor: pointer; - pointer-events: visible; - opacity: 0.1; - transition: opacity 1s ease-in-out; -} -.Startsettings:hover { - opacity: 1; -} - -.progressbg { - stroke: rgb(231, 231, 232); - stroke-width: 8px; - stroke-linecap: round; - stroke-linejoin: round; - stroke-dasharray: 400; - stroke-dashoffset: 0; -} -.deskStart { - fill: none; - stroke: rgb(231, 231, 232); - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 22px; - stroke-dasharray: 681; - stroke: url(#gradient); -} -#UI-Desk { - display: none; -} -#UI-Mob { - display: none; -} -.oDoTop-Speed { - font-size: 16.96px; - fill: gray; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: end; -} -#upSymbolDesk { - fill: #14b0fe; - display: none; -} -#downSymbolDesk { - fill: #14b0fe; - display: none; -} -#upSymbolMob { - fill: #14b0fe; - display: none; -} -#downSymbolMob { - fill: #14b0fe; - display: none; -} - -#ipMob { - font-size: 15px; - fill: #201e1e; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; - display: none; -} -#ipDesk { - font-size: 15px; - fill: #201e1e; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; - display: none; -} - -.spinner { - position: absolute; - z-index: 999; - top: 50vh; - left: 50vw; - text-align: center; -} - -.spinner > div { - width: 20px; - height: 20px; - background-color: #2196f3; - - border-radius: 100%; - display: inline-block; - -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both; - animation: sk-bouncedelay 1.4s infinite ease-in-out both; -} - -.spinner .bounce1 { - -webkit-animation-delay: -0.32s; - animation-delay: -0.32s; -} - -.spinner .bounce2 { - -webkit-animation-delay: -0.16s; - animation-delay: -0.16s; -} - -@-webkit-keyframes sk-bouncedelay { - 0%, - 80%, - 100% { - -webkit-transform: scale(0); - } - 40% { - -webkit-transform: scale(1); - } -} - -@keyframes sk-bouncedelay { - 0%, - 80%, - 100% { - -webkit-transform: scale(0); - transform: scale(0); - } - 40% { - -webkit-transform: scale(1); - transform: scale(1); - } -} - -.darkmode { - margin-bottom: -15px; - fill: #75757a99; - padding-top: 16px; - display: none; - cursor: pointer; - margin-right: 30px; -} -#daymode { - margin-right: 40px; -} -.darkmode:hover { - fill: #000000; -} - -.Mobile, -.Desktop { - visibility: hidden; - width: 100vw; - height: 100vh; -} - -@media only screen and (orientation: landscape) { - .Mobile { - visibility: hidden; - } - .Desktop { - visibility: visible; - } -} - -@media only screen and (orientation: portrait) { - .spinner { - top: 42vh; - left: 42vw; - } - .Mobile { - visibility: visible; - } - .Desktop { - visibility: hidden; - } -} -@media only screen and (max-width: 300px) { - .Credits{ - display: none; - } -} diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/css/darkmode.css b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/css/darkmode.css deleted file mode 100644 index 14fef300..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/css/darkmode.css +++ /dev/null @@ -1,45 +0,0 @@ -body { - background-color: #181818; -} -#ipDesk { - fill: aliceblue; -} - -.oDoLive-Speed { - fill: #ffffff; -} -.oDoLive-Status { - fill: aliceblue; -} -#ipMob { - fill: aliceblue; -} -.rtextnum { - fill: #ffffff; -} -.rtextmbms { - fill: #ffffff; -} -.rtext { - fill: #ffffff; -} -.Cards { - fill: #000000; -} - -.main-Gaugebg { - stroke: #000000; -} -.uiBg { - fill: #000000; -} -.progressbg { - stroke: #202020; -} -.jitter-Mob { - fill: #ffffff; -} - -.ConnectError { - fill: #ffffff; -} diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-500.eot b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-500.eot deleted file mode 100644 index fec03612..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-500.eot and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-500.svg b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-500.svg deleted file mode 100644 index 67eecf44..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-500.svg +++ /dev/null @@ -1,305 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-500.ttf b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-500.ttf deleted file mode 100644 index 4f515e2a..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-500.ttf and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-500.woff b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-500.woff deleted file mode 100644 index d39bb52a..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-500.woff and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-500.woff2 b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-500.woff2 deleted file mode 100644 index 29342a8d..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-500.woff2 and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-regular.eot b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-regular.eot deleted file mode 100644 index c244b089..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-regular.eot and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-regular.svg b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-regular.svg deleted file mode 100644 index 627f5a36..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-regular.svg +++ /dev/null @@ -1,308 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-regular.ttf b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-regular.ttf deleted file mode 100644 index d0e63254..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-regular.ttf and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-regular.woff b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-regular.woff deleted file mode 100644 index 9eaa94f9..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-regular.woff and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-regular.woff2 b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-regular.woff2 deleted file mode 100644 index 020729ef..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/fonts/roboto-v30-latin-regular.woff2 and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/app.svg b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/app.svg deleted file mode 100644 index 01441ac0..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/app.svg +++ /dev/null @@ -1,459 +0,0 @@ - - - - - - - - - - - - - 0 - .5 - 1 - 10 - 100 - 500 - 1000+ - ... - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DOWNLOAD - - - - - UPLOAD - - - - - - - - - - PING - - - - JITTER - - - --- - Mbps - - - --- - Mbps - - - -- - ms - - - -- - ms - - - - - - - - - -- - Jitter - -- - ms - - - - - - - - - - - - Start - - - - - - - - - - - - - - - - - - - Network Error - - - - SpeedTest by OpenSpeedTest™️ - - - - - - - - - - - - - - - - - - - - - HTML5 Network Performance Estimation Tool. by OpenSpeedTest™ - - - - - - The download speed is how fast you can pull data from the server to you in the form of images, videos, text and more. Activities such as listening to music on Spotify, downloading large files or streaming videos on Netflix all require you to download data. - - - The upload speed is how fast you send data from you to others in the form of images, videos, text and more. Activities such as uploading to YouTube or Sending email, playing live games like PubG, Voice and Video calling a friend require fast upload speeds for you to send data to someone else’s server. - - - Ping "Round-trip time" is more or less well defined as the network delay from point A to B and back. This is the sum of all encoding, queueing, processing, decoding, and propagation delays in both directions. Essentially, it's the delay when A may be expecting an answer from B for a request that requires very little processing. A fast ping means a more responsive connection, especially in applications where timing is everything (like video games). Ping is measured in Milliseconds (ms). Jitter is simply the difference in Ping "Round-trip time" . In other words, jitter is measuring time difference in packet inter-arrival time. Jitter helps diagnose internet connection stability. The higher the jitter value, the worse the stability of the connection. - - - - - - - - - Download speed is measured in megabits per second (Mbps). - - - Upload speed is measured in megabits per second (Mbps). - - - - - Ping is measured in Milliseconds (ms). - - - Jitter is measured in Milliseconds (ms). - - - - - Shows you real-time network traffic graph for Upload! - - - Shows you real-time network traffic graph for Download! - - - Your download speed! - - - Your upload speed! - - - Your ping! - - - Your jitter! - - - - - - - - - - Download speed is measured in megabits per second (Mbps). - - - Upload speed is measured in megabits per second (Mbps). - - - - - Ping is measured in Milliseconds (ms). - - - Jitter is measured in Milliseconds (ms). - - - - - Shows you real-time network traffic graph for Upload! - - - Shows you real-time network traffic graph for Download! - - - Your download speed! - - - Your upload speed! - - - Your ping! - - - Your jitter! - - - - - Click here to Run a Speed Test! Or Press "Enter" - - - Options - - - - - - HTML5 Network Performance Estimation Tool. by OpenSpeedTest™ - - - - - - The download speed is how fast you can pull data from the server to you in the form of images, videos, text and more. Activities such as listening to music on Spotify, downloading large files or streaming videos on Netflix all require you to download data. - - - The upload speed is how fast you send data from you to others in the form of images, videos, text and more. Activities such as uploading to YouTube or Sending email, playing live games like PubG, Voice and Video calling a friend require fast upload speeds for you to send data to someone else’s server. - - - Ping "Round-trip time" is more or less well defined as the network delay from point A to B and back. This is the sum of all encoding, queueing, processing, decoding, and propagation delays in both directions. Essentially, it's the delay when A may be expecting an answer from B for a request that requires very little processing. A fast ping means a more responsive connection, especially in applications where timing is everything (like video games). Ping is measured in Milliseconds (ms). Jitter is simply the difference in Ping "Round-trip time" . In other words, jitter is measuring time difference in packet inter-arrival time. Jitter helps diagnose internet connection stability. The higher the jitter value, the worse the stability of the connection. - - - - - - - - - Download speed is measured in megabits per second (Mbps). - - - Upload speed is measured in megabits per second (Mbps). - - - Ping is measured in Milliseconds (ms). - - - - - - Shows you real-time network traffic graph for Download! - - - Upload speed is measured in megabits per second (Mbps). - - - Your ping! and Jitter - - - Your upload speed! - - - Your download speed! - - - - - - - - - Click here to run a speed! - - - Options - - - - - - - - - - - - - - \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/android-chrome-192x192.png b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/android-chrome-192x192.png deleted file mode 100644 index df78b83c..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/android-chrome-192x192.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/android-chrome-512x512.png b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/android-chrome-512x512.png deleted file mode 100644 index 559d3eaf..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/android-chrome-512x512.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/apple-touch-icon.png b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/apple-touch-icon.png deleted file mode 100644 index 14aec788..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/apple-touch-icon.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/browserconfig.xml b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/browserconfig.xml deleted file mode 100644 index ef1658c6..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/browserconfig.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - #ffc40d - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/favicon-16x16.png b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/favicon-16x16.png deleted file mode 100644 index 0dd76a80..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/favicon-16x16.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/favicon-32x32.png b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/favicon-32x32.png deleted file mode 100644 index e3ad7510..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/favicon-32x32.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/favicon.ico b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/favicon.ico deleted file mode 100644 index bebc1524..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/favicon.ico and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/launcher-icon-1x.png b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/launcher-icon-1x.png deleted file mode 100644 index 50bdeae8..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/launcher-icon-1x.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/launcher-icon-2x.png b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/launcher-icon-2x.png deleted file mode 100644 index 98687998..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/launcher-icon-2x.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/launcher-icon-3x.png b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/launcher-icon-3x.png deleted file mode 100644 index b612922b..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/launcher-icon-3x.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/launcher-icon-4x.png b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/launcher-icon-4x.png deleted file mode 100644 index 5936c147..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/launcher-icon-4x.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/mstile-150x150.png b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/mstile-150x150.png deleted file mode 100644 index eb66ae2b..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/mstile-150x150.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/safari-pinned-tab.svg b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/safari-pinned-tab.svg deleted file mode 100644 index e525e9b4..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/safari-pinned-tab.svg +++ /dev/null @@ -1,108 +0,0 @@ - - - - -Created by potrace 1.11, written by Peter Selinger 2001-2013 - - - - - - - - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/site.webmanifest b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/site.webmanifest deleted file mode 100644 index 058491fb..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/images/icons/site.webmanifest +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "OpenSpeedTest", - "short_name": "Speed Test", - "icons": [ - { - "src": "/assets/images/icons/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/assets/images/icons/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone", - "start_url": "/" - - -} diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/js/app-2.5.4.js b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/js/app-2.5.4.js deleted file mode 100644 index f5b5b6e4..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/js/app-2.5.4.js +++ /dev/null @@ -1,1427 +0,0 @@ -/* - Official Website : https://OpenSpeedTest.COM | Email: support@openspeedtest.com - Developed by : Vishnu | https://Vishnu.Pro | Email : me@vishnu.pro - Like this Project? Please Donate NOW & Keep us Alive -> https://go.openspeedtest.com/Donate - Speed Test by OpenSpeedTest™️ is Free and Open-Source Software (FOSS) with MIT License. - Read full license terms @ http://go.openspeedtest.com/License - If you have any Questions, ideas or Comments Please Send it via -> https://go.openspeedtest.com/SendMessage -*/ -window.onload = function() { - var appSVG = document.getElementById("OpenSpeedTest-UI"); - appSVG.parentNode.replaceChild(appSVG.contentDocument.documentElement, appSVG); - ostOnload(); - OpenSpeedTest.Start(); -}; -(function(OpenSpeedTest) { - var Status; - var ProG; - var Callback = function(callback) { - if (callback && typeof callback === "function") { - callback(); - } - }; - function _(el) { - if (!(this instanceof _)) { - return new _(el); - } - this.el = document.getElementById(el); - } - _.prototype.fade = function fade(type, ms, callback00) { - var isIn = type === "in", opacity = isIn ? 0 : 1, interval = 14, duration = ms, gap = interval / duration, self = this; - if (isIn) { - self.el.style.display = "block"; - self.el.style.opacity = opacity; - } - function func() { - opacity = isIn ? opacity + gap : opacity - gap; - self.el.style.opacity = opacity; - if (opacity <= 0) { - self.el.style.display = "none"; - } - if (opacity <= 0 || opacity >= 1) { - window.clearInterval(fading, Callback(callback00)); - } - } - var fading = window.setInterval(func, interval); - }; - var easeOutQuint = function(t, b, c, d) { - t /= d; - t--; - return c * (t * t * t * t * t + 1) + b; - }; - var easeOutCubic = function(t, b, c, d) { - t /= d; - t--; - return c * (t * t * t + 1) + b; - }; - var openSpeedtestShow = function() { - this.YourIP = _("YourIP"); - this.ipDesk = _("ipDesk"); - this.ipMob = _("ipMob"); - this.downSymbolDesk = _("downSymbolDesk"); - this.upSymbolDesk = _("upSymbolDesk"); - this.upSymbolMob = _("upSymbolMob"); - this.downSymbolMob = _("downSymbolMob"); - this.settingsMob = _("settingsMob"); - this.settingsDesk = _("settingsDesk"); - this.oDoLiveStatus = _("oDoLiveStatus"); - this.ConnectErrorMob = _("ConnectErrorMob"); - this.ConnectErrorDesk = _("ConnectErrorDesk"); - this.downResult = _("downResult"); - this.upRestxt = _("upRestxt"); - this.pingResult = _("pingResult"); - this.jitterDesk = _("jitterDesk"); - this.pingMobres = _("pingMobres"); - this.JitterResultMon = _("JitterResultMon"); - this.JitterResultms = _("JitterResultms"); - this.UI_Desk = _("UI-Desk"); - this.UI_Mob = _("UI-Mob"); - this.oDoTopSpeed = _("oDoTopSpeed"); - this.startButtonMob = _("startButtonMob"); - this.startButtonDesk = _("startButtonDesk"); - this.intro_Desk = _("intro-Desk"); - this.intro_Mob = _("intro-Mob"); - this.loader = _("loading_app"); - this.OpenSpeedtest = _("OpenSpeedtest"); - this.mainGaugebg_Desk = _("mainGaugebg-Desk"); - this.mainGaugeBlue_Desk = _("mainGaugeBlue-Desk"); - this.mainGaugeWhite_Desk = _("mainGaugeWhite-Desk"); - this.mainGaugebg_Mob = _("mainGaugebg-Mob"); - this.mainGaugeBlue_Mob = _("mainGaugeBlue-Mob"); - this.mainGaugeWhite_Mob = _("mainGaugeWhite-Mob"); - this.oDoLiveSpeed = _("oDoLiveSpeed"); - this.progressStatus_Mob = _("progressStatus-Mob"); - this.progressStatus_Desk = _("progressStatus-Desk"); - this.graphc1 = _("graphc1"); - this.graphc2 = _("graphc2"); - this.graphMob2 = _("graphMob2"); - this.graphMob1 = _("graphMob1"); - this.text = _("text"); - this.scale = [{degree:680, value:0}, {degree:570, value:0.5}, {degree:460, value:1}, {degree:337, value:10}, {degree:220, value:100}, {degree:115, value:500}, {degree:0, value:1000},]; - this.element = ""; - this.chart = ""; - this.polygon = ""; - this.width = 200; - this.height = 50; - this.maxValue = 0; - this.values = []; - this.points = []; - this.vSteps = 5; - this.measurements = []; - this.points = []; - }; - openSpeedtestShow.prototype.reset = function() { - this.element = ""; - this.chart = ""; - this.polygon = ""; - this.width = 200; - this.height = 50; - this.maxValue = 0; - this.values = []; - this.points = []; - this.vSteps = 5; - this.measurements = []; - this.points = []; - }; - openSpeedtestShow.prototype.ip = function() { - var Self = this; - if (Self.ipDesk.el.style.display === "block") { - Self.ipDesk.el.style.display = "none"; - Self.ipMob.el.style.display = "none"; - } else { - Self.ipDesk.el.style.display = "block"; - Self.ipMob.el.style.display = "block"; - } - }; - openSpeedtestShow.prototype.prePing = function() { - this.loader.fade("out", 500); - this.OpenSpeedtest.fade("in", 1000); - }; - openSpeedtestShow.prototype.app = function() { - this.loader.fade("out", 500, this.ShowAppIntro()); - }; - openSpeedtestShow.prototype.ShowAppIntro = function() { - this.OpenSpeedtest.fade("in", 1000); - }; - openSpeedtestShow.prototype.userInterface = function() { - var Self = this; - this.intro_Desk.fade("out", 1000); - this.intro_Mob.fade("out", 1000, this.ShowUI()); - }; - openSpeedtestShow.prototype.ShowUI = function() { - this.UI_Desk.fade("in", 1000); - this.UI_Mob.fade("in", 1000, uiLoaded); - function uiLoaded(argument) { - Status = "Loaded"; - console.log("Developed by Vishnu. Email --\x3e me@vishnu.pro"); - } - }; - openSpeedtestShow.prototype.Symbol = function(dir) { - if (dir == 0) { - this.downSymbolMob.el.style.display = "block"; - this.downSymbolDesk.el.style.display = "block"; - this.upSymbolMob.el.style.display = "none"; - this.upSymbolDesk.el.style.display = "none"; - } - if (dir == 1) { - this.downSymbolMob.el.style.display = "none"; - this.downSymbolDesk.el.style.display = "none"; - this.upSymbolMob.el.style.display = "block"; - this.upSymbolDesk.el.style.display = "block"; - } - if (dir == 2) { - this.downSymbolMob.el.style.display = "none"; - this.downSymbolDesk.el.style.display = "none"; - this.upSymbolMob.el.style.display = "none"; - this.upSymbolDesk.el.style.display = "none"; - } - }; - openSpeedtestShow.prototype.Graph = function(speed, select) { - if (!("remove" in Element.prototype)) { - Element.prototype.remove = function() { - if (this.parentNode) { - this.parentNode.removeChild(this); - } - }; - } - var Self = this; - var Remove; - if (select === 0) { - var Graphelement = this.graphc1.el; - Remove = "line"; - this.graphMob2.el.style.display = "none"; - this.graphMob1.el.style.display = "block"; - } else { - Graphelement = this.graphc2.el; - Remove = "line2"; - this.graphMob1.el.style.display = "none"; - this.graphMob2.el.style.display = "block"; - } - if (!isNaN(speed)) { - this.values.push(speed); - } else { - this.values.push(""); - } - function calcMeasure() { - for (x = 0; x < Self.vSteps; x++) { - var measurement = Math.ceil(Self.maxValue / Self.vSteps * (x + 1)); - Self.measurements.push(measurement); - } - Self.measurements.reverse(); - } - function createChart(element, values) { - calcMaxValue(); - calcPoints(); - calcMeasure(); - var removeLine = document.getElementsByClassName(Remove); - while (removeLine.length > 0) { - removeLine[0].remove(); - } - Self.polygon = document.createElementNS("http://www.w3.org/2000/svg", "polygon"); - Self.polygon.setAttribute("points", Self.points); - Self.polygon.setAttribute("class", Remove); - if (Self.values.length > 1) { - Graphelement.appendChild(Self.polygon); - } - } - function calcPoints() { - if (Self.values.length > 1) { - var points = "0," + Self.height + " "; - for (x = 0; x < Self.values.length; x++) { - var perc = Self.values[x] / Self.maxValue; - var steps = 130 / (Self.values.length - 1); - var point = (steps * x).toFixed(2) + "," + (Self.height - Self.height * perc).toFixed(2) + " "; - points += point; - } - points += "130," + Self.height; - Self.points = points; - } - } - var x; - function calcMaxValue() { - Self.maxValue = 0; - for (x = 0; x < Self.values.length; x++) { - if (Self.values[x] > Self.maxValue) { - Self.maxValue = Self.values[x]; - } - } - Self.maxValue = Math.ceil(Self.maxValue); - } - if (speed > 0) { - createChart(Graphelement, speed); - } - }; - openSpeedtestShow.prototype.progress = function(Switch, duration) { - var Self = this; - var Stop = duration; - var Stage = Switch; - var currTime = Date.now(); - var chan2 = 0 - 400; - var interval = setInterval(function() { - var timeNow = (Date.now() - currTime) / 1000; - var toLeft = easeOutCubic(timeNow, 400, 400, Stop); - var toRight = easeOutCubic(timeNow, 400, chan2, Stop); - if (Stage) { - Self.progressStatus_Desk.el.style.strokeDashoffset = toLeft; - Self.progressStatus_Mob.el.style.strokeDashoffset = toLeft; - } else { - Self.progressStatus_Desk.el.style.strokeDashoffset = toRight; - Self.progressStatus_Mob.el.style.strokeDashoffset = toRight; - } - if (timeNow >= Stop) { - clearInterval(interval); - ProG = "done"; - Self.progressStatus_Desk.el.style.strokeDashoffset = 800; - Self.progressStatus_Mob.el.style.strokeDashoffset = 800; - } - }, 14); - }; - openSpeedtestShow.prototype.mainGaugeProgress = function(currentSpeed) { - var Self = this; - var speed = currentSpeed; - if (speed < 0) { - speed = 0; - } - var mainGaugeOffset = Self.getNonlinearDegree(speed); - if (currentSpeed > 0) { - this.mainGaugeBlue_Desk.el.style.strokeOpacity = 1; - this.mainGaugeWhite_Desk.el.style.strokeOpacity = 1; - this.mainGaugeBlue_Mob.el.style.strokeOpacity = 1; - this.mainGaugeWhite_Mob.el.style.strokeOpacity = 1; - this.mainGaugeBlue_Desk.el.style.strokeDashoffset = mainGaugeOffset; - this.mainGaugeWhite_Desk.el.style.strokeDashoffset = mainGaugeOffset == 0 ? 1 : mainGaugeOffset + 1; - this.mainGaugeBlue_Mob.el.style.strokeDashoffset = mainGaugeOffset; - this.mainGaugeWhite_Mob.el.style.strokeDashoffset = mainGaugeOffset == 0 ? 1 : mainGaugeOffset + 1; - } - if (mainGaugeOffset == 0 && speed > 1000) { - this.mainGaugeBlue_Mob.el.style.strokeDashoffset = mainGaugeOffset >= 681 ? 681 : mainGaugeOffset; - this.mainGaugeWhite_Mob.el.style.strokeDashoffset = mainGaugeOffset == 0 ? 1 : mainGaugeOffset + 1; - this.mainGaugeWhite_Desk.el.style.strokeDashoffset = mainGaugeOffset == 0 ? 1 : mainGaugeOffset + 1; - this.mainGaugeBlue_Desk.el.style.strokeDashoffset = mainGaugeOffset >= 681 ? 681 : mainGaugeOffset; - } else if (mainGaugeOffset == 0 && speed <= 0) { - this.mainGaugeBlue_Mob.el.style.strokeDashoffset = 681.1; - this.mainGaugeWhite_Mob.el.style.strokeDashoffset = 0.1; - this.mainGaugeWhite_Desk.el.style.strokeDashoffset = 0.1; - this.mainGaugeBlue_Desk.el.style.strokeDashoffset = 681.1; - } - }; - openSpeedtestShow.prototype.showStatus = function(e) { - this.oDoLiveStatus.el.textContent = e; - }; - openSpeedtestShow.prototype.ConnectionError = function() { - this.ConnectErrorMob.el.style.display = "block"; - this.ConnectErrorDesk.el.style.display = "block"; - }; - openSpeedtestShow.prototype.uploadResult = function(upload) { - if (upload < 1) { - this.upRestxt.el.textContent = upload.toFixed(3); - } - if (upload >= 1 && upload < 9999) { - this.upRestxt.el.textContent = upload.toFixed(1); - } - if (upload >= 10000 && upload < 99999) { - this.upRestxt.el.textContent = upload.toFixed(1); - this.upRestxt.el.style.fontSize = "20px"; - } - if (upload >= 100000) { - this.upRestxt.el.textContent = upload.toFixed(1); - this.upRestxt.el.style.fontSize = "18px"; - } - }; - openSpeedtestShow.prototype.pingResults = function(data, Display) { - var ShowData = data; - if (Display === "Ping") { - if (ShowData >= 1 && ShowData < 10000) { - this.pingResult.el.textContent = Math.floor(ShowData); - this.pingMobres.el.textContent = Math.floor(ShowData); - } else if (ShowData >= 0 && ShowData < 1) { - if (ShowData == 0) { - ShowData = 0; - } - this.pingResult.el.textContent = ShowData; - this.pingMobres.el.textContent = ShowData; - } - } - if (Display === "Error") { - this.oDoLiveSpeed.el.textContent = ShowData; - } - }; - openSpeedtestShow.prototype.downloadResult = function(download) { - if (download < 1) { - this.downResult.el.textContent = download.toFixed(3); - } - if (download >= 1 && download < 9999) { - this.downResult.el.textContent = download.toFixed(1); - } - if (download >= 10000 && download < 99999) { - this.downResult.el.textContent = download.toFixed(1); - this.downResult.el.style.fontSize = "20px"; - } - if (download >= 100000) { - this.downResult.el.textContent = download.toFixed(1); - this.downResult.el.style.fontSize = "18px"; - } - }; - openSpeedtestShow.prototype.jitterResult = function(data, Display) { - var ShowData = data; - if (Display === "Jitter") { - if (ShowData >= 1 && ShowData < 10000) { - this.jitterDesk.el.textContent = Math.floor(ShowData); - if (ShowData >= 1 && ShowData < 100) { - this.JitterResultMon.el.textContent = Math.floor(ShowData); - } - if (ShowData >= 100) { - var kData = (ShowData / 1000).toFixed(1); - this.JitterResultMon.el.textContent = kData + "k"; - } - } else if (ShowData >= 0 && ShowData < 1) { - if (ShowData == 0) { - ShowData = 0; - } - this.jitterDesk.el.textContent = ShowData; - this.JitterResultMon.el.textContent = ShowData; - } - } - }; - openSpeedtestShow.prototype.LiveSpeed = function(data, Display) { - var ShowData = data; - if (Display === "countDown") { - var speed = ShowData.toFixed(0); - this.oDoLiveSpeed.el.textContent = speed; - return; - } - if (Display === "speedToZero") { - if (typeof ShowData == "number") { - ShowData = ShowData.toFixed(1); - } - if (ShowData <= 0) { - ShowData = 0; - } - this.oDoLiveSpeed.el.textContent = ShowData; - this.oDoTopSpeed.el.textContent = "1000+"; - this.oDoTopSpeed.el.style.fontSize = "16.9px"; - this.oDoTopSpeed.el.style.fill = "gray"; - return; - } - if (Display === "Ping") { - if (ShowData >= 1 && ShowData < 10000) { - this.oDoLiveSpeed.el.textContent = Math.floor(ShowData); - } else if (ShowData >= 0 && ShowData < 1) { - if (ShowData == 0) { - ShowData = 0; - } - this.oDoLiveSpeed.el.textContent = ShowData; - } - } else { - if (ShowData == 0) { - var speed = ShowData.toFixed(0); - this.oDoLiveSpeed.el.textContent = speed; - } - if (ShowData <= 1 && ShowData > 0) { - var speed = ShowData.toFixed(3); - this.oDoLiveSpeed.el.textContent = speed; - } - if (ShowData > 1) { - var speed = ShowData.toFixed(1); - this.oDoLiveSpeed.el.textContent = speed; - } - if (ShowData <= 1000) { - this.oDoTopSpeed.el.textContent = "1000+"; - this.oDoTopSpeed.el.style.fontSize = "16.9px"; - this.oDoTopSpeed.el.style.fill = "gray"; - } - if (ShowData >= 1010) { - this.oDoTopSpeed.el.textContent = Math.floor(ShowData / 1010) * 1000 + "+"; - this.oDoTopSpeed.el.style.fill = "gray"; - this.oDoTopSpeed.el.style.fontSize = "17.2px"; - } - } - }; - openSpeedtestShow.prototype.GaugeProgresstoZero = function(currentSpeed, status) { - var speed = currentSpeed; - var Self = this; - var duration = 3; - if (speed >= 0) { - var time = Date.now(); - var SpeedtoZero = 0 - speed; - var interval = setInterval(function() { - var timeNow = (Date.now() - time) / 1000; - var speedToZero = easeOutQuint(timeNow, speed, SpeedtoZero, duration); - Self.LiveSpeed(speedToZero, "speedToZero"); - Self.mainGaugeProgress(speedToZero); - if (timeNow >= duration || speedToZero <= 0) { - clearInterval(interval); - Self.LiveSpeed(0, "speedToZero"); - Self.mainGaugeProgress(0); - Status = status; - } - }, 16); - } - }; - openSpeedtestShow.prototype.getNonlinearDegree = function(mega_bps) { - var i = 0; - if (0 == mega_bps || mega_bps <= 0 || isNaN(mega_bps)) { - return 0; - } - while (i < this.scale.length) { - if (mega_bps > this.scale[i].value) { - i++; - } else { - return this.scale[i - 1].degree + (mega_bps - this.scale[i - 1].value) * (this.scale[i].degree - this.scale[i - 1].degree) / (this.scale[i].value - this.scale[i - 1].value); - } - } - return this.scale[this.scale.length - 1].degree; - }; - var openSpeedtestGet = function() { - this.OverAllTimeAvg = window.performance.now(); - this.SpeedSamples = []; - this.FinalSpeed; - }; - openSpeedtestGet.prototype.reset = function() { - this.OverAllTimeAvg = window.performance.now(); - this.SpeedSamples = []; - this.FinalSpeed = 0; - }; - openSpeedtestGet.prototype.ArraySum = function(Arr) { - var array = Arr; - if (array) { - var sum = array.reduce(function(A, B) { - if (typeof A === "number" && typeof B === "number") { - return A + B; - } - }, 0); - return sum; - } else { - return 0; - } - }; - openSpeedtestGet.prototype.AvgSpeed = function(Livespeed, Start, duration) { - var Self = this; - this.timeNow = (window.performance.now() - this.OverAllTimeAvg) / 1000; - this.FinalSpeed; - var StartRecoding = Start; - StartRecoding = duration - StartRecoding; - if (this.timeNow >= StartRecoding) { - if (Livespeed > 0) { - this.SpeedSamples.push(Livespeed); - } - Self.FinalSpeed = Self.ArraySum(Self.SpeedSamples) / Self.SpeedSamples.length; - } - return Self.FinalSpeed; - }; - openSpeedtestGet.prototype.uRandom = function(size, callback) { - var size = size; - var randomValue = new Uint32Array(262144); - function getRandom() { - var n = randomValue.length; - for (var i = 0; i < n; i++) { - randomValue[i] = Math.random() * 4294967296; - } - return randomValue; - } - var randomData = []; - var genData = function(dataSize) { - var dataSize = dataSize; - for (var i = 0; i < dataSize; i++) { - randomData[i] = getRandom(); - } - return randomData; - }; - return new Blob(genData(size), {type:"application/octet-stream"}, Callback(callback)); - }; - openSpeedtestGet.prototype.addEvt = function(o, e, f) { - o.addEventListener(e, f); - }; - openSpeedtestGet.prototype.remEvt = function(o, e, f) { - o.removeEventListener(e, f); - }; - var openSpeedtestEngine = function() { - var Get = new openSpeedtestGet(); - var Show = new openSpeedtestShow(); - Show.app(); - var SendData; - var myhostName = location.hostname; - var key; - var TestServerip; - var downloadSpeed; - var uploadSpeed; - var dataUsedfordl; - var dataUsedforul; - var pingEstimate; - var jitterEstimate; - var logData; - var return_data; - var ReQ = []; - var StartTime = []; - var CurrentTime = []; - var LiveSpeedArr; - var dLoaded = 0; - var uLoaded = 0; - var currentSpeed = 0; - var uploadTimeing; - var downloadTimeing; - var downloadTime; - var uploadTime; - var saveTestData; - var stop = 0; - function reSett() { - StartTime = 0; - CurrentTime = 0; - LiveSpeedArr = 0; - currentSpeed = 0; - } - var userAgentString; - if (window.navigator.userAgent) { - userAgentString = window.navigator.userAgent; - } else { - userAgentString = "Not Found"; - } - var ulFinal = ulDuration * 0.6; - var dlFinal = dlDuration * 0.6; - function setFinal() { - if (ulDuration * 0.6 >= 7) { - ulFinal = 7; - } - if (dlDuration * 0.6 >= 7) { - dlFinal = 7; - } - } - setFinal(); - var launch = true; - var init = true; - Get.addEvt(Show.settingsMob.el, "click", ShowIP); - Get.addEvt(Show.settingsDesk.el, "click", ShowIP); - Get.addEvt(Show.startButtonDesk.el, "click", runTasks); - Get.addEvt(Show.startButtonMob.el, "click", runTasks); - Get.addEvt(document, "keypress", hiEnter); - var addEvent = true; - var getParams = function(url) { - var params = {}; - var parser = document.createElement("a"); - parser.href = url; - var query = parser.search.substring(1); - var vars = query.split("&"); - for (var i = 0; i < vars.length; i++) { - var pair = vars[i].split("="); - params[pair[0]] = decodeURIComponent(pair[1]); - } - return params; - }; - var getCommand = getParams(window.location.href.toLowerCase()); - if (setPingSamples) { - if (typeof getCommand.ping === "string" || typeof getCommand.p === "string") { - var setPing; - if (typeof getCommand.ping !== "undefined") { - setPing = getCommand.ping; - } else if (typeof getCommand.p !== "undefined") { - setPing = getCommand.p; - } - if (setPing > 0) { - pingSamples = setPing; - pingSamples = setPing; - } - } - } - if (setPingTimeout) { - if (typeof getCommand.out === "string" || typeof getCommand.o === "string") { - var setOut; - if (typeof getCommand.out !== "undefined") { - setOut = getCommand.out; - } else if (typeof getCommand.o !== "undefined") { - setOut = getCommand.o; - } - if (setOut > 1) { - pingTimeOut = setOut; - pingTimeOut = setOut; - } - } - } - if (setHTTPReq) { - if (typeof getCommand.xhr === "string" || typeof getCommand.x === "string") { - var setThreads; - if (typeof getCommand.xhr !== "undefined") { - setThreads = getCommand.xhr; - } else if (typeof getCommand.x !== "undefined") { - setThreads = getCommand.x; - } - if (setThreads > 0 && setThreads <= 32) { - dlThreads = setThreads; - ulThreads = setThreads; - } - } - } - function isValidHttpUrl(str) { - var regex = /(?:https?):\/\/(\w+:?\w*)?(\S+)(:\d+)?(\/|\/([\w#!:.?+=&%!\-\/]))?/; - if (!regex.test(str)) { - return false; - } else { - return true; - } - } - if (selectServer) { - if (typeof getCommand.host === "string" || typeof getCommand.h === "string") { - var severAddress; - if (typeof getCommand.host !== "undefined") { - severAddress = getCommand.host; - } else if (typeof getCommand.h !== "undefined") { - severAddress = getCommand.h; - } - if (isValidHttpUrl(severAddress)) { - openSpeedTestServerList = [{ServerName:"Home", Download:severAddress + "/downloading", Upload:severAddress + "/upload", ServerIcon:"DefaultIcon",},]; - } - } - } - var custom = parseInt(getCommand.stress); - var customS = parseInt(getCommand.s); - var runStress; - var runStressCustom; - if (typeof getCommand.stress === "string") { - runStress = getCommand.stress; - runStressCustom = custom; - } else if (typeof getCommand.s === "string") { - runStress = getCommand.s; - runStressCustom = customS; - } - if (runStress && stressTest) { - if (runStress === "low" || runStress === "l") { - dlDuration = 300; - ulDuration = 300; - } - if (runStress === "medium" || runStress === "m") { - dlDuration = 600; - ulDuration = 600; - } - if (runStress === "high" || runStress === "h") { - dlDuration = 900; - ulDuration = 900; - } - if (runStress === "veryhigh" || runStress === "v") { - dlDuration = 1800; - ulDuration = 1800; - } - if (runStress === "extreme" || runStress === "e") { - dlDuration = 3600; - ulDuration = 3600; - } - if (runStress === "day" || runStress === "d") { - dlDuration = 86400; - ulDuration = 86400; - } - if (runStress === "year" || runStress === "y") { - dlDuration = 31557600; - ulDuration = 31557600; - } - if (custom > 12 || customS > 12) { - dlDuration = runStressCustom; - ulDuration = runStressCustom; - } - } - var overheadClean = parseInt(getCommand.clean); - var overheadCleanC = parseInt(getCommand.c); - var customOverHeadValue = 1; - if (overheadClean) { - customOverHeadValue = overheadClean; - } else if (overheadCleanC) { - customOverHeadValue = overheadCleanC; - } - if (enableClean) { - if (typeof getCommand.clean === "string" || typeof getCommand.c === "string") { - if (overheadClean >= 1 || overheadCleanC >= 1) { - if (overheadClean < 5 || overheadCleanC < 5) { - upAdjust = 1 + customOverHeadValue / 100; - dlAdjust = 1 + customOverHeadValue / 100; - } - } else { - upAdjust = 1; - dlAdjust = 1; - } - } - } - var OpenSpeedTestRun = parseInt(getCommand.run); - var OpenSpeedTestRunR = parseInt(getCommand.r); - var OpenSpeedTestStart; - if (enableRun) { - if (typeof getCommand.run === "string" || typeof getCommand.r === "string") { - if (OpenSpeedTestRun > 0) { - OpenSpeedTestStart = OpenSpeedTestRun; - } else if (OpenSpeedTestRunR > 0) { - OpenSpeedTestStart = OpenSpeedTestRunR; - } else { - OpenSpeedTestStart = 0; - } - } - } - if (OpenSpeedTestStart >= 0) { - if (launch) { - runTasks(); - } - } - var runTest = getCommand.test; - var runTestT = getCommand.t; - var SelectTest = false; - if (selectTest) { - if (typeof runTest === "string" || typeof runTestT === "string") { - var runTestC; - if (runTest) { - runTestC = runTest; - SelectTest = runTest; - } else if (runTestT) { - runTestC = runTestT; - SelectTest = runTestT; - } - if (runTestC === "download" || runTestC === "d") { - uploadSpeed = 0; - dataUsedforul = 0; - SelectTest = "Download"; - if (launch) { - runTasks(); - } - } else if (runTestC === "upload" || runTestC === "u") { - downloadSpeed = 0; - dataUsedfordl = 0; - SelectTest = "Upload"; - stop = 1; - if (launch) { - runTasks(); - } - } else if (runTestC === "ping" || runTestC === "p") { - uploadSpeed = 0; - dataUsedforul = 0; - downloadSpeed = 0; - dataUsedfordl = 0; - SelectTest = "Ping"; - if (launch) { - runTasks(); - } - } else { - SelectTest = false; - } - } - } - var Startit = 0; - function removeEvts() { - Get.remEvt(Show.settingsMob.el, "click", ShowIP); - Get.remEvt(Show.settingsDesk.el, "click", ShowIP); - Get.remEvt(Show.startButtonDesk.el, "click", runTasks); - Get.remEvt(Show.startButtonMob.el, "click", runTasks); - Get.remEvt(document, "keypress", hiEnter); - } - var requestIP = false; - function ShowIP() { - if (requestIP) { - Show.YourIP.el.textContent = "Please wait.."; - ServerConnect(7); - requestIP = false; - } - Show.ip(); - } - function runTasks() { - if (addEvent) { - removeEvts(); - addEvent = false; - } - if (OpenSpeedTestStart >= 0) { - launch = false; - Show.userInterface(); - init = false; - var AutoTme = Math.ceil(Math.abs(OpenSpeedTestStart)); - Show.showStatus("Automatic Test Starts in ..."); - var autoTest = setInterval(countDownF, 1000); - } - function countDownF() { - if (AutoTme >= 1) { - AutoTme = AutoTme - 1; - Show.LiveSpeed(AutoTme, "countDown"); - } else { - if (AutoTme <= 0) { - clearInterval(autoTest); - launch = true; - OpenSpeedTestStart = undefined; - runTasks(); - } - } - } - if (openSpeedTestServerList === "fetch" && launch === true) { - launch = false; - Show.showStatus("Fetching Server Info.."); - ServerConnect(6); - } - if (launch === true) { - if (SelectTest === "Ping") { - testRun(); - } else if (SelectTest === "Download") { - testRun(); - } else if (SelectTest === "Upload") { - testRun(); - } else if (SelectTest === false) { - testRun(); - } - } - } - var osttm = "\u2122"; - var myname = "OpenSpeedTest"; - var com = ".com"; - var ost = myname + osttm; - function hiEnter(e) { - if (e.key === "Enter") { - runTasks(); - } - } - var showResult = 0; - if (openChannel === "web") { - showResult = webRe; - requestIP = true; - } - if (openChannel === "widget") { - showResult = widgetRe; - requestIP = true; - } - if (openChannel === "selfwidget") { - showResult = widgetRe; - TestServerip = domainx; - myhostName = TestServerip; - } - if (openChannel === "dev") { - } - function testRun() { - if (init) { - Show.userInterface(); - init = false; - } - OpenSpeedtest(); - } - function OpenSpeedtest() { - if (openChannel === "widget" || openChannel === "web") { - ServerConnect(1); - } - function readyToUP() { - uploadTime = window.performance.now(); - upReq(); - } - var Engine = setInterval(function() { - if (Status === "Loaded") { - Status = "busy"; - sendPing(0); - } - if (Status === "Ping") { - Status = "busy"; - Show.showStatus("Milliseconds"); - } - if (Status === "Download") { - Show.showStatus("Initializing.."); - Get.reset(); - reSett(); - Show.reset(); - downloadTime = window.performance.now(); - downReq(); - Status = "initDown"; - } - if (Status === "Downloading") { - Show.Symbol(0); - if (Startit == 0) { - Startit = 1; - Show.showStatus("Testing download speed.."); - var extraTime = (window.performance.now() - downloadTime) / 1000; - dReset = extraTime; - Show.progress(1, dlDuration + 2.5); - dlDuration += extraTime; - } - downloadTimeing = (window.performance.now() - downloadTime) / 1000; - reportCurrentSpeed("dl"); - Show.showStatus("Mbps download"); - Show.mainGaugeProgress(currentSpeed); - Show.LiveSpeed(currentSpeed); - Show.Graph(currentSpeed, 0); - downloadSpeed = Get.AvgSpeed(currentSpeed, dlFinal, dlDuration); - if (downloadTimeing >= dlDuration && ProG == "done") { - if (SelectTest) { - Show.GaugeProgresstoZero(currentSpeed, "SendR"); - Show.showStatus("All done"); - Show.Symbol(2); - } else { - Show.GaugeProgresstoZero(currentSpeed, "Upload"); - } - Show.downloadResult(downloadSpeed); - dataUsedfordl = dLoaded; - stop = 1; - Status = "busy"; - reSett(); - Get.reset(); - } - } - if (Status == "Upload") { - if (stop === 1) { - Show.Symbol(1); - Status = "initup"; - Show.showStatus("Initializing.."); - Show.LiveSpeed("...", "speedToZero"); - SendData = Get.uRandom(ulDataSize, readyToUP); - if (SelectTest) { - Startit = 1; - } - } - } - if (Status === "Uploading") { - if (Startit == 1) { - Startit = 2; - Show.showStatus("Testing upload speed.."); - currentSpeed = 0; - Get.reset(); - Show.reset(); - var extraUTime = (window.performance.now() - uploadTime) / 1000; - uReset = extraUTime; - Show.progress(false, ulDuration + 2.5); - ulDuration += extraUTime; - } - Show.showStatus("Mbps upload"); - uploadTimeing = (window.performance.now() - uploadTime) / 1000; - reportCurrentSpeed("up"); - Show.mainGaugeProgress(currentSpeed); - Show.LiveSpeed(currentSpeed); - Show.Graph(currentSpeed, 1); - uploadSpeed = Get.AvgSpeed(currentSpeed, ulFinal, ulDuration); - if (uploadTimeing >= ulDuration && stop == 1) { - dataUsedforul = uLoaded; - Show.uploadResult(uploadSpeed); - Show.GaugeProgresstoZero(currentSpeed, "SendR"); - SendData = undefined; - Show.showStatus("All done"); - Show.Symbol(2); - Status = "busy"; - stop = 0; - } - } - if (Status === "Error") { - Show.showStatus("Check your network connection status."); - Show.ConnectionError(); - Status = "busy"; - clearInterval(Engine); - var dummyElement = document.createElement("div"); - dummyElement.innerHTML = ''; - var htmlAnchorElement = dummyElement.querySelector("a"); - Show.oDoLiveSpeed.el.textContent = "Network Error"; - var circleSVG = document.getElementById("oDoLiveSpeed"); - htmlAnchorElement.innerHTML = circleSVG.innerHTML; - circleSVG.innerHTML = dummyElement.innerHTML; - } - if (Status === "SendR") { - Show.showStatus("All done"); - var dummyElement = document.createElement("div"); - dummyElement.innerHTML = ''; - var htmlAnchorElement = dummyElement.querySelector("a"); - Show.oDoLiveSpeed.el.textContent = ost; - var circleSVG = document.getElementById("oDoLiveSpeed"); - htmlAnchorElement.innerHTML = circleSVG.innerHTML; - circleSVG.innerHTML = dummyElement.innerHTML; - if (location.hostname != myname.toLowerCase() + com) { - saveTestData = "https://" + myname.toLowerCase() + com + "/results/show.php?" + "&d=" + downloadSpeed.toFixed(3) + "&u=" + uploadSpeed.toFixed(3) + "&p=" + pingEstimate + "&j=" + jitterEstimate + "&dd=" + (dataUsedfordl / 1048576).toFixed(3) + "&ud=" + (dataUsedforul / 1048576).toFixed(3) + "&ua=" + userAgentString; - saveTestData = encodeURI(saveTestData); - var circleSVG2 = document.getElementById("resultsData"); - circleSVG2.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", saveTestData); - circleSVG2.setAttribute("target", "_blank"); - if (saveData) { - ServerConnect(5); - } - } else { - ServerConnect(3); - } - Status = "busy"; - clearInterval(Engine); - } - }, 100); - } - function downReq() { - for (var i = 0; i < dlThreads; i++) { - setTimeout(function(i) { - SendReQ(i); - }, dlDelay * i, i); - } - } - function upReq() { - for (var i = 0; i < ulThreads; i++) { - setTimeout(function(i) { - SendUpReq(i); - }, ulDelay * i, i); - } - } - var dLoad = 0; - var dDiff = 0; - var dTotal = 0; - var dtLoad = 0; - var dtDiff = 0; - var dtTotal = 0; - var dRest = 0; - var dReset; - var uReset; - var uLoad = 0; - var uDiff = 0; - var uTotal = 0; - var utLoad = 0; - var utDiff = 0; - var utTotal = 0; - var uRest = 0; - var dualReset; - var neXT = dlDuration * 1000 - 6000; - var dualupReset; - var neXTUp = ulDuration * 1000 - 6000; - function reportCurrentSpeed(now) { - if (now === "dl") { - var dTime = downloadTimeing * 1000; - if (dTime > dReset * 1000 + dlFinal / 2 * 1000 && dRest === 0) { - dRest = 1; - dtTotal = dtTotal * 0.01; - dTotal = dTotal * 0.01; - dualReset = dTime + 10000; - } - if (dTime >= dualReset && dualReset < neXT) { - dualReset += 10000; - dtTotal = dtTotal * 0.01; - dTotal = dTotal * 0.01; - } - dLoad = dLoaded <= 0 ? 0 : dLoaded - dDiff; - dDiff = dLoaded; - dTotal += dLoad; - dtLoad = dtDiff = 0 ? 0 : dTime - dtDiff; - dtDiff = dTime; - dtTotal += dtLoad; - if (dTotal > 0) { - LiveSpeedArr = dTotal / dtTotal / 125 * upAdjust; - currentSpeed = LiveSpeedArr; - } - } - if (now === "up") { - var Tym = uploadTimeing * 1000; - if (Tym > uReset * 1000 + ulFinal / 2 * 1000 && uRest === 0) { - uRest = 1; - utTotal = utTotal * 0.1; - uTotal = uTotal * 0.1; - dualupReset = Tym + 10000; - } - if (Tym >= dualupReset && dualupReset < neXTUp) { - dualupReset += 10000; - utTotal = utTotal * 0.1; - uTotal = uTotal * 0.1; - } - uLoad = uLoaded <= 0 ? 0 : uLoaded - uDiff; - uDiff = uLoaded; - uTotal += uLoad; - utLoad = utDiff = 0 ? 0 : Tym - utDiff; - utDiff = Tym; - utTotal += utLoad; - if (uTotal > 0) { - LiveSpeedArr = uTotal / utTotal / 125 * upAdjust; - currentSpeed = LiveSpeedArr; - } - } - } - function SendReQ(i) { - var lastLoaded = 0; - var OST = new XMLHttpRequest(); - ReQ[i] = OST; - ReQ[i].open("GET", fianlPingServer.Download + "?n=" + Math.random(), true); - ReQ[i].onprogress = function(e) { - if (stop === 1) { - ReQ[i].abort(); - ReQ[i] = null; - ReQ[i] = undefined; - delete ReQ[i]; - return false; - } - if (Status == "initDown") { - Status = "Downloading"; - } - var eLoaded = e.loaded <= 0 ? 0 : e.loaded - lastLoaded; - if (isNaN(eLoaded) || !isFinite(eLoaded) || eLoaded < 0) { - return false; - } - dLoaded += eLoaded; - lastLoaded = e.loaded; - }; - ReQ[i].onload = function(e) { - if (lastLoaded === 0) { - dLoaded += e.total; - } - if (Status == "initDown") { - Status = "Downloading"; - } - if (ReQ[i]) { - ReQ[i].abort(); - ReQ[i] = null; - ReQ[i] = undefined; - delete ReQ[i]; - } - if (stop === 0) { - SendReQ(i); - } - }; - ReQ[i].onerror = function(e) { - if (stop === 0) { - SendReQ(i); - } - }; - ReQ[i].responseType = "arraybuffer"; - ReQ[i].send(); - } - var uReQ = []; - function SendUpReq(i) { - var lastULoaded = 0; - var OST = new XMLHttpRequest(); - uReQ[i] = OST; - uReQ[i].open("POST", fianlPingServer.Upload + "?n=" + Math.random(), true); - uReQ[i].upload.onprogress = function(e) { - if (Status == "initup" && some === undefined) { - var some; - Status = "Uploading"; - } - if (uploadTimeing >= ulDuration) { - uReQ[i].abort(); - uReQ[i] = null; - uReQ[i] = undefined; - delete uReQ[i]; - return false; - } - var eLoaded = e.loaded <= 0 ? 0 : e.loaded - lastULoaded; - if (isNaN(eLoaded) || !isFinite(eLoaded) || eLoaded < 0) { - return false; - } - uLoaded += eLoaded; - lastULoaded = e.loaded; - }; - uReQ[i].onload = function() { - if (lastULoaded === 0) { - uLoaded += ulDataSize * 1048576; - if (uploadTimeing >= ulDuration) { - uReQ[i].abort(); - uReQ[i] = null; - uReQ[i] = undefined; - delete uReQ[i]; - return false; - } - } - if (Status == "initup" && some === undefined) { - var some; - Status = "Uploading"; - } - if (uReQ[i]) { - uReQ[i].abort(); - uReQ[i] = null; - uReQ[i] = undefined; - delete uReQ[i]; - } - if (stop === 1) { - SendUpReq(i); - } - }; - uReQ[i].onerror = function(e) { - if (uploadTimeing <= ulDuration) { - SendUpReq(i); - } - }; - uReQ[i].setRequestHeader("Content-Type", "application/octet-stream"); - if (i > 0 && uLoaded <= 17000) { - } else { - uReQ[i].send(SendData); - } - } - function sendPing() { - readServerList(); - } - var fianlPingServer; - var statusPing; - var statusPingFinal; - var statusJitter; - var statusJitterFinal; - var statusPingTest; - var pingSendStatus = -1; - var finalPing = []; - var pingServer = []; - var finalJitter = []; - var pingSendLength = openSpeedTestServerList.length; - function readServerList() { - pingSendLength = openSpeedTestServerList.length; - Status = "Ping"; - performance.clearResourceTimings(); - if (pingSendStatus < pingSendLength - 1) { - pingSendStatus++; - if (statusPingTest != "Stop") { - sendPingRequest(openSpeedTestServerList[pingSendStatus], readServerList); - } - } else { - if (pingServer.length >= 1) { - var finalLeastPingResult = Math.min.apply(Math, finalPing); - var finalLeastPingResultIndex = finalPing.indexOf(finalLeastPingResult); - fianlPingServer = pingServer[finalLeastPingResultIndex]; - statusPingFinal = finalLeastPingResult; - statusJitterFinal = finalJitter[finalLeastPingResultIndex]; - statusPingTest = "Busy"; - Show.LiveSpeed(statusPingFinal, "Ping"); - Show.pingResults(statusPingFinal, "Ping"); - Show.jitterResult(statusJitterFinal, "Jitter"); - pingEstimate = statusPingFinal; - jitterEstimate = statusJitterFinal; - if (SelectTest) { - if (SelectTest == "Ping") { - Status = "SendR"; - } else { - Status = SelectTest; - } - } else { - Status = "Download"; - } - } else { - if (pingServer.Download) { - } else { - Status = "Error"; - } - } - } - } - function sendPingRequest(serverListElm, callback) { - var pingSamplesSend = 0; - var pingResult = []; - var jitterResult = []; - function sendNewPingReq() { - if (pingSamplesSend < pingSamples) { - pingSamplesSend++; - if (statusPingTest != "Stop") { - PingRequest(); - } - } else { - if (pingResult.length > 1) { - jitterResult.sort(function(a, b) { - return a - b; - }); - jitterResult = jitterResult.slice(0, jitterResult.length * jitterFinalSample); - jitterResult = jitterResult.reduce(function(acc, val) { - return acc + val; - }, 0) / jitterResult.length; - var leastJitter = jitterResult.toFixed(1); - var leastPing = Math.min.apply(Math, pingResult); - finalPing.push(leastPing); - pingServer.push(serverListElm); - finalJitter.push(leastJitter); - if (typeof callback === "function") { - callback(); - } - } else { - if (typeof callback === "function") { - callback(); - } - } - } - } - function PingRequest() { - var OST = new XMLHttpRequest(); - var ReQ = OST; - if (statusPingTest != "Stop") { - ReQ.abort(); - } - ReQ.open(pingMethod, serverListElm[pingFile] + "?n=" + Math.random(), true); - ReQ.timeout = pingTimeOut; - var startTime = window.performance.now(); - ReQ.send(); - ReQ.onload = function() { - if (this.status === 200 && this.readyState === 4) { - var endTime = Math.floor(window.performance.now() - startTime); - var perfNum = performance.getEntries(); - perfNum = perfNum[perfNum.length - 1]; - var perfPing; - if (perfNum.initiatorType === "xmlhttprequest") { - perfPing = parseFloat(perfNum.duration.toFixed(1)); - } else { - perfPing = endTime; - } - if (pingSamplesSend > 250) { - perfPing = endTime; - } - if (perfPing <= 0) { - statusPing = 0.1; - pingResult.push(0.1); - } else { - statusPing = perfPing; - pingResult.push(perfPing); - } - if (pingResult.length > 1) { - var jitterCalc = Math.abs(pingResult[pingResult.length - 1] - pingResult[pingResult.length - 2]).toFixed(1); - jitterResult.push(parseFloat(jitterCalc)); - statusJitter = jitterCalc; - Show.LiveSpeed(perfPing, "Ping"); - Show.pingResults(perfPing, "Ping"); - Show.jitterResult(jitterCalc, "Jitter"); - } - sendNewPingReq(); - } - if (this.status === 404 && this.readyState === 4) { - pingSamplesSend++; - sendNewPingReq(); - } - }; - ReQ.onerror = function(e) { - pingSamplesSend++; - sendNewPingReq(); - }; - ReQ.ontimeout = function(e) { - pingSamplesSend++; - sendNewPingReq(); - }; - } - PingRequest(); - } - var ServerConnect = function(auth) { - var Self = this; - var xhr = new XMLHttpRequest(); - var url = OpenSpeedTestdb; - if (auth == 1) { - url = webIP; - } - if (auth == 5) { - url = saveDataURL; - } - if (auth == 7) { - url = get_IP; - } - xhr.open("POST", url, true); - xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - xhr.onreadystatechange = function() { - if (xhr.readyState == 4 && xhr.status == 200) { - return_data = xhr.responseText.trim(); - if (auth == 2) { - key = return_data; - } - if (auth == 1) { - TestServerip = return_data; - } - if (auth == 3) { - setTimeout(function() { - location.href = showResult + return_data; - }, 1500); - } - if (auth == 6) { - openSpeedTestServerList = JSON.parse(return_data); - launch = true; - runTasks(); - } - if (auth == 7) { - Show.YourIP.el.textContent = return_data; - } - } - }; - if (auth == 2) { - logData = "r=n"; - } - if (auth == 3) { - logData = "r=l" + "&d=" + downloadSpeed + "&u=" + uploadSpeed + "&dd=" + dataUsedfordl / 1048576 + "&ud=" + dataUsedforul / 1048576 + "&p=" + pingEstimate + "&do=" + myhostName + "&S=" + key + "&sip=" + TestServerip + "&jit=" + jitterEstimate + "&ua=" + userAgentString; - } - if (auth == 5) { - logData = saveTestData; - } - if (auth == 6) { - logData = "r=s"; - } - xhr.send(logData); - }; - }; - OpenSpeedTest.Start = function() { - new openSpeedtestEngine(); - }; -})(window.OpenSpeedTest = window.OpenSpeedTest || {}); diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/js/app-2.5.4.min.js b/data/agent/var/lib/openflare/pages/deployments/1/current/assets/js/app-2.5.4.min.js deleted file mode 100644 index 0df99a1f..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/current/assets/js/app-2.5.4.min.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - Official Website : https://OpenSpeedTest.COM | Email: support@openspeedtest.com - Developed by : Vishnu | https://Vishnu.Pro | Email : me@vishnu.pro - Like this Project? Please Donate NOW & Keep us Alive -> https://go.openspeedtest.com/Donate - Speed Test by OpenSpeedTest™️ is Free and Open-Source Software (FOSS) with MIT License. - Read full license terms @ http://go.openspeedtest.com/License - If you have any Questions, ideas or Comments Please Send it via -> https://go.openspeedtest.com/SendMessage -*/ -window.onload=function(){var X=document.getElementById("OpenSpeedTest-UI");X.parentNode.replaceChild(X.contentDocument.documentElement,X);ostOnload();OpenSpeedTest.Start()}; -(function(X){function h(c){if(!(this instanceof h))return new h(c);this.el=document.getElementById(c)}var m,Ma,Na=function(c){c&&"function"===typeof c&&c()};h.prototype.fade=function(c,d,a){var f="in"===c,t=f?0:1,w=14/d,n=this;f&&(n.el.style.display="block",n.el.style.opacity=t);var q=window.setInterval(function(){t=f?t+w:t-w;n.el.style.opacity=t;0>=t&&(n.el.style.display="none");(0>=t||1<=t)&&window.clearInterval(q,Na(a))},14)};var Oa=function(c,d,a,f){c/=f;c--;return a*(c*c*c+1)+d},r=function(){this.YourIP= -h("YourIP");this.ipDesk=h("ipDesk");this.ipMob=h("ipMob");this.downSymbolDesk=h("downSymbolDesk");this.upSymbolDesk=h("upSymbolDesk");this.upSymbolMob=h("upSymbolMob");this.downSymbolMob=h("downSymbolMob");this.settingsMob=h("settingsMob");this.settingsDesk=h("settingsDesk");this.oDoLiveStatus=h("oDoLiveStatus");this.ConnectErrorMob=h("ConnectErrorMob");this.ConnectErrorDesk=h("ConnectErrorDesk");this.downResult=h("downResult");this.upRestxt=h("upRestxt");this.pingResult=h("pingResult");this.jitterDesk= -h("jitterDesk");this.pingMobres=h("pingMobres");this.JitterResultMon=h("JitterResultMon");this.JitterResultms=h("JitterResultms");this.UI_Desk=h("UI-Desk");this.UI_Mob=h("UI-Mob");this.oDoTopSpeed=h("oDoTopSpeed");this.startButtonMob=h("startButtonMob");this.startButtonDesk=h("startButtonDesk");this.intro_Desk=h("intro-Desk");this.intro_Mob=h("intro-Mob");this.loader=h("loading_app");this.OpenSpeedtest=h("OpenSpeedtest");this.mainGaugebg_Desk=h("mainGaugebg-Desk");this.mainGaugeBlue_Desk=h("mainGaugeBlue-Desk"); -this.mainGaugeWhite_Desk=h("mainGaugeWhite-Desk");this.mainGaugebg_Mob=h("mainGaugebg-Mob");this.mainGaugeBlue_Mob=h("mainGaugeBlue-Mob");this.mainGaugeWhite_Mob=h("mainGaugeWhite-Mob");this.oDoLiveSpeed=h("oDoLiveSpeed");this.progressStatus_Mob=h("progressStatus-Mob");this.progressStatus_Desk=h("progressStatus-Desk");this.graphc1=h("graphc1");this.graphc2=h("graphc2");this.graphMob2=h("graphMob2");this.graphMob1=h("graphMob1");this.text=h("text");this.scale=[{degree:680,value:0},{degree:570,value:.5}, -{degree:460,value:1},{degree:337,value:10},{degree:220,value:100},{degree:115,value:500},{degree:0,value:1E3}];this.polygon=this.chart=this.element="";this.width=200;this.height=50;this.maxValue=0;this.values=[];this.points=[];this.vSteps=5;this.measurements=[];this.points=[]};r.prototype.reset=function(){this.polygon=this.chart=this.element="";this.width=200;this.height=50;this.maxValue=0;this.values=[];this.points=[];this.vSteps=5;this.measurements=[];this.points=[]};r.prototype.ip=function(){"block"=== -this.ipDesk.el.style.display?(this.ipDesk.el.style.display="none",this.ipMob.el.style.display="none"):(this.ipDesk.el.style.display="block",this.ipMob.el.style.display="block")};r.prototype.prePing=function(){this.loader.fade("out",500);this.OpenSpeedtest.fade("in",1E3)};r.prototype.app=function(){this.loader.fade("out",500,this.ShowAppIntro())};r.prototype.ShowAppIntro=function(){this.OpenSpeedtest.fade("in",1E3)};r.prototype.userInterface=function(){this.intro_Desk.fade("out",1E3);this.intro_Mob.fade("out", -1E3,this.ShowUI())};r.prototype.ShowUI=function(){this.UI_Desk.fade("in",1E3);this.UI_Mob.fade("in",1E3,function(c){m="Loaded";console.log("Developed by Vishnu. Email --\x3e me@vishnu.pro")})};r.prototype.Symbol=function(c){0==c&&(this.downSymbolMob.el.style.display="block",this.downSymbolDesk.el.style.display="block",this.upSymbolMob.el.style.display="none",this.upSymbolDesk.el.style.display="none");1==c&&(this.downSymbolMob.el.style.display="none",this.downSymbolDesk.el.style.display="none",this.upSymbolMob.el.style.display= -"block",this.upSymbolDesk.el.style.display="block");2==c&&(this.downSymbolMob.el.style.display="none",this.downSymbolDesk.el.style.display="none",this.upSymbolMob.el.style.display="none",this.upSymbolDesk.el.style.display="none")};r.prototype.Graph=function(c,d){function a(q,M){for(n=f.maxValue=0;nf.maxValue&&(f.maxValue=f.values[n]);f.maxValue=Math.ceil(f.maxValue);if(1=d&&(clearInterval(t),Ma="done",a.progressStatus_Desk.el.style.strokeDashoffset=800,a.progressStatus_Mob.el.style.strokeDashoffset=800)},14)};r.prototype.mainGaugeProgress=function(c){var d=c;0>d&&(d=0);var a=this.getNonlinearDegree(d);0=d&&(this.mainGaugeBlue_Mob.el.style.strokeDashoffset=681.1,this.mainGaugeWhite_Mob.el.style.strokeDashoffset=.1,this.mainGaugeWhite_Desk.el.style.strokeDashoffset=.1,this.mainGaugeBlue_Desk.el.style.strokeDashoffset=681.1)};r.prototype.showStatus=function(c){this.oDoLiveStatus.el.textContent=c};r.prototype.ConnectionError=function(){this.ConnectErrorMob.el.style.display= -"block";this.ConnectErrorDesk.el.style.display="block"};r.prototype.uploadResult=function(c){1>c&&(this.upRestxt.el.textContent=c.toFixed(3));1<=c&&9999>c&&(this.upRestxt.el.textContent=c.toFixed(1));1E4<=c&&99999>c&&(this.upRestxt.el.textContent=c.toFixed(1),this.upRestxt.el.style.fontSize="20px");1E5<=c&&(this.upRestxt.el.textContent=c.toFixed(1),this.upRestxt.el.style.fontSize="18px")};r.prototype.pingResults=function(c,d){var a=c;"Ping"===d&&(1<=a&&1E4>a?(this.pingResult.el.textContent=Math.floor(a), -this.pingMobres.el.textContent=Math.floor(a)):0<=a&&1>a&&(0==a&&(a=0),this.pingResult.el.textContent=a,this.pingMobres.el.textContent=a));"Error"===d&&(this.oDoLiveSpeed.el.textContent=a)};r.prototype.downloadResult=function(c){1>c&&(this.downResult.el.textContent=c.toFixed(3));1<=c&&9999>c&&(this.downResult.el.textContent=c.toFixed(1));1E4<=c&&99999>c&&(this.downResult.el.textContent=c.toFixed(1),this.downResult.el.style.fontSize="20px");1E5<=c&&(this.downResult.el.textContent=c.toFixed(1),this.downResult.el.style.fontSize= -"18px")};r.prototype.jitterResult=function(c,d){var a=c;"Jitter"===d&&(1<=a&&1E4>a?(this.jitterDesk.el.textContent=Math.floor(a),1<=a&&100>a&&(this.JitterResultMon.el.textContent=Math.floor(a)),100<=a&&(a=(a/1E3).toFixed(1),this.JitterResultMon.el.textContent=a+"k")):0<=a&&1>a&&(0==a&&(a=0),this.jitterDesk.el.textContent=a,this.JitterResultMon.el.textContent=a))};r.prototype.LiveSpeed=function(c,d){var a=c;if("countDown"===d){var f=a.toFixed(0);this.oDoLiveSpeed.el.textContent=f}else"speedToZero"=== -d?("number"==typeof a&&(a=a.toFixed(1)),0>=a&&(a=0),this.oDoLiveSpeed.el.textContent=a,this.oDoTopSpeed.el.textContent="1000+",this.oDoTopSpeed.el.style.fontSize="16.9px",this.oDoTopSpeed.el.style.fill="gray"):"Ping"===d?1<=a&&1E4>a?this.oDoLiveSpeed.el.textContent=Math.floor(a):0<=a&&1>a&&(0==a&&(a=0),this.oDoLiveSpeed.el.textContent=a):(0==a&&(f=a.toFixed(0),this.oDoLiveSpeed.el.textContent=f),1>=a&&0=a&&(this.oDoTopSpeed.el.textContent="1000+",this.oDoTopSpeed.el.style.fontSize="16.9px",this.oDoTopSpeed.el.style.fill="gray"),1010<=a&&(this.oDoTopSpeed.el.textContent=1E3*Math.floor(a/1010)+"+",this.oDoTopSpeed.el.style.fill="gray",this.oDoTopSpeed.el.style.fontSize="17.2px"))};r.prototype.GaugeProgresstoZero=function(c,d){var a=this;if(0<=c)var f=Date.now(),t=0-c,w=setInterval(function(){var n=(Date.now()-f)/1E3;var q=n/3;q--;q=t*(q*q*q*q*q+1)+c;a.LiveSpeed(q,"speedToZero");a.mainGaugeProgress(q); -if(3<=n||0>=q)clearInterval(w),a.LiveSpeed(0,"speedToZero"),a.mainGaugeProgress(0),m=d},16)};r.prototype.getNonlinearDegree=function(c){var d=0;if(0==c||0>=c||isNaN(c))return 0;for(;dthis.scale[d].value)d++;else return this.scale[d-1].degree+(c-this.scale[d-1].value)*(this.scale[d].degree-this.scale[d-1].degree)/(this.scale[d].value-this.scale[d-1].value);return this.scale[this.scale.length-1].degree};var O=function(){this.OverAllTimeAvg=window.performance.now();this.SpeedSamples= -[];this.FinalSpeed};O.prototype.reset=function(){this.OverAllTimeAvg=window.performance.now();this.SpeedSamples=[];this.FinalSpeed=0};O.prototype.ArraySum=function(c){return c?c.reduce(function(d,a){if("number"===typeof d&&"number"===typeof a)return d+a},0):0};O.prototype.AvgSpeed=function(c,d,a){this.timeNow=(window.performance.now()-this.OverAllTimeAvg)/1E3;this.FinalSpeed;this.timeNow>=a-d&&(0=l&&(clearInterval(g),G=!0,Z=void 0,d())}Pa&&(x.remEvt(e.settingsMob.el,"click",c),x.remEvt(e.settingsDesk.el,"click",c),x.remEvt(e.startButtonDesk.el,"click",d),x.remEvt(e.startButtonMob.el,"click",d),x.remEvt(document,"keypress",a),Pa=!1);if(0<=Z){G=!1;e.userInterface();ua=!1;var l=Math.ceil(Math.abs(Z));e.showStatus("Automatic Test Starts in ...");var g=setInterval(b,1E3)}"fetch"===openSpeedTestServerList&&!0===G&&(G=!1,e.showStatus("Fetching Server Info.."),Y(6));!0===G&&("Ping"=== -A?f():"Download"===A?f():"Upload"===A?f():!1===A&&f())}function a(b){"Enter"===b.key&&d()}function f(){ua&&(e.userInterface(),ua=!1);t()}function t(){function b(){va=window.performance.now();n()}"widget"!==openChannel&&"web"!==openChannel||Y(1);var l=setInterval(function(){"Loaded"===m&&(m="busy",Q());"Ping"===m&&(m="busy",e.showStatus("Milliseconds"));"Download"===m&&(e.showStatus("Initializing.."),x.reset(),y=ha=0,e.reset(),wa=window.performance.now(),w(),m="initDown");if("Downloading"===m){e.Symbol(0); -if(0==aa){aa=1;e.showStatus("Testing download speed..");var g=(window.performance.now()-wa)/1E3;Qa=g;e.progress(1,dlDuration+2.5);dlDuration+=g}xa=(window.performance.now()-wa)/1E3;q("dl");e.showStatus("Mbps download");e.mainGaugeProgress(y);e.LiveSpeed(y);e.Graph(y,0);R=x.AvgSpeed(y,ya,dlDuration);xa>=dlDuration&&"done"==Ma&&(A?(e.GaugeProgresstoZero(y,"SendR"),e.showStatus("All done"),e.Symbol(2)):e.GaugeProgresstoZero(y,"Upload"),e.downloadResult(R),ia=S,I=1,m="busy",y=ha=0,x.reset())}"Upload"== -m&&1===I&&(e.Symbol(1),m="initup",e.showStatus("Initializing.."),e.LiveSpeed("...","speedToZero"),za=x.uRandom(ulDataSize,b),A&&(aa=1));"Uploading"===m&&(1==aa&&(aa=2,e.showStatus("Testing upload speed.."),y=0,x.reset(),e.reset(),Ra=g=(window.performance.now()-va)/1E3,e.progress(!1,ulDuration+2.5),ulDuration+=g),e.showStatus("Mbps upload"),T=(window.performance.now()-va)/1E3,q("up"),e.mainGaugeProgress(y),e.LiveSpeed(y),e.Graph(y,1),U=x.AvgSpeed(y,Aa,ulDuration),T>=ulDuration&&1==I&&(ja=P,e.uploadResult(U), -e.GaugeProgresstoZero(y,"SendR"),za=void 0,e.showStatus("All done"),e.Symbol(2),m="busy",I=0));if("Error"===m){e.showStatus("Check your network connection status.");e.ConnectionError();m="busy";clearInterval(l);g=document.createElement("div");g.innerHTML='';var u=g.querySelector("a");e.oDoLiveSpeed.el.textContent="Network Error";var C=document.getElementById("oDoLiveSpeed");u.innerHTML=C.innerHTML; -C.innerHTML=g.innerHTML}"SendR"===m&&(e.showStatus("All done"),g=document.createElement("div"),g.innerHTML='',u=g.querySelector("a"),e.oDoLiveSpeed.el.textContent=fb,C=document.getElementById("oDoLiveSpeed"),u.innerHTML=C.innerHTML,C.innerHTML=g.innerHTML,location.hostname!=Ba.toLowerCase()+Sa?(ba="https://"+Ba.toLowerCase()+Sa+"/results/show.php?&d="+R.toFixed(3)+"&u="+U.toFixed(3)+"&p="+ -Ca+"&j="+Da+"&dd="+(ia/1048576).toFixed(3)+"&ud="+(ja/1048576).toFixed(3)+"&ua="+Ta,ba=encodeURI(ba),g=document.getElementById("resultsData"),g.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",ba),g.setAttribute("target","_blank"),saveData&&Y(5)):Y(3),m="busy",clearInterval(l))},100)}function w(){for(var b=0;b1E3*Qa+ya/2*1E3&&0===Ua&&(Ua=1,ka*=.01,ca*=.01,la=l+1E4);l>=la&&la=S?0:S-Wa;Wa=S;ca+=Va;Xa=Ea=l-Ea;Ea=l;ka+=Xa;01E3*Ra+Aa/2*1E3&&0===Ya&&(Ya=1,ma*=.1,da*=.1,na=b+1E4),b>=na&&na=P?0:P-$a,$a=P,da+=Za,ab=Fa=b-Fa,Fa=b,ma+=ab,0=g.loaded?0:g.loaded-l;if(isNaN(u)||!isFinite(u)||0>u)return!1;S+=u;l=g.loaded};z[b].onload=function(g){0===l&&(S+=g.total);"initDown"==m&&(m="Downloading");z[b]&&(z[b].abort(),z[b]=null,z[b]=void 0,delete z[b]);0===I&&M(b)};z[b].onerror=function(g){0===I&&M(b)};z[b].responseType="arraybuffer";z[b].send()}function F(b){var l=0;v[b]=new XMLHttpRequest;v[b].open("POST",Ga.Upload+"?n="+Math.random(),!0); -v[b].upload.onprogress=function(g){"initup"==m&&void 0===u&&(m="Uploading");if(T>=ulDuration)return v[b].abort(),v[b]=null,v[b]=void 0,delete v[b],!1;var u=0>=g.loaded?0:g.loaded-l;if(isNaN(u)||!isFinite(u)||0>u)return!1;P+=u;l=g.loaded};v[b].onload=function(){if(0===l&&(P+=1048576*ulDataSize,T>=ulDuration))return v[b].abort(),v[b]=null,v[b]=void 0,delete v[b],!1;if("initup"==m&&void 0===g){var g;m="Uploading"}v[b]&&(v[b].abort(),v[b]=null,v[b]=void 0,delete v[b]);1===I&&F(b)};v[b].onerror=function(g){T<= -ulDuration&&F(b)};v[b].setRequestHeader("Content-Type","application/octet-stream");0=P||v[b].send(za)}function Q(){bb=openSpeedTestServerList.length;m="Ping";performance.clearResourceTimings();if(Ha=B?J.push(.1):J.push(B);1=ra&&(ulThreads=dlThreads=ra)}if(selectServer&&("string"===typeof k.host||"string"===typeof k.h)){if("undefined"!==typeof k.host)var sa=k.host;else"undefined"!==typeof k.h&&(sa=k.h);/(?:https?):\/\/(\w+:?\w*)?(\S+)(:\d+)?(\/|\/([\w#!:.?+=&%!\-\/]))?/.test(sa)&& -(openSpeedTestServerList=[{ServerName:"Home",Download:sa+"/downloading",Upload:sa+"/upload",ServerIcon:"DefaultIcon"}])}E=parseInt(k.stress);W=parseInt(k.s);if("string"===typeof k.stress){var p=k.stress;var H=E}else"string"===typeof k.s&&(p=k.s,H=W);if(p&&stressTest){if("low"===p||"l"===p)ulDuration=dlDuration=300;if("medium"===p||"m"===p)ulDuration=dlDuration=600;if("high"===p||"h"===p)ulDuration=dlDuration=900;if("veryhigh"===p||"v"===p)ulDuration=dlDuration=1800;if("extreme"===p||"e"===p)ulDuration= -dlDuration=3600;if("day"===p||"d"===p)ulDuration=dlDuration=86400;if("year"===p||"y"===p)ulDuration=dlDuration=31557600;if(12p||5>H)upAdjust=1+E/100,dlAdjust=1+E/100}else dlAdjust=upAdjust=1;p=parseInt(k.run);H=parseInt(k.r);var Z;!enableRun||"string"!==typeof k.run&&"string"!==typeof k.r||(Z=0',createCookie("mode","dark")));"light"===a&&(nightModeMob.style.display="none",dayModeMob.style.display="inline-block",nightMode.style.display="none", -dayMode.style.display="inline-block",(darkStyle=document.getElementById("darkmode"))&&darkStyle.parentNode.removeChild(darkStyle),createCookie("mode","light"))}function toggleSkin(){(darkStyle=document.getElementById("darkmode"))?setSkin("light"):setSkin("dark")}function createCookie(a,c,b){if(b){var d=new Date;d.setTime(d.getTime()+864E5*b);b="; expires="+d.toGMTString()}else b="";document.cookie=a+"="+c+b+"; path=/"} -function getCookieValue(a,c){return(c=document.cookie.match("(^|;)\\s*"+a+"\\s*=\\s*([^;]+)"))?c.pop():""}; \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/downloading b/data/agent/var/lib/openflare/pages/deployments/1/current/downloading deleted file mode 100644 index b78c32fa..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/current/downloading and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/hosted.html b/data/agent/var/lib/openflare/pages/deployments/1/current/hosted.html deleted file mode 100644 index ea51ac3b..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/current/hosted.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - Speedtest by OpenSpeedtest.com - - - - -
- Speed Test by OpenSpeedTest™ -
- - \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/index.html b/data/agent/var/lib/openflare/pages/deployments/1/current/index.html deleted file mode 100644 index 2772b6b6..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/current/index.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - SpeedTest by OpenSpeedTest™ - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
- - -
- SpeedTest by OpenSpeedTest™ is a Free and Open-Source HTML5 Network Speed Test Software. -

© Copyright 2013-2024 OpenSpeedTest™ All Rights Reserved.

-
- - - - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/1/current/upload b/data/agent/var/lib/openflare/pages/deployments/1/current/upload deleted file mode 100644 index e69de29b..00000000 diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/.gitignore b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/.gitignore deleted file mode 100644 index e43b0f98..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.DS_Store diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/.openflare-pages.json b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/.openflare-pages.json deleted file mode 100644 index 660e9c7f..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/.openflare-pages.json +++ /dev/null @@ -1 +0,0 @@ -{"deployment_id":1,"checksum":"4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6"} \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/License.md b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/License.md deleted file mode 100644 index b93a9207..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/License.md +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 OpenSpeedTest™ - -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 permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/README.md b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/README.md deleted file mode 100644 index 17d89eeb..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/README.md +++ /dev/null @@ -1,506 +0,0 @@ -# **[SpeedTest by OpenSpeedTest™](https://openspeedtest.com?Run&ref=Github)** - Free & Open-Source HTML5 Network Performance Estimation Tool. - - - -SpeedTest by OpenSpeedTest™ is a Free and Open-Source HTML5 Network Performance Estimation Tool Written in Vanilla Javascript and only uses built-in Web APIs like `XMLHttpRequest` `(XHR)`, `HTML`, `CSS`, `JS`, & `SVG`. No Third-Party frameworks or libraries are Required. All we need is a static web server like `NGINX`. I started this project in 2011 and moved to OpenSpeedTest.com dedicated Project/Domain Name in 2013. - ---- -Just launched: OpenPacketLoss. Check it out! - -While OpenSpeedTest measures bandwidth, this tool helps you detect packet loss directly from your browser. - -**Try it:** https://openpacketloss.com -**Source:** https://github.com/openpacketloss - ---- - - -[![Download OpenSpeedTest-Server V2.1](https://github.com/openspeedtest/v2-Test/raw/main/images/10G-S.gif)](https://go.openspeedtest.com/Server "Download OpenSpeedTest-Server V2.1") - Download from the Microsoft Store Download from the Mac App Store Download from the App Store Download from the Google Play Store Download from the Snap Store Download from the Docker Hub Download from the Helm Store Download from GitHub - -**No client-side software or plugin is required. You can run a network speed test from any device with a [Web Browser that is IE10 or newer.](https://www.youtube.com/watch?v=9f-OM_WQ7Bw&list=PLt-deStxFJOMEAs2O1lJhscMNzcg9E3Po&index=1)** - - Video Tutorial by Roberto Jorge Tech - Video Tutorial by LanPad - -## Why OpenSpeedTest - - - -### Secure by Design. - - - -OpenSpeedTest contains Only `STATIC` Files like `HTML`,`CSS` & `JS`. - -So you don't need to worry about Security Updates or Hidden Exploits that may compromise your secure environments. - - - -### Lightweight, High Performance. - - - -OpenSpeedTest is written in Vanilla JavaScript. No Third-Party frameworks or libraries were used. SpeedTest script file size is under 8kB gzip. The unexpected side effect of using Vanilla JavaScript is High Performance. - - - -### Run a speed test from Any Device. - - - -OpenSpeedTest will run on Any Web Browser that is IE10 or newer. - - - -### Ready for Any Display Size and Resolution. - - - -OpenSpeedTest User interface is written in SVG. - - - -# Create Your Own SpeedTest Server. - -### Server Requirements : - -`Nginx`, `Apache`, `IIS`, `Express`, or Any Web server that supports `HTTP/1.1` or newer. - -- Accept, `GET`, `POST`, `HEAD` & `OPTIONS`, Response `200 OK`. - -- Accept, `POST` to Static Files, Response `200 OK`. - -- `client_max_body_size`, 35 Megabytes or more. - -- Timeout greater than `60 seconds`. - -- Disable `Access logs` for Increasing server performance. - -- Improve `Time to First Byte` (TTFB) - -- Warning! If you run it behind a **[Reverse Proxy](https://github.com/openspeedtest/Speed-Test/issues/4#issuecomment-1229157193)**, you should increase the `post-body content length` to 35 megabytes. -- Supports `HTTP2` & `HTTP3`. -- `HTTP1.1` is recommended for achieving maximum performance. -- **[You Should Follow our Nginx Config.](https://github.com/openspeedtest/Nginx-Configuration)** - - - -# Or, You can use OpenSpeedTest-Server. - -OpenSpeedTest-Server is available for Windows, Mac, Linux, Android, iOS & Docker. -[![Download OpenSpeedTest-Server V2.1](https://open.cachefly.net/assets/images/OSTV2-SS.png)](https://go.openspeedtest.com/Server "Download OpenSpeedTest-Server V2.1") -#### Fully Optimized and ready to use applications. - Download from the Microsoft Store Download from the Mac App Store Download from the App Store Download from the Google Play Store Download from the Snap Store Download from the Docker Hub Download from the Helm Store Download from GitHub - - -### New features: - - - -1. Stress Test. (Continuous Speed Test) - - - -To enable the stress test. Pass `Stress` or `S` keyword as a URL parameter. - - - -```` - -http://192.168.1.5?Stress=Low - -```` - -After the `STRESS` or `S` keyword, you can specify the number of seconds you need to run the StressTest in seconds, or preset values such as `Low`, `Medium`, `High`, `VeryHigh`, `Extreme`, `Day`, and `Year`. Will run a speed test for `300`,`600`,`900`,`1800`,`3600`,`86400`,`31557600` seconds, respectively. Also, you can feed the first letter of each parameter and its values. - - -```` - -http://192.168.1.5?S=L - -```` - -`S=L` is the same as passing `Stress=low` - -Or you can specify the number of seconds eg:`5000` directly without any preset keywords. - - - -```` - -http://192.168.1.5?Stress=5000 - -```` - - - -2. Run a speed test automatically - - - -Run a speed test automatically on page load. - -```` - -http://192.168.1.5?Run - -```` - -Run a speed test automatically after a few seconds. - -```` - -http://192.168.1.5?Run=10 or http://192.168.1.5?R=10 - -```` - - - -You can pass multiple keywords, and it's not `Case-Sensitive`. - - - -```` - -http://192.168.1.5?Run&Stress=300 OR http://192.168.1.5?R&S=300 - -```` - -This will start a speed test immediately and run for `300 seconds` in each direction. That is 300 seconds for download and `300 seconds` for upload. - - - -3. Save results to a Database - - - -Edit `Index.html` - -```` - -var saveData = true; - -var saveDataURL = "//yourDatabase.Server.com:4500/save?data="; - -```` - -4. Add multiple servers. The app will choose one with the least latency automatically. - - - -Edit `Index.html` - -```` - -var openSpeedTestServerList = [ - -{"ServerName":"Home-Earth", "Download":"/downloading", "Upload":"/upload", "ServerIcon":"DefaultIcon"}, - -{"ServerName":"Home-Mars", "Download":"/downloading", "Upload":"/upload", "ServerIcon":"DefaultIcon"}, - -{"ServerName":"Home-Moon", "Download":"/downloading", "Upload":"/upload", "ServerIcon":"DefaultIcon"} - -]; - -```` - -5. Disable or change Overhead Compensation factor. - - - -```` - -http://192.168.1.5?clean - -```` - -Overhead Compensation factor, This is browser based test, Many Unknowns. Currently 4%. That is within the margin of error. - -You can pass `Clean` or `C` as a URL Parameter and reset Overhead Compensation factor to Zero or set any value between 0 and 4. 1 = 1% to 4 = 4%. - -`Clean` will not accept values above 4, so Compensation is limited to maximum 4%. - - - -6. Change the default limit of 6 parallel HTTP connections to the Server. - - - -```` - -http://192.168.1.5?XHR=3 OR http://192.168.1.5?X=3 - -```` - -Allow the user to Change the default limit of 6 parallel HTTP connections to the Server. `XHR` will Accept values above 1 and max 32 - -pass `XHR` or `X` as a URL Parameter. - - - -7. Select a different server to run a speed test. - - - -```` - -http://192.168.1.5?Host=http://192.168.55.1:90 OR http://192.168.1.5?h=http://192.168.55.1:90 - -```` - -Pass `Host` or `H` as a URL Parameter. - -`HOST` will Accept only valid HTTP URLs like `http://192.168.1.10:3000` or `https://yourHost.com`. - - - -8. Select and run one test at a time, `DOWNLOAD`, `UPLOAD`, or `PING`. - - - -```` - -http://192.168.1.5?Test=Upload OR http://192.168.1.5?T=U - -```` - -`TEST` Allow the user to select and run one test at a time, Download, Upload, or Ping. - -Pass `Test` or `T` as a URL Parameter. - - - -9. Set a PingTimeout dynamically by passing `Out` or `O` as a URL Parameter - - - -```` - -http://192.168.1.5?Out=7000 OR http://192.168.1.5?O=7000 - -```` - -If Server not responded within 5 Seconds for any requests we send ('pingSamples' times) - -We will show `Network Error`, You can change the limit here. - -In milliseconds, if you need to set `6 seconds`. Change the value to `6000`. - - - -10. Set the Number of ping samples by adding `Ping` or `P` as a URL Parameter - - - -```` - -http://192.168.1.5?Ping=500 OR http://192.168.1.5?P=500 - -```` - -More samples = more accurate representation. `Ping = 500` will send `501` requests to server to find the accurate ping value. -Take a look at index.html, you can set a custom ping sample size, threads, upload data size etc. - -## Self-hosted (On-Premise) / (Docker Image/Source Code) -### For Headless large-scale deployments. -You have two options here. If you need a custom deployment, use our source code along with a web server of your choice. I prefer Nginx, and you can find my [Nginx Configuration](https://github.com/openspeedtest/Nginx-Configuration) here. Or you can use our docker image. You can deploy it on your LAN/WAN with or without an active internet connection. - -**This is docker implementation using nginxinc/nginx-unprivileged:stable-alpine. uses significantly fewer resources.** - -- NGINX Docker image that runs NGINX as a non root, unprivileged user. - - ### Docker install instructions: - - Install Docker and run the following command! - -````bash - -sudo docker run --restart=unless-stopped --name openspeedtest -d -p 3000:3000 -p 3001:3001 openspeedtest/latest - -```` -#### Or use docker-compose.yml -```` -version: '3.3' -services: - speedtest: - restart: unless-stopped - container_name: openspeedtest - ports: - - '3000:3000' - - '3001:3001' - image: openspeedtest/latest -```` -- Warning! If you run it behind a **[Reverse Proxy](https://github.com/openspeedtest/Speed-Test/issues/4#issuecomment-1229157193)**, you should increase the `post-body content length` to 35 megabytes. - -- **[Follow our Nginx Config.](https://github.com/openspeedtest/Nginx-Configuration)** - -Now open your browser and direct it to: - -A: For **HTTP** use: `http://YOUR-SERVER-IP:3000` - -B: For **HTTPS** use: `https://YOUR-SERVER-IP:3001` - -#### Container-Port for http is 3000 -If you need to run this image on a different port for `HTTP`, Eg: change to `80` = `-p 80:3000` -#### Container-Port for https is 3001 -If you need to run this image on a different port for `HTTPS`, Eg: change to `443` = `-p 443:3001` - -### Setup Free LetsEncrypt SSL with Automatic Certificate Renewal -***Requirements*** -- PUBLIC IPV4 and/or IPV6 address. -- A domain name that resolves to speed test server's IP address. -- Email ID - -The following command will generate a Let's Encrypt certificate for your domain name and configure a cron job to automatically renew the certificate. - -```` -docker run -e ENABLE_LETSENCRYPT=True -e DOMAIN_NAME=speedtest.yourdomain.com -e USER_EMAIL=you@yourdomain.pro --restart=unless-stopped --name openspeedtest -d -p 80:3000 -p 443:3001 openspeedtest/latest -```` -#### Or use docker-compose.yml -```` -version: '3.3' -services: - speedtest: - environment: - - ENABLE_LETSENCRYPT=True - - DOMAIN_NAME=speedtest.yourdomain.com - - USER_EMAIL=you@yourdomain.pro - restart: unless-stopped - container_name: openspeedtest - ports: - - '80:3000' - - '443:3001' - image: openspeedtest/latest -```` - -### How to Use Your Own Secure Sockets Layer (SSL) Certificate, Self-Signed or Paid? -***Requirements*** -- Folder with your Certificate, Self-Signed or Paid. -- Rename .cet file and .key file to `nginx.crt` & `nginx.key` - - The folder needs to contain: - -- `nginx.crt` - -- `nginx.key` - - -```` -sudo docker run --restart=unless-stopped --name openspeedtest -d -p 3000:3000 -p 3001:3001 openspeedtest/latest -```` - -To mount a folder with your own SSL certificate to this Docker container, append the following line to the above command: - - -````bash - --v /${PATH-TO-YOUR-OWN-SSL-CERTIFICATE}:/etc/ssl/ - -```` - -I am adding a folder with nginx.crt and nginx.key from my desktop by using the following command. - -````bash - -sudo docker run -v /Users/vishnu/Desktop/docker/:/etc/ssl/ --restart=unless-stopped --name openspeedtest -d -p 3000:3000 -p 3001:3001 openspeedtest/latest - -```` -#### Or use docker-compose.yml -```` -version: '3.3' -services: - speedtest: - volumes: - - '/Users/vishnu/Desktop/docker/:/etc/ssl/' - restart: unless-stopped - container_name: openspeedtest - ports: - - '3000:3000' - - '3001:3001' - image: openspeedtest/latest -```` -## Advanced Configuration Options - -- Container Port Configuration - -To enable port changes, set the `CHANGE_CONTAINER_PORTS` environment variable to `"True"` and provide appropriate values for the following variables. - -`CHANGE_CONTAINER_PORTS=True` - -`HTTP_PORT=3000` - -`HTTPS_PORT=3001` - -- Set User - -`SET_USER=101` - -- Only Allow `CORS Request` from listed domains. - -`ALLOW_ONLY=domain1.com;domain2.com;domain3.com` - -- `SET_SERVER_NAME` Display the server name on the UI. - -`SET_SERVER_NAME=HOME-NAS` - - -Docker images run better on Linux Platforms, including your NAS. But if you install docker on macOS or Windows, you may see poor performance. I asked this on Docker forums, and they told me macOS and Windows support is for Development purposes only. For Production, you need to use any Linux Platform. - -The same Story goes for Windows NGINX. Nginx uses only one worker even if you specify n number of worker processes. They will show in Task Manager, but the system will only use one. I got this information directly from the Nginx website. - - - - - -## Why do you need to Create Your Own SpeedTest Server? - -You can run OpenSpeedTest Server in your Home Lab, Office Server or Cloud Server. So that you or employees who work from home can run a speed test to your office and make sure they can run everything smoothly. - - - -**Choosing between ISP1 & ISP2.** - - - -Sometimes your ISP2 is Faster than ISP1 when you test your speed on popular speed test sites. But when you connect to your Home/Office/Cloud, that slower connection may perform better. The only way to find out is to run a speed test against your infrastructure. - - - -**Troubleshooting network issues.** - - - -It is common even when your Internet connection is working fine, but some of the devices in your network may experience trouble getting decent connectivity to the internet. The issue might be the wrong VLAN ID or Faulty Switch. If you run a Local network speed test, you can find and fix these issues easily. - - - -**Before you add a repeater.** - - - -Most repeaters will reduce your network speed by 50%, so if you put it far away, it will perform worst, and if you put it too close, you will not get enough coverage if you run a Local Network speed test. Depending on the application requirements, you can decide exactly where you need to put your repeater. - - - -**Browsing experience.** - - - -Many useful browser extensions are out there that we all know and love. But some of them are really slowing you down for **few seconds per page you visit**. You may see good performance when you test your network performance via File Transfer or Command-line utilities, but you may experience poor performance when browsing the internet. This is due to a bad browser configuration that including unwanted extensions installed. From my experience, only keep the one you are going to use every single day. Extension that you may use once in a while should be removed or disabled for maximum performance. If you see poor performance, try OpenSpeedTest from Private Window or Incognito Window. **This tool can be used to check the browser performance and impact of Extensions on your browsing experience.** - - - -**No client-side software or plugin is required. You can run a network speed test from any device with a [Web Browser that is IE10 or newer.](https://www.youtube.com/watch?v=9f-OM_WQ7Bw&list=PLt-deStxFJOMEAs2O1lJhscMNzcg9E3Po&index=1)** - -- Like this Project? Please **Donate NOW & Keep us Alive** -> https://go.openspeedtest.com/Donate - -MIT License - -Copyright (c) 2013 - 2023 OpenSpeedTest™ - -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 permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/css/app.css b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/css/app.css deleted file mode 100644 index 39fa9393..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/css/app.css +++ /dev/null @@ -1,382 +0,0 @@ -/* roboto-regular - latin */ -@font-face { - font-family: "Roboto"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("..fonts/roboto-v30-latin-regular.eot"); /* IE9 Compat Modes */ - src: local(""), - url("../fonts/roboto-v30-latin-regular.eot?#iefix") - format("embedded-opentype"), - /* IE6-IE8 */ url("../fonts/roboto-v30-latin-regular.woff2") - format("woff2"), - /* Super Modern Browsers */ - url("../fonts/roboto-v30-latin-regular.woff") format("woff"), - /* Modern Browsers */ url("../fonts/roboto-v30-latin-regular.ttf") - format("truetype"), - /* Safari, Android, iOS */ - url("../fonts/roboto-v30-latin-regular.svg#Roboto") format("svg"); /* Legacy iOS */ -} -/* roboto-500 - latin */ -@font-face { - font-family: "Roboto"; - font-style: normal; - font-weight: 500; - font-display: swap; - src: url("assets/fonts/roboto-v30-latin-500.eot"); /* IE9 Compat Modes */ - src: local(""), - url("assets/fonts/roboto-v30-latin-500.eot?#iefix") - format("embedded-opentype"), - /* IE6-IE8 */ url("../fonts/roboto-v30-latin-500.woff2") - format("woff2"), - /* Super Modern Browsers */ url("../fonts/roboto-v30-latin-500.woff") - format("woff"), - /* Modern Browsers */ url("../fonts/roboto-v30-latin-500.ttf") - format("truetype"), - /* Safari, Android, iOS */ - url("../fonts/roboto-v30-latin-500.svg#Roboto") format("svg"); /* Legacy iOS */ -} - -body { - margin: 0px; - padding: 0px; - display: block; -} - -::-webkit-scrollbar { - display: none; -} -html { - -ms-overflow-style: none; - scrollbar-width: none; -} - -.Credits { -color: rgb(125 119 119); -text-align: center; -font-size:14px; -font-family: Roboto-Medium, Roboto; -font-weight: 500; -} -.Credits a { - text-decoration: none; - color: rgb(113, 113, 113); -} -.Credits a:hover { -color: #14b0fe; -} - -.ConnectError { - display: none; - -} - -.openSpeedtestApp { - - height: 100vh; - width: 100vw; - display: none; - overflow: hidden; -} -.main-Gaugebg { - fill: none; - stroke: rgb(231, 231, 232); - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 22px; - stroke-dasharray: 681; -} -.main-GaugeBlue { - fill: none; - stroke: url(#gradient); - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 22px; - stroke-dasharray: 681; - stroke-opacity: 0; -} -.main-GaugeWhite { - fill: none; - stroke: rgb(255, 255, 255); - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 15px; - stroke-dasharray: 0, 681; - stroke-dashoffset: 1; - stroke-opacity: 0; -} -.oDo-Meter { - font-size: 16.633283615112305px; - fill: gray; - font-family: Roboto-Medium, Roboto; - font-weight: 500; -} -.oDoLive-Speed { - font-size: 28px; - fill: #201e1e; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} - -.oDoLive-Status { - font-size: 10px; - fill: #d2d1d2; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} -.uiBg { - fill: #d2d1d2; -} -.progressbg { - stroke: rgb(231, 231, 232); - stroke-width: 8px; - stroke-linecap: round; - stroke-linejoin: round; - stroke-dasharray: 400; - stroke-dashoffset: 0; -} -.Cards { - fill: #f2f2f2; -} -.Symbol { - fill: url(#gradient); -} -.rtext { - font-size: 12px; - fill: #333; - font-family: Roboto-Medium, Roboto; - font-weight: 500; -} -.rtextnum { - font-size: 23px; - fill: #201e1e; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} -.rtextmbms { - font-size: 12px; - fill: #5f5f5f; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} -.jitter-Mob { - font-size: 9px; - fill: #5f5f5f; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} -.startButton { - fill: url(#RadialGradient1); - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - -webkit-tap-highlight-color: transparent; - cursor: pointer; - pointer-events: visible; -} -.buttonTxt { - font-size: 40px; - fill: #ffffff; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} - -.intro-Progress { - stroke: #56c4fb; - stroke-width: 8px; - stroke-linecap: round; - stroke-linejoin: round; - stroke-dasharray: 400; - stroke-dashoffset: 0; -} -.progressElmstart { - stroke: #56c4fb; - stroke-width: 8px; - stroke-linecap: round; - stroke-linejoin: round; - stroke-dasharray: 400; - stroke-dashoffset: 0; - display: block; -} -.Startsettings { - fill: url(#RadialGradient1); - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - -webkit-tap-highlight-color: transparent; - cursor: pointer; - pointer-events: visible; - opacity: 0.1; - transition: opacity 1s ease-in-out; -} -.Startsettings:hover { - opacity: 1; -} - -.progressbg { - stroke: rgb(231, 231, 232); - stroke-width: 8px; - stroke-linecap: round; - stroke-linejoin: round; - stroke-dasharray: 400; - stroke-dashoffset: 0; -} -.deskStart { - fill: none; - stroke: rgb(231, 231, 232); - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 22px; - stroke-dasharray: 681; - stroke: url(#gradient); -} -#UI-Desk { - display: none; -} -#UI-Mob { - display: none; -} -.oDoTop-Speed { - font-size: 16.96px; - fill: gray; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: end; -} -#upSymbolDesk { - fill: #14b0fe; - display: none; -} -#downSymbolDesk { - fill: #14b0fe; - display: none; -} -#upSymbolMob { - fill: #14b0fe; - display: none; -} -#downSymbolMob { - fill: #14b0fe; - display: none; -} - -#ipMob { - font-size: 15px; - fill: #201e1e; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; - display: none; -} -#ipDesk { - font-size: 15px; - fill: #201e1e; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; - display: none; -} - -.spinner { - position: absolute; - z-index: 999; - top: 50vh; - left: 50vw; - text-align: center; -} - -.spinner > div { - width: 20px; - height: 20px; - background-color: #2196f3; - - border-radius: 100%; - display: inline-block; - -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both; - animation: sk-bouncedelay 1.4s infinite ease-in-out both; -} - -.spinner .bounce1 { - -webkit-animation-delay: -0.32s; - animation-delay: -0.32s; -} - -.spinner .bounce2 { - -webkit-animation-delay: -0.16s; - animation-delay: -0.16s; -} - -@-webkit-keyframes sk-bouncedelay { - 0%, - 80%, - 100% { - -webkit-transform: scale(0); - } - 40% { - -webkit-transform: scale(1); - } -} - -@keyframes sk-bouncedelay { - 0%, - 80%, - 100% { - -webkit-transform: scale(0); - transform: scale(0); - } - 40% { - -webkit-transform: scale(1); - transform: scale(1); - } -} - -.darkmode { - margin-bottom: -15px; - fill: #75757a99; - padding-top: 16px; - display: none; - cursor: pointer; - margin-right: 30px; -} -#daymode { - margin-right: 40px; -} -.darkmode:hover { - fill: #000000; -} - -.Mobile, -.Desktop { - visibility: hidden; - width: 100vw; - height: 100vh; -} - -@media only screen and (orientation: landscape) { - .Mobile { - visibility: hidden; - } - .Desktop { - visibility: visible; - } -} - -@media only screen and (orientation: portrait) { - .spinner { - top: 42vh; - left: 42vw; - } - .Mobile { - visibility: visible; - } - .Desktop { - visibility: hidden; - } -} -@media only screen and (max-width: 300px) { - .Credits{ - display: none; - } -} diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/css/darkmode.css b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/css/darkmode.css deleted file mode 100644 index 14fef300..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/css/darkmode.css +++ /dev/null @@ -1,45 +0,0 @@ -body { - background-color: #181818; -} -#ipDesk { - fill: aliceblue; -} - -.oDoLive-Speed { - fill: #ffffff; -} -.oDoLive-Status { - fill: aliceblue; -} -#ipMob { - fill: aliceblue; -} -.rtextnum { - fill: #ffffff; -} -.rtextmbms { - fill: #ffffff; -} -.rtext { - fill: #ffffff; -} -.Cards { - fill: #000000; -} - -.main-Gaugebg { - stroke: #000000; -} -.uiBg { - fill: #000000; -} -.progressbg { - stroke: #202020; -} -.jitter-Mob { - fill: #ffffff; -} - -.ConnectError { - fill: #ffffff; -} diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.eot b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.eot deleted file mode 100644 index fec03612..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.eot and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.svg b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.svg deleted file mode 100644 index 67eecf44..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.svg +++ /dev/null @@ -1,305 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.ttf b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.ttf deleted file mode 100644 index 4f515e2a..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.ttf and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.woff b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.woff deleted file mode 100644 index d39bb52a..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.woff and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.woff2 b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.woff2 deleted file mode 100644 index 29342a8d..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.woff2 and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.eot b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.eot deleted file mode 100644 index c244b089..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.eot and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.svg b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.svg deleted file mode 100644 index 627f5a36..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.svg +++ /dev/null @@ -1,308 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.ttf b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.ttf deleted file mode 100644 index d0e63254..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.ttf and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.woff b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.woff deleted file mode 100644 index 9eaa94f9..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.woff and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.woff2 b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.woff2 deleted file mode 100644 index 020729ef..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.woff2 and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/app.svg b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/app.svg deleted file mode 100644 index 01441ac0..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/app.svg +++ /dev/null @@ -1,459 +0,0 @@ - - - - - - - - - - - - - 0 - .5 - 1 - 10 - 100 - 500 - 1000+ - ... - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DOWNLOAD - - - - - UPLOAD - - - - - - - - - - PING - - - - JITTER - - - --- - Mbps - - - --- - Mbps - - - -- - ms - - - -- - ms - - - - - - - - - -- - Jitter - -- - ms - - - - - - - - - - - - Start - - - - - - - - - - - - - - - - - - - Network Error - - - - SpeedTest by OpenSpeedTest™️ - - - - - - - - - - - - - - - - - - - - - HTML5 Network Performance Estimation Tool. by OpenSpeedTest™ - - - - - - The download speed is how fast you can pull data from the server to you in the form of images, videos, text and more. Activities such as listening to music on Spotify, downloading large files or streaming videos on Netflix all require you to download data. - - - The upload speed is how fast you send data from you to others in the form of images, videos, text and more. Activities such as uploading to YouTube or Sending email, playing live games like PubG, Voice and Video calling a friend require fast upload speeds for you to send data to someone else’s server. - - - Ping "Round-trip time" is more or less well defined as the network delay from point A to B and back. This is the sum of all encoding, queueing, processing, decoding, and propagation delays in both directions. Essentially, it's the delay when A may be expecting an answer from B for a request that requires very little processing. A fast ping means a more responsive connection, especially in applications where timing is everything (like video games). Ping is measured in Milliseconds (ms). Jitter is simply the difference in Ping "Round-trip time" . In other words, jitter is measuring time difference in packet inter-arrival time. Jitter helps diagnose internet connection stability. The higher the jitter value, the worse the stability of the connection. - - - - - - - - - Download speed is measured in megabits per second (Mbps). - - - Upload speed is measured in megabits per second (Mbps). - - - - - Ping is measured in Milliseconds (ms). - - - Jitter is measured in Milliseconds (ms). - - - - - Shows you real-time network traffic graph for Upload! - - - Shows you real-time network traffic graph for Download! - - - Your download speed! - - - Your upload speed! - - - Your ping! - - - Your jitter! - - - - - - - - - - Download speed is measured in megabits per second (Mbps). - - - Upload speed is measured in megabits per second (Mbps). - - - - - Ping is measured in Milliseconds (ms). - - - Jitter is measured in Milliseconds (ms). - - - - - Shows you real-time network traffic graph for Upload! - - - Shows you real-time network traffic graph for Download! - - - Your download speed! - - - Your upload speed! - - - Your ping! - - - Your jitter! - - - - - Click here to Run a Speed Test! Or Press "Enter" - - - Options - - - - - - HTML5 Network Performance Estimation Tool. by OpenSpeedTest™ - - - - - - The download speed is how fast you can pull data from the server to you in the form of images, videos, text and more. Activities such as listening to music on Spotify, downloading large files or streaming videos on Netflix all require you to download data. - - - The upload speed is how fast you send data from you to others in the form of images, videos, text and more. Activities such as uploading to YouTube or Sending email, playing live games like PubG, Voice and Video calling a friend require fast upload speeds for you to send data to someone else’s server. - - - Ping "Round-trip time" is more or less well defined as the network delay from point A to B and back. This is the sum of all encoding, queueing, processing, decoding, and propagation delays in both directions. Essentially, it's the delay when A may be expecting an answer from B for a request that requires very little processing. A fast ping means a more responsive connection, especially in applications where timing is everything (like video games). Ping is measured in Milliseconds (ms). Jitter is simply the difference in Ping "Round-trip time" . In other words, jitter is measuring time difference in packet inter-arrival time. Jitter helps diagnose internet connection stability. The higher the jitter value, the worse the stability of the connection. - - - - - - - - - Download speed is measured in megabits per second (Mbps). - - - Upload speed is measured in megabits per second (Mbps). - - - Ping is measured in Milliseconds (ms). - - - - - - Shows you real-time network traffic graph for Download! - - - Upload speed is measured in megabits per second (Mbps). - - - Your ping! and Jitter - - - Your upload speed! - - - Your download speed! - - - - - - - - - Click here to run a speed! - - - Options - - - - - - - - - - - - - - \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/android-chrome-192x192.png b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/android-chrome-192x192.png deleted file mode 100644 index df78b83c..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/android-chrome-192x192.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/android-chrome-512x512.png b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/android-chrome-512x512.png deleted file mode 100644 index 559d3eaf..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/android-chrome-512x512.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/apple-touch-icon.png b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/apple-touch-icon.png deleted file mode 100644 index 14aec788..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/apple-touch-icon.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/browserconfig.xml b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/browserconfig.xml deleted file mode 100644 index ef1658c6..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/browserconfig.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - #ffc40d - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/favicon-16x16.png b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/favicon-16x16.png deleted file mode 100644 index 0dd76a80..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/favicon-16x16.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/favicon-32x32.png b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/favicon-32x32.png deleted file mode 100644 index e3ad7510..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/favicon-32x32.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/favicon.ico b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/favicon.ico deleted file mode 100644 index bebc1524..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/favicon.ico and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-1x.png b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-1x.png deleted file mode 100644 index 50bdeae8..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-1x.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-2x.png b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-2x.png deleted file mode 100644 index 98687998..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-2x.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-3x.png b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-3x.png deleted file mode 100644 index b612922b..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-3x.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-4x.png b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-4x.png deleted file mode 100644 index 5936c147..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-4x.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/mstile-150x150.png b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/mstile-150x150.png deleted file mode 100644 index eb66ae2b..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/mstile-150x150.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/safari-pinned-tab.svg b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/safari-pinned-tab.svg deleted file mode 100644 index e525e9b4..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/safari-pinned-tab.svg +++ /dev/null @@ -1,108 +0,0 @@ - - - - -Created by potrace 1.11, written by Peter Selinger 2001-2013 - - - - - - - - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/site.webmanifest b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/site.webmanifest deleted file mode 100644 index 058491fb..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/site.webmanifest +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "OpenSpeedTest", - "short_name": "Speed Test", - "icons": [ - { - "src": "/assets/images/icons/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/assets/images/icons/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone", - "start_url": "/" - - -} diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/js/app-2.5.4.js b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/js/app-2.5.4.js deleted file mode 100644 index f5b5b6e4..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/js/app-2.5.4.js +++ /dev/null @@ -1,1427 +0,0 @@ -/* - Official Website : https://OpenSpeedTest.COM | Email: support@openspeedtest.com - Developed by : Vishnu | https://Vishnu.Pro | Email : me@vishnu.pro - Like this Project? Please Donate NOW & Keep us Alive -> https://go.openspeedtest.com/Donate - Speed Test by OpenSpeedTest™️ is Free and Open-Source Software (FOSS) with MIT License. - Read full license terms @ http://go.openspeedtest.com/License - If you have any Questions, ideas or Comments Please Send it via -> https://go.openspeedtest.com/SendMessage -*/ -window.onload = function() { - var appSVG = document.getElementById("OpenSpeedTest-UI"); - appSVG.parentNode.replaceChild(appSVG.contentDocument.documentElement, appSVG); - ostOnload(); - OpenSpeedTest.Start(); -}; -(function(OpenSpeedTest) { - var Status; - var ProG; - var Callback = function(callback) { - if (callback && typeof callback === "function") { - callback(); - } - }; - function _(el) { - if (!(this instanceof _)) { - return new _(el); - } - this.el = document.getElementById(el); - } - _.prototype.fade = function fade(type, ms, callback00) { - var isIn = type === "in", opacity = isIn ? 0 : 1, interval = 14, duration = ms, gap = interval / duration, self = this; - if (isIn) { - self.el.style.display = "block"; - self.el.style.opacity = opacity; - } - function func() { - opacity = isIn ? opacity + gap : opacity - gap; - self.el.style.opacity = opacity; - if (opacity <= 0) { - self.el.style.display = "none"; - } - if (opacity <= 0 || opacity >= 1) { - window.clearInterval(fading, Callback(callback00)); - } - } - var fading = window.setInterval(func, interval); - }; - var easeOutQuint = function(t, b, c, d) { - t /= d; - t--; - return c * (t * t * t * t * t + 1) + b; - }; - var easeOutCubic = function(t, b, c, d) { - t /= d; - t--; - return c * (t * t * t + 1) + b; - }; - var openSpeedtestShow = function() { - this.YourIP = _("YourIP"); - this.ipDesk = _("ipDesk"); - this.ipMob = _("ipMob"); - this.downSymbolDesk = _("downSymbolDesk"); - this.upSymbolDesk = _("upSymbolDesk"); - this.upSymbolMob = _("upSymbolMob"); - this.downSymbolMob = _("downSymbolMob"); - this.settingsMob = _("settingsMob"); - this.settingsDesk = _("settingsDesk"); - this.oDoLiveStatus = _("oDoLiveStatus"); - this.ConnectErrorMob = _("ConnectErrorMob"); - this.ConnectErrorDesk = _("ConnectErrorDesk"); - this.downResult = _("downResult"); - this.upRestxt = _("upRestxt"); - this.pingResult = _("pingResult"); - this.jitterDesk = _("jitterDesk"); - this.pingMobres = _("pingMobres"); - this.JitterResultMon = _("JitterResultMon"); - this.JitterResultms = _("JitterResultms"); - this.UI_Desk = _("UI-Desk"); - this.UI_Mob = _("UI-Mob"); - this.oDoTopSpeed = _("oDoTopSpeed"); - this.startButtonMob = _("startButtonMob"); - this.startButtonDesk = _("startButtonDesk"); - this.intro_Desk = _("intro-Desk"); - this.intro_Mob = _("intro-Mob"); - this.loader = _("loading_app"); - this.OpenSpeedtest = _("OpenSpeedtest"); - this.mainGaugebg_Desk = _("mainGaugebg-Desk"); - this.mainGaugeBlue_Desk = _("mainGaugeBlue-Desk"); - this.mainGaugeWhite_Desk = _("mainGaugeWhite-Desk"); - this.mainGaugebg_Mob = _("mainGaugebg-Mob"); - this.mainGaugeBlue_Mob = _("mainGaugeBlue-Mob"); - this.mainGaugeWhite_Mob = _("mainGaugeWhite-Mob"); - this.oDoLiveSpeed = _("oDoLiveSpeed"); - this.progressStatus_Mob = _("progressStatus-Mob"); - this.progressStatus_Desk = _("progressStatus-Desk"); - this.graphc1 = _("graphc1"); - this.graphc2 = _("graphc2"); - this.graphMob2 = _("graphMob2"); - this.graphMob1 = _("graphMob1"); - this.text = _("text"); - this.scale = [{degree:680, value:0}, {degree:570, value:0.5}, {degree:460, value:1}, {degree:337, value:10}, {degree:220, value:100}, {degree:115, value:500}, {degree:0, value:1000},]; - this.element = ""; - this.chart = ""; - this.polygon = ""; - this.width = 200; - this.height = 50; - this.maxValue = 0; - this.values = []; - this.points = []; - this.vSteps = 5; - this.measurements = []; - this.points = []; - }; - openSpeedtestShow.prototype.reset = function() { - this.element = ""; - this.chart = ""; - this.polygon = ""; - this.width = 200; - this.height = 50; - this.maxValue = 0; - this.values = []; - this.points = []; - this.vSteps = 5; - this.measurements = []; - this.points = []; - }; - openSpeedtestShow.prototype.ip = function() { - var Self = this; - if (Self.ipDesk.el.style.display === "block") { - Self.ipDesk.el.style.display = "none"; - Self.ipMob.el.style.display = "none"; - } else { - Self.ipDesk.el.style.display = "block"; - Self.ipMob.el.style.display = "block"; - } - }; - openSpeedtestShow.prototype.prePing = function() { - this.loader.fade("out", 500); - this.OpenSpeedtest.fade("in", 1000); - }; - openSpeedtestShow.prototype.app = function() { - this.loader.fade("out", 500, this.ShowAppIntro()); - }; - openSpeedtestShow.prototype.ShowAppIntro = function() { - this.OpenSpeedtest.fade("in", 1000); - }; - openSpeedtestShow.prototype.userInterface = function() { - var Self = this; - this.intro_Desk.fade("out", 1000); - this.intro_Mob.fade("out", 1000, this.ShowUI()); - }; - openSpeedtestShow.prototype.ShowUI = function() { - this.UI_Desk.fade("in", 1000); - this.UI_Mob.fade("in", 1000, uiLoaded); - function uiLoaded(argument) { - Status = "Loaded"; - console.log("Developed by Vishnu. Email --\x3e me@vishnu.pro"); - } - }; - openSpeedtestShow.prototype.Symbol = function(dir) { - if (dir == 0) { - this.downSymbolMob.el.style.display = "block"; - this.downSymbolDesk.el.style.display = "block"; - this.upSymbolMob.el.style.display = "none"; - this.upSymbolDesk.el.style.display = "none"; - } - if (dir == 1) { - this.downSymbolMob.el.style.display = "none"; - this.downSymbolDesk.el.style.display = "none"; - this.upSymbolMob.el.style.display = "block"; - this.upSymbolDesk.el.style.display = "block"; - } - if (dir == 2) { - this.downSymbolMob.el.style.display = "none"; - this.downSymbolDesk.el.style.display = "none"; - this.upSymbolMob.el.style.display = "none"; - this.upSymbolDesk.el.style.display = "none"; - } - }; - openSpeedtestShow.prototype.Graph = function(speed, select) { - if (!("remove" in Element.prototype)) { - Element.prototype.remove = function() { - if (this.parentNode) { - this.parentNode.removeChild(this); - } - }; - } - var Self = this; - var Remove; - if (select === 0) { - var Graphelement = this.graphc1.el; - Remove = "line"; - this.graphMob2.el.style.display = "none"; - this.graphMob1.el.style.display = "block"; - } else { - Graphelement = this.graphc2.el; - Remove = "line2"; - this.graphMob1.el.style.display = "none"; - this.graphMob2.el.style.display = "block"; - } - if (!isNaN(speed)) { - this.values.push(speed); - } else { - this.values.push(""); - } - function calcMeasure() { - for (x = 0; x < Self.vSteps; x++) { - var measurement = Math.ceil(Self.maxValue / Self.vSteps * (x + 1)); - Self.measurements.push(measurement); - } - Self.measurements.reverse(); - } - function createChart(element, values) { - calcMaxValue(); - calcPoints(); - calcMeasure(); - var removeLine = document.getElementsByClassName(Remove); - while (removeLine.length > 0) { - removeLine[0].remove(); - } - Self.polygon = document.createElementNS("http://www.w3.org/2000/svg", "polygon"); - Self.polygon.setAttribute("points", Self.points); - Self.polygon.setAttribute("class", Remove); - if (Self.values.length > 1) { - Graphelement.appendChild(Self.polygon); - } - } - function calcPoints() { - if (Self.values.length > 1) { - var points = "0," + Self.height + " "; - for (x = 0; x < Self.values.length; x++) { - var perc = Self.values[x] / Self.maxValue; - var steps = 130 / (Self.values.length - 1); - var point = (steps * x).toFixed(2) + "," + (Self.height - Self.height * perc).toFixed(2) + " "; - points += point; - } - points += "130," + Self.height; - Self.points = points; - } - } - var x; - function calcMaxValue() { - Self.maxValue = 0; - for (x = 0; x < Self.values.length; x++) { - if (Self.values[x] > Self.maxValue) { - Self.maxValue = Self.values[x]; - } - } - Self.maxValue = Math.ceil(Self.maxValue); - } - if (speed > 0) { - createChart(Graphelement, speed); - } - }; - openSpeedtestShow.prototype.progress = function(Switch, duration) { - var Self = this; - var Stop = duration; - var Stage = Switch; - var currTime = Date.now(); - var chan2 = 0 - 400; - var interval = setInterval(function() { - var timeNow = (Date.now() - currTime) / 1000; - var toLeft = easeOutCubic(timeNow, 400, 400, Stop); - var toRight = easeOutCubic(timeNow, 400, chan2, Stop); - if (Stage) { - Self.progressStatus_Desk.el.style.strokeDashoffset = toLeft; - Self.progressStatus_Mob.el.style.strokeDashoffset = toLeft; - } else { - Self.progressStatus_Desk.el.style.strokeDashoffset = toRight; - Self.progressStatus_Mob.el.style.strokeDashoffset = toRight; - } - if (timeNow >= Stop) { - clearInterval(interval); - ProG = "done"; - Self.progressStatus_Desk.el.style.strokeDashoffset = 800; - Self.progressStatus_Mob.el.style.strokeDashoffset = 800; - } - }, 14); - }; - openSpeedtestShow.prototype.mainGaugeProgress = function(currentSpeed) { - var Self = this; - var speed = currentSpeed; - if (speed < 0) { - speed = 0; - } - var mainGaugeOffset = Self.getNonlinearDegree(speed); - if (currentSpeed > 0) { - this.mainGaugeBlue_Desk.el.style.strokeOpacity = 1; - this.mainGaugeWhite_Desk.el.style.strokeOpacity = 1; - this.mainGaugeBlue_Mob.el.style.strokeOpacity = 1; - this.mainGaugeWhite_Mob.el.style.strokeOpacity = 1; - this.mainGaugeBlue_Desk.el.style.strokeDashoffset = mainGaugeOffset; - this.mainGaugeWhite_Desk.el.style.strokeDashoffset = mainGaugeOffset == 0 ? 1 : mainGaugeOffset + 1; - this.mainGaugeBlue_Mob.el.style.strokeDashoffset = mainGaugeOffset; - this.mainGaugeWhite_Mob.el.style.strokeDashoffset = mainGaugeOffset == 0 ? 1 : mainGaugeOffset + 1; - } - if (mainGaugeOffset == 0 && speed > 1000) { - this.mainGaugeBlue_Mob.el.style.strokeDashoffset = mainGaugeOffset >= 681 ? 681 : mainGaugeOffset; - this.mainGaugeWhite_Mob.el.style.strokeDashoffset = mainGaugeOffset == 0 ? 1 : mainGaugeOffset + 1; - this.mainGaugeWhite_Desk.el.style.strokeDashoffset = mainGaugeOffset == 0 ? 1 : mainGaugeOffset + 1; - this.mainGaugeBlue_Desk.el.style.strokeDashoffset = mainGaugeOffset >= 681 ? 681 : mainGaugeOffset; - } else if (mainGaugeOffset == 0 && speed <= 0) { - this.mainGaugeBlue_Mob.el.style.strokeDashoffset = 681.1; - this.mainGaugeWhite_Mob.el.style.strokeDashoffset = 0.1; - this.mainGaugeWhite_Desk.el.style.strokeDashoffset = 0.1; - this.mainGaugeBlue_Desk.el.style.strokeDashoffset = 681.1; - } - }; - openSpeedtestShow.prototype.showStatus = function(e) { - this.oDoLiveStatus.el.textContent = e; - }; - openSpeedtestShow.prototype.ConnectionError = function() { - this.ConnectErrorMob.el.style.display = "block"; - this.ConnectErrorDesk.el.style.display = "block"; - }; - openSpeedtestShow.prototype.uploadResult = function(upload) { - if (upload < 1) { - this.upRestxt.el.textContent = upload.toFixed(3); - } - if (upload >= 1 && upload < 9999) { - this.upRestxt.el.textContent = upload.toFixed(1); - } - if (upload >= 10000 && upload < 99999) { - this.upRestxt.el.textContent = upload.toFixed(1); - this.upRestxt.el.style.fontSize = "20px"; - } - if (upload >= 100000) { - this.upRestxt.el.textContent = upload.toFixed(1); - this.upRestxt.el.style.fontSize = "18px"; - } - }; - openSpeedtestShow.prototype.pingResults = function(data, Display) { - var ShowData = data; - if (Display === "Ping") { - if (ShowData >= 1 && ShowData < 10000) { - this.pingResult.el.textContent = Math.floor(ShowData); - this.pingMobres.el.textContent = Math.floor(ShowData); - } else if (ShowData >= 0 && ShowData < 1) { - if (ShowData == 0) { - ShowData = 0; - } - this.pingResult.el.textContent = ShowData; - this.pingMobres.el.textContent = ShowData; - } - } - if (Display === "Error") { - this.oDoLiveSpeed.el.textContent = ShowData; - } - }; - openSpeedtestShow.prototype.downloadResult = function(download) { - if (download < 1) { - this.downResult.el.textContent = download.toFixed(3); - } - if (download >= 1 && download < 9999) { - this.downResult.el.textContent = download.toFixed(1); - } - if (download >= 10000 && download < 99999) { - this.downResult.el.textContent = download.toFixed(1); - this.downResult.el.style.fontSize = "20px"; - } - if (download >= 100000) { - this.downResult.el.textContent = download.toFixed(1); - this.downResult.el.style.fontSize = "18px"; - } - }; - openSpeedtestShow.prototype.jitterResult = function(data, Display) { - var ShowData = data; - if (Display === "Jitter") { - if (ShowData >= 1 && ShowData < 10000) { - this.jitterDesk.el.textContent = Math.floor(ShowData); - if (ShowData >= 1 && ShowData < 100) { - this.JitterResultMon.el.textContent = Math.floor(ShowData); - } - if (ShowData >= 100) { - var kData = (ShowData / 1000).toFixed(1); - this.JitterResultMon.el.textContent = kData + "k"; - } - } else if (ShowData >= 0 && ShowData < 1) { - if (ShowData == 0) { - ShowData = 0; - } - this.jitterDesk.el.textContent = ShowData; - this.JitterResultMon.el.textContent = ShowData; - } - } - }; - openSpeedtestShow.prototype.LiveSpeed = function(data, Display) { - var ShowData = data; - if (Display === "countDown") { - var speed = ShowData.toFixed(0); - this.oDoLiveSpeed.el.textContent = speed; - return; - } - if (Display === "speedToZero") { - if (typeof ShowData == "number") { - ShowData = ShowData.toFixed(1); - } - if (ShowData <= 0) { - ShowData = 0; - } - this.oDoLiveSpeed.el.textContent = ShowData; - this.oDoTopSpeed.el.textContent = "1000+"; - this.oDoTopSpeed.el.style.fontSize = "16.9px"; - this.oDoTopSpeed.el.style.fill = "gray"; - return; - } - if (Display === "Ping") { - if (ShowData >= 1 && ShowData < 10000) { - this.oDoLiveSpeed.el.textContent = Math.floor(ShowData); - } else if (ShowData >= 0 && ShowData < 1) { - if (ShowData == 0) { - ShowData = 0; - } - this.oDoLiveSpeed.el.textContent = ShowData; - } - } else { - if (ShowData == 0) { - var speed = ShowData.toFixed(0); - this.oDoLiveSpeed.el.textContent = speed; - } - if (ShowData <= 1 && ShowData > 0) { - var speed = ShowData.toFixed(3); - this.oDoLiveSpeed.el.textContent = speed; - } - if (ShowData > 1) { - var speed = ShowData.toFixed(1); - this.oDoLiveSpeed.el.textContent = speed; - } - if (ShowData <= 1000) { - this.oDoTopSpeed.el.textContent = "1000+"; - this.oDoTopSpeed.el.style.fontSize = "16.9px"; - this.oDoTopSpeed.el.style.fill = "gray"; - } - if (ShowData >= 1010) { - this.oDoTopSpeed.el.textContent = Math.floor(ShowData / 1010) * 1000 + "+"; - this.oDoTopSpeed.el.style.fill = "gray"; - this.oDoTopSpeed.el.style.fontSize = "17.2px"; - } - } - }; - openSpeedtestShow.prototype.GaugeProgresstoZero = function(currentSpeed, status) { - var speed = currentSpeed; - var Self = this; - var duration = 3; - if (speed >= 0) { - var time = Date.now(); - var SpeedtoZero = 0 - speed; - var interval = setInterval(function() { - var timeNow = (Date.now() - time) / 1000; - var speedToZero = easeOutQuint(timeNow, speed, SpeedtoZero, duration); - Self.LiveSpeed(speedToZero, "speedToZero"); - Self.mainGaugeProgress(speedToZero); - if (timeNow >= duration || speedToZero <= 0) { - clearInterval(interval); - Self.LiveSpeed(0, "speedToZero"); - Self.mainGaugeProgress(0); - Status = status; - } - }, 16); - } - }; - openSpeedtestShow.prototype.getNonlinearDegree = function(mega_bps) { - var i = 0; - if (0 == mega_bps || mega_bps <= 0 || isNaN(mega_bps)) { - return 0; - } - while (i < this.scale.length) { - if (mega_bps > this.scale[i].value) { - i++; - } else { - return this.scale[i - 1].degree + (mega_bps - this.scale[i - 1].value) * (this.scale[i].degree - this.scale[i - 1].degree) / (this.scale[i].value - this.scale[i - 1].value); - } - } - return this.scale[this.scale.length - 1].degree; - }; - var openSpeedtestGet = function() { - this.OverAllTimeAvg = window.performance.now(); - this.SpeedSamples = []; - this.FinalSpeed; - }; - openSpeedtestGet.prototype.reset = function() { - this.OverAllTimeAvg = window.performance.now(); - this.SpeedSamples = []; - this.FinalSpeed = 0; - }; - openSpeedtestGet.prototype.ArraySum = function(Arr) { - var array = Arr; - if (array) { - var sum = array.reduce(function(A, B) { - if (typeof A === "number" && typeof B === "number") { - return A + B; - } - }, 0); - return sum; - } else { - return 0; - } - }; - openSpeedtestGet.prototype.AvgSpeed = function(Livespeed, Start, duration) { - var Self = this; - this.timeNow = (window.performance.now() - this.OverAllTimeAvg) / 1000; - this.FinalSpeed; - var StartRecoding = Start; - StartRecoding = duration - StartRecoding; - if (this.timeNow >= StartRecoding) { - if (Livespeed > 0) { - this.SpeedSamples.push(Livespeed); - } - Self.FinalSpeed = Self.ArraySum(Self.SpeedSamples) / Self.SpeedSamples.length; - } - return Self.FinalSpeed; - }; - openSpeedtestGet.prototype.uRandom = function(size, callback) { - var size = size; - var randomValue = new Uint32Array(262144); - function getRandom() { - var n = randomValue.length; - for (var i = 0; i < n; i++) { - randomValue[i] = Math.random() * 4294967296; - } - return randomValue; - } - var randomData = []; - var genData = function(dataSize) { - var dataSize = dataSize; - for (var i = 0; i < dataSize; i++) { - randomData[i] = getRandom(); - } - return randomData; - }; - return new Blob(genData(size), {type:"application/octet-stream"}, Callback(callback)); - }; - openSpeedtestGet.prototype.addEvt = function(o, e, f) { - o.addEventListener(e, f); - }; - openSpeedtestGet.prototype.remEvt = function(o, e, f) { - o.removeEventListener(e, f); - }; - var openSpeedtestEngine = function() { - var Get = new openSpeedtestGet(); - var Show = new openSpeedtestShow(); - Show.app(); - var SendData; - var myhostName = location.hostname; - var key; - var TestServerip; - var downloadSpeed; - var uploadSpeed; - var dataUsedfordl; - var dataUsedforul; - var pingEstimate; - var jitterEstimate; - var logData; - var return_data; - var ReQ = []; - var StartTime = []; - var CurrentTime = []; - var LiveSpeedArr; - var dLoaded = 0; - var uLoaded = 0; - var currentSpeed = 0; - var uploadTimeing; - var downloadTimeing; - var downloadTime; - var uploadTime; - var saveTestData; - var stop = 0; - function reSett() { - StartTime = 0; - CurrentTime = 0; - LiveSpeedArr = 0; - currentSpeed = 0; - } - var userAgentString; - if (window.navigator.userAgent) { - userAgentString = window.navigator.userAgent; - } else { - userAgentString = "Not Found"; - } - var ulFinal = ulDuration * 0.6; - var dlFinal = dlDuration * 0.6; - function setFinal() { - if (ulDuration * 0.6 >= 7) { - ulFinal = 7; - } - if (dlDuration * 0.6 >= 7) { - dlFinal = 7; - } - } - setFinal(); - var launch = true; - var init = true; - Get.addEvt(Show.settingsMob.el, "click", ShowIP); - Get.addEvt(Show.settingsDesk.el, "click", ShowIP); - Get.addEvt(Show.startButtonDesk.el, "click", runTasks); - Get.addEvt(Show.startButtonMob.el, "click", runTasks); - Get.addEvt(document, "keypress", hiEnter); - var addEvent = true; - var getParams = function(url) { - var params = {}; - var parser = document.createElement("a"); - parser.href = url; - var query = parser.search.substring(1); - var vars = query.split("&"); - for (var i = 0; i < vars.length; i++) { - var pair = vars[i].split("="); - params[pair[0]] = decodeURIComponent(pair[1]); - } - return params; - }; - var getCommand = getParams(window.location.href.toLowerCase()); - if (setPingSamples) { - if (typeof getCommand.ping === "string" || typeof getCommand.p === "string") { - var setPing; - if (typeof getCommand.ping !== "undefined") { - setPing = getCommand.ping; - } else if (typeof getCommand.p !== "undefined") { - setPing = getCommand.p; - } - if (setPing > 0) { - pingSamples = setPing; - pingSamples = setPing; - } - } - } - if (setPingTimeout) { - if (typeof getCommand.out === "string" || typeof getCommand.o === "string") { - var setOut; - if (typeof getCommand.out !== "undefined") { - setOut = getCommand.out; - } else if (typeof getCommand.o !== "undefined") { - setOut = getCommand.o; - } - if (setOut > 1) { - pingTimeOut = setOut; - pingTimeOut = setOut; - } - } - } - if (setHTTPReq) { - if (typeof getCommand.xhr === "string" || typeof getCommand.x === "string") { - var setThreads; - if (typeof getCommand.xhr !== "undefined") { - setThreads = getCommand.xhr; - } else if (typeof getCommand.x !== "undefined") { - setThreads = getCommand.x; - } - if (setThreads > 0 && setThreads <= 32) { - dlThreads = setThreads; - ulThreads = setThreads; - } - } - } - function isValidHttpUrl(str) { - var regex = /(?:https?):\/\/(\w+:?\w*)?(\S+)(:\d+)?(\/|\/([\w#!:.?+=&%!\-\/]))?/; - if (!regex.test(str)) { - return false; - } else { - return true; - } - } - if (selectServer) { - if (typeof getCommand.host === "string" || typeof getCommand.h === "string") { - var severAddress; - if (typeof getCommand.host !== "undefined") { - severAddress = getCommand.host; - } else if (typeof getCommand.h !== "undefined") { - severAddress = getCommand.h; - } - if (isValidHttpUrl(severAddress)) { - openSpeedTestServerList = [{ServerName:"Home", Download:severAddress + "/downloading", Upload:severAddress + "/upload", ServerIcon:"DefaultIcon",},]; - } - } - } - var custom = parseInt(getCommand.stress); - var customS = parseInt(getCommand.s); - var runStress; - var runStressCustom; - if (typeof getCommand.stress === "string") { - runStress = getCommand.stress; - runStressCustom = custom; - } else if (typeof getCommand.s === "string") { - runStress = getCommand.s; - runStressCustom = customS; - } - if (runStress && stressTest) { - if (runStress === "low" || runStress === "l") { - dlDuration = 300; - ulDuration = 300; - } - if (runStress === "medium" || runStress === "m") { - dlDuration = 600; - ulDuration = 600; - } - if (runStress === "high" || runStress === "h") { - dlDuration = 900; - ulDuration = 900; - } - if (runStress === "veryhigh" || runStress === "v") { - dlDuration = 1800; - ulDuration = 1800; - } - if (runStress === "extreme" || runStress === "e") { - dlDuration = 3600; - ulDuration = 3600; - } - if (runStress === "day" || runStress === "d") { - dlDuration = 86400; - ulDuration = 86400; - } - if (runStress === "year" || runStress === "y") { - dlDuration = 31557600; - ulDuration = 31557600; - } - if (custom > 12 || customS > 12) { - dlDuration = runStressCustom; - ulDuration = runStressCustom; - } - } - var overheadClean = parseInt(getCommand.clean); - var overheadCleanC = parseInt(getCommand.c); - var customOverHeadValue = 1; - if (overheadClean) { - customOverHeadValue = overheadClean; - } else if (overheadCleanC) { - customOverHeadValue = overheadCleanC; - } - if (enableClean) { - if (typeof getCommand.clean === "string" || typeof getCommand.c === "string") { - if (overheadClean >= 1 || overheadCleanC >= 1) { - if (overheadClean < 5 || overheadCleanC < 5) { - upAdjust = 1 + customOverHeadValue / 100; - dlAdjust = 1 + customOverHeadValue / 100; - } - } else { - upAdjust = 1; - dlAdjust = 1; - } - } - } - var OpenSpeedTestRun = parseInt(getCommand.run); - var OpenSpeedTestRunR = parseInt(getCommand.r); - var OpenSpeedTestStart; - if (enableRun) { - if (typeof getCommand.run === "string" || typeof getCommand.r === "string") { - if (OpenSpeedTestRun > 0) { - OpenSpeedTestStart = OpenSpeedTestRun; - } else if (OpenSpeedTestRunR > 0) { - OpenSpeedTestStart = OpenSpeedTestRunR; - } else { - OpenSpeedTestStart = 0; - } - } - } - if (OpenSpeedTestStart >= 0) { - if (launch) { - runTasks(); - } - } - var runTest = getCommand.test; - var runTestT = getCommand.t; - var SelectTest = false; - if (selectTest) { - if (typeof runTest === "string" || typeof runTestT === "string") { - var runTestC; - if (runTest) { - runTestC = runTest; - SelectTest = runTest; - } else if (runTestT) { - runTestC = runTestT; - SelectTest = runTestT; - } - if (runTestC === "download" || runTestC === "d") { - uploadSpeed = 0; - dataUsedforul = 0; - SelectTest = "Download"; - if (launch) { - runTasks(); - } - } else if (runTestC === "upload" || runTestC === "u") { - downloadSpeed = 0; - dataUsedfordl = 0; - SelectTest = "Upload"; - stop = 1; - if (launch) { - runTasks(); - } - } else if (runTestC === "ping" || runTestC === "p") { - uploadSpeed = 0; - dataUsedforul = 0; - downloadSpeed = 0; - dataUsedfordl = 0; - SelectTest = "Ping"; - if (launch) { - runTasks(); - } - } else { - SelectTest = false; - } - } - } - var Startit = 0; - function removeEvts() { - Get.remEvt(Show.settingsMob.el, "click", ShowIP); - Get.remEvt(Show.settingsDesk.el, "click", ShowIP); - Get.remEvt(Show.startButtonDesk.el, "click", runTasks); - Get.remEvt(Show.startButtonMob.el, "click", runTasks); - Get.remEvt(document, "keypress", hiEnter); - } - var requestIP = false; - function ShowIP() { - if (requestIP) { - Show.YourIP.el.textContent = "Please wait.."; - ServerConnect(7); - requestIP = false; - } - Show.ip(); - } - function runTasks() { - if (addEvent) { - removeEvts(); - addEvent = false; - } - if (OpenSpeedTestStart >= 0) { - launch = false; - Show.userInterface(); - init = false; - var AutoTme = Math.ceil(Math.abs(OpenSpeedTestStart)); - Show.showStatus("Automatic Test Starts in ..."); - var autoTest = setInterval(countDownF, 1000); - } - function countDownF() { - if (AutoTme >= 1) { - AutoTme = AutoTme - 1; - Show.LiveSpeed(AutoTme, "countDown"); - } else { - if (AutoTme <= 0) { - clearInterval(autoTest); - launch = true; - OpenSpeedTestStart = undefined; - runTasks(); - } - } - } - if (openSpeedTestServerList === "fetch" && launch === true) { - launch = false; - Show.showStatus("Fetching Server Info.."); - ServerConnect(6); - } - if (launch === true) { - if (SelectTest === "Ping") { - testRun(); - } else if (SelectTest === "Download") { - testRun(); - } else if (SelectTest === "Upload") { - testRun(); - } else if (SelectTest === false) { - testRun(); - } - } - } - var osttm = "\u2122"; - var myname = "OpenSpeedTest"; - var com = ".com"; - var ost = myname + osttm; - function hiEnter(e) { - if (e.key === "Enter") { - runTasks(); - } - } - var showResult = 0; - if (openChannel === "web") { - showResult = webRe; - requestIP = true; - } - if (openChannel === "widget") { - showResult = widgetRe; - requestIP = true; - } - if (openChannel === "selfwidget") { - showResult = widgetRe; - TestServerip = domainx; - myhostName = TestServerip; - } - if (openChannel === "dev") { - } - function testRun() { - if (init) { - Show.userInterface(); - init = false; - } - OpenSpeedtest(); - } - function OpenSpeedtest() { - if (openChannel === "widget" || openChannel === "web") { - ServerConnect(1); - } - function readyToUP() { - uploadTime = window.performance.now(); - upReq(); - } - var Engine = setInterval(function() { - if (Status === "Loaded") { - Status = "busy"; - sendPing(0); - } - if (Status === "Ping") { - Status = "busy"; - Show.showStatus("Milliseconds"); - } - if (Status === "Download") { - Show.showStatus("Initializing.."); - Get.reset(); - reSett(); - Show.reset(); - downloadTime = window.performance.now(); - downReq(); - Status = "initDown"; - } - if (Status === "Downloading") { - Show.Symbol(0); - if (Startit == 0) { - Startit = 1; - Show.showStatus("Testing download speed.."); - var extraTime = (window.performance.now() - downloadTime) / 1000; - dReset = extraTime; - Show.progress(1, dlDuration + 2.5); - dlDuration += extraTime; - } - downloadTimeing = (window.performance.now() - downloadTime) / 1000; - reportCurrentSpeed("dl"); - Show.showStatus("Mbps download"); - Show.mainGaugeProgress(currentSpeed); - Show.LiveSpeed(currentSpeed); - Show.Graph(currentSpeed, 0); - downloadSpeed = Get.AvgSpeed(currentSpeed, dlFinal, dlDuration); - if (downloadTimeing >= dlDuration && ProG == "done") { - if (SelectTest) { - Show.GaugeProgresstoZero(currentSpeed, "SendR"); - Show.showStatus("All done"); - Show.Symbol(2); - } else { - Show.GaugeProgresstoZero(currentSpeed, "Upload"); - } - Show.downloadResult(downloadSpeed); - dataUsedfordl = dLoaded; - stop = 1; - Status = "busy"; - reSett(); - Get.reset(); - } - } - if (Status == "Upload") { - if (stop === 1) { - Show.Symbol(1); - Status = "initup"; - Show.showStatus("Initializing.."); - Show.LiveSpeed("...", "speedToZero"); - SendData = Get.uRandom(ulDataSize, readyToUP); - if (SelectTest) { - Startit = 1; - } - } - } - if (Status === "Uploading") { - if (Startit == 1) { - Startit = 2; - Show.showStatus("Testing upload speed.."); - currentSpeed = 0; - Get.reset(); - Show.reset(); - var extraUTime = (window.performance.now() - uploadTime) / 1000; - uReset = extraUTime; - Show.progress(false, ulDuration + 2.5); - ulDuration += extraUTime; - } - Show.showStatus("Mbps upload"); - uploadTimeing = (window.performance.now() - uploadTime) / 1000; - reportCurrentSpeed("up"); - Show.mainGaugeProgress(currentSpeed); - Show.LiveSpeed(currentSpeed); - Show.Graph(currentSpeed, 1); - uploadSpeed = Get.AvgSpeed(currentSpeed, ulFinal, ulDuration); - if (uploadTimeing >= ulDuration && stop == 1) { - dataUsedforul = uLoaded; - Show.uploadResult(uploadSpeed); - Show.GaugeProgresstoZero(currentSpeed, "SendR"); - SendData = undefined; - Show.showStatus("All done"); - Show.Symbol(2); - Status = "busy"; - stop = 0; - } - } - if (Status === "Error") { - Show.showStatus("Check your network connection status."); - Show.ConnectionError(); - Status = "busy"; - clearInterval(Engine); - var dummyElement = document.createElement("div"); - dummyElement.innerHTML = ''; - var htmlAnchorElement = dummyElement.querySelector("a"); - Show.oDoLiveSpeed.el.textContent = "Network Error"; - var circleSVG = document.getElementById("oDoLiveSpeed"); - htmlAnchorElement.innerHTML = circleSVG.innerHTML; - circleSVG.innerHTML = dummyElement.innerHTML; - } - if (Status === "SendR") { - Show.showStatus("All done"); - var dummyElement = document.createElement("div"); - dummyElement.innerHTML = ''; - var htmlAnchorElement = dummyElement.querySelector("a"); - Show.oDoLiveSpeed.el.textContent = ost; - var circleSVG = document.getElementById("oDoLiveSpeed"); - htmlAnchorElement.innerHTML = circleSVG.innerHTML; - circleSVG.innerHTML = dummyElement.innerHTML; - if (location.hostname != myname.toLowerCase() + com) { - saveTestData = "https://" + myname.toLowerCase() + com + "/results/show.php?" + "&d=" + downloadSpeed.toFixed(3) + "&u=" + uploadSpeed.toFixed(3) + "&p=" + pingEstimate + "&j=" + jitterEstimate + "&dd=" + (dataUsedfordl / 1048576).toFixed(3) + "&ud=" + (dataUsedforul / 1048576).toFixed(3) + "&ua=" + userAgentString; - saveTestData = encodeURI(saveTestData); - var circleSVG2 = document.getElementById("resultsData"); - circleSVG2.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", saveTestData); - circleSVG2.setAttribute("target", "_blank"); - if (saveData) { - ServerConnect(5); - } - } else { - ServerConnect(3); - } - Status = "busy"; - clearInterval(Engine); - } - }, 100); - } - function downReq() { - for (var i = 0; i < dlThreads; i++) { - setTimeout(function(i) { - SendReQ(i); - }, dlDelay * i, i); - } - } - function upReq() { - for (var i = 0; i < ulThreads; i++) { - setTimeout(function(i) { - SendUpReq(i); - }, ulDelay * i, i); - } - } - var dLoad = 0; - var dDiff = 0; - var dTotal = 0; - var dtLoad = 0; - var dtDiff = 0; - var dtTotal = 0; - var dRest = 0; - var dReset; - var uReset; - var uLoad = 0; - var uDiff = 0; - var uTotal = 0; - var utLoad = 0; - var utDiff = 0; - var utTotal = 0; - var uRest = 0; - var dualReset; - var neXT = dlDuration * 1000 - 6000; - var dualupReset; - var neXTUp = ulDuration * 1000 - 6000; - function reportCurrentSpeed(now) { - if (now === "dl") { - var dTime = downloadTimeing * 1000; - if (dTime > dReset * 1000 + dlFinal / 2 * 1000 && dRest === 0) { - dRest = 1; - dtTotal = dtTotal * 0.01; - dTotal = dTotal * 0.01; - dualReset = dTime + 10000; - } - if (dTime >= dualReset && dualReset < neXT) { - dualReset += 10000; - dtTotal = dtTotal * 0.01; - dTotal = dTotal * 0.01; - } - dLoad = dLoaded <= 0 ? 0 : dLoaded - dDiff; - dDiff = dLoaded; - dTotal += dLoad; - dtLoad = dtDiff = 0 ? 0 : dTime - dtDiff; - dtDiff = dTime; - dtTotal += dtLoad; - if (dTotal > 0) { - LiveSpeedArr = dTotal / dtTotal / 125 * upAdjust; - currentSpeed = LiveSpeedArr; - } - } - if (now === "up") { - var Tym = uploadTimeing * 1000; - if (Tym > uReset * 1000 + ulFinal / 2 * 1000 && uRest === 0) { - uRest = 1; - utTotal = utTotal * 0.1; - uTotal = uTotal * 0.1; - dualupReset = Tym + 10000; - } - if (Tym >= dualupReset && dualupReset < neXTUp) { - dualupReset += 10000; - utTotal = utTotal * 0.1; - uTotal = uTotal * 0.1; - } - uLoad = uLoaded <= 0 ? 0 : uLoaded - uDiff; - uDiff = uLoaded; - uTotal += uLoad; - utLoad = utDiff = 0 ? 0 : Tym - utDiff; - utDiff = Tym; - utTotal += utLoad; - if (uTotal > 0) { - LiveSpeedArr = uTotal / utTotal / 125 * upAdjust; - currentSpeed = LiveSpeedArr; - } - } - } - function SendReQ(i) { - var lastLoaded = 0; - var OST = new XMLHttpRequest(); - ReQ[i] = OST; - ReQ[i].open("GET", fianlPingServer.Download + "?n=" + Math.random(), true); - ReQ[i].onprogress = function(e) { - if (stop === 1) { - ReQ[i].abort(); - ReQ[i] = null; - ReQ[i] = undefined; - delete ReQ[i]; - return false; - } - if (Status == "initDown") { - Status = "Downloading"; - } - var eLoaded = e.loaded <= 0 ? 0 : e.loaded - lastLoaded; - if (isNaN(eLoaded) || !isFinite(eLoaded) || eLoaded < 0) { - return false; - } - dLoaded += eLoaded; - lastLoaded = e.loaded; - }; - ReQ[i].onload = function(e) { - if (lastLoaded === 0) { - dLoaded += e.total; - } - if (Status == "initDown") { - Status = "Downloading"; - } - if (ReQ[i]) { - ReQ[i].abort(); - ReQ[i] = null; - ReQ[i] = undefined; - delete ReQ[i]; - } - if (stop === 0) { - SendReQ(i); - } - }; - ReQ[i].onerror = function(e) { - if (stop === 0) { - SendReQ(i); - } - }; - ReQ[i].responseType = "arraybuffer"; - ReQ[i].send(); - } - var uReQ = []; - function SendUpReq(i) { - var lastULoaded = 0; - var OST = new XMLHttpRequest(); - uReQ[i] = OST; - uReQ[i].open("POST", fianlPingServer.Upload + "?n=" + Math.random(), true); - uReQ[i].upload.onprogress = function(e) { - if (Status == "initup" && some === undefined) { - var some; - Status = "Uploading"; - } - if (uploadTimeing >= ulDuration) { - uReQ[i].abort(); - uReQ[i] = null; - uReQ[i] = undefined; - delete uReQ[i]; - return false; - } - var eLoaded = e.loaded <= 0 ? 0 : e.loaded - lastULoaded; - if (isNaN(eLoaded) || !isFinite(eLoaded) || eLoaded < 0) { - return false; - } - uLoaded += eLoaded; - lastULoaded = e.loaded; - }; - uReQ[i].onload = function() { - if (lastULoaded === 0) { - uLoaded += ulDataSize * 1048576; - if (uploadTimeing >= ulDuration) { - uReQ[i].abort(); - uReQ[i] = null; - uReQ[i] = undefined; - delete uReQ[i]; - return false; - } - } - if (Status == "initup" && some === undefined) { - var some; - Status = "Uploading"; - } - if (uReQ[i]) { - uReQ[i].abort(); - uReQ[i] = null; - uReQ[i] = undefined; - delete uReQ[i]; - } - if (stop === 1) { - SendUpReq(i); - } - }; - uReQ[i].onerror = function(e) { - if (uploadTimeing <= ulDuration) { - SendUpReq(i); - } - }; - uReQ[i].setRequestHeader("Content-Type", "application/octet-stream"); - if (i > 0 && uLoaded <= 17000) { - } else { - uReQ[i].send(SendData); - } - } - function sendPing() { - readServerList(); - } - var fianlPingServer; - var statusPing; - var statusPingFinal; - var statusJitter; - var statusJitterFinal; - var statusPingTest; - var pingSendStatus = -1; - var finalPing = []; - var pingServer = []; - var finalJitter = []; - var pingSendLength = openSpeedTestServerList.length; - function readServerList() { - pingSendLength = openSpeedTestServerList.length; - Status = "Ping"; - performance.clearResourceTimings(); - if (pingSendStatus < pingSendLength - 1) { - pingSendStatus++; - if (statusPingTest != "Stop") { - sendPingRequest(openSpeedTestServerList[pingSendStatus], readServerList); - } - } else { - if (pingServer.length >= 1) { - var finalLeastPingResult = Math.min.apply(Math, finalPing); - var finalLeastPingResultIndex = finalPing.indexOf(finalLeastPingResult); - fianlPingServer = pingServer[finalLeastPingResultIndex]; - statusPingFinal = finalLeastPingResult; - statusJitterFinal = finalJitter[finalLeastPingResultIndex]; - statusPingTest = "Busy"; - Show.LiveSpeed(statusPingFinal, "Ping"); - Show.pingResults(statusPingFinal, "Ping"); - Show.jitterResult(statusJitterFinal, "Jitter"); - pingEstimate = statusPingFinal; - jitterEstimate = statusJitterFinal; - if (SelectTest) { - if (SelectTest == "Ping") { - Status = "SendR"; - } else { - Status = SelectTest; - } - } else { - Status = "Download"; - } - } else { - if (pingServer.Download) { - } else { - Status = "Error"; - } - } - } - } - function sendPingRequest(serverListElm, callback) { - var pingSamplesSend = 0; - var pingResult = []; - var jitterResult = []; - function sendNewPingReq() { - if (pingSamplesSend < pingSamples) { - pingSamplesSend++; - if (statusPingTest != "Stop") { - PingRequest(); - } - } else { - if (pingResult.length > 1) { - jitterResult.sort(function(a, b) { - return a - b; - }); - jitterResult = jitterResult.slice(0, jitterResult.length * jitterFinalSample); - jitterResult = jitterResult.reduce(function(acc, val) { - return acc + val; - }, 0) / jitterResult.length; - var leastJitter = jitterResult.toFixed(1); - var leastPing = Math.min.apply(Math, pingResult); - finalPing.push(leastPing); - pingServer.push(serverListElm); - finalJitter.push(leastJitter); - if (typeof callback === "function") { - callback(); - } - } else { - if (typeof callback === "function") { - callback(); - } - } - } - } - function PingRequest() { - var OST = new XMLHttpRequest(); - var ReQ = OST; - if (statusPingTest != "Stop") { - ReQ.abort(); - } - ReQ.open(pingMethod, serverListElm[pingFile] + "?n=" + Math.random(), true); - ReQ.timeout = pingTimeOut; - var startTime = window.performance.now(); - ReQ.send(); - ReQ.onload = function() { - if (this.status === 200 && this.readyState === 4) { - var endTime = Math.floor(window.performance.now() - startTime); - var perfNum = performance.getEntries(); - perfNum = perfNum[perfNum.length - 1]; - var perfPing; - if (perfNum.initiatorType === "xmlhttprequest") { - perfPing = parseFloat(perfNum.duration.toFixed(1)); - } else { - perfPing = endTime; - } - if (pingSamplesSend > 250) { - perfPing = endTime; - } - if (perfPing <= 0) { - statusPing = 0.1; - pingResult.push(0.1); - } else { - statusPing = perfPing; - pingResult.push(perfPing); - } - if (pingResult.length > 1) { - var jitterCalc = Math.abs(pingResult[pingResult.length - 1] - pingResult[pingResult.length - 2]).toFixed(1); - jitterResult.push(parseFloat(jitterCalc)); - statusJitter = jitterCalc; - Show.LiveSpeed(perfPing, "Ping"); - Show.pingResults(perfPing, "Ping"); - Show.jitterResult(jitterCalc, "Jitter"); - } - sendNewPingReq(); - } - if (this.status === 404 && this.readyState === 4) { - pingSamplesSend++; - sendNewPingReq(); - } - }; - ReQ.onerror = function(e) { - pingSamplesSend++; - sendNewPingReq(); - }; - ReQ.ontimeout = function(e) { - pingSamplesSend++; - sendNewPingReq(); - }; - } - PingRequest(); - } - var ServerConnect = function(auth) { - var Self = this; - var xhr = new XMLHttpRequest(); - var url = OpenSpeedTestdb; - if (auth == 1) { - url = webIP; - } - if (auth == 5) { - url = saveDataURL; - } - if (auth == 7) { - url = get_IP; - } - xhr.open("POST", url, true); - xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - xhr.onreadystatechange = function() { - if (xhr.readyState == 4 && xhr.status == 200) { - return_data = xhr.responseText.trim(); - if (auth == 2) { - key = return_data; - } - if (auth == 1) { - TestServerip = return_data; - } - if (auth == 3) { - setTimeout(function() { - location.href = showResult + return_data; - }, 1500); - } - if (auth == 6) { - openSpeedTestServerList = JSON.parse(return_data); - launch = true; - runTasks(); - } - if (auth == 7) { - Show.YourIP.el.textContent = return_data; - } - } - }; - if (auth == 2) { - logData = "r=n"; - } - if (auth == 3) { - logData = "r=l" + "&d=" + downloadSpeed + "&u=" + uploadSpeed + "&dd=" + dataUsedfordl / 1048576 + "&ud=" + dataUsedforul / 1048576 + "&p=" + pingEstimate + "&do=" + myhostName + "&S=" + key + "&sip=" + TestServerip + "&jit=" + jitterEstimate + "&ua=" + userAgentString; - } - if (auth == 5) { - logData = saveTestData; - } - if (auth == 6) { - logData = "r=s"; - } - xhr.send(logData); - }; - }; - OpenSpeedTest.Start = function() { - new openSpeedtestEngine(); - }; -})(window.OpenSpeedTest = window.OpenSpeedTest || {}); diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/js/app-2.5.4.min.js b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/js/app-2.5.4.min.js deleted file mode 100644 index 0df99a1f..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/js/app-2.5.4.min.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - Official Website : https://OpenSpeedTest.COM | Email: support@openspeedtest.com - Developed by : Vishnu | https://Vishnu.Pro | Email : me@vishnu.pro - Like this Project? Please Donate NOW & Keep us Alive -> https://go.openspeedtest.com/Donate - Speed Test by OpenSpeedTest™️ is Free and Open-Source Software (FOSS) with MIT License. - Read full license terms @ http://go.openspeedtest.com/License - If you have any Questions, ideas or Comments Please Send it via -> https://go.openspeedtest.com/SendMessage -*/ -window.onload=function(){var X=document.getElementById("OpenSpeedTest-UI");X.parentNode.replaceChild(X.contentDocument.documentElement,X);ostOnload();OpenSpeedTest.Start()}; -(function(X){function h(c){if(!(this instanceof h))return new h(c);this.el=document.getElementById(c)}var m,Ma,Na=function(c){c&&"function"===typeof c&&c()};h.prototype.fade=function(c,d,a){var f="in"===c,t=f?0:1,w=14/d,n=this;f&&(n.el.style.display="block",n.el.style.opacity=t);var q=window.setInterval(function(){t=f?t+w:t-w;n.el.style.opacity=t;0>=t&&(n.el.style.display="none");(0>=t||1<=t)&&window.clearInterval(q,Na(a))},14)};var Oa=function(c,d,a,f){c/=f;c--;return a*(c*c*c+1)+d},r=function(){this.YourIP= -h("YourIP");this.ipDesk=h("ipDesk");this.ipMob=h("ipMob");this.downSymbolDesk=h("downSymbolDesk");this.upSymbolDesk=h("upSymbolDesk");this.upSymbolMob=h("upSymbolMob");this.downSymbolMob=h("downSymbolMob");this.settingsMob=h("settingsMob");this.settingsDesk=h("settingsDesk");this.oDoLiveStatus=h("oDoLiveStatus");this.ConnectErrorMob=h("ConnectErrorMob");this.ConnectErrorDesk=h("ConnectErrorDesk");this.downResult=h("downResult");this.upRestxt=h("upRestxt");this.pingResult=h("pingResult");this.jitterDesk= -h("jitterDesk");this.pingMobres=h("pingMobres");this.JitterResultMon=h("JitterResultMon");this.JitterResultms=h("JitterResultms");this.UI_Desk=h("UI-Desk");this.UI_Mob=h("UI-Mob");this.oDoTopSpeed=h("oDoTopSpeed");this.startButtonMob=h("startButtonMob");this.startButtonDesk=h("startButtonDesk");this.intro_Desk=h("intro-Desk");this.intro_Mob=h("intro-Mob");this.loader=h("loading_app");this.OpenSpeedtest=h("OpenSpeedtest");this.mainGaugebg_Desk=h("mainGaugebg-Desk");this.mainGaugeBlue_Desk=h("mainGaugeBlue-Desk"); -this.mainGaugeWhite_Desk=h("mainGaugeWhite-Desk");this.mainGaugebg_Mob=h("mainGaugebg-Mob");this.mainGaugeBlue_Mob=h("mainGaugeBlue-Mob");this.mainGaugeWhite_Mob=h("mainGaugeWhite-Mob");this.oDoLiveSpeed=h("oDoLiveSpeed");this.progressStatus_Mob=h("progressStatus-Mob");this.progressStatus_Desk=h("progressStatus-Desk");this.graphc1=h("graphc1");this.graphc2=h("graphc2");this.graphMob2=h("graphMob2");this.graphMob1=h("graphMob1");this.text=h("text");this.scale=[{degree:680,value:0},{degree:570,value:.5}, -{degree:460,value:1},{degree:337,value:10},{degree:220,value:100},{degree:115,value:500},{degree:0,value:1E3}];this.polygon=this.chart=this.element="";this.width=200;this.height=50;this.maxValue=0;this.values=[];this.points=[];this.vSteps=5;this.measurements=[];this.points=[]};r.prototype.reset=function(){this.polygon=this.chart=this.element="";this.width=200;this.height=50;this.maxValue=0;this.values=[];this.points=[];this.vSteps=5;this.measurements=[];this.points=[]};r.prototype.ip=function(){"block"=== -this.ipDesk.el.style.display?(this.ipDesk.el.style.display="none",this.ipMob.el.style.display="none"):(this.ipDesk.el.style.display="block",this.ipMob.el.style.display="block")};r.prototype.prePing=function(){this.loader.fade("out",500);this.OpenSpeedtest.fade("in",1E3)};r.prototype.app=function(){this.loader.fade("out",500,this.ShowAppIntro())};r.prototype.ShowAppIntro=function(){this.OpenSpeedtest.fade("in",1E3)};r.prototype.userInterface=function(){this.intro_Desk.fade("out",1E3);this.intro_Mob.fade("out", -1E3,this.ShowUI())};r.prototype.ShowUI=function(){this.UI_Desk.fade("in",1E3);this.UI_Mob.fade("in",1E3,function(c){m="Loaded";console.log("Developed by Vishnu. Email --\x3e me@vishnu.pro")})};r.prototype.Symbol=function(c){0==c&&(this.downSymbolMob.el.style.display="block",this.downSymbolDesk.el.style.display="block",this.upSymbolMob.el.style.display="none",this.upSymbolDesk.el.style.display="none");1==c&&(this.downSymbolMob.el.style.display="none",this.downSymbolDesk.el.style.display="none",this.upSymbolMob.el.style.display= -"block",this.upSymbolDesk.el.style.display="block");2==c&&(this.downSymbolMob.el.style.display="none",this.downSymbolDesk.el.style.display="none",this.upSymbolMob.el.style.display="none",this.upSymbolDesk.el.style.display="none")};r.prototype.Graph=function(c,d){function a(q,M){for(n=f.maxValue=0;nf.maxValue&&(f.maxValue=f.values[n]);f.maxValue=Math.ceil(f.maxValue);if(1=d&&(clearInterval(t),Ma="done",a.progressStatus_Desk.el.style.strokeDashoffset=800,a.progressStatus_Mob.el.style.strokeDashoffset=800)},14)};r.prototype.mainGaugeProgress=function(c){var d=c;0>d&&(d=0);var a=this.getNonlinearDegree(d);0=d&&(this.mainGaugeBlue_Mob.el.style.strokeDashoffset=681.1,this.mainGaugeWhite_Mob.el.style.strokeDashoffset=.1,this.mainGaugeWhite_Desk.el.style.strokeDashoffset=.1,this.mainGaugeBlue_Desk.el.style.strokeDashoffset=681.1)};r.prototype.showStatus=function(c){this.oDoLiveStatus.el.textContent=c};r.prototype.ConnectionError=function(){this.ConnectErrorMob.el.style.display= -"block";this.ConnectErrorDesk.el.style.display="block"};r.prototype.uploadResult=function(c){1>c&&(this.upRestxt.el.textContent=c.toFixed(3));1<=c&&9999>c&&(this.upRestxt.el.textContent=c.toFixed(1));1E4<=c&&99999>c&&(this.upRestxt.el.textContent=c.toFixed(1),this.upRestxt.el.style.fontSize="20px");1E5<=c&&(this.upRestxt.el.textContent=c.toFixed(1),this.upRestxt.el.style.fontSize="18px")};r.prototype.pingResults=function(c,d){var a=c;"Ping"===d&&(1<=a&&1E4>a?(this.pingResult.el.textContent=Math.floor(a), -this.pingMobres.el.textContent=Math.floor(a)):0<=a&&1>a&&(0==a&&(a=0),this.pingResult.el.textContent=a,this.pingMobres.el.textContent=a));"Error"===d&&(this.oDoLiveSpeed.el.textContent=a)};r.prototype.downloadResult=function(c){1>c&&(this.downResult.el.textContent=c.toFixed(3));1<=c&&9999>c&&(this.downResult.el.textContent=c.toFixed(1));1E4<=c&&99999>c&&(this.downResult.el.textContent=c.toFixed(1),this.downResult.el.style.fontSize="20px");1E5<=c&&(this.downResult.el.textContent=c.toFixed(1),this.downResult.el.style.fontSize= -"18px")};r.prototype.jitterResult=function(c,d){var a=c;"Jitter"===d&&(1<=a&&1E4>a?(this.jitterDesk.el.textContent=Math.floor(a),1<=a&&100>a&&(this.JitterResultMon.el.textContent=Math.floor(a)),100<=a&&(a=(a/1E3).toFixed(1),this.JitterResultMon.el.textContent=a+"k")):0<=a&&1>a&&(0==a&&(a=0),this.jitterDesk.el.textContent=a,this.JitterResultMon.el.textContent=a))};r.prototype.LiveSpeed=function(c,d){var a=c;if("countDown"===d){var f=a.toFixed(0);this.oDoLiveSpeed.el.textContent=f}else"speedToZero"=== -d?("number"==typeof a&&(a=a.toFixed(1)),0>=a&&(a=0),this.oDoLiveSpeed.el.textContent=a,this.oDoTopSpeed.el.textContent="1000+",this.oDoTopSpeed.el.style.fontSize="16.9px",this.oDoTopSpeed.el.style.fill="gray"):"Ping"===d?1<=a&&1E4>a?this.oDoLiveSpeed.el.textContent=Math.floor(a):0<=a&&1>a&&(0==a&&(a=0),this.oDoLiveSpeed.el.textContent=a):(0==a&&(f=a.toFixed(0),this.oDoLiveSpeed.el.textContent=f),1>=a&&0=a&&(this.oDoTopSpeed.el.textContent="1000+",this.oDoTopSpeed.el.style.fontSize="16.9px",this.oDoTopSpeed.el.style.fill="gray"),1010<=a&&(this.oDoTopSpeed.el.textContent=1E3*Math.floor(a/1010)+"+",this.oDoTopSpeed.el.style.fill="gray",this.oDoTopSpeed.el.style.fontSize="17.2px"))};r.prototype.GaugeProgresstoZero=function(c,d){var a=this;if(0<=c)var f=Date.now(),t=0-c,w=setInterval(function(){var n=(Date.now()-f)/1E3;var q=n/3;q--;q=t*(q*q*q*q*q+1)+c;a.LiveSpeed(q,"speedToZero");a.mainGaugeProgress(q); -if(3<=n||0>=q)clearInterval(w),a.LiveSpeed(0,"speedToZero"),a.mainGaugeProgress(0),m=d},16)};r.prototype.getNonlinearDegree=function(c){var d=0;if(0==c||0>=c||isNaN(c))return 0;for(;dthis.scale[d].value)d++;else return this.scale[d-1].degree+(c-this.scale[d-1].value)*(this.scale[d].degree-this.scale[d-1].degree)/(this.scale[d].value-this.scale[d-1].value);return this.scale[this.scale.length-1].degree};var O=function(){this.OverAllTimeAvg=window.performance.now();this.SpeedSamples= -[];this.FinalSpeed};O.prototype.reset=function(){this.OverAllTimeAvg=window.performance.now();this.SpeedSamples=[];this.FinalSpeed=0};O.prototype.ArraySum=function(c){return c?c.reduce(function(d,a){if("number"===typeof d&&"number"===typeof a)return d+a},0):0};O.prototype.AvgSpeed=function(c,d,a){this.timeNow=(window.performance.now()-this.OverAllTimeAvg)/1E3;this.FinalSpeed;this.timeNow>=a-d&&(0=l&&(clearInterval(g),G=!0,Z=void 0,d())}Pa&&(x.remEvt(e.settingsMob.el,"click",c),x.remEvt(e.settingsDesk.el,"click",c),x.remEvt(e.startButtonDesk.el,"click",d),x.remEvt(e.startButtonMob.el,"click",d),x.remEvt(document,"keypress",a),Pa=!1);if(0<=Z){G=!1;e.userInterface();ua=!1;var l=Math.ceil(Math.abs(Z));e.showStatus("Automatic Test Starts in ...");var g=setInterval(b,1E3)}"fetch"===openSpeedTestServerList&&!0===G&&(G=!1,e.showStatus("Fetching Server Info.."),Y(6));!0===G&&("Ping"=== -A?f():"Download"===A?f():"Upload"===A?f():!1===A&&f())}function a(b){"Enter"===b.key&&d()}function f(){ua&&(e.userInterface(),ua=!1);t()}function t(){function b(){va=window.performance.now();n()}"widget"!==openChannel&&"web"!==openChannel||Y(1);var l=setInterval(function(){"Loaded"===m&&(m="busy",Q());"Ping"===m&&(m="busy",e.showStatus("Milliseconds"));"Download"===m&&(e.showStatus("Initializing.."),x.reset(),y=ha=0,e.reset(),wa=window.performance.now(),w(),m="initDown");if("Downloading"===m){e.Symbol(0); -if(0==aa){aa=1;e.showStatus("Testing download speed..");var g=(window.performance.now()-wa)/1E3;Qa=g;e.progress(1,dlDuration+2.5);dlDuration+=g}xa=(window.performance.now()-wa)/1E3;q("dl");e.showStatus("Mbps download");e.mainGaugeProgress(y);e.LiveSpeed(y);e.Graph(y,0);R=x.AvgSpeed(y,ya,dlDuration);xa>=dlDuration&&"done"==Ma&&(A?(e.GaugeProgresstoZero(y,"SendR"),e.showStatus("All done"),e.Symbol(2)):e.GaugeProgresstoZero(y,"Upload"),e.downloadResult(R),ia=S,I=1,m="busy",y=ha=0,x.reset())}"Upload"== -m&&1===I&&(e.Symbol(1),m="initup",e.showStatus("Initializing.."),e.LiveSpeed("...","speedToZero"),za=x.uRandom(ulDataSize,b),A&&(aa=1));"Uploading"===m&&(1==aa&&(aa=2,e.showStatus("Testing upload speed.."),y=0,x.reset(),e.reset(),Ra=g=(window.performance.now()-va)/1E3,e.progress(!1,ulDuration+2.5),ulDuration+=g),e.showStatus("Mbps upload"),T=(window.performance.now()-va)/1E3,q("up"),e.mainGaugeProgress(y),e.LiveSpeed(y),e.Graph(y,1),U=x.AvgSpeed(y,Aa,ulDuration),T>=ulDuration&&1==I&&(ja=P,e.uploadResult(U), -e.GaugeProgresstoZero(y,"SendR"),za=void 0,e.showStatus("All done"),e.Symbol(2),m="busy",I=0));if("Error"===m){e.showStatus("Check your network connection status.");e.ConnectionError();m="busy";clearInterval(l);g=document.createElement("div");g.innerHTML='';var u=g.querySelector("a");e.oDoLiveSpeed.el.textContent="Network Error";var C=document.getElementById("oDoLiveSpeed");u.innerHTML=C.innerHTML; -C.innerHTML=g.innerHTML}"SendR"===m&&(e.showStatus("All done"),g=document.createElement("div"),g.innerHTML='',u=g.querySelector("a"),e.oDoLiveSpeed.el.textContent=fb,C=document.getElementById("oDoLiveSpeed"),u.innerHTML=C.innerHTML,C.innerHTML=g.innerHTML,location.hostname!=Ba.toLowerCase()+Sa?(ba="https://"+Ba.toLowerCase()+Sa+"/results/show.php?&d="+R.toFixed(3)+"&u="+U.toFixed(3)+"&p="+ -Ca+"&j="+Da+"&dd="+(ia/1048576).toFixed(3)+"&ud="+(ja/1048576).toFixed(3)+"&ua="+Ta,ba=encodeURI(ba),g=document.getElementById("resultsData"),g.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",ba),g.setAttribute("target","_blank"),saveData&&Y(5)):Y(3),m="busy",clearInterval(l))},100)}function w(){for(var b=0;b1E3*Qa+ya/2*1E3&&0===Ua&&(Ua=1,ka*=.01,ca*=.01,la=l+1E4);l>=la&&la=S?0:S-Wa;Wa=S;ca+=Va;Xa=Ea=l-Ea;Ea=l;ka+=Xa;01E3*Ra+Aa/2*1E3&&0===Ya&&(Ya=1,ma*=.1,da*=.1,na=b+1E4),b>=na&&na=P?0:P-$a,$a=P,da+=Za,ab=Fa=b-Fa,Fa=b,ma+=ab,0=g.loaded?0:g.loaded-l;if(isNaN(u)||!isFinite(u)||0>u)return!1;S+=u;l=g.loaded};z[b].onload=function(g){0===l&&(S+=g.total);"initDown"==m&&(m="Downloading");z[b]&&(z[b].abort(),z[b]=null,z[b]=void 0,delete z[b]);0===I&&M(b)};z[b].onerror=function(g){0===I&&M(b)};z[b].responseType="arraybuffer";z[b].send()}function F(b){var l=0;v[b]=new XMLHttpRequest;v[b].open("POST",Ga.Upload+"?n="+Math.random(),!0); -v[b].upload.onprogress=function(g){"initup"==m&&void 0===u&&(m="Uploading");if(T>=ulDuration)return v[b].abort(),v[b]=null,v[b]=void 0,delete v[b],!1;var u=0>=g.loaded?0:g.loaded-l;if(isNaN(u)||!isFinite(u)||0>u)return!1;P+=u;l=g.loaded};v[b].onload=function(){if(0===l&&(P+=1048576*ulDataSize,T>=ulDuration))return v[b].abort(),v[b]=null,v[b]=void 0,delete v[b],!1;if("initup"==m&&void 0===g){var g;m="Uploading"}v[b]&&(v[b].abort(),v[b]=null,v[b]=void 0,delete v[b]);1===I&&F(b)};v[b].onerror=function(g){T<= -ulDuration&&F(b)};v[b].setRequestHeader("Content-Type","application/octet-stream");0=P||v[b].send(za)}function Q(){bb=openSpeedTestServerList.length;m="Ping";performance.clearResourceTimings();if(Ha=B?J.push(.1):J.push(B);1=ra&&(ulThreads=dlThreads=ra)}if(selectServer&&("string"===typeof k.host||"string"===typeof k.h)){if("undefined"!==typeof k.host)var sa=k.host;else"undefined"!==typeof k.h&&(sa=k.h);/(?:https?):\/\/(\w+:?\w*)?(\S+)(:\d+)?(\/|\/([\w#!:.?+=&%!\-\/]))?/.test(sa)&& -(openSpeedTestServerList=[{ServerName:"Home",Download:sa+"/downloading",Upload:sa+"/upload",ServerIcon:"DefaultIcon"}])}E=parseInt(k.stress);W=parseInt(k.s);if("string"===typeof k.stress){var p=k.stress;var H=E}else"string"===typeof k.s&&(p=k.s,H=W);if(p&&stressTest){if("low"===p||"l"===p)ulDuration=dlDuration=300;if("medium"===p||"m"===p)ulDuration=dlDuration=600;if("high"===p||"h"===p)ulDuration=dlDuration=900;if("veryhigh"===p||"v"===p)ulDuration=dlDuration=1800;if("extreme"===p||"e"===p)ulDuration= -dlDuration=3600;if("day"===p||"d"===p)ulDuration=dlDuration=86400;if("year"===p||"y"===p)ulDuration=dlDuration=31557600;if(12p||5>H)upAdjust=1+E/100,dlAdjust=1+E/100}else dlAdjust=upAdjust=1;p=parseInt(k.run);H=parseInt(k.r);var Z;!enableRun||"string"!==typeof k.run&&"string"!==typeof k.r||(Z=0',createCookie("mode","dark")));"light"===a&&(nightModeMob.style.display="none",dayModeMob.style.display="inline-block",nightMode.style.display="none", -dayMode.style.display="inline-block",(darkStyle=document.getElementById("darkmode"))&&darkStyle.parentNode.removeChild(darkStyle),createCookie("mode","light"))}function toggleSkin(){(darkStyle=document.getElementById("darkmode"))?setSkin("light"):setSkin("dark")}function createCookie(a,c,b){if(b){var d=new Date;d.setTime(d.getTime()+864E5*b);b="; expires="+d.toGMTString()}else b="";document.cookie=a+"="+c+b+"; path=/"} -function getCookieValue(a,c){return(c=document.cookie.match("(^|;)\\s*"+a+"\\s*=\\s*([^;]+)"))?c.pop():""}; \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/downloading b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/downloading deleted file mode 100644 index b78c32fa..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/downloading and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/hosted.html b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/hosted.html deleted file mode 100644 index ea51ac3b..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/hosted.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - Speedtest by OpenSpeedtest.com - - - - -
- Speed Test by OpenSpeedTest™ -
- - \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/index.html b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/index.html deleted file mode 100644 index 2772b6b6..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/index.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - SpeedTest by OpenSpeedTest™ - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
- - -
- SpeedTest by OpenSpeedTest™ is a Free and Open-Source HTML5 Network Speed Test Software. -

© Copyright 2013-2024 OpenSpeedTest™ All Rights Reserved.

-
- - - - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/upload b/data/agent/var/lib/openflare/pages/deployments/1/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/upload deleted file mode 100644 index e69de29b..00000000 diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/.gitignore b/data/agent/var/lib/openflare/pages/deployments/3/current/.gitignore deleted file mode 100644 index e43b0f98..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/current/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.DS_Store diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/.openflare-pages.json b/data/agent/var/lib/openflare/pages/deployments/3/current/.openflare-pages.json deleted file mode 100644 index 8b73ac2e..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/current/.openflare-pages.json +++ /dev/null @@ -1 +0,0 @@ -{"deployment_id":3,"checksum":"4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6"} \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/License.md b/data/agent/var/lib/openflare/pages/deployments/3/current/License.md deleted file mode 100644 index b93a9207..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/current/License.md +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 OpenSpeedTest™ - -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 permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/README.md b/data/agent/var/lib/openflare/pages/deployments/3/current/README.md deleted file mode 100644 index 17d89eeb..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/current/README.md +++ /dev/null @@ -1,506 +0,0 @@ -# **[SpeedTest by OpenSpeedTest™](https://openspeedtest.com?Run&ref=Github)** - Free & Open-Source HTML5 Network Performance Estimation Tool. - - - -SpeedTest by OpenSpeedTest™ is a Free and Open-Source HTML5 Network Performance Estimation Tool Written in Vanilla Javascript and only uses built-in Web APIs like `XMLHttpRequest` `(XHR)`, `HTML`, `CSS`, `JS`, & `SVG`. No Third-Party frameworks or libraries are Required. All we need is a static web server like `NGINX`. I started this project in 2011 and moved to OpenSpeedTest.com dedicated Project/Domain Name in 2013. - ---- -Just launched: OpenPacketLoss. Check it out! - -While OpenSpeedTest measures bandwidth, this tool helps you detect packet loss directly from your browser. - -**Try it:** https://openpacketloss.com -**Source:** https://github.com/openpacketloss - ---- - - -[![Download OpenSpeedTest-Server V2.1](https://github.com/openspeedtest/v2-Test/raw/main/images/10G-S.gif)](https://go.openspeedtest.com/Server "Download OpenSpeedTest-Server V2.1") - Download from the Microsoft Store Download from the Mac App Store Download from the App Store Download from the Google Play Store Download from the Snap Store Download from the Docker Hub Download from the Helm Store Download from GitHub - -**No client-side software or plugin is required. You can run a network speed test from any device with a [Web Browser that is IE10 or newer.](https://www.youtube.com/watch?v=9f-OM_WQ7Bw&list=PLt-deStxFJOMEAs2O1lJhscMNzcg9E3Po&index=1)** - - Video Tutorial by Roberto Jorge Tech - Video Tutorial by LanPad - -## Why OpenSpeedTest - - - -### Secure by Design. - - - -OpenSpeedTest contains Only `STATIC` Files like `HTML`,`CSS` & `JS`. - -So you don't need to worry about Security Updates or Hidden Exploits that may compromise your secure environments. - - - -### Lightweight, High Performance. - - - -OpenSpeedTest is written in Vanilla JavaScript. No Third-Party frameworks or libraries were used. SpeedTest script file size is under 8kB gzip. The unexpected side effect of using Vanilla JavaScript is High Performance. - - - -### Run a speed test from Any Device. - - - -OpenSpeedTest will run on Any Web Browser that is IE10 or newer. - - - -### Ready for Any Display Size and Resolution. - - - -OpenSpeedTest User interface is written in SVG. - - - -# Create Your Own SpeedTest Server. - -### Server Requirements : - -`Nginx`, `Apache`, `IIS`, `Express`, or Any Web server that supports `HTTP/1.1` or newer. - -- Accept, `GET`, `POST`, `HEAD` & `OPTIONS`, Response `200 OK`. - -- Accept, `POST` to Static Files, Response `200 OK`. - -- `client_max_body_size`, 35 Megabytes or more. - -- Timeout greater than `60 seconds`. - -- Disable `Access logs` for Increasing server performance. - -- Improve `Time to First Byte` (TTFB) - -- Warning! If you run it behind a **[Reverse Proxy](https://github.com/openspeedtest/Speed-Test/issues/4#issuecomment-1229157193)**, you should increase the `post-body content length` to 35 megabytes. -- Supports `HTTP2` & `HTTP3`. -- `HTTP1.1` is recommended for achieving maximum performance. -- **[You Should Follow our Nginx Config.](https://github.com/openspeedtest/Nginx-Configuration)** - - - -# Or, You can use OpenSpeedTest-Server. - -OpenSpeedTest-Server is available for Windows, Mac, Linux, Android, iOS & Docker. -[![Download OpenSpeedTest-Server V2.1](https://open.cachefly.net/assets/images/OSTV2-SS.png)](https://go.openspeedtest.com/Server "Download OpenSpeedTest-Server V2.1") -#### Fully Optimized and ready to use applications. - Download from the Microsoft Store Download from the Mac App Store Download from the App Store Download from the Google Play Store Download from the Snap Store Download from the Docker Hub Download from the Helm Store Download from GitHub - - -### New features: - - - -1. Stress Test. (Continuous Speed Test) - - - -To enable the stress test. Pass `Stress` or `S` keyword as a URL parameter. - - - -```` - -http://192.168.1.5?Stress=Low - -```` - -After the `STRESS` or `S` keyword, you can specify the number of seconds you need to run the StressTest in seconds, or preset values such as `Low`, `Medium`, `High`, `VeryHigh`, `Extreme`, `Day`, and `Year`. Will run a speed test for `300`,`600`,`900`,`1800`,`3600`,`86400`,`31557600` seconds, respectively. Also, you can feed the first letter of each parameter and its values. - - -```` - -http://192.168.1.5?S=L - -```` - -`S=L` is the same as passing `Stress=low` - -Or you can specify the number of seconds eg:`5000` directly without any preset keywords. - - - -```` - -http://192.168.1.5?Stress=5000 - -```` - - - -2. Run a speed test automatically - - - -Run a speed test automatically on page load. - -```` - -http://192.168.1.5?Run - -```` - -Run a speed test automatically after a few seconds. - -```` - -http://192.168.1.5?Run=10 or http://192.168.1.5?R=10 - -```` - - - -You can pass multiple keywords, and it's not `Case-Sensitive`. - - - -```` - -http://192.168.1.5?Run&Stress=300 OR http://192.168.1.5?R&S=300 - -```` - -This will start a speed test immediately and run for `300 seconds` in each direction. That is 300 seconds for download and `300 seconds` for upload. - - - -3. Save results to a Database - - - -Edit `Index.html` - -```` - -var saveData = true; - -var saveDataURL = "//yourDatabase.Server.com:4500/save?data="; - -```` - -4. Add multiple servers. The app will choose one with the least latency automatically. - - - -Edit `Index.html` - -```` - -var openSpeedTestServerList = [ - -{"ServerName":"Home-Earth", "Download":"/downloading", "Upload":"/upload", "ServerIcon":"DefaultIcon"}, - -{"ServerName":"Home-Mars", "Download":"/downloading", "Upload":"/upload", "ServerIcon":"DefaultIcon"}, - -{"ServerName":"Home-Moon", "Download":"/downloading", "Upload":"/upload", "ServerIcon":"DefaultIcon"} - -]; - -```` - -5. Disable or change Overhead Compensation factor. - - - -```` - -http://192.168.1.5?clean - -```` - -Overhead Compensation factor, This is browser based test, Many Unknowns. Currently 4%. That is within the margin of error. - -You can pass `Clean` or `C` as a URL Parameter and reset Overhead Compensation factor to Zero or set any value between 0 and 4. 1 = 1% to 4 = 4%. - -`Clean` will not accept values above 4, so Compensation is limited to maximum 4%. - - - -6. Change the default limit of 6 parallel HTTP connections to the Server. - - - -```` - -http://192.168.1.5?XHR=3 OR http://192.168.1.5?X=3 - -```` - -Allow the user to Change the default limit of 6 parallel HTTP connections to the Server. `XHR` will Accept values above 1 and max 32 - -pass `XHR` or `X` as a URL Parameter. - - - -7. Select a different server to run a speed test. - - - -```` - -http://192.168.1.5?Host=http://192.168.55.1:90 OR http://192.168.1.5?h=http://192.168.55.1:90 - -```` - -Pass `Host` or `H` as a URL Parameter. - -`HOST` will Accept only valid HTTP URLs like `http://192.168.1.10:3000` or `https://yourHost.com`. - - - -8. Select and run one test at a time, `DOWNLOAD`, `UPLOAD`, or `PING`. - - - -```` - -http://192.168.1.5?Test=Upload OR http://192.168.1.5?T=U - -```` - -`TEST` Allow the user to select and run one test at a time, Download, Upload, or Ping. - -Pass `Test` or `T` as a URL Parameter. - - - -9. Set a PingTimeout dynamically by passing `Out` or `O` as a URL Parameter - - - -```` - -http://192.168.1.5?Out=7000 OR http://192.168.1.5?O=7000 - -```` - -If Server not responded within 5 Seconds for any requests we send ('pingSamples' times) - -We will show `Network Error`, You can change the limit here. - -In milliseconds, if you need to set `6 seconds`. Change the value to `6000`. - - - -10. Set the Number of ping samples by adding `Ping` or `P` as a URL Parameter - - - -```` - -http://192.168.1.5?Ping=500 OR http://192.168.1.5?P=500 - -```` - -More samples = more accurate representation. `Ping = 500` will send `501` requests to server to find the accurate ping value. -Take a look at index.html, you can set a custom ping sample size, threads, upload data size etc. - -## Self-hosted (On-Premise) / (Docker Image/Source Code) -### For Headless large-scale deployments. -You have two options here. If you need a custom deployment, use our source code along with a web server of your choice. I prefer Nginx, and you can find my [Nginx Configuration](https://github.com/openspeedtest/Nginx-Configuration) here. Or you can use our docker image. You can deploy it on your LAN/WAN with or without an active internet connection. - -**This is docker implementation using nginxinc/nginx-unprivileged:stable-alpine. uses significantly fewer resources.** - -- NGINX Docker image that runs NGINX as a non root, unprivileged user. - - ### Docker install instructions: - - Install Docker and run the following command! - -````bash - -sudo docker run --restart=unless-stopped --name openspeedtest -d -p 3000:3000 -p 3001:3001 openspeedtest/latest - -```` -#### Or use docker-compose.yml -```` -version: '3.3' -services: - speedtest: - restart: unless-stopped - container_name: openspeedtest - ports: - - '3000:3000' - - '3001:3001' - image: openspeedtest/latest -```` -- Warning! If you run it behind a **[Reverse Proxy](https://github.com/openspeedtest/Speed-Test/issues/4#issuecomment-1229157193)**, you should increase the `post-body content length` to 35 megabytes. - -- **[Follow our Nginx Config.](https://github.com/openspeedtest/Nginx-Configuration)** - -Now open your browser and direct it to: - -A: For **HTTP** use: `http://YOUR-SERVER-IP:3000` - -B: For **HTTPS** use: `https://YOUR-SERVER-IP:3001` - -#### Container-Port for http is 3000 -If you need to run this image on a different port for `HTTP`, Eg: change to `80` = `-p 80:3000` -#### Container-Port for https is 3001 -If you need to run this image on a different port for `HTTPS`, Eg: change to `443` = `-p 443:3001` - -### Setup Free LetsEncrypt SSL with Automatic Certificate Renewal -***Requirements*** -- PUBLIC IPV4 and/or IPV6 address. -- A domain name that resolves to speed test server's IP address. -- Email ID - -The following command will generate a Let's Encrypt certificate for your domain name and configure a cron job to automatically renew the certificate. - -```` -docker run -e ENABLE_LETSENCRYPT=True -e DOMAIN_NAME=speedtest.yourdomain.com -e USER_EMAIL=you@yourdomain.pro --restart=unless-stopped --name openspeedtest -d -p 80:3000 -p 443:3001 openspeedtest/latest -```` -#### Or use docker-compose.yml -```` -version: '3.3' -services: - speedtest: - environment: - - ENABLE_LETSENCRYPT=True - - DOMAIN_NAME=speedtest.yourdomain.com - - USER_EMAIL=you@yourdomain.pro - restart: unless-stopped - container_name: openspeedtest - ports: - - '80:3000' - - '443:3001' - image: openspeedtest/latest -```` - -### How to Use Your Own Secure Sockets Layer (SSL) Certificate, Self-Signed or Paid? -***Requirements*** -- Folder with your Certificate, Self-Signed or Paid. -- Rename .cet file and .key file to `nginx.crt` & `nginx.key` - - The folder needs to contain: - -- `nginx.crt` - -- `nginx.key` - - -```` -sudo docker run --restart=unless-stopped --name openspeedtest -d -p 3000:3000 -p 3001:3001 openspeedtest/latest -```` - -To mount a folder with your own SSL certificate to this Docker container, append the following line to the above command: - - -````bash - --v /${PATH-TO-YOUR-OWN-SSL-CERTIFICATE}:/etc/ssl/ - -```` - -I am adding a folder with nginx.crt and nginx.key from my desktop by using the following command. - -````bash - -sudo docker run -v /Users/vishnu/Desktop/docker/:/etc/ssl/ --restart=unless-stopped --name openspeedtest -d -p 3000:3000 -p 3001:3001 openspeedtest/latest - -```` -#### Or use docker-compose.yml -```` -version: '3.3' -services: - speedtest: - volumes: - - '/Users/vishnu/Desktop/docker/:/etc/ssl/' - restart: unless-stopped - container_name: openspeedtest - ports: - - '3000:3000' - - '3001:3001' - image: openspeedtest/latest -```` -## Advanced Configuration Options - -- Container Port Configuration - -To enable port changes, set the `CHANGE_CONTAINER_PORTS` environment variable to `"True"` and provide appropriate values for the following variables. - -`CHANGE_CONTAINER_PORTS=True` - -`HTTP_PORT=3000` - -`HTTPS_PORT=3001` - -- Set User - -`SET_USER=101` - -- Only Allow `CORS Request` from listed domains. - -`ALLOW_ONLY=domain1.com;domain2.com;domain3.com` - -- `SET_SERVER_NAME` Display the server name on the UI. - -`SET_SERVER_NAME=HOME-NAS` - - -Docker images run better on Linux Platforms, including your NAS. But if you install docker on macOS or Windows, you may see poor performance. I asked this on Docker forums, and they told me macOS and Windows support is for Development purposes only. For Production, you need to use any Linux Platform. - -The same Story goes for Windows NGINX. Nginx uses only one worker even if you specify n number of worker processes. They will show in Task Manager, but the system will only use one. I got this information directly from the Nginx website. - - - - - -## Why do you need to Create Your Own SpeedTest Server? - -You can run OpenSpeedTest Server in your Home Lab, Office Server or Cloud Server. So that you or employees who work from home can run a speed test to your office and make sure they can run everything smoothly. - - - -**Choosing between ISP1 & ISP2.** - - - -Sometimes your ISP2 is Faster than ISP1 when you test your speed on popular speed test sites. But when you connect to your Home/Office/Cloud, that slower connection may perform better. The only way to find out is to run a speed test against your infrastructure. - - - -**Troubleshooting network issues.** - - - -It is common even when your Internet connection is working fine, but some of the devices in your network may experience trouble getting decent connectivity to the internet. The issue might be the wrong VLAN ID or Faulty Switch. If you run a Local network speed test, you can find and fix these issues easily. - - - -**Before you add a repeater.** - - - -Most repeaters will reduce your network speed by 50%, so if you put it far away, it will perform worst, and if you put it too close, you will not get enough coverage if you run a Local Network speed test. Depending on the application requirements, you can decide exactly where you need to put your repeater. - - - -**Browsing experience.** - - - -Many useful browser extensions are out there that we all know and love. But some of them are really slowing you down for **few seconds per page you visit**. You may see good performance when you test your network performance via File Transfer or Command-line utilities, but you may experience poor performance when browsing the internet. This is due to a bad browser configuration that including unwanted extensions installed. From my experience, only keep the one you are going to use every single day. Extension that you may use once in a while should be removed or disabled for maximum performance. If you see poor performance, try OpenSpeedTest from Private Window or Incognito Window. **This tool can be used to check the browser performance and impact of Extensions on your browsing experience.** - - - -**No client-side software or plugin is required. You can run a network speed test from any device with a [Web Browser that is IE10 or newer.](https://www.youtube.com/watch?v=9f-OM_WQ7Bw&list=PLt-deStxFJOMEAs2O1lJhscMNzcg9E3Po&index=1)** - -- Like this Project? Please **Donate NOW & Keep us Alive** -> https://go.openspeedtest.com/Donate - -MIT License - -Copyright (c) 2013 - 2023 OpenSpeedTest™ - -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 permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/css/app.css b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/css/app.css deleted file mode 100644 index 39fa9393..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/css/app.css +++ /dev/null @@ -1,382 +0,0 @@ -/* roboto-regular - latin */ -@font-face { - font-family: "Roboto"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("..fonts/roboto-v30-latin-regular.eot"); /* IE9 Compat Modes */ - src: local(""), - url("../fonts/roboto-v30-latin-regular.eot?#iefix") - format("embedded-opentype"), - /* IE6-IE8 */ url("../fonts/roboto-v30-latin-regular.woff2") - format("woff2"), - /* Super Modern Browsers */ - url("../fonts/roboto-v30-latin-regular.woff") format("woff"), - /* Modern Browsers */ url("../fonts/roboto-v30-latin-regular.ttf") - format("truetype"), - /* Safari, Android, iOS */ - url("../fonts/roboto-v30-latin-regular.svg#Roboto") format("svg"); /* Legacy iOS */ -} -/* roboto-500 - latin */ -@font-face { - font-family: "Roboto"; - font-style: normal; - font-weight: 500; - font-display: swap; - src: url("assets/fonts/roboto-v30-latin-500.eot"); /* IE9 Compat Modes */ - src: local(""), - url("assets/fonts/roboto-v30-latin-500.eot?#iefix") - format("embedded-opentype"), - /* IE6-IE8 */ url("../fonts/roboto-v30-latin-500.woff2") - format("woff2"), - /* Super Modern Browsers */ url("../fonts/roboto-v30-latin-500.woff") - format("woff"), - /* Modern Browsers */ url("../fonts/roboto-v30-latin-500.ttf") - format("truetype"), - /* Safari, Android, iOS */ - url("../fonts/roboto-v30-latin-500.svg#Roboto") format("svg"); /* Legacy iOS */ -} - -body { - margin: 0px; - padding: 0px; - display: block; -} - -::-webkit-scrollbar { - display: none; -} -html { - -ms-overflow-style: none; - scrollbar-width: none; -} - -.Credits { -color: rgb(125 119 119); -text-align: center; -font-size:14px; -font-family: Roboto-Medium, Roboto; -font-weight: 500; -} -.Credits a { - text-decoration: none; - color: rgb(113, 113, 113); -} -.Credits a:hover { -color: #14b0fe; -} - -.ConnectError { - display: none; - -} - -.openSpeedtestApp { - - height: 100vh; - width: 100vw; - display: none; - overflow: hidden; -} -.main-Gaugebg { - fill: none; - stroke: rgb(231, 231, 232); - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 22px; - stroke-dasharray: 681; -} -.main-GaugeBlue { - fill: none; - stroke: url(#gradient); - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 22px; - stroke-dasharray: 681; - stroke-opacity: 0; -} -.main-GaugeWhite { - fill: none; - stroke: rgb(255, 255, 255); - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 15px; - stroke-dasharray: 0, 681; - stroke-dashoffset: 1; - stroke-opacity: 0; -} -.oDo-Meter { - font-size: 16.633283615112305px; - fill: gray; - font-family: Roboto-Medium, Roboto; - font-weight: 500; -} -.oDoLive-Speed { - font-size: 28px; - fill: #201e1e; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} - -.oDoLive-Status { - font-size: 10px; - fill: #d2d1d2; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} -.uiBg { - fill: #d2d1d2; -} -.progressbg { - stroke: rgb(231, 231, 232); - stroke-width: 8px; - stroke-linecap: round; - stroke-linejoin: round; - stroke-dasharray: 400; - stroke-dashoffset: 0; -} -.Cards { - fill: #f2f2f2; -} -.Symbol { - fill: url(#gradient); -} -.rtext { - font-size: 12px; - fill: #333; - font-family: Roboto-Medium, Roboto; - font-weight: 500; -} -.rtextnum { - font-size: 23px; - fill: #201e1e; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} -.rtextmbms { - font-size: 12px; - fill: #5f5f5f; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} -.jitter-Mob { - font-size: 9px; - fill: #5f5f5f; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} -.startButton { - fill: url(#RadialGradient1); - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - -webkit-tap-highlight-color: transparent; - cursor: pointer; - pointer-events: visible; -} -.buttonTxt { - font-size: 40px; - fill: #ffffff; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} - -.intro-Progress { - stroke: #56c4fb; - stroke-width: 8px; - stroke-linecap: round; - stroke-linejoin: round; - stroke-dasharray: 400; - stroke-dashoffset: 0; -} -.progressElmstart { - stroke: #56c4fb; - stroke-width: 8px; - stroke-linecap: round; - stroke-linejoin: round; - stroke-dasharray: 400; - stroke-dashoffset: 0; - display: block; -} -.Startsettings { - fill: url(#RadialGradient1); - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - -webkit-tap-highlight-color: transparent; - cursor: pointer; - pointer-events: visible; - opacity: 0.1; - transition: opacity 1s ease-in-out; -} -.Startsettings:hover { - opacity: 1; -} - -.progressbg { - stroke: rgb(231, 231, 232); - stroke-width: 8px; - stroke-linecap: round; - stroke-linejoin: round; - stroke-dasharray: 400; - stroke-dashoffset: 0; -} -.deskStart { - fill: none; - stroke: rgb(231, 231, 232); - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 22px; - stroke-dasharray: 681; - stroke: url(#gradient); -} -#UI-Desk { - display: none; -} -#UI-Mob { - display: none; -} -.oDoTop-Speed { - font-size: 16.96px; - fill: gray; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: end; -} -#upSymbolDesk { - fill: #14b0fe; - display: none; -} -#downSymbolDesk { - fill: #14b0fe; - display: none; -} -#upSymbolMob { - fill: #14b0fe; - display: none; -} -#downSymbolMob { - fill: #14b0fe; - display: none; -} - -#ipMob { - font-size: 15px; - fill: #201e1e; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; - display: none; -} -#ipDesk { - font-size: 15px; - fill: #201e1e; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; - display: none; -} - -.spinner { - position: absolute; - z-index: 999; - top: 50vh; - left: 50vw; - text-align: center; -} - -.spinner > div { - width: 20px; - height: 20px; - background-color: #2196f3; - - border-radius: 100%; - display: inline-block; - -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both; - animation: sk-bouncedelay 1.4s infinite ease-in-out both; -} - -.spinner .bounce1 { - -webkit-animation-delay: -0.32s; - animation-delay: -0.32s; -} - -.spinner .bounce2 { - -webkit-animation-delay: -0.16s; - animation-delay: -0.16s; -} - -@-webkit-keyframes sk-bouncedelay { - 0%, - 80%, - 100% { - -webkit-transform: scale(0); - } - 40% { - -webkit-transform: scale(1); - } -} - -@keyframes sk-bouncedelay { - 0%, - 80%, - 100% { - -webkit-transform: scale(0); - transform: scale(0); - } - 40% { - -webkit-transform: scale(1); - transform: scale(1); - } -} - -.darkmode { - margin-bottom: -15px; - fill: #75757a99; - padding-top: 16px; - display: none; - cursor: pointer; - margin-right: 30px; -} -#daymode { - margin-right: 40px; -} -.darkmode:hover { - fill: #000000; -} - -.Mobile, -.Desktop { - visibility: hidden; - width: 100vw; - height: 100vh; -} - -@media only screen and (orientation: landscape) { - .Mobile { - visibility: hidden; - } - .Desktop { - visibility: visible; - } -} - -@media only screen and (orientation: portrait) { - .spinner { - top: 42vh; - left: 42vw; - } - .Mobile { - visibility: visible; - } - .Desktop { - visibility: hidden; - } -} -@media only screen and (max-width: 300px) { - .Credits{ - display: none; - } -} diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/css/darkmode.css b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/css/darkmode.css deleted file mode 100644 index 14fef300..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/css/darkmode.css +++ /dev/null @@ -1,45 +0,0 @@ -body { - background-color: #181818; -} -#ipDesk { - fill: aliceblue; -} - -.oDoLive-Speed { - fill: #ffffff; -} -.oDoLive-Status { - fill: aliceblue; -} -#ipMob { - fill: aliceblue; -} -.rtextnum { - fill: #ffffff; -} -.rtextmbms { - fill: #ffffff; -} -.rtext { - fill: #ffffff; -} -.Cards { - fill: #000000; -} - -.main-Gaugebg { - stroke: #000000; -} -.uiBg { - fill: #000000; -} -.progressbg { - stroke: #202020; -} -.jitter-Mob { - fill: #ffffff; -} - -.ConnectError { - fill: #ffffff; -} diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-500.eot b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-500.eot deleted file mode 100644 index fec03612..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-500.eot and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-500.svg b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-500.svg deleted file mode 100644 index 67eecf44..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-500.svg +++ /dev/null @@ -1,305 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-500.ttf b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-500.ttf deleted file mode 100644 index 4f515e2a..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-500.ttf and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-500.woff b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-500.woff deleted file mode 100644 index d39bb52a..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-500.woff and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-500.woff2 b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-500.woff2 deleted file mode 100644 index 29342a8d..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-500.woff2 and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-regular.eot b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-regular.eot deleted file mode 100644 index c244b089..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-regular.eot and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-regular.svg b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-regular.svg deleted file mode 100644 index 627f5a36..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-regular.svg +++ /dev/null @@ -1,308 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-regular.ttf b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-regular.ttf deleted file mode 100644 index d0e63254..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-regular.ttf and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-regular.woff b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-regular.woff deleted file mode 100644 index 9eaa94f9..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-regular.woff and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-regular.woff2 b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-regular.woff2 deleted file mode 100644 index 020729ef..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/fonts/roboto-v30-latin-regular.woff2 and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/app.svg b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/app.svg deleted file mode 100644 index 01441ac0..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/app.svg +++ /dev/null @@ -1,459 +0,0 @@ - - - - - - - - - - - - - 0 - .5 - 1 - 10 - 100 - 500 - 1000+ - ... - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DOWNLOAD - - - - - UPLOAD - - - - - - - - - - PING - - - - JITTER - - - --- - Mbps - - - --- - Mbps - - - -- - ms - - - -- - ms - - - - - - - - - -- - Jitter - -- - ms - - - - - - - - - - - - Start - - - - - - - - - - - - - - - - - - - Network Error - - - - SpeedTest by OpenSpeedTest™️ - - - - - - - - - - - - - - - - - - - - - HTML5 Network Performance Estimation Tool. by OpenSpeedTest™ - - - - - - The download speed is how fast you can pull data from the server to you in the form of images, videos, text and more. Activities such as listening to music on Spotify, downloading large files or streaming videos on Netflix all require you to download data. - - - The upload speed is how fast you send data from you to others in the form of images, videos, text and more. Activities such as uploading to YouTube or Sending email, playing live games like PubG, Voice and Video calling a friend require fast upload speeds for you to send data to someone else’s server. - - - Ping "Round-trip time" is more or less well defined as the network delay from point A to B and back. This is the sum of all encoding, queueing, processing, decoding, and propagation delays in both directions. Essentially, it's the delay when A may be expecting an answer from B for a request that requires very little processing. A fast ping means a more responsive connection, especially in applications where timing is everything (like video games). Ping is measured in Milliseconds (ms). Jitter is simply the difference in Ping "Round-trip time" . In other words, jitter is measuring time difference in packet inter-arrival time. Jitter helps diagnose internet connection stability. The higher the jitter value, the worse the stability of the connection. - - - - - - - - - Download speed is measured in megabits per second (Mbps). - - - Upload speed is measured in megabits per second (Mbps). - - - - - Ping is measured in Milliseconds (ms). - - - Jitter is measured in Milliseconds (ms). - - - - - Shows you real-time network traffic graph for Upload! - - - Shows you real-time network traffic graph for Download! - - - Your download speed! - - - Your upload speed! - - - Your ping! - - - Your jitter! - - - - - - - - - - Download speed is measured in megabits per second (Mbps). - - - Upload speed is measured in megabits per second (Mbps). - - - - - Ping is measured in Milliseconds (ms). - - - Jitter is measured in Milliseconds (ms). - - - - - Shows you real-time network traffic graph for Upload! - - - Shows you real-time network traffic graph for Download! - - - Your download speed! - - - Your upload speed! - - - Your ping! - - - Your jitter! - - - - - Click here to Run a Speed Test! Or Press "Enter" - - - Options - - - - - - HTML5 Network Performance Estimation Tool. by OpenSpeedTest™ - - - - - - The download speed is how fast you can pull data from the server to you in the form of images, videos, text and more. Activities such as listening to music on Spotify, downloading large files or streaming videos on Netflix all require you to download data. - - - The upload speed is how fast you send data from you to others in the form of images, videos, text and more. Activities such as uploading to YouTube or Sending email, playing live games like PubG, Voice and Video calling a friend require fast upload speeds for you to send data to someone else’s server. - - - Ping "Round-trip time" is more or less well defined as the network delay from point A to B and back. This is the sum of all encoding, queueing, processing, decoding, and propagation delays in both directions. Essentially, it's the delay when A may be expecting an answer from B for a request that requires very little processing. A fast ping means a more responsive connection, especially in applications where timing is everything (like video games). Ping is measured in Milliseconds (ms). Jitter is simply the difference in Ping "Round-trip time" . In other words, jitter is measuring time difference in packet inter-arrival time. Jitter helps diagnose internet connection stability. The higher the jitter value, the worse the stability of the connection. - - - - - - - - - Download speed is measured in megabits per second (Mbps). - - - Upload speed is measured in megabits per second (Mbps). - - - Ping is measured in Milliseconds (ms). - - - - - - Shows you real-time network traffic graph for Download! - - - Upload speed is measured in megabits per second (Mbps). - - - Your ping! and Jitter - - - Your upload speed! - - - Your download speed! - - - - - - - - - Click here to run a speed! - - - Options - - - - - - - - - - - - - - \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/android-chrome-192x192.png b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/android-chrome-192x192.png deleted file mode 100644 index df78b83c..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/android-chrome-192x192.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/android-chrome-512x512.png b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/android-chrome-512x512.png deleted file mode 100644 index 559d3eaf..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/android-chrome-512x512.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/apple-touch-icon.png b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/apple-touch-icon.png deleted file mode 100644 index 14aec788..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/apple-touch-icon.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/browserconfig.xml b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/browserconfig.xml deleted file mode 100644 index ef1658c6..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/browserconfig.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - #ffc40d - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/favicon-16x16.png b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/favicon-16x16.png deleted file mode 100644 index 0dd76a80..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/favicon-16x16.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/favicon-32x32.png b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/favicon-32x32.png deleted file mode 100644 index e3ad7510..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/favicon-32x32.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/favicon.ico b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/favicon.ico deleted file mode 100644 index bebc1524..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/favicon.ico and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/launcher-icon-1x.png b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/launcher-icon-1x.png deleted file mode 100644 index 50bdeae8..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/launcher-icon-1x.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/launcher-icon-2x.png b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/launcher-icon-2x.png deleted file mode 100644 index 98687998..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/launcher-icon-2x.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/launcher-icon-3x.png b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/launcher-icon-3x.png deleted file mode 100644 index b612922b..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/launcher-icon-3x.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/launcher-icon-4x.png b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/launcher-icon-4x.png deleted file mode 100644 index 5936c147..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/launcher-icon-4x.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/mstile-150x150.png b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/mstile-150x150.png deleted file mode 100644 index eb66ae2b..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/mstile-150x150.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/safari-pinned-tab.svg b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/safari-pinned-tab.svg deleted file mode 100644 index e525e9b4..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/safari-pinned-tab.svg +++ /dev/null @@ -1,108 +0,0 @@ - - - - -Created by potrace 1.11, written by Peter Selinger 2001-2013 - - - - - - - - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/site.webmanifest b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/site.webmanifest deleted file mode 100644 index 058491fb..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/images/icons/site.webmanifest +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "OpenSpeedTest", - "short_name": "Speed Test", - "icons": [ - { - "src": "/assets/images/icons/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/assets/images/icons/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone", - "start_url": "/" - - -} diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/js/app-2.5.4.js b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/js/app-2.5.4.js deleted file mode 100644 index f5b5b6e4..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/js/app-2.5.4.js +++ /dev/null @@ -1,1427 +0,0 @@ -/* - Official Website : https://OpenSpeedTest.COM | Email: support@openspeedtest.com - Developed by : Vishnu | https://Vishnu.Pro | Email : me@vishnu.pro - Like this Project? Please Donate NOW & Keep us Alive -> https://go.openspeedtest.com/Donate - Speed Test by OpenSpeedTest™️ is Free and Open-Source Software (FOSS) with MIT License. - Read full license terms @ http://go.openspeedtest.com/License - If you have any Questions, ideas or Comments Please Send it via -> https://go.openspeedtest.com/SendMessage -*/ -window.onload = function() { - var appSVG = document.getElementById("OpenSpeedTest-UI"); - appSVG.parentNode.replaceChild(appSVG.contentDocument.documentElement, appSVG); - ostOnload(); - OpenSpeedTest.Start(); -}; -(function(OpenSpeedTest) { - var Status; - var ProG; - var Callback = function(callback) { - if (callback && typeof callback === "function") { - callback(); - } - }; - function _(el) { - if (!(this instanceof _)) { - return new _(el); - } - this.el = document.getElementById(el); - } - _.prototype.fade = function fade(type, ms, callback00) { - var isIn = type === "in", opacity = isIn ? 0 : 1, interval = 14, duration = ms, gap = interval / duration, self = this; - if (isIn) { - self.el.style.display = "block"; - self.el.style.opacity = opacity; - } - function func() { - opacity = isIn ? opacity + gap : opacity - gap; - self.el.style.opacity = opacity; - if (opacity <= 0) { - self.el.style.display = "none"; - } - if (opacity <= 0 || opacity >= 1) { - window.clearInterval(fading, Callback(callback00)); - } - } - var fading = window.setInterval(func, interval); - }; - var easeOutQuint = function(t, b, c, d) { - t /= d; - t--; - return c * (t * t * t * t * t + 1) + b; - }; - var easeOutCubic = function(t, b, c, d) { - t /= d; - t--; - return c * (t * t * t + 1) + b; - }; - var openSpeedtestShow = function() { - this.YourIP = _("YourIP"); - this.ipDesk = _("ipDesk"); - this.ipMob = _("ipMob"); - this.downSymbolDesk = _("downSymbolDesk"); - this.upSymbolDesk = _("upSymbolDesk"); - this.upSymbolMob = _("upSymbolMob"); - this.downSymbolMob = _("downSymbolMob"); - this.settingsMob = _("settingsMob"); - this.settingsDesk = _("settingsDesk"); - this.oDoLiveStatus = _("oDoLiveStatus"); - this.ConnectErrorMob = _("ConnectErrorMob"); - this.ConnectErrorDesk = _("ConnectErrorDesk"); - this.downResult = _("downResult"); - this.upRestxt = _("upRestxt"); - this.pingResult = _("pingResult"); - this.jitterDesk = _("jitterDesk"); - this.pingMobres = _("pingMobres"); - this.JitterResultMon = _("JitterResultMon"); - this.JitterResultms = _("JitterResultms"); - this.UI_Desk = _("UI-Desk"); - this.UI_Mob = _("UI-Mob"); - this.oDoTopSpeed = _("oDoTopSpeed"); - this.startButtonMob = _("startButtonMob"); - this.startButtonDesk = _("startButtonDesk"); - this.intro_Desk = _("intro-Desk"); - this.intro_Mob = _("intro-Mob"); - this.loader = _("loading_app"); - this.OpenSpeedtest = _("OpenSpeedtest"); - this.mainGaugebg_Desk = _("mainGaugebg-Desk"); - this.mainGaugeBlue_Desk = _("mainGaugeBlue-Desk"); - this.mainGaugeWhite_Desk = _("mainGaugeWhite-Desk"); - this.mainGaugebg_Mob = _("mainGaugebg-Mob"); - this.mainGaugeBlue_Mob = _("mainGaugeBlue-Mob"); - this.mainGaugeWhite_Mob = _("mainGaugeWhite-Mob"); - this.oDoLiveSpeed = _("oDoLiveSpeed"); - this.progressStatus_Mob = _("progressStatus-Mob"); - this.progressStatus_Desk = _("progressStatus-Desk"); - this.graphc1 = _("graphc1"); - this.graphc2 = _("graphc2"); - this.graphMob2 = _("graphMob2"); - this.graphMob1 = _("graphMob1"); - this.text = _("text"); - this.scale = [{degree:680, value:0}, {degree:570, value:0.5}, {degree:460, value:1}, {degree:337, value:10}, {degree:220, value:100}, {degree:115, value:500}, {degree:0, value:1000},]; - this.element = ""; - this.chart = ""; - this.polygon = ""; - this.width = 200; - this.height = 50; - this.maxValue = 0; - this.values = []; - this.points = []; - this.vSteps = 5; - this.measurements = []; - this.points = []; - }; - openSpeedtestShow.prototype.reset = function() { - this.element = ""; - this.chart = ""; - this.polygon = ""; - this.width = 200; - this.height = 50; - this.maxValue = 0; - this.values = []; - this.points = []; - this.vSteps = 5; - this.measurements = []; - this.points = []; - }; - openSpeedtestShow.prototype.ip = function() { - var Self = this; - if (Self.ipDesk.el.style.display === "block") { - Self.ipDesk.el.style.display = "none"; - Self.ipMob.el.style.display = "none"; - } else { - Self.ipDesk.el.style.display = "block"; - Self.ipMob.el.style.display = "block"; - } - }; - openSpeedtestShow.prototype.prePing = function() { - this.loader.fade("out", 500); - this.OpenSpeedtest.fade("in", 1000); - }; - openSpeedtestShow.prototype.app = function() { - this.loader.fade("out", 500, this.ShowAppIntro()); - }; - openSpeedtestShow.prototype.ShowAppIntro = function() { - this.OpenSpeedtest.fade("in", 1000); - }; - openSpeedtestShow.prototype.userInterface = function() { - var Self = this; - this.intro_Desk.fade("out", 1000); - this.intro_Mob.fade("out", 1000, this.ShowUI()); - }; - openSpeedtestShow.prototype.ShowUI = function() { - this.UI_Desk.fade("in", 1000); - this.UI_Mob.fade("in", 1000, uiLoaded); - function uiLoaded(argument) { - Status = "Loaded"; - console.log("Developed by Vishnu. Email --\x3e me@vishnu.pro"); - } - }; - openSpeedtestShow.prototype.Symbol = function(dir) { - if (dir == 0) { - this.downSymbolMob.el.style.display = "block"; - this.downSymbolDesk.el.style.display = "block"; - this.upSymbolMob.el.style.display = "none"; - this.upSymbolDesk.el.style.display = "none"; - } - if (dir == 1) { - this.downSymbolMob.el.style.display = "none"; - this.downSymbolDesk.el.style.display = "none"; - this.upSymbolMob.el.style.display = "block"; - this.upSymbolDesk.el.style.display = "block"; - } - if (dir == 2) { - this.downSymbolMob.el.style.display = "none"; - this.downSymbolDesk.el.style.display = "none"; - this.upSymbolMob.el.style.display = "none"; - this.upSymbolDesk.el.style.display = "none"; - } - }; - openSpeedtestShow.prototype.Graph = function(speed, select) { - if (!("remove" in Element.prototype)) { - Element.prototype.remove = function() { - if (this.parentNode) { - this.parentNode.removeChild(this); - } - }; - } - var Self = this; - var Remove; - if (select === 0) { - var Graphelement = this.graphc1.el; - Remove = "line"; - this.graphMob2.el.style.display = "none"; - this.graphMob1.el.style.display = "block"; - } else { - Graphelement = this.graphc2.el; - Remove = "line2"; - this.graphMob1.el.style.display = "none"; - this.graphMob2.el.style.display = "block"; - } - if (!isNaN(speed)) { - this.values.push(speed); - } else { - this.values.push(""); - } - function calcMeasure() { - for (x = 0; x < Self.vSteps; x++) { - var measurement = Math.ceil(Self.maxValue / Self.vSteps * (x + 1)); - Self.measurements.push(measurement); - } - Self.measurements.reverse(); - } - function createChart(element, values) { - calcMaxValue(); - calcPoints(); - calcMeasure(); - var removeLine = document.getElementsByClassName(Remove); - while (removeLine.length > 0) { - removeLine[0].remove(); - } - Self.polygon = document.createElementNS("http://www.w3.org/2000/svg", "polygon"); - Self.polygon.setAttribute("points", Self.points); - Self.polygon.setAttribute("class", Remove); - if (Self.values.length > 1) { - Graphelement.appendChild(Self.polygon); - } - } - function calcPoints() { - if (Self.values.length > 1) { - var points = "0," + Self.height + " "; - for (x = 0; x < Self.values.length; x++) { - var perc = Self.values[x] / Self.maxValue; - var steps = 130 / (Self.values.length - 1); - var point = (steps * x).toFixed(2) + "," + (Self.height - Self.height * perc).toFixed(2) + " "; - points += point; - } - points += "130," + Self.height; - Self.points = points; - } - } - var x; - function calcMaxValue() { - Self.maxValue = 0; - for (x = 0; x < Self.values.length; x++) { - if (Self.values[x] > Self.maxValue) { - Self.maxValue = Self.values[x]; - } - } - Self.maxValue = Math.ceil(Self.maxValue); - } - if (speed > 0) { - createChart(Graphelement, speed); - } - }; - openSpeedtestShow.prototype.progress = function(Switch, duration) { - var Self = this; - var Stop = duration; - var Stage = Switch; - var currTime = Date.now(); - var chan2 = 0 - 400; - var interval = setInterval(function() { - var timeNow = (Date.now() - currTime) / 1000; - var toLeft = easeOutCubic(timeNow, 400, 400, Stop); - var toRight = easeOutCubic(timeNow, 400, chan2, Stop); - if (Stage) { - Self.progressStatus_Desk.el.style.strokeDashoffset = toLeft; - Self.progressStatus_Mob.el.style.strokeDashoffset = toLeft; - } else { - Self.progressStatus_Desk.el.style.strokeDashoffset = toRight; - Self.progressStatus_Mob.el.style.strokeDashoffset = toRight; - } - if (timeNow >= Stop) { - clearInterval(interval); - ProG = "done"; - Self.progressStatus_Desk.el.style.strokeDashoffset = 800; - Self.progressStatus_Mob.el.style.strokeDashoffset = 800; - } - }, 14); - }; - openSpeedtestShow.prototype.mainGaugeProgress = function(currentSpeed) { - var Self = this; - var speed = currentSpeed; - if (speed < 0) { - speed = 0; - } - var mainGaugeOffset = Self.getNonlinearDegree(speed); - if (currentSpeed > 0) { - this.mainGaugeBlue_Desk.el.style.strokeOpacity = 1; - this.mainGaugeWhite_Desk.el.style.strokeOpacity = 1; - this.mainGaugeBlue_Mob.el.style.strokeOpacity = 1; - this.mainGaugeWhite_Mob.el.style.strokeOpacity = 1; - this.mainGaugeBlue_Desk.el.style.strokeDashoffset = mainGaugeOffset; - this.mainGaugeWhite_Desk.el.style.strokeDashoffset = mainGaugeOffset == 0 ? 1 : mainGaugeOffset + 1; - this.mainGaugeBlue_Mob.el.style.strokeDashoffset = mainGaugeOffset; - this.mainGaugeWhite_Mob.el.style.strokeDashoffset = mainGaugeOffset == 0 ? 1 : mainGaugeOffset + 1; - } - if (mainGaugeOffset == 0 && speed > 1000) { - this.mainGaugeBlue_Mob.el.style.strokeDashoffset = mainGaugeOffset >= 681 ? 681 : mainGaugeOffset; - this.mainGaugeWhite_Mob.el.style.strokeDashoffset = mainGaugeOffset == 0 ? 1 : mainGaugeOffset + 1; - this.mainGaugeWhite_Desk.el.style.strokeDashoffset = mainGaugeOffset == 0 ? 1 : mainGaugeOffset + 1; - this.mainGaugeBlue_Desk.el.style.strokeDashoffset = mainGaugeOffset >= 681 ? 681 : mainGaugeOffset; - } else if (mainGaugeOffset == 0 && speed <= 0) { - this.mainGaugeBlue_Mob.el.style.strokeDashoffset = 681.1; - this.mainGaugeWhite_Mob.el.style.strokeDashoffset = 0.1; - this.mainGaugeWhite_Desk.el.style.strokeDashoffset = 0.1; - this.mainGaugeBlue_Desk.el.style.strokeDashoffset = 681.1; - } - }; - openSpeedtestShow.prototype.showStatus = function(e) { - this.oDoLiveStatus.el.textContent = e; - }; - openSpeedtestShow.prototype.ConnectionError = function() { - this.ConnectErrorMob.el.style.display = "block"; - this.ConnectErrorDesk.el.style.display = "block"; - }; - openSpeedtestShow.prototype.uploadResult = function(upload) { - if (upload < 1) { - this.upRestxt.el.textContent = upload.toFixed(3); - } - if (upload >= 1 && upload < 9999) { - this.upRestxt.el.textContent = upload.toFixed(1); - } - if (upload >= 10000 && upload < 99999) { - this.upRestxt.el.textContent = upload.toFixed(1); - this.upRestxt.el.style.fontSize = "20px"; - } - if (upload >= 100000) { - this.upRestxt.el.textContent = upload.toFixed(1); - this.upRestxt.el.style.fontSize = "18px"; - } - }; - openSpeedtestShow.prototype.pingResults = function(data, Display) { - var ShowData = data; - if (Display === "Ping") { - if (ShowData >= 1 && ShowData < 10000) { - this.pingResult.el.textContent = Math.floor(ShowData); - this.pingMobres.el.textContent = Math.floor(ShowData); - } else if (ShowData >= 0 && ShowData < 1) { - if (ShowData == 0) { - ShowData = 0; - } - this.pingResult.el.textContent = ShowData; - this.pingMobres.el.textContent = ShowData; - } - } - if (Display === "Error") { - this.oDoLiveSpeed.el.textContent = ShowData; - } - }; - openSpeedtestShow.prototype.downloadResult = function(download) { - if (download < 1) { - this.downResult.el.textContent = download.toFixed(3); - } - if (download >= 1 && download < 9999) { - this.downResult.el.textContent = download.toFixed(1); - } - if (download >= 10000 && download < 99999) { - this.downResult.el.textContent = download.toFixed(1); - this.downResult.el.style.fontSize = "20px"; - } - if (download >= 100000) { - this.downResult.el.textContent = download.toFixed(1); - this.downResult.el.style.fontSize = "18px"; - } - }; - openSpeedtestShow.prototype.jitterResult = function(data, Display) { - var ShowData = data; - if (Display === "Jitter") { - if (ShowData >= 1 && ShowData < 10000) { - this.jitterDesk.el.textContent = Math.floor(ShowData); - if (ShowData >= 1 && ShowData < 100) { - this.JitterResultMon.el.textContent = Math.floor(ShowData); - } - if (ShowData >= 100) { - var kData = (ShowData / 1000).toFixed(1); - this.JitterResultMon.el.textContent = kData + "k"; - } - } else if (ShowData >= 0 && ShowData < 1) { - if (ShowData == 0) { - ShowData = 0; - } - this.jitterDesk.el.textContent = ShowData; - this.JitterResultMon.el.textContent = ShowData; - } - } - }; - openSpeedtestShow.prototype.LiveSpeed = function(data, Display) { - var ShowData = data; - if (Display === "countDown") { - var speed = ShowData.toFixed(0); - this.oDoLiveSpeed.el.textContent = speed; - return; - } - if (Display === "speedToZero") { - if (typeof ShowData == "number") { - ShowData = ShowData.toFixed(1); - } - if (ShowData <= 0) { - ShowData = 0; - } - this.oDoLiveSpeed.el.textContent = ShowData; - this.oDoTopSpeed.el.textContent = "1000+"; - this.oDoTopSpeed.el.style.fontSize = "16.9px"; - this.oDoTopSpeed.el.style.fill = "gray"; - return; - } - if (Display === "Ping") { - if (ShowData >= 1 && ShowData < 10000) { - this.oDoLiveSpeed.el.textContent = Math.floor(ShowData); - } else if (ShowData >= 0 && ShowData < 1) { - if (ShowData == 0) { - ShowData = 0; - } - this.oDoLiveSpeed.el.textContent = ShowData; - } - } else { - if (ShowData == 0) { - var speed = ShowData.toFixed(0); - this.oDoLiveSpeed.el.textContent = speed; - } - if (ShowData <= 1 && ShowData > 0) { - var speed = ShowData.toFixed(3); - this.oDoLiveSpeed.el.textContent = speed; - } - if (ShowData > 1) { - var speed = ShowData.toFixed(1); - this.oDoLiveSpeed.el.textContent = speed; - } - if (ShowData <= 1000) { - this.oDoTopSpeed.el.textContent = "1000+"; - this.oDoTopSpeed.el.style.fontSize = "16.9px"; - this.oDoTopSpeed.el.style.fill = "gray"; - } - if (ShowData >= 1010) { - this.oDoTopSpeed.el.textContent = Math.floor(ShowData / 1010) * 1000 + "+"; - this.oDoTopSpeed.el.style.fill = "gray"; - this.oDoTopSpeed.el.style.fontSize = "17.2px"; - } - } - }; - openSpeedtestShow.prototype.GaugeProgresstoZero = function(currentSpeed, status) { - var speed = currentSpeed; - var Self = this; - var duration = 3; - if (speed >= 0) { - var time = Date.now(); - var SpeedtoZero = 0 - speed; - var interval = setInterval(function() { - var timeNow = (Date.now() - time) / 1000; - var speedToZero = easeOutQuint(timeNow, speed, SpeedtoZero, duration); - Self.LiveSpeed(speedToZero, "speedToZero"); - Self.mainGaugeProgress(speedToZero); - if (timeNow >= duration || speedToZero <= 0) { - clearInterval(interval); - Self.LiveSpeed(0, "speedToZero"); - Self.mainGaugeProgress(0); - Status = status; - } - }, 16); - } - }; - openSpeedtestShow.prototype.getNonlinearDegree = function(mega_bps) { - var i = 0; - if (0 == mega_bps || mega_bps <= 0 || isNaN(mega_bps)) { - return 0; - } - while (i < this.scale.length) { - if (mega_bps > this.scale[i].value) { - i++; - } else { - return this.scale[i - 1].degree + (mega_bps - this.scale[i - 1].value) * (this.scale[i].degree - this.scale[i - 1].degree) / (this.scale[i].value - this.scale[i - 1].value); - } - } - return this.scale[this.scale.length - 1].degree; - }; - var openSpeedtestGet = function() { - this.OverAllTimeAvg = window.performance.now(); - this.SpeedSamples = []; - this.FinalSpeed; - }; - openSpeedtestGet.prototype.reset = function() { - this.OverAllTimeAvg = window.performance.now(); - this.SpeedSamples = []; - this.FinalSpeed = 0; - }; - openSpeedtestGet.prototype.ArraySum = function(Arr) { - var array = Arr; - if (array) { - var sum = array.reduce(function(A, B) { - if (typeof A === "number" && typeof B === "number") { - return A + B; - } - }, 0); - return sum; - } else { - return 0; - } - }; - openSpeedtestGet.prototype.AvgSpeed = function(Livespeed, Start, duration) { - var Self = this; - this.timeNow = (window.performance.now() - this.OverAllTimeAvg) / 1000; - this.FinalSpeed; - var StartRecoding = Start; - StartRecoding = duration - StartRecoding; - if (this.timeNow >= StartRecoding) { - if (Livespeed > 0) { - this.SpeedSamples.push(Livespeed); - } - Self.FinalSpeed = Self.ArraySum(Self.SpeedSamples) / Self.SpeedSamples.length; - } - return Self.FinalSpeed; - }; - openSpeedtestGet.prototype.uRandom = function(size, callback) { - var size = size; - var randomValue = new Uint32Array(262144); - function getRandom() { - var n = randomValue.length; - for (var i = 0; i < n; i++) { - randomValue[i] = Math.random() * 4294967296; - } - return randomValue; - } - var randomData = []; - var genData = function(dataSize) { - var dataSize = dataSize; - for (var i = 0; i < dataSize; i++) { - randomData[i] = getRandom(); - } - return randomData; - }; - return new Blob(genData(size), {type:"application/octet-stream"}, Callback(callback)); - }; - openSpeedtestGet.prototype.addEvt = function(o, e, f) { - o.addEventListener(e, f); - }; - openSpeedtestGet.prototype.remEvt = function(o, e, f) { - o.removeEventListener(e, f); - }; - var openSpeedtestEngine = function() { - var Get = new openSpeedtestGet(); - var Show = new openSpeedtestShow(); - Show.app(); - var SendData; - var myhostName = location.hostname; - var key; - var TestServerip; - var downloadSpeed; - var uploadSpeed; - var dataUsedfordl; - var dataUsedforul; - var pingEstimate; - var jitterEstimate; - var logData; - var return_data; - var ReQ = []; - var StartTime = []; - var CurrentTime = []; - var LiveSpeedArr; - var dLoaded = 0; - var uLoaded = 0; - var currentSpeed = 0; - var uploadTimeing; - var downloadTimeing; - var downloadTime; - var uploadTime; - var saveTestData; - var stop = 0; - function reSett() { - StartTime = 0; - CurrentTime = 0; - LiveSpeedArr = 0; - currentSpeed = 0; - } - var userAgentString; - if (window.navigator.userAgent) { - userAgentString = window.navigator.userAgent; - } else { - userAgentString = "Not Found"; - } - var ulFinal = ulDuration * 0.6; - var dlFinal = dlDuration * 0.6; - function setFinal() { - if (ulDuration * 0.6 >= 7) { - ulFinal = 7; - } - if (dlDuration * 0.6 >= 7) { - dlFinal = 7; - } - } - setFinal(); - var launch = true; - var init = true; - Get.addEvt(Show.settingsMob.el, "click", ShowIP); - Get.addEvt(Show.settingsDesk.el, "click", ShowIP); - Get.addEvt(Show.startButtonDesk.el, "click", runTasks); - Get.addEvt(Show.startButtonMob.el, "click", runTasks); - Get.addEvt(document, "keypress", hiEnter); - var addEvent = true; - var getParams = function(url) { - var params = {}; - var parser = document.createElement("a"); - parser.href = url; - var query = parser.search.substring(1); - var vars = query.split("&"); - for (var i = 0; i < vars.length; i++) { - var pair = vars[i].split("="); - params[pair[0]] = decodeURIComponent(pair[1]); - } - return params; - }; - var getCommand = getParams(window.location.href.toLowerCase()); - if (setPingSamples) { - if (typeof getCommand.ping === "string" || typeof getCommand.p === "string") { - var setPing; - if (typeof getCommand.ping !== "undefined") { - setPing = getCommand.ping; - } else if (typeof getCommand.p !== "undefined") { - setPing = getCommand.p; - } - if (setPing > 0) { - pingSamples = setPing; - pingSamples = setPing; - } - } - } - if (setPingTimeout) { - if (typeof getCommand.out === "string" || typeof getCommand.o === "string") { - var setOut; - if (typeof getCommand.out !== "undefined") { - setOut = getCommand.out; - } else if (typeof getCommand.o !== "undefined") { - setOut = getCommand.o; - } - if (setOut > 1) { - pingTimeOut = setOut; - pingTimeOut = setOut; - } - } - } - if (setHTTPReq) { - if (typeof getCommand.xhr === "string" || typeof getCommand.x === "string") { - var setThreads; - if (typeof getCommand.xhr !== "undefined") { - setThreads = getCommand.xhr; - } else if (typeof getCommand.x !== "undefined") { - setThreads = getCommand.x; - } - if (setThreads > 0 && setThreads <= 32) { - dlThreads = setThreads; - ulThreads = setThreads; - } - } - } - function isValidHttpUrl(str) { - var regex = /(?:https?):\/\/(\w+:?\w*)?(\S+)(:\d+)?(\/|\/([\w#!:.?+=&%!\-\/]))?/; - if (!regex.test(str)) { - return false; - } else { - return true; - } - } - if (selectServer) { - if (typeof getCommand.host === "string" || typeof getCommand.h === "string") { - var severAddress; - if (typeof getCommand.host !== "undefined") { - severAddress = getCommand.host; - } else if (typeof getCommand.h !== "undefined") { - severAddress = getCommand.h; - } - if (isValidHttpUrl(severAddress)) { - openSpeedTestServerList = [{ServerName:"Home", Download:severAddress + "/downloading", Upload:severAddress + "/upload", ServerIcon:"DefaultIcon",},]; - } - } - } - var custom = parseInt(getCommand.stress); - var customS = parseInt(getCommand.s); - var runStress; - var runStressCustom; - if (typeof getCommand.stress === "string") { - runStress = getCommand.stress; - runStressCustom = custom; - } else if (typeof getCommand.s === "string") { - runStress = getCommand.s; - runStressCustom = customS; - } - if (runStress && stressTest) { - if (runStress === "low" || runStress === "l") { - dlDuration = 300; - ulDuration = 300; - } - if (runStress === "medium" || runStress === "m") { - dlDuration = 600; - ulDuration = 600; - } - if (runStress === "high" || runStress === "h") { - dlDuration = 900; - ulDuration = 900; - } - if (runStress === "veryhigh" || runStress === "v") { - dlDuration = 1800; - ulDuration = 1800; - } - if (runStress === "extreme" || runStress === "e") { - dlDuration = 3600; - ulDuration = 3600; - } - if (runStress === "day" || runStress === "d") { - dlDuration = 86400; - ulDuration = 86400; - } - if (runStress === "year" || runStress === "y") { - dlDuration = 31557600; - ulDuration = 31557600; - } - if (custom > 12 || customS > 12) { - dlDuration = runStressCustom; - ulDuration = runStressCustom; - } - } - var overheadClean = parseInt(getCommand.clean); - var overheadCleanC = parseInt(getCommand.c); - var customOverHeadValue = 1; - if (overheadClean) { - customOverHeadValue = overheadClean; - } else if (overheadCleanC) { - customOverHeadValue = overheadCleanC; - } - if (enableClean) { - if (typeof getCommand.clean === "string" || typeof getCommand.c === "string") { - if (overheadClean >= 1 || overheadCleanC >= 1) { - if (overheadClean < 5 || overheadCleanC < 5) { - upAdjust = 1 + customOverHeadValue / 100; - dlAdjust = 1 + customOverHeadValue / 100; - } - } else { - upAdjust = 1; - dlAdjust = 1; - } - } - } - var OpenSpeedTestRun = parseInt(getCommand.run); - var OpenSpeedTestRunR = parseInt(getCommand.r); - var OpenSpeedTestStart; - if (enableRun) { - if (typeof getCommand.run === "string" || typeof getCommand.r === "string") { - if (OpenSpeedTestRun > 0) { - OpenSpeedTestStart = OpenSpeedTestRun; - } else if (OpenSpeedTestRunR > 0) { - OpenSpeedTestStart = OpenSpeedTestRunR; - } else { - OpenSpeedTestStart = 0; - } - } - } - if (OpenSpeedTestStart >= 0) { - if (launch) { - runTasks(); - } - } - var runTest = getCommand.test; - var runTestT = getCommand.t; - var SelectTest = false; - if (selectTest) { - if (typeof runTest === "string" || typeof runTestT === "string") { - var runTestC; - if (runTest) { - runTestC = runTest; - SelectTest = runTest; - } else if (runTestT) { - runTestC = runTestT; - SelectTest = runTestT; - } - if (runTestC === "download" || runTestC === "d") { - uploadSpeed = 0; - dataUsedforul = 0; - SelectTest = "Download"; - if (launch) { - runTasks(); - } - } else if (runTestC === "upload" || runTestC === "u") { - downloadSpeed = 0; - dataUsedfordl = 0; - SelectTest = "Upload"; - stop = 1; - if (launch) { - runTasks(); - } - } else if (runTestC === "ping" || runTestC === "p") { - uploadSpeed = 0; - dataUsedforul = 0; - downloadSpeed = 0; - dataUsedfordl = 0; - SelectTest = "Ping"; - if (launch) { - runTasks(); - } - } else { - SelectTest = false; - } - } - } - var Startit = 0; - function removeEvts() { - Get.remEvt(Show.settingsMob.el, "click", ShowIP); - Get.remEvt(Show.settingsDesk.el, "click", ShowIP); - Get.remEvt(Show.startButtonDesk.el, "click", runTasks); - Get.remEvt(Show.startButtonMob.el, "click", runTasks); - Get.remEvt(document, "keypress", hiEnter); - } - var requestIP = false; - function ShowIP() { - if (requestIP) { - Show.YourIP.el.textContent = "Please wait.."; - ServerConnect(7); - requestIP = false; - } - Show.ip(); - } - function runTasks() { - if (addEvent) { - removeEvts(); - addEvent = false; - } - if (OpenSpeedTestStart >= 0) { - launch = false; - Show.userInterface(); - init = false; - var AutoTme = Math.ceil(Math.abs(OpenSpeedTestStart)); - Show.showStatus("Automatic Test Starts in ..."); - var autoTest = setInterval(countDownF, 1000); - } - function countDownF() { - if (AutoTme >= 1) { - AutoTme = AutoTme - 1; - Show.LiveSpeed(AutoTme, "countDown"); - } else { - if (AutoTme <= 0) { - clearInterval(autoTest); - launch = true; - OpenSpeedTestStart = undefined; - runTasks(); - } - } - } - if (openSpeedTestServerList === "fetch" && launch === true) { - launch = false; - Show.showStatus("Fetching Server Info.."); - ServerConnect(6); - } - if (launch === true) { - if (SelectTest === "Ping") { - testRun(); - } else if (SelectTest === "Download") { - testRun(); - } else if (SelectTest === "Upload") { - testRun(); - } else if (SelectTest === false) { - testRun(); - } - } - } - var osttm = "\u2122"; - var myname = "OpenSpeedTest"; - var com = ".com"; - var ost = myname + osttm; - function hiEnter(e) { - if (e.key === "Enter") { - runTasks(); - } - } - var showResult = 0; - if (openChannel === "web") { - showResult = webRe; - requestIP = true; - } - if (openChannel === "widget") { - showResult = widgetRe; - requestIP = true; - } - if (openChannel === "selfwidget") { - showResult = widgetRe; - TestServerip = domainx; - myhostName = TestServerip; - } - if (openChannel === "dev") { - } - function testRun() { - if (init) { - Show.userInterface(); - init = false; - } - OpenSpeedtest(); - } - function OpenSpeedtest() { - if (openChannel === "widget" || openChannel === "web") { - ServerConnect(1); - } - function readyToUP() { - uploadTime = window.performance.now(); - upReq(); - } - var Engine = setInterval(function() { - if (Status === "Loaded") { - Status = "busy"; - sendPing(0); - } - if (Status === "Ping") { - Status = "busy"; - Show.showStatus("Milliseconds"); - } - if (Status === "Download") { - Show.showStatus("Initializing.."); - Get.reset(); - reSett(); - Show.reset(); - downloadTime = window.performance.now(); - downReq(); - Status = "initDown"; - } - if (Status === "Downloading") { - Show.Symbol(0); - if (Startit == 0) { - Startit = 1; - Show.showStatus("Testing download speed.."); - var extraTime = (window.performance.now() - downloadTime) / 1000; - dReset = extraTime; - Show.progress(1, dlDuration + 2.5); - dlDuration += extraTime; - } - downloadTimeing = (window.performance.now() - downloadTime) / 1000; - reportCurrentSpeed("dl"); - Show.showStatus("Mbps download"); - Show.mainGaugeProgress(currentSpeed); - Show.LiveSpeed(currentSpeed); - Show.Graph(currentSpeed, 0); - downloadSpeed = Get.AvgSpeed(currentSpeed, dlFinal, dlDuration); - if (downloadTimeing >= dlDuration && ProG == "done") { - if (SelectTest) { - Show.GaugeProgresstoZero(currentSpeed, "SendR"); - Show.showStatus("All done"); - Show.Symbol(2); - } else { - Show.GaugeProgresstoZero(currentSpeed, "Upload"); - } - Show.downloadResult(downloadSpeed); - dataUsedfordl = dLoaded; - stop = 1; - Status = "busy"; - reSett(); - Get.reset(); - } - } - if (Status == "Upload") { - if (stop === 1) { - Show.Symbol(1); - Status = "initup"; - Show.showStatus("Initializing.."); - Show.LiveSpeed("...", "speedToZero"); - SendData = Get.uRandom(ulDataSize, readyToUP); - if (SelectTest) { - Startit = 1; - } - } - } - if (Status === "Uploading") { - if (Startit == 1) { - Startit = 2; - Show.showStatus("Testing upload speed.."); - currentSpeed = 0; - Get.reset(); - Show.reset(); - var extraUTime = (window.performance.now() - uploadTime) / 1000; - uReset = extraUTime; - Show.progress(false, ulDuration + 2.5); - ulDuration += extraUTime; - } - Show.showStatus("Mbps upload"); - uploadTimeing = (window.performance.now() - uploadTime) / 1000; - reportCurrentSpeed("up"); - Show.mainGaugeProgress(currentSpeed); - Show.LiveSpeed(currentSpeed); - Show.Graph(currentSpeed, 1); - uploadSpeed = Get.AvgSpeed(currentSpeed, ulFinal, ulDuration); - if (uploadTimeing >= ulDuration && stop == 1) { - dataUsedforul = uLoaded; - Show.uploadResult(uploadSpeed); - Show.GaugeProgresstoZero(currentSpeed, "SendR"); - SendData = undefined; - Show.showStatus("All done"); - Show.Symbol(2); - Status = "busy"; - stop = 0; - } - } - if (Status === "Error") { - Show.showStatus("Check your network connection status."); - Show.ConnectionError(); - Status = "busy"; - clearInterval(Engine); - var dummyElement = document.createElement("div"); - dummyElement.innerHTML = ''; - var htmlAnchorElement = dummyElement.querySelector("a"); - Show.oDoLiveSpeed.el.textContent = "Network Error"; - var circleSVG = document.getElementById("oDoLiveSpeed"); - htmlAnchorElement.innerHTML = circleSVG.innerHTML; - circleSVG.innerHTML = dummyElement.innerHTML; - } - if (Status === "SendR") { - Show.showStatus("All done"); - var dummyElement = document.createElement("div"); - dummyElement.innerHTML = ''; - var htmlAnchorElement = dummyElement.querySelector("a"); - Show.oDoLiveSpeed.el.textContent = ost; - var circleSVG = document.getElementById("oDoLiveSpeed"); - htmlAnchorElement.innerHTML = circleSVG.innerHTML; - circleSVG.innerHTML = dummyElement.innerHTML; - if (location.hostname != myname.toLowerCase() + com) { - saveTestData = "https://" + myname.toLowerCase() + com + "/results/show.php?" + "&d=" + downloadSpeed.toFixed(3) + "&u=" + uploadSpeed.toFixed(3) + "&p=" + pingEstimate + "&j=" + jitterEstimate + "&dd=" + (dataUsedfordl / 1048576).toFixed(3) + "&ud=" + (dataUsedforul / 1048576).toFixed(3) + "&ua=" + userAgentString; - saveTestData = encodeURI(saveTestData); - var circleSVG2 = document.getElementById("resultsData"); - circleSVG2.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", saveTestData); - circleSVG2.setAttribute("target", "_blank"); - if (saveData) { - ServerConnect(5); - } - } else { - ServerConnect(3); - } - Status = "busy"; - clearInterval(Engine); - } - }, 100); - } - function downReq() { - for (var i = 0; i < dlThreads; i++) { - setTimeout(function(i) { - SendReQ(i); - }, dlDelay * i, i); - } - } - function upReq() { - for (var i = 0; i < ulThreads; i++) { - setTimeout(function(i) { - SendUpReq(i); - }, ulDelay * i, i); - } - } - var dLoad = 0; - var dDiff = 0; - var dTotal = 0; - var dtLoad = 0; - var dtDiff = 0; - var dtTotal = 0; - var dRest = 0; - var dReset; - var uReset; - var uLoad = 0; - var uDiff = 0; - var uTotal = 0; - var utLoad = 0; - var utDiff = 0; - var utTotal = 0; - var uRest = 0; - var dualReset; - var neXT = dlDuration * 1000 - 6000; - var dualupReset; - var neXTUp = ulDuration * 1000 - 6000; - function reportCurrentSpeed(now) { - if (now === "dl") { - var dTime = downloadTimeing * 1000; - if (dTime > dReset * 1000 + dlFinal / 2 * 1000 && dRest === 0) { - dRest = 1; - dtTotal = dtTotal * 0.01; - dTotal = dTotal * 0.01; - dualReset = dTime + 10000; - } - if (dTime >= dualReset && dualReset < neXT) { - dualReset += 10000; - dtTotal = dtTotal * 0.01; - dTotal = dTotal * 0.01; - } - dLoad = dLoaded <= 0 ? 0 : dLoaded - dDiff; - dDiff = dLoaded; - dTotal += dLoad; - dtLoad = dtDiff = 0 ? 0 : dTime - dtDiff; - dtDiff = dTime; - dtTotal += dtLoad; - if (dTotal > 0) { - LiveSpeedArr = dTotal / dtTotal / 125 * upAdjust; - currentSpeed = LiveSpeedArr; - } - } - if (now === "up") { - var Tym = uploadTimeing * 1000; - if (Tym > uReset * 1000 + ulFinal / 2 * 1000 && uRest === 0) { - uRest = 1; - utTotal = utTotal * 0.1; - uTotal = uTotal * 0.1; - dualupReset = Tym + 10000; - } - if (Tym >= dualupReset && dualupReset < neXTUp) { - dualupReset += 10000; - utTotal = utTotal * 0.1; - uTotal = uTotal * 0.1; - } - uLoad = uLoaded <= 0 ? 0 : uLoaded - uDiff; - uDiff = uLoaded; - uTotal += uLoad; - utLoad = utDiff = 0 ? 0 : Tym - utDiff; - utDiff = Tym; - utTotal += utLoad; - if (uTotal > 0) { - LiveSpeedArr = uTotal / utTotal / 125 * upAdjust; - currentSpeed = LiveSpeedArr; - } - } - } - function SendReQ(i) { - var lastLoaded = 0; - var OST = new XMLHttpRequest(); - ReQ[i] = OST; - ReQ[i].open("GET", fianlPingServer.Download + "?n=" + Math.random(), true); - ReQ[i].onprogress = function(e) { - if (stop === 1) { - ReQ[i].abort(); - ReQ[i] = null; - ReQ[i] = undefined; - delete ReQ[i]; - return false; - } - if (Status == "initDown") { - Status = "Downloading"; - } - var eLoaded = e.loaded <= 0 ? 0 : e.loaded - lastLoaded; - if (isNaN(eLoaded) || !isFinite(eLoaded) || eLoaded < 0) { - return false; - } - dLoaded += eLoaded; - lastLoaded = e.loaded; - }; - ReQ[i].onload = function(e) { - if (lastLoaded === 0) { - dLoaded += e.total; - } - if (Status == "initDown") { - Status = "Downloading"; - } - if (ReQ[i]) { - ReQ[i].abort(); - ReQ[i] = null; - ReQ[i] = undefined; - delete ReQ[i]; - } - if (stop === 0) { - SendReQ(i); - } - }; - ReQ[i].onerror = function(e) { - if (stop === 0) { - SendReQ(i); - } - }; - ReQ[i].responseType = "arraybuffer"; - ReQ[i].send(); - } - var uReQ = []; - function SendUpReq(i) { - var lastULoaded = 0; - var OST = new XMLHttpRequest(); - uReQ[i] = OST; - uReQ[i].open("POST", fianlPingServer.Upload + "?n=" + Math.random(), true); - uReQ[i].upload.onprogress = function(e) { - if (Status == "initup" && some === undefined) { - var some; - Status = "Uploading"; - } - if (uploadTimeing >= ulDuration) { - uReQ[i].abort(); - uReQ[i] = null; - uReQ[i] = undefined; - delete uReQ[i]; - return false; - } - var eLoaded = e.loaded <= 0 ? 0 : e.loaded - lastULoaded; - if (isNaN(eLoaded) || !isFinite(eLoaded) || eLoaded < 0) { - return false; - } - uLoaded += eLoaded; - lastULoaded = e.loaded; - }; - uReQ[i].onload = function() { - if (lastULoaded === 0) { - uLoaded += ulDataSize * 1048576; - if (uploadTimeing >= ulDuration) { - uReQ[i].abort(); - uReQ[i] = null; - uReQ[i] = undefined; - delete uReQ[i]; - return false; - } - } - if (Status == "initup" && some === undefined) { - var some; - Status = "Uploading"; - } - if (uReQ[i]) { - uReQ[i].abort(); - uReQ[i] = null; - uReQ[i] = undefined; - delete uReQ[i]; - } - if (stop === 1) { - SendUpReq(i); - } - }; - uReQ[i].onerror = function(e) { - if (uploadTimeing <= ulDuration) { - SendUpReq(i); - } - }; - uReQ[i].setRequestHeader("Content-Type", "application/octet-stream"); - if (i > 0 && uLoaded <= 17000) { - } else { - uReQ[i].send(SendData); - } - } - function sendPing() { - readServerList(); - } - var fianlPingServer; - var statusPing; - var statusPingFinal; - var statusJitter; - var statusJitterFinal; - var statusPingTest; - var pingSendStatus = -1; - var finalPing = []; - var pingServer = []; - var finalJitter = []; - var pingSendLength = openSpeedTestServerList.length; - function readServerList() { - pingSendLength = openSpeedTestServerList.length; - Status = "Ping"; - performance.clearResourceTimings(); - if (pingSendStatus < pingSendLength - 1) { - pingSendStatus++; - if (statusPingTest != "Stop") { - sendPingRequest(openSpeedTestServerList[pingSendStatus], readServerList); - } - } else { - if (pingServer.length >= 1) { - var finalLeastPingResult = Math.min.apply(Math, finalPing); - var finalLeastPingResultIndex = finalPing.indexOf(finalLeastPingResult); - fianlPingServer = pingServer[finalLeastPingResultIndex]; - statusPingFinal = finalLeastPingResult; - statusJitterFinal = finalJitter[finalLeastPingResultIndex]; - statusPingTest = "Busy"; - Show.LiveSpeed(statusPingFinal, "Ping"); - Show.pingResults(statusPingFinal, "Ping"); - Show.jitterResult(statusJitterFinal, "Jitter"); - pingEstimate = statusPingFinal; - jitterEstimate = statusJitterFinal; - if (SelectTest) { - if (SelectTest == "Ping") { - Status = "SendR"; - } else { - Status = SelectTest; - } - } else { - Status = "Download"; - } - } else { - if (pingServer.Download) { - } else { - Status = "Error"; - } - } - } - } - function sendPingRequest(serverListElm, callback) { - var pingSamplesSend = 0; - var pingResult = []; - var jitterResult = []; - function sendNewPingReq() { - if (pingSamplesSend < pingSamples) { - pingSamplesSend++; - if (statusPingTest != "Stop") { - PingRequest(); - } - } else { - if (pingResult.length > 1) { - jitterResult.sort(function(a, b) { - return a - b; - }); - jitterResult = jitterResult.slice(0, jitterResult.length * jitterFinalSample); - jitterResult = jitterResult.reduce(function(acc, val) { - return acc + val; - }, 0) / jitterResult.length; - var leastJitter = jitterResult.toFixed(1); - var leastPing = Math.min.apply(Math, pingResult); - finalPing.push(leastPing); - pingServer.push(serverListElm); - finalJitter.push(leastJitter); - if (typeof callback === "function") { - callback(); - } - } else { - if (typeof callback === "function") { - callback(); - } - } - } - } - function PingRequest() { - var OST = new XMLHttpRequest(); - var ReQ = OST; - if (statusPingTest != "Stop") { - ReQ.abort(); - } - ReQ.open(pingMethod, serverListElm[pingFile] + "?n=" + Math.random(), true); - ReQ.timeout = pingTimeOut; - var startTime = window.performance.now(); - ReQ.send(); - ReQ.onload = function() { - if (this.status === 200 && this.readyState === 4) { - var endTime = Math.floor(window.performance.now() - startTime); - var perfNum = performance.getEntries(); - perfNum = perfNum[perfNum.length - 1]; - var perfPing; - if (perfNum.initiatorType === "xmlhttprequest") { - perfPing = parseFloat(perfNum.duration.toFixed(1)); - } else { - perfPing = endTime; - } - if (pingSamplesSend > 250) { - perfPing = endTime; - } - if (perfPing <= 0) { - statusPing = 0.1; - pingResult.push(0.1); - } else { - statusPing = perfPing; - pingResult.push(perfPing); - } - if (pingResult.length > 1) { - var jitterCalc = Math.abs(pingResult[pingResult.length - 1] - pingResult[pingResult.length - 2]).toFixed(1); - jitterResult.push(parseFloat(jitterCalc)); - statusJitter = jitterCalc; - Show.LiveSpeed(perfPing, "Ping"); - Show.pingResults(perfPing, "Ping"); - Show.jitterResult(jitterCalc, "Jitter"); - } - sendNewPingReq(); - } - if (this.status === 404 && this.readyState === 4) { - pingSamplesSend++; - sendNewPingReq(); - } - }; - ReQ.onerror = function(e) { - pingSamplesSend++; - sendNewPingReq(); - }; - ReQ.ontimeout = function(e) { - pingSamplesSend++; - sendNewPingReq(); - }; - } - PingRequest(); - } - var ServerConnect = function(auth) { - var Self = this; - var xhr = new XMLHttpRequest(); - var url = OpenSpeedTestdb; - if (auth == 1) { - url = webIP; - } - if (auth == 5) { - url = saveDataURL; - } - if (auth == 7) { - url = get_IP; - } - xhr.open("POST", url, true); - xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - xhr.onreadystatechange = function() { - if (xhr.readyState == 4 && xhr.status == 200) { - return_data = xhr.responseText.trim(); - if (auth == 2) { - key = return_data; - } - if (auth == 1) { - TestServerip = return_data; - } - if (auth == 3) { - setTimeout(function() { - location.href = showResult + return_data; - }, 1500); - } - if (auth == 6) { - openSpeedTestServerList = JSON.parse(return_data); - launch = true; - runTasks(); - } - if (auth == 7) { - Show.YourIP.el.textContent = return_data; - } - } - }; - if (auth == 2) { - logData = "r=n"; - } - if (auth == 3) { - logData = "r=l" + "&d=" + downloadSpeed + "&u=" + uploadSpeed + "&dd=" + dataUsedfordl / 1048576 + "&ud=" + dataUsedforul / 1048576 + "&p=" + pingEstimate + "&do=" + myhostName + "&S=" + key + "&sip=" + TestServerip + "&jit=" + jitterEstimate + "&ua=" + userAgentString; - } - if (auth == 5) { - logData = saveTestData; - } - if (auth == 6) { - logData = "r=s"; - } - xhr.send(logData); - }; - }; - OpenSpeedTest.Start = function() { - new openSpeedtestEngine(); - }; -})(window.OpenSpeedTest = window.OpenSpeedTest || {}); diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/js/app-2.5.4.min.js b/data/agent/var/lib/openflare/pages/deployments/3/current/assets/js/app-2.5.4.min.js deleted file mode 100644 index 0df99a1f..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/current/assets/js/app-2.5.4.min.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - Official Website : https://OpenSpeedTest.COM | Email: support@openspeedtest.com - Developed by : Vishnu | https://Vishnu.Pro | Email : me@vishnu.pro - Like this Project? Please Donate NOW & Keep us Alive -> https://go.openspeedtest.com/Donate - Speed Test by OpenSpeedTest™️ is Free and Open-Source Software (FOSS) with MIT License. - Read full license terms @ http://go.openspeedtest.com/License - If you have any Questions, ideas or Comments Please Send it via -> https://go.openspeedtest.com/SendMessage -*/ -window.onload=function(){var X=document.getElementById("OpenSpeedTest-UI");X.parentNode.replaceChild(X.contentDocument.documentElement,X);ostOnload();OpenSpeedTest.Start()}; -(function(X){function h(c){if(!(this instanceof h))return new h(c);this.el=document.getElementById(c)}var m,Ma,Na=function(c){c&&"function"===typeof c&&c()};h.prototype.fade=function(c,d,a){var f="in"===c,t=f?0:1,w=14/d,n=this;f&&(n.el.style.display="block",n.el.style.opacity=t);var q=window.setInterval(function(){t=f?t+w:t-w;n.el.style.opacity=t;0>=t&&(n.el.style.display="none");(0>=t||1<=t)&&window.clearInterval(q,Na(a))},14)};var Oa=function(c,d,a,f){c/=f;c--;return a*(c*c*c+1)+d},r=function(){this.YourIP= -h("YourIP");this.ipDesk=h("ipDesk");this.ipMob=h("ipMob");this.downSymbolDesk=h("downSymbolDesk");this.upSymbolDesk=h("upSymbolDesk");this.upSymbolMob=h("upSymbolMob");this.downSymbolMob=h("downSymbolMob");this.settingsMob=h("settingsMob");this.settingsDesk=h("settingsDesk");this.oDoLiveStatus=h("oDoLiveStatus");this.ConnectErrorMob=h("ConnectErrorMob");this.ConnectErrorDesk=h("ConnectErrorDesk");this.downResult=h("downResult");this.upRestxt=h("upRestxt");this.pingResult=h("pingResult");this.jitterDesk= -h("jitterDesk");this.pingMobres=h("pingMobres");this.JitterResultMon=h("JitterResultMon");this.JitterResultms=h("JitterResultms");this.UI_Desk=h("UI-Desk");this.UI_Mob=h("UI-Mob");this.oDoTopSpeed=h("oDoTopSpeed");this.startButtonMob=h("startButtonMob");this.startButtonDesk=h("startButtonDesk");this.intro_Desk=h("intro-Desk");this.intro_Mob=h("intro-Mob");this.loader=h("loading_app");this.OpenSpeedtest=h("OpenSpeedtest");this.mainGaugebg_Desk=h("mainGaugebg-Desk");this.mainGaugeBlue_Desk=h("mainGaugeBlue-Desk"); -this.mainGaugeWhite_Desk=h("mainGaugeWhite-Desk");this.mainGaugebg_Mob=h("mainGaugebg-Mob");this.mainGaugeBlue_Mob=h("mainGaugeBlue-Mob");this.mainGaugeWhite_Mob=h("mainGaugeWhite-Mob");this.oDoLiveSpeed=h("oDoLiveSpeed");this.progressStatus_Mob=h("progressStatus-Mob");this.progressStatus_Desk=h("progressStatus-Desk");this.graphc1=h("graphc1");this.graphc2=h("graphc2");this.graphMob2=h("graphMob2");this.graphMob1=h("graphMob1");this.text=h("text");this.scale=[{degree:680,value:0},{degree:570,value:.5}, -{degree:460,value:1},{degree:337,value:10},{degree:220,value:100},{degree:115,value:500},{degree:0,value:1E3}];this.polygon=this.chart=this.element="";this.width=200;this.height=50;this.maxValue=0;this.values=[];this.points=[];this.vSteps=5;this.measurements=[];this.points=[]};r.prototype.reset=function(){this.polygon=this.chart=this.element="";this.width=200;this.height=50;this.maxValue=0;this.values=[];this.points=[];this.vSteps=5;this.measurements=[];this.points=[]};r.prototype.ip=function(){"block"=== -this.ipDesk.el.style.display?(this.ipDesk.el.style.display="none",this.ipMob.el.style.display="none"):(this.ipDesk.el.style.display="block",this.ipMob.el.style.display="block")};r.prototype.prePing=function(){this.loader.fade("out",500);this.OpenSpeedtest.fade("in",1E3)};r.prototype.app=function(){this.loader.fade("out",500,this.ShowAppIntro())};r.prototype.ShowAppIntro=function(){this.OpenSpeedtest.fade("in",1E3)};r.prototype.userInterface=function(){this.intro_Desk.fade("out",1E3);this.intro_Mob.fade("out", -1E3,this.ShowUI())};r.prototype.ShowUI=function(){this.UI_Desk.fade("in",1E3);this.UI_Mob.fade("in",1E3,function(c){m="Loaded";console.log("Developed by Vishnu. Email --\x3e me@vishnu.pro")})};r.prototype.Symbol=function(c){0==c&&(this.downSymbolMob.el.style.display="block",this.downSymbolDesk.el.style.display="block",this.upSymbolMob.el.style.display="none",this.upSymbolDesk.el.style.display="none");1==c&&(this.downSymbolMob.el.style.display="none",this.downSymbolDesk.el.style.display="none",this.upSymbolMob.el.style.display= -"block",this.upSymbolDesk.el.style.display="block");2==c&&(this.downSymbolMob.el.style.display="none",this.downSymbolDesk.el.style.display="none",this.upSymbolMob.el.style.display="none",this.upSymbolDesk.el.style.display="none")};r.prototype.Graph=function(c,d){function a(q,M){for(n=f.maxValue=0;nf.maxValue&&(f.maxValue=f.values[n]);f.maxValue=Math.ceil(f.maxValue);if(1=d&&(clearInterval(t),Ma="done",a.progressStatus_Desk.el.style.strokeDashoffset=800,a.progressStatus_Mob.el.style.strokeDashoffset=800)},14)};r.prototype.mainGaugeProgress=function(c){var d=c;0>d&&(d=0);var a=this.getNonlinearDegree(d);0=d&&(this.mainGaugeBlue_Mob.el.style.strokeDashoffset=681.1,this.mainGaugeWhite_Mob.el.style.strokeDashoffset=.1,this.mainGaugeWhite_Desk.el.style.strokeDashoffset=.1,this.mainGaugeBlue_Desk.el.style.strokeDashoffset=681.1)};r.prototype.showStatus=function(c){this.oDoLiveStatus.el.textContent=c};r.prototype.ConnectionError=function(){this.ConnectErrorMob.el.style.display= -"block";this.ConnectErrorDesk.el.style.display="block"};r.prototype.uploadResult=function(c){1>c&&(this.upRestxt.el.textContent=c.toFixed(3));1<=c&&9999>c&&(this.upRestxt.el.textContent=c.toFixed(1));1E4<=c&&99999>c&&(this.upRestxt.el.textContent=c.toFixed(1),this.upRestxt.el.style.fontSize="20px");1E5<=c&&(this.upRestxt.el.textContent=c.toFixed(1),this.upRestxt.el.style.fontSize="18px")};r.prototype.pingResults=function(c,d){var a=c;"Ping"===d&&(1<=a&&1E4>a?(this.pingResult.el.textContent=Math.floor(a), -this.pingMobres.el.textContent=Math.floor(a)):0<=a&&1>a&&(0==a&&(a=0),this.pingResult.el.textContent=a,this.pingMobres.el.textContent=a));"Error"===d&&(this.oDoLiveSpeed.el.textContent=a)};r.prototype.downloadResult=function(c){1>c&&(this.downResult.el.textContent=c.toFixed(3));1<=c&&9999>c&&(this.downResult.el.textContent=c.toFixed(1));1E4<=c&&99999>c&&(this.downResult.el.textContent=c.toFixed(1),this.downResult.el.style.fontSize="20px");1E5<=c&&(this.downResult.el.textContent=c.toFixed(1),this.downResult.el.style.fontSize= -"18px")};r.prototype.jitterResult=function(c,d){var a=c;"Jitter"===d&&(1<=a&&1E4>a?(this.jitterDesk.el.textContent=Math.floor(a),1<=a&&100>a&&(this.JitterResultMon.el.textContent=Math.floor(a)),100<=a&&(a=(a/1E3).toFixed(1),this.JitterResultMon.el.textContent=a+"k")):0<=a&&1>a&&(0==a&&(a=0),this.jitterDesk.el.textContent=a,this.JitterResultMon.el.textContent=a))};r.prototype.LiveSpeed=function(c,d){var a=c;if("countDown"===d){var f=a.toFixed(0);this.oDoLiveSpeed.el.textContent=f}else"speedToZero"=== -d?("number"==typeof a&&(a=a.toFixed(1)),0>=a&&(a=0),this.oDoLiveSpeed.el.textContent=a,this.oDoTopSpeed.el.textContent="1000+",this.oDoTopSpeed.el.style.fontSize="16.9px",this.oDoTopSpeed.el.style.fill="gray"):"Ping"===d?1<=a&&1E4>a?this.oDoLiveSpeed.el.textContent=Math.floor(a):0<=a&&1>a&&(0==a&&(a=0),this.oDoLiveSpeed.el.textContent=a):(0==a&&(f=a.toFixed(0),this.oDoLiveSpeed.el.textContent=f),1>=a&&0=a&&(this.oDoTopSpeed.el.textContent="1000+",this.oDoTopSpeed.el.style.fontSize="16.9px",this.oDoTopSpeed.el.style.fill="gray"),1010<=a&&(this.oDoTopSpeed.el.textContent=1E3*Math.floor(a/1010)+"+",this.oDoTopSpeed.el.style.fill="gray",this.oDoTopSpeed.el.style.fontSize="17.2px"))};r.prototype.GaugeProgresstoZero=function(c,d){var a=this;if(0<=c)var f=Date.now(),t=0-c,w=setInterval(function(){var n=(Date.now()-f)/1E3;var q=n/3;q--;q=t*(q*q*q*q*q+1)+c;a.LiveSpeed(q,"speedToZero");a.mainGaugeProgress(q); -if(3<=n||0>=q)clearInterval(w),a.LiveSpeed(0,"speedToZero"),a.mainGaugeProgress(0),m=d},16)};r.prototype.getNonlinearDegree=function(c){var d=0;if(0==c||0>=c||isNaN(c))return 0;for(;dthis.scale[d].value)d++;else return this.scale[d-1].degree+(c-this.scale[d-1].value)*(this.scale[d].degree-this.scale[d-1].degree)/(this.scale[d].value-this.scale[d-1].value);return this.scale[this.scale.length-1].degree};var O=function(){this.OverAllTimeAvg=window.performance.now();this.SpeedSamples= -[];this.FinalSpeed};O.prototype.reset=function(){this.OverAllTimeAvg=window.performance.now();this.SpeedSamples=[];this.FinalSpeed=0};O.prototype.ArraySum=function(c){return c?c.reduce(function(d,a){if("number"===typeof d&&"number"===typeof a)return d+a},0):0};O.prototype.AvgSpeed=function(c,d,a){this.timeNow=(window.performance.now()-this.OverAllTimeAvg)/1E3;this.FinalSpeed;this.timeNow>=a-d&&(0=l&&(clearInterval(g),G=!0,Z=void 0,d())}Pa&&(x.remEvt(e.settingsMob.el,"click",c),x.remEvt(e.settingsDesk.el,"click",c),x.remEvt(e.startButtonDesk.el,"click",d),x.remEvt(e.startButtonMob.el,"click",d),x.remEvt(document,"keypress",a),Pa=!1);if(0<=Z){G=!1;e.userInterface();ua=!1;var l=Math.ceil(Math.abs(Z));e.showStatus("Automatic Test Starts in ...");var g=setInterval(b,1E3)}"fetch"===openSpeedTestServerList&&!0===G&&(G=!1,e.showStatus("Fetching Server Info.."),Y(6));!0===G&&("Ping"=== -A?f():"Download"===A?f():"Upload"===A?f():!1===A&&f())}function a(b){"Enter"===b.key&&d()}function f(){ua&&(e.userInterface(),ua=!1);t()}function t(){function b(){va=window.performance.now();n()}"widget"!==openChannel&&"web"!==openChannel||Y(1);var l=setInterval(function(){"Loaded"===m&&(m="busy",Q());"Ping"===m&&(m="busy",e.showStatus("Milliseconds"));"Download"===m&&(e.showStatus("Initializing.."),x.reset(),y=ha=0,e.reset(),wa=window.performance.now(),w(),m="initDown");if("Downloading"===m){e.Symbol(0); -if(0==aa){aa=1;e.showStatus("Testing download speed..");var g=(window.performance.now()-wa)/1E3;Qa=g;e.progress(1,dlDuration+2.5);dlDuration+=g}xa=(window.performance.now()-wa)/1E3;q("dl");e.showStatus("Mbps download");e.mainGaugeProgress(y);e.LiveSpeed(y);e.Graph(y,0);R=x.AvgSpeed(y,ya,dlDuration);xa>=dlDuration&&"done"==Ma&&(A?(e.GaugeProgresstoZero(y,"SendR"),e.showStatus("All done"),e.Symbol(2)):e.GaugeProgresstoZero(y,"Upload"),e.downloadResult(R),ia=S,I=1,m="busy",y=ha=0,x.reset())}"Upload"== -m&&1===I&&(e.Symbol(1),m="initup",e.showStatus("Initializing.."),e.LiveSpeed("...","speedToZero"),za=x.uRandom(ulDataSize,b),A&&(aa=1));"Uploading"===m&&(1==aa&&(aa=2,e.showStatus("Testing upload speed.."),y=0,x.reset(),e.reset(),Ra=g=(window.performance.now()-va)/1E3,e.progress(!1,ulDuration+2.5),ulDuration+=g),e.showStatus("Mbps upload"),T=(window.performance.now()-va)/1E3,q("up"),e.mainGaugeProgress(y),e.LiveSpeed(y),e.Graph(y,1),U=x.AvgSpeed(y,Aa,ulDuration),T>=ulDuration&&1==I&&(ja=P,e.uploadResult(U), -e.GaugeProgresstoZero(y,"SendR"),za=void 0,e.showStatus("All done"),e.Symbol(2),m="busy",I=0));if("Error"===m){e.showStatus("Check your network connection status.");e.ConnectionError();m="busy";clearInterval(l);g=document.createElement("div");g.innerHTML='';var u=g.querySelector("a");e.oDoLiveSpeed.el.textContent="Network Error";var C=document.getElementById("oDoLiveSpeed");u.innerHTML=C.innerHTML; -C.innerHTML=g.innerHTML}"SendR"===m&&(e.showStatus("All done"),g=document.createElement("div"),g.innerHTML='',u=g.querySelector("a"),e.oDoLiveSpeed.el.textContent=fb,C=document.getElementById("oDoLiveSpeed"),u.innerHTML=C.innerHTML,C.innerHTML=g.innerHTML,location.hostname!=Ba.toLowerCase()+Sa?(ba="https://"+Ba.toLowerCase()+Sa+"/results/show.php?&d="+R.toFixed(3)+"&u="+U.toFixed(3)+"&p="+ -Ca+"&j="+Da+"&dd="+(ia/1048576).toFixed(3)+"&ud="+(ja/1048576).toFixed(3)+"&ua="+Ta,ba=encodeURI(ba),g=document.getElementById("resultsData"),g.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",ba),g.setAttribute("target","_blank"),saveData&&Y(5)):Y(3),m="busy",clearInterval(l))},100)}function w(){for(var b=0;b1E3*Qa+ya/2*1E3&&0===Ua&&(Ua=1,ka*=.01,ca*=.01,la=l+1E4);l>=la&&la=S?0:S-Wa;Wa=S;ca+=Va;Xa=Ea=l-Ea;Ea=l;ka+=Xa;01E3*Ra+Aa/2*1E3&&0===Ya&&(Ya=1,ma*=.1,da*=.1,na=b+1E4),b>=na&&na=P?0:P-$a,$a=P,da+=Za,ab=Fa=b-Fa,Fa=b,ma+=ab,0=g.loaded?0:g.loaded-l;if(isNaN(u)||!isFinite(u)||0>u)return!1;S+=u;l=g.loaded};z[b].onload=function(g){0===l&&(S+=g.total);"initDown"==m&&(m="Downloading");z[b]&&(z[b].abort(),z[b]=null,z[b]=void 0,delete z[b]);0===I&&M(b)};z[b].onerror=function(g){0===I&&M(b)};z[b].responseType="arraybuffer";z[b].send()}function F(b){var l=0;v[b]=new XMLHttpRequest;v[b].open("POST",Ga.Upload+"?n="+Math.random(),!0); -v[b].upload.onprogress=function(g){"initup"==m&&void 0===u&&(m="Uploading");if(T>=ulDuration)return v[b].abort(),v[b]=null,v[b]=void 0,delete v[b],!1;var u=0>=g.loaded?0:g.loaded-l;if(isNaN(u)||!isFinite(u)||0>u)return!1;P+=u;l=g.loaded};v[b].onload=function(){if(0===l&&(P+=1048576*ulDataSize,T>=ulDuration))return v[b].abort(),v[b]=null,v[b]=void 0,delete v[b],!1;if("initup"==m&&void 0===g){var g;m="Uploading"}v[b]&&(v[b].abort(),v[b]=null,v[b]=void 0,delete v[b]);1===I&&F(b)};v[b].onerror=function(g){T<= -ulDuration&&F(b)};v[b].setRequestHeader("Content-Type","application/octet-stream");0=P||v[b].send(za)}function Q(){bb=openSpeedTestServerList.length;m="Ping";performance.clearResourceTimings();if(Ha=B?J.push(.1):J.push(B);1=ra&&(ulThreads=dlThreads=ra)}if(selectServer&&("string"===typeof k.host||"string"===typeof k.h)){if("undefined"!==typeof k.host)var sa=k.host;else"undefined"!==typeof k.h&&(sa=k.h);/(?:https?):\/\/(\w+:?\w*)?(\S+)(:\d+)?(\/|\/([\w#!:.?+=&%!\-\/]))?/.test(sa)&& -(openSpeedTestServerList=[{ServerName:"Home",Download:sa+"/downloading",Upload:sa+"/upload",ServerIcon:"DefaultIcon"}])}E=parseInt(k.stress);W=parseInt(k.s);if("string"===typeof k.stress){var p=k.stress;var H=E}else"string"===typeof k.s&&(p=k.s,H=W);if(p&&stressTest){if("low"===p||"l"===p)ulDuration=dlDuration=300;if("medium"===p||"m"===p)ulDuration=dlDuration=600;if("high"===p||"h"===p)ulDuration=dlDuration=900;if("veryhigh"===p||"v"===p)ulDuration=dlDuration=1800;if("extreme"===p||"e"===p)ulDuration= -dlDuration=3600;if("day"===p||"d"===p)ulDuration=dlDuration=86400;if("year"===p||"y"===p)ulDuration=dlDuration=31557600;if(12p||5>H)upAdjust=1+E/100,dlAdjust=1+E/100}else dlAdjust=upAdjust=1;p=parseInt(k.run);H=parseInt(k.r);var Z;!enableRun||"string"!==typeof k.run&&"string"!==typeof k.r||(Z=0',createCookie("mode","dark")));"light"===a&&(nightModeMob.style.display="none",dayModeMob.style.display="inline-block",nightMode.style.display="none", -dayMode.style.display="inline-block",(darkStyle=document.getElementById("darkmode"))&&darkStyle.parentNode.removeChild(darkStyle),createCookie("mode","light"))}function toggleSkin(){(darkStyle=document.getElementById("darkmode"))?setSkin("light"):setSkin("dark")}function createCookie(a,c,b){if(b){var d=new Date;d.setTime(d.getTime()+864E5*b);b="; expires="+d.toGMTString()}else b="";document.cookie=a+"="+c+b+"; path=/"} -function getCookieValue(a,c){return(c=document.cookie.match("(^|;)\\s*"+a+"\\s*=\\s*([^;]+)"))?c.pop():""}; \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/downloading b/data/agent/var/lib/openflare/pages/deployments/3/current/downloading deleted file mode 100644 index b78c32fa..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/current/downloading and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/hosted.html b/data/agent/var/lib/openflare/pages/deployments/3/current/hosted.html deleted file mode 100644 index ea51ac3b..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/current/hosted.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - Speedtest by OpenSpeedtest.com - - - - -
- Speed Test by OpenSpeedTest™ -
- - \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/index.html b/data/agent/var/lib/openflare/pages/deployments/3/current/index.html deleted file mode 100644 index 2772b6b6..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/current/index.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - SpeedTest by OpenSpeedTest™ - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
- - -
- SpeedTest by OpenSpeedTest™ is a Free and Open-Source HTML5 Network Speed Test Software. -

© Copyright 2013-2024 OpenSpeedTest™ All Rights Reserved.

-
- - - - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/3/current/upload b/data/agent/var/lib/openflare/pages/deployments/3/current/upload deleted file mode 100644 index e69de29b..00000000 diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/.gitignore b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/.gitignore deleted file mode 100644 index e43b0f98..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.DS_Store diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/.openflare-pages.json b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/.openflare-pages.json deleted file mode 100644 index 8b73ac2e..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/.openflare-pages.json +++ /dev/null @@ -1 +0,0 @@ -{"deployment_id":3,"checksum":"4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6"} \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/License.md b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/License.md deleted file mode 100644 index b93a9207..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/License.md +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 OpenSpeedTest™ - -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 permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/README.md b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/README.md deleted file mode 100644 index 17d89eeb..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/README.md +++ /dev/null @@ -1,506 +0,0 @@ -# **[SpeedTest by OpenSpeedTest™](https://openspeedtest.com?Run&ref=Github)** - Free & Open-Source HTML5 Network Performance Estimation Tool. - - - -SpeedTest by OpenSpeedTest™ is a Free and Open-Source HTML5 Network Performance Estimation Tool Written in Vanilla Javascript and only uses built-in Web APIs like `XMLHttpRequest` `(XHR)`, `HTML`, `CSS`, `JS`, & `SVG`. No Third-Party frameworks or libraries are Required. All we need is a static web server like `NGINX`. I started this project in 2011 and moved to OpenSpeedTest.com dedicated Project/Domain Name in 2013. - ---- -Just launched: OpenPacketLoss. Check it out! - -While OpenSpeedTest measures bandwidth, this tool helps you detect packet loss directly from your browser. - -**Try it:** https://openpacketloss.com -**Source:** https://github.com/openpacketloss - ---- - - -[![Download OpenSpeedTest-Server V2.1](https://github.com/openspeedtest/v2-Test/raw/main/images/10G-S.gif)](https://go.openspeedtest.com/Server "Download OpenSpeedTest-Server V2.1") - Download from the Microsoft Store Download from the Mac App Store Download from the App Store Download from the Google Play Store Download from the Snap Store Download from the Docker Hub Download from the Helm Store Download from GitHub - -**No client-side software or plugin is required. You can run a network speed test from any device with a [Web Browser that is IE10 or newer.](https://www.youtube.com/watch?v=9f-OM_WQ7Bw&list=PLt-deStxFJOMEAs2O1lJhscMNzcg9E3Po&index=1)** - - Video Tutorial by Roberto Jorge Tech - Video Tutorial by LanPad - -## Why OpenSpeedTest - - - -### Secure by Design. - - - -OpenSpeedTest contains Only `STATIC` Files like `HTML`,`CSS` & `JS`. - -So you don't need to worry about Security Updates or Hidden Exploits that may compromise your secure environments. - - - -### Lightweight, High Performance. - - - -OpenSpeedTest is written in Vanilla JavaScript. No Third-Party frameworks or libraries were used. SpeedTest script file size is under 8kB gzip. The unexpected side effect of using Vanilla JavaScript is High Performance. - - - -### Run a speed test from Any Device. - - - -OpenSpeedTest will run on Any Web Browser that is IE10 or newer. - - - -### Ready for Any Display Size and Resolution. - - - -OpenSpeedTest User interface is written in SVG. - - - -# Create Your Own SpeedTest Server. - -### Server Requirements : - -`Nginx`, `Apache`, `IIS`, `Express`, or Any Web server that supports `HTTP/1.1` or newer. - -- Accept, `GET`, `POST`, `HEAD` & `OPTIONS`, Response `200 OK`. - -- Accept, `POST` to Static Files, Response `200 OK`. - -- `client_max_body_size`, 35 Megabytes or more. - -- Timeout greater than `60 seconds`. - -- Disable `Access logs` for Increasing server performance. - -- Improve `Time to First Byte` (TTFB) - -- Warning! If you run it behind a **[Reverse Proxy](https://github.com/openspeedtest/Speed-Test/issues/4#issuecomment-1229157193)**, you should increase the `post-body content length` to 35 megabytes. -- Supports `HTTP2` & `HTTP3`. -- `HTTP1.1` is recommended for achieving maximum performance. -- **[You Should Follow our Nginx Config.](https://github.com/openspeedtest/Nginx-Configuration)** - - - -# Or, You can use OpenSpeedTest-Server. - -OpenSpeedTest-Server is available for Windows, Mac, Linux, Android, iOS & Docker. -[![Download OpenSpeedTest-Server V2.1](https://open.cachefly.net/assets/images/OSTV2-SS.png)](https://go.openspeedtest.com/Server "Download OpenSpeedTest-Server V2.1") -#### Fully Optimized and ready to use applications. - Download from the Microsoft Store Download from the Mac App Store Download from the App Store Download from the Google Play Store Download from the Snap Store Download from the Docker Hub Download from the Helm Store Download from GitHub - - -### New features: - - - -1. Stress Test. (Continuous Speed Test) - - - -To enable the stress test. Pass `Stress` or `S` keyword as a URL parameter. - - - -```` - -http://192.168.1.5?Stress=Low - -```` - -After the `STRESS` or `S` keyword, you can specify the number of seconds you need to run the StressTest in seconds, or preset values such as `Low`, `Medium`, `High`, `VeryHigh`, `Extreme`, `Day`, and `Year`. Will run a speed test for `300`,`600`,`900`,`1800`,`3600`,`86400`,`31557600` seconds, respectively. Also, you can feed the first letter of each parameter and its values. - - -```` - -http://192.168.1.5?S=L - -```` - -`S=L` is the same as passing `Stress=low` - -Or you can specify the number of seconds eg:`5000` directly without any preset keywords. - - - -```` - -http://192.168.1.5?Stress=5000 - -```` - - - -2. Run a speed test automatically - - - -Run a speed test automatically on page load. - -```` - -http://192.168.1.5?Run - -```` - -Run a speed test automatically after a few seconds. - -```` - -http://192.168.1.5?Run=10 or http://192.168.1.5?R=10 - -```` - - - -You can pass multiple keywords, and it's not `Case-Sensitive`. - - - -```` - -http://192.168.1.5?Run&Stress=300 OR http://192.168.1.5?R&S=300 - -```` - -This will start a speed test immediately and run for `300 seconds` in each direction. That is 300 seconds for download and `300 seconds` for upload. - - - -3. Save results to a Database - - - -Edit `Index.html` - -```` - -var saveData = true; - -var saveDataURL = "//yourDatabase.Server.com:4500/save?data="; - -```` - -4. Add multiple servers. The app will choose one with the least latency automatically. - - - -Edit `Index.html` - -```` - -var openSpeedTestServerList = [ - -{"ServerName":"Home-Earth", "Download":"/downloading", "Upload":"/upload", "ServerIcon":"DefaultIcon"}, - -{"ServerName":"Home-Mars", "Download":"/downloading", "Upload":"/upload", "ServerIcon":"DefaultIcon"}, - -{"ServerName":"Home-Moon", "Download":"/downloading", "Upload":"/upload", "ServerIcon":"DefaultIcon"} - -]; - -```` - -5. Disable or change Overhead Compensation factor. - - - -```` - -http://192.168.1.5?clean - -```` - -Overhead Compensation factor, This is browser based test, Many Unknowns. Currently 4%. That is within the margin of error. - -You can pass `Clean` or `C` as a URL Parameter and reset Overhead Compensation factor to Zero or set any value between 0 and 4. 1 = 1% to 4 = 4%. - -`Clean` will not accept values above 4, so Compensation is limited to maximum 4%. - - - -6. Change the default limit of 6 parallel HTTP connections to the Server. - - - -```` - -http://192.168.1.5?XHR=3 OR http://192.168.1.5?X=3 - -```` - -Allow the user to Change the default limit of 6 parallel HTTP connections to the Server. `XHR` will Accept values above 1 and max 32 - -pass `XHR` or `X` as a URL Parameter. - - - -7. Select a different server to run a speed test. - - - -```` - -http://192.168.1.5?Host=http://192.168.55.1:90 OR http://192.168.1.5?h=http://192.168.55.1:90 - -```` - -Pass `Host` or `H` as a URL Parameter. - -`HOST` will Accept only valid HTTP URLs like `http://192.168.1.10:3000` or `https://yourHost.com`. - - - -8. Select and run one test at a time, `DOWNLOAD`, `UPLOAD`, or `PING`. - - - -```` - -http://192.168.1.5?Test=Upload OR http://192.168.1.5?T=U - -```` - -`TEST` Allow the user to select and run one test at a time, Download, Upload, or Ping. - -Pass `Test` or `T` as a URL Parameter. - - - -9. Set a PingTimeout dynamically by passing `Out` or `O` as a URL Parameter - - - -```` - -http://192.168.1.5?Out=7000 OR http://192.168.1.5?O=7000 - -```` - -If Server not responded within 5 Seconds for any requests we send ('pingSamples' times) - -We will show `Network Error`, You can change the limit here. - -In milliseconds, if you need to set `6 seconds`. Change the value to `6000`. - - - -10. Set the Number of ping samples by adding `Ping` or `P` as a URL Parameter - - - -```` - -http://192.168.1.5?Ping=500 OR http://192.168.1.5?P=500 - -```` - -More samples = more accurate representation. `Ping = 500` will send `501` requests to server to find the accurate ping value. -Take a look at index.html, you can set a custom ping sample size, threads, upload data size etc. - -## Self-hosted (On-Premise) / (Docker Image/Source Code) -### For Headless large-scale deployments. -You have two options here. If you need a custom deployment, use our source code along with a web server of your choice. I prefer Nginx, and you can find my [Nginx Configuration](https://github.com/openspeedtest/Nginx-Configuration) here. Or you can use our docker image. You can deploy it on your LAN/WAN with or without an active internet connection. - -**This is docker implementation using nginxinc/nginx-unprivileged:stable-alpine. uses significantly fewer resources.** - -- NGINX Docker image that runs NGINX as a non root, unprivileged user. - - ### Docker install instructions: - - Install Docker and run the following command! - -````bash - -sudo docker run --restart=unless-stopped --name openspeedtest -d -p 3000:3000 -p 3001:3001 openspeedtest/latest - -```` -#### Or use docker-compose.yml -```` -version: '3.3' -services: - speedtest: - restart: unless-stopped - container_name: openspeedtest - ports: - - '3000:3000' - - '3001:3001' - image: openspeedtest/latest -```` -- Warning! If you run it behind a **[Reverse Proxy](https://github.com/openspeedtest/Speed-Test/issues/4#issuecomment-1229157193)**, you should increase the `post-body content length` to 35 megabytes. - -- **[Follow our Nginx Config.](https://github.com/openspeedtest/Nginx-Configuration)** - -Now open your browser and direct it to: - -A: For **HTTP** use: `http://YOUR-SERVER-IP:3000` - -B: For **HTTPS** use: `https://YOUR-SERVER-IP:3001` - -#### Container-Port for http is 3000 -If you need to run this image on a different port for `HTTP`, Eg: change to `80` = `-p 80:3000` -#### Container-Port for https is 3001 -If you need to run this image on a different port for `HTTPS`, Eg: change to `443` = `-p 443:3001` - -### Setup Free LetsEncrypt SSL with Automatic Certificate Renewal -***Requirements*** -- PUBLIC IPV4 and/or IPV6 address. -- A domain name that resolves to speed test server's IP address. -- Email ID - -The following command will generate a Let's Encrypt certificate for your domain name and configure a cron job to automatically renew the certificate. - -```` -docker run -e ENABLE_LETSENCRYPT=True -e DOMAIN_NAME=speedtest.yourdomain.com -e USER_EMAIL=you@yourdomain.pro --restart=unless-stopped --name openspeedtest -d -p 80:3000 -p 443:3001 openspeedtest/latest -```` -#### Or use docker-compose.yml -```` -version: '3.3' -services: - speedtest: - environment: - - ENABLE_LETSENCRYPT=True - - DOMAIN_NAME=speedtest.yourdomain.com - - USER_EMAIL=you@yourdomain.pro - restart: unless-stopped - container_name: openspeedtest - ports: - - '80:3000' - - '443:3001' - image: openspeedtest/latest -```` - -### How to Use Your Own Secure Sockets Layer (SSL) Certificate, Self-Signed or Paid? -***Requirements*** -- Folder with your Certificate, Self-Signed or Paid. -- Rename .cet file and .key file to `nginx.crt` & `nginx.key` - - The folder needs to contain: - -- `nginx.crt` - -- `nginx.key` - - -```` -sudo docker run --restart=unless-stopped --name openspeedtest -d -p 3000:3000 -p 3001:3001 openspeedtest/latest -```` - -To mount a folder with your own SSL certificate to this Docker container, append the following line to the above command: - - -````bash - --v /${PATH-TO-YOUR-OWN-SSL-CERTIFICATE}:/etc/ssl/ - -```` - -I am adding a folder with nginx.crt and nginx.key from my desktop by using the following command. - -````bash - -sudo docker run -v /Users/vishnu/Desktop/docker/:/etc/ssl/ --restart=unless-stopped --name openspeedtest -d -p 3000:3000 -p 3001:3001 openspeedtest/latest - -```` -#### Or use docker-compose.yml -```` -version: '3.3' -services: - speedtest: - volumes: - - '/Users/vishnu/Desktop/docker/:/etc/ssl/' - restart: unless-stopped - container_name: openspeedtest - ports: - - '3000:3000' - - '3001:3001' - image: openspeedtest/latest -```` -## Advanced Configuration Options - -- Container Port Configuration - -To enable port changes, set the `CHANGE_CONTAINER_PORTS` environment variable to `"True"` and provide appropriate values for the following variables. - -`CHANGE_CONTAINER_PORTS=True` - -`HTTP_PORT=3000` - -`HTTPS_PORT=3001` - -- Set User - -`SET_USER=101` - -- Only Allow `CORS Request` from listed domains. - -`ALLOW_ONLY=domain1.com;domain2.com;domain3.com` - -- `SET_SERVER_NAME` Display the server name on the UI. - -`SET_SERVER_NAME=HOME-NAS` - - -Docker images run better on Linux Platforms, including your NAS. But if you install docker on macOS or Windows, you may see poor performance. I asked this on Docker forums, and they told me macOS and Windows support is for Development purposes only. For Production, you need to use any Linux Platform. - -The same Story goes for Windows NGINX. Nginx uses only one worker even if you specify n number of worker processes. They will show in Task Manager, but the system will only use one. I got this information directly from the Nginx website. - - - - - -## Why do you need to Create Your Own SpeedTest Server? - -You can run OpenSpeedTest Server in your Home Lab, Office Server or Cloud Server. So that you or employees who work from home can run a speed test to your office and make sure they can run everything smoothly. - - - -**Choosing between ISP1 & ISP2.** - - - -Sometimes your ISP2 is Faster than ISP1 when you test your speed on popular speed test sites. But when you connect to your Home/Office/Cloud, that slower connection may perform better. The only way to find out is to run a speed test against your infrastructure. - - - -**Troubleshooting network issues.** - - - -It is common even when your Internet connection is working fine, but some of the devices in your network may experience trouble getting decent connectivity to the internet. The issue might be the wrong VLAN ID or Faulty Switch. If you run a Local network speed test, you can find and fix these issues easily. - - - -**Before you add a repeater.** - - - -Most repeaters will reduce your network speed by 50%, so if you put it far away, it will perform worst, and if you put it too close, you will not get enough coverage if you run a Local Network speed test. Depending on the application requirements, you can decide exactly where you need to put your repeater. - - - -**Browsing experience.** - - - -Many useful browser extensions are out there that we all know and love. But some of them are really slowing you down for **few seconds per page you visit**. You may see good performance when you test your network performance via File Transfer or Command-line utilities, but you may experience poor performance when browsing the internet. This is due to a bad browser configuration that including unwanted extensions installed. From my experience, only keep the one you are going to use every single day. Extension that you may use once in a while should be removed or disabled for maximum performance. If you see poor performance, try OpenSpeedTest from Private Window or Incognito Window. **This tool can be used to check the browser performance and impact of Extensions on your browsing experience.** - - - -**No client-side software or plugin is required. You can run a network speed test from any device with a [Web Browser that is IE10 or newer.](https://www.youtube.com/watch?v=9f-OM_WQ7Bw&list=PLt-deStxFJOMEAs2O1lJhscMNzcg9E3Po&index=1)** - -- Like this Project? Please **Donate NOW & Keep us Alive** -> https://go.openspeedtest.com/Donate - -MIT License - -Copyright (c) 2013 - 2023 OpenSpeedTest™ - -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 permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/css/app.css b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/css/app.css deleted file mode 100644 index 39fa9393..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/css/app.css +++ /dev/null @@ -1,382 +0,0 @@ -/* roboto-regular - latin */ -@font-face { - font-family: "Roboto"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("..fonts/roboto-v30-latin-regular.eot"); /* IE9 Compat Modes */ - src: local(""), - url("../fonts/roboto-v30-latin-regular.eot?#iefix") - format("embedded-opentype"), - /* IE6-IE8 */ url("../fonts/roboto-v30-latin-regular.woff2") - format("woff2"), - /* Super Modern Browsers */ - url("../fonts/roboto-v30-latin-regular.woff") format("woff"), - /* Modern Browsers */ url("../fonts/roboto-v30-latin-regular.ttf") - format("truetype"), - /* Safari, Android, iOS */ - url("../fonts/roboto-v30-latin-regular.svg#Roboto") format("svg"); /* Legacy iOS */ -} -/* roboto-500 - latin */ -@font-face { - font-family: "Roboto"; - font-style: normal; - font-weight: 500; - font-display: swap; - src: url("assets/fonts/roboto-v30-latin-500.eot"); /* IE9 Compat Modes */ - src: local(""), - url("assets/fonts/roboto-v30-latin-500.eot?#iefix") - format("embedded-opentype"), - /* IE6-IE8 */ url("../fonts/roboto-v30-latin-500.woff2") - format("woff2"), - /* Super Modern Browsers */ url("../fonts/roboto-v30-latin-500.woff") - format("woff"), - /* Modern Browsers */ url("../fonts/roboto-v30-latin-500.ttf") - format("truetype"), - /* Safari, Android, iOS */ - url("../fonts/roboto-v30-latin-500.svg#Roboto") format("svg"); /* Legacy iOS */ -} - -body { - margin: 0px; - padding: 0px; - display: block; -} - -::-webkit-scrollbar { - display: none; -} -html { - -ms-overflow-style: none; - scrollbar-width: none; -} - -.Credits { -color: rgb(125 119 119); -text-align: center; -font-size:14px; -font-family: Roboto-Medium, Roboto; -font-weight: 500; -} -.Credits a { - text-decoration: none; - color: rgb(113, 113, 113); -} -.Credits a:hover { -color: #14b0fe; -} - -.ConnectError { - display: none; - -} - -.openSpeedtestApp { - - height: 100vh; - width: 100vw; - display: none; - overflow: hidden; -} -.main-Gaugebg { - fill: none; - stroke: rgb(231, 231, 232); - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 22px; - stroke-dasharray: 681; -} -.main-GaugeBlue { - fill: none; - stroke: url(#gradient); - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 22px; - stroke-dasharray: 681; - stroke-opacity: 0; -} -.main-GaugeWhite { - fill: none; - stroke: rgb(255, 255, 255); - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 15px; - stroke-dasharray: 0, 681; - stroke-dashoffset: 1; - stroke-opacity: 0; -} -.oDo-Meter { - font-size: 16.633283615112305px; - fill: gray; - font-family: Roboto-Medium, Roboto; - font-weight: 500; -} -.oDoLive-Speed { - font-size: 28px; - fill: #201e1e; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} - -.oDoLive-Status { - font-size: 10px; - fill: #d2d1d2; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} -.uiBg { - fill: #d2d1d2; -} -.progressbg { - stroke: rgb(231, 231, 232); - stroke-width: 8px; - stroke-linecap: round; - stroke-linejoin: round; - stroke-dasharray: 400; - stroke-dashoffset: 0; -} -.Cards { - fill: #f2f2f2; -} -.Symbol { - fill: url(#gradient); -} -.rtext { - font-size: 12px; - fill: #333; - font-family: Roboto-Medium, Roboto; - font-weight: 500; -} -.rtextnum { - font-size: 23px; - fill: #201e1e; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} -.rtextmbms { - font-size: 12px; - fill: #5f5f5f; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} -.jitter-Mob { - font-size: 9px; - fill: #5f5f5f; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} -.startButton { - fill: url(#RadialGradient1); - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - -webkit-tap-highlight-color: transparent; - cursor: pointer; - pointer-events: visible; -} -.buttonTxt { - font-size: 40px; - fill: #ffffff; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; -} - -.intro-Progress { - stroke: #56c4fb; - stroke-width: 8px; - stroke-linecap: round; - stroke-linejoin: round; - stroke-dasharray: 400; - stroke-dashoffset: 0; -} -.progressElmstart { - stroke: #56c4fb; - stroke-width: 8px; - stroke-linecap: round; - stroke-linejoin: round; - stroke-dasharray: 400; - stroke-dashoffset: 0; - display: block; -} -.Startsettings { - fill: url(#RadialGradient1); - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - -webkit-tap-highlight-color: transparent; - cursor: pointer; - pointer-events: visible; - opacity: 0.1; - transition: opacity 1s ease-in-out; -} -.Startsettings:hover { - opacity: 1; -} - -.progressbg { - stroke: rgb(231, 231, 232); - stroke-width: 8px; - stroke-linecap: round; - stroke-linejoin: round; - stroke-dasharray: 400; - stroke-dashoffset: 0; -} -.deskStart { - fill: none; - stroke: rgb(231, 231, 232); - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 22px; - stroke-dasharray: 681; - stroke: url(#gradient); -} -#UI-Desk { - display: none; -} -#UI-Mob { - display: none; -} -.oDoTop-Speed { - font-size: 16.96px; - fill: gray; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: end; -} -#upSymbolDesk { - fill: #14b0fe; - display: none; -} -#downSymbolDesk { - fill: #14b0fe; - display: none; -} -#upSymbolMob { - fill: #14b0fe; - display: none; -} -#downSymbolMob { - fill: #14b0fe; - display: none; -} - -#ipMob { - font-size: 15px; - fill: #201e1e; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; - display: none; -} -#ipDesk { - font-size: 15px; - fill: #201e1e; - font-family: Roboto-Medium, Roboto; - font-weight: 500; - text-anchor: middle; - display: none; -} - -.spinner { - position: absolute; - z-index: 999; - top: 50vh; - left: 50vw; - text-align: center; -} - -.spinner > div { - width: 20px; - height: 20px; - background-color: #2196f3; - - border-radius: 100%; - display: inline-block; - -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both; - animation: sk-bouncedelay 1.4s infinite ease-in-out both; -} - -.spinner .bounce1 { - -webkit-animation-delay: -0.32s; - animation-delay: -0.32s; -} - -.spinner .bounce2 { - -webkit-animation-delay: -0.16s; - animation-delay: -0.16s; -} - -@-webkit-keyframes sk-bouncedelay { - 0%, - 80%, - 100% { - -webkit-transform: scale(0); - } - 40% { - -webkit-transform: scale(1); - } -} - -@keyframes sk-bouncedelay { - 0%, - 80%, - 100% { - -webkit-transform: scale(0); - transform: scale(0); - } - 40% { - -webkit-transform: scale(1); - transform: scale(1); - } -} - -.darkmode { - margin-bottom: -15px; - fill: #75757a99; - padding-top: 16px; - display: none; - cursor: pointer; - margin-right: 30px; -} -#daymode { - margin-right: 40px; -} -.darkmode:hover { - fill: #000000; -} - -.Mobile, -.Desktop { - visibility: hidden; - width: 100vw; - height: 100vh; -} - -@media only screen and (orientation: landscape) { - .Mobile { - visibility: hidden; - } - .Desktop { - visibility: visible; - } -} - -@media only screen and (orientation: portrait) { - .spinner { - top: 42vh; - left: 42vw; - } - .Mobile { - visibility: visible; - } - .Desktop { - visibility: hidden; - } -} -@media only screen and (max-width: 300px) { - .Credits{ - display: none; - } -} diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/css/darkmode.css b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/css/darkmode.css deleted file mode 100644 index 14fef300..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/css/darkmode.css +++ /dev/null @@ -1,45 +0,0 @@ -body { - background-color: #181818; -} -#ipDesk { - fill: aliceblue; -} - -.oDoLive-Speed { - fill: #ffffff; -} -.oDoLive-Status { - fill: aliceblue; -} -#ipMob { - fill: aliceblue; -} -.rtextnum { - fill: #ffffff; -} -.rtextmbms { - fill: #ffffff; -} -.rtext { - fill: #ffffff; -} -.Cards { - fill: #000000; -} - -.main-Gaugebg { - stroke: #000000; -} -.uiBg { - fill: #000000; -} -.progressbg { - stroke: #202020; -} -.jitter-Mob { - fill: #ffffff; -} - -.ConnectError { - fill: #ffffff; -} diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.eot b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.eot deleted file mode 100644 index fec03612..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.eot and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.svg b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.svg deleted file mode 100644 index 67eecf44..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.svg +++ /dev/null @@ -1,305 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.ttf b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.ttf deleted file mode 100644 index 4f515e2a..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.ttf and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.woff b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.woff deleted file mode 100644 index d39bb52a..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.woff and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.woff2 b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.woff2 deleted file mode 100644 index 29342a8d..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-500.woff2 and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.eot b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.eot deleted file mode 100644 index c244b089..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.eot and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.svg b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.svg deleted file mode 100644 index 627f5a36..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.svg +++ /dev/null @@ -1,308 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.ttf b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.ttf deleted file mode 100644 index d0e63254..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.ttf and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.woff b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.woff deleted file mode 100644 index 9eaa94f9..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.woff and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.woff2 b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.woff2 deleted file mode 100644 index 020729ef..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/fonts/roboto-v30-latin-regular.woff2 and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/app.svg b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/app.svg deleted file mode 100644 index 01441ac0..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/app.svg +++ /dev/null @@ -1,459 +0,0 @@ - - - - - - - - - - - - - 0 - .5 - 1 - 10 - 100 - 500 - 1000+ - ... - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DOWNLOAD - - - - - UPLOAD - - - - - - - - - - PING - - - - JITTER - - - --- - Mbps - - - --- - Mbps - - - -- - ms - - - -- - ms - - - - - - - - - -- - Jitter - -- - ms - - - - - - - - - - - - Start - - - - - - - - - - - - - - - - - - - Network Error - - - - SpeedTest by OpenSpeedTest™️ - - - - - - - - - - - - - - - - - - - - - HTML5 Network Performance Estimation Tool. by OpenSpeedTest™ - - - - - - The download speed is how fast you can pull data from the server to you in the form of images, videos, text and more. Activities such as listening to music on Spotify, downloading large files or streaming videos on Netflix all require you to download data. - - - The upload speed is how fast you send data from you to others in the form of images, videos, text and more. Activities such as uploading to YouTube or Sending email, playing live games like PubG, Voice and Video calling a friend require fast upload speeds for you to send data to someone else’s server. - - - Ping "Round-trip time" is more or less well defined as the network delay from point A to B and back. This is the sum of all encoding, queueing, processing, decoding, and propagation delays in both directions. Essentially, it's the delay when A may be expecting an answer from B for a request that requires very little processing. A fast ping means a more responsive connection, especially in applications where timing is everything (like video games). Ping is measured in Milliseconds (ms). Jitter is simply the difference in Ping "Round-trip time" . In other words, jitter is measuring time difference in packet inter-arrival time. Jitter helps diagnose internet connection stability. The higher the jitter value, the worse the stability of the connection. - - - - - - - - - Download speed is measured in megabits per second (Mbps). - - - Upload speed is measured in megabits per second (Mbps). - - - - - Ping is measured in Milliseconds (ms). - - - Jitter is measured in Milliseconds (ms). - - - - - Shows you real-time network traffic graph for Upload! - - - Shows you real-time network traffic graph for Download! - - - Your download speed! - - - Your upload speed! - - - Your ping! - - - Your jitter! - - - - - - - - - - Download speed is measured in megabits per second (Mbps). - - - Upload speed is measured in megabits per second (Mbps). - - - - - Ping is measured in Milliseconds (ms). - - - Jitter is measured in Milliseconds (ms). - - - - - Shows you real-time network traffic graph for Upload! - - - Shows you real-time network traffic graph for Download! - - - Your download speed! - - - Your upload speed! - - - Your ping! - - - Your jitter! - - - - - Click here to Run a Speed Test! Or Press "Enter" - - - Options - - - - - - HTML5 Network Performance Estimation Tool. by OpenSpeedTest™ - - - - - - The download speed is how fast you can pull data from the server to you in the form of images, videos, text and more. Activities such as listening to music on Spotify, downloading large files or streaming videos on Netflix all require you to download data. - - - The upload speed is how fast you send data from you to others in the form of images, videos, text and more. Activities such as uploading to YouTube or Sending email, playing live games like PubG, Voice and Video calling a friend require fast upload speeds for you to send data to someone else’s server. - - - Ping "Round-trip time" is more or less well defined as the network delay from point A to B and back. This is the sum of all encoding, queueing, processing, decoding, and propagation delays in both directions. Essentially, it's the delay when A may be expecting an answer from B for a request that requires very little processing. A fast ping means a more responsive connection, especially in applications where timing is everything (like video games). Ping is measured in Milliseconds (ms). Jitter is simply the difference in Ping "Round-trip time" . In other words, jitter is measuring time difference in packet inter-arrival time. Jitter helps diagnose internet connection stability. The higher the jitter value, the worse the stability of the connection. - - - - - - - - - Download speed is measured in megabits per second (Mbps). - - - Upload speed is measured in megabits per second (Mbps). - - - Ping is measured in Milliseconds (ms). - - - - - - Shows you real-time network traffic graph for Download! - - - Upload speed is measured in megabits per second (Mbps). - - - Your ping! and Jitter - - - Your upload speed! - - - Your download speed! - - - - - - - - - Click here to run a speed! - - - Options - - - - - - - - - - - - - - \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/android-chrome-192x192.png b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/android-chrome-192x192.png deleted file mode 100644 index df78b83c..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/android-chrome-192x192.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/android-chrome-512x512.png b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/android-chrome-512x512.png deleted file mode 100644 index 559d3eaf..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/android-chrome-512x512.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/apple-touch-icon.png b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/apple-touch-icon.png deleted file mode 100644 index 14aec788..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/apple-touch-icon.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/browserconfig.xml b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/browserconfig.xml deleted file mode 100644 index ef1658c6..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/browserconfig.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - #ffc40d - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/favicon-16x16.png b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/favicon-16x16.png deleted file mode 100644 index 0dd76a80..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/favicon-16x16.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/favicon-32x32.png b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/favicon-32x32.png deleted file mode 100644 index e3ad7510..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/favicon-32x32.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/favicon.ico b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/favicon.ico deleted file mode 100644 index bebc1524..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/favicon.ico and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-1x.png b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-1x.png deleted file mode 100644 index 50bdeae8..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-1x.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-2x.png b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-2x.png deleted file mode 100644 index 98687998..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-2x.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-3x.png b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-3x.png deleted file mode 100644 index b612922b..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-3x.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-4x.png b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-4x.png deleted file mode 100644 index 5936c147..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/launcher-icon-4x.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/mstile-150x150.png b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/mstile-150x150.png deleted file mode 100644 index eb66ae2b..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/mstile-150x150.png and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/safari-pinned-tab.svg b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/safari-pinned-tab.svg deleted file mode 100644 index e525e9b4..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/safari-pinned-tab.svg +++ /dev/null @@ -1,108 +0,0 @@ - - - - -Created by potrace 1.11, written by Peter Selinger 2001-2013 - - - - - - - - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/site.webmanifest b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/site.webmanifest deleted file mode 100644 index 058491fb..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/images/icons/site.webmanifest +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "OpenSpeedTest", - "short_name": "Speed Test", - "icons": [ - { - "src": "/assets/images/icons/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/assets/images/icons/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone", - "start_url": "/" - - -} diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/js/app-2.5.4.js b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/js/app-2.5.4.js deleted file mode 100644 index f5b5b6e4..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/js/app-2.5.4.js +++ /dev/null @@ -1,1427 +0,0 @@ -/* - Official Website : https://OpenSpeedTest.COM | Email: support@openspeedtest.com - Developed by : Vishnu | https://Vishnu.Pro | Email : me@vishnu.pro - Like this Project? Please Donate NOW & Keep us Alive -> https://go.openspeedtest.com/Donate - Speed Test by OpenSpeedTest™️ is Free and Open-Source Software (FOSS) with MIT License. - Read full license terms @ http://go.openspeedtest.com/License - If you have any Questions, ideas or Comments Please Send it via -> https://go.openspeedtest.com/SendMessage -*/ -window.onload = function() { - var appSVG = document.getElementById("OpenSpeedTest-UI"); - appSVG.parentNode.replaceChild(appSVG.contentDocument.documentElement, appSVG); - ostOnload(); - OpenSpeedTest.Start(); -}; -(function(OpenSpeedTest) { - var Status; - var ProG; - var Callback = function(callback) { - if (callback && typeof callback === "function") { - callback(); - } - }; - function _(el) { - if (!(this instanceof _)) { - return new _(el); - } - this.el = document.getElementById(el); - } - _.prototype.fade = function fade(type, ms, callback00) { - var isIn = type === "in", opacity = isIn ? 0 : 1, interval = 14, duration = ms, gap = interval / duration, self = this; - if (isIn) { - self.el.style.display = "block"; - self.el.style.opacity = opacity; - } - function func() { - opacity = isIn ? opacity + gap : opacity - gap; - self.el.style.opacity = opacity; - if (opacity <= 0) { - self.el.style.display = "none"; - } - if (opacity <= 0 || opacity >= 1) { - window.clearInterval(fading, Callback(callback00)); - } - } - var fading = window.setInterval(func, interval); - }; - var easeOutQuint = function(t, b, c, d) { - t /= d; - t--; - return c * (t * t * t * t * t + 1) + b; - }; - var easeOutCubic = function(t, b, c, d) { - t /= d; - t--; - return c * (t * t * t + 1) + b; - }; - var openSpeedtestShow = function() { - this.YourIP = _("YourIP"); - this.ipDesk = _("ipDesk"); - this.ipMob = _("ipMob"); - this.downSymbolDesk = _("downSymbolDesk"); - this.upSymbolDesk = _("upSymbolDesk"); - this.upSymbolMob = _("upSymbolMob"); - this.downSymbolMob = _("downSymbolMob"); - this.settingsMob = _("settingsMob"); - this.settingsDesk = _("settingsDesk"); - this.oDoLiveStatus = _("oDoLiveStatus"); - this.ConnectErrorMob = _("ConnectErrorMob"); - this.ConnectErrorDesk = _("ConnectErrorDesk"); - this.downResult = _("downResult"); - this.upRestxt = _("upRestxt"); - this.pingResult = _("pingResult"); - this.jitterDesk = _("jitterDesk"); - this.pingMobres = _("pingMobres"); - this.JitterResultMon = _("JitterResultMon"); - this.JitterResultms = _("JitterResultms"); - this.UI_Desk = _("UI-Desk"); - this.UI_Mob = _("UI-Mob"); - this.oDoTopSpeed = _("oDoTopSpeed"); - this.startButtonMob = _("startButtonMob"); - this.startButtonDesk = _("startButtonDesk"); - this.intro_Desk = _("intro-Desk"); - this.intro_Mob = _("intro-Mob"); - this.loader = _("loading_app"); - this.OpenSpeedtest = _("OpenSpeedtest"); - this.mainGaugebg_Desk = _("mainGaugebg-Desk"); - this.mainGaugeBlue_Desk = _("mainGaugeBlue-Desk"); - this.mainGaugeWhite_Desk = _("mainGaugeWhite-Desk"); - this.mainGaugebg_Mob = _("mainGaugebg-Mob"); - this.mainGaugeBlue_Mob = _("mainGaugeBlue-Mob"); - this.mainGaugeWhite_Mob = _("mainGaugeWhite-Mob"); - this.oDoLiveSpeed = _("oDoLiveSpeed"); - this.progressStatus_Mob = _("progressStatus-Mob"); - this.progressStatus_Desk = _("progressStatus-Desk"); - this.graphc1 = _("graphc1"); - this.graphc2 = _("graphc2"); - this.graphMob2 = _("graphMob2"); - this.graphMob1 = _("graphMob1"); - this.text = _("text"); - this.scale = [{degree:680, value:0}, {degree:570, value:0.5}, {degree:460, value:1}, {degree:337, value:10}, {degree:220, value:100}, {degree:115, value:500}, {degree:0, value:1000},]; - this.element = ""; - this.chart = ""; - this.polygon = ""; - this.width = 200; - this.height = 50; - this.maxValue = 0; - this.values = []; - this.points = []; - this.vSteps = 5; - this.measurements = []; - this.points = []; - }; - openSpeedtestShow.prototype.reset = function() { - this.element = ""; - this.chart = ""; - this.polygon = ""; - this.width = 200; - this.height = 50; - this.maxValue = 0; - this.values = []; - this.points = []; - this.vSteps = 5; - this.measurements = []; - this.points = []; - }; - openSpeedtestShow.prototype.ip = function() { - var Self = this; - if (Self.ipDesk.el.style.display === "block") { - Self.ipDesk.el.style.display = "none"; - Self.ipMob.el.style.display = "none"; - } else { - Self.ipDesk.el.style.display = "block"; - Self.ipMob.el.style.display = "block"; - } - }; - openSpeedtestShow.prototype.prePing = function() { - this.loader.fade("out", 500); - this.OpenSpeedtest.fade("in", 1000); - }; - openSpeedtestShow.prototype.app = function() { - this.loader.fade("out", 500, this.ShowAppIntro()); - }; - openSpeedtestShow.prototype.ShowAppIntro = function() { - this.OpenSpeedtest.fade("in", 1000); - }; - openSpeedtestShow.prototype.userInterface = function() { - var Self = this; - this.intro_Desk.fade("out", 1000); - this.intro_Mob.fade("out", 1000, this.ShowUI()); - }; - openSpeedtestShow.prototype.ShowUI = function() { - this.UI_Desk.fade("in", 1000); - this.UI_Mob.fade("in", 1000, uiLoaded); - function uiLoaded(argument) { - Status = "Loaded"; - console.log("Developed by Vishnu. Email --\x3e me@vishnu.pro"); - } - }; - openSpeedtestShow.prototype.Symbol = function(dir) { - if (dir == 0) { - this.downSymbolMob.el.style.display = "block"; - this.downSymbolDesk.el.style.display = "block"; - this.upSymbolMob.el.style.display = "none"; - this.upSymbolDesk.el.style.display = "none"; - } - if (dir == 1) { - this.downSymbolMob.el.style.display = "none"; - this.downSymbolDesk.el.style.display = "none"; - this.upSymbolMob.el.style.display = "block"; - this.upSymbolDesk.el.style.display = "block"; - } - if (dir == 2) { - this.downSymbolMob.el.style.display = "none"; - this.downSymbolDesk.el.style.display = "none"; - this.upSymbolMob.el.style.display = "none"; - this.upSymbolDesk.el.style.display = "none"; - } - }; - openSpeedtestShow.prototype.Graph = function(speed, select) { - if (!("remove" in Element.prototype)) { - Element.prototype.remove = function() { - if (this.parentNode) { - this.parentNode.removeChild(this); - } - }; - } - var Self = this; - var Remove; - if (select === 0) { - var Graphelement = this.graphc1.el; - Remove = "line"; - this.graphMob2.el.style.display = "none"; - this.graphMob1.el.style.display = "block"; - } else { - Graphelement = this.graphc2.el; - Remove = "line2"; - this.graphMob1.el.style.display = "none"; - this.graphMob2.el.style.display = "block"; - } - if (!isNaN(speed)) { - this.values.push(speed); - } else { - this.values.push(""); - } - function calcMeasure() { - for (x = 0; x < Self.vSteps; x++) { - var measurement = Math.ceil(Self.maxValue / Self.vSteps * (x + 1)); - Self.measurements.push(measurement); - } - Self.measurements.reverse(); - } - function createChart(element, values) { - calcMaxValue(); - calcPoints(); - calcMeasure(); - var removeLine = document.getElementsByClassName(Remove); - while (removeLine.length > 0) { - removeLine[0].remove(); - } - Self.polygon = document.createElementNS("http://www.w3.org/2000/svg", "polygon"); - Self.polygon.setAttribute("points", Self.points); - Self.polygon.setAttribute("class", Remove); - if (Self.values.length > 1) { - Graphelement.appendChild(Self.polygon); - } - } - function calcPoints() { - if (Self.values.length > 1) { - var points = "0," + Self.height + " "; - for (x = 0; x < Self.values.length; x++) { - var perc = Self.values[x] / Self.maxValue; - var steps = 130 / (Self.values.length - 1); - var point = (steps * x).toFixed(2) + "," + (Self.height - Self.height * perc).toFixed(2) + " "; - points += point; - } - points += "130," + Self.height; - Self.points = points; - } - } - var x; - function calcMaxValue() { - Self.maxValue = 0; - for (x = 0; x < Self.values.length; x++) { - if (Self.values[x] > Self.maxValue) { - Self.maxValue = Self.values[x]; - } - } - Self.maxValue = Math.ceil(Self.maxValue); - } - if (speed > 0) { - createChart(Graphelement, speed); - } - }; - openSpeedtestShow.prototype.progress = function(Switch, duration) { - var Self = this; - var Stop = duration; - var Stage = Switch; - var currTime = Date.now(); - var chan2 = 0 - 400; - var interval = setInterval(function() { - var timeNow = (Date.now() - currTime) / 1000; - var toLeft = easeOutCubic(timeNow, 400, 400, Stop); - var toRight = easeOutCubic(timeNow, 400, chan2, Stop); - if (Stage) { - Self.progressStatus_Desk.el.style.strokeDashoffset = toLeft; - Self.progressStatus_Mob.el.style.strokeDashoffset = toLeft; - } else { - Self.progressStatus_Desk.el.style.strokeDashoffset = toRight; - Self.progressStatus_Mob.el.style.strokeDashoffset = toRight; - } - if (timeNow >= Stop) { - clearInterval(interval); - ProG = "done"; - Self.progressStatus_Desk.el.style.strokeDashoffset = 800; - Self.progressStatus_Mob.el.style.strokeDashoffset = 800; - } - }, 14); - }; - openSpeedtestShow.prototype.mainGaugeProgress = function(currentSpeed) { - var Self = this; - var speed = currentSpeed; - if (speed < 0) { - speed = 0; - } - var mainGaugeOffset = Self.getNonlinearDegree(speed); - if (currentSpeed > 0) { - this.mainGaugeBlue_Desk.el.style.strokeOpacity = 1; - this.mainGaugeWhite_Desk.el.style.strokeOpacity = 1; - this.mainGaugeBlue_Mob.el.style.strokeOpacity = 1; - this.mainGaugeWhite_Mob.el.style.strokeOpacity = 1; - this.mainGaugeBlue_Desk.el.style.strokeDashoffset = mainGaugeOffset; - this.mainGaugeWhite_Desk.el.style.strokeDashoffset = mainGaugeOffset == 0 ? 1 : mainGaugeOffset + 1; - this.mainGaugeBlue_Mob.el.style.strokeDashoffset = mainGaugeOffset; - this.mainGaugeWhite_Mob.el.style.strokeDashoffset = mainGaugeOffset == 0 ? 1 : mainGaugeOffset + 1; - } - if (mainGaugeOffset == 0 && speed > 1000) { - this.mainGaugeBlue_Mob.el.style.strokeDashoffset = mainGaugeOffset >= 681 ? 681 : mainGaugeOffset; - this.mainGaugeWhite_Mob.el.style.strokeDashoffset = mainGaugeOffset == 0 ? 1 : mainGaugeOffset + 1; - this.mainGaugeWhite_Desk.el.style.strokeDashoffset = mainGaugeOffset == 0 ? 1 : mainGaugeOffset + 1; - this.mainGaugeBlue_Desk.el.style.strokeDashoffset = mainGaugeOffset >= 681 ? 681 : mainGaugeOffset; - } else if (mainGaugeOffset == 0 && speed <= 0) { - this.mainGaugeBlue_Mob.el.style.strokeDashoffset = 681.1; - this.mainGaugeWhite_Mob.el.style.strokeDashoffset = 0.1; - this.mainGaugeWhite_Desk.el.style.strokeDashoffset = 0.1; - this.mainGaugeBlue_Desk.el.style.strokeDashoffset = 681.1; - } - }; - openSpeedtestShow.prototype.showStatus = function(e) { - this.oDoLiveStatus.el.textContent = e; - }; - openSpeedtestShow.prototype.ConnectionError = function() { - this.ConnectErrorMob.el.style.display = "block"; - this.ConnectErrorDesk.el.style.display = "block"; - }; - openSpeedtestShow.prototype.uploadResult = function(upload) { - if (upload < 1) { - this.upRestxt.el.textContent = upload.toFixed(3); - } - if (upload >= 1 && upload < 9999) { - this.upRestxt.el.textContent = upload.toFixed(1); - } - if (upload >= 10000 && upload < 99999) { - this.upRestxt.el.textContent = upload.toFixed(1); - this.upRestxt.el.style.fontSize = "20px"; - } - if (upload >= 100000) { - this.upRestxt.el.textContent = upload.toFixed(1); - this.upRestxt.el.style.fontSize = "18px"; - } - }; - openSpeedtestShow.prototype.pingResults = function(data, Display) { - var ShowData = data; - if (Display === "Ping") { - if (ShowData >= 1 && ShowData < 10000) { - this.pingResult.el.textContent = Math.floor(ShowData); - this.pingMobres.el.textContent = Math.floor(ShowData); - } else if (ShowData >= 0 && ShowData < 1) { - if (ShowData == 0) { - ShowData = 0; - } - this.pingResult.el.textContent = ShowData; - this.pingMobres.el.textContent = ShowData; - } - } - if (Display === "Error") { - this.oDoLiveSpeed.el.textContent = ShowData; - } - }; - openSpeedtestShow.prototype.downloadResult = function(download) { - if (download < 1) { - this.downResult.el.textContent = download.toFixed(3); - } - if (download >= 1 && download < 9999) { - this.downResult.el.textContent = download.toFixed(1); - } - if (download >= 10000 && download < 99999) { - this.downResult.el.textContent = download.toFixed(1); - this.downResult.el.style.fontSize = "20px"; - } - if (download >= 100000) { - this.downResult.el.textContent = download.toFixed(1); - this.downResult.el.style.fontSize = "18px"; - } - }; - openSpeedtestShow.prototype.jitterResult = function(data, Display) { - var ShowData = data; - if (Display === "Jitter") { - if (ShowData >= 1 && ShowData < 10000) { - this.jitterDesk.el.textContent = Math.floor(ShowData); - if (ShowData >= 1 && ShowData < 100) { - this.JitterResultMon.el.textContent = Math.floor(ShowData); - } - if (ShowData >= 100) { - var kData = (ShowData / 1000).toFixed(1); - this.JitterResultMon.el.textContent = kData + "k"; - } - } else if (ShowData >= 0 && ShowData < 1) { - if (ShowData == 0) { - ShowData = 0; - } - this.jitterDesk.el.textContent = ShowData; - this.JitterResultMon.el.textContent = ShowData; - } - } - }; - openSpeedtestShow.prototype.LiveSpeed = function(data, Display) { - var ShowData = data; - if (Display === "countDown") { - var speed = ShowData.toFixed(0); - this.oDoLiveSpeed.el.textContent = speed; - return; - } - if (Display === "speedToZero") { - if (typeof ShowData == "number") { - ShowData = ShowData.toFixed(1); - } - if (ShowData <= 0) { - ShowData = 0; - } - this.oDoLiveSpeed.el.textContent = ShowData; - this.oDoTopSpeed.el.textContent = "1000+"; - this.oDoTopSpeed.el.style.fontSize = "16.9px"; - this.oDoTopSpeed.el.style.fill = "gray"; - return; - } - if (Display === "Ping") { - if (ShowData >= 1 && ShowData < 10000) { - this.oDoLiveSpeed.el.textContent = Math.floor(ShowData); - } else if (ShowData >= 0 && ShowData < 1) { - if (ShowData == 0) { - ShowData = 0; - } - this.oDoLiveSpeed.el.textContent = ShowData; - } - } else { - if (ShowData == 0) { - var speed = ShowData.toFixed(0); - this.oDoLiveSpeed.el.textContent = speed; - } - if (ShowData <= 1 && ShowData > 0) { - var speed = ShowData.toFixed(3); - this.oDoLiveSpeed.el.textContent = speed; - } - if (ShowData > 1) { - var speed = ShowData.toFixed(1); - this.oDoLiveSpeed.el.textContent = speed; - } - if (ShowData <= 1000) { - this.oDoTopSpeed.el.textContent = "1000+"; - this.oDoTopSpeed.el.style.fontSize = "16.9px"; - this.oDoTopSpeed.el.style.fill = "gray"; - } - if (ShowData >= 1010) { - this.oDoTopSpeed.el.textContent = Math.floor(ShowData / 1010) * 1000 + "+"; - this.oDoTopSpeed.el.style.fill = "gray"; - this.oDoTopSpeed.el.style.fontSize = "17.2px"; - } - } - }; - openSpeedtestShow.prototype.GaugeProgresstoZero = function(currentSpeed, status) { - var speed = currentSpeed; - var Self = this; - var duration = 3; - if (speed >= 0) { - var time = Date.now(); - var SpeedtoZero = 0 - speed; - var interval = setInterval(function() { - var timeNow = (Date.now() - time) / 1000; - var speedToZero = easeOutQuint(timeNow, speed, SpeedtoZero, duration); - Self.LiveSpeed(speedToZero, "speedToZero"); - Self.mainGaugeProgress(speedToZero); - if (timeNow >= duration || speedToZero <= 0) { - clearInterval(interval); - Self.LiveSpeed(0, "speedToZero"); - Self.mainGaugeProgress(0); - Status = status; - } - }, 16); - } - }; - openSpeedtestShow.prototype.getNonlinearDegree = function(mega_bps) { - var i = 0; - if (0 == mega_bps || mega_bps <= 0 || isNaN(mega_bps)) { - return 0; - } - while (i < this.scale.length) { - if (mega_bps > this.scale[i].value) { - i++; - } else { - return this.scale[i - 1].degree + (mega_bps - this.scale[i - 1].value) * (this.scale[i].degree - this.scale[i - 1].degree) / (this.scale[i].value - this.scale[i - 1].value); - } - } - return this.scale[this.scale.length - 1].degree; - }; - var openSpeedtestGet = function() { - this.OverAllTimeAvg = window.performance.now(); - this.SpeedSamples = []; - this.FinalSpeed; - }; - openSpeedtestGet.prototype.reset = function() { - this.OverAllTimeAvg = window.performance.now(); - this.SpeedSamples = []; - this.FinalSpeed = 0; - }; - openSpeedtestGet.prototype.ArraySum = function(Arr) { - var array = Arr; - if (array) { - var sum = array.reduce(function(A, B) { - if (typeof A === "number" && typeof B === "number") { - return A + B; - } - }, 0); - return sum; - } else { - return 0; - } - }; - openSpeedtestGet.prototype.AvgSpeed = function(Livespeed, Start, duration) { - var Self = this; - this.timeNow = (window.performance.now() - this.OverAllTimeAvg) / 1000; - this.FinalSpeed; - var StartRecoding = Start; - StartRecoding = duration - StartRecoding; - if (this.timeNow >= StartRecoding) { - if (Livespeed > 0) { - this.SpeedSamples.push(Livespeed); - } - Self.FinalSpeed = Self.ArraySum(Self.SpeedSamples) / Self.SpeedSamples.length; - } - return Self.FinalSpeed; - }; - openSpeedtestGet.prototype.uRandom = function(size, callback) { - var size = size; - var randomValue = new Uint32Array(262144); - function getRandom() { - var n = randomValue.length; - for (var i = 0; i < n; i++) { - randomValue[i] = Math.random() * 4294967296; - } - return randomValue; - } - var randomData = []; - var genData = function(dataSize) { - var dataSize = dataSize; - for (var i = 0; i < dataSize; i++) { - randomData[i] = getRandom(); - } - return randomData; - }; - return new Blob(genData(size), {type:"application/octet-stream"}, Callback(callback)); - }; - openSpeedtestGet.prototype.addEvt = function(o, e, f) { - o.addEventListener(e, f); - }; - openSpeedtestGet.prototype.remEvt = function(o, e, f) { - o.removeEventListener(e, f); - }; - var openSpeedtestEngine = function() { - var Get = new openSpeedtestGet(); - var Show = new openSpeedtestShow(); - Show.app(); - var SendData; - var myhostName = location.hostname; - var key; - var TestServerip; - var downloadSpeed; - var uploadSpeed; - var dataUsedfordl; - var dataUsedforul; - var pingEstimate; - var jitterEstimate; - var logData; - var return_data; - var ReQ = []; - var StartTime = []; - var CurrentTime = []; - var LiveSpeedArr; - var dLoaded = 0; - var uLoaded = 0; - var currentSpeed = 0; - var uploadTimeing; - var downloadTimeing; - var downloadTime; - var uploadTime; - var saveTestData; - var stop = 0; - function reSett() { - StartTime = 0; - CurrentTime = 0; - LiveSpeedArr = 0; - currentSpeed = 0; - } - var userAgentString; - if (window.navigator.userAgent) { - userAgentString = window.navigator.userAgent; - } else { - userAgentString = "Not Found"; - } - var ulFinal = ulDuration * 0.6; - var dlFinal = dlDuration * 0.6; - function setFinal() { - if (ulDuration * 0.6 >= 7) { - ulFinal = 7; - } - if (dlDuration * 0.6 >= 7) { - dlFinal = 7; - } - } - setFinal(); - var launch = true; - var init = true; - Get.addEvt(Show.settingsMob.el, "click", ShowIP); - Get.addEvt(Show.settingsDesk.el, "click", ShowIP); - Get.addEvt(Show.startButtonDesk.el, "click", runTasks); - Get.addEvt(Show.startButtonMob.el, "click", runTasks); - Get.addEvt(document, "keypress", hiEnter); - var addEvent = true; - var getParams = function(url) { - var params = {}; - var parser = document.createElement("a"); - parser.href = url; - var query = parser.search.substring(1); - var vars = query.split("&"); - for (var i = 0; i < vars.length; i++) { - var pair = vars[i].split("="); - params[pair[0]] = decodeURIComponent(pair[1]); - } - return params; - }; - var getCommand = getParams(window.location.href.toLowerCase()); - if (setPingSamples) { - if (typeof getCommand.ping === "string" || typeof getCommand.p === "string") { - var setPing; - if (typeof getCommand.ping !== "undefined") { - setPing = getCommand.ping; - } else if (typeof getCommand.p !== "undefined") { - setPing = getCommand.p; - } - if (setPing > 0) { - pingSamples = setPing; - pingSamples = setPing; - } - } - } - if (setPingTimeout) { - if (typeof getCommand.out === "string" || typeof getCommand.o === "string") { - var setOut; - if (typeof getCommand.out !== "undefined") { - setOut = getCommand.out; - } else if (typeof getCommand.o !== "undefined") { - setOut = getCommand.o; - } - if (setOut > 1) { - pingTimeOut = setOut; - pingTimeOut = setOut; - } - } - } - if (setHTTPReq) { - if (typeof getCommand.xhr === "string" || typeof getCommand.x === "string") { - var setThreads; - if (typeof getCommand.xhr !== "undefined") { - setThreads = getCommand.xhr; - } else if (typeof getCommand.x !== "undefined") { - setThreads = getCommand.x; - } - if (setThreads > 0 && setThreads <= 32) { - dlThreads = setThreads; - ulThreads = setThreads; - } - } - } - function isValidHttpUrl(str) { - var regex = /(?:https?):\/\/(\w+:?\w*)?(\S+)(:\d+)?(\/|\/([\w#!:.?+=&%!\-\/]))?/; - if (!regex.test(str)) { - return false; - } else { - return true; - } - } - if (selectServer) { - if (typeof getCommand.host === "string" || typeof getCommand.h === "string") { - var severAddress; - if (typeof getCommand.host !== "undefined") { - severAddress = getCommand.host; - } else if (typeof getCommand.h !== "undefined") { - severAddress = getCommand.h; - } - if (isValidHttpUrl(severAddress)) { - openSpeedTestServerList = [{ServerName:"Home", Download:severAddress + "/downloading", Upload:severAddress + "/upload", ServerIcon:"DefaultIcon",},]; - } - } - } - var custom = parseInt(getCommand.stress); - var customS = parseInt(getCommand.s); - var runStress; - var runStressCustom; - if (typeof getCommand.stress === "string") { - runStress = getCommand.stress; - runStressCustom = custom; - } else if (typeof getCommand.s === "string") { - runStress = getCommand.s; - runStressCustom = customS; - } - if (runStress && stressTest) { - if (runStress === "low" || runStress === "l") { - dlDuration = 300; - ulDuration = 300; - } - if (runStress === "medium" || runStress === "m") { - dlDuration = 600; - ulDuration = 600; - } - if (runStress === "high" || runStress === "h") { - dlDuration = 900; - ulDuration = 900; - } - if (runStress === "veryhigh" || runStress === "v") { - dlDuration = 1800; - ulDuration = 1800; - } - if (runStress === "extreme" || runStress === "e") { - dlDuration = 3600; - ulDuration = 3600; - } - if (runStress === "day" || runStress === "d") { - dlDuration = 86400; - ulDuration = 86400; - } - if (runStress === "year" || runStress === "y") { - dlDuration = 31557600; - ulDuration = 31557600; - } - if (custom > 12 || customS > 12) { - dlDuration = runStressCustom; - ulDuration = runStressCustom; - } - } - var overheadClean = parseInt(getCommand.clean); - var overheadCleanC = parseInt(getCommand.c); - var customOverHeadValue = 1; - if (overheadClean) { - customOverHeadValue = overheadClean; - } else if (overheadCleanC) { - customOverHeadValue = overheadCleanC; - } - if (enableClean) { - if (typeof getCommand.clean === "string" || typeof getCommand.c === "string") { - if (overheadClean >= 1 || overheadCleanC >= 1) { - if (overheadClean < 5 || overheadCleanC < 5) { - upAdjust = 1 + customOverHeadValue / 100; - dlAdjust = 1 + customOverHeadValue / 100; - } - } else { - upAdjust = 1; - dlAdjust = 1; - } - } - } - var OpenSpeedTestRun = parseInt(getCommand.run); - var OpenSpeedTestRunR = parseInt(getCommand.r); - var OpenSpeedTestStart; - if (enableRun) { - if (typeof getCommand.run === "string" || typeof getCommand.r === "string") { - if (OpenSpeedTestRun > 0) { - OpenSpeedTestStart = OpenSpeedTestRun; - } else if (OpenSpeedTestRunR > 0) { - OpenSpeedTestStart = OpenSpeedTestRunR; - } else { - OpenSpeedTestStart = 0; - } - } - } - if (OpenSpeedTestStart >= 0) { - if (launch) { - runTasks(); - } - } - var runTest = getCommand.test; - var runTestT = getCommand.t; - var SelectTest = false; - if (selectTest) { - if (typeof runTest === "string" || typeof runTestT === "string") { - var runTestC; - if (runTest) { - runTestC = runTest; - SelectTest = runTest; - } else if (runTestT) { - runTestC = runTestT; - SelectTest = runTestT; - } - if (runTestC === "download" || runTestC === "d") { - uploadSpeed = 0; - dataUsedforul = 0; - SelectTest = "Download"; - if (launch) { - runTasks(); - } - } else if (runTestC === "upload" || runTestC === "u") { - downloadSpeed = 0; - dataUsedfordl = 0; - SelectTest = "Upload"; - stop = 1; - if (launch) { - runTasks(); - } - } else if (runTestC === "ping" || runTestC === "p") { - uploadSpeed = 0; - dataUsedforul = 0; - downloadSpeed = 0; - dataUsedfordl = 0; - SelectTest = "Ping"; - if (launch) { - runTasks(); - } - } else { - SelectTest = false; - } - } - } - var Startit = 0; - function removeEvts() { - Get.remEvt(Show.settingsMob.el, "click", ShowIP); - Get.remEvt(Show.settingsDesk.el, "click", ShowIP); - Get.remEvt(Show.startButtonDesk.el, "click", runTasks); - Get.remEvt(Show.startButtonMob.el, "click", runTasks); - Get.remEvt(document, "keypress", hiEnter); - } - var requestIP = false; - function ShowIP() { - if (requestIP) { - Show.YourIP.el.textContent = "Please wait.."; - ServerConnect(7); - requestIP = false; - } - Show.ip(); - } - function runTasks() { - if (addEvent) { - removeEvts(); - addEvent = false; - } - if (OpenSpeedTestStart >= 0) { - launch = false; - Show.userInterface(); - init = false; - var AutoTme = Math.ceil(Math.abs(OpenSpeedTestStart)); - Show.showStatus("Automatic Test Starts in ..."); - var autoTest = setInterval(countDownF, 1000); - } - function countDownF() { - if (AutoTme >= 1) { - AutoTme = AutoTme - 1; - Show.LiveSpeed(AutoTme, "countDown"); - } else { - if (AutoTme <= 0) { - clearInterval(autoTest); - launch = true; - OpenSpeedTestStart = undefined; - runTasks(); - } - } - } - if (openSpeedTestServerList === "fetch" && launch === true) { - launch = false; - Show.showStatus("Fetching Server Info.."); - ServerConnect(6); - } - if (launch === true) { - if (SelectTest === "Ping") { - testRun(); - } else if (SelectTest === "Download") { - testRun(); - } else if (SelectTest === "Upload") { - testRun(); - } else if (SelectTest === false) { - testRun(); - } - } - } - var osttm = "\u2122"; - var myname = "OpenSpeedTest"; - var com = ".com"; - var ost = myname + osttm; - function hiEnter(e) { - if (e.key === "Enter") { - runTasks(); - } - } - var showResult = 0; - if (openChannel === "web") { - showResult = webRe; - requestIP = true; - } - if (openChannel === "widget") { - showResult = widgetRe; - requestIP = true; - } - if (openChannel === "selfwidget") { - showResult = widgetRe; - TestServerip = domainx; - myhostName = TestServerip; - } - if (openChannel === "dev") { - } - function testRun() { - if (init) { - Show.userInterface(); - init = false; - } - OpenSpeedtest(); - } - function OpenSpeedtest() { - if (openChannel === "widget" || openChannel === "web") { - ServerConnect(1); - } - function readyToUP() { - uploadTime = window.performance.now(); - upReq(); - } - var Engine = setInterval(function() { - if (Status === "Loaded") { - Status = "busy"; - sendPing(0); - } - if (Status === "Ping") { - Status = "busy"; - Show.showStatus("Milliseconds"); - } - if (Status === "Download") { - Show.showStatus("Initializing.."); - Get.reset(); - reSett(); - Show.reset(); - downloadTime = window.performance.now(); - downReq(); - Status = "initDown"; - } - if (Status === "Downloading") { - Show.Symbol(0); - if (Startit == 0) { - Startit = 1; - Show.showStatus("Testing download speed.."); - var extraTime = (window.performance.now() - downloadTime) / 1000; - dReset = extraTime; - Show.progress(1, dlDuration + 2.5); - dlDuration += extraTime; - } - downloadTimeing = (window.performance.now() - downloadTime) / 1000; - reportCurrentSpeed("dl"); - Show.showStatus("Mbps download"); - Show.mainGaugeProgress(currentSpeed); - Show.LiveSpeed(currentSpeed); - Show.Graph(currentSpeed, 0); - downloadSpeed = Get.AvgSpeed(currentSpeed, dlFinal, dlDuration); - if (downloadTimeing >= dlDuration && ProG == "done") { - if (SelectTest) { - Show.GaugeProgresstoZero(currentSpeed, "SendR"); - Show.showStatus("All done"); - Show.Symbol(2); - } else { - Show.GaugeProgresstoZero(currentSpeed, "Upload"); - } - Show.downloadResult(downloadSpeed); - dataUsedfordl = dLoaded; - stop = 1; - Status = "busy"; - reSett(); - Get.reset(); - } - } - if (Status == "Upload") { - if (stop === 1) { - Show.Symbol(1); - Status = "initup"; - Show.showStatus("Initializing.."); - Show.LiveSpeed("...", "speedToZero"); - SendData = Get.uRandom(ulDataSize, readyToUP); - if (SelectTest) { - Startit = 1; - } - } - } - if (Status === "Uploading") { - if (Startit == 1) { - Startit = 2; - Show.showStatus("Testing upload speed.."); - currentSpeed = 0; - Get.reset(); - Show.reset(); - var extraUTime = (window.performance.now() - uploadTime) / 1000; - uReset = extraUTime; - Show.progress(false, ulDuration + 2.5); - ulDuration += extraUTime; - } - Show.showStatus("Mbps upload"); - uploadTimeing = (window.performance.now() - uploadTime) / 1000; - reportCurrentSpeed("up"); - Show.mainGaugeProgress(currentSpeed); - Show.LiveSpeed(currentSpeed); - Show.Graph(currentSpeed, 1); - uploadSpeed = Get.AvgSpeed(currentSpeed, ulFinal, ulDuration); - if (uploadTimeing >= ulDuration && stop == 1) { - dataUsedforul = uLoaded; - Show.uploadResult(uploadSpeed); - Show.GaugeProgresstoZero(currentSpeed, "SendR"); - SendData = undefined; - Show.showStatus("All done"); - Show.Symbol(2); - Status = "busy"; - stop = 0; - } - } - if (Status === "Error") { - Show.showStatus("Check your network connection status."); - Show.ConnectionError(); - Status = "busy"; - clearInterval(Engine); - var dummyElement = document.createElement("div"); - dummyElement.innerHTML = ''; - var htmlAnchorElement = dummyElement.querySelector("a"); - Show.oDoLiveSpeed.el.textContent = "Network Error"; - var circleSVG = document.getElementById("oDoLiveSpeed"); - htmlAnchorElement.innerHTML = circleSVG.innerHTML; - circleSVG.innerHTML = dummyElement.innerHTML; - } - if (Status === "SendR") { - Show.showStatus("All done"); - var dummyElement = document.createElement("div"); - dummyElement.innerHTML = ''; - var htmlAnchorElement = dummyElement.querySelector("a"); - Show.oDoLiveSpeed.el.textContent = ost; - var circleSVG = document.getElementById("oDoLiveSpeed"); - htmlAnchorElement.innerHTML = circleSVG.innerHTML; - circleSVG.innerHTML = dummyElement.innerHTML; - if (location.hostname != myname.toLowerCase() + com) { - saveTestData = "https://" + myname.toLowerCase() + com + "/results/show.php?" + "&d=" + downloadSpeed.toFixed(3) + "&u=" + uploadSpeed.toFixed(3) + "&p=" + pingEstimate + "&j=" + jitterEstimate + "&dd=" + (dataUsedfordl / 1048576).toFixed(3) + "&ud=" + (dataUsedforul / 1048576).toFixed(3) + "&ua=" + userAgentString; - saveTestData = encodeURI(saveTestData); - var circleSVG2 = document.getElementById("resultsData"); - circleSVG2.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", saveTestData); - circleSVG2.setAttribute("target", "_blank"); - if (saveData) { - ServerConnect(5); - } - } else { - ServerConnect(3); - } - Status = "busy"; - clearInterval(Engine); - } - }, 100); - } - function downReq() { - for (var i = 0; i < dlThreads; i++) { - setTimeout(function(i) { - SendReQ(i); - }, dlDelay * i, i); - } - } - function upReq() { - for (var i = 0; i < ulThreads; i++) { - setTimeout(function(i) { - SendUpReq(i); - }, ulDelay * i, i); - } - } - var dLoad = 0; - var dDiff = 0; - var dTotal = 0; - var dtLoad = 0; - var dtDiff = 0; - var dtTotal = 0; - var dRest = 0; - var dReset; - var uReset; - var uLoad = 0; - var uDiff = 0; - var uTotal = 0; - var utLoad = 0; - var utDiff = 0; - var utTotal = 0; - var uRest = 0; - var dualReset; - var neXT = dlDuration * 1000 - 6000; - var dualupReset; - var neXTUp = ulDuration * 1000 - 6000; - function reportCurrentSpeed(now) { - if (now === "dl") { - var dTime = downloadTimeing * 1000; - if (dTime > dReset * 1000 + dlFinal / 2 * 1000 && dRest === 0) { - dRest = 1; - dtTotal = dtTotal * 0.01; - dTotal = dTotal * 0.01; - dualReset = dTime + 10000; - } - if (dTime >= dualReset && dualReset < neXT) { - dualReset += 10000; - dtTotal = dtTotal * 0.01; - dTotal = dTotal * 0.01; - } - dLoad = dLoaded <= 0 ? 0 : dLoaded - dDiff; - dDiff = dLoaded; - dTotal += dLoad; - dtLoad = dtDiff = 0 ? 0 : dTime - dtDiff; - dtDiff = dTime; - dtTotal += dtLoad; - if (dTotal > 0) { - LiveSpeedArr = dTotal / dtTotal / 125 * upAdjust; - currentSpeed = LiveSpeedArr; - } - } - if (now === "up") { - var Tym = uploadTimeing * 1000; - if (Tym > uReset * 1000 + ulFinal / 2 * 1000 && uRest === 0) { - uRest = 1; - utTotal = utTotal * 0.1; - uTotal = uTotal * 0.1; - dualupReset = Tym + 10000; - } - if (Tym >= dualupReset && dualupReset < neXTUp) { - dualupReset += 10000; - utTotal = utTotal * 0.1; - uTotal = uTotal * 0.1; - } - uLoad = uLoaded <= 0 ? 0 : uLoaded - uDiff; - uDiff = uLoaded; - uTotal += uLoad; - utLoad = utDiff = 0 ? 0 : Tym - utDiff; - utDiff = Tym; - utTotal += utLoad; - if (uTotal > 0) { - LiveSpeedArr = uTotal / utTotal / 125 * upAdjust; - currentSpeed = LiveSpeedArr; - } - } - } - function SendReQ(i) { - var lastLoaded = 0; - var OST = new XMLHttpRequest(); - ReQ[i] = OST; - ReQ[i].open("GET", fianlPingServer.Download + "?n=" + Math.random(), true); - ReQ[i].onprogress = function(e) { - if (stop === 1) { - ReQ[i].abort(); - ReQ[i] = null; - ReQ[i] = undefined; - delete ReQ[i]; - return false; - } - if (Status == "initDown") { - Status = "Downloading"; - } - var eLoaded = e.loaded <= 0 ? 0 : e.loaded - lastLoaded; - if (isNaN(eLoaded) || !isFinite(eLoaded) || eLoaded < 0) { - return false; - } - dLoaded += eLoaded; - lastLoaded = e.loaded; - }; - ReQ[i].onload = function(e) { - if (lastLoaded === 0) { - dLoaded += e.total; - } - if (Status == "initDown") { - Status = "Downloading"; - } - if (ReQ[i]) { - ReQ[i].abort(); - ReQ[i] = null; - ReQ[i] = undefined; - delete ReQ[i]; - } - if (stop === 0) { - SendReQ(i); - } - }; - ReQ[i].onerror = function(e) { - if (stop === 0) { - SendReQ(i); - } - }; - ReQ[i].responseType = "arraybuffer"; - ReQ[i].send(); - } - var uReQ = []; - function SendUpReq(i) { - var lastULoaded = 0; - var OST = new XMLHttpRequest(); - uReQ[i] = OST; - uReQ[i].open("POST", fianlPingServer.Upload + "?n=" + Math.random(), true); - uReQ[i].upload.onprogress = function(e) { - if (Status == "initup" && some === undefined) { - var some; - Status = "Uploading"; - } - if (uploadTimeing >= ulDuration) { - uReQ[i].abort(); - uReQ[i] = null; - uReQ[i] = undefined; - delete uReQ[i]; - return false; - } - var eLoaded = e.loaded <= 0 ? 0 : e.loaded - lastULoaded; - if (isNaN(eLoaded) || !isFinite(eLoaded) || eLoaded < 0) { - return false; - } - uLoaded += eLoaded; - lastULoaded = e.loaded; - }; - uReQ[i].onload = function() { - if (lastULoaded === 0) { - uLoaded += ulDataSize * 1048576; - if (uploadTimeing >= ulDuration) { - uReQ[i].abort(); - uReQ[i] = null; - uReQ[i] = undefined; - delete uReQ[i]; - return false; - } - } - if (Status == "initup" && some === undefined) { - var some; - Status = "Uploading"; - } - if (uReQ[i]) { - uReQ[i].abort(); - uReQ[i] = null; - uReQ[i] = undefined; - delete uReQ[i]; - } - if (stop === 1) { - SendUpReq(i); - } - }; - uReQ[i].onerror = function(e) { - if (uploadTimeing <= ulDuration) { - SendUpReq(i); - } - }; - uReQ[i].setRequestHeader("Content-Type", "application/octet-stream"); - if (i > 0 && uLoaded <= 17000) { - } else { - uReQ[i].send(SendData); - } - } - function sendPing() { - readServerList(); - } - var fianlPingServer; - var statusPing; - var statusPingFinal; - var statusJitter; - var statusJitterFinal; - var statusPingTest; - var pingSendStatus = -1; - var finalPing = []; - var pingServer = []; - var finalJitter = []; - var pingSendLength = openSpeedTestServerList.length; - function readServerList() { - pingSendLength = openSpeedTestServerList.length; - Status = "Ping"; - performance.clearResourceTimings(); - if (pingSendStatus < pingSendLength - 1) { - pingSendStatus++; - if (statusPingTest != "Stop") { - sendPingRequest(openSpeedTestServerList[pingSendStatus], readServerList); - } - } else { - if (pingServer.length >= 1) { - var finalLeastPingResult = Math.min.apply(Math, finalPing); - var finalLeastPingResultIndex = finalPing.indexOf(finalLeastPingResult); - fianlPingServer = pingServer[finalLeastPingResultIndex]; - statusPingFinal = finalLeastPingResult; - statusJitterFinal = finalJitter[finalLeastPingResultIndex]; - statusPingTest = "Busy"; - Show.LiveSpeed(statusPingFinal, "Ping"); - Show.pingResults(statusPingFinal, "Ping"); - Show.jitterResult(statusJitterFinal, "Jitter"); - pingEstimate = statusPingFinal; - jitterEstimate = statusJitterFinal; - if (SelectTest) { - if (SelectTest == "Ping") { - Status = "SendR"; - } else { - Status = SelectTest; - } - } else { - Status = "Download"; - } - } else { - if (pingServer.Download) { - } else { - Status = "Error"; - } - } - } - } - function sendPingRequest(serverListElm, callback) { - var pingSamplesSend = 0; - var pingResult = []; - var jitterResult = []; - function sendNewPingReq() { - if (pingSamplesSend < pingSamples) { - pingSamplesSend++; - if (statusPingTest != "Stop") { - PingRequest(); - } - } else { - if (pingResult.length > 1) { - jitterResult.sort(function(a, b) { - return a - b; - }); - jitterResult = jitterResult.slice(0, jitterResult.length * jitterFinalSample); - jitterResult = jitterResult.reduce(function(acc, val) { - return acc + val; - }, 0) / jitterResult.length; - var leastJitter = jitterResult.toFixed(1); - var leastPing = Math.min.apply(Math, pingResult); - finalPing.push(leastPing); - pingServer.push(serverListElm); - finalJitter.push(leastJitter); - if (typeof callback === "function") { - callback(); - } - } else { - if (typeof callback === "function") { - callback(); - } - } - } - } - function PingRequest() { - var OST = new XMLHttpRequest(); - var ReQ = OST; - if (statusPingTest != "Stop") { - ReQ.abort(); - } - ReQ.open(pingMethod, serverListElm[pingFile] + "?n=" + Math.random(), true); - ReQ.timeout = pingTimeOut; - var startTime = window.performance.now(); - ReQ.send(); - ReQ.onload = function() { - if (this.status === 200 && this.readyState === 4) { - var endTime = Math.floor(window.performance.now() - startTime); - var perfNum = performance.getEntries(); - perfNum = perfNum[perfNum.length - 1]; - var perfPing; - if (perfNum.initiatorType === "xmlhttprequest") { - perfPing = parseFloat(perfNum.duration.toFixed(1)); - } else { - perfPing = endTime; - } - if (pingSamplesSend > 250) { - perfPing = endTime; - } - if (perfPing <= 0) { - statusPing = 0.1; - pingResult.push(0.1); - } else { - statusPing = perfPing; - pingResult.push(perfPing); - } - if (pingResult.length > 1) { - var jitterCalc = Math.abs(pingResult[pingResult.length - 1] - pingResult[pingResult.length - 2]).toFixed(1); - jitterResult.push(parseFloat(jitterCalc)); - statusJitter = jitterCalc; - Show.LiveSpeed(perfPing, "Ping"); - Show.pingResults(perfPing, "Ping"); - Show.jitterResult(jitterCalc, "Jitter"); - } - sendNewPingReq(); - } - if (this.status === 404 && this.readyState === 4) { - pingSamplesSend++; - sendNewPingReq(); - } - }; - ReQ.onerror = function(e) { - pingSamplesSend++; - sendNewPingReq(); - }; - ReQ.ontimeout = function(e) { - pingSamplesSend++; - sendNewPingReq(); - }; - } - PingRequest(); - } - var ServerConnect = function(auth) { - var Self = this; - var xhr = new XMLHttpRequest(); - var url = OpenSpeedTestdb; - if (auth == 1) { - url = webIP; - } - if (auth == 5) { - url = saveDataURL; - } - if (auth == 7) { - url = get_IP; - } - xhr.open("POST", url, true); - xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - xhr.onreadystatechange = function() { - if (xhr.readyState == 4 && xhr.status == 200) { - return_data = xhr.responseText.trim(); - if (auth == 2) { - key = return_data; - } - if (auth == 1) { - TestServerip = return_data; - } - if (auth == 3) { - setTimeout(function() { - location.href = showResult + return_data; - }, 1500); - } - if (auth == 6) { - openSpeedTestServerList = JSON.parse(return_data); - launch = true; - runTasks(); - } - if (auth == 7) { - Show.YourIP.el.textContent = return_data; - } - } - }; - if (auth == 2) { - logData = "r=n"; - } - if (auth == 3) { - logData = "r=l" + "&d=" + downloadSpeed + "&u=" + uploadSpeed + "&dd=" + dataUsedfordl / 1048576 + "&ud=" + dataUsedforul / 1048576 + "&p=" + pingEstimate + "&do=" + myhostName + "&S=" + key + "&sip=" + TestServerip + "&jit=" + jitterEstimate + "&ua=" + userAgentString; - } - if (auth == 5) { - logData = saveTestData; - } - if (auth == 6) { - logData = "r=s"; - } - xhr.send(logData); - }; - }; - OpenSpeedTest.Start = function() { - new openSpeedtestEngine(); - }; -})(window.OpenSpeedTest = window.OpenSpeedTest || {}); diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/js/app-2.5.4.min.js b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/js/app-2.5.4.min.js deleted file mode 100644 index 0df99a1f..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/assets/js/app-2.5.4.min.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - Official Website : https://OpenSpeedTest.COM | Email: support@openspeedtest.com - Developed by : Vishnu | https://Vishnu.Pro | Email : me@vishnu.pro - Like this Project? Please Donate NOW & Keep us Alive -> https://go.openspeedtest.com/Donate - Speed Test by OpenSpeedTest™️ is Free and Open-Source Software (FOSS) with MIT License. - Read full license terms @ http://go.openspeedtest.com/License - If you have any Questions, ideas or Comments Please Send it via -> https://go.openspeedtest.com/SendMessage -*/ -window.onload=function(){var X=document.getElementById("OpenSpeedTest-UI");X.parentNode.replaceChild(X.contentDocument.documentElement,X);ostOnload();OpenSpeedTest.Start()}; -(function(X){function h(c){if(!(this instanceof h))return new h(c);this.el=document.getElementById(c)}var m,Ma,Na=function(c){c&&"function"===typeof c&&c()};h.prototype.fade=function(c,d,a){var f="in"===c,t=f?0:1,w=14/d,n=this;f&&(n.el.style.display="block",n.el.style.opacity=t);var q=window.setInterval(function(){t=f?t+w:t-w;n.el.style.opacity=t;0>=t&&(n.el.style.display="none");(0>=t||1<=t)&&window.clearInterval(q,Na(a))},14)};var Oa=function(c,d,a,f){c/=f;c--;return a*(c*c*c+1)+d},r=function(){this.YourIP= -h("YourIP");this.ipDesk=h("ipDesk");this.ipMob=h("ipMob");this.downSymbolDesk=h("downSymbolDesk");this.upSymbolDesk=h("upSymbolDesk");this.upSymbolMob=h("upSymbolMob");this.downSymbolMob=h("downSymbolMob");this.settingsMob=h("settingsMob");this.settingsDesk=h("settingsDesk");this.oDoLiveStatus=h("oDoLiveStatus");this.ConnectErrorMob=h("ConnectErrorMob");this.ConnectErrorDesk=h("ConnectErrorDesk");this.downResult=h("downResult");this.upRestxt=h("upRestxt");this.pingResult=h("pingResult");this.jitterDesk= -h("jitterDesk");this.pingMobres=h("pingMobres");this.JitterResultMon=h("JitterResultMon");this.JitterResultms=h("JitterResultms");this.UI_Desk=h("UI-Desk");this.UI_Mob=h("UI-Mob");this.oDoTopSpeed=h("oDoTopSpeed");this.startButtonMob=h("startButtonMob");this.startButtonDesk=h("startButtonDesk");this.intro_Desk=h("intro-Desk");this.intro_Mob=h("intro-Mob");this.loader=h("loading_app");this.OpenSpeedtest=h("OpenSpeedtest");this.mainGaugebg_Desk=h("mainGaugebg-Desk");this.mainGaugeBlue_Desk=h("mainGaugeBlue-Desk"); -this.mainGaugeWhite_Desk=h("mainGaugeWhite-Desk");this.mainGaugebg_Mob=h("mainGaugebg-Mob");this.mainGaugeBlue_Mob=h("mainGaugeBlue-Mob");this.mainGaugeWhite_Mob=h("mainGaugeWhite-Mob");this.oDoLiveSpeed=h("oDoLiveSpeed");this.progressStatus_Mob=h("progressStatus-Mob");this.progressStatus_Desk=h("progressStatus-Desk");this.graphc1=h("graphc1");this.graphc2=h("graphc2");this.graphMob2=h("graphMob2");this.graphMob1=h("graphMob1");this.text=h("text");this.scale=[{degree:680,value:0},{degree:570,value:.5}, -{degree:460,value:1},{degree:337,value:10},{degree:220,value:100},{degree:115,value:500},{degree:0,value:1E3}];this.polygon=this.chart=this.element="";this.width=200;this.height=50;this.maxValue=0;this.values=[];this.points=[];this.vSteps=5;this.measurements=[];this.points=[]};r.prototype.reset=function(){this.polygon=this.chart=this.element="";this.width=200;this.height=50;this.maxValue=0;this.values=[];this.points=[];this.vSteps=5;this.measurements=[];this.points=[]};r.prototype.ip=function(){"block"=== -this.ipDesk.el.style.display?(this.ipDesk.el.style.display="none",this.ipMob.el.style.display="none"):(this.ipDesk.el.style.display="block",this.ipMob.el.style.display="block")};r.prototype.prePing=function(){this.loader.fade("out",500);this.OpenSpeedtest.fade("in",1E3)};r.prototype.app=function(){this.loader.fade("out",500,this.ShowAppIntro())};r.prototype.ShowAppIntro=function(){this.OpenSpeedtest.fade("in",1E3)};r.prototype.userInterface=function(){this.intro_Desk.fade("out",1E3);this.intro_Mob.fade("out", -1E3,this.ShowUI())};r.prototype.ShowUI=function(){this.UI_Desk.fade("in",1E3);this.UI_Mob.fade("in",1E3,function(c){m="Loaded";console.log("Developed by Vishnu. Email --\x3e me@vishnu.pro")})};r.prototype.Symbol=function(c){0==c&&(this.downSymbolMob.el.style.display="block",this.downSymbolDesk.el.style.display="block",this.upSymbolMob.el.style.display="none",this.upSymbolDesk.el.style.display="none");1==c&&(this.downSymbolMob.el.style.display="none",this.downSymbolDesk.el.style.display="none",this.upSymbolMob.el.style.display= -"block",this.upSymbolDesk.el.style.display="block");2==c&&(this.downSymbolMob.el.style.display="none",this.downSymbolDesk.el.style.display="none",this.upSymbolMob.el.style.display="none",this.upSymbolDesk.el.style.display="none")};r.prototype.Graph=function(c,d){function a(q,M){for(n=f.maxValue=0;nf.maxValue&&(f.maxValue=f.values[n]);f.maxValue=Math.ceil(f.maxValue);if(1=d&&(clearInterval(t),Ma="done",a.progressStatus_Desk.el.style.strokeDashoffset=800,a.progressStatus_Mob.el.style.strokeDashoffset=800)},14)};r.prototype.mainGaugeProgress=function(c){var d=c;0>d&&(d=0);var a=this.getNonlinearDegree(d);0=d&&(this.mainGaugeBlue_Mob.el.style.strokeDashoffset=681.1,this.mainGaugeWhite_Mob.el.style.strokeDashoffset=.1,this.mainGaugeWhite_Desk.el.style.strokeDashoffset=.1,this.mainGaugeBlue_Desk.el.style.strokeDashoffset=681.1)};r.prototype.showStatus=function(c){this.oDoLiveStatus.el.textContent=c};r.prototype.ConnectionError=function(){this.ConnectErrorMob.el.style.display= -"block";this.ConnectErrorDesk.el.style.display="block"};r.prototype.uploadResult=function(c){1>c&&(this.upRestxt.el.textContent=c.toFixed(3));1<=c&&9999>c&&(this.upRestxt.el.textContent=c.toFixed(1));1E4<=c&&99999>c&&(this.upRestxt.el.textContent=c.toFixed(1),this.upRestxt.el.style.fontSize="20px");1E5<=c&&(this.upRestxt.el.textContent=c.toFixed(1),this.upRestxt.el.style.fontSize="18px")};r.prototype.pingResults=function(c,d){var a=c;"Ping"===d&&(1<=a&&1E4>a?(this.pingResult.el.textContent=Math.floor(a), -this.pingMobres.el.textContent=Math.floor(a)):0<=a&&1>a&&(0==a&&(a=0),this.pingResult.el.textContent=a,this.pingMobres.el.textContent=a));"Error"===d&&(this.oDoLiveSpeed.el.textContent=a)};r.prototype.downloadResult=function(c){1>c&&(this.downResult.el.textContent=c.toFixed(3));1<=c&&9999>c&&(this.downResult.el.textContent=c.toFixed(1));1E4<=c&&99999>c&&(this.downResult.el.textContent=c.toFixed(1),this.downResult.el.style.fontSize="20px");1E5<=c&&(this.downResult.el.textContent=c.toFixed(1),this.downResult.el.style.fontSize= -"18px")};r.prototype.jitterResult=function(c,d){var a=c;"Jitter"===d&&(1<=a&&1E4>a?(this.jitterDesk.el.textContent=Math.floor(a),1<=a&&100>a&&(this.JitterResultMon.el.textContent=Math.floor(a)),100<=a&&(a=(a/1E3).toFixed(1),this.JitterResultMon.el.textContent=a+"k")):0<=a&&1>a&&(0==a&&(a=0),this.jitterDesk.el.textContent=a,this.JitterResultMon.el.textContent=a))};r.prototype.LiveSpeed=function(c,d){var a=c;if("countDown"===d){var f=a.toFixed(0);this.oDoLiveSpeed.el.textContent=f}else"speedToZero"=== -d?("number"==typeof a&&(a=a.toFixed(1)),0>=a&&(a=0),this.oDoLiveSpeed.el.textContent=a,this.oDoTopSpeed.el.textContent="1000+",this.oDoTopSpeed.el.style.fontSize="16.9px",this.oDoTopSpeed.el.style.fill="gray"):"Ping"===d?1<=a&&1E4>a?this.oDoLiveSpeed.el.textContent=Math.floor(a):0<=a&&1>a&&(0==a&&(a=0),this.oDoLiveSpeed.el.textContent=a):(0==a&&(f=a.toFixed(0),this.oDoLiveSpeed.el.textContent=f),1>=a&&0=a&&(this.oDoTopSpeed.el.textContent="1000+",this.oDoTopSpeed.el.style.fontSize="16.9px",this.oDoTopSpeed.el.style.fill="gray"),1010<=a&&(this.oDoTopSpeed.el.textContent=1E3*Math.floor(a/1010)+"+",this.oDoTopSpeed.el.style.fill="gray",this.oDoTopSpeed.el.style.fontSize="17.2px"))};r.prototype.GaugeProgresstoZero=function(c,d){var a=this;if(0<=c)var f=Date.now(),t=0-c,w=setInterval(function(){var n=(Date.now()-f)/1E3;var q=n/3;q--;q=t*(q*q*q*q*q+1)+c;a.LiveSpeed(q,"speedToZero");a.mainGaugeProgress(q); -if(3<=n||0>=q)clearInterval(w),a.LiveSpeed(0,"speedToZero"),a.mainGaugeProgress(0),m=d},16)};r.prototype.getNonlinearDegree=function(c){var d=0;if(0==c||0>=c||isNaN(c))return 0;for(;dthis.scale[d].value)d++;else return this.scale[d-1].degree+(c-this.scale[d-1].value)*(this.scale[d].degree-this.scale[d-1].degree)/(this.scale[d].value-this.scale[d-1].value);return this.scale[this.scale.length-1].degree};var O=function(){this.OverAllTimeAvg=window.performance.now();this.SpeedSamples= -[];this.FinalSpeed};O.prototype.reset=function(){this.OverAllTimeAvg=window.performance.now();this.SpeedSamples=[];this.FinalSpeed=0};O.prototype.ArraySum=function(c){return c?c.reduce(function(d,a){if("number"===typeof d&&"number"===typeof a)return d+a},0):0};O.prototype.AvgSpeed=function(c,d,a){this.timeNow=(window.performance.now()-this.OverAllTimeAvg)/1E3;this.FinalSpeed;this.timeNow>=a-d&&(0=l&&(clearInterval(g),G=!0,Z=void 0,d())}Pa&&(x.remEvt(e.settingsMob.el,"click",c),x.remEvt(e.settingsDesk.el,"click",c),x.remEvt(e.startButtonDesk.el,"click",d),x.remEvt(e.startButtonMob.el,"click",d),x.remEvt(document,"keypress",a),Pa=!1);if(0<=Z){G=!1;e.userInterface();ua=!1;var l=Math.ceil(Math.abs(Z));e.showStatus("Automatic Test Starts in ...");var g=setInterval(b,1E3)}"fetch"===openSpeedTestServerList&&!0===G&&(G=!1,e.showStatus("Fetching Server Info.."),Y(6));!0===G&&("Ping"=== -A?f():"Download"===A?f():"Upload"===A?f():!1===A&&f())}function a(b){"Enter"===b.key&&d()}function f(){ua&&(e.userInterface(),ua=!1);t()}function t(){function b(){va=window.performance.now();n()}"widget"!==openChannel&&"web"!==openChannel||Y(1);var l=setInterval(function(){"Loaded"===m&&(m="busy",Q());"Ping"===m&&(m="busy",e.showStatus("Milliseconds"));"Download"===m&&(e.showStatus("Initializing.."),x.reset(),y=ha=0,e.reset(),wa=window.performance.now(),w(),m="initDown");if("Downloading"===m){e.Symbol(0); -if(0==aa){aa=1;e.showStatus("Testing download speed..");var g=(window.performance.now()-wa)/1E3;Qa=g;e.progress(1,dlDuration+2.5);dlDuration+=g}xa=(window.performance.now()-wa)/1E3;q("dl");e.showStatus("Mbps download");e.mainGaugeProgress(y);e.LiveSpeed(y);e.Graph(y,0);R=x.AvgSpeed(y,ya,dlDuration);xa>=dlDuration&&"done"==Ma&&(A?(e.GaugeProgresstoZero(y,"SendR"),e.showStatus("All done"),e.Symbol(2)):e.GaugeProgresstoZero(y,"Upload"),e.downloadResult(R),ia=S,I=1,m="busy",y=ha=0,x.reset())}"Upload"== -m&&1===I&&(e.Symbol(1),m="initup",e.showStatus("Initializing.."),e.LiveSpeed("...","speedToZero"),za=x.uRandom(ulDataSize,b),A&&(aa=1));"Uploading"===m&&(1==aa&&(aa=2,e.showStatus("Testing upload speed.."),y=0,x.reset(),e.reset(),Ra=g=(window.performance.now()-va)/1E3,e.progress(!1,ulDuration+2.5),ulDuration+=g),e.showStatus("Mbps upload"),T=(window.performance.now()-va)/1E3,q("up"),e.mainGaugeProgress(y),e.LiveSpeed(y),e.Graph(y,1),U=x.AvgSpeed(y,Aa,ulDuration),T>=ulDuration&&1==I&&(ja=P,e.uploadResult(U), -e.GaugeProgresstoZero(y,"SendR"),za=void 0,e.showStatus("All done"),e.Symbol(2),m="busy",I=0));if("Error"===m){e.showStatus("Check your network connection status.");e.ConnectionError();m="busy";clearInterval(l);g=document.createElement("div");g.innerHTML='';var u=g.querySelector("a");e.oDoLiveSpeed.el.textContent="Network Error";var C=document.getElementById("oDoLiveSpeed");u.innerHTML=C.innerHTML; -C.innerHTML=g.innerHTML}"SendR"===m&&(e.showStatus("All done"),g=document.createElement("div"),g.innerHTML='',u=g.querySelector("a"),e.oDoLiveSpeed.el.textContent=fb,C=document.getElementById("oDoLiveSpeed"),u.innerHTML=C.innerHTML,C.innerHTML=g.innerHTML,location.hostname!=Ba.toLowerCase()+Sa?(ba="https://"+Ba.toLowerCase()+Sa+"/results/show.php?&d="+R.toFixed(3)+"&u="+U.toFixed(3)+"&p="+ -Ca+"&j="+Da+"&dd="+(ia/1048576).toFixed(3)+"&ud="+(ja/1048576).toFixed(3)+"&ua="+Ta,ba=encodeURI(ba),g=document.getElementById("resultsData"),g.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",ba),g.setAttribute("target","_blank"),saveData&&Y(5)):Y(3),m="busy",clearInterval(l))},100)}function w(){for(var b=0;b1E3*Qa+ya/2*1E3&&0===Ua&&(Ua=1,ka*=.01,ca*=.01,la=l+1E4);l>=la&&la=S?0:S-Wa;Wa=S;ca+=Va;Xa=Ea=l-Ea;Ea=l;ka+=Xa;01E3*Ra+Aa/2*1E3&&0===Ya&&(Ya=1,ma*=.1,da*=.1,na=b+1E4),b>=na&&na=P?0:P-$a,$a=P,da+=Za,ab=Fa=b-Fa,Fa=b,ma+=ab,0=g.loaded?0:g.loaded-l;if(isNaN(u)||!isFinite(u)||0>u)return!1;S+=u;l=g.loaded};z[b].onload=function(g){0===l&&(S+=g.total);"initDown"==m&&(m="Downloading");z[b]&&(z[b].abort(),z[b]=null,z[b]=void 0,delete z[b]);0===I&&M(b)};z[b].onerror=function(g){0===I&&M(b)};z[b].responseType="arraybuffer";z[b].send()}function F(b){var l=0;v[b]=new XMLHttpRequest;v[b].open("POST",Ga.Upload+"?n="+Math.random(),!0); -v[b].upload.onprogress=function(g){"initup"==m&&void 0===u&&(m="Uploading");if(T>=ulDuration)return v[b].abort(),v[b]=null,v[b]=void 0,delete v[b],!1;var u=0>=g.loaded?0:g.loaded-l;if(isNaN(u)||!isFinite(u)||0>u)return!1;P+=u;l=g.loaded};v[b].onload=function(){if(0===l&&(P+=1048576*ulDataSize,T>=ulDuration))return v[b].abort(),v[b]=null,v[b]=void 0,delete v[b],!1;if("initup"==m&&void 0===g){var g;m="Uploading"}v[b]&&(v[b].abort(),v[b]=null,v[b]=void 0,delete v[b]);1===I&&F(b)};v[b].onerror=function(g){T<= -ulDuration&&F(b)};v[b].setRequestHeader("Content-Type","application/octet-stream");0=P||v[b].send(za)}function Q(){bb=openSpeedTestServerList.length;m="Ping";performance.clearResourceTimings();if(Ha=B?J.push(.1):J.push(B);1=ra&&(ulThreads=dlThreads=ra)}if(selectServer&&("string"===typeof k.host||"string"===typeof k.h)){if("undefined"!==typeof k.host)var sa=k.host;else"undefined"!==typeof k.h&&(sa=k.h);/(?:https?):\/\/(\w+:?\w*)?(\S+)(:\d+)?(\/|\/([\w#!:.?+=&%!\-\/]))?/.test(sa)&& -(openSpeedTestServerList=[{ServerName:"Home",Download:sa+"/downloading",Upload:sa+"/upload",ServerIcon:"DefaultIcon"}])}E=parseInt(k.stress);W=parseInt(k.s);if("string"===typeof k.stress){var p=k.stress;var H=E}else"string"===typeof k.s&&(p=k.s,H=W);if(p&&stressTest){if("low"===p||"l"===p)ulDuration=dlDuration=300;if("medium"===p||"m"===p)ulDuration=dlDuration=600;if("high"===p||"h"===p)ulDuration=dlDuration=900;if("veryhigh"===p||"v"===p)ulDuration=dlDuration=1800;if("extreme"===p||"e"===p)ulDuration= -dlDuration=3600;if("day"===p||"d"===p)ulDuration=dlDuration=86400;if("year"===p||"y"===p)ulDuration=dlDuration=31557600;if(12p||5>H)upAdjust=1+E/100,dlAdjust=1+E/100}else dlAdjust=upAdjust=1;p=parseInt(k.run);H=parseInt(k.r);var Z;!enableRun||"string"!==typeof k.run&&"string"!==typeof k.r||(Z=0',createCookie("mode","dark")));"light"===a&&(nightModeMob.style.display="none",dayModeMob.style.display="inline-block",nightMode.style.display="none", -dayMode.style.display="inline-block",(darkStyle=document.getElementById("darkmode"))&&darkStyle.parentNode.removeChild(darkStyle),createCookie("mode","light"))}function toggleSkin(){(darkStyle=document.getElementById("darkmode"))?setSkin("light"):setSkin("dark")}function createCookie(a,c,b){if(b){var d=new Date;d.setTime(d.getTime()+864E5*b);b="; expires="+d.toGMTString()}else b="";document.cookie=a+"="+c+b+"; path=/"} -function getCookieValue(a,c){return(c=document.cookie.match("(^|;)\\s*"+a+"\\s*=\\s*([^;]+)"))?c.pop():""}; \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/downloading b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/downloading deleted file mode 100644 index b78c32fa..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/downloading and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/hosted.html b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/hosted.html deleted file mode 100644 index ea51ac3b..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/hosted.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - Speedtest by OpenSpeedtest.com - - - - -
- Speed Test by OpenSpeedTest™ -
- - \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/index.html b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/index.html deleted file mode 100644 index 2772b6b6..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/index.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - SpeedTest by OpenSpeedTest™ - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
- - -
- SpeedTest by OpenSpeedTest™ is a Free and Open-Source HTML5 Network Speed Test Software. -

© Copyright 2013-2024 OpenSpeedTest™ All Rights Reserved.

-
- - - - - - diff --git a/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/upload b/data/agent/var/lib/openflare/pages/deployments/3/releases/4b4e50095b4b0ea6fac74ad141dae50c3497481a0710b47e30b0f579951d55c6/upload deleted file mode 100644 index e69de29b..00000000 diff --git a/data/agent/var/lib/openflare/pages/deployments/4/current/.openflare-pages.json b/data/agent/var/lib/openflare/pages/deployments/4/current/.openflare-pages.json deleted file mode 100644 index 08ec7e7b..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/4/current/.openflare-pages.json +++ /dev/null @@ -1 +0,0 @@ -{"deployment_id":4,"checksum":"50cf372aadd13c763da8c8657a374cdd70b14e7c2d89d9ad35f53c62561f73d2"} \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/4/current/__MACOSX/._build b/data/agent/var/lib/openflare/pages/deployments/4/current/__MACOSX/._build deleted file mode 100755 index a869c39e..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/4/current/__MACOSX/._build and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/4/releases/50cf372aadd13c763da8c8657a374cdd70b14e7c2d89d9ad35f53c62561f73d2/.openflare-pages.json b/data/agent/var/lib/openflare/pages/deployments/4/releases/50cf372aadd13c763da8c8657a374cdd70b14e7c2d89d9ad35f53c62561f73d2/.openflare-pages.json deleted file mode 100644 index 08ec7e7b..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/4/releases/50cf372aadd13c763da8c8657a374cdd70b14e7c2d89d9ad35f53c62561f73d2/.openflare-pages.json +++ /dev/null @@ -1 +0,0 @@ -{"deployment_id":4,"checksum":"50cf372aadd13c763da8c8657a374cdd70b14e7c2d89d9ad35f53c62561f73d2"} \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/4/releases/50cf372aadd13c763da8c8657a374cdd70b14e7c2d89d9ad35f53c62561f73d2/__MACOSX/._build b/data/agent/var/lib/openflare/pages/deployments/4/releases/50cf372aadd13c763da8c8657a374cdd70b14e7c2d89d9ad35f53c62561f73d2/__MACOSX/._build deleted file mode 100755 index a869c39e..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/4/releases/50cf372aadd13c763da8c8657a374cdd70b14e7c2d89d9ad35f53c62561f73d2/__MACOSX/._build and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/5/current/.openflare-pages.json b/data/agent/var/lib/openflare/pages/deployments/5/current/.openflare-pages.json deleted file mode 100644 index 897af358..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/5/current/.openflare-pages.json +++ /dev/null @@ -1 +0,0 @@ -{"deployment_id":5,"checksum":"50cf372aadd13c763da8c8657a374cdd70b14e7c2d89d9ad35f53c62561f73d2"} \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/5/current/__MACOSX/._build b/data/agent/var/lib/openflare/pages/deployments/5/current/__MACOSX/._build deleted file mode 100755 index a869c39e..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/5/current/__MACOSX/._build and /dev/null differ diff --git a/data/agent/var/lib/openflare/pages/deployments/5/releases/50cf372aadd13c763da8c8657a374cdd70b14e7c2d89d9ad35f53c62561f73d2/.openflare-pages.json b/data/agent/var/lib/openflare/pages/deployments/5/releases/50cf372aadd13c763da8c8657a374cdd70b14e7c2d89d9ad35f53c62561f73d2/.openflare-pages.json deleted file mode 100644 index 897af358..00000000 --- a/data/agent/var/lib/openflare/pages/deployments/5/releases/50cf372aadd13c763da8c8657a374cdd70b14e7c2d89d9ad35f53c62561f73d2/.openflare-pages.json +++ /dev/null @@ -1 +0,0 @@ -{"deployment_id":5,"checksum":"50cf372aadd13c763da8c8657a374cdd70b14e7c2d89d9ad35f53c62561f73d2"} \ No newline at end of file diff --git a/data/agent/var/lib/openflare/pages/deployments/5/releases/50cf372aadd13c763da8c8657a374cdd70b14e7c2d89d9ad35f53c62561f73d2/__MACOSX/._build b/data/agent/var/lib/openflare/pages/deployments/5/releases/50cf372aadd13c763da8c8657a374cdd70b14e7c2d89d9ad35f53c62561f73d2/__MACOSX/._build deleted file mode 100755 index a869c39e..00000000 Binary files a/data/agent/var/lib/openflare/pages/deployments/5/releases/50cf372aadd13c763da8c8657a374cdd70b14e7c2d89d9ad35f53c62561f73d2/__MACOSX/._build and /dev/null differ diff --git a/data/flared/flared-state.json b/data/flared/flared-state.json deleted file mode 100644 index 5051c7ad..00000000 --- a/data/flared/flared-state.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "Version": "20260604-002", - "Checksum": "47362a4edad373922a14ee157a5b8c9db99432cde75f7ed7533b7e5cf8f4202d" -} \ No newline at end of file diff --git a/data/flared/frpc_node-34153b9566e6f8a634d1f1da689a0d45.toml b/data/flared/frpc_node-34153b9566e6f8a634d1f1da689a0d45.toml deleted file mode 100644 index 534cc2c5..00000000 --- a/data/flared/frpc_node-34153b9566e6f8a634d1f1da689a0d45.toml +++ /dev/null @@ -1,12 +0,0 @@ -serverAddr = "127.0.0.1" -serverPort = 17000 -auth.method = "token" -auth.token = "f977f20f25883e624eb83ee9be84926c" - -[[proxies]] -name = "node-d3dbcc88b3cc381c004bf04eafbfbbdb-local2-arctel-de" -type = "http" -localIP = "abc" -localPort = 5666 -customDomains = ["local2.arctel.de"] - diff --git a/data/relay/frps.toml b/data/relay/frps.toml deleted file mode 100644 index 386c92cf..00000000 --- a/data/relay/frps.toml +++ /dev/null @@ -1,11 +0,0 @@ -bindPort = 17000 -vhostHTTPPort = 8080 -[auth] -method = "token" -token = "f977f20f25883e624eb83ee9be84926c" - -[webServer] -addr = "0.0.0.0" -port = 17500 -user = "admin" -password = "85464eeb72c49abc430569d6b9c77f78" diff --git a/data/relay/relay-state.json b/data/relay/relay-state.json deleted file mode 100644 index 4839fd46..00000000 --- a/data/relay/relay-state.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "last_auth_token": "", - "last_profile_fingerprint": "8b44bae0a094467933f9ddeb35b11451dbabbb231e710902587cb791917c3e9f", - "last_cpu_stat_total": 1696, - "last_cpu_stat_idle": 1267, - "last_metric_at_unix": 1780827595 -} \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 8f3e198f..f21cb6c2 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -78,7 +78,7 @@ services: environment: CLICKHOUSE_DB: ${CLICKHOUSE_DB:-openflare} CLICKHOUSE_USER: ${CLICKHOUSE_USER:-default} - CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-} + CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-123456} CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: 1 TZ: ${TZ:-Asia/Shanghai} ports: diff --git a/docs/changelog/index.md b/docs/changelog/index.md index 51928a18..0986bc1b 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -18,99 +18,19 @@ sidebar: false ### 变更 -- 边缘组件运行时去重:新增 `internal/apps/edge/` 共享包(`updater`、`httpclient`、`nodeip`、`logging`、`heartbeat/autoupdate`、`runner`),Agent/Relay/Flared 三组件改为薄包装委托,删除约 1100 行重复自更新与 HTTP 传输层代码。 -- 边缘运行时 Phase 3 Batch 1:抽取 `edge/config/duration`、`edge/observability/linux`、`edge/heartbeat/loop`,统一 MillisecondDuration、Linux 指标采集与 relay/flared 心跳循环。 -- 边缘运行时 Phase 3 Batch 2:Agent 心跳周期下沉至 `heartbeat/cycle.go`;`pkg/protocol/agent.go` 统一 Agent 客户端协议类型。 -- 边缘运行时 Phase 3 Batch 3(T6):Server 侧 `openflare/agent`、`relay`、`flared` 协议类型统一为 `pkg/protocol` 别名,消除与客户端的重复定义。 -- 边缘运行时 Phase 3 Batch 3(T7):新增 `edge/wsclient`,Agent/Relay/Flared 三组件 `wsclient/` 改为 Preset 薄包装,删除约 150 行重复 WebSocket 传输层代码。 -- 合并并简化仓库结构:将 `openflare-server` 单体目录下的所有文件/目录提升至仓库根目录(去除了 `openflare-server` 嵌套层级),保留 `.github` 目录不变;统一配置 `docker-compose.yaml` 及所有 Dockerfile 的构建上下文为根目录。 -- 调整子项目结构与包路径:将 `agent`、`relay` 和 `flared` 子项目从 `internal/` 移动至 `internal/apps/`(分别为 `internal/apps/agent`、`internal/apps/relay` 和 `internal/apps/flared`),并递归更新了所有涉及的 Go 导入路径(如 `github.com/Rain-kl/Wavelet/internal/apps/agent` 等)。 -- 调整编译产物输出名称与 Makefile: - - 更新 `Makefile` 编译目标,在 `bin/` 目录下统一输出 `openflare-server`(而不是 `wavelet`)、`openflare-agent`、`openflare-relay` 及 `flared`。 - - 新增 `build-all` 编译目标以一键按序编译所有服务端、客户端和网关组件。 -- 修复 `internal/apps/openflare/tasks/ssl_renew_test.go` 中在未模拟 ACME 证书续期调用时测试执行的并发/竞态问题。 -- `of_node_access_logs` 从 PostgreSQL/SQLite 迁移至 ClickHouse(数据库 `openflare`);ClickHouse 表结构改由 goose 独立迁移管线管理(`goose_clickhouse_version`),启动时在 `migrator.MigrateClickHouse()` 中执行,移除 `clickhouse_schema.go` 手写 DDL。 -- ClickHouse 分析库接入 GORM(`db.ChDB`)与 `internal/repository/analytics/`:用户访问日志(`w_user_access_logs`)读写、风控批量写入与管理端日志统计 API 统一经 repository 访问;新增 `internal/model/analytics/` 分析表模型。 -- OpenFlare 节点访问日志(`of_node_access_logs`)ClickHouse 读写迁入 `internal/repository/analytics/`;`model` 层保留聚合编排与内存测试 store,生产路径经薄适配器调用 repository。 -- ClickHouse 默认启用(`clickhouse.enabled: true`),`docker-compose` 默认启动 `clickhouse` 服务并纳入 `wavelet` 健康依赖。 ### 移除 -- 删除旧版 `openflare-server/` 单体实现(含 `web/` 前端);仓库内 `openflare-server/` 现指迁移后的 Wavelet 统一控制面。 -- 移除 Wavelet About 界面相关代码:删除 `AboutService` 与 `GET /api/v1/d/about` 接口(页面从未实现,属冗余遗留)。 -- 移除 Wavelet 中从旧系统迁移但未实装的全局 API / Web / 敏感接口限流选项(`GlobalApiRateLimit*`、`GlobalWebRateLimit*`、`CriticalRateLimit*`)及相关校验与数据库种子。 -- 移除 `internal/apps/openflare/legacy/` 控制台兼容层、`compat/auth.go`(`OpenFlare-Token` JWT 桥接)及前端 `legacy-base.service.ts`;`openflare-server/web` 不再能对接当前 Wavelet 后端管理 API。 -- 移除旧 OpenFlare 服务升级兼容层(`/api/v1/openflare/update/*`);统一使用 Wavelet 框架 `admin/updater`(`/api/v1/admin/update`)。 ### 新增 -- Wavelet 总览仪表盘图表对齐旧前端 ECharts 样式:24 小时请求/容量趋势补充请求量与错误量展示,新增网络与磁盘趋势、Top 节点榜单、来源分布、状态码分布与 Top Domain 板块。 -- Wavelet 总览仪表盘迁移旧前端「全球态势板」:世界地图展示节点健康与来源国家热度,并保留在线覆盖、流量与容量摘要指标。 -- Wavelet 全球态势板视觉对齐系统风格:改用 `Card` / `Badge` / `Progress` 与虚线边框指标卡,地图配色与 tooltip 跟随主题 token。 -- Wavelet 全球态势板布局重构:关键指标前置为摘要栅格,地图与容量诊断并排展示,移动端自动堆叠。 -- 修复 Wavelet 全球态势板 ECharts 地图在紧凑布局下容器尺寸为 0 导致无法渲染的问题。 -- Wavelet 全球态势板地图容器调整为约 2:3(宽:高)比例,避免世界地图横向过度拉伸。 -- Wavelet WAF / 代理规则认证实装完整 PoW 配置面板:支持难度、算法、TTL 与路径/IP/UA 黑白名单编辑。 -- Wavelet WAF 新建/编辑规则组改用右侧 Sheet 抽屉,替代居中弹窗,与站点绑定等操作保持一致的 shadcn 侧栏交互。 - -- 将 `openflare-server` 后端业务域迁移至 `Wavelet/internal/apps/openflare/`;管理控制台 API 挂载于 `/api/v1/d/*`,节点协议挂载于 `/api/v1/agent|relay|tunnel/*`。 -- 新增 OpenFlare 业务表 goose 迁移(`of_options`、`of_origins`、`of_proxy_routes`、`of_nodes`、`of_waf_*`、`of_tls_*`、`of_config_versions`、`of_pages_*`、`of_apply_logs` 等)。 -- 重叠职能复用 Wavelet 内置用户/OAuth/Cap/认证源能力;新增 `integration` 包覆盖认证、核心链路、安全、Agent 协议集成测试。 -- 修复 Wavelet 引入 `openflare` 模块后 `gomodule/redigo v2.0.0+incompatible` 导致 `redistore` 编译失败的问题。 -- Wavelet 后端默认监听端口由 `:8000` 调整为 `:3000`,与旧 OpenFlare Server 保持一致。 -- 新增 OpenFlare 可观测性表 goose 迁移(`of_node_system_profiles`、`of_node_metric_snapshots`、`of_node_request_reports`、`of_node_health_events`、`of_node_obs_openresty`、`of_node_obs_frps`、`of_node_access_logs`)。 -- Agent heartbeat 恢复可观测性数据持久化(系统画像、指标快照、流量报表、健康事件等)。 -- Wavelet 默认数据库名由 `wavelet` 调整为 `openflare`(PostgreSQL、ClickHouse、SQLite 后备路径同步更新)。 -- Wavelet 默认 PostgreSQL `application_name` 由 `wavelet-server` 调整为 `openflare-server`,Redis 键前缀由 `wavelet:` 调整为 `openflare:`。 -- 实装 Agent WAF IP 组同步(heartbeat `waf_ip_groups` 增量下发与 `/api/v1/agent/waf/ip-groups/sync`)。 -- 实装 Pages Agent 部署包下载(`/api/v1/agent/pages/deployments/:deployment_id/package` 二进制响应)。 - -- 实装访问日志单表查询层(列表、折叠、IP 汇总/趋势、地域统计)及 `(node_id, logged_at)` 复合索引。 -- 扩展 Relay/Tunnel heartbeat 载荷与可观测性持久化(frps 观测、健康事件);新增 `of_node_obs_frpc` 单表。 -- Agent heartbeat 恢复 Geo 自动更新、访问日志地域解析与 90 天保留清理;对齐 config `support_files` 过滤规则。 -- OAuth 登录/绑定统一由 Wavelet `/api/v1/oauth/:source/*` 提供;旧 `/api/oauth/*` 快捷路径未保留。 -- 新增 `internal/apps/openflare/tasks/` 集中承载 OpenFlare 定时/后台任务业务逻辑;调度已迁入 Wavelet Asynq 异步任务框架(`async_tasks.go` + `w_schedules` 种子迁移),含数据库可观测性自动清理、WAF IP 组周期同步、UptimeKuma 同步、ACME 证书自动续期。 -- Pages 部署包上传改为通过 Wavelet `upload.Ingest` 写入本地文件存储框架,部署记录关联 `upload_id`;Agent 下载改为从存储后端流式读取。 -- 实装数据库可观测性手动/自动清理、WAF IP 组订阅/自动同步与测试接口、UptimeKuma 监控同步、TLS ACME 申请/续期(lego DNS-01)。 -- 修复 Wavelet Agent WebSocket 未处理 `status` 消息导致 WS 模式下 `last_seen_at` 停止更新、节点超时显示离线的问题;列表「最近心跳」恢复显示「WS 已连接」。 -- 修复节点「强制同步」仍为 stub 导致始终返回「节点不在线或通过 WebSocket 发送同步指令失败」的问题。 -- 将 OpenFlare 管理控制台从 `openflare-server/web` 迁移至 `Wavelet/frontend/`,复用 Wavelet shadcn 组件与 Session 鉴权;Service 层对接 `/api/v1/d/*`(`OpenFlareBaseService`)。 -- 实装节点、代理规则(6 Section)、配置发布、WAF、网站/证书/DNS、Pages、源站、访问/应用日志、仪表盘、性能调优等业务页面;Admin 设置新增 OpenFlare 运维扩展 Tab。 -- 修复静态导出构建中 `useSearchParams` 未包裹 Suspense 的问题;`pnpm build:embed` 全量 46 页通过。 -- 补全前端迁移收尾:新增 `/openflare/about` 关于页与 `AboutService`;补全 `UpdateService` 升级流程(在线升级、手动上传、WebSocket 日志流)及顶栏版本升级入口。 -- 前端 UI 打磨:节点/WAF/DNS/配置清理等 Dialog 补 RHF+Zod;代理规则详情与访问日志 Tab 补 error 态;`pnpm build:embed` 增至 47 静态页。 -- 修复开发模式下 `/api/*` 尾斜杠引发 Next.js 308 与 Gin 301 循环重定向:前端 Service 与 `api-client` 请求拦截器规范化路径、强制浏览器同源代理、Next `skipTrailingSlashRedirect`、后端 `RegisterCollection` 双路径注册;Access Token 管理员权限校验对齐 `token_admin`。 -- 补全 Wavelet 边缘节点详情数据看板:迁移运行诊断摘要、系统信息、实时资源、网络流量、24 小时请求/容量/网络/磁盘趋势、请求结构分布与健康事件时间线。 -- 节点详情页改为 Tabs 布局(概览 / 数据看板 / 配置与部署),统一 Edge/Relay/Tunnel 详情壳层并支持 `tab` URL 参数深链。 -- 节点详情 24 小时网络/磁盘 IO 趋势图对齐原版 ECharts 样式,恢复系列摘要卡与平滑面积折线图。 -- 侧栏新增「网站管理」折叠组(shadcn Collapsible + SidebarMenuSub),收纳网站、证书、DNS、源站入口。 -- 移除网站、证书、DNS 页面右上角互相跳转按钮,改由侧栏折叠组统一导航。 -- 侧栏导航改为 `openflareSidebarNav` 单一配置源,折叠组位置由数组顺序决定,移除 `slice(0, 4)` 硬编码切分。 -- 侧栏新增「安全性」折叠组,收纳 WAF 与 IP 组入口;同步移除两页右上角互相跳转按钮。 -- OpenFlare 前端路由去除 `/openflare` 前缀,业务页面直接挂载于 `/`(如 `/nodes`、`/websites`);侧栏移除「首页」「我的文件」;旧路径 `/openflare/*`、`/home` 永久重定向至新路径。 ### 变更 -- 将 `Wavelet/` 目录重命名为 `openflare-server/`,完成 OpenFlare 后端与前端迁移后的仓库结构收敛;同步更新 Server Docker/Release 工作流与部署文档中的路径(`frontend/`、`docker/Dockerfile`)。 -- 补齐 OpenFlare Swagger 文档:新增 Agent/Relay/Tunnel 协议端点(13 个 `/api/v1/agent|relay|tunnel/*`)注解与安全定义(`AgentTokenAuth`、`TunnelTokenAuth`);管理端注解将权限不足响应由 403 统一为 404。 -- Wavelet API 路径统一:管理端由 `/api/v1/openflare/*` 调整为 `/api/v1/d/*`;Agent/Relay/Tunnel 协议路由分别迁移至 `/api/v1/agent/*`、`/api/v1/relay/*`、`/api/v1/tunnel/*`(原 `/api/flared/*`)。同步更新 Wavelet 前端服务层与 `openflare-agent`、`openflare-relay`、`openflared` 客户端连接端点。 -- Agent/Relay/Tunnel 协议 API 响应格式对齐 Wavelet `{error_msg, data}`:服务端移除 `compat` 包,业务错误改为 HTTP 4xx + `error_msg`;同步更新 `openflare-agent`、`openflare-relay`、`openflared` HTTP 客户端解析逻辑。 -- OpenFlare 管理端权限模型对齐 Wavelet:取消旧系统 Admin/Root 三级角色区分,统一以 `user.IsAdmin` 为管理门槛;Access Token 访问敏感接口(Option、Update 等)须 `token_admin=true`;权限不足返回 HTTP 404 + `error_msg`,参数错误返回 HTTP 400 + `error_msg`(`apiutil.AdminMiddlewares` = `oauth.LoginRequired` + `admin.LoginAdminRequired`)。 -- 移除 Wavelet 顶栏 OpenFlare 服务端版本入口按钮;版本升级能力保留在 Admin 设置 OpenFlare 运维 Tab。 -- 将 Wavelet 默认上游仓库调整为 `Rain-kl/OpenFlare`,站点名称、邮件模板、前端默认标题与页脚品牌统一初始化为 OpenFlare;新增 goose 迁移回填既有环境的旧 Wavelet 默认值。 -- Wavelet WAF IP 组列表新增「查看」操作,支持在查看弹窗中浏览当前 IP 并移除单条 IP;自动类型同步裁剪 `ext_ips` 以与 `ip_list` 保持一致。 -- 系统默认关闭用户自主注册(`registration_enabled` / `password_register_enabled`),默认开启登录 Cap 人机验证(`cap_login_enabled`)。 -- 将 OpenFlare 业务 API 路由注册从 `internal/apps/openflare/legacy` 迁移至 `internal/router/openflare`,与 Wavelet 路由分层规范对齐。 -- 移除 `internal/router/openflare` 中与 Wavelet `/api/v1` 重复的 legacy 认证路由(用户登录/注册、OAuth、Cap、认证源等),统一由 Wavelet 内置用户/OAuth/Cap 模块提供。 -- OpenFlare 路由注册统一收敛至 `internal/router/v1/openflare/`(`register_*.go` + `RegisterV1Routes`);`internal/apps/openflare/*/routers.go` 仅保留 Handler 与业务逻辑。 -- 将 OpenFlare 路由注册包从 `internal/router/openflare/` 迁移至 `internal/router/v1/openflare/`,与 v1 路由分层目录结构对齐。 -- OpenFlare 管理端路由注册从 `RegisterCustomRoutes` 移至 `v1.RegisterV1Routes`,API 前缀由 `/api/v1/custom/openflare` 调整为 `/api/v1/openflare`。 -- OpenFlare 管理控制台 API 统一迁移至 `/api/v1/d/*`,响应格式对齐 Wavelet `{error_msg, data}` + `response.Abort*`。 -- 节点协议路由迁移至 `/api/v1/agent|relay|tunnel/*`(`flared` 更名为 `tunnel`);协议层响应格式统一为 Wavelet `{error_msg, data}` + `response.Abort*`,移除 `compat` 遗留信封。 -- 控制台鉴权改为 `apiutil.AdminMiddlewares()`(`oauth.LoginRequired` + `admin.LoginAdminRequired`;Session / Access Token)。 -- 前端 OpenFlare Service 层切换为 `OpenFlareBaseService`(`BaseService` + `/api/v1/d/*`)。 -- 为 OpenFlare 管理端 API 补充 Swagger 注解(约 99 个端点)。 -- 移除已无引用的 `internal/apps/openflare/auth/`、`compat/routes.go`。 + +### 修复 + +- 修复访问日志 ClickHouse 聚合查询因 `trim(x) AS x` 别名与表列同名导致总览看板地域分布及 IP 统计失败的问题。 ## [v2.3.4] - 2026-06-17 diff --git a/frontend/app/(main)/nodes/components/node-detail-shell.tsx b/frontend/app/(main)/nodes/components/node-detail-shell.tsx index e1c2054a..134f51b5 100644 --- a/frontend/app/(main)/nodes/components/node-detail-shell.tsx +++ b/frontend/app/(main)/nodes/components/node-detail-shell.tsx @@ -88,8 +88,6 @@ export function NodeDetailShell({ [pathname, router, searchParams], ); - const activeTabMeta = TAB_CONFIGS.find((tab) => tab.id === activeTab) ?? TAB_CONFIGS[0]; - return (
diff --git a/frontend/lib/navigation/openflare-nav.ts b/frontend/lib/navigation/openflare-nav.ts index 0ee9eb2c..395685a6 100644 --- a/frontend/lib/navigation/openflare-nav.ts +++ b/frontend/lib/navigation/openflare-nav.ts @@ -76,7 +76,11 @@ export const openflareSidebarNav: OpenFlareSidebarNavEntry[] = [ /** 扁平菜单项(供路由判断等逻辑复用) */ export const openflareNavItems: OpenFlareNavItem[] = openflareSidebarNav .filter((entry): entry is {kind: 'item'} & OpenFlareNavItem => entry.kind === 'item') - .map(({kind: _kind, ...item}) => item); + .map((entry) => { + const {kind, ...item} = entry; + void kind; + return item; + }); /** 网站模块页内二级导航 */ export const openflareWebsiteSubNav = [ diff --git a/internal/apps/agent/agent/runner.go b/internal/apps/agent/agent/runner.go index b4a44de8..36622cd0 100644 --- a/internal/apps/agent/agent/runner.go +++ b/internal/apps/agent/agent/runner.go @@ -1,3 +1,4 @@ +// Package agent implements the local OpenFlare agent runtime loop. package agent import ( @@ -16,12 +17,14 @@ import ( edgeheartbeat "github.com/Rain-kl/Wavelet/internal/apps/edge/heartbeat" ) +// HeartbeatService handles node registration and periodic heartbeat reporting. type HeartbeatService interface { Register(ctx context.Context, payload protocol.NodePayload) (*protocol.RegisterNodeResponse, error) Heartbeat(ctx context.Context, payload protocol.NodePayload) (*protocol.HeartbeatResult, error) SetToken(token string) } +// SyncService handles configuration synchronisation between the agent and the server. type SyncService interface { SyncOnStartup(ctx context.Context, target *protocol.ActiveConfigMeta) error SyncOnce(ctx context.Context, target *protocol.ActiveConfigMeta) error @@ -30,30 +33,36 @@ type SyncService interface { ApplyWAFIPGroups(ctx context.Context, groups []protocol.WAFIPGroup) error } +// RuntimeManager manages the lifecycle and health checks of the OpenResty runtime. type RuntimeManager interface { CheckHealth(ctx context.Context) error Restart(ctx context.Context) error } +// WebSocketService manages the persistent WebSocket connection to the server. type WebSocketService interface { Connect(ctx context.Context) (protocol.WebSocketConnection, error) SetToken(token string) URL() string } +const websocketBackoffDefaultDelay = 30 * time.Second + +// Runner coordinates the agent's heartbeat, configuration sync, and WebSocket upgrade lifecycle. type Runner struct { - Config *config.Config - StateStore *state.Store - HeartbeatCycle *agentheartbeat.Cycle - HeartbeatService HeartbeatService - SyncService SyncService - RuntimeManager RuntimeManager - WebSocketService WebSocketService + Config *config.Config + StateStore *state.Store + HeartbeatCycle *agentheartbeat.Cycle + HeartbeatService HeartbeatService + SyncService SyncService + RuntimeManager RuntimeManager + WebSocketService WebSocketService restartOpenrestyNow bool websocketUpgradeEnabled bool } +// Run starts the agent's main loop, performing heartbeats and upgrading to WebSocket when available. func (r *Runner) Run(ctx context.Context) error { if r.HeartbeatCycle != nil { r.HeartbeatCycle.RecordSyncError = r.recordSyncError @@ -63,13 +72,7 @@ func (r *Runner) Run(ctx context.Context) error { return err } slog.Info("agent runner started", "node_id", nodeID, "node", r.Config.NodeName, "ip", r.Config.NodeIP) - if r.hasAccessToken() { - if _, hbErr := r.performHeartbeatCycle(ctx, nodeID, true); hbErr != nil { - slog.Error("agent startup heartbeat failed", "error", hbErr) - } - } else if err = r.tryRegister(ctx, &nodeID); err != nil { - slog.Error("agent initial discovery register failed", "error", err) - } + r.runStartupAuth(ctx, &nodeID) heartbeatTicker := time.NewTicker(r.Config.HeartbeatInterval.Duration()) defer heartbeatTicker.Stop() @@ -108,42 +111,66 @@ func (r *Runner) Run(ctx context.Context) error { delay := wsBackoff.Next() nextWSAttempt = time.Now().Add(delay) slog.Debug("agent ws disconnected; resuming http heartbeat", "retry_after", delay, "error", wsErr) - if r.hasAccessToken() { - if _, hbErr := r.performHeartbeatCycle(ctx, nodeID, false); hbErr != nil { - slog.Error("agent heartbeat after ws disconnect failed", "error", hbErr) - } - } + r.handleWSDisconnect(ctx, nodeID) case <-heartbeatTicker.C: if wsDone != nil { continue } - if !r.hasAccessToken() { - if err = r.tryRegister(ctx, &nodeID); err != nil { - slog.Error("agent discovery register failed", "error", err) - } - continue - } - if changed, hbErr := r.performHeartbeatCycle(ctx, nodeID, false); hbErr != nil { - slog.Error("agent heartbeat failed", "error", hbErr) - } else { - if changed { - heartbeatTicker.Reset(r.Config.HeartbeatInterval.Duration()) - } - tryStartWebSocket() - } + r.handleHeartbeatTick(ctx, &nodeID, heartbeatTicker, tryStartWebSocket) } } } +func (r *Runner) runStartupAuth(ctx context.Context, nodeID *string) { + if r.hasAccessToken() { + if _, hbErr := r.performHeartbeatCycle(ctx, *nodeID, true); hbErr != nil { + slog.Error("agent startup heartbeat failed", "error", hbErr) + } + return + } + if err := r.tryRegister(ctx, nodeID); err != nil { + slog.Error("agent initial discovery register failed", "error", err) + } +} + +func (r *Runner) handleWSDisconnect(ctx context.Context, nodeID string) { + if !r.hasAccessToken() { + return + } + if _, hbErr := r.performHeartbeatCycle(ctx, nodeID, false); hbErr != nil { + slog.Error("agent heartbeat after ws disconnect failed", "error", hbErr) + } +} + +func (r *Runner) handleHeartbeatTick(ctx context.Context, nodeID *string, heartbeatTicker *time.Ticker, tryStartWebSocket func()) { + if !r.hasAccessToken() { + if err := r.tryRegister(ctx, nodeID); err != nil { + slog.Error("agent discovery register failed", "error", err) + } + return + } + changed, hbErr := r.performHeartbeatCycle(ctx, *nodeID, false) + if hbErr != nil { + slog.Error("agent heartbeat failed", "error", hbErr) + return + } + if changed { + heartbeatTicker.Reset(r.Config.HeartbeatInterval.Duration()) + } + tryStartWebSocket() +} + func (r *Runner) performHeartbeatCycle(ctx context.Context, nodeID string, startup bool) (bool, error) { r.refreshOpenrestyHealth(ctx) return r.HeartbeatCycle.Perform(ctx, nodeID, startup, r) } +// Apply applies the provided agent settings and reports whether the heartbeat interval changed. func (r *Runner) Apply(settings *protocol.AgentSettings) bool { return r.applySettings(settings) } +// RestartOpenrestyIfNeeded restarts OpenResty when a server-requested restart is pending. func (r *Runner) RestartOpenrestyIfNeeded(ctx context.Context) { r.tryRestartOpenresty(ctx) } @@ -251,7 +278,7 @@ func (r *Runner) runWebSocket(ctx context.Context, nodeID string, conn protocol. func (r *Runner) sendWebSocketStatus(ctx context.Context, nodeID string, conn protocol.WebSocketConnection) error { r.refreshOpenrestyHealth(ctx) - payload, ackWindows := r.HeartbeatCycle.PrepareHeartbeatPayload(nodeID) + payload, ackWindows := r.HeartbeatCycle.PrepareHeartbeatPayload(ctx, nodeID) if err := conn.SendStatus(payload); err != nil { return err } @@ -338,7 +365,7 @@ func newWebSocketBackoff() *webSocketBackoff { func (backoff *webSocketBackoff) Next() time.Duration { if backoff == nil || len(backoff.delays) == 0 { - return 30 * time.Second + return websocketBackoffDefaultDelay } if backoff.index >= len(backoff.delays) { return backoff.delays[len(backoff.delays)-1] @@ -402,7 +429,7 @@ func (r *Runner) tryRegister(ctx context.Context, nodeID *string) error { return errors.New("agent_token 为空且未配置 discovery_token") } slog.Info("agent discovery registration started") - response, err := r.HeartbeatService.Register(ctx, r.HeartbeatCycle.NodePayload(*nodeID)) + response, err := r.HeartbeatService.Register(ctx, r.HeartbeatCycle.NodePayload(ctx, *nodeID)) if err != nil { return err } @@ -501,4 +528,4 @@ func (r *Runner) recordOpenrestyUnhealthy(err error, fallbackOnly bool) { if saveErr := r.StateStore.Save(snapshot); saveErr != nil { slog.Error("save state after recording openresty error failed", "error", saveErr) } -} \ No newline at end of file +} diff --git a/internal/apps/agent/agent/runner_test.go b/internal/apps/agent/agent/runner_test.go index d81b9110..615aea4f 100644 --- a/internal/apps/agent/agent/runner_test.go +++ b/internal/apps/agent/agent/runner_test.go @@ -395,7 +395,7 @@ func TestRunnerHeartbeatPayloadIncludesObservabilityExtensions(t *testing.T) { t.Fatalf("failed to prepare access log: %v", err) } - firstPayload := runner.HeartbeatCycle.NodePayload("node-observe") + firstPayload := runner.HeartbeatCycle.NodePayload(context.Background(), "node-observe") if firstPayload.Profile == nil { t.Fatal("expected first heartbeat payload to include system profile") } @@ -412,7 +412,7 @@ func TestRunnerHeartbeatPayloadIncludesObservabilityExtensions(t *testing.T) { t.Fatalf("expected health events for openresty and sync error, got %+v", firstPayload.HealthEvents) } - secondPayload := runner.HeartbeatCycle.NodePayload("node-observe") + secondPayload := runner.HeartbeatCycle.NodePayload(context.Background(), "node-observe") if secondPayload.Profile != nil { t.Fatal("expected unchanged profile to be omitted on subsequent heartbeat") } diff --git a/internal/apps/agent/config/config.go b/internal/apps/agent/config/config.go index 0e2054e5..ea1f2c0c 100644 --- a/internal/apps/agent/config/config.go +++ b/internal/apps/agent/config/config.go @@ -1,3 +1,4 @@ +// Package config loads and persists agent daemon configuration. package config import ( @@ -30,8 +31,12 @@ const ( defaultObservabilityReplayMinutes = 15 defaultMMDBUpdateInterval = 24 * time.Hour defaultMMDBDownloadURL = "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/GeoLite2-Country.mmdb" + defaultHeartbeatInterval = 10 * time.Second + defaultRequestTimeout = 10 * time.Second + configFilePerm = 0o600 ) +// Config holds the full runtime configuration for the OpenFlare agent. type Config struct { ServerURL string `json:"server_url"` AccessToken string `json:"agent_token"` @@ -61,7 +66,7 @@ type Config struct { StatePath string `json:"state_path"` HeartbeatInterval MillisecondDuration `json:"heartbeat_interval"` RequestTimeout MillisecondDuration `json:"request_timeout"` - configPath string + configPath string `json:"-"` } type configFile struct { @@ -93,8 +98,17 @@ type configFile struct { RequestTimeout MillisecondDuration `json:"request_timeout"` } +func transferPersistedConfig(dst, src any) error { + data, err := json.Marshal(src) + if err != nil { + return err + } + return json.Unmarshal(data, dst) +} + +// Load reads and parses the agent configuration file at the given path. func Load(path string) (*Config, error) { - data, err := os.ReadFile(path) + data, err := os.ReadFile(path) //nolint:gosec // path is the configured agent config location if err != nil && !os.IsNotExist(err) { return nil, err } @@ -107,33 +121,11 @@ func Load(path string) (*Config, error) { if err != nil && !hasEnvConfig() { return nil, err } - cfg := &Config{ - ServerURL: file.ServerURL, - AccessToken: file.AccessToken, - DiscoveryToken: file.DiscoveryToken, - NodeName: file.NodeName, - NodeIP: file.NodeIP, - OpenrestyPath: file.OpenrestyPath, - OpenrestyResolvers: append([]string{}, file.OpenrestyResolvers...), - DataDir: file.DataDir, - MainConfigPath: file.MainConfigPath, - RouteConfigPath: file.RouteConfigPath, - AccessLogPath: file.AccessLogPath, - CertDir: file.CertDir, - OpenrestyCertDir: file.OpenrestyCertDir, - LuaDir: file.LuaDir, - OpenrestyLuaDir: file.OpenrestyLuaDir, - RuntimeConfigDir: file.RuntimeConfigDir, - PagesDir: file.PagesDir, - MMDBPath: file.MMDBPath, - MMDBUpdateInterval: file.MMDBUpdateInterval, - MMDBDownloadURL: file.MMDBDownloadURL, - OpenrestyObservabilityPort: file.OpenrestyObservabilityPort, - ObservabilityBufferPath: file.ObservabilityBufferPath, - ObservabilityReplayMinutes: file.ObservabilityReplayMinutes, - StatePath: file.StatePath, - HeartbeatInterval: file.HeartbeatInterval, - RequestTimeout: file.RequestTimeout, + cfg := &Config{} + if err == nil { + if err = transferPersistedConfig(cfg, file); err != nil { + return nil, err + } } cfg.configPath = path applyEnvOverrides(cfg) @@ -148,51 +140,58 @@ func applyDefaults(cfg *Config, baseDir string) { baseDir = filepath.Clean(baseDir) cfg.Version = Version cfg.OpenrestyResolvers = utils.UniqueAndCleanStringSlice(cfg.OpenrestyResolvers) + applyAgentIdentityDefaults(cfg) + applyAgentPathDefaults(cfg, baseDir) + applyAgentTimingDefaults(cfg) + normalizeManagedPaths(cfg) +} + +func applyAgentIdentityDefaults(cfg *Config) { if cfg.OpenrestyPath == "" { cfg.OpenrestyPath = "openresty" } - if cfg.DataDir == "" { - cfg.DataDir = filepath.Join(baseDir, "data") - } if cfg.NodeName == "" { cfg.NodeName = detectHostname() } if cfg.NodeIP == "" { cfg.NodeIP = nodeip.Detect() } - if cfg.MainConfigPath == "" { - cfg.MainConfigPath = joinManagedPath(cfg.DataDir, defaultMainConfigRelativePath) +} + +func applyAgentPathDefaults(cfg *Config, baseDir string) { + if cfg.DataDir == "" { + cfg.DataDir = filepath.Join(baseDir, "data") } - if cfg.RouteConfigPath == "" { - cfg.RouteConfigPath = joinManagedPath(cfg.DataDir, defaultRouteConfigRelativePath) + type managedPathDefault struct { + target *string + relative string } - if cfg.AccessLogPath == "" { - cfg.AccessLogPath = joinManagedPath(cfg.DataDir, defaultAccessLogRelativePath) + pathDefaults := []managedPathDefault{ + {&cfg.MainConfigPath, defaultMainConfigRelativePath}, + {&cfg.RouteConfigPath, defaultRouteConfigRelativePath}, + {&cfg.AccessLogPath, defaultAccessLogRelativePath}, + {&cfg.StatePath, defaultStateRelativePath}, + {&cfg.CertDir, defaultCertDirRelativePath}, + {&cfg.LuaDir, defaultLuaDirRelativePath}, + {&cfg.RuntimeConfigDir, defaultRuntimeConfigDirRelativePath}, + {&cfg.PagesDir, defaultPagesDirRelativePath}, + {&cfg.MMDBPath, defaultMMDBRelativePath}, + {&cfg.ObservabilityBufferPath, defaultObservabilityBufferRelativePath}, } - if cfg.StatePath == "" { - cfg.StatePath = joinManagedPath(cfg.DataDir, defaultStateRelativePath) - } - if cfg.CertDir == "" { - cfg.CertDir = joinManagedPath(cfg.DataDir, defaultCertDirRelativePath) + for _, item := range pathDefaults { + if strings.TrimSpace(*item.target) == "" { + *item.target = joinManagedPath(cfg.DataDir, item.relative) + } } if cfg.OpenrestyCertDir == "" { cfg.OpenrestyCertDir = cfg.CertDir } - if cfg.LuaDir == "" { - cfg.LuaDir = joinManagedPath(cfg.DataDir, defaultLuaDirRelativePath) - } if cfg.OpenrestyLuaDir == "" { cfg.OpenrestyLuaDir = cfg.LuaDir } - if cfg.RuntimeConfigDir == "" { - cfg.RuntimeConfigDir = joinManagedPath(cfg.DataDir, defaultRuntimeConfigDirRelativePath) - } - if cfg.PagesDir == "" { - cfg.PagesDir = joinManagedPath(cfg.DataDir, defaultPagesDirRelativePath) - } - if cfg.MMDBPath == "" { - cfg.MMDBPath = joinManagedPath(cfg.DataDir, defaultMMDBRelativePath) - } +} + +func applyAgentTimingDefaults(cfg *Config) { if cfg.MMDBUpdateInterval <= 0 { cfg.MMDBUpdateInterval = MillisecondDuration(defaultMMDBUpdateInterval) } @@ -202,19 +201,15 @@ func applyDefaults(cfg *Config, baseDir string) { if cfg.OpenrestyObservabilityPort <= 0 { cfg.OpenrestyObservabilityPort = defaultOpenRestyObservabilityPort } - if cfg.ObservabilityBufferPath == "" { - cfg.ObservabilityBufferPath = joinManagedPath(cfg.DataDir, defaultObservabilityBufferRelativePath) - } if cfg.ObservabilityReplayMinutes <= 0 { cfg.ObservabilityReplayMinutes = defaultObservabilityReplayMinutes } if cfg.HeartbeatInterval <= 0 { - cfg.HeartbeatInterval = MillisecondDuration(10 * time.Second) + cfg.HeartbeatInterval = MillisecondDuration(defaultHeartbeatInterval) } if cfg.RequestTimeout <= 0 { - cfg.RequestTimeout = MillisecondDuration(10 * time.Second) + cfg.RequestTimeout = MillisecondDuration(defaultRequestTimeout) } - normalizeManagedPaths(cfg) } func normalizeManagedPaths(cfg *Config) { @@ -360,6 +355,7 @@ func validate(cfg *Config) error { return nil } +// InitialAuthToken returns the agent access token, falling back to the discovery token if absent. func (cfg *Config) InitialAuthToken() string { if cfg == nil { return "" @@ -370,6 +366,15 @@ func (cfg *Config) InitialAuthToken() string { return strings.TrimSpace(cfg.DiscoveryToken) } +func (cfg *Config) toConfigFile() configFile { + var file configFile + if err := transferPersistedConfig(&file, cfg); err != nil { + return configFile{} + } + return file +} + +// Save persists the current configuration back to its original file path. func (cfg *Config) Save() error { if cfg == nil { return errors.New("config 不能为空") @@ -377,11 +382,11 @@ func (cfg *Config) Save() error { if cfg.configPath == "" { return errors.New("config path 未初始化") } - data, err := json.MarshalIndent(cfg, "", " ") + data, err := json.MarshalIndent(cfg.toConfigFile(), "", " ") //nolint:gosec // agent token must be persisted in local config file if err != nil { return err } - return os.WriteFile(cfg.configPath, data, 0o644) + return os.WriteFile(cfg.configPath, data, configFilePerm) } func detectHostname() string { @@ -391,5 +396,3 @@ func detectHostname() string { } return strings.TrimSpace(host) } - - diff --git a/internal/apps/agent/config/duration.go b/internal/apps/agent/config/duration.go index 254dcbd8..cc743d0e 100644 --- a/internal/apps/agent/config/duration.go +++ b/internal/apps/agent/config/duration.go @@ -2,4 +2,5 @@ package config import edgeconfig "github.com/Rain-kl/Wavelet/internal/apps/edge/config" -type MillisecondDuration = edgeconfig.MillisecondDuration \ No newline at end of file +// MillisecondDuration is an alias for the edge config millisecond-precision duration type. +type MillisecondDuration = edgeconfig.MillisecondDuration diff --git a/internal/apps/agent/config/version.go b/internal/apps/agent/config/version.go index f356490f..9e758d4d 100644 --- a/internal/apps/agent/config/version.go +++ b/internal/apps/agent/config/version.go @@ -1,3 +1,4 @@ package config +// Version is the current agent version string, overridden at build time. var Version = "dev" diff --git a/internal/apps/agent/geoipdata/data.go b/internal/apps/agent/geoipdata/data.go index 49899533..b5572fcb 100644 --- a/internal/apps/agent/geoipdata/data.go +++ b/internal/apps/agent/geoipdata/data.go @@ -1,8 +1,12 @@ +// Package geoipdata embeds the default MaxMind GeoLite2 country database. package geoipdata import "embed" +// FS holds the embedded GeoLite2-Country.mmdb database. +// //go:embed GeoLite2-Country.mmdb var FS embed.FS +// DefaultMMDBName is the filename of the embedded MaxMind country database. const DefaultMMDBName = "GeoLite2-Country.mmdb" diff --git a/internal/apps/agent/geoipupdate/updater.go b/internal/apps/agent/geoipupdate/updater.go index b2a94134..e373617d 100644 --- a/internal/apps/agent/geoipupdate/updater.go +++ b/internal/apps/agent/geoipupdate/updater.go @@ -1,3 +1,4 @@ +// Package geoipupdate schedules local MaxMind GeoIP database updates for the agent. package geoipupdate import ( @@ -13,12 +14,20 @@ import ( "github.com/Rain-kl/Wavelet/pkg/geoip" ) +const ( + mmdbDirPerm = 0o750 + mmdbFilePerm = 0o600 +) + +// Updater periodically downloads a fresh GeoIP MMDB file and seeds the +// initial embedded database when none is present on disk. type Updater struct { MMDBPath string DownloadURL string UpdateInterval time.Duration } +// EnsureInitialDatabase seeds the MMDB file from the embedded database if it does not exist on disk. func (u *Updater) EnsureInitialDatabase() error { path := filepath.Clean(u.MMDBPath) if path == "" || path == "." { @@ -33,16 +42,17 @@ func (u *Updater) EnsureInitialDatabase() error { if err != nil { return fmt.Errorf("read embedded mmdb failed: %w", err) } - if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + if err := os.MkdirAll(filepath.Dir(path), mmdbDirPerm); err != nil { return fmt.Errorf("create mmdb directory failed: %w", err) } - if err := os.WriteFile(path, data, 0o644); err != nil { + if err := os.WriteFile(path, data, mmdbFilePerm); err != nil { return fmt.Errorf("write initial mmdb failed: %w", err) } slog.Info("initialized GeoIP mmdb from embedded database", "path", path, "size", len(data)) return nil } +// Run starts the periodic GeoIP update loop and blocks until ctx is cancelled. func (u *Updater) Run(ctx context.Context) { if u == nil || u.MMDBPath == "" || u.UpdateInterval <= 0 { return @@ -57,7 +67,7 @@ func (u *Updater) Run(ctx context.Context) { case <-ctx.Done(): return case <-ticker.C: - if err := geoip.DownloadMaxMindDatabase(u.MMDBPath, u.DownloadURL); err != nil { + if err := geoip.DownloadMaxMindDatabase(ctx, u.MMDBPath, u.DownloadURL); err != nil { slog.Warn("update GeoIP mmdb failed", "path", u.MMDBPath, "error", err) continue } diff --git a/internal/apps/agent/heartbeat/cycle.go b/internal/apps/agent/heartbeat/cycle.go index 39e2a381..380c540f 100644 --- a/internal/apps/agent/heartbeat/cycle.go +++ b/internal/apps/agent/heartbeat/cycle.go @@ -1,3 +1,5 @@ +// Package heartbeat implements the periodic heartbeat cycle executed by the agent, +// including payload preparation, config sync, WAF IP group application, and observability buffering. package heartbeat import ( @@ -14,10 +16,7 @@ import ( edgeheartbeat "github.com/Rain-kl/Wavelet/internal/apps/edge/heartbeat" ) -type HeartbeatClient interface { - Heartbeat(ctx context.Context, payload protocol.NodePayload) (*protocol.HeartbeatResult, error) -} - +// SyncService is the interface used by Cycle to sync active configuration and WAF IP groups. type SyncService interface { SyncOnStartup(ctx context.Context, target *protocol.ActiveConfigMeta) error SyncOnce(ctx context.Context, target *protocol.ActiveConfigMeta) error @@ -25,23 +24,26 @@ type SyncService interface { ApplyWAFIPGroups(ctx context.Context, groups []protocol.WAFIPGroup) error } +// SettingsApplier is the interface used by Cycle to apply agent settings received from the server. type SettingsApplier interface { Apply(settings *protocol.AgentSettings) (intervalChanged bool) RestartOpenrestyIfNeeded(ctx context.Context) } +// Cycle holds the dependencies required to execute a single agent heartbeat cycle. type Cycle struct { Config *config.Config StateStore *state.Store ObservabilityBuffer *state.ObservabilityBufferStore - Heartbeat HeartbeatClient + Heartbeat API Sync SyncService Updater *updater.Service RecordSyncError func(err error) } +// Perform executes one complete heartbeat cycle: sends the heartbeat, syncs config, and applies settings. func (c *Cycle) Perform(ctx context.Context, nodeID string, startup bool, settings SettingsApplier) (bool, error) { - payload, ackWindows := c.PrepareHeartbeatPayload(nodeID) + payload, ackWindows := c.PrepareHeartbeatPayload(ctx, nodeID) heartbeatResult, err := c.Heartbeat.Heartbeat(ctx, payload) if err != nil { return false, err @@ -79,14 +81,15 @@ func (c *Cycle) Perform(ctx context.Context, nodeID string, startup bool, settin return changed, nil } -func (c *Cycle) NodePayload(nodeID string) protocol.NodePayload { +// NodePayload builds and returns the full NodePayload to be sent in a heartbeat request. +func (c *Cycle) NodePayload(ctx context.Context, nodeID string) protocol.NodePayload { snapshot, _ := c.StateStore.Load() openrestyStatus := strings.TrimSpace(snapshot.OpenrestyStatus) if openrestyStatus == "" { openrestyStatus = protocol.OpenrestyStatusUnknown } profile := observability.BuildProfile(c.Config, c.StateStore) - managedOpenRestyMetrics := observability.CollectManagedOpenRestyMetrics(c.Config) + managedOpenRestyMetrics := observability.CollectManagedOpenRestyMetrics(ctx, c.Config) trafficReport, accessLogs, fallbackMetrics := observability.BuildTrafficObservability(c.Config, c.StateStore, managedOpenRestyMetrics) if managedOpenRestyMetrics == nil { managedOpenRestyMetrics = fallbackMetrics @@ -122,8 +125,9 @@ func (c *Cycle) NodePayload(nodeID string) protocol.NodePayload { return payload } -func (c *Cycle) PrepareHeartbeatPayload(nodeID string) (protocol.NodePayload, []int64) { - payload := c.NodePayload(nodeID) +// PrepareHeartbeatPayload constructs the heartbeat payload with buffered observability records and returns the window timestamps to acknowledge. +func (c *Cycle) PrepareHeartbeatPayload(ctx context.Context, nodeID string) (protocol.NodePayload, []int64) { + payload := c.NodePayload(ctx, nodeID) if c.ObservabilityBuffer == nil || (payload.Snapshot == nil && payload.TrafficReport == nil && len(payload.AccessLogs) == 0) { return payload, nil } @@ -173,6 +177,7 @@ func (c *Cycle) PrepareHeartbeatPayload(nodeID string) (protocol.NodePayload, [] return payload, ackWindows } +// AckObservabilityWindows acknowledges the given observability window timestamps in the buffer store. func (c *Cycle) AckObservabilityWindows(windowStartedAtUnix []int64) { if c.ObservabilityBuffer == nil || len(windowStartedAtUnix) == 0 { return @@ -183,6 +188,7 @@ func (c *Cycle) AckObservabilityWindows(windowStartedAtUnix []int64) { } } +// ApplyWAFIPGroups applies the WAF IP groups received from the server via the SyncService. func (c *Cycle) ApplyWAFIPGroups(ctx context.Context, groups []protocol.WAFIPGroup) { if len(groups) == 0 || c.Sync == nil { return @@ -199,6 +205,7 @@ func (c *Cycle) recordSyncError(err error) { } } +// AgentSettingsToAutoUpdate converts AgentSettings to an AutoUpdateSettings value used by the edge heartbeat updater. func AgentSettingsToAutoUpdate(settings *protocol.AgentSettings) *edgeheartbeat.AutoUpdateSettings { if settings == nil { return nil @@ -214,4 +221,4 @@ func AgentSettingsToAutoUpdate(settings *protocol.AgentSettings) *edgeheartbeat. func agentSettingsToAutoUpdate(settings *protocol.AgentSettings) *edgeheartbeat.AutoUpdateSettings { return AgentSettingsToAutoUpdate(settings) -} \ No newline at end of file +} diff --git a/internal/apps/agent/heartbeat/service.go b/internal/apps/agent/heartbeat/service.go index 089839d3..bc22fdea 100644 --- a/internal/apps/agent/heartbeat/service.go +++ b/internal/apps/agent/heartbeat/service.go @@ -6,28 +6,41 @@ import ( "github.com/Rain-kl/Wavelet/internal/apps/agent/protocol" ) -type Client interface { +// RemoteClient is the interface that abstracts the remote API calls performed by Service. +type RemoteClient interface { RegisterNode(ctx context.Context, payload protocol.NodePayload) (*protocol.RegisterNodeResponse, error) Heartbeat(ctx context.Context, payload protocol.NodePayload) (*protocol.HeartbeatResult, error) SetToken(token string) } -type Service struct { - client Client +// API abstracts registration and heartbeat operations used by Cycle. +type API interface { + Register(ctx context.Context, payload protocol.NodePayload) (*protocol.RegisterNodeResponse, error) + Heartbeat(ctx context.Context, payload protocol.NodePayload) (*protocol.HeartbeatResult, error) + SetToken(token string) } -func New(client Client) *Service { +// Service wraps a RemoteClient to expose agent registration and heartbeat operations. +type Service struct { + client RemoteClient +} + +// New creates a new Service backed by the given RemoteClient. +func New(client RemoteClient) *Service { return &Service{client: client} } +// Register sends a node registration request to the server. func (s *Service) Register(ctx context.Context, payload protocol.NodePayload) (*protocol.RegisterNodeResponse, error) { return s.client.RegisterNode(ctx, payload) } +// Heartbeat sends a heartbeat to the server using the service client. func (s *Service) Heartbeat(ctx context.Context, payload protocol.NodePayload) (*protocol.HeartbeatResult, error) { return s.client.Heartbeat(ctx, payload) } +// SetToken sets the authentication token for the service client. func (s *Service) SetToken(token string) { s.client.SetToken(token) } diff --git a/internal/apps/agent/httpclient/client.go b/internal/apps/agent/httpclient/client.go index f29d7e25..45bf830e 100644 --- a/internal/apps/agent/httpclient/client.go +++ b/internal/apps/agent/httpclient/client.go @@ -1,3 +1,4 @@ +// Package httpclient provides an authenticated HTTP client for the agent. package httpclient import ( @@ -8,20 +9,23 @@ import ( "net/http" "time" - edgehttp "github.com/Rain-kl/Wavelet/internal/apps/edge/httpclient" "github.com/Rain-kl/Wavelet/internal/apps/agent/protocol" + edgehttp "github.com/Rain-kl/Wavelet/internal/apps/edge/httpclient" ) +// Client is a HTTP client used by the agent to communicate with the control plane server. type Client struct { base *edgehttp.Client } +// New creates a new Client instance with the specified base URL, token, and timeout. func New(baseURL string, token string, timeout time.Duration) *Client { return &Client{ base: edgehttp.New(baseURL, token, timeout, "X-Agent-Token"), } } +// RegisterNode registers the agent node with the control plane server. func (c *Client) RegisterNode(ctx context.Context, payload protocol.NodePayload) (*protocol.RegisterNodeResponse, error) { resp := protocol.APIResponse[protocol.RegisterNodeResponse]{} if err := c.base.PostJSON(ctx, "/api/v1/agent/nodes/register", payload, &resp); err != nil { @@ -33,6 +37,7 @@ func (c *Client) RegisterNode(ctx context.Context, payload protocol.NodePayload) return &resp.Data, nil } +// Heartbeat sends a heartbeat payload to the control plane and returns the response result. func (c *Client) Heartbeat(ctx context.Context, payload protocol.NodePayload) (*protocol.HeartbeatResult, error) { resp := protocol.APIResponse[protocol.HeartbeatData]{} if err := c.base.PostJSON(ctx, "/api/v1/agent/nodes/heartbeat", payload, &resp); err != nil { @@ -48,6 +53,7 @@ func (c *Client) Heartbeat(ctx context.Context, payload protocol.NodePayload) (* }, nil } +// GetActiveConfig retrieves the current active configuration from the control plane server. func (c *Client) GetActiveConfig(ctx context.Context) (*protocol.ActiveConfigResponse, error) { resp := protocol.APIResponse[protocol.ActiveConfigResponse]{} if err := c.base.GetJSON(ctx, "/api/v1/agent/config-versions/active", &resp); err != nil { @@ -59,6 +65,7 @@ func (c *Client) GetActiveConfig(ctx context.Context) (*protocol.ActiveConfigRes return &resp.Data, nil } +// ReportApplyLog reports the configuration application logs back to the control plane. func (c *Client) ReportApplyLog(ctx context.Context, payload protocol.ApplyLogPayload) error { resp := protocol.APIResponse[json.RawMessage]{} if err := c.base.PostJSON(ctx, "/api/v1/agent/apply-logs", payload, &resp); err != nil { @@ -67,6 +74,7 @@ func (c *Client) ReportApplyLog(ctx context.Context, payload protocol.ApplyLogPa return edgehttp.APIError(resp.ErrorMsg) } +// SyncWAFIPGroups synchronizes WAF IP groups with the control plane server. func (c *Client) SyncWAFIPGroups(ctx context.Context, payload protocol.WAFIPGroupSyncRequest) (*protocol.WAFIPGroupSyncResponse, error) { resp := protocol.APIResponse[protocol.WAFIPGroupSyncResponse]{} if err := c.base.PostJSON(ctx, "/api/v1/agent/waf/ip-groups/sync", payload, &resp); err != nil { @@ -78,18 +86,20 @@ func (c *Client) SyncWAFIPGroups(ctx context.Context, payload protocol.WAFIPGrou return &resp.Data, nil } +// DownloadPagesDeploymentPackage downloads the deployment package for the given Pages deployment ID. func (c *Client) DownloadPagesDeploymentPackage(ctx context.Context, deploymentID uint) ([]byte, error) { res, err := c.base.DoRaw(ctx, http.MethodGet, fmt.Sprintf("/api/v1/agent/pages/deployments/%d/package", deploymentID), nil) if err != nil { return nil, err } - defer res.Body.Close() + defer func() { _ = res.Body.Close() }() if res.StatusCode != http.StatusOK { return nil, edgehttp.ReadHTTPError(res) } return io.ReadAll(res.Body) } +// SetToken updates the authentication token used for API requests. func (c *Client) SetToken(token string) { c.base.SetToken(token) -} \ No newline at end of file +} diff --git a/internal/apps/agent/logging/logger.go b/internal/apps/agent/logging/logger.go index 1e630a51..e2900fc6 100644 --- a/internal/apps/agent/logging/logger.go +++ b/internal/apps/agent/logging/logger.go @@ -1,7 +1,9 @@ +// Package logging configures structured logging for the agent process. package logging import edgelogging "github.com/Rain-kl/Wavelet/internal/apps/edge/logging" +// Setup initialises structured logging for the agent process. func Setup() { edgelogging.Setup(edgelogging.Options{AddSource: true}) -} \ No newline at end of file +} diff --git a/internal/apps/agent/nginx/manager.go b/internal/apps/agent/nginx/manager.go index 8c838af3..9fe90f5d 100644 --- a/internal/apps/agent/nginx/manager.go +++ b/internal/apps/agent/nginx/manager.go @@ -1,3 +1,4 @@ +// Package nginx manages OpenResty configuration, runtime, and supporting assets. package nginx import ( @@ -26,10 +27,26 @@ import ( "github.com/Rain-kl/Wavelet/internal/apps/agent/protocol" ) +// RuntimeConfigDirPlaceholder is substituted into generated configs at apply time. const RuntimeConfigDirPlaceholder = "__OPENFLARE_RUNTIME_CONFIG_DIR__" -const ResolverDirectivePlaceholder = "__OPENFLARE_RESOLVER_DIRECTIVE__" -const WAFIPGroupsConfigFileName = "waf_ip_groups.json" +// ResolverDirectivePlaceholder is substituted into generated configs at apply time. +const ResolverDirectivePlaceholder = "__OPENFLARE_RESOLVER_DIRECTIVE__" + +// WAFIPGroupsConfigFileName is the runtime filename for synced WAF IP group data. +const WAFIPGroupsConfigFileName = "waf_ip_groups.json" +const powConfigFileName = "pow_config.json" + +const ( + nginxConfigFilePerm = 0o644 + nginxPrivateKeyFilePerm = 0o600 + nginxDirPerm = 0o755 + stubStatusCheckTimeout = 1500 * time.Millisecond + nginxVersionSubmatchCount = 2 + resolverAddressCapacity = 2 +) + +// Executor controls OpenResty validation, reload, health, and lifecycle operations. type Executor interface { Test(ctx context.Context) error Reload(ctx context.Context) error @@ -38,44 +55,49 @@ type Executor interface { Restart(ctx context.Context) error } +// CommandRunner executes external commands on behalf of an Executor. type CommandRunner interface { Run(ctx context.Context, name string, args ...string) ([]byte, error) } +// OSCommandRunner runs commands using the host operating system. type OSCommandRunner struct{} +// Run executes the named command and returns its combined output. func (r *OSCommandRunner) Run(ctx context.Context, name string, args ...string) ([]byte, error) { slog.Debug("OSCommandRunner starting command", "name", name, "args", args) tmpFile, err := os.CreateTemp("", "openflare-cmd-*") if err != nil { slog.Error("OSCommandRunner failed to create temp file, falling back to CombinedOutput", "error", err) - cmd := exec.CommandContext(ctx, name, args...) + cmd := exec.CommandContext(ctx, name, args...) //nolint:gosec // command name and args come from trusted OpenResty management paths output, outErr := cmd.CombinedOutput() slog.Debug("OSCommandRunner finished CombinedOutput", "name", name, "error", outErr) return output, outErr } - defer os.Remove(tmpFile.Name()) + defer func() { _ = os.Remove(tmpFile.Name()) }() - cmd := exec.CommandContext(ctx, name, args...) + cmd := exec.CommandContext(ctx, name, args...) //nolint:gosec // command name and args come from trusted OpenResty management paths cmd.Stdout = tmpFile cmd.Stderr = tmpFile slog.Debug("OSCommandRunner executing cmd.Run()", "name", name) runErr := cmd.Run() slog.Debug("OSCommandRunner cmd.Run() returned", "name", name, "error", runErr) - tmpFile.Close() + _ = tmpFile.Close() output, _ := os.ReadFile(tmpFile.Name()) slog.Debug("OSCommandRunner command complete", "name", name, "output_len", len(output)) return output, runErr } +// PathExecutor runs OpenResty using a configured binary and config path. type PathExecutor struct { Path string ConfigPath string Runner CommandRunner } +// Test validates the current OpenResty configuration. func (e *PathExecutor) Test(ctx context.Context) error { slog.Debug("running openresty test with binary", "path", e.Path, "config", e.ConfigPath) output, err := e.Runner.Run(ctx, e.Path, "-t", "-c", e.ConfigPath) @@ -86,6 +108,7 @@ func (e *PathExecutor) Test(ctx context.Context) error { return nil } +// Reload reloads OpenResty or starts it when no runtime process is running. func (e *PathExecutor) Reload(ctx context.Context) error { slog.Debug("running openresty reload with binary", "path", e.Path, "config", e.ConfigPath) output, err := e.Runner.Run(ctx, e.Path, "-s", "reload", "-c", e.ConfigPath) @@ -104,6 +127,7 @@ func (e *PathExecutor) Reload(ctx context.Context) error { return nil } +// EnsureRuntime validates configuration and reloads the OpenResty runtime. func (e *PathExecutor) EnsureRuntime(ctx context.Context, _ bool) error { if err := e.Test(ctx); err != nil { return err @@ -111,10 +135,12 @@ func (e *PathExecutor) EnsureRuntime(ctx context.Context, _ bool) error { return e.Reload(ctx) } +// CheckHealth reports whether the OpenResty configuration is valid. func (e *PathExecutor) CheckHealth(ctx context.Context) error { return e.Test(ctx) } +// Restart stops and starts the OpenResty runtime process. func (e *PathExecutor) Restart(ctx context.Context) error { slog.Info("restarting openresty with binary", "path", e.Path, "config", e.ConfigPath) output, err := e.Runner.Run(ctx, e.Path, "-s", "quit", "-c", e.ConfigPath) @@ -132,6 +158,7 @@ func (e *PathExecutor) Restart(ctx context.Context) error { return nil } +// Manager applies OpenResty configuration and manages runtime assets. type Manager struct { MainConfigPath string RouteConfigPath string @@ -148,9 +175,12 @@ type Manager struct { Executor Executor } +// ApplyStatus reports the outcome of an OpenResty configuration apply. type ApplyStatus string +// Apply outcome status values. const ( + // ApplyStatusSuccess indicates the configuration was applied successfully. ApplyStatusSuccess ApplyStatus = "success" ApplyStatusWarning ApplyStatus = "warning" ApplyStatusFatal ApplyStatus = "fatal" @@ -188,6 +218,7 @@ const safeDefaultFallbackObservabilityServerBlock = ` } ` +// ApplyOutcome contains the status and message from a configuration apply. type ApplyOutcome struct { Status ApplyStatus Message string @@ -197,6 +228,7 @@ type wafIPGroupsRuntimeConfig struct { Groups map[string]protocol.WAFIPGroup `json:"groups"` } +// Apply writes, validates, and activates new OpenResty configuration files. func (m *Manager) Apply(ctx context.Context, mainConfig string, routeConfig string, supportFiles []protocol.SupportFile) ApplyOutcome { slog.Info("openresty apply started", "main_config", m.MainConfigPath, "route_config", m.RouteConfigPath, "cert_files", len(supportFiles)) backup, err := m.backup() @@ -236,11 +268,11 @@ func (m *Manager) writeTargetFiles(mainConfig string, routeConfig string, suppor slog.Warn("runtime-resolved hostname upstreams detected without available resolvers; hostname origin requests may fail until resolvers are configured") } renderedMainConfig := m.renderMainConfig(mainConfig) - if err := os.WriteFile(m.MainConfigPath, []byte(renderedMainConfig), 0o644); err != nil { + if err := os.WriteFile(m.MainConfigPath, []byte(renderedMainConfig), nginxConfigFilePerm); err != nil { return err } renderedRouteConfig := m.renderRouteConfig(routeConfig) - if err := os.WriteFile(m.RouteConfigPath, []byte(renderedRouteConfig), 0o644); err != nil { + if err := os.WriteFile(m.RouteConfigPath, []byte(renderedRouteConfig), nginxConfigFilePerm); err != nil { return err } return nil @@ -293,6 +325,7 @@ func fatalApplyOutcome(err error) ApplyOutcome { } } +// EnsureLuaAssets synchronizes managed Lua and static assets to the runtime directory. func (m *Manager) EnsureLuaAssets() error { if strings.TrimSpace(m.LuaDir) == "" { return nil @@ -317,12 +350,13 @@ func (m *Manager) EnsureLuaAssets() error { files = append(files, managedFile{ Path: filepath.ToSlash(relativePath), Content: []byte(file.Content), - Mode: 0o644, + Mode: nginxConfigFilePerm, }) } return syncManagedFiles(m.LuaDir, files) } +// EnsureRuntime validates and reloads the current OpenResty runtime configuration. func (m *Manager) EnsureRuntime(ctx context.Context, recreate bool) error { if m.Executor == nil { return errors.New("executor 未配置") @@ -331,6 +365,7 @@ func (m *Manager) EnsureRuntime(ctx context.Context, recreate bool) error { return m.Executor.EnsureRuntime(ctx, recreate) } +// EnsureSafeFallbackRuntime starts a minimal safe default OpenResty runtime. func (m *Manager) EnsureSafeFallbackRuntime(ctx context.Context, reason string) error { if m.Executor == nil { return errors.New("executor 未配置") @@ -350,6 +385,7 @@ func (m *Manager) EnsureSafeFallbackRuntime(ctx context.Context, reason string) return nil } +// CheckHealth verifies that OpenResty configuration and health endpoints are available. func (m *Manager) CheckHealth(ctx context.Context) error { if m.Executor == nil { return errors.New("executor 未配置") @@ -365,6 +401,7 @@ func (m *Manager) CheckHealth(ctx context.Context) error { return m.checkStubStatus(ctx) } +// Restart restarts the OpenResty runtime process. func (m *Manager) Restart(ctx context.Context) error { if m.Executor == nil { return errors.New("executor 未配置") @@ -373,6 +410,7 @@ func (m *Manager) Restart(ctx context.Context) error { return m.Executor.Restart(ctx) } +// CurrentChecksum returns a stable checksum for the active OpenResty configuration bundle. func (m *Manager) CurrentChecksum() (string, error) { if m.RouteConfigPath == "" { return "", errors.New("route config path 不能为空") @@ -435,6 +473,7 @@ func (m *Manager) CurrentChecksum() (string, error) { return result, nil } +// WAFIPGroupChecksums returns checksums for locally synced WAF IP groups. func (m *Manager) WAFIPGroupChecksums() (map[string]string, error) { config, err := m.readWAFIPGroupsRuntimeConfig() if err != nil { @@ -449,6 +488,7 @@ func (m *Manager) WAFIPGroupChecksums() (map[string]string, error) { return result, nil } +// SyncWAFIPGroups writes WAF IP group definitions to the runtime config directory. func (m *Manager) SyncWAFIPGroups(groups []protocol.WAFIPGroup) error { if m.RuntimeConfigDir == "" || len(groups) == 0 { return nil @@ -470,11 +510,11 @@ func (m *Manager) SyncWAFIPGroups(groups []protocol.WAFIPGroup) error { if err != nil { return err } - if err := os.MkdirAll(m.RuntimeConfigDir, 0o755); err != nil { + if err := os.MkdirAll(m.RuntimeConfigDir, nginxDirPerm); err != nil { return err } path := filepath.Join(m.RuntimeConfigDir, WAFIPGroupsConfigFileName) - if err := os.WriteFile(path, data, 0o644); err != nil { + if err := os.WriteFile(path, data, nginxConfigFilePerm); err != nil { return fmt.Errorf("write %s: %w", WAFIPGroupsConfigFileName, err) } slog.Info("synced waf ip groups", "path", path, "group_count", len(groups)) @@ -487,7 +527,7 @@ func (m *Manager) readWAFIPGroupsRuntimeConfig() (*wafIPGroupsRuntimeConfig, err return config, nil } path := filepath.Join(m.RuntimeConfigDir, WAFIPGroupsConfigFileName) - data, err := os.ReadFile(path) + data, err := os.ReadFile(path) //nolint:gosec // path is under managed RuntimeConfigDir if err != nil { if os.IsNotExist(err) { return config, nil @@ -506,6 +546,7 @@ func (m *Manager) readWAFIPGroupsRuntimeConfig() (*wafIPGroupsRuntimeConfig, err return config, nil } +// ExecutorOptions configures construction of an OpenResty Executor. type ExecutorOptions struct { NginxPath string MainConfigPath string @@ -517,6 +558,7 @@ type ExecutorOptions struct { OpenrestyObservabilityPort int } +// NewExecutor creates an Executor backed by a configured OpenResty binary. func NewExecutor(options ExecutorOptions) Executor { runner := &OSCommandRunner{} return &PathExecutor{ @@ -526,6 +568,7 @@ func NewExecutor(options ExecutorOptions) Executor { } } +// DetectVersion returns the OpenResty version reported by the configured binary. func DetectVersion(ctx context.Context, options ExecutorOptions) string { version, err := detectVersion(ctx, options, &OSCommandRunner{}) if err != nil { @@ -556,7 +599,7 @@ func detectVersion(ctx context.Context, options ExecutorOptions, runner CommandR func parseExtVersion(output string) string { matches := nginxVersionPattern.FindStringSubmatch(output) - if len(matches) != 2 { + if len(matches) != nginxVersionSubmatchCount { return "" } return matches[1] @@ -606,24 +649,24 @@ func (m *Manager) backup() (*backupState, error) { if m.RouteConfigPath == "" { return nil, errors.New("route config path 不能为空") } - if err := os.MkdirAll(filepath.Dir(m.MainConfigPath), 0o755); err != nil { + if err := os.MkdirAll(filepath.Dir(m.MainConfigPath), nginxDirPerm); err != nil { return nil, err } - if err := os.MkdirAll(filepath.Dir(m.RouteConfigPath), 0o755); err != nil { + if err := os.MkdirAll(filepath.Dir(m.RouteConfigPath), nginxDirPerm); err != nil { return nil, err } if m.AccessLogPath != "" { - if err := os.MkdirAll(filepath.Dir(m.AccessLogPath), 0o755); err != nil { + if err := os.MkdirAll(filepath.Dir(m.AccessLogPath), nginxDirPerm); err != nil { return nil, err } } if m.CertDir != "" { - if err := os.MkdirAll(m.CertDir, 0o755); err != nil { + if err := os.MkdirAll(m.CertDir, nginxDirPerm); err != nil { return nil, err } } if m.RuntimeConfigDir != "" { - if err := os.MkdirAll(m.RuntimeConfigDir, 0o755); err != nil { + if err := os.MkdirAll(m.RuntimeConfigDir, nginxDirPerm); err != nil { return nil, err } } @@ -672,14 +715,14 @@ func (m *Manager) restore(state *backupState) error { } slog.Warn("restoring nginx backup", "main_existed", state.MainExisted, "route_existed", state.RouteExisted, "cert_files", len(state.Files)) if state.MainExisted { - if err := os.WriteFile(m.MainConfigPath, state.MainData, 0o644); err != nil { + if err := os.WriteFile(m.MainConfigPath, state.MainData, nginxConfigFilePerm); err != nil { return err } } else if err := os.Remove(m.MainConfigPath); err != nil && !os.IsNotExist(err) { return err } if state.RouteExisted { - if err := os.WriteFile(m.RouteConfigPath, state.RouteData, 0o644); err != nil { + if err := os.WriteFile(m.RouteConfigPath, state.RouteData, nginxConfigFilePerm); err != nil { return err } } else if err := os.Remove(m.RouteConfigPath); err != nil && !os.IsNotExist(err) { @@ -690,7 +733,7 @@ func (m *Manager) restore(state *backupState) error { return err } } - if err := m.restoreRuntimeConfig(state.PowConfig, "pow_config.json"); err != nil { + if err := m.restoreRuntimeConfig(state.PowConfig, powConfigFileName); err != nil { return err } if err := m.restoreRuntimeConfig(state.WAFConfig, "waf_config.json"); err != nil { @@ -710,10 +753,10 @@ func (m *Manager) writePowConfig(supportFiles []protocol.SupportFile) error { if m.RuntimeConfigDir == "" { return nil } - configPath := filepath.Join(m.RuntimeConfigDir, "pow_config.json") + configPath := filepath.Join(m.RuntimeConfigDir, powConfigFileName) for _, file := range supportFiles { - if file.Path == "pow_config.json" { - if err := os.WriteFile(configPath, []byte(file.Content), 0o644); err != nil { + if file.Path == powConfigFileName { + if err := os.WriteFile(configPath, []byte(file.Content), nginxConfigFilePerm); err != nil { return fmt.Errorf("write pow_config.json: %w", err) } slog.Info("wrote pow config", "path", configPath, "size", len(file.Content)) @@ -723,10 +766,10 @@ func (m *Manager) writePowConfig(supportFiles []protocol.SupportFile) error { if err := os.Remove(configPath); err != nil && !os.IsNotExist(err) { return fmt.Errorf("remove pow_config.json: %w", err) } - if err := removeLegacyPowConfig(filepath.Join(m.LuaDir, "pow_config.json")); err != nil { + if err := removeLegacyPowConfig(filepath.Join(m.LuaDir, powConfigFileName)); err != nil { return err } - if err := removeLegacyPowConfig(filepath.Join(m.CertDir, "pow_config.json")); err != nil { + if err := removeLegacyPowConfig(filepath.Join(m.CertDir, powConfigFileName)); err != nil { return err } return nil @@ -739,7 +782,7 @@ func (m *Manager) writeWAFConfig(supportFiles []protocol.SupportFile) error { configPath := filepath.Join(m.RuntimeConfigDir, "waf_config.json") for _, file := range supportFiles { if file.Path == "waf_config.json" { - if err := os.WriteFile(configPath, []byte(file.Content), 0o644); err != nil { + if err := os.WriteFile(configPath, []byte(file.Content), nginxConfigFilePerm); err != nil { return fmt.Errorf("write waf_config.json: %w", err) } slog.Info("wrote waf config", "path", configPath, "size", len(file.Content)) @@ -759,7 +802,7 @@ func (m *Manager) writeSourceConfig(supportFiles []protocol.SupportFile) error { configPath := filepath.Join(m.RuntimeConfigDir, openrestyrender.SourceConfigFileName) for _, file := range supportFiles { if file.Path == openrestyrender.SourceConfigFileName { - if err := os.WriteFile(configPath, []byte(file.Content), 0o644); err != nil { + if err := os.WriteFile(configPath, []byte(file.Content), nginxConfigFilePerm); err != nil { return fmt.Errorf("write %s: %w", openrestyrender.SourceConfigFileName, err) } slog.Info("wrote openresty source config", "path", configPath, "size", len(file.Content)) @@ -775,7 +818,7 @@ func (m *Manager) writeSourceConfig(supportFiles []protocol.SupportFile) error { func (m *Manager) writeManagedCertFiles(certFiles []protocol.SupportFile) error { files := make([]managedFile, 0, len(certFiles)) for _, file := range certFiles { - if file.Path == "pow_config.json" || file.Path == "waf_config.json" || file.Path == openrestyrender.SourceConfigFileName { + if file.Path == powConfigFileName || file.Path == "waf_config.json" || file.Path == openrestyrender.SourceConfigFileName { continue } targetPath, err := m.certFileTargetPath(file.Path) @@ -817,10 +860,10 @@ func (m *Manager) readCertFiles() ([]protocol.SupportFile, error) { if err != nil { return err } - if filepath.ToSlash(relativePath) == "pow_config.json" { + if filepath.ToSlash(relativePath) == powConfigFileName { return nil } - data, err := os.ReadFile(path) + data, err := os.ReadFile(path) //nolint:gosec // path is under managed baseDir walk root if err != nil { return err } @@ -840,7 +883,7 @@ func (m *Manager) readCertFiles() ([]protocol.SupportFile, error) { } func (m *Manager) readPowConfigFile() (*protocol.SupportFile, error) { - return m.readRuntimeConfigFile("pow_config.json") + return m.readRuntimeConfigFile(powConfigFileName) } func (m *Manager) readRuntimeConfigFile(name string) (*protocol.SupportFile, error) { @@ -848,7 +891,7 @@ func (m *Manager) readRuntimeConfigFile(name string) (*protocol.SupportFile, err return nil, nil } configPath := filepath.Join(m.RuntimeConfigDir, name) - data, err := os.ReadFile(configPath) + data, err := os.ReadFile(configPath) //nolint:gosec // configPath is under managed RuntimeConfigDir if err != nil { if os.IsNotExist(err) { return nil, nil @@ -894,7 +937,7 @@ func (m *Manager) restoreRuntimeConfig(file *protocol.SupportFile, name string) } return nil } - return os.WriteFile(configPath, []byte(file.Content), 0o644) + return os.WriteFile(configPath, []byte(file.Content), nginxConfigFilePerm) } func (m *Manager) writeSafeDefaultFallbackFiles() error { @@ -904,16 +947,16 @@ func (m *Manager) writeSafeDefaultFallbackFiles() error { if strings.TrimSpace(m.RouteConfigPath) == "" { return errors.New("route config path 不能为空") } - if err := os.MkdirAll(filepath.Dir(m.MainConfigPath), 0o755); err != nil { + if err := os.MkdirAll(filepath.Dir(m.MainConfigPath), nginxDirPerm); err != nil { return err } - if err := os.MkdirAll(filepath.Dir(m.RouteConfigPath), 0o755); err != nil { + if err := os.MkdirAll(filepath.Dir(m.RouteConfigPath), nginxDirPerm); err != nil { return err } - if err := os.WriteFile(m.RouteConfigPath, nil, 0o644); err != nil { + if err := os.WriteFile(m.RouteConfigPath, nil, nginxConfigFilePerm); err != nil { return err } - if err := os.WriteFile(m.MainConfigPath, []byte(m.safeDefaultFallbackMainConfig()), 0o644); err != nil { + if err := os.WriteFile(m.MainConfigPath, []byte(m.safeDefaultFallbackMainConfig()), nginxConfigFilePerm); err != nil { return err } return nil @@ -928,10 +971,10 @@ func (m *Manager) safeDefaultFallbackMainConfig() string { } func (m *Manager) checkStubStatus(ctx context.Context) error { - ctx, cancel := context.WithTimeout(ctx, 1500*time.Millisecond) + ctx, cancel := context.WithTimeout(ctx, stubStatusCheckTimeout) defer cancel() - openrestyStubUrl := fmt.Sprintf("http://127.0.0.1:%d/openflare/stub_status", m.OpenrestyObservabilityPort) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, openrestyStubUrl, nil) + openrestyStubURL := fmt.Sprintf("http://127.0.0.1:%d/openflare/stub_status", m.OpenrestyObservabilityPort) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, openrestyStubURL, nil) if err != nil { return err } @@ -939,11 +982,11 @@ func (m *Manager) checkStubStatus(ctx context.Context) error { if err != nil { return fmt.Errorf("openresty health endpoint unreachable: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return fmt.Errorf("openresty health endpoint returned %s", resp.Status) } - slog.Debug("openresty health endpoint is healthy", "url", openrestyStubUrl) + slog.Debug("openresty health endpoint is healthy", "url", openrestyStubURL) return nil } @@ -986,11 +1029,11 @@ func (m *Manager) certFileTargetPath(relativePath string) (string, error) { func certFileMode(relativePath string) fs.FileMode { switch strings.ToLower(filepath.Ext(strings.TrimSpace(relativePath))) { case ".crt", ".pem": - return 0o644 + return nginxConfigFilePerm case ".key": - return 0o600 + return nginxPrivateKeyFilePerm default: - return 0o644 + return nginxConfigFilePerm } } @@ -1029,7 +1072,7 @@ func syncManagedFiles(baseDir string, files []managedFile) error { } else if err != nil && !os.IsNotExist(err) { return err } - if err := os.MkdirAll(baseDir, 0o755); err != nil { + if err := os.MkdirAll(baseDir, nginxDirPerm); err != nil { return err } @@ -1060,14 +1103,14 @@ func syncManagedFiles(baseDir string, files []managedFile) error { if _, ok := desired[filepath.Clean(relativePath)]; ok { return nil } - return os.Remove(path) + return os.Remove(path) //nolint:gosec // path is resolved under the managed baseDir walk root }); err != nil { return err } for _, file := range desired { targetPath := filepath.Join(baseDir, file.Path) - if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil { + if err := os.MkdirAll(filepath.Dir(targetPath), nginxDirPerm); err != nil { return err } if err := os.WriteFile(targetPath, file.Content, file.Mode); err != nil { @@ -1119,10 +1162,10 @@ func (m *Manager) ensureMimeTypes() error { } else if !os.IsNotExist(err) { return err } - if err := os.MkdirAll(configDir, 0o755); err != nil { + if err := os.MkdirAll(configDir, nginxDirPerm); err != nil { return err } - return os.WriteFile(mimeTypesPath, []byte(DefaultMimeTypes), 0o644) + return os.WriteFile(mimeTypesPath, []byte(DefaultMimeTypes), nginxConfigFilePerm) } func (m *Manager) renderRouteConfig(content string) string { @@ -1183,6 +1226,7 @@ func (m *Manager) managedWAFLuaFiles() []protocol.SupportFile { return files } +// ObservabilityListenAddress returns the localhost listen address for stub_status. func ObservabilityListenAddress(port int) string { if port <= 0 { return "" @@ -1190,6 +1234,7 @@ func ObservabilityListenAddress(port int) string { return fmt.Sprintf("127.0.0.1:%d", port) } +// ResolverDirective renders the nginx resolver block for runtime upstream lookups. func ResolverDirective(explicitResolvers []string) string { resolvers := resolverAddresses(explicitResolvers) if len(resolvers) == 0 { @@ -1211,7 +1256,7 @@ func resolverAddresses(explicitResolvers []string) []string { func parseResolverAddresses(content string, dockerMode bool) []string { lines := strings.Split(content, "\n") - resolvers := make([]string, 0, 2) + resolvers := make([]string, 0, resolverAddressCapacity) seen := make(map[string]struct{}) for _, line := range lines { fields := strings.Fields(strings.TrimSpace(line)) @@ -1242,6 +1287,7 @@ func isUsableDockerResolver(addr string) bool { return !ip.IsLoopback() && !ip.IsUnspecified() } +// RequiresRuntimeResolver reports whether originURL needs a runtime DNS resolver. func RequiresRuntimeResolver(originURL string) bool { parsed, err := url.Parse(strings.TrimSpace(originURL)) if err != nil || parsed.Hostname() == "" { diff --git a/internal/apps/agent/nginx/mimetypes.go b/internal/apps/agent/nginx/mimetypes.go index f3ba4ded..8d2fb044 100644 --- a/internal/apps/agent/nginx/mimetypes.go +++ b/internal/apps/agent/nginx/mimetypes.go @@ -1,5 +1,6 @@ package nginx +// DefaultMimeTypes is the embedded nginx mime.types map used by generated configs. const DefaultMimeTypes = ` types { text/html html htm shtml; diff --git a/internal/apps/agent/nginx/observability_assets.go b/internal/apps/agent/nginx/observability_assets.go index 72657bd7..8998af5d 100644 --- a/internal/apps/agent/nginx/observability_assets.go +++ b/internal/apps/agent/nginx/observability_assets.go @@ -146,6 +146,7 @@ ngx.header.content_type = "application/json" ngx.say(cjson.encode(payload)) ` +// ManagedObservabilityLuaFiles returns embedded Lua assets for OpenResty observability. func ManagedObservabilityLuaFiles() []protocol.SupportFile { return []protocol.SupportFile{ {Path: "init.lua", Content: openRestyObservabilityInitLua}, diff --git a/internal/apps/agent/nginx/pow_assets.go b/internal/apps/agent/nginx/pow_assets.go index 5aaa18ee..04d9ec71 100644 --- a/internal/apps/agent/nginx/pow_assets.go +++ b/internal/apps/agent/nginx/pow_assets.go @@ -542,6 +542,7 @@ end return M ` +// ManagedPowLuaFiles returns embedded Lua assets for proof-of-work challenges. func ManagedPowLuaFiles() []protocol.SupportFile { return []protocol.SupportFile{ {Path: "pow/runtime.lua", Content: openRestyPowRuntimeLua}, @@ -552,6 +553,7 @@ func ManagedPowLuaFiles() []protocol.SupportFile { } } +// ManagedPowStaticFiles returns embedded static assets served by the PoW module. func ManagedPowStaticFiles() ([]protocol.SupportFile, error) { var files []protocol.SupportFile entries, err := powStaticFS.ReadDir("pow_static") diff --git a/internal/apps/agent/nginx/waf_assets.go b/internal/apps/agent/nginx/waf_assets.go index ff48cdb8..8b7d12bc 100644 --- a/internal/apps/agent/nginx/waf_assets.go +++ b/internal/apps/agent/nginx/waf_assets.go @@ -302,6 +302,7 @@ end return require("waf.runtime").check() ` +// ManagedWAFLuaFiles returns the embedded Lua source files that must be deployed to the WAF runtime directory. func ManagedWAFLuaFiles() []protocol.SupportFile { return []protocol.SupportFile{ {Path: "waf/runtime.lua", Content: openRestyWAFRuntimeLua}, diff --git a/internal/apps/agent/observability/collector.go b/internal/apps/agent/observability/collector.go index bf9953e9..398b0766 100644 --- a/internal/apps/agent/observability/collector.go +++ b/internal/apps/agent/observability/collector.go @@ -1,3 +1,4 @@ +// Package observability provides system and service level observability data collection for the agent. package observability import ( @@ -15,6 +16,9 @@ import ( edgeobs "github.com/Rain-kl/Wavelet/internal/apps/edge/observability" ) +const nodeHealthEventInitialCapacity = 2 + +// BuildProfile collects the system profile and returns it only if the fingerprint has changed. func BuildProfile(cfg *config.Config, stateStore *state.Store) *protocol.NodeSystemProfile { profile := collectProfile(cfg) if profile == nil { @@ -38,6 +42,7 @@ func BuildProfile(cfg *config.Config, stateStore *state.Store) *protocol.NodeSys return profile } +// BuildSnapshot captures current system metrics and returns a metric snapshot. func BuildSnapshot(cfg *config.Config, stateStore *state.Store) *protocol.NodeMetricSnapshot { now := time.Now().UTC() metric := &protocol.NodeMetricSnapshot{ @@ -79,6 +84,7 @@ func BuildSnapshot(cfg *config.Config, stateStore *state.Store) *protocol.NodeMe return metric } +// BuildOpenrestyObservation builds the OpenResty observation protocol model from the managed metrics. func BuildOpenrestyObservation(managed *ManagedOpenRestyMetrics) *protocol.NodeOpenrestyObservation { if managed == nil { return nil @@ -91,11 +97,12 @@ func BuildOpenrestyObservation(managed *ManagedOpenRestyMetrics) *protocol.NodeO } } +// BuildHealthEvents converts system snapshot health state into a list of health events. func BuildHealthEvents(snapshot *state.Snapshot) []protocol.NodeHealthEvent { if snapshot == nil { return []protocol.NodeHealthEvent{} } - events := make([]protocol.NodeHealthEvent, 0, 2) + events := make([]protocol.NodeHealthEvent, 0, nodeHealthEventInitialCapacity) nowUnix := time.Now().UTC().Unix() if strings.TrimSpace(snapshot.OpenrestyStatus) == protocol.OpenrestyStatusUnhealthy { events = append(events, protocol.NodeHealthEvent{ @@ -147,4 +154,4 @@ func fingerprintProfile(profile *protocol.NodeSystemProfile) string { } sum := sha256.Sum256(raw) return hex.EncodeToString(sum[:]) -} \ No newline at end of file +} diff --git a/internal/apps/agent/observability/openresty_local.go b/internal/apps/agent/observability/openresty_local.go index ccf9b24d..091d928a 100644 --- a/internal/apps/agent/observability/openresty_local.go +++ b/internal/apps/agent/observability/openresty_local.go @@ -1,6 +1,7 @@ package observability import ( + "context" "encoding/json" "fmt" "io" @@ -14,11 +15,15 @@ import ( "github.com/Rain-kl/Wavelet/internal/apps/agent/protocol" ) -const openRestyObservabilityPath = "/openflare/observability" -const openRestyStubStatusPath = "/openflare/stub_status" +const ( + openRestyObservabilityPath = "/openflare/observability" + openRestyStubStatusPath = "/openflare/stub_status" + stubStatusActiveMatchGroupCount = 2 +) var stubStatusActivePattern = regexp.MustCompile(`Active connections:\s+(\d+)`) +// ManagedOpenRestyMetrics holds metrics collected from the local OpenResty instance. type ManagedOpenRestyMetrics struct { TrafficReport *protocol.NodeTrafficReport OpenrestyRxBytes int64 @@ -39,7 +44,8 @@ type openRestyObservabilityResponse struct { OpenrestyTxBytes int64 `json:"openresty_tx_bytes"` } -func CollectManagedOpenRestyMetrics(cfg *config.Config) *ManagedOpenRestyMetrics { +// CollectManagedOpenRestyMetrics collects metrics from the local OpenResty observability endpoints. +func CollectManagedOpenRestyMetrics(ctx context.Context, cfg *config.Config) *ManagedOpenRestyMetrics { if cfg == nil || cfg.OpenrestyObservabilityPort <= 0 { return nil } @@ -48,7 +54,7 @@ func CollectManagedOpenRestyMetrics(cfg *config.Config) *ManagedOpenRestyMetrics client := &http.Client{Timeout: 1500 * time.Millisecond} observabilityResp := openRestyObservabilityResponse{} - if err := fetchLocalJSON(client, baseURL+openRestyObservabilityPath, &observabilityResp); err != nil { + if err := fetchLocalJSON(ctx, client, baseURL+openRestyObservabilityPath, &observabilityResp); err != nil { return nil } @@ -67,31 +73,39 @@ func CollectManagedOpenRestyMetrics(cfg *config.Config) *ManagedOpenRestyMetrics OpenrestyTxBytes: observabilityResp.OpenrestyTxBytes, } - if text, err := fetchLocalText(client, baseURL+openRestyStubStatusPath); err == nil { + if text, err := fetchLocalText(ctx, client, baseURL+openRestyStubStatusPath); err == nil { result.OpenrestyConnections = parseStubStatusActiveConnections(text) } return result } -func fetchLocalJSON(client *http.Client, url string, target any) error { - resp, err := client.Get(url) +func fetchLocalJSON(ctx context.Context, client *http.Client, url string, target any) error { + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return err } - defer resp.Body.Close() + resp, err := client.Do(req) + if err != nil { + return err + } + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return fmt.Errorf("unexpected local observability status: %s", resp.Status) } return json.NewDecoder(resp.Body).Decode(target) } -func fetchLocalText(client *http.Client, url string) (string, error) { - resp, err := client.Get(url) +func fetchLocalText(ctx context.Context, client *http.Client, url string) (string, error) { + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return "", err } - defer resp.Body.Close() + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("unexpected local stub status: %s", resp.Status) } @@ -104,7 +118,7 @@ func fetchLocalText(client *http.Client, url string) (string, error) { func parseStubStatusActiveConnections(raw string) int64 { matches := stubStatusActivePattern.FindStringSubmatch(raw) - if len(matches) != 2 { + if len(matches) != stubStatusActiveMatchGroupCount { return 0 } value, err := strconv.ParseInt(matches[1], 10, 64) diff --git a/internal/apps/agent/observability/openresty_local_test.go b/internal/apps/agent/observability/openresty_local_test.go index 028d8319..ee8adb08 100644 --- a/internal/apps/agent/observability/openresty_local_test.go +++ b/internal/apps/agent/observability/openresty_local_test.go @@ -1,6 +1,7 @@ package observability import ( + "context" "net" "net/http" "net/http/httptest" @@ -31,7 +32,7 @@ func TestCollectManagedOpenRestyMetrics(t *testing.T) { server.Start() defer server.Close() - metrics := CollectManagedOpenRestyMetrics(&config.Config{ + metrics := CollectManagedOpenRestyMetrics(context.Background(), &config.Config{ OpenrestyObservabilityPort: port, }) if metrics == nil || metrics.TrafficReport == nil { @@ -67,7 +68,7 @@ func TestNormalizeCountMapDropsEmptyKeys(t *testing.T) { func TestCollectManagedOpenRestyMetricsHandlesUnavailableEndpoint(t *testing.T) { cfg := &config.Config{OpenrestyObservabilityPort: 1} - if metrics := CollectManagedOpenRestyMetrics(cfg); metrics != nil { + if metrics := CollectManagedOpenRestyMetrics(context.Background(), cfg); metrics != nil { t.Fatalf("expected nil metrics for unavailable endpoint, got %+v", metrics) } } diff --git a/internal/apps/agent/observability/traffic.go b/internal/apps/agent/observability/traffic.go index aef7576c..8dafd8ad 100644 --- a/internal/apps/agent/observability/traffic.go +++ b/internal/apps/agent/observability/traffic.go @@ -6,6 +6,7 @@ import ( "errors" "io" "log/slog" + "net/http" "os" "regexp" "sort" @@ -28,6 +29,11 @@ type accessLogRecord struct { RequestLength int64 `json:"request_length"` } +const ( + combinedAccessLogMatchGroupCount = 5 + trafficTopDomainsLimit = 8 +) + var combinedAccessLogPattern = regexp.MustCompile(`^(\S+)\s+\S+\s+\S+\s+\[([^]]+)]\s+"\S+\s+(\S+)(?:\s+[^"]*)?"\s+(\d{3})\s+\S+`) type trafficAggregate struct { @@ -43,11 +49,13 @@ type trafficAggregate struct { logs []protocol.NodeAccessLog } +// BuildTrafficReport generates a traffic report using access logs or falling back to managed metrics. func BuildTrafficReport(cfg *config.Config, stateStore *state.Store, managed *ManagedOpenRestyMetrics) *protocol.NodeTrafficReport { report, _, _ := BuildTrafficObservability(cfg, stateStore, managed) return report } +// BuildTrafficObservability returns the traffic report, parsed access logs, and managed metrics. func BuildTrafficObservability(cfg *config.Config, stateStore *state.Store, managed *ManagedOpenRestyMetrics) (*protocol.NodeTrafficReport, []protocol.NodeAccessLog, *ManagedOpenRestyMetrics) { if cfg == nil || stateStore == nil { if managed != nil && managed.TrafficReport != nil { @@ -78,7 +86,7 @@ func readAccessLogDelta(cfg *config.Config, stateStore *state.Store) *trafficAgg } logPath := managedAccessLogPath(cfg) - file, err := os.Open(logPath) + file, err := os.Open(logPath) //nolint:gosec // path is the configured managed access log location if err != nil { if os.IsNotExist(err) { if snapshot.AccessLogOffset != 0 { @@ -167,7 +175,7 @@ func (aggregate *trafficAggregate) consume(line []byte) { } aggregate.requestCount++ - if record.Status >= 500 { + if record.Status >= http.StatusInternalServerError { aggregate.errorCount++ } if record.Status > 0 { @@ -234,7 +242,7 @@ func parseJSONAccessLogRecord(raw string) (parsedAccessLogRecord, bool) { func parseCombinedAccessLogRecord(raw string) (parsedAccessLogRecord, bool) { matches := combinedAccessLogPattern.FindStringSubmatch(raw) - if len(matches) != 5 { + if len(matches) != combinedAccessLogMatchGroupCount { return parsedAccessLogRecord{}, false } timestamp, err := parseAccessLogTime(matches[2]) @@ -265,7 +273,7 @@ func (aggregate *trafficAggregate) report() *protocol.NodeTrafficReport { ErrorCount: aggregate.errorCount, UniqueVisitorCount: int64(len(aggregate.visitors)), StatusCodes: cloneTrafficCounts(aggregate.statusCodes, 0), - TopDomains: topCounts(aggregate.topDomains, 8), + TopDomains: topCounts(aggregate.topDomains, trafficTopDomainsLimit), SourceCountries: map[string]int64{}, } } diff --git a/internal/apps/agent/protocol/alias.go b/internal/apps/agent/protocol/alias.go index 9d21e26c..9897513d 100644 --- a/internal/apps/agent/protocol/alias.go +++ b/internal/apps/agent/protocol/alias.go @@ -1,43 +1,99 @@ +// Package protocol defines type aliases and constants for the agent protocol. package protocol import pkgprotocol "github.com/Rain-kl/Wavelet/pkg/protocol" +// APIResponse is an alias for pkgprotocol.APIResponse. type APIResponse[T any] = pkgprotocol.APIResponse[T] + +// HeartbeatData is an alias for pkgprotocol.HeartbeatData. type HeartbeatData = pkgprotocol.HeartbeatData + +// HeartbeatResult is an alias for pkgprotocol.HeartbeatResult. type HeartbeatResult = pkgprotocol.HeartbeatResult + +// AgentSettings is an alias for pkgprotocol.AgentSettings. type AgentSettings = pkgprotocol.AgentSettings + +// WSMessage is an alias for pkgprotocol.WSMessage. type WSMessage = pkgprotocol.WSMessage + +// WSOutboundMessage is an alias for pkgprotocol.WSOutboundMessage. type WSOutboundMessage = pkgprotocol.WSOutboundMessage + +// WebSocketConnection is an alias for pkgprotocol.WebSocketConnection. type WebSocketConnection = pkgprotocol.WebSocketConnection + +// NodePayload is an alias for pkgprotocol.NodePayload. type NodePayload = pkgprotocol.NodePayload + +// NodeSystemProfile is an alias for pkgprotocol.NodeSystemProfile. type NodeSystemProfile = pkgprotocol.NodeSystemProfile + +// NodeMetricSnapshot is an alias for pkgprotocol.NodeMetricSnapshot. type NodeMetricSnapshot = pkgprotocol.NodeMetricSnapshot + +// NodeOpenrestyObservation is an alias for pkgprotocol.NodeOpenrestyObservation. type NodeOpenrestyObservation = pkgprotocol.NodeOpenrestyObservation + +// NodeTrafficReport is an alias for pkgprotocol.NodeTrafficReport. type NodeTrafficReport = pkgprotocol.NodeTrafficReport + +// NodeAccessLog is an alias for pkgprotocol.NodeAccessLog. type NodeAccessLog = pkgprotocol.NodeAccessLog + +// BufferedObservabilityRecord is an alias for pkgprotocol.BufferedObservabilityRecord. type BufferedObservabilityRecord = pkgprotocol.BufferedObservabilityRecord + +// NodeHealthEvent is an alias for pkgprotocol.NodeHealthEvent. type NodeHealthEvent = pkgprotocol.NodeHealthEvent + +// RegisterNodeResponse is an alias for pkgprotocol.RegisterNodeResponse. type RegisterNodeResponse = pkgprotocol.RegisterNodeResponse + +// ApplyLogPayload is an alias for pkgprotocol.ApplyLogPayload. type ApplyLogPayload = pkgprotocol.ApplyLogPayload + +// ActiveConfigResponse is an alias for pkgprotocol.ActiveConfigResponse. type ActiveConfigResponse = pkgprotocol.ActiveConfigResponse + +// ActiveConfigMeta is an alias for pkgprotocol.ActiveConfigMeta. type ActiveConfigMeta = pkgprotocol.ActiveConfigMeta + +// WAFIPGroup is an alias for pkgprotocol.WAFIPGroup. type WAFIPGroup = pkgprotocol.WAFIPGroup + +// WAFIPGroupSyncRequest is an alias for pkgprotocol.WAFIPGroupSyncRequest. type WAFIPGroupSyncRequest = pkgprotocol.WAFIPGroupSyncRequest + +// WAFIPGroupSyncResponse is an alias for pkgprotocol.WAFIPGroupSyncResponse. type WAFIPGroupSyncResponse = pkgprotocol.WAFIPGroupSyncResponse + +// SupportFile is an alias for pkgprotocol.SupportFile. type SupportFile = pkgprotocol.SupportFile const ( - WSMessageTypeStatus = pkgprotocol.WSMessageTypeStatus - WSMessageTypeSettings = pkgprotocol.WSMessageTypeSettings - WSMessageTypeActiveConfig = pkgprotocol.WSMessageTypeActiveConfig + // WSMessageTypeStatus is an alias for pkgprotocol.WSMessageTypeStatus. + WSMessageTypeStatus = pkgprotocol.WSMessageTypeStatus + // WSMessageTypeSettings is an alias for pkgprotocol.WSMessageTypeSettings. + WSMessageTypeSettings = pkgprotocol.WSMessageTypeSettings + // WSMessageTypeActiveConfig is an alias for pkgprotocol.WSMessageTypeActiveConfig. + WSMessageTypeActiveConfig = pkgprotocol.WSMessageTypeActiveConfig + // WSMessageTypeForceSyncConfig is an alias for pkgprotocol.WSMessageTypeForceSyncConfig. WSMessageTypeForceSyncConfig = pkgprotocol.WSMessageTypeForceSyncConfig - WSMessageTypeWAFIPGroups = pkgprotocol.WSMessageTypeWAFIPGroups - WSMessageTypePing = pkgprotocol.WSMessageTypePing - WSMessageTypePong = pkgprotocol.WSMessageTypePong + // WSMessageTypeWAFIPGroups is an alias for pkgprotocol.WSMessageTypeWAFIPGroups. + WSMessageTypeWAFIPGroups = pkgprotocol.WSMessageTypeWAFIPGroups + // WSMessageTypePing is an alias for pkgprotocol.WSMessageTypePing. + WSMessageTypePing = pkgprotocol.WSMessageTypePing + // WSMessageTypePong is an alias for pkgprotocol.WSMessageTypePong. + WSMessageTypePong = pkgprotocol.WSMessageTypePong ) const ( - OpenrestyStatusHealthy = pkgprotocol.OpenrestyStatusHealthy + // OpenrestyStatusHealthy is an alias for pkgprotocol.OpenrestyStatusHealthy. + OpenrestyStatusHealthy = pkgprotocol.OpenrestyStatusHealthy + // OpenrestyStatusUnhealthy is an alias for pkgprotocol.OpenrestyStatusUnhealthy. OpenrestyStatusUnhealthy = pkgprotocol.OpenrestyStatusUnhealthy - OpenrestyStatusUnknown = pkgprotocol.OpenrestyStatusUnknown -) \ No newline at end of file + // OpenrestyStatusUnknown is an alias for pkgprotocol.OpenrestyStatusUnknown. + OpenrestyStatusUnknown = pkgprotocol.OpenrestyStatusUnknown +) diff --git a/internal/apps/agent/state/observability_buffer.go b/internal/apps/agent/state/observability_buffer.go index b1f48e90..5c11c1d2 100644 --- a/internal/apps/agent/state/observability_buffer.go +++ b/internal/apps/agent/state/observability_buffer.go @@ -1,3 +1,4 @@ +// Package state persists agent runtime state and observability snapshots. package state import ( @@ -13,6 +14,7 @@ import ( const observabilityBufferWindowSeconds = 60 +// ObservabilityBufferRecord stores observability data for a single time window. type ObservabilityBufferRecord struct { WindowStartedAtUnix int64 `json:"window_started_at_unix"` Snapshot *protocol.NodeMetricSnapshot `json:"snapshot,omitempty"` @@ -22,15 +24,18 @@ type ObservabilityBufferRecord struct { QueuedAtUnix int64 `json:"queued_at_unix"` } +// ObservabilityBufferStore persists observability records to disk for replay on heartbeat. type ObservabilityBufferStore struct { path string mu sync.Mutex } +// NewObservabilityBufferStore creates a store backed by the file at path. func NewObservabilityBufferStore(path string) *ObservabilityBufferStore { return &ObservabilityBufferStore{path: filepath.Clean(path)} } +// Upsert inserts or merges an observability record and prunes entries older than retainAfterUnix. func (s *ObservabilityBufferStore) Upsert(record ObservabilityBufferRecord, retainAfterUnix int64) error { if s == nil || record.WindowStartedAtUnix <= 0 || (record.Snapshot == nil && record.OpenrestyObservation == nil && record.TrafficReport == nil && len(record.AccessLogs) == 0) { return nil @@ -113,6 +118,7 @@ func accessLogKey(item protocol.NodeAccessLog) string { return strconv.FormatInt(item.LoggedAtUnix, 10) + "|" + item.RemoteAddr + "|" + item.Host + "|" + item.Path + "|" + strconv.Itoa(item.StatusCode) } +// Replayable returns buffered records from windows before currentWindowStartedAtUnix. func (s *ObservabilityBufferStore) Replayable(currentWindowStartedAtUnix int64, retainAfterUnix int64) ([]ObservabilityBufferRecord, error) { if s == nil { return nil, nil @@ -138,6 +144,7 @@ func (s *ObservabilityBufferStore) Replayable(currentWindowStartedAtUnix int64, return result, nil } +// Ack removes acknowledged observability windows and prunes entries older than retainAfterUnix. func (s *ObservabilityBufferStore) Ack(windowStartedAtUnix []int64, retainAfterUnix int64) error { if s == nil || len(windowStartedAtUnix) == 0 { return nil @@ -185,16 +192,17 @@ func (s *ObservabilityBufferStore) loadUnlocked() ([]ObservabilityBufferRecord, } func (s *ObservabilityBufferStore) saveUnlocked(records []ObservabilityBufferRecord) error { - if err := os.MkdirAll(filepath.Dir(s.path), 0o755); err != nil { + if err := os.MkdirAll(filepath.Dir(s.path), stateDirPerm); err != nil { return err } data, err := json.MarshalIndent(records, "", " ") if err != nil { return err } - return os.WriteFile(s.path, data, 0o644) + return os.WriteFile(s.path, data, stateFilePerm) } +// ObservabilityWindowStartedAt calculates the start of the 60-second window for the given metrics, openresty observation, or traffic report. func ObservabilityWindowStartedAt(snapshot *protocol.NodeMetricSnapshot, openresty *protocol.NodeOpenrestyObservation, traffic *protocol.NodeTrafficReport) int64 { if traffic != nil && traffic.WindowStartedAtUnix > 0 { return traffic.WindowStartedAtUnix - (traffic.WindowStartedAtUnix % observabilityBufferWindowSeconds) diff --git a/internal/apps/agent/state/state.go b/internal/apps/agent/state/state.go index 10bf68ab..921fcb98 100644 --- a/internal/apps/agent/state/state.go +++ b/internal/apps/agent/state/state.go @@ -9,6 +9,13 @@ import ( "sync" ) +const ( + stateDirPerm = 0o750 + stateFilePerm = 0o600 + nodeIDRandomBytes = 8 +) + +// Snapshot represents the state of the agent at a given point in time. type Snapshot struct { NodeID string `json:"node_id"` CurrentVersion string `json:"current_version"` @@ -26,21 +33,25 @@ type Snapshot struct { AccessLogOffset int64 `json:"access_log_offset"` } +// Store manages the storage and retrieval of the agent state snapshot. type Store struct { path string mu sync.Mutex } +// NewStore creates a new Store instance at the given path. func NewStore(path string) *Store { return &Store{path: filepath.Clean(path)} } +// Load loads the snapshot from the store. func (s *Store) Load() (*Snapshot, error) { s.mu.Lock() defer s.mu.Unlock() return s.loadUnlocked() } +// EnsureNodeID returns the existing node ID, or generates and saves a new one if it does not exist. func (s *Store) EnsureNodeID() (string, error) { s.mu.Lock() defer s.mu.Unlock() @@ -62,6 +73,7 @@ func (s *Store) EnsureNodeID() (string, error) { return snapshot.NodeID, nil } +// Save saves the given snapshot to the store. func (s *Store) Save(snapshot *Snapshot) error { s.mu.Lock() defer s.mu.Unlock() @@ -87,18 +99,18 @@ func (s *Store) loadUnlocked() (*Snapshot, error) { } func (s *Store) saveUnlocked(snapshot *Snapshot) error { - if err := os.MkdirAll(filepath.Dir(s.path), 0o755); err != nil { + if err := os.MkdirAll(filepath.Dir(s.path), stateDirPerm); err != nil { return err } data, err := json.MarshalIndent(snapshot, "", " ") if err != nil { return err } - return os.WriteFile(s.path, data, 0o644) + return os.WriteFile(s.path, data, stateFilePerm) } func newNodeID() (string, error) { - buf := make([]byte, 8) + buf := make([]byte, nodeIDRandomBytes) if _, err := rand.Read(buf); err != nil { return "", err } diff --git a/internal/apps/agent/sync/pages.go b/internal/apps/agent/sync/pages.go index 3a903caf..a3898468 100644 --- a/internal/apps/agent/sync/pages.go +++ b/internal/apps/agent/sync/pages.go @@ -1,3 +1,4 @@ +// Package sync applies control-plane configuration to the local agent runtime. package sync import ( @@ -10,6 +11,7 @@ import ( "errors" "fmt" "io" + "math" "os" "path" "path/filepath" @@ -18,6 +20,12 @@ import ( "github.com/Rain-kl/Wavelet/internal/apps/agent/protocol" ) +const ( + pagesMaxExtractedFileBytes = 100 * 1024 * 1024 + pagesDirPerm = 0o755 + pagesManifestFilePerm = 0o644 +) + type pagesSourceDocument struct { Routes []pagesSourceRoute `json:"routes"` } @@ -66,7 +74,7 @@ func (s *Service) ensurePagesDeployment(ctx context.Context, deployment pagesDep return fmt.Errorf("download Pages deployment %d: %w", deployment.DeploymentID, err) } if got := checksumBytes(packageBytes); got != deployment.Checksum { - return fmt.Errorf("Pages deployment %d checksum mismatch: expected %s, got %s", deployment.DeploymentID, deployment.Checksum, got) + return fmt.Errorf("pages deployment %d checksum mismatch: expected %s, got %s", deployment.DeploymentID, deployment.Checksum, got) } releaseDir := pagesReleaseDir(s.pagesDir, deployment.DeploymentID, deployment.Checksum) if !markerMatches(releaseDir, deployment) { @@ -83,7 +91,7 @@ func referencedPagesDeployments(config *protocol.ActiveConfigResponse) ([]pagesD } var doc pagesSourceDocument if err := json.Unmarshal([]byte(config.SourceConfigJSON), &doc); err != nil { - return nil, fmt.Errorf("decode Pages references: %w", err) + return nil, fmt.Errorf("decode pages references: %w", err) } seen := make(map[uint]struct{}) result := make([]pagesDeploymentSource, 0) @@ -94,7 +102,7 @@ func referencedPagesDeployments(config *protocol.ActiveConfigResponse) ([]pagesD deploymentID := route.PagesDeployment.DeploymentID checksum := strings.TrimSpace(route.PagesDeployment.Checksum) if deploymentID == 0 || checksum == "" { - return nil, errors.New("Pages deployment snapshot is incomplete") + return nil, errors.New("pages deployment snapshot is incomplete") } if _, ok := seen[deploymentID]; ok { continue @@ -152,7 +160,7 @@ func findCommonRootPrefix(files []*zip.File) (string, error) { func extractPagesPackage(packageBytes []byte, releaseDir string, deployment pagesDeploymentSource) error { tmpDir := releaseDir + ".tmp" _ = os.RemoveAll(tmpDir) - if err := os.MkdirAll(tmpDir, 0o755); err != nil { + if err := os.MkdirAll(tmpDir, pagesDirPerm); err != nil { return err } reader, err := zip.NewReader(bytes.NewReader(packageBytes), int64(len(packageBytes))) @@ -182,7 +190,7 @@ func extractPagesPackage(packageBytes []byte, releaseDir string, deployment page } if item.FileInfo().Mode()&os.ModeSymlink != 0 { _ = os.RemoveAll(tmpDir) - return fmt.Errorf("Pages package contains unsupported symlink: %s", relativePath) + return fmt.Errorf("pages package contains unsupported symlink: %s", relativePath) } if err := extractPagesFile(item, filepath.Join(tmpDir, relativePath)); err != nil { _ = os.RemoveAll(tmpDir) @@ -197,21 +205,32 @@ func extractPagesPackage(packageBytes []byte, releaseDir string, deployment page return os.Rename(tmpDir, releaseDir) } +func pagesZipEntryCopyLimit(size uint64) (int64, error) { + if size == 0 || size > pagesMaxExtractedFileBytes || size > uint64(math.MaxInt64) { + return 0, errors.New("pages file size out of bounds") + } + return int64(size), nil //nolint:gosec // size is bounded to math.MaxInt64 above +} + func extractPagesFile(item *zip.File, targetPath string) error { - if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil { + if err := os.MkdirAll(filepath.Dir(targetPath), pagesDirPerm); err != nil { return err } source, err := item.Open() if err != nil { return err } - defer source.Close() - target, err := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, item.FileInfo().Mode().Perm()) + defer func() { _ = source.Close() }() + target, err := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, item.FileInfo().Mode().Perm()) //nolint:gosec // targetPath is under managed PagesDir from validated zip entry if err != nil { return err } - defer target.Close() - _, err = io.Copy(target, source) + defer func() { _ = target.Close() }() + limit, err := pagesZipEntryCopyLimit(item.UncompressedSize64) + if err != nil { + return fmt.Errorf("%s: %w", item.Name, err) + } + _, err = io.CopyN(target, source, limit) return err } @@ -219,7 +238,7 @@ func switchPagesCurrentDir(baseDir string, deploymentID uint, releaseDir string) currentDir := pagesCurrentDir(baseDir, deploymentID) previousDir := currentDir + ".previous" _ = os.RemoveAll(previousDir) - if err := os.MkdirAll(filepath.Dir(currentDir), 0o755); err != nil { + if err := os.MkdirAll(filepath.Dir(currentDir), pagesDirPerm); err != nil { return err } if _, err := os.Stat(currentDir); err == nil { @@ -249,25 +268,25 @@ func copyPagesDir(sourceDir string, targetDir string) error { } targetPath := filepath.Join(targetDir, relativePath) if entry.IsDir() { - return os.MkdirAll(targetPath, 0o755) + return os.MkdirAll(targetPath, pagesDirPerm) } info, err := entry.Info() if err != nil { return err } - input, err := os.Open(sourcePath) + input, err := os.Open(sourcePath) //nolint:gosec // sourcePath is under managed PagesDir walk root if err != nil { return err } - defer input.Close() - if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil { + defer func() { _ = input.Close() }() + if err := os.MkdirAll(filepath.Dir(targetPath), pagesDirPerm); err != nil { return err } - output, err := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode().Perm()) + output, err := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode().Perm()) //nolint:gosec // targetPath is under managed PagesDir walk root if err != nil { return err } - defer output.Close() + defer func() { _ = output.Close() }() _, err = io.Copy(output, input) return err }) @@ -279,20 +298,20 @@ func normalizePagesArchivePath(raw string) (string, bool, error) { return "", true, nil } if strings.HasPrefix(name, "/") { - return "", false, fmt.Errorf("Pages package contains absolute path: %s", raw) + return "", false, fmt.Errorf("pages package contains absolute path: %s", raw) } cleaned := path.Clean(name) if cleaned == "." { return "", true, nil } if cleaned == ".." || strings.HasPrefix(cleaned, "../") || strings.Contains(cleaned, "/../") { - return "", false, fmt.Errorf("Pages package path escapes deployment root: %s", raw) + return "", false, fmt.Errorf("pages package path escapes deployment root: %s", raw) } return filepath.FromSlash(cleaned), false, nil } func markerMatches(dir string, deployment pagesDeploymentSource) bool { - data, err := os.ReadFile(filepath.Join(dir, ".openflare-pages.json")) + data, err := os.ReadFile(filepath.Join(dir, ".openflare-pages.json")) //nolint:gosec // dir is managed PagesDir if err != nil { return false } @@ -304,14 +323,11 @@ func markerMatches(dir string, deployment pagesDeploymentSource) bool { } func writePagesMarker(dir string, deployment pagesDeploymentSource) error { - data, err := json.Marshal(pagesDeploymentMarker{ - DeploymentID: deployment.DeploymentID, - Checksum: deployment.Checksum, - }) + data, err := json.Marshal(pagesDeploymentMarker(deployment)) if err != nil { return err } - return os.WriteFile(filepath.Join(dir, ".openflare-pages.json"), data, 0o644) + return os.WriteFile(filepath.Join(dir, ".openflare-pages.json"), data, pagesManifestFilePerm) } func pagesCurrentDir(baseDir string, deploymentID uint) string { diff --git a/internal/apps/agent/sync/service.go b/internal/apps/agent/sync/service.go index baa3491a..b9b0f818 100644 --- a/internal/apps/agent/sync/service.go +++ b/internal/apps/agent/sync/service.go @@ -18,12 +18,14 @@ import ( "github.com/Rain-kl/Wavelet/internal/apps/agent/state" ) +// Apply result constants indicate the outcome reported back to the server. const ( ApplyResultSuccess = "success" ApplyResultWarning = "warning" ApplyResultFailed = "failed" ) +// ConfigClient is the interface for communicating with the server control plane. type ConfigClient interface { GetActiveConfig(ctx context.Context) (*protocol.ActiveConfigResponse, error) DownloadPagesDeploymentPackage(ctx context.Context, deploymentID uint) ([]byte, error) @@ -31,6 +33,7 @@ type ConfigClient interface { SyncWAFIPGroups(ctx context.Context, payload protocol.WAFIPGroupSyncRequest) (*protocol.WAFIPGroupSyncResponse, error) } +// NginxManager is the interface for managing the local OpenResty instance. type NginxManager interface { Apply(ctx context.Context, mainConfig string, routeConfig string, supportFiles []protocol.SupportFile) nginx.ApplyOutcome EnsureRuntime(ctx context.Context, recreate bool) error @@ -40,6 +43,7 @@ type NginxManager interface { SyncWAFIPGroups(groups []protocol.WAFIPGroup) error } +// Service orchestrates configuration synchronisation between the server and the local OpenResty instance. type Service struct { client ConfigClient nginxManager NginxManager @@ -47,10 +51,12 @@ type Service struct { pagesDir string } +// SetPagesDir sets the local directory used for pages deployment packages. func (s *Service) SetPagesDir(path string) { s.pagesDir = strings.TrimSpace(path) } +// New creates a new Service with the given client, nginx manager, and state store. func New(client ConfigClient, nginxManager NginxManager, stateStore *state.Store) *Service { return &Service{ client: client, @@ -59,100 +65,34 @@ func New(client ConfigClient, nginxManager NginxManager, stateStore *state.Store } } +// SyncOnce performs a single periodic sync against the given active config summary. func (s *Service) SyncOnce(ctx context.Context, target *protocol.ActiveConfigMeta) error { return s.sync(ctx, false, target) } +// SyncOnStartup performs an initial sync at agent startup, applying config even when checksums already match. func (s *Service) SyncOnStartup(ctx context.Context, target *protocol.ActiveConfigMeta) error { return s.sync(ctx, true, target) } func (s *Service) sync(ctx context.Context, startup bool, target *protocol.ActiveConfigMeta) error { - mode := "periodic" - if startup { - mode = "startup" - } - snapshot, err := s.stateStore.Load() + mode := syncMode(startup) + snapshot, currentChecksum, err := s.loadSyncState() if err != nil { return err } - currentChecksum, err := s.nginxManager.CurrentChecksum() - if err != nil { - return err - } - - if target != nil { - target.Version = strings.TrimSpace(target.Version) - target.Checksum = strings.TrimSpace(target.Checksum) - } + normalizeSyncTarget(target) if target == nil || target.Version == "" || target.Checksum == "" { - if !startup { - slog.Debug("skipping sync because heartbeat returned no active config summary", "mode", mode) - return nil - } - slog.Debug("sync startup fallback: active config summary unavailable, fetching active config directly") - config, fetchErr := s.client.GetActiveConfig(ctx) - if fetchErr != nil { - slog.Error("fetch active config failed", "mode", mode, "error", fetchErr) - return fetchErr - } - target = &protocol.ActiveConfigMeta{ - Version: config.Version, - Checksum: config.Checksum, - } - return s.applyIfNeeded(ctx, mode, startup, snapshot, currentChecksum, target, config) + return s.syncWithoutTarget(ctx, mode, startup, snapshot, currentChecksum) } - if currentChecksum == target.Checksum { - if startup { - config, fetchErr := s.client.GetActiveConfig(ctx) - if fetchErr != nil { - slog.Error("fetch active config failed", "mode", mode, "error", fetchErr) - return fetchErr - } - return s.applyIfNeeded(ctx, mode, startup, snapshot, currentChecksum, target, config) - } - slog.Debug("local openresty config already up to date", "mode", mode, "version", target.Version) - shouldReport := shouldReportNoopApply(snapshot, target.Version, target.Checksum) - if shouldReport { - if err = s.reportNoopApply(ctx, snapshot.NodeID, target.Version, target.Checksum, "", "", 0); err != nil { - return err - } - } - snapshot.CurrentVersion = target.Version - snapshot.CurrentChecksum = target.Checksum - clearBlockedTarget(snapshot) - snapshot.LastError = "" - slog.Debug("sync finished without changes", "mode", mode, "version", target.Version) - return s.stateStore.Save(snapshot) + return s.syncMatchingChecksum(ctx, mode, startup, snapshot, currentChecksum, target) } - if isBlockedTarget(snapshot, target.Version, target.Checksum) { - slog.Warn("skipping blocked config version after previous failed apply", "mode", mode, "version", target.Version, "checksum", target.Checksum) - if startup { - if err = s.ensureRuntimeForCurrentConfig(ctx, mode, snapshot, currentChecksum); err != nil { - return err - } - return s.stateStore.Save(snapshot) - } - return nil - } - if hasBlockedTarget(snapshot) { - clearBlockedTarget(snapshot) - } - if snapshot.CurrentVersion == target.Version && snapshot.CurrentChecksum == target.Checksum && !startup { - slog.Debug("skipping config fetch because state already records target version/checksum", "version", target.Version, "checksum", target.Checksum) - return s.stateStore.Save(snapshot) - } - - config, err := s.client.GetActiveConfig(ctx) - if err != nil { - slog.Error("fetch active config failed", "mode", mode, "error", err) - return err - } - return s.applyIfNeeded(ctx, mode, startup, snapshot, currentChecksum, target, config) + return s.syncMismatchedChecksum(ctx, mode, startup, snapshot, currentChecksum, target) } +// ForceSyncOnce clears any blocked target state then unconditionally fetches and applies the active config. func (s *Service) ForceSyncOnce(ctx context.Context, target *protocol.ActiveConfigMeta) error { snapshot, err := s.stateStore.Load() if err != nil { @@ -174,6 +114,7 @@ func (s *Service) ForceSyncOnce(ctx context.Context, target *protocol.ActiveConf return s.applyIfNeeded(ctx, "force", true, snapshot, currentChecksum, target, config) } +// WAFIPGroupChecksums returns the current per-group checksums held by the nginx manager. func (s *Service) WAFIPGroupChecksums() (map[string]string, error) { if s.nginxManager == nil { return map[string]string{}, nil @@ -181,7 +122,8 @@ func (s *Service) WAFIPGroupChecksums() (map[string]string, error) { return s.nginxManager.WAFIPGroupChecksums() } -func (s *Service) ApplyWAFIPGroups(ctx context.Context, groups []protocol.WAFIPGroup) error { +// ApplyWAFIPGroups writes the given WAF IP groups to the nginx manager. +func (s *Service) ApplyWAFIPGroups(_ context.Context, groups []protocol.WAFIPGroup) error { if len(groups) == 0 || s.nginxManager == nil { return nil } @@ -190,36 +132,13 @@ func (s *Service) ApplyWAFIPGroups(ctx context.Context, groups []protocol.WAFIPG func (s *Service) applyIfNeeded(ctx context.Context, mode string, startup bool, snapshot *state.Snapshot, currentChecksum string, target *protocol.ActiveConfigMeta, config *protocol.ActiveConfigResponse) error { if currentChecksum == config.Checksum && !startup { - slog.Debug("local openresty config already up to date", "mode", mode, "version", config.Version) - shouldReport := shouldReportNoopApply(snapshot, config.Version, config.Checksum) - if shouldReport { - rendered, renderErr := renderActiveConfig(config) - if renderErr != nil { - return renderErr - } - if err := s.reportNoopApply(ctx, snapshot.NodeID, config.Version, config.Checksum, checksumString(rendered.mainConfig), checksumString(rendered.routeConfig), len(rendered.supportFiles)); err != nil { - return err - } - } - snapshot.CurrentVersion = config.Version - snapshot.CurrentChecksum = config.Checksum - clearBlockedTarget(snapshot) - snapshot.LastError = "" - slog.Debug("sync finished without changes", "mode", mode, "version", config.Version) - return s.stateStore.Save(snapshot) + return s.handleUpToDateConfig(ctx, mode, snapshot, config) } if target != nil && (target.Version != config.Version || target.Checksum != config.Checksum) { slog.Warn("active config changed between heartbeat and fetch", "heartbeat_version", target.Version, "heartbeat_checksum", target.Checksum, "fetched_version", config.Version, "fetched_checksum", config.Checksum) } - if isBlockedTarget(snapshot, config.Version, config.Checksum) { - slog.Warn("skipping blocked config after fetch because the same version previously failed", "mode", mode, "version", config.Version, "checksum", config.Checksum) - if startup { - if err := s.ensureRuntimeForCurrentConfig(ctx, mode, snapshot, currentChecksum); err != nil { - return err - } - return s.stateStore.Save(snapshot) - } - return nil + if handled, err := s.handleBlockedConfigAfterFetch(ctx, mode, startup, snapshot, currentChecksum, config); handled { + return err } if hasBlockedTarget(snapshot) { clearBlockedTarget(snapshot) @@ -228,6 +147,10 @@ func (s *Service) applyIfNeeded(ctx context.Context, mode string, startup bool, slog.Debug("skipping apply because state already records target version/checksum", "version", config.Version, "checksum", config.Checksum) return s.stateStore.Save(snapshot) } + return s.applyRenderedConfig(ctx, mode, snapshot, currentChecksum, config) +} + +func (s *Service) applyRenderedConfig(ctx context.Context, mode string, snapshot *state.Snapshot, currentChecksum string, config *protocol.ActiveConfigResponse) error { rendered, err := renderActiveConfig(config) if err != nil { return err @@ -238,49 +161,8 @@ func (s *Service) applyIfNeeded(ctx context.Context, mode string, startup bool, mainConfigChecksum := checksumString(rendered.mainConfig) routeConfigChecksum := checksumString(rendered.routeConfig) slog.Info("applying new openresty config", "mode", mode, "from_version", snapshot.CurrentVersion, "to_version", config.Version, "old_checksum", currentChecksum, "new_checksum", config.Checksum) - outcome := s.nginxManager.Apply(ctx, rendered.mainConfig, rendered.routeConfig, rendered.supportFiles) - message := strings.TrimSpace(outcome.Message) - if outcome.Status == "" { - outcome.Status = nginx.ApplyStatusFatal - if message == "" { - message = "openresty apply returned empty outcome" - } - } - - reportResult := ApplyResultFailed - switch outcome.Status { - case nginx.ApplyStatusSuccess: - slog.Info("openresty config applied successfully", "mode", mode, "version", config.Version) - snapshot.CurrentVersion = config.Version - snapshot.CurrentChecksum = config.Checksum - clearBlockedTarget(snapshot) - snapshot.LastError = "" - snapshot.OpenrestyStatus = protocol.OpenrestyStatusHealthy - snapshot.OpenrestyMessage = "" - reportResult = ApplyResultSuccess - if message == "" { - message = "apply success" - } - case nginx.ApplyStatusWarning: - if message == "" { - message = "apply rolled back to previous config" - } - slog.Warn("openresty config apply rolled back", "mode", mode, "version", config.Version, "message", message) - markBlockedTarget(snapshot, config.Version, config.Checksum, message) - snapshot.LastError = message - snapshot.OpenrestyStatus = protocol.OpenrestyStatusHealthy - snapshot.OpenrestyMessage = message - reportResult = ApplyResultWarning - default: - if message == "" { - message = "openresty apply failed" - } - slog.Error("apply openresty config failed", "mode", mode, "version", config.Version, "message", message) - markBlockedTarget(snapshot, config.Version, config.Checksum, message) - snapshot.LastError = message - snapshot.OpenrestyStatus = protocol.OpenrestyStatusUnhealthy - snapshot.OpenrestyMessage = message - } + outcome, message := normalizeApplyOutcome(s.nginxManager.Apply(ctx, rendered.mainConfig, rendered.routeConfig, rendered.supportFiles)) + applyResult := updateSnapshotFromApplyOutcome(mode, snapshot, config, outcome, message) if err := s.stateStore.Save(snapshot); err != nil { return err @@ -288,25 +170,25 @@ func (s *Service) applyIfNeeded(ctx context.Context, mode string, startup bool, if err := s.client.ReportApplyLog(ctx, protocol.ApplyLogPayload{ NodeID: snapshot.NodeID, Version: config.Version, - Result: reportResult, - Message: message, + Result: applyResult.reportResult, + Message: applyResult.message, Checksum: config.Checksum, MainConfigChecksum: mainConfigChecksum, RouteConfigChecksum: routeConfigChecksum, SupportFileCount: len(rendered.supportFiles), }); err != nil { - slog.Error("report apply log failed", "version", config.Version, "result", reportResult, "error", err) + slog.Error("report apply log failed", "version", config.Version, "result", applyResult.reportResult, "error", err) return err } - if reportResult == ApplyResultFailed { + if applyResult.reportResult == ApplyResultFailed { slog.Warn("failed apply log reported", "version", config.Version) - return outcomeError(config.Version, message) + return outcomeError(config.Version, applyResult.message) } if err := s.syncReferencedWAFIPGroups(ctx, rendered.supportFiles); err != nil { slog.Error("sync referenced waf ip groups failed", "version", config.Version, "error", err) return err } - slog.Debug("apply log reported", "version", config.Version, "result", reportResult) + slog.Debug("apply log reported", "version", config.Version, "result", applyResult.reportResult) return nil } @@ -476,13 +358,13 @@ func (s *Service) ensureRuntimeForCurrentConfig(ctx context.Context, mode string if err := s.nginxManager.EnsureRuntime(ctx, true); err != nil { if strings.TrimSpace(snapshot.CurrentChecksum) == "" { reason := fmt.Sprintf("blocked config %s has no historical config and current local config cannot start: %v", strings.TrimSpace(snapshot.BlockedVersion), err) - if fallbackErr := s.nginxManager.EnsureSafeFallbackRuntime(ctx, reason); fallbackErr == nil { + fallbackErr := s.nginxManager.EnsureSafeFallbackRuntime(ctx, reason) + if fallbackErr == nil { snapshot.OpenrestyStatus = protocol.OpenrestyStatusHealthy snapshot.OpenrestyMessage = "safe default fallback runtime started" return nil - } else { - err = fmt.Errorf("%v; fallback recovery failed: %w", err, fallbackErr) } + err = fmt.Errorf("%v; fallback recovery failed: %w", err, fallbackErr) } snapshot.OpenrestyStatus = protocol.OpenrestyStatusUnhealthy snapshot.OpenrestyMessage = err.Error() diff --git a/internal/apps/agent/sync/sync_helpers.go b/internal/apps/agent/sync/sync_helpers.go new file mode 100644 index 00000000..8bcd5bf9 --- /dev/null +++ b/internal/apps/agent/sync/sync_helpers.go @@ -0,0 +1,205 @@ +package sync + +import ( + "context" + "log/slog" + "strings" + + "github.com/Rain-kl/Wavelet/internal/apps/agent/nginx" + "github.com/Rain-kl/Wavelet/internal/apps/agent/protocol" + "github.com/Rain-kl/Wavelet/internal/apps/agent/state" +) + +func syncMode(startup bool) string { + if startup { + return "startup" + } + return "periodic" +} + +func normalizeSyncTarget(target *protocol.ActiveConfigMeta) { + if target == nil { + return + } + target.Version = strings.TrimSpace(target.Version) + target.Checksum = strings.TrimSpace(target.Checksum) +} + +func (s *Service) loadSyncState() (*state.Snapshot, string, error) { + snapshot, err := s.stateStore.Load() + if err != nil { + return nil, "", err + } + currentChecksum, err := s.nginxManager.CurrentChecksum() + if err != nil { + return nil, "", err + } + return snapshot, currentChecksum, nil +} + +func (s *Service) syncWithoutTarget(ctx context.Context, mode string, startup bool, snapshot *state.Snapshot, currentChecksum string) error { + if !startup { + slog.Debug("skipping sync because heartbeat returned no active config summary", "mode", mode) + return nil + } + slog.Debug("sync startup fallback: active config summary unavailable, fetching active config directly") + config, fetchErr := s.client.GetActiveConfig(ctx) + if fetchErr != nil { + slog.Error("fetch active config failed", "mode", mode, "error", fetchErr) + return fetchErr + } + target := &protocol.ActiveConfigMeta{ + Version: config.Version, + Checksum: config.Checksum, + } + return s.applyIfNeeded(ctx, mode, startup, snapshot, currentChecksum, target, config) +} + +func (s *Service) syncMatchingChecksum(ctx context.Context, mode string, startup bool, snapshot *state.Snapshot, currentChecksum string, target *protocol.ActiveConfigMeta) error { + if startup { + config, fetchErr := s.client.GetActiveConfig(ctx) + if fetchErr != nil { + slog.Error("fetch active config failed", "mode", mode, "error", fetchErr) + return fetchErr + } + return s.applyIfNeeded(ctx, mode, startup, snapshot, currentChecksum, target, config) + } + return s.finishUpToDateSync(ctx, mode, snapshot, target) +} + +func (s *Service) finishUpToDateSync(ctx context.Context, mode string, snapshot *state.Snapshot, target *protocol.ActiveConfigMeta) error { + slog.Debug("local openresty config already up to date", "mode", mode, "version", target.Version) + if shouldReportNoopApply(snapshot, target.Version, target.Checksum) { + if err := s.reportNoopApply(ctx, snapshot.NodeID, target.Version, target.Checksum, "", "", 0); err != nil { + return err + } + } + snapshot.CurrentVersion = target.Version + snapshot.CurrentChecksum = target.Checksum + clearBlockedTarget(snapshot) + snapshot.LastError = "" + slog.Debug("sync finished without changes", "mode", mode, "version", target.Version) + return s.stateStore.Save(snapshot) +} + +func (s *Service) syncMismatchedChecksum(ctx context.Context, mode string, startup bool, snapshot *state.Snapshot, currentChecksum string, target *protocol.ActiveConfigMeta) error { + if isBlockedTarget(snapshot, target.Version, target.Checksum) { + slog.Warn("skipping blocked config version after previous failed apply", "mode", mode, "version", target.Version, "checksum", target.Checksum) + if startup { + if err := s.ensureRuntimeForCurrentConfig(ctx, mode, snapshot, currentChecksum); err != nil { + return err + } + return s.stateStore.Save(snapshot) + } + return nil + } + if hasBlockedTarget(snapshot) { + clearBlockedTarget(snapshot) + } + if snapshot.CurrentVersion == target.Version && snapshot.CurrentChecksum == target.Checksum && !startup { + slog.Debug("skipping config fetch because state already records target version/checksum", "version", target.Version, "checksum", target.Checksum) + return s.stateStore.Save(snapshot) + } + + config, err := s.client.GetActiveConfig(ctx) + if err != nil { + slog.Error("fetch active config failed", "mode", mode, "error", err) + return err + } + return s.applyIfNeeded(ctx, mode, startup, snapshot, currentChecksum, target, config) +} + +func (s *Service) handleUpToDateConfig(ctx context.Context, mode string, snapshot *state.Snapshot, config *protocol.ActiveConfigResponse) error { + slog.Debug("local openresty config already up to date", "mode", mode, "version", config.Version) + if shouldReportNoopApply(snapshot, config.Version, config.Checksum) { + rendered, renderErr := renderActiveConfig(config) + if renderErr != nil { + return renderErr + } + if err := s.reportNoopApply( + ctx, + snapshot.NodeID, + config.Version, + config.Checksum, + checksumString(rendered.mainConfig), + checksumString(rendered.routeConfig), + len(rendered.supportFiles), + ); err != nil { + return err + } + } + snapshot.CurrentVersion = config.Version + snapshot.CurrentChecksum = config.Checksum + clearBlockedTarget(snapshot) + snapshot.LastError = "" + slog.Debug("sync finished without changes", "mode", mode, "version", config.Version) + return s.stateStore.Save(snapshot) +} + +func (s *Service) handleBlockedConfigAfterFetch(ctx context.Context, mode string, startup bool, snapshot *state.Snapshot, currentChecksum string, config *protocol.ActiveConfigResponse) (bool, error) { + if !isBlockedTarget(snapshot, config.Version, config.Checksum) { + return false, nil + } + slog.Warn("skipping blocked config after fetch because the same version previously failed", "mode", mode, "version", config.Version, "checksum", config.Checksum) + if startup { + if err := s.ensureRuntimeForCurrentConfig(ctx, mode, snapshot, currentChecksum); err != nil { + return true, err + } + return true, s.stateStore.Save(snapshot) + } + return true, nil +} + +type applyOutcomeResult struct { + reportResult string + message string +} + +func normalizeApplyOutcome(outcome nginx.ApplyOutcome) (nginx.ApplyOutcome, string) { + message := strings.TrimSpace(outcome.Message) + if outcome.Status == "" { + outcome.Status = nginx.ApplyStatusFatal + if message == "" { + message = "openresty apply returned empty outcome" + } + } + return outcome, message +} + +func updateSnapshotFromApplyOutcome(mode string, snapshot *state.Snapshot, config *protocol.ActiveConfigResponse, outcome nginx.ApplyOutcome, message string) applyOutcomeResult { + result := applyOutcomeResult{reportResult: ApplyResultFailed, message: message} + switch outcome.Status { + case nginx.ApplyStatusSuccess: + slog.Info("openresty config applied successfully", "mode", mode, "version", config.Version) + snapshot.CurrentVersion = config.Version + snapshot.CurrentChecksum = config.Checksum + clearBlockedTarget(snapshot) + snapshot.LastError = "" + snapshot.OpenrestyStatus = protocol.OpenrestyStatusHealthy + snapshot.OpenrestyMessage = "" + result.reportResult = ApplyResultSuccess + if result.message == "" { + result.message = "apply success" + } + case nginx.ApplyStatusWarning: + if result.message == "" { + result.message = "apply rolled back to previous config" + } + slog.Warn("openresty config apply rolled back", "mode", mode, "version", config.Version, "message", result.message) + markBlockedTarget(snapshot, config.Version, config.Checksum, result.message) + snapshot.LastError = result.message + snapshot.OpenrestyStatus = protocol.OpenrestyStatusHealthy + snapshot.OpenrestyMessage = result.message + result.reportResult = ApplyResultWarning + default: + if result.message == "" { + result.message = "openresty apply failed" + } + slog.Error("apply openresty config failed", "mode", mode, "version", config.Version, "message", result.message) + markBlockedTarget(snapshot, config.Version, config.Checksum, result.message) + snapshot.LastError = result.message + snapshot.OpenrestyStatus = protocol.OpenrestyStatusUnhealthy + snapshot.OpenrestyMessage = result.message + } + return result +} diff --git a/internal/apps/agent/updater/updater.go b/internal/apps/agent/updater/updater.go index 30cb8796..ce8e16e3 100644 --- a/internal/apps/agent/updater/updater.go +++ b/internal/apps/agent/updater/updater.go @@ -1,17 +1,22 @@ +// Package updater provides agent self-update integration with the edge updater. package updater import ( - edgeupdater "github.com/Rain-kl/Wavelet/internal/apps/edge/updater" "github.com/Rain-kl/Wavelet/internal/apps/agent/config" + edgeupdater "github.com/Rain-kl/Wavelet/internal/apps/edge/updater" ) +// Service is an alias for the edge updater service type used by the agent. type Service = edgeupdater.Service + +// UpdateOptions is an alias for the edge updater options type. type UpdateOptions = edgeupdater.UpdateOptions +// New creates and returns a new agent updater Service with the agent-specific configuration. func New() *Service { return edgeupdater.New(edgeupdater.Config{ LocalVersion: config.Version, AssetPrefix: "openflare-agent", LogLabel: "agent", }) -} \ No newline at end of file +} diff --git a/internal/apps/agent/wsclient/client.go b/internal/apps/agent/wsclient/client.go index 5440ed74..7530c033 100644 --- a/internal/apps/agent/wsclient/client.go +++ b/internal/apps/agent/wsclient/client.go @@ -1,3 +1,4 @@ +// Package wsclient provides the agent-side WebSocket client for connecting to the OpenFlare server. package wsclient import ( @@ -8,28 +9,38 @@ import ( edgews "github.com/Rain-kl/Wavelet/internal/apps/edge/wsclient" ) +// WSMessage is an alias for the WebSocket message type. type WSMessage = edgews.WSMessage + +// MessageHandler is an alias for the WebSocket message handler function type. type MessageHandler = edgews.MessageHandler + +// Connection is an alias for the AgentConnection interface. type Connection = edgews.AgentConnection +// Client wraps the connection client for agent WebSockets. type Client struct { inner *edgews.Client } +// New creates a new Client instance. func New(baseURL, token string, timeout time.Duration) *Client { return &Client{ inner: edgews.New(edgews.PresetAgent, baseURL, token, timeout), } } +// SetToken updates the client's token. func (c *Client) SetToken(token string) { c.inner.SetToken(token) } +// URL returns the WebSocket client's target URL. func (c *Client) URL() string { return c.inner.URL() } +// Connect establishes a WebSocket connection to the server and returns the connection handle. func (c *Client) Connect(ctx context.Context) (protocol.WebSocketConnection, error) { return c.inner.ConnectAgent(ctx) -} \ No newline at end of file +} diff --git a/internal/apps/edge/config/duration.go b/internal/apps/edge/config/duration.go index abed860b..86303c08 100644 --- a/internal/apps/edge/config/duration.go +++ b/internal/apps/edge/config/duration.go @@ -1,3 +1,4 @@ +// Package config provides shared configuration types for edge applications. package config import ( @@ -8,8 +9,11 @@ import ( "time" ) +// MillisecondDuration is a time.Duration that marshals to/from JSON as an integer number of milliseconds +// or as a Go duration string (e.g. "1s", "500ms"). type MillisecondDuration time.Duration +// Duration returns the underlying time.Duration value. func (d MillisecondDuration) Duration() time.Duration { return time.Duration(d) } @@ -18,6 +22,7 @@ func (d MillisecondDuration) String() string { return time.Duration(d).String() } +// UnmarshalJSON decodes either a numeric millisecond value or a quoted Go duration string. func (d *MillisecondDuration) UnmarshalJSON(data []byte) error { raw := strings.TrimSpace(string(data)) if raw == "" || raw == "null" { @@ -49,6 +54,7 @@ func (d *MillisecondDuration) UnmarshalJSON(data []byte) error { return nil } +// MarshalJSON encodes the duration as an integer number of milliseconds. func (d MillisecondDuration) MarshalJSON() ([]byte, error) { return json.Marshal(time.Duration(d).Milliseconds()) -} \ No newline at end of file +} diff --git a/internal/apps/edge/heartbeat/autoupdate.go b/internal/apps/edge/heartbeat/autoupdate.go index ec6f8e19..bda6cab2 100644 --- a/internal/apps/edge/heartbeat/autoupdate.go +++ b/internal/apps/edge/heartbeat/autoupdate.go @@ -1,3 +1,4 @@ +// Package heartbeat handles periodic heartbeat and update checks. package heartbeat import ( @@ -8,6 +9,7 @@ import ( edgeupdater "github.com/Rain-kl/Wavelet/internal/apps/edge/updater" ) +// AutoUpdateSettings defines the settings for automatic edge updates. type AutoUpdateSettings struct { AutoUpdate bool UpdateNow bool @@ -16,6 +18,7 @@ type AutoUpdateSettings struct { UpdateTag string } +// TryAutoUpdate attempts to check and apply auto updates for the edge service. func TryAutoUpdate(ctx context.Context, updater *edgeupdater.Service, settings *AutoUpdateSettings, logLabel string) { if settings == nil || updater == nil { return @@ -38,4 +41,4 @@ func TryAutoUpdate(ctx context.Context, updater *edgeupdater.Service, settings * if err != nil { slog.Error(logLabel+" update check failed", "error", err) } -} \ No newline at end of file +} diff --git a/internal/apps/edge/httpclient/client.go b/internal/apps/edge/httpclient/client.go index 11d17c5b..3f4b5420 100644 --- a/internal/apps/edge/httpclient/client.go +++ b/internal/apps/edge/httpclient/client.go @@ -1,3 +1,4 @@ +// Package httpclient provides an authenticated HTTP client for edge services. package httpclient import ( @@ -12,6 +13,7 @@ import ( "time" ) +// Client is an HTTP client wrapper for communicating with remote HTTP services. type Client struct { baseURL string token string @@ -19,6 +21,7 @@ type Client struct { httpClient *http.Client } +// New creates a new Client instance. func New(baseURL, token string, timeout time.Duration, authHeader string) *Client { return &Client{ baseURL: strings.TrimRight(baseURL, "/"), @@ -28,11 +31,13 @@ func New(baseURL, token string, timeout time.Duration, authHeader string) *Clien } } +// SetToken updates the client auth token. func (c *Client) SetToken(token string) { c.token = strings.TrimSpace(token) slog.Debug("http client token updated") } +// GetJSON sends a GET request and decodes the response body into target. func (c *Client) GetJSON(ctx context.Context, path string, target any) error { req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+path, nil) if err != nil { @@ -42,6 +47,7 @@ func (c *Client) GetJSON(ctx context.Context, path string, target any) error { return c.do(req, target) } +// PostJSON sends a POST request with JSON body and decodes the response body into target. func (c *Client) PostJSON(ctx context.Context, path string, body any, target any) error { data, err := json.Marshal(body) if err != nil { @@ -56,6 +62,7 @@ func (c *Client) PostJSON(ctx context.Context, path string, body any, target any return c.do(req, target) } +// DoRaw performs an HTTP request with custom headers and returns the raw response. func (c *Client) DoRaw(ctx context.Context, method, path string, headers map[string]string) (*http.Response, error) { req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, nil) if err != nil { @@ -80,12 +87,11 @@ func (c *Client) do(req *http.Request, target any) error { slog.Error("http request failed", "method", req.Method, "path", req.URL.Path, "error", err) return err } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { + defer func() { + if err := res.Body.Close(); err != nil { slog.Error("failed to close response body", "error", err) } - }(res.Body) + }() body, err := io.ReadAll(res.Body) if err != nil { @@ -106,6 +112,7 @@ func (c *Client) do(req *http.Request, target any) error { return nil } +// APIError creates a new API error with the given message if it is not empty. func APIError(msg string) error { if strings.TrimSpace(msg) == "" { return nil @@ -113,6 +120,7 @@ func APIError(msg string) error { return errors.New(msg) } +// ReadBodyError parses the error message from the response body, or returns the fallback message. func ReadBodyError(body []byte, fallback string) error { var errBody struct { ErrorMsg string `json:"error_msg"` @@ -123,10 +131,11 @@ func ReadBodyError(body []byte, fallback string) error { return errors.New(fallback) } +// ReadHTTPError reads the error message from the HTTP response. func ReadHTTPError(res *http.Response) error { body, err := io.ReadAll(res.Body) if err != nil { return errors.New(res.Status) } return ReadBodyError(body, res.Status) -} \ No newline at end of file +} diff --git a/internal/apps/edge/logging/logging.go b/internal/apps/edge/logging/logging.go index 0663a86a..b80071b5 100644 --- a/internal/apps/edge/logging/logging.go +++ b/internal/apps/edge/logging/logging.go @@ -1,3 +1,4 @@ +// Package logging configures structured logging for edge applications. package logging import ( @@ -6,10 +7,12 @@ import ( "strings" ) +// Options holds configuration options for the structured logger. type Options struct { AddSource bool } +// Setup initialises the default slog handler using the given options and the LOG_LEVEL environment variable. func Setup(opts Options) { handlerOpts := &slog.HandlerOptions{ AddSource: opts.AddSource, @@ -19,6 +22,7 @@ func Setup(opts Options) { slog.SetDefault(slog.New(handler)) } +// ParseLevel converts a log-level string (e.g. "debug", "warn") to the corresponding slog.Level. func ParseLevel(value string) slog.Level { switch strings.ToLower(strings.TrimSpace(value)) { case "debug": @@ -30,4 +34,4 @@ func ParseLevel(value string) slog.Level { default: return slog.LevelInfo } -} \ No newline at end of file +} diff --git a/internal/apps/edge/nodeip/nodeip.go b/internal/apps/edge/nodeip/nodeip.go index f1d57338..771348c4 100644 --- a/internal/apps/edge/nodeip/nodeip.go +++ b/internal/apps/edge/nodeip/nodeip.go @@ -1,3 +1,4 @@ +// Package nodeip detects the preferred public IP address for edge nodes. package nodeip import ( @@ -9,20 +10,36 @@ import ( "github.com/Rain-kl/Wavelet/pkg/geoip/iputil" ) +const ( + outboundIPLookupTimeout = 5 * time.Second + publicIPPriorityScore = 2 // matches iputil.Score for public IPv4 addresses +) + +// LookupOutboundIP and LookupLocalIP are the provider functions used to detect the node's outbound/local IP. +// They are package-level variables so they can be overridden in tests. var ( LookupOutboundIP = geoip.GetOutboundIP LookupLocalIP = DetectLocal ) +// Detect returns the best available outbound or local IPv4 address for this node. func Detect() string { - if ip := detectOutbound(); ip != "" { + if ip := detectOutbound(context.Background()); ip != "" { return ip } return LookupLocalIP() } -func detectOutbound() string { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) +// DetectWithContext returns the best available outbound or local IPv4 address, respecting ctx for cancellation. +func DetectWithContext(ctx context.Context) string { + if ip := detectOutbound(ctx); ip != "" { + return ip + } + return LookupLocalIP() +} + +func detectOutbound(ctx context.Context) string { + ctx, cancel := context.WithTimeout(ctx, outboundIPLookupTimeout) defer cancel() ip, err := LookupOutboundIP(ctx) if err != nil || ip == nil { @@ -31,6 +48,7 @@ func detectOutbound() string { return ip.String() } +// DetectLocal returns the highest-priority non-loopback local IPv4 address found on system interfaces. func DetectLocal() string { interfaces, err := net.Interfaces() if err != nil { @@ -60,10 +78,10 @@ func DetectLocal() string { bestIP = ipv4.String() bestPriority = priority } - if bestPriority == 2 { + if bestPriority == publicIPPriorityScore { return bestIP } } } return bestIP -} \ No newline at end of file +} diff --git a/internal/apps/edge/observability/linux.go b/internal/apps/edge/observability/linux.go index 6d2b1bc3..e7796c79 100644 --- a/internal/apps/edge/observability/linux.go +++ b/internal/apps/edge/observability/linux.go @@ -1,7 +1,9 @@ +// Package observability provides helpers that read Linux /proc and /sys metrics for system monitoring. package observability import ( "bufio" + "math" "os" "path/filepath" "runtime" @@ -10,13 +12,20 @@ import ( "syscall" ) +const ( + memInfoMinFieldCount = 2 + cpuStatMinFieldCount = 5 + netDevMinFieldCount = 16 + diskStatsMinFieldCount = 14 +) + // ReadLinuxOSRelease returns the OS name and version from /etc/os-release. func ReadLinuxOSRelease() (string, string) { file, err := os.Open("/etc/os-release") if err != nil { return runtime.GOOS, "" } - defer file.Close() + defer func() { _ = file.Close() }() values := make(map[string]string) scanner := bufio.NewScanner(file) @@ -47,7 +56,7 @@ func ReadLinuxCPUModel() string { if err != nil { return "" } - defer file.Close() + defer func() { _ = file.Close() }() scanner := bufio.NewScanner(file) for scanner.Scan() { @@ -68,7 +77,7 @@ func ReadMemInfo() (int64, int64) { if err != nil { return 0, 0 } - defer file.Close() + defer func() { _ = file.Close() }() var memTotalKB int64 var memAvailableKB int64 @@ -96,7 +105,7 @@ func ReadMemInfo() (int64, int64) { func parseMemInfoValue(line string) int64 { fields := strings.Fields(line) - if len(fields) < 2 { + if len(fields) < memInfoMinFieldCount { return 0 } value, err := strconv.ParseInt(fields[1], 10, 64) @@ -135,7 +144,7 @@ func ReadLinuxCPUStat() (uint64, uint64) { continue } fields := strings.Fields(line) - if len(fields) < 5 { + if len(fields) < cpuStatMinFieldCount { return 0, 0 } var total uint64 @@ -161,7 +170,7 @@ func ReadLinuxNetworkTotals() (int64, int64) { if err != nil { return 0, 0 } - defer file.Close() + defer func() { _ = file.Close() }() var rx int64 var tx int64 @@ -179,7 +188,7 @@ func ReadLinuxNetworkTotals() (int64, int64) { continue } fields := strings.Fields(data) - if len(fields) < 16 { + if len(fields) < netDevMinFieldCount { continue } rxValue, err := strconv.ParseInt(fields[0], 10, 64) @@ -200,14 +209,14 @@ func ReadLinuxDiskTotals() (int64, int64) { if err != nil { return 0, 0 } - defer file.Close() + defer func() { _ = file.Close() }() var readBytes int64 var writeBytes int64 scanner := bufio.NewScanner(file) for scanner.Scan() { fields := strings.Fields(scanner.Text()) - if len(fields) < 14 { + if len(fields) < diskStatsMinFieldCount { continue } device := fields[2] @@ -249,8 +258,8 @@ func StatFilesystem(path string) (int64, int64) { if err := syscall.Statfs(absPath, &stat); err != nil { return 0, 0 } - total := int64(stat.Blocks) * int64(stat.Bsize) - free := int64(stat.Bavail) * int64(stat.Bsize) + total := multiplyUint64ToInt64(stat.Blocks, uint64(stat.Bsize)) + free := multiplyUint64ToInt64(stat.Bavail, uint64(stat.Bsize)) used := total - free if used < 0 { used = 0 @@ -258,11 +267,21 @@ func StatFilesystem(path string) (int64, int64) { return total, used } +func multiplyUint64ToInt64(a uint64, b uint64) int64 { + if a == 0 || b == 0 { + return 0 + } + if a > math.MaxInt64/b { + return math.MaxInt64 + } + return int64(a * b) //nolint:gosec // product is bounded to math.MaxInt64 above +} + // ReadFirstLine reads and returns the trimmed first line of a file. func ReadFirstLine(path string) string { - content, err := os.ReadFile(path) + content, err := os.ReadFile(path) //nolint:gosec // path is a fixed /proc or /sys path from internal callers, not user input if err != nil { return "" } return strings.TrimSpace(string(content)) -} \ No newline at end of file +} diff --git a/internal/apps/edge/runner/wsreconnect.go b/internal/apps/edge/runner/wsreconnect.go index 579bcefe..1dfef0a3 100644 --- a/internal/apps/edge/runner/wsreconnect.go +++ b/internal/apps/edge/runner/wsreconnect.go @@ -1,3 +1,4 @@ +// Package runner provides shared WebSocket reconnect helpers for edge daemons. package runner import ( @@ -6,10 +7,12 @@ import ( "time" ) +// WSConnection defines the minimum interface required for a WebSocket connection that can be closed. type WSConnection interface { Close() error } +// WSReconnectConfig specifies configuration parameters for the WebSocket reconnect loop. type WSReconnectConfig struct { ComponentName string ConnectBackoff time.Duration @@ -17,6 +20,7 @@ type WSReconnectConfig struct { OnShutdown func() } +// RunWSReconnectLoop runs a loop that attempts to keep a WebSocket connection active, automatically reconnecting when closed or failed. func RunWSReconnectLoop(ctx context.Context, cfg WSReconnectConfig, connect func(context.Context) (WSConnection, error), handle func(context.Context, WSConnection), @@ -40,6 +44,7 @@ func RunWSReconnectLoop(ctx context.Context, cfg WSReconnectConfig, } return ctx.Err() default: + // Continue reconnect loop } conn, err := connect(ctx) @@ -56,9 +61,10 @@ func RunWSReconnectLoop(ctx context.Context, cfg WSReconnectConfig, } } +// SleepContext pauses execution for the given duration or until the context is canceled. func SleepContext(ctx context.Context, d time.Duration) { select { case <-ctx.Done(): case <-time.After(d): } -} \ No newline at end of file +} diff --git a/internal/apps/edge/updater/restart_unix.go b/internal/apps/edge/updater/restart_unix.go index 2f6dddd9..403aac11 100644 --- a/internal/apps/edge/updater/restart_unix.go +++ b/internal/apps/edge/updater/restart_unix.go @@ -1,5 +1,6 @@ //go:build !windows +// Package updater provides capabilities to check for, download, and apply updates. package updater import ( @@ -33,7 +34,7 @@ func replaceAndRestart(execPath string, tmpPath string) error { if err := removeBackupBinary(backupPath); err != nil { return err } - if err := syscall.Exec(execPath, os.Args, os.Environ()); err != nil { + if err := syscall.Exec(execPath, os.Args, os.Environ()); err != nil { //nolint:gosec // execPath is the validated edge updater binary path return fmt.Errorf("exec restart: %w", err) } return fmt.Errorf("unreachable after exec") @@ -48,4 +49,4 @@ func removeBackupBinary(path string) error { return err } return nil -} \ No newline at end of file +} diff --git a/internal/apps/edge/updater/service.go b/internal/apps/edge/updater/service.go index 0d0f5021..f9a01a00 100644 --- a/internal/apps/edge/updater/service.go +++ b/internal/apps/edge/updater/service.go @@ -1,3 +1,4 @@ +// Package updater provides capabilities to check for, download, and apply updates. package updater import ( @@ -17,16 +18,23 @@ import ( "github.com/Rain-kl/Wavelet/pkg/utils" ) -const maxChecksumAssetSize = 64 * 1024 +const ( + maxChecksumAssetSize = 64 * 1024 + goosWindows = "windows" + updateTmpFilePerm = 0o600 + updateBinaryFilePerm = 0o755 +) var replaceAndRestartFunc = replaceAndRestart +// Config defines the configuration for the update service. type Config struct { LocalVersion string AssetPrefix string LogLabel string } +// Service handles checking and applying application binary updates. type Service struct { httpClient *http.Client lastCheckKey string @@ -35,6 +43,7 @@ type Service struct { logLabel string } +// New creates a new updater Service with the provided configuration. func New(cfg Config) *Service { return &Service{ httpClient: &http.Client{Timeout: 30 * time.Second}, @@ -44,6 +53,7 @@ func New(cfg Config) *Service { } } +// UpdateOptions specifies parameters for checking and applying updates. type UpdateOptions struct { Channel string TagName string @@ -62,6 +72,7 @@ type githubAsset struct { BrowserDownloadURL string `json:"browser_download_url"` } +// CheckAndUpdate checks for a newer release on GitHub and performs an update if available. func (s *Service) CheckAndUpdate(ctx context.Context, repo string, options UpdateOptions) error { release, err := s.getRelease(ctx, repo, options) if err != nil { @@ -152,7 +163,7 @@ func (s *Service) getLatestPreviewRelease(ctx context.Context, repo string) (*gi if err != nil { return nil, err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("github api returned %s", resp.Status) @@ -188,12 +199,11 @@ func (s *Service) fetchReleaseFromURL(ctx context.Context, url string) (*githubR if err != nil { return nil, err } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { + defer func() { + if err := resp.Body.Close(); err != nil { slog.Error("failed to close response body", "error", err) } - }(resp.Body) + }() if resp.StatusCode == http.StatusNotFound { return nil, nil @@ -222,7 +232,7 @@ func (s *Service) downloadChecksum(ctx context.Context, url string, assetName st if err != nil { return "", err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("checksum download returned %s", resp.Status) @@ -309,37 +319,37 @@ func (s *Service) downloadAndRestart(ctx context.Context, url string, expectedCh if err != nil { return err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return fmt.Errorf("download returned %s", resp.Status) } tmpPath := targetPath + ".update" - if runtime.GOOS == "windows" && !strings.HasSuffix(strings.ToLower(tmpPath), ".exe") { + if runtime.GOOS == goosWindows && !strings.HasSuffix(strings.ToLower(tmpPath), ".exe") { tmpPath += ".exe" } - tmpFile, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600) + tmpFile, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, updateTmpFilePerm) //nolint:gosec // tmpPath is derived from the configured updater binary location if err != nil { return err } hasher := sha256.New() if _, err = io.Copy(io.MultiWriter(tmpFile, hasher), resp.Body); err != nil { - tmpFile.Close() - os.Remove(tmpPath) + _ = tmpFile.Close() + _ = os.Remove(tmpPath) return err } if err = tmpFile.Close(); err != nil { - os.Remove(tmpPath) + _ = os.Remove(tmpPath) return err } actualChecksum := hex.EncodeToString(hasher.Sum(nil)) if actualChecksum != expectedChecksum { - os.Remove(tmpPath) + _ = os.Remove(tmpPath) return fmt.Errorf("sha256 checksum mismatch: expected %s, got %s", expectedChecksum, actualChecksum) } - if err = os.Chmod(tmpPath, 0o755); err != nil && runtime.GOOS != "windows" { - os.Remove(tmpPath) + if err = os.Chmod(tmpPath, updateBinaryFilePerm); err != nil && runtime.GOOS != goosWindows { //nolint:gosec // downloaded edge binary must remain executable + _ = os.Remove(tmpPath) return fmt.Errorf("set executable permission: %w", err) } @@ -349,7 +359,7 @@ func (s *Service) downloadAndRestart(ctx context.Context, url string, expectedCh func (s *Service) assetNameForGOOSGOARCH(goos string, goarch string) string { name := fmt.Sprintf("%s-%s-%s", s.assetPrefix, goos, goarch) - if goos == "windows" { + if goos == goosWindows { return name + ".exe" } return name @@ -378,4 +388,4 @@ func buildReleaseCheckKey(options UpdateOptions, remoteVersion string) string { func compareVersions(local string, remote string) int { return utils.CompareVersions(local, remote) -} \ No newline at end of file +} diff --git a/internal/apps/edge/wsclient/client.go b/internal/apps/edge/wsclient/client.go index fd168e1c..fbb006eb 100644 --- a/internal/apps/edge/wsclient/client.go +++ b/internal/apps/edge/wsclient/client.go @@ -1,3 +1,4 @@ +// Package wsclient provides WebSocket client abstractions for edge node communication. package wsclient import ( @@ -8,14 +9,21 @@ import ( shared "github.com/Rain-kl/Wavelet/pkg/wsclient" ) +// WSMessage is an alias for the shared WebSocket message type. type WSMessage = shared.WSMessage + +// MessageHandler is an alias for the shared WebSocket message handler type. type MessageHandler = shared.MessageHandler +// Preset represents a predefined connection configuration for a specific edge role. type Preset int const ( + // PresetAgent is the configuration preset for agent connections. PresetAgent Preset = iota + // PresetRelay is the configuration preset for relay connections. PresetRelay + // PresetFlared is the configuration preset for flared (tunnel) connections. PresetFlared ) @@ -30,18 +38,22 @@ var presets = map[Preset]presetConfig{ PresetFlared: {HeaderKey: "X-Tunnel-Token", WSPath: "/api/v1/tunnel/ws"}, } +// PresetHeaderKey returns the HTTP header key used for authentication with the given preset. func PresetHeaderKey(preset Preset) string { return presets[preset].HeaderKey } +// PresetWSPath returns the WebSocket path used for the given preset. func PresetWSPath(preset Preset) string { return presets[preset].WSPath } +// Client is a WebSocket client configured for a specific edge preset. type Client struct { sharedClient *shared.Client } +// New creates a new Client for the given preset, base URL, token, and timeout. func New(preset Preset, baseURL, token string, timeout time.Duration) *Client { cfg := presets[preset] return &Client{ @@ -55,18 +67,22 @@ func New(preset Preset, baseURL, token string, timeout time.Duration) *Client { } } +// SetToken updates the authentication token used by the client. func (c *Client) SetToken(token string) { c.sharedClient.SetToken(token) } +// URL returns the fully resolved WebSocket URL for this client. func (c *Client) URL() string { return c.sharedClient.URL() } +// Connection represents an established WebSocket connection to an edge node. type Connection struct { sharedConn *shared.Connection } +// Connect establishes a WebSocket connection using the client configuration. func (c *Client) Connect(ctx context.Context) (*Connection, error) { conn, err := c.sharedClient.Connect(ctx) if err != nil { @@ -75,10 +91,12 @@ func (c *Client) Connect(ctx context.Context) (*Connection, error) { return &Connection{sharedConn: conn}, nil } +// AgentConnection is a Connection specialized for agent node communication. type AgentConnection struct { Connection } +// ConnectAgent establishes a WebSocket connection and returns it as an AgentConnection. func (c *Client) ConnectAgent(ctx context.Context) (*AgentConnection, error) { conn, err := c.Connect(ctx) if err != nil { @@ -87,6 +105,7 @@ func (c *Client) ConnectAgent(ctx context.Context) (*AgentConnection, error) { return &AgentConnection{Connection: *conn}, nil } +// URL returns the resolved WebSocket URL of this connection. func (conn *Connection) URL() string { if conn == nil || conn.sharedConn == nil { return "" @@ -94,18 +113,22 @@ func (conn *Connection) URL() string { return conn.sharedConn.URL } +// SendPing sends a ping message over the connection. func (conn *Connection) SendPing() error { return conn.sharedConn.SendMessage(pkgprotocol.WSMessageTypePing, nil) } +// SendPong sends a pong message over the connection. func (conn *Connection) SendPong() error { return conn.sharedConn.SendMessage(pkgprotocol.WSMessageTypePong, nil) } +// SendMessage sends a typed message with an optional payload over the connection. func (conn *Connection) SendMessage(msgType string, payload any) error { return conn.sharedConn.SendMessage(msgType, payload) } +// Receive reads the next message from the connection. func (conn *Connection) Receive() (pkgprotocol.WSMessage, error) { var message pkgprotocol.WSMessage if err := conn.sharedConn.Receive(&message); err != nil { @@ -114,10 +137,12 @@ func (conn *Connection) Receive() (pkgprotocol.WSMessage, error) { return message, nil } +// RunReceiveLoop blocks and dispatches incoming messages to the handler until the context is canceled. func (conn *Connection) RunReceiveLoop(ctx context.Context, handler MessageHandler) error { return conn.sharedConn.RunReceiveLoop(ctx, handler) } +// Close gracefully closes the WebSocket connection. func (conn *Connection) Close() error { if conn == nil || conn.sharedConn == nil { return nil @@ -125,6 +150,7 @@ func (conn *Connection) Close() error { return conn.sharedConn.Close() } +// SendStatus sends a node status payload over the agent connection. func (conn *AgentConnection) SendStatus(payload pkgprotocol.NodePayload) error { return conn.sharedConn.SendMessage(pkgprotocol.WSMessageTypeStatus, payload) -} \ No newline at end of file +} diff --git a/internal/apps/flared/config/config.go b/internal/apps/flared/config/config.go index 017bdb34..11038304 100644 --- a/internal/apps/flared/config/config.go +++ b/internal/apps/flared/config/config.go @@ -1,3 +1,4 @@ +// Package config loads and persists flared daemon configuration. package config import ( @@ -11,8 +12,17 @@ import ( edgeconfig "github.com/Rain-kl/Wavelet/internal/apps/edge/config" ) +const ( + defaultHeartbeatInterval = 10 * time.Second + defaultSyncInterval = 30 * time.Second + defaultRequestTimeout = 10 * time.Second + configFilePerm = 0o644 +) + +// MillisecondDuration is a JSON-friendly duration type shared with edge config. type MillisecondDuration = edgeconfig.MillisecondDuration +// Config holds flared daemon settings loaded from file and environment. type Config struct { ServerURL string `json:"server_url"` TunnelToken string `json:"tunnel_token"` @@ -25,8 +35,9 @@ type Config struct { configPath string } +// Load reads configuration from path, applying environment overrides and defaults. func Load(path string) (*Config, error) { - data, err := os.ReadFile(path) + data, err := os.ReadFile(path) //nolint:gosec // path is the flared config file location from startup configuration if err != nil && !os.IsNotExist(err) { return nil, err } @@ -89,13 +100,13 @@ func applyDefaults(cfg *Config, baseDir string) { cfg.StatePath = filepath.Join(cfg.DataDir, "flared-state.json") } if cfg.HeartbeatInterval <= 0 { - cfg.HeartbeatInterval = MillisecondDuration(10 * time.Second) + cfg.HeartbeatInterval = MillisecondDuration(defaultHeartbeatInterval) } if cfg.SyncInterval <= 0 { - cfg.SyncInterval = MillisecondDuration(30 * time.Second) + cfg.SyncInterval = MillisecondDuration(defaultSyncInterval) } if cfg.RequestTimeout <= 0 { - cfg.RequestTimeout = MillisecondDuration(10 * time.Second) + cfg.RequestTimeout = MillisecondDuration(defaultRequestTimeout) } } @@ -109,6 +120,7 @@ func validate(cfg *Config) error { return nil } +// InitialAuthToken returns the tunnel token used for initial authentication. func (cfg *Config) InitialAuthToken() string { if cfg == nil { return "" @@ -116,6 +128,7 @@ func (cfg *Config) InitialAuthToken() string { return strings.TrimSpace(cfg.TunnelToken) } +// Save writes the current configuration back to the loaded config path. func (cfg *Config) Save() error { if cfg == nil { return errors.New("config 不能为空") @@ -127,5 +140,5 @@ func (cfg *Config) Save() error { if err != nil { return err } - return os.WriteFile(cfg.configPath, data, 0o644) + return os.WriteFile(cfg.configPath, data, configFilePerm) } diff --git a/internal/apps/flared/config/version.go b/internal/apps/flared/config/version.go index f356490f..657a95de 100644 --- a/internal/apps/flared/config/version.go +++ b/internal/apps/flared/config/version.go @@ -1,3 +1,4 @@ package config +// Version is the flared daemon build version string. var Version = "dev" diff --git a/internal/apps/flared/flared/runner.go b/internal/apps/flared/flared/runner.go index 3c62abad..87743a54 100644 --- a/internal/apps/flared/flared/runner.go +++ b/internal/apps/flared/flared/runner.go @@ -1,3 +1,4 @@ +// Package flared implements the tunnel client daemon runtime loop. package flared import ( @@ -13,15 +14,19 @@ import ( "github.com/Rain-kl/Wavelet/internal/apps/flared/wsclient" ) +// Runner is the top-level orchestrator for the flared agent. It wires together +// heartbeat, sync, frpc management, and the WebSocket control-plane connection. type Runner struct { Config *config.Config HeartbeatService *heartbeat.Service FrpcManager *frpc.Manager SyncService *sync.Service WebSocketService *wsclient.Client - HttpClient *httpclient.Client + HTTPClient *httpclient.Client } +// Run starts all background services and enters the WebSocket reconnect loop. +// It blocks until ctx is cancelled or an unrecoverable error occurs. func (r *Runner) Run(ctx context.Context) error { go r.HeartbeatService.Run(ctx) go r.SyncService.Run(ctx) @@ -40,11 +45,11 @@ type flaredWSHandler struct { runner *Runner } -func (h *flaredWSHandler) OnConnect(ctx context.Context) error { +func (h *flaredWSHandler) OnConnect(_ context.Context) error { return nil } -func (h *flaredWSHandler) HandleMessage(ctx context.Context, msg wsclient.WSMessage) error { +func (h *flaredWSHandler) HandleMessage(_ context.Context, msg wsclient.WSMessage) error { switch msg.Type { case "active_config": slog.Info("received config update notification from server") @@ -66,4 +71,4 @@ func (r *Runner) handleConnection(ctx context.Context, conn edgerunner.WSConnect return } _ = wsConn.RunReceiveLoop(ctx, &flaredWSHandler{runner: r}) -} \ No newline at end of file +} diff --git a/internal/apps/flared/frpc/manager.go b/internal/apps/flared/frpc/manager.go index 99a428f0..00ecefb6 100644 --- a/internal/apps/flared/frpc/manager.go +++ b/internal/apps/flared/frpc/manager.go @@ -1,3 +1,4 @@ +// Package frpc manages frpc child processes for tunnel relay connections. package frpc import ( @@ -18,6 +19,13 @@ import ( service "github.com/Rain-kl/Wavelet/pkg/protocol" ) +const ( + dataDirPerm = 0o750 + frpcConfigFilePerm = 0o644 + orphanProcessKillDelay = 500 * time.Millisecond +) + +// Manager supervises frpc processes for each active relay node. type Manager struct { cfg *config.Config processes map[string]*Process @@ -27,6 +35,7 @@ type Manager struct { currentChecksum string } +// Process tracks a single frpc child process and its runtime state. type Process struct { RelayID string Cmd *exec.Cmd @@ -36,6 +45,7 @@ type Process struct { LastError string } +// NewManager creates a Manager using the given flared configuration. func NewManager(cfg *config.Config) *Manager { return &Manager{ cfg: cfg, @@ -43,8 +53,9 @@ func NewManager(cfg *config.Config) *Manager { } } -func (m *Manager) GetVersion() string { - cmd := exec.Command(m.cfg.FrpcPath, "-v") +// GetVersion returns the installed frpc binary version string. +func (m *Manager) GetVersion(ctx context.Context) string { + cmd := exec.CommandContext(ctx, m.cfg.FrpcPath, "-v") //nolint:gosec // FrpcPath is the configured trusted frpc binary location out, err := cmd.Output() if err != nil { return "unknown" @@ -52,6 +63,7 @@ func (m *Manager) GetVersion() string { return strings.TrimSpace(string(out)) } +// GetConnectedRelays reports the relay nodes with active or managed frpc processes. func (m *Manager) GetConnectedRelays() []service.FlaredConnectedRelay { m.mu.RLock() defer m.mu.RUnlock() @@ -66,18 +78,21 @@ func (m *Manager) GetConnectedRelays() []service.FlaredConnectedRelay { return result } +// GetCurrentConfigVersion returns the version of the applied tunnel configuration. func (m *Manager) GetCurrentConfigVersion() string { m.mu.RLock() defer m.mu.RUnlock() return m.currentVersion } +// GetCurrentConfigChecksum returns the checksum of the applied tunnel configuration. func (m *Manager) GetCurrentConfigChecksum() string { m.mu.RLock() defer m.mu.RUnlock() return m.currentChecksum } +// UpdateConfig reconciles running frpc processes with the latest tunnel configuration. func (m *Manager) UpdateConfig(ctx context.Context, newConfig *service.FlaredTunnelConfigResponse) error { m.mu.Lock() defer m.mu.Unlock() @@ -93,7 +108,7 @@ func (m *Manager) UpdateConfig(ctx context.Context, newConfig *service.FlaredTun slog.Debug("tunnel config version unchanged, ensuring processes are running", "version", newConfig.Version) } - if err := os.MkdirAll(m.cfg.DataDir, 0o755); err != nil { + if err := os.MkdirAll(m.cfg.DataDir, dataDirPerm); err != nil { return fmt.Errorf("create data dir failed: %w", err) } @@ -105,14 +120,14 @@ func (m *Manager) UpdateConfig(ctx context.Context, newConfig *service.FlaredTun configPath := filepath.Join(m.cfg.DataDir, fmt.Sprintf("frpc_%s.toml", relay.RelayNodeID)) needsRestart := false - existingData, err := os.ReadFile(configPath) + existingData, err := os.ReadFile(configPath) //nolint:gosec // configPath is under managed DataDir if err != nil || string(existingData) != tomlContent { // 配置文件不存在或内容有变化,需要写入并重启 needsRestart = true } if needsRestart { - if err := os.WriteFile(configPath, []byte(tomlContent), 0o644); err != nil { + if err := os.WriteFile(configPath, []byte(tomlContent), frpcConfigFilePerm); err != nil { slog.Error("failed to write frpc config", "relay_id", relay.RelayNodeID, "error", err) continue } @@ -150,10 +165,7 @@ func (m *Manager) restartProcess(ctx context.Context, relayID string, configPath _ = os.Remove(pidPath) } - if ctx == nil { - ctx = context.Background() - } - procCtx, cancel := context.WithCancel(ctx) + procCtx, cancel := context.WithCancel(context.WithoutCancel(ctx)) proc := &Process{ RelayID: relayID, Cancel: cancel, @@ -176,7 +188,7 @@ func (m *Manager) restartProcess(ctx context.Context, relayID string, configPath ensureNoOrphanProcess(pidPath) - cmd := exec.CommandContext(procCtx, m.cfg.FrpcPath, "-c", configPath) + cmd := exec.CommandContext(procCtx, m.cfg.FrpcPath, "-c", configPath) //nolint:gosec // FrpcPath and configPath are managed trusted locations m.mu.Lock() proc.Cmd = cmd @@ -186,7 +198,7 @@ func (m *Manager) restartProcess(ctx context.Context, relayID string, configPath startedAt := time.Now() err := cmd.Start() if err == nil { - _ = os.WriteFile(pidPath, []byte(fmt.Sprintf("%d", cmd.Process.Pid)), 0o644) + _ = os.WriteFile(pidPath, []byte(fmt.Sprintf("%d", cmd.Process.Pid)), frpcConfigFilePerm) err = cmd.Wait() } _ = os.Remove(pidPath) @@ -217,7 +229,7 @@ func (m *Manager) restartProcess(ctx context.Context, relayID string, configPath case <-procCtx.Done(): return case <-time.After(backoff): - backoff = backoff * 2 + backoff *= 2 if backoff > maxBackoff { backoff = maxBackoff } @@ -226,6 +238,7 @@ func (m *Manager) restartProcess(ctx context.Context, relayID string, configPath }() } +// Stop cancels and removes all managed frpc processes. func (m *Manager) Stop() { m.mu.Lock() defer m.mu.Unlock() @@ -245,28 +258,23 @@ func buildFrpcToml(relay service.FlaredRelayInfo, proxies []service.FlaredProxyE host, port := parseAddr(relay.Address) - buf.WriteString(fmt.Sprintf(`serverAddr = "%s" -serverPort = %s -`, host, port)) + fmt.Fprintf(&buf, "serverAddr = \"%s\"\nserverPort = %s\n", host, port) if relay.AuthToken != "" { - buf.WriteString(fmt.Sprintf(`auth.method = "token" -auth.token = "%s" -`, relay.AuthToken)) + fmt.Fprintf(&buf, "auth.method = \"token\"\nauth.token = \"%s\"\n", relay.AuthToken) } if relay.ProxyURL != "" { - buf.WriteString(fmt.Sprintf(`transport.proxyURL = "%s" -`, relay.ProxyURL)) + fmt.Fprintf(&buf, "transport.proxyURL = \"%s\"\n", relay.ProxyURL) } buf.WriteString("\n") for _, proxy := range proxies { - buf.WriteString(fmt.Sprintf("[[proxies]]\nname = \"%s\"\ntype = \"%s\"\nlocalIP = \"%s\"\nlocalPort = %d\n", - proxy.Name, proxy.Type, proxy.LocalAddr, proxy.LocalPort)) + fmt.Fprintf(&buf, "[[proxies]]\nname = \"%s\"\ntype = \"%s\"\nlocalIP = \"%s\"\nlocalPort = %d\n", + proxy.Name, proxy.Type, proxy.LocalAddr, proxy.LocalPort) if len(proxy.CustomDomains) > 0 { - buf.WriteString(fmt.Sprintf("customDomains = [\"%s\"]\n", strings.Join(proxy.CustomDomains, "\", \""))) + fmt.Fprintf(&buf, "customDomains = [\"%s\"]\n", strings.Join(proxy.CustomDomains, "\", \"")) } buf.WriteString("\n") } @@ -290,7 +298,7 @@ func parseAddr(addr string) (string, string) { return addr, "7000" } -// State persistence +// ManagerState persists the last applied tunnel configuration version and checksum. type ManagerState struct { Version string Checksum string @@ -305,9 +313,10 @@ func (m *Manager) saveState() error { if err != nil { return err } - return os.WriteFile(m.cfg.StatePath, data, 0o644) + return os.WriteFile(m.cfg.StatePath, data, frpcConfigFilePerm) } +// LoadState restores the last applied configuration version and checksum from disk. func (m *Manager) LoadState() error { data, err := os.ReadFile(m.cfg.StatePath) if err != nil { @@ -328,7 +337,7 @@ func (m *Manager) LoadState() error { } func ensureNoOrphanProcess(pidPath string) { - data, err := os.ReadFile(pidPath) + data, err := os.ReadFile(pidPath) //nolint:gosec // pidPath is under managed DataDir if err != nil { return } @@ -344,7 +353,7 @@ func ensureNoOrphanProcess(pidPath string) { slog.Warn("attempting to kill potentially orphan process", "pid", pid, "pid_path", pidPath) _ = process.Kill() // Wait a little bit to ensure the OS has reclaimed ports - time.Sleep(500 * time.Millisecond) + time.Sleep(orphanProcessKillDelay) } _ = os.Remove(pidPath) } diff --git a/internal/apps/flared/heartbeat/service.go b/internal/apps/flared/heartbeat/service.go index 214c8ad1..a9654720 100644 --- a/internal/apps/flared/heartbeat/service.go +++ b/internal/apps/flared/heartbeat/service.go @@ -1,3 +1,4 @@ +// Package heartbeat runs the periodic flared heartbeat loop against the control plane. package heartbeat import ( @@ -13,6 +14,7 @@ import ( service "github.com/Rain-kl/Wavelet/pkg/protocol" ) +// Service sends periodic heartbeat payloads and applies tunnel settings from responses. type Service struct { client *httpclient.Client frpcManager *frpc.Manager @@ -20,6 +22,7 @@ type Service struct { updater *updater.Service } +// New creates a heartbeat service with the given client, frpc manager, and config. func New(client *httpclient.Client, manager *frpc.Manager, cfg *config.Config) *Service { return &Service{ client: client, @@ -29,6 +32,7 @@ func New(client *httpclient.Client, manager *frpc.Manager, cfg *config.Config) * } } +// Run starts the heartbeat loop until ctx is canceled. func (s *Service) Run(ctx context.Context) { edgeheartbeat.RunLoop(ctx, s.config.HeartbeatInterval.Duration(), s.doHeartbeat) } @@ -38,8 +42,8 @@ func (s *Service) doHeartbeat(ctx context.Context) { payload := service.FlaredHeartbeatPayload{ ClientVersion: config.Version, - FrpVersion: s.frpcManager.GetVersion(), - IP: nodeip.Detect(), + FrpVersion: s.frpcManager.GetVersion(ctx), + IP: nodeip.DetectWithContext(ctx), TunnelStatus: "running", ConnectedRelays: s.frpcManager.GetConnectedRelays(), CurrentVersion: s.frpcManager.GetCurrentConfigVersion(), @@ -69,4 +73,4 @@ func tunnelSettingsToAutoUpdate(settings *service.RelaySettings) *edgeheartbeat. UpdateChannel: settings.UpdateChannel, UpdateTag: settings.UpdateTag, } -} \ No newline at end of file +} diff --git a/internal/apps/flared/httpclient/client.go b/internal/apps/flared/httpclient/client.go index 17160296..9dd284a2 100644 --- a/internal/apps/flared/httpclient/client.go +++ b/internal/apps/flared/httpclient/client.go @@ -1,3 +1,4 @@ +// Package httpclient provides the HTTP client used by the flared agent to communicate with the Wavelet server. package httpclient import ( @@ -8,21 +9,25 @@ import ( service "github.com/Rain-kl/Wavelet/pkg/protocol" ) +// APIResponse is the standard JSON envelope returned by the Wavelet API. type APIResponse[T any] struct { ErrorMsg string `json:"error_msg"` Data T `json:"data"` } +// Client is the HTTP client for the flared tunnel API. type Client struct { base *edgehttp.Client } +// New creates a new Client configured with the given base URL, authentication token, and request timeout. func New(baseURL string, token string, timeout time.Duration) *Client { return &Client{ base: edgehttp.New(baseURL, token, timeout, "X-Tunnel-Token"), } } +// Heartbeat sends a tunnel heartbeat payload and returns the server response. func (c *Client) Heartbeat(ctx context.Context, payload service.FlaredHeartbeatPayload) (*service.FlaredHeartbeatResponse, error) { resp := APIResponse[service.FlaredHeartbeatResponse]{} if err := c.base.PostJSON(ctx, "/api/v1/tunnel/heartbeat", payload, &resp); err != nil { @@ -34,6 +39,7 @@ func (c *Client) Heartbeat(ctx context.Context, payload service.FlaredHeartbeatP return &resp.Data, nil } +// GetActiveConfig fetches the currently active tunnel configuration from the server. func (c *Client) GetActiveConfig(ctx context.Context) (*service.FlaredTunnelConfigResponse, error) { resp := APIResponse[service.FlaredTunnelConfigResponse]{} if err := c.base.GetJSON(ctx, "/api/v1/tunnel/config/active", &resp); err != nil { @@ -45,6 +51,7 @@ func (c *Client) GetActiveConfig(ctx context.Context) (*service.FlaredTunnelConf return &resp.Data, nil } +// ReportApplyLog submits a configuration apply-log entry to the server. func (c *Client) ReportApplyLog(ctx context.Context, payload service.ApplyLogPayload) error { resp := APIResponse[any]{} if err := c.base.PostJSON(ctx, "/api/v1/tunnel/apply-log", payload, &resp); err != nil { @@ -53,6 +60,7 @@ func (c *Client) ReportApplyLog(ctx context.Context, payload service.ApplyLogPay return edgehttp.APIError(resp.ErrorMsg) } +// SetToken updates the authentication token used by the client. func (c *Client) SetToken(token string) { c.base.SetToken(token) -} \ No newline at end of file +} diff --git a/internal/apps/flared/sync/service.go b/internal/apps/flared/sync/service.go index e3e42125..814624ee 100644 --- a/internal/apps/flared/sync/service.go +++ b/internal/apps/flared/sync/service.go @@ -1,3 +1,4 @@ +// Package sync periodically fetches and applies the active tunnel configuration. package sync import ( @@ -11,6 +12,7 @@ import ( service "github.com/Rain-kl/Wavelet/pkg/protocol" ) +// Service synchronizes tunnel configuration from the control plane to the local frpc manager. type Service struct { client *httpclient.Client frpcManager *frpc.Manager @@ -18,6 +20,7 @@ type Service struct { triggerCh chan struct{} } +// New creates a sync service with the given client, frpc manager, and config. func New(client *httpclient.Client, manager *frpc.Manager, cfg *config.Config) *Service { return &Service{ client: client, @@ -27,6 +30,7 @@ func New(client *httpclient.Client, manager *frpc.Manager, cfg *config.Config) * } } +// Trigger requests an immediate configuration sync without waiting for the next interval. func (s *Service) Trigger() { select { case s.triggerCh <- struct{}{}: @@ -34,6 +38,7 @@ func (s *Service) Trigger() { } } +// Run starts the sync loop until ctx is canceled. func (s *Service) Run(ctx context.Context) { ticker := time.NewTicker(s.config.SyncInterval.Duration()) defer ticker.Stop() diff --git a/internal/apps/flared/updater/updater.go b/internal/apps/flared/updater/updater.go index 9579cfd9..7fbeb2fd 100644 --- a/internal/apps/flared/updater/updater.go +++ b/internal/apps/flared/updater/updater.go @@ -1,3 +1,4 @@ +// Package updater provides update service capabilities for flared. package updater import ( @@ -5,13 +6,17 @@ import ( "github.com/Rain-kl/Wavelet/internal/apps/flared/config" ) +// Service is an alias for the edge updater Service. type Service = edgeupdater.Service + +// UpdateOptions is an alias for the edge updater UpdateOptions. type UpdateOptions = edgeupdater.UpdateOptions +// New creates a new updater Service instance. func New() *Service { return edgeupdater.New(edgeupdater.Config{ LocalVersion: config.Version, AssetPrefix: "openflared", LogLabel: "flared", }) -} \ No newline at end of file +} diff --git a/internal/apps/flared/wsclient/client.go b/internal/apps/flared/wsclient/client.go index b4b9ddc8..389a0adb 100644 --- a/internal/apps/flared/wsclient/client.go +++ b/internal/apps/flared/wsclient/client.go @@ -1,3 +1,4 @@ +// Package wsclient provides a WebSocket client for flared control-plane communication. package wsclient import ( @@ -7,24 +8,33 @@ import ( edgews "github.com/Rain-kl/Wavelet/internal/apps/edge/wsclient" ) +// WSMessage is a WebSocket message exchanged with the control plane. type WSMessage = edgews.WSMessage + +// MessageHandler processes incoming WebSocket messages. type MessageHandler = edgews.MessageHandler + +// Connection represents an active WebSocket connection. type Connection = edgews.Connection +// Client connects to the flared WebSocket endpoint on the control plane. type Client struct { inner *edgews.Client } +// New creates a WebSocket client for the flared control-plane endpoint. func New(baseURL, token string, timeout time.Duration) *Client { return &Client{ inner: edgews.New(edgews.PresetFlared, baseURL, token, timeout), } } +// SetToken updates the authentication token used for the WebSocket connection. func (c *Client) SetToken(token string) { c.inner.SetToken(token) } +// Connect establishes a WebSocket connection to the control plane. func (c *Client) Connect(ctx context.Context) (*Connection, error) { return c.inner.Connect(ctx) -} \ No newline at end of file +} diff --git a/internal/apps/openflare/agent/access_log_region.go b/internal/apps/openflare/agent/access_log_region.go index 2e9dd76b..f4377da1 100644 --- a/internal/apps/openflare/agent/access_log_region.go +++ b/internal/apps/openflare/agent/access_log_region.go @@ -1,6 +1,8 @@ // Copyright 2026 Arctel.net // SPDX-License-Identifier: Apache-2.0 +// Package agent implements the OpenFlare agent protocol: node registration, +// heartbeat processing, access-log ingestion, and related middleware. package agent import ( @@ -11,12 +13,12 @@ import ( pkggeoip "github.com/Rain-kl/Wavelet/pkg/geoip" ) -var accessLogGeoProviderFactory = func() (pkggeoip.GeoIPService, error) { +var accessLogGeoProviderFactory = func() (pkggeoip.Service, error) { return pkggeoip.NewMaxMindGeoIPService() } type accessLogRegionResolver struct { - provider pkggeoip.GeoIPService + provider pkggeoip.Service cache map[string]string } diff --git a/internal/apps/openflare/agent/auth_cache.go b/internal/apps/openflare/agent/auth_cache.go index 758a060a..15f6b681 100644 --- a/internal/apps/openflare/agent/auth_cache.go +++ b/internal/apps/openflare/agent/auth_cache.go @@ -36,12 +36,10 @@ var tokenCache = newAccessTokenAuthCache() func newAccessTokenAuthCache() *accessTokenAuthCache { return &accessTokenAuthCache{ - positive: make(map[string]cachedAgentNode), - negative: make(map[string]time.Time), - now: time.Now, - loadNodeByToken: func(ctx context.Context, token string) (*model.OpenFlareNode, error) { - return model.GetOpenFlareNodeByAccessToken(ctx, token) - }, + positive: make(map[string]cachedAgentNode), + negative: make(map[string]time.Time), + now: time.Now, + loadNodeByToken: model.GetOpenFlareNodeByAccessToken, } } diff --git a/internal/apps/openflare/agent/errs.go b/internal/apps/openflare/agent/errs.go index ed553aab..21bc37f7 100644 --- a/internal/apps/openflare/agent/errs.go +++ b/internal/apps/openflare/agent/errs.go @@ -4,9 +4,9 @@ package agent const ( - errMissingAgentToken = "缺少 Agent Token" - errInvalidAgentToken = "无权进行此操作,Agent Token 无效" - errInvalidDiscoveryToken = "无权进行此操作,注册 Token 无效" + errMissingAgentToken = "缺少 Agent Token" //nolint:gosec // false positive: this is an error message, not hardcoded credentials + errInvalidAgentToken = "无权进行此操作,Agent Token 无效" //nolint:gosec // false positive: this is an error message, not hardcoded credentials + errInvalidDiscoveryToken = "无权进行此操作,注册 Token 无效" //nolint:gosec // false positive: this is an error message, not hardcoded credentials errNodeMissingFromContext = "Node object missing from context" errNoActiveConfig = "当前没有激活版本" errNodeNotFound = "节点不存在" diff --git a/internal/apps/openflare/agent/helpers.go b/internal/apps/openflare/agent/helpers.go index 85b15875..ac83ae2b 100644 --- a/internal/apps/openflare/agent/helpers.go +++ b/internal/apps/openflare/agent/helpers.go @@ -20,10 +20,12 @@ const ( openrestyStatusUnhealthy = "unhealthy" openrestyStatusUnknown = "unknown" releaseChannelStable = "stable" + randomTokenBytes = 16 + maxDatabaseTextLength = 16000 ) func newRandomToken() (string, error) { - buf := make([]byte, 16) + buf := make([]byte, randomTokenBytes) if _, err := rand.Read(buf); err != nil { return "", err } @@ -55,9 +57,9 @@ func normalizeNodePayload(payload NodePayload) NodePayload { payload.Version = strings.TrimSpace(payload.Version) payload.ExtVersion = strings.TrimSpace(payload.ExtVersion) payload.CurrentVersion = strings.TrimSpace(payload.CurrentVersion) - payload.LastError = truncateForDatabase(payload.LastError, 16000) + payload.LastError = truncateForDatabase(payload.LastError, maxDatabaseTextLength) payload.OpenrestyStatus = normalizeOpenrestyStatus(payload.OpenrestyStatus) - payload.OpenrestyMessage = truncateForDatabase(payload.OpenrestyMessage, 16000) + payload.OpenrestyMessage = truncateForDatabase(payload.OpenrestyMessage, maxDatabaseTextLength) return payload } @@ -92,12 +94,12 @@ func applyNodeRuntime(node *model.OpenFlareNode, payload NodePayload, preserveNa node.Version = strings.TrimSpace(payload.Version) node.ExtVersion = strings.TrimSpace(payload.ExtVersion) node.OpenrestyStatus = normalizeOpenrestyStatus(payload.OpenrestyStatus) - node.OpenrestyMessage = truncateForDatabase(payload.OpenrestyMessage, 16000) + node.OpenrestyMessage = truncateForDatabase(payload.OpenrestyMessage, maxDatabaseTextLength) node.Status = nodeStatusOnline node.CurrentVersion = strings.TrimSpace(payload.CurrentVersion) now := time.Now() node.LastSeenAt = &now - node.LastError = truncateForDatabase(payload.LastError, 16000) + node.LastError = truncateForDatabase(payload.LastError, maxDatabaseTextLength) if !node.GeoManualOverride { applyGeoInfoFromIP(node, node.IP) } @@ -135,15 +137,15 @@ func cloneCoordinate(value *float64) *float64 { return &cloned } -func truncateForDatabase(value string, max int) string { - if max <= 0 { +func truncateForDatabase(value string, maxVal int) string { + if maxVal <= 0 { return "" } runes := []rune(strings.TrimSpace(value)) - if len(runes) <= max { + if len(runes) <= maxVal { return string(runes) } - return string(runes[:max]) + return string(runes[:maxVal]) } func resolveReportedNodeIP(reportedIP string, remoteAddr string) string { @@ -277,7 +279,7 @@ func normalizeApplyLogPayload(payload ApplyLogPayload) ApplyLogPayload { payload.NodeID = strings.TrimSpace(payload.NodeID) payload.Version = strings.TrimSpace(payload.Version) payload.Result = strings.ToLower(strings.TrimSpace(payload.Result)) - payload.Message = truncateForDatabase(strings.TrimSpace(payload.Message), 16000) + payload.Message = truncateForDatabase(strings.TrimSpace(payload.Message), maxDatabaseTextLength) payload.Checksum = strings.TrimSpace(payload.Checksum) payload.MainConfigChecksum = strings.TrimSpace(payload.MainConfigChecksum) payload.RouteConfigChecksum = strings.TrimSpace(payload.RouteConfigChecksum) @@ -292,7 +294,7 @@ func isUniqueConstraintError(err error) bool { } // RefreshAccessTokenCache updates the in-memory node cache after heartbeat mutations. -func RefreshAccessTokenCache(ctx context.Context, node *model.OpenFlareNode) { +func RefreshAccessTokenCache(_ context.Context, node *model.OpenFlareNode) { if node == nil { return } diff --git a/internal/apps/openflare/agent/middleware.go b/internal/apps/openflare/agent/middleware.go index 213f00b9..a8f12e45 100644 --- a/internal/apps/openflare/agent/middleware.go +++ b/internal/apps/openflare/agent/middleware.go @@ -12,12 +12,12 @@ import ( ) const ( - agentTokenHeader = "X-Agent-Token" + agentTokenHeader = "X-Agent-Token" //nolint:gosec // HTTP header name, not a credential value agentNodeContextKey = "agent_node" ) -// AgentAuth validates X-Agent-Token against of_nodes.access_token. -func AgentAuth() gin.HandlerFunc { +// Auth validates X-Agent-Token against of_nodes.access_token. +func Auth() gin.HandlerFunc { return func(c *gin.Context) { token := strings.TrimSpace(c.GetHeader(agentTokenHeader)) node, err := AuthenticateAccessToken(c.Request.Context(), token) @@ -30,8 +30,8 @@ func AgentAuth() gin.HandlerFunc { } } -// AgentRegisterAuth accepts either a node access token or the global discovery token. -func AgentRegisterAuth() gin.HandlerFunc { +// RegisterAuth accepts either a node access token or the global discovery token. +func RegisterAuth() gin.HandlerFunc { return func(c *gin.Context) { token := strings.TrimSpace(c.GetHeader(agentTokenHeader)) if node, err := AuthenticateAccessToken(c.Request.Context(), token); err == nil { @@ -48,12 +48,12 @@ func AgentRegisterAuth() gin.HandlerFunc { } } -// AgentNodeFromContext returns the authenticated agent node. -func AgentNodeFromContext(c *gin.Context) (*model.OpenFlareNode, bool) { +// NodeFromContext returns the authenticated agent node. +func NodeFromContext(c *gin.Context) (*model.OpenFlareNode, bool) { value, ok := c.Get(agentNodeContextKey) if !ok { return nil, false } node, ok := value.(*model.OpenFlareNode) return node, ok -} \ No newline at end of file +} diff --git a/internal/apps/openflare/agent/middleware_test.go b/internal/apps/openflare/agent/middleware_test.go index 6a9309b7..5c1a575b 100644 --- a/internal/apps/openflare/agent/middleware_test.go +++ b/internal/apps/openflare/agent/middleware_test.go @@ -109,8 +109,8 @@ func TestAgentAuthMiddleware(t *testing.T) { }).Error) router := testhelper.NewTestGinEngine() - router.GET("/protected", AgentAuth(), func(c *gin.Context) { - node, ok := AgentNodeFromContext(c) + router.GET("/protected", Auth(), func(c *gin.Context) { + node, ok := NodeFromContext(c) if !ok { c.Status(http.StatusInternalServerError) return @@ -157,8 +157,8 @@ func TestAgentRegisterAuthMiddleware(t *testing.T) { require.NoError(t, model.UpdateOpenFlareOption(ctx, "AgentDiscoveryToken", "discovery-token")) router := testhelper.NewTestGinEngine() - router.POST("/register", AgentRegisterAuth(), func(c *gin.Context) { - if node, ok := AgentNodeFromContext(c); ok { + router.POST("/register", RegisterAuth(), func(c *gin.Context) { + if node, ok := NodeFromContext(c); ok { c.JSON(http.StatusOK, response.OK(gin.H{"mode": "node", "node_id": node.NodeID})) return } @@ -196,4 +196,4 @@ func TestAgentRegisterAuthMiddleware(t *testing.T) { require.True(t, ok) assert.Equal(t, "discovery", data["mode"]) }) -} \ No newline at end of file +} diff --git a/internal/apps/openflare/agent/observability.go b/internal/apps/openflare/agent/observability.go index 6e7f21f7..8bd4c9f5 100644 --- a/internal/apps/openflare/agent/observability.go +++ b/internal/apps/openflare/agent/observability.go @@ -27,6 +27,7 @@ const ( nodeAccessLogRetentionDays = 90 nodeAccessLogRetentionWindow = nodeAccessLogRetentionDays * 24 * time.Hour accessLogPathMaxLength = 100 + healthEventMessageMaxLength = 4096 ) // PersistHeartbeatObservability stores profile, snapshots, traffic, access logs, and health events. @@ -396,7 +397,7 @@ func normalizeHealthSeverity(severity string) string { } func normalizeHealthEventMessage(message string) string { - return truncateForDatabase(message, 4096) + return truncateForDatabase(message, healthEventMessageMaxLength) } func timeFromUnix(unixSeconds int64, fallback time.Time) time.Time { diff --git a/internal/apps/openflare/agent/protocol_alias.go b/internal/apps/openflare/agent/protocol_alias.go index 0a2df55a..d0e2ede5 100644 --- a/internal/apps/openflare/agent/protocol_alias.go +++ b/internal/apps/openflare/agent/protocol_alias.go @@ -5,22 +5,55 @@ package agent import pkgprotocol "github.com/Rain-kl/Wavelet/pkg/protocol" +// NodePayload is the data sent by an agent on registration or heartbeat. type NodePayload = pkgprotocol.NodePayload + +// NodeSystemProfile carries static host information reported by an agent. type NodeSystemProfile = pkgprotocol.NodeSystemProfile + +// NodeMetricSnapshot holds a point-in-time resource-usage sample from an agent. type NodeMetricSnapshot = pkgprotocol.NodeMetricSnapshot + +// NodeOpenrestyObservation reports the OpenResty process health observed by an agent. type NodeOpenrestyObservation = pkgprotocol.NodeOpenrestyObservation + +// NodeTrafficReport aggregates traffic counters collected by an agent. type NodeTrafficReport = pkgprotocol.NodeTrafficReport + +// NodeAccessLog is a single access-log record forwarded by an agent. type NodeAccessLog = pkgprotocol.NodeAccessLog + +// BufferedObservabilityRecord bundles multiple observability payloads into one upload. type BufferedObservabilityRecord = pkgprotocol.BufferedObservabilityRecord + +// NodeHealthEvent represents a discrete health-state change on an agent node. type NodeHealthEvent = pkgprotocol.NodeHealthEvent + +// ApplyLogPayload carries the result of a configuration-apply attempt reported by an agent. type ApplyLogPayload = pkgprotocol.ApplyLogPayload + +// Settings contains remote-control directives sent from the server to an agent. type Settings = pkgprotocol.AgentSettings + +// ActiveConfigMeta describes the currently active configuration version on the server. type ActiveConfigMeta = pkgprotocol.ActiveConfigMeta + +// SupportFile represents a supplementary file bundled with an agent configuration package. type SupportFile = pkgprotocol.SupportFile + +// WAFIPGroup is a named IP-address group used in WAF allow/block rules. type WAFIPGroup = pkgprotocol.WAFIPGroup + +// WAFIPGroupSyncRequest is sent by an agent to request an incremental WAF IP-group sync. type WAFIPGroupSyncRequest = pkgprotocol.WAFIPGroupSyncRequest + +// WAFIPGroupSyncResponse carries the server's reply to a WAF IP-group sync request. type WAFIPGroupSyncResponse = pkgprotocol.WAFIPGroupSyncResponse // Backward-compatible names used by server routers and handlers. + +// WAFIPGroupSyncInput is an alias for WAFIPGroupSyncRequest kept for backward compatibility. type WAFIPGroupSyncInput = WAFIPGroupSyncRequest -type WAFIPGroupSyncResult = WAFIPGroupSyncResponse \ No newline at end of file + +// WAFIPGroupSyncResult is an alias for WAFIPGroupSyncResponse kept for backward compatibility. +type WAFIPGroupSyncResult = WAFIPGroupSyncResponse diff --git a/internal/apps/openflare/agent/routers.go b/internal/apps/openflare/agent/routers.go index 50b921ab..bd3d8a10 100644 --- a/internal/apps/openflare/agent/routers.go +++ b/internal/apps/openflare/agent/routers.go @@ -37,7 +37,7 @@ func RegisterHandler(c *gin.Context) { result *RegistrationResponse err error ) - if authNode, ok := AgentNodeFromContext(c); ok { + if authNode, ok := NodeFromContext(c); ok { result, err = RegisterWithAccessToken(c.Request.Context(), authNode, payload) } else { result, err = RegisterWithDiscovery(c.Request.Context(), payload) @@ -67,7 +67,7 @@ func HeartbeatHandler(c *gin.Context) { } payload.IP = resolveReportedNodeIP(payload.IP, c.Request.RemoteAddr) - authNode, ok := AgentNodeFromContext(c) + authNode, ok := NodeFromContext(c) if !ok { response.AbortUnauthorized(c, errInvalidAgentToken) return @@ -91,7 +91,7 @@ func HeartbeatHandler(c *gin.Context) { // @Failure 401 {object} response.Any "Token 无效" // @Router /api/v1/agent/config-versions/active [get] func GetActiveConfigHandler(c *gin.Context) { - if _, ok := AgentNodeFromContext(c); !ok { + if _, ok := NodeFromContext(c); !ok { response.AbortUnauthorized(c, errNodeMissingFromContext) return } @@ -143,7 +143,7 @@ func ReportApplyLogHandler(c *gin.Context) { if !apiutil.BindJSON(c, &payload) { return } - if authNode, ok := AgentNodeFromContext(c); ok { + if authNode, ok := NodeFromContext(c); ok { payload.NodeID = authNode.NodeID } log, err := ReportApplyLog(c.Request.Context(), payload) @@ -173,7 +173,7 @@ func DownloadPagesPackageHandler(c *gin.Context) { if apiutil.AbortBadRequestOnError(c, err) { return } - defer packageObj.Body.Close() + defer func() { _ = packageObj.Body.Close() }() c.Header("Content-Disposition", "attachment; filename="+fileName) if packageObj.ContentType != "" { c.Header("Content-Type", packageObj.ContentType) @@ -195,18 +195,18 @@ func pagesDeploymentIDParam(c *gin.Context) (uint, bool) { return uint(id64), true } -// AgentWebSocketHandler upgrades an authenticated agent websocket connection. +// WebSocketHandler upgrades an authenticated agent websocket connection. // @Summary Agent WebSocket 连接 // @Description 升级为 WebSocket 长连接,用于实时推送配置同步、WAF IP 组等指令;需携带 X-Agent-Token // @Tags openflare-agent // @Security AgentTokenAuth // @Failure 401 {object} response.Any "Token 无效" // @Router /api/v1/agent/ws [get] -func AgentWebSocketHandler(c *gin.Context) { - authNode, ok := AgentNodeFromContext(c) +func WebSocketHandler(c *gin.Context) { + authNode, ok := NodeFromContext(c) if !ok { response.AbortUnauthorized(c, errInvalidAgentToken) return } websocket.ServeAgent(c, authNode.NodeID, HandleWSStatus) -} \ No newline at end of file +} diff --git a/internal/apps/openflare/apply_log/errs.go b/internal/apps/openflare/apply_log/errs.go index 914b3635..54eede47 100644 --- a/internal/apps/openflare/apply_log/errs.go +++ b/internal/apps/openflare/apply_log/errs.go @@ -1,6 +1,8 @@ // Copyright 2026 Arctel.net // SPDX-License-Identifier: Apache-2.0 +// Package apply_log manages the application of configuration change logs, +// including validation and retention policy enforcement. package apply_log const ( diff --git a/internal/apps/openflare/async_tasks.go b/internal/apps/openflare/async_tasks.go index 3c52a4a6..05f7ab83 100644 --- a/internal/apps/openflare/async_tasks.go +++ b/internal/apps/openflare/async_tasks.go @@ -1,6 +1,4 @@ -// Copyright 2026 Arctel.net -// SPDX-License-Identifier: Apache-2.0 - +// Package openflare implements openflare configuration, service orchestration, and background tasks. package openflare import ( @@ -118,7 +116,7 @@ func (h *DatabaseAutoCleanupHandler) Execute(ctx context.Context, _ []byte) (*ta } task.AppendLog(ctx, "开始执行可观测数据自动清理,保留天数=%d", model.DatabaseAutoCleanupRetentionDays) - summary, err := tasks.RunDatabaseAutoCleanupOnce(time.Now()) + summary, err := tasks.RunDatabaseAutoCleanupOnce(ctx, time.Now()) if err != nil { task.AppendLog(ctx, "可观测数据自动清理失败: %v", err) return nil, err @@ -197,4 +195,4 @@ func (h *UptimeKumaSyncHandler) Execute(ctx context.Context, _ []byte) (*task.Ta msg := "Uptime Kuma 同步完成" task.AppendLog(ctx, "%s", msg) return &task.TaskResult{Message: msg}, nil -} \ No newline at end of file +} diff --git a/internal/apps/openflare/config_version/errs.go b/internal/apps/openflare/config_version/errs.go index 2ccd9bb6..44abb1f6 100644 --- a/internal/apps/openflare/config_version/errs.go +++ b/internal/apps/openflare/config_version/errs.go @@ -1,6 +1,7 @@ // Copyright 2026 Arctel.net // SPDX-License-Identifier: Apache-2.0 +// Package config_version defines shared error messages for configuration versions. package config_version const ( diff --git a/internal/apps/openflare/config_version/logics.go b/internal/apps/openflare/config_version/logics.go index 0102e664..8db03655 100644 --- a/internal/apps/openflare/config_version/logics.go +++ b/internal/apps/openflare/config_version/logics.go @@ -8,6 +8,7 @@ import ( "encoding/json" "errors" "fmt" + "slices" "sort" "strconv" "strings" @@ -17,6 +18,11 @@ import ( "gorm.io/gorm" ) +const ( + cleanupSuccessMessage = "清理成功" + minConfigVersionKeepCount = 3 +) + // ConfigPreviewResult is the preview response payload. type ConfigPreviewResult struct { SnapshotJSON string `json:"snapshot_json"` @@ -243,15 +249,15 @@ func ActivateConfigVersion(ctx context.Context, id uint) (*model.ConfigVersion, // CleanupConfigVersions removes old inactive config versions. func CleanupConfigVersions(ctx context.Context, keepCount int) (*CleanupResult, error) { - if keepCount < 3 { - keepCount = 3 + if keepCount < minConfigVersionKeepCount { + keepCount = minConfigVersionKeepCount } versions, err := model.ListConfigVersionSummaries(ctx) if err != nil { return nil, err } if len(versions) <= keepCount { - return &CleanupResult{DeletedCount: 0, Message: "清理成功"}, nil + return &CleanupResult{DeletedCount: 0, Message: cleanupSuccessMessage}, nil } var deleteIDs []uint for index, version := range versions { @@ -264,13 +270,13 @@ func CleanupConfigVersions(ctx context.Context, keepCount int) (*CleanupResult, deleteIDs = append(deleteIDs, version.ID) } if len(deleteIDs) == 0 { - return &CleanupResult{DeletedCount: 0, Message: "清理成功"}, nil + return &CleanupResult{DeletedCount: 0, Message: cleanupSuccessMessage}, nil } deletedCount, err := model.DeleteConfigVersionsByIDs(ctx, deleteIDs) if err != nil { return nil, err } - return &CleanupResult{DeletedCount: deletedCount, Message: "清理成功"}, nil + return &CleanupResult{DeletedCount: deletedCount, Message: cleanupSuccessMessage}, nil } func nextVersionNumber(ctx context.Context, now time.Time) (string, error) { @@ -368,50 +374,50 @@ func flattenSnapshotRoutesByDomain(routes []snapshotRoute) map[string]snapshotRo } func snapshotRouteConfigEqual(left snapshotRoute, right snapshotRoute) bool { - if left.SiteName != right.SiteName || left.Domain != right.Domain || left.OriginURL != right.OriginURL || - left.OriginHost != right.OriginHost || left.EnableHTTPS != right.EnableHTTPS || left.RedirectHTTP != right.RedirectHTTP || - left.LimitConnPerServer != right.LimitConnPerServer || left.LimitConnPerIP != right.LimitConnPerIP || - left.LimitRate != right.LimitRate || left.CacheEnabled != right.CacheEnabled || left.CachePolicy != right.CachePolicy || - left.BasicAuthEnabled != right.BasicAuthEnabled || left.BasicAuthUsername != right.BasicAuthUsername || - left.BasicAuthPassword != right.BasicAuthPassword || left.UpstreamType != right.UpstreamType || - !uintPtrEqual(left.TunnelNodeID, right.TunnelNodeID) || left.TunnelTargetAddr != right.TunnelTargetAddr || - left.TunnelTargetProto != right.TunnelTargetProto || !uintPtrEqual(left.PagesProjectID, right.PagesProjectID) || - !uintSliceEqual(left.CertIDs, right.CertIDs) || !uintSliceEqual(left.DomainCertIDs, right.DomainCertIDs) { - return false - } - if len(left.Domains) != len(right.Domains) { - return false - } - for index := range left.Domains { - if left.Domains[index] != right.Domains[index] { - return false - } - } - if len(left.Upstreams) != len(right.Upstreams) { - return false - } - for index := range left.Upstreams { - if left.Upstreams[index] != right.Upstreams[index] { - return false - } - } - if len(left.CacheRules) != len(right.CacheRules) { - return false - } - for index := range left.CacheRules { - if left.CacheRules[index] != right.CacheRules[index] { - return false - } - } - if len(left.CustomHeaders) != len(right.CustomHeaders) { - return false - } - for index := range left.CustomHeaders { - if left.CustomHeaders[index] != right.CustomHeaders[index] { - return false - } - } - return true + return snapshotRouteScalarsEqual(left, right) && + slices.Equal(left.Domains, right.Domains) && + slices.Equal(left.Upstreams, right.Upstreams) && + slices.Equal(left.CacheRules, right.CacheRules) && + slices.Equal(left.CustomHeaders, right.CustomHeaders) +} + +func snapshotRouteScalarsEqual(left, right snapshotRoute) bool { + return snapshotRouteIdentityEqual(left, right) && + snapshotRouteOriginEqual(left, right) && + snapshotRoutePolicyEqual(left, right) && + snapshotRouteTunnelEqual(left, right) && + uintSliceEqual(left.CertIDs, right.CertIDs) && + uintSliceEqual(left.DomainCertIDs, right.DomainCertIDs) +} + +func snapshotRouteIdentityEqual(left, right snapshotRoute) bool { + return left.SiteName == right.SiteName && left.Domain == right.Domain +} + +func snapshotRouteOriginEqual(left, right snapshotRoute) bool { + return left.OriginURL == right.OriginURL && + left.OriginHost == right.OriginHost && + left.UpstreamType == right.UpstreamType +} + +func snapshotRoutePolicyEqual(left, right snapshotRoute) bool { + return left.EnableHTTPS == right.EnableHTTPS && + left.RedirectHTTP == right.RedirectHTTP && + left.LimitConnPerServer == right.LimitConnPerServer && + left.LimitConnPerIP == right.LimitConnPerIP && + left.LimitRate == right.LimitRate && + left.CacheEnabled == right.CacheEnabled && + left.CachePolicy == right.CachePolicy && + left.BasicAuthEnabled == right.BasicAuthEnabled && + left.BasicAuthUsername == right.BasicAuthUsername && + left.BasicAuthPassword == right.BasicAuthPassword +} + +func snapshotRouteTunnelEqual(left, right snapshotRoute) bool { + return left.TunnelTargetAddr == right.TunnelTargetAddr && + left.TunnelTargetProto == right.TunnelTargetProto && + uintPtrEqual(left.TunnelNodeID, right.TunnelNodeID) && + uintPtrEqual(left.PagesProjectID, right.PagesProjectID) } func snapshotWAFConfigEqual(left snapshotWAFDocument, right snapshotWAFDocument) bool { diff --git a/internal/apps/openflare/config_version/snapshot.go b/internal/apps/openflare/config_version/snapshot.go index 6912d98a..f0ea71d4 100644 --- a/internal/apps/openflare/config_version/snapshot.go +++ b/internal/apps/openflare/config_version/snapshot.go @@ -16,6 +16,8 @@ import ( openrestyrender "github.com/Rain-kl/Wavelet/pkg/render/openresty" ) +const supportFilesPerCertificate = 2 + type snapshotRoute struct { ID uint `json:"id,omitempty"` SiteName string `json:"site_name,omitempty"` @@ -227,13 +229,14 @@ func buildSnapshotRoutes(ctx context.Context, routes []*model.ProxyRoute) ([]sna var tunnelTargetAddr string var tunnelTargetProtocol string var pagesProjectID *uint - if upstreamType == "tunnel" { + switch upstreamType { + case "tunnel": originURL = resolveTunnelOpenRestyUpstreamURL(ctx) upstreams = []string{originURL} tunnelNodeID = route.TunnelNodeID tunnelTargetAddr = strings.TrimSpace(route.TunnelTargetAddr) tunnelTargetProtocol = normalizeTunnelTargetProtocol(route.TunnelTargetProtocol) - } else if upstreamType == "pages" { + case "pages": return nil, fmt.Errorf("路由 %s Pages 配置无效: pages module is not available", route.Domain) } cacheRules, err := decodeStoredCacheRules(route.CacheRules) @@ -495,7 +498,7 @@ func buildCertificateSupportFiles(ctx context.Context, routes []snapshotRoute) ( certIDs = append(certIDs, certID) } sort.Slice(certIDs, func(i, j int) bool { return certIDs[i] < certIDs[j] }) - files := make([]SupportFile, 0, len(certIDs)*2) + files := make([]SupportFile, 0, len(certIDs)*supportFilesPerCertificate) for _, certID := range certIDs { certificate, err := model.GetTLSCertificateByID(ctx, certID) if err != nil { diff --git a/internal/apps/openflare/dashboard/helpers.go b/internal/apps/openflare/dashboard/helpers.go index cb0e4f66..b3999b8a 100644 --- a/internal/apps/openflare/dashboard/helpers.go +++ b/internal/apps/openflare/dashboard/helpers.go @@ -1,6 +1,7 @@ // Copyright 2026 Arctel.net // SPDX-License-Identifier: Apache-2.0 +// Package dashboard provides helper utilities for dashboard API handlers. package dashboard import ( @@ -15,6 +16,11 @@ const ( nodeStatusOnline = "online" nodeStatusOffline = "offline" nodeStatusPending = "pending" + + dashboardDistributionLimit = 8 + highCPUUsagePercentThreshold = 80 + highMemoryUsagePercentThreshold = 85 + highStorageUsagePercentThreshold = 85 ) func computeNodeStatus(node *model.OpenFlareNode) string { diff --git a/internal/apps/openflare/dashboard/logics.go b/internal/apps/openflare/dashboard/logics.go index 9ee857b3..d9aa3077 100644 --- a/internal/apps/openflare/dashboard/logics.go +++ b/internal/apps/openflare/dashboard/logics.go @@ -120,7 +120,7 @@ func buildOverviewView(ctx context.Context) (*OverviewView, error) { if err != nil { return nil, err } - accessLogRegions, err := model.ListOpenFlareAccessLogRegionCounts(ctx, "", since, 8) + accessLogRegions, err := model.ListOpenFlareAccessLogRegionCounts(ctx, "", since, dashboardDistributionLimit) if err != nil { return nil, err } @@ -136,7 +136,7 @@ func buildOverviewView(ctx context.Context) (*OverviewView, error) { view := &OverviewView{ GeneratedAt: now, Nodes: make([]NodeHealth, 0, len(nodes)), - Distributions: observability.BuildTrafficDistributions(reports, accessLogRegions, 8), + Distributions: observability.BuildTrafficDistributions(reports, accessLogRegions, dashboardDistributionLimit), Trends: observability.NodeTrends{ Traffic24h: observability.BuildTrafficTrendPoints(now, reports), Capacity24h: observability.BuildCapacityTrendPoints(now, snapshots), @@ -183,41 +183,8 @@ func buildOverviewView(ctx context.Context) (*OverviewView, error) { ActiveEventCount: len(nodeActiveEvents), } - if latestSnapshot != nil { - nodeHealth.CPUUsagePercent = latestSnapshot.CPUUsagePercent - nodeHealth.MemoryUsagePercent = observability.Percentage(latestSnapshot.MemoryUsedBytes, latestSnapshot.MemoryTotalBytes) - nodeHealth.StorageUsagePercent = observability.Percentage(latestSnapshot.StorageUsedBytes, latestSnapshot.StorageTotalBytes) - if latestSnapshot.CPUUsagePercent > 0 { - view.Capacity.AverageCPUUsagePercent += latestSnapshot.CPUUsagePercent - cpuNodeCount++ - } - if nodeHealth.MemoryUsagePercent > 0 { - view.Capacity.AverageMemoryUsagePercent += nodeHealth.MemoryUsagePercent - memoryNodeCount++ - } - if latestSnapshot.CPUUsagePercent >= 80 { - view.Capacity.HighCPUNodes++ - } - if nodeHealth.MemoryUsagePercent >= 85 { - view.Capacity.HighMemoryNodes++ - } - if nodeHealth.StorageUsagePercent >= 85 { - view.Capacity.HighStorageNodes++ - } - } - - if latestTraffic != nil { - nodeHealth.RequestCount = latestTraffic.RequestCount - nodeHealth.ErrorCount = latestTraffic.ErrorCount - nodeHealth.UniqueVisitorCount = latestTraffic.UniqueVisitorCount - view.Traffic.RequestCount += latestTraffic.RequestCount - view.Traffic.UniqueVisitors += latestTraffic.UniqueVisitorCount - view.Traffic.ErrorCount += latestTraffic.ErrorCount - if duration := latestTraffic.WindowEndedAt.Sub(latestTraffic.WindowStartedAt).Seconds(); duration > 0 { - view.Traffic.EstimatedQPS += float64(latestTraffic.RequestCount) / duration - } - view.Traffic.ReportedNodes++ - } + cpuNodeCount, memoryNodeCount = applyNodeSnapshotMetrics(&nodeHealth, latestSnapshot, view, cpuNodeCount, memoryNodeCount) + applyNodeTrafficMetrics(&nodeHealth, latestTraffic, view) view.Nodes = append(view.Nodes, nodeHealth) } @@ -240,6 +207,49 @@ func buildOverviewView(ctx context.Context) (*OverviewView, error) { return view, nil } +func applyNodeSnapshotMetrics(nodeHealth *NodeHealth, snapshot *model.OpenFlareMetricSnapshot, view *OverviewView, cpuNodeCount, memoryNodeCount int) (int, int) { + if snapshot == nil { + return cpuNodeCount, memoryNodeCount + } + nodeHealth.CPUUsagePercent = snapshot.CPUUsagePercent + nodeHealth.MemoryUsagePercent = observability.Percentage(snapshot.MemoryUsedBytes, snapshot.MemoryTotalBytes) + nodeHealth.StorageUsagePercent = observability.Percentage(snapshot.StorageUsedBytes, snapshot.StorageTotalBytes) + if snapshot.CPUUsagePercent > 0 { + view.Capacity.AverageCPUUsagePercent += snapshot.CPUUsagePercent + cpuNodeCount++ + } + if nodeHealth.MemoryUsagePercent > 0 { + view.Capacity.AverageMemoryUsagePercent += nodeHealth.MemoryUsagePercent + memoryNodeCount++ + } + if snapshot.CPUUsagePercent >= highCPUUsagePercentThreshold { + view.Capacity.HighCPUNodes++ + } + if nodeHealth.MemoryUsagePercent >= highMemoryUsagePercentThreshold { + view.Capacity.HighMemoryNodes++ + } + if nodeHealth.StorageUsagePercent >= highStorageUsagePercentThreshold { + view.Capacity.HighStorageNodes++ + } + return cpuNodeCount, memoryNodeCount +} + +func applyNodeTrafficMetrics(nodeHealth *NodeHealth, traffic *model.OpenFlareRequestReport, view *OverviewView) { + if traffic == nil { + return + } + nodeHealth.RequestCount = traffic.RequestCount + nodeHealth.ErrorCount = traffic.ErrorCount + nodeHealth.UniqueVisitorCount = traffic.UniqueVisitorCount + view.Traffic.RequestCount += traffic.RequestCount + view.Traffic.UniqueVisitors += traffic.UniqueVisitorCount + view.Traffic.ErrorCount += traffic.ErrorCount + if duration := traffic.WindowEndedAt.Sub(traffic.WindowStartedAt).Seconds(); duration > 0 { + view.Traffic.EstimatedQPS += float64(traffic.RequestCount) / duration + } + view.Traffic.ReportedNodes++ +} + func compressOverview(view *OverviewView) *OverviewPayload { if view == nil { return &OverviewPayload{ diff --git a/internal/apps/openflare/flared/errs.go b/internal/apps/openflare/flared/errs.go index 84a54a43..950db1f5 100644 --- a/internal/apps/openflare/flared/errs.go +++ b/internal/apps/openflare/flared/errs.go @@ -1,9 +1,10 @@ // Copyright 2026 Arctel.net // SPDX-License-Identifier: Apache-2.0 +// Package flared defines shared error messages for tunnel client operations. package flared const ( - errTunnelTokenInvalid = "无权进行此操作,Tunnel Token 无效" + errTunnelTokenInvalid = "无权进行此操作,Tunnel Token 无效" //nolint:gosec // false positive: this is an error message, not hardcoded credentials errTunnelNodeTypeMismatch = "此节点不是 TunnelClient 类型" ) diff --git a/internal/apps/openflare/flared/helpers.go b/internal/apps/openflare/flared/helpers.go index 17ce7a3a..9ed07d48 100644 --- a/internal/apps/openflare/flared/helpers.go +++ b/internal/apps/openflare/flared/helpers.go @@ -18,6 +18,11 @@ import ( "gorm.io/gorm" ) +const ( + updateChannelStable = "stable" + defaultTunnelTargetPort = 80 +) + type configVersionRow struct { Version string `gorm:"column:version"` Checksum string `gorm:"column:checksum"` @@ -31,7 +36,7 @@ func normalizeReleaseChannel(channel string) string { if strings.ToLower(strings.TrimSpace(channel)) == "preview" { return "preview" } - return "stable" + return updateChannelStable } func normalizeFlaredHeartbeatPayload(payload HeartbeatPayload) HeartbeatPayload { @@ -146,20 +151,20 @@ func decodeStoredDomains(raw string, fallbackDomain string) ([]string, error) { func parseTunnelTargetAddr(addr string) (string, int) { addr = strings.TrimSpace(addr) if addr == "" { - return "127.0.0.1", 80 + return "127.0.0.1", defaultTunnelTargetPort } host, portStr, err := net.SplitHostPort(addr) if err != nil { lastColon := strings.LastIndex(addr, ":") if lastColon < 0 { - return addr, 80 + return addr, defaultTunnelTargetPort } host = addr[:lastColon] portStr = addr[lastColon+1:] } - port := 80 + port := defaultTunnelTargetPort if _, scanErr := fmt.Sscanf(portStr, "%d", &port); scanErr != nil { - port = 80 + port = defaultTunnelTargetPort } if host == "" { host = "127.0.0.1" diff --git a/internal/apps/openflare/flared/logics.go b/internal/apps/openflare/flared/logics.go index 045a439f..c2ce899e 100644 --- a/internal/apps/openflare/flared/logics.go +++ b/internal/apps/openflare/flared/logics.go @@ -17,10 +17,11 @@ import ( ) const ( - nodeStatusOnline = "online" - applyResultOK = "success" - applyResultWarn = "warning" - applyResultFail = "failed" + nodeStatusOnline = "online" + applyResultOK = "success" + applyResultWarn = "warning" + applyResultFail = "failed" + maxApplyLogMessageLength = 16000 ) // Heartbeat processes an OpenFlared heartbeat and returns runtime settings. @@ -46,13 +47,13 @@ func Heartbeat(ctx context.Context, node *model.OpenFlareNode, payload Heartbeat "last_seen_at": now, "status": nodeStatusOnline, "update_requested": false, - "update_channel": "stable", + "update_channel": updateChannelStable, "update_tag": "", } if !previous.UpdateRequested { delete(changes, "update_requested") } - if previous.UpdateChannel == "stable" { + if previous.UpdateChannel == updateChannelStable { delete(changes, "update_channel") } if previous.UpdateTag == "" { @@ -67,7 +68,7 @@ func Heartbeat(ctx context.Context, node *model.OpenFlareNode, payload Heartbeat node.ExtVersion = payload.FrpVersion node.CurrentVersion = payload.CurrentVersion node.UpdateRequested = false - node.UpdateChannel = "stable" + node.UpdateChannel = updateChannelStable node.UpdateTag = "" lastSeen := now node.LastSeenAt = &lastSeen @@ -228,8 +229,8 @@ func normalizeApplyLogPayload(payload ApplyLogPayload) ApplyLogPayload { payload.Checksum = strings.TrimSpace(payload.Checksum) payload.MainConfigChecksum = strings.TrimSpace(payload.MainConfigChecksum) payload.RouteConfigChecksum = strings.TrimSpace(payload.RouteConfigChecksum) - if len(payload.Message) > 16000 { - payload.Message = payload.Message[:16000] + if len(payload.Message) > maxApplyLogMessageLength { + payload.Message = payload.Message[:maxApplyLogMessageLength] } return payload } diff --git a/internal/apps/openflare/flared/protocol_alias.go b/internal/apps/openflare/flared/protocol_alias.go index 4cc0d500..4d3a3c49 100644 --- a/internal/apps/openflare/flared/protocol_alias.go +++ b/internal/apps/openflare/flared/protocol_alias.go @@ -5,11 +5,26 @@ package flared import pkgprotocol "github.com/Rain-kl/Wavelet/pkg/protocol" +// HeartbeatPayload is an alias for FlaredHeartbeatPayload. type HeartbeatPayload = pkgprotocol.FlaredHeartbeatPayload + +// ConnectedRelay is an alias for FlaredConnectedRelay. type ConnectedRelay = pkgprotocol.FlaredConnectedRelay + +// ActiveConfigMeta is an alias for ActiveConfigMeta. type ActiveConfigMeta = pkgprotocol.ActiveConfigMeta + +// HeartbeatResponse is an alias for FlaredHeartbeatResponse. type HeartbeatResponse = pkgprotocol.FlaredHeartbeatResponse + +// TunnelConfigResponse is an alias for FlaredTunnelConfigResponse. type TunnelConfigResponse = pkgprotocol.FlaredTunnelConfigResponse + +// RelayInfo is an alias for FlaredRelayInfo. type RelayInfo = pkgprotocol.FlaredRelayInfo + +// ProxyEntry is an alias for FlaredProxyEntry. type ProxyEntry = pkgprotocol.FlaredProxyEntry -type ApplyLogPayload = pkgprotocol.ApplyLogPayload \ No newline at end of file + +// ApplyLogPayload is an alias for ApplyLogPayload. +type ApplyLogPayload = pkgprotocol.ApplyLogPayload diff --git a/internal/apps/openflare/geoip/lookup_test.go b/internal/apps/openflare/geoip/lookup_test.go index 3ea878a0..42cad359 100644 --- a/internal/apps/openflare/geoip/lookup_test.go +++ b/internal/apps/openflare/geoip/lookup_test.go @@ -31,7 +31,7 @@ func (f *fakeLookupProvider) Close() error { return nil } func TestLookupWithProvider(t *testing.T) { previousFactory := pkggeoip.ProviderFactoryForTest() - pkggeoip.SetProviderFactoryForTest(func(provider string) (pkggeoip.GeoIPService, error) { + pkggeoip.SetProviderFactoryForTest(func(provider string) (pkggeoip.Service, error) { return &fakeLookupProvider{}, nil }) t.Cleanup(func() { diff --git a/internal/apps/openflare/node/errs.go b/internal/apps/openflare/node/errs.go index 512ce4c9..61f6c867 100644 --- a/internal/apps/openflare/node/errs.go +++ b/internal/apps/openflare/node/errs.go @@ -1,6 +1,7 @@ // Copyright 2026 Arctel.net // SPDX-License-Identifier: Apache-2.0 +// Package node defines node validation and management error messages. package node const ( diff --git a/internal/apps/openflare/node/helpers.go b/internal/apps/openflare/node/helpers.go index 3acd0e3a..a58520ba 100644 --- a/internal/apps/openflare/node/helpers.go +++ b/internal/apps/openflare/node/helpers.go @@ -28,6 +28,13 @@ const ( openrestyStatusUnhealthy = "unhealthy" openrestyStatusUnknown = "unknown" githubReleasesAPIBase = "https://api.github.com/repos/%s/releases" + nodeTypeTunnelRelay = "tunnel_relay" + nodeTypeTunnelClient = "tunnel_client" + nodeTypeEdgeNode = "edge_node" + + nodeTokenByteLength = 16 + maxNodeIPLength = 64 + maxNodeGeoNameLength = 128 ) type releaseChannel string @@ -49,7 +56,7 @@ type githubReleaseResponse struct { } func newRandomToken() (string, error) { - buf := make([]byte, 16) + buf := make([]byte, nodeTokenByteLength) if _, err := rand.Read(buf); err != nil { return "", err } @@ -66,12 +73,12 @@ func newServerNodeID() (string, error) { func normalizeNodeType(raw string) string { switch strings.ToLower(strings.TrimSpace(raw)) { - case "tunnel_relay": - return "tunnel_relay" - case "tunnel_client": - return "tunnel_client" + case nodeTypeTunnelRelay: + return nodeTypeTunnelRelay + case nodeTypeTunnelClient: + return nodeTypeTunnelClient default: - return "edge_node" + return nodeTypeEdgeNode } } @@ -134,40 +141,50 @@ func normalizeNodeInput(input Input) (string, string, string, *float64, *float64 name := strings.TrimSpace(input.Name) ip := strings.TrimSpace(input.IP) geoName := strings.TrimSpace(input.GeoName) - manualOverride := input.GeoManualOverride || geoName != "" || input.GeoLatitude != nil || input.GeoLongitude != nil - if len(ip) > 64 { - return "", "", "", nil, nil, false, fmt.Errorf("%s", errNodeIPTooLong) + if err := validateNodeIPInput(input, ip); err != nil { + return "", "", "", nil, nil, false, err } - if ip != "" && net.ParseIP(ip) == nil { - return "", "", "", nil, nil, false, fmt.Errorf("%s", errNodeIPInvalid) - } - if input.IPManualOverride != nil && *input.IPManualOverride && ip == "" { - return "", "", "", nil, nil, false, fmt.Errorf("%s", errNodeIPManualRequired) - } - if len(geoName) > 128 { + if len(geoName) > maxNodeGeoNameLength { return "", "", "", nil, nil, false, fmt.Errorf("%s", errNodeGeoNameTooLong) } geoLatitude := cloneCoordinate(input.GeoLatitude) geoLongitude := cloneCoordinate(input.GeoLongitude) + if err := validateNodeGeoCoordinates(geoLatitude, geoLongitude); err != nil { + return "", "", "", nil, nil, false, err + } + + manualOverride := input.GeoManualOverride || geoName != "" || geoLatitude != nil || geoLongitude != nil + if !manualOverride || (geoLatitude == nil && geoLongitude == nil && geoName == "") { + return name, ip, "", nil, nil, false, nil + } + return name, ip, geoName, geoLatitude, geoLongitude, true, nil +} + +func validateNodeIPInput(input Input, ip string) error { + if len(ip) > maxNodeIPLength { + return fmt.Errorf("%s", errNodeIPTooLong) + } + if ip != "" && net.ParseIP(ip) == nil { + return fmt.Errorf("%s", errNodeIPInvalid) + } + if input.IPManualOverride != nil && *input.IPManualOverride && ip == "" { + return fmt.Errorf("%s", errNodeIPManualRequired) + } + return nil +} + +func validateNodeGeoCoordinates(geoLatitude, geoLongitude *float64) error { if (geoLatitude == nil) != (geoLongitude == nil) { - return "", "", "", nil, nil, false, fmt.Errorf("%s", errNodeGeoCoordinateMismatch) + return fmt.Errorf("%s", errNodeGeoCoordinateMismatch) } if geoLatitude != nil && (*geoLatitude < -90 || *geoLatitude > 90) { - return "", "", "", nil, nil, false, fmt.Errorf("%s", errNodeGeoLatitudeInvalid) + return fmt.Errorf("%s", errNodeGeoLatitudeInvalid) } if geoLongitude != nil && (*geoLongitude < -180 || *geoLongitude > 180) { - return "", "", "", nil, nil, false, fmt.Errorf("%s", errNodeGeoLongitudeInvalid) + return fmt.Errorf("%s", errNodeGeoLongitudeInvalid) } - - if !manualOverride { - return name, ip, "", nil, nil, false, nil - } - if geoLatitude == nil && geoLongitude == nil && geoName == "" { - return name, ip, "", nil, nil, false, nil - } - - return name, ip, geoName, geoLatitude, geoLongitude, true, nil + return nil } func computeNodeStatus(node *model.OpenFlareNode) string { @@ -189,12 +206,12 @@ func nodeViewLastSeenAt(node *model.OpenFlareNode) any { } nodeType := strings.TrimSpace(node.NodeType) if nodeType == "" { - nodeType = "edge_node" + nodeType = nodeTypeEdgeNode } - if nodeType == "tunnel_relay" && ofws.IsRelayConnected(node.NodeID) { + if nodeType == nodeTypeTunnelRelay && ofws.IsRelayConnected(node.NodeID) { return ofws.RelayWSConnectedLastSeenValue } - if nodeType == "tunnel_client" && ofws.IsFlaredConnected(node.NodeID) { + if nodeType == nodeTypeTunnelClient && ofws.IsFlaredConnected(node.NodeID) { return ofws.FlaredWSConnectedLastSeenValue } if ofws.IsAgentConnected(node.NodeID) { @@ -250,7 +267,7 @@ func buildNodeView(node *model.OpenFlareNode) *View { view.UpdateChannel = releaseChannelStable.String() } if view.NodeType == "" { - view.NodeType = "edge_node" + view.NodeType = nodeTypeEdgeNode } return view } @@ -378,7 +395,7 @@ func fetchLatestStableGitHubRelease(ctx context.Context, repo string) (*githubRe if err != nil { return nil, fmt.Errorf("获取最新版本失败: %v", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("GitHub 返回异常状态: %s", resp.Status) } @@ -395,7 +412,7 @@ func fetchLatestPreviewGitHubRelease(ctx context.Context, repo string) (*githubR if err != nil { return nil, fmt.Errorf("获取 preview 版本失败: %v", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("GitHub 返回异常状态: %s", resp.Status) } @@ -427,7 +444,7 @@ func fetchGitHubReleaseByTag(ctx context.Context, repo string, tag string) (*git if err != nil { return nil, fmt.Errorf("获取指定版本失败: %v", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode == http.StatusNotFound { return nil, fmt.Errorf("未找到指定版本: %s", tag) } @@ -461,13 +478,3 @@ func isUniqueConstraintError(err error) bool { } return strings.Contains(strings.ToLower(err.Error()), "unique") } - -func setReleaseHTTPClientForTest(client *http.Client) *http.Client { - previous := releaseHTTPClient - if client == nil { - releaseHTTPClient = &http.Client{Timeout: 30 * time.Second} - } else { - releaseHTTPClient = client - } - return previous -} diff --git a/internal/apps/openflare/node/logics.go b/internal/apps/openflare/node/logics.go index 96a614a0..39f605da 100644 --- a/internal/apps/openflare/node/logics.go +++ b/internal/apps/openflare/node/logics.go @@ -17,6 +17,11 @@ import ( "gorm.io/gorm" ) +const ( + defaultRelayBindPort = 7000 + defaultRelayVhostHTTPPort = 8080 +) + // Input is the create/update node payload. type Input struct { Name string `json:"name"` @@ -184,8 +189,8 @@ func CreateNode(ctx context.Context, input Input) (*View, error) { return nil, err } if node.NodeType == "tunnel_relay" { - node.RelayBindPort = normalizeRelayPort(input.RelayBindPort, 7000) - node.RelayVhostHTTPPort = normalizeRelayPort(input.RelayVhostHTTPPort, 8080) + node.RelayBindPort = normalizeRelayPort(input.RelayBindPort, defaultRelayBindPort) + node.RelayVhostHTTPPort = normalizeRelayPort(input.RelayVhostHTTPPort, defaultRelayVhostHTTPPort) node.RelayAuthToken, err = newRandomToken() if err != nil { return nil, err @@ -399,7 +404,7 @@ func ValidateDiscoveryToken(ctx context.Context, token string) error { return err } if token != discoveryToken { - return fmt.Errorf("Discovery Token 无效") + return fmt.Errorf("discovery Token 无效") // error 消息首字母小写 } return nil } diff --git a/internal/apps/openflare/observability/access_log_logics.go b/internal/apps/openflare/observability/access_log_logics.go index 0b2d44e3..ed19ad9d 100644 --- a/internal/apps/openflare/observability/access_log_logics.go +++ b/internal/apps/openflare/observability/access_log_logics.go @@ -1,6 +1,4 @@ -// Copyright 2026 Arctel.net -// SPDX-License-Identifier: Apache-2.0 - +// Package observability provides monitoring, metrics, and access log analysis for OpenFlare. package observability import ( @@ -22,6 +20,8 @@ const ( defaultIPTrendBucketMinute = 30 maxIPTrendHours = 168 nodeAccessLogRetentionDays = 90 + accessLogFieldRemoteAddr = "remote_addr" + accessLogFieldRequestCount = "request_count" ) var nodeAccessLogRetentionWindow = nodeAccessLogRetentionDays * 24 * time.Hour @@ -524,9 +524,9 @@ func normalizeFoldedAccessLogIPQuery(input FoldedAccessLogIPQuery) (FoldedAccess } normalizedSortBy := strings.TrimSpace(input.SortBy) switch normalizedSortBy { - case "last_seen_at", "remote_addr": + case "last_seen_at", accessLogFieldRemoteAddr: default: - normalizedSortBy = "request_count" + normalizedSortBy = accessLogFieldRequestCount } return FoldedAccessLogIPQuery{ NodeID: strings.TrimSpace(input.NodeID), @@ -591,7 +591,7 @@ func normalizeAccessLogPageSize(pageSize int) int { func normalizeAccessLogSortBy(sortBy string) string { switch strings.TrimSpace(sortBy) { - case "status_code", "remote_addr", "host", "path": + case "status_code", accessLogFieldRemoteAddr, "host", "path": return strings.TrimSpace(sortBy) default: return defaultAccessLogSortBy @@ -607,8 +607,8 @@ func normalizeAccessLogSortOrder(sortOrder string) string { func normalizeFoldSortBy(sortBy string) string { switch strings.TrimSpace(sortBy) { - case "request_count": - return "request_count" + case accessLogFieldRequestCount: + return accessLogFieldRequestCount default: return "bucket_started_at" } @@ -616,7 +616,7 @@ func normalizeFoldSortBy(sortBy string) string { func normalizeIPSummarySortBy(sortBy string) string { switch strings.TrimSpace(sortBy) { - case "recent_requests", "last_seen_at", "remote_addr": + case "recent_requests", "last_seen_at", accessLogFieldRemoteAddr: return strings.TrimSpace(sortBy) default: return "total_requests" diff --git a/internal/apps/openflare/observability/analytics.go b/internal/apps/openflare/observability/analytics.go index d4e1bcbb..d1778aef 100644 --- a/internal/apps/openflare/observability/analytics.go +++ b/internal/apps/openflare/observability/analytics.go @@ -19,6 +19,7 @@ const ( healthEventStatusResolved = "resolved" healthSeverityCritical = "critical" healthSeverityWarning = "warning" + percentageMultiplier = 100 ) // DistributionItem is a key/value distribution entry. @@ -415,7 +416,7 @@ func Percentage(used int64, total int64) float64 { if used <= 0 || total <= 0 { return 0 } - return (float64(used) / float64(total)) * 100 + return (float64(used) / float64(total)) * percentageMultiplier } func mergeJSONCounts(target distributionAccumulator, raw string) { diff --git a/internal/apps/openflare/observability/node_logics.go b/internal/apps/openflare/observability/node_logics.go index a7275ad4..fd06fd13 100644 --- a/internal/apps/openflare/observability/node_logics.go +++ b/internal/apps/openflare/observability/node_logics.go @@ -14,9 +14,10 @@ import ( ) const ( - defaultObservabilityWindow = 24 * time.Hour - defaultObservabilityLimit = 120 - maxObservabilityLimit = 500 + defaultObservabilityWindow = 24 * time.Hour + defaultObservabilityLimit = 120 + maxObservabilityLimit = 500 + defaultTrafficDistributionLimit = 8 ) // NodeQuery filters node observability data. @@ -106,7 +107,7 @@ func GetNodeObservability(ctx context.Context, id uint, query NodeQuery) (*NodeV if err != nil { return nil, err } - accessLogRegions, err := model.ListOpenFlareAccessLogRegionCounts(ctx, node.NodeID, since, 8) + accessLogRegions, err := model.ListOpenFlareAccessLogRegionCounts(ctx, node.NodeID, since, defaultTrafficDistributionLimit) if err != nil { return nil, err } @@ -135,7 +136,7 @@ func GetNodeObservability(ctx context.Context, id uint, query NodeQuery) (*NodeV HealthEvents: events, Analytics: NodeAnalytics{ Traffic: buildTrafficWindowSummary(latestTrafficReport(reports)), - Distributions: BuildTrafficDistributions(reports, accessLogRegions, 8), + Distributions: BuildTrafficDistributions(reports, accessLogRegions, defaultTrafficDistributionLimit), Health: buildHealthSummary(latestMetricSnapshot(snapshots), latestTrafficReport(reports), events), }, Trends: NodeTrends{ diff --git a/internal/apps/openflare/observability/routers.go b/internal/apps/openflare/observability/routers.go index c3c06816..09942985 100644 --- a/internal/apps/openflare/observability/routers.go +++ b/internal/apps/openflare/observability/routers.go @@ -40,7 +40,7 @@ func GetAccessLogsHandler(c *gin.Context) { c.JSON(http.StatusOK, response.OK(logs)) } -// getFoldedAccessLogsHandler 分页列出折叠访问日志。 +// GetFoldedAccessLogsHandler 分页列出折叠访问日志。 // @Summary 列出折叠访问日志 // @Description 按时间桶聚合访问日志并分页返回,需要管理员权限 // @Tags openflare-observability @@ -71,7 +71,7 @@ func GetFoldedAccessLogsHandler(c *gin.Context) { c.JSON(http.StatusOK, response.OK(logs)) } -// getFoldedAccessLogIPsHandler 列出折叠桶内的 IP 汇总。 +// GetFoldedAccessLogIPsHandler 列出折叠桶内的 IP 汇总。 // @Summary 列出折叠访问日志 IP 汇总 // @Description 在指定时间桶内按 IP 聚合访问统计,需要管理员权限 // @Tags openflare-observability @@ -112,7 +112,7 @@ func GetFoldedAccessLogIPsHandler(c *gin.Context) { c.JSON(http.StatusOK, response.OK(result)) } -// getAccessLogIPSummariesHandler 列出访问日志 IP 汇总。 +// GetAccessLogIPSummariesHandler 列出访问日志 IP 汇总。 // @Summary 列出访问日志 IP 汇总 // @Description 按 IP 聚合访问日志统计并分页返回,需要管理员权限 // @Tags openflare-observability @@ -147,7 +147,7 @@ func GetAccessLogIPSummariesHandler(c *gin.Context) { c.JSON(http.StatusOK, response.OK(result)) } -// getAccessLogIPTrendHandler 获取 IP 访问趋势。 +// GetAccessLogIPTrendHandler 获取 IP 访问趋势。 // @Summary 获取访问日志 IP 趋势 // @Description 返回指定 IP 在时间范围内的访问趋势数据,需要管理员权限 // @Tags openflare-observability @@ -178,7 +178,7 @@ func GetAccessLogIPTrendHandler(c *gin.Context) { c.JSON(http.StatusOK, response.OK(result)) } -// cleanupAccessLogsHandler 清理过期访问日志。 +// CleanupAccessLogsHandler 清理过期访问日志。 // @Summary 清理访问日志 // @Description 按保留天数清理过期访问日志记录,需要管理员权限 // @Tags openflare-observability @@ -220,4 +220,4 @@ func readAccessLogQuery(c *gin.Context) AccessLogQuery { func readQueryInt(c *gin.Context, key string) int { value, _ := strconv.Atoi(c.DefaultQuery(key, "0")) return value -} \ No newline at end of file +} diff --git a/internal/apps/openflare/option/errs.go b/internal/apps/openflare/option/errs.go index 4056a8ee..0b1f7f0a 100644 --- a/internal/apps/openflare/option/errs.go +++ b/internal/apps/openflare/option/errs.go @@ -1,6 +1,7 @@ // Copyright 2026 Arctel.net // SPDX-License-Identifier: Apache-2.0 +// Package option provides handler-level error message constants for the openflare option module. package option const ( diff --git a/internal/apps/openflare/option/logics.go b/internal/apps/openflare/option/logics.go index af62588e..05d7b0ed 100644 --- a/internal/apps/openflare/option/logics.go +++ b/internal/apps/openflare/option/logics.go @@ -161,7 +161,7 @@ func getStatus(ctx context.Context, baseAPIPath string) (*statusView, error) { StartTime: model.StartTime, EmailVerification: model.EmailVerificationEnabled, GitHubOAuth: model.GitHubOAuthEnabled, - GitHubClientID: model.GitHubClientId, + GitHubClientID: model.GitHubClientID, SystemName: model.SystemName, HomePageLink: model.HomePageLink, FooterHTML: model.Footer, diff --git a/internal/apps/openflare/option/openresty_validators.go b/internal/apps/openflare/option/openresty_validators.go new file mode 100644 index 00000000..6e9c10e9 --- /dev/null +++ b/internal/apps/openflare/option/openresty_validators.go @@ -0,0 +1,179 @@ +// Copyright 2026 Arctel.net +// SPDX-License-Identifier: Apache-2.0 + +package option + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +var openRestyOptionValidators = map[string]func(key, value string) error{ + "OpenRestyDefaultServerReturnStatus": validateOpenRestyDefaultServerReturnStatus, + "OpenRestyWorkerProcesses": validateOpenRestyWorkerProcesses, + "OpenRestyWorkerConnections": validatePositiveIntegerOption, + "OpenRestyWorkerRlimitNofile": validatePositiveIntegerOption, + "OpenRestyKeepaliveTimeout": validatePositiveIntegerOption, + "OpenRestyKeepaliveRequests": validatePositiveIntegerOption, + "OpenRestyClientHeaderTimeout": validatePositiveIntegerOption, + "OpenRestyClientBodyTimeout": validatePositiveIntegerOption, + "OpenRestySendTimeout": validatePositiveIntegerOption, + "OpenRestyProxyConnectTimeout": validatePositiveIntegerOption, + "OpenRestyProxySendTimeout": validatePositiveIntegerOption, + "OpenRestyProxyReadTimeout": validatePositiveIntegerOption, + "OpenRestyGzipMinLength": validatePositiveIntegerOption, + "OpenRestyGzipCompLevel": validateOpenRestyGzipCompLevel, + "OpenRestyEventsUse": validateOpenRestyEventsUse, + "OpenRestyResolvers": validateOpenRestyResolvers, + "OpenRestyEventsMultiAcceptEnabled": validateBooleanOption, + "OpenRestyWebsocketEnabled": validateBooleanOption, + "OpenRestyHTTP3Enabled": validateBooleanOption, + "OpenRestyProxyRequestBufferingEnabled": validateBooleanOption, + "OpenRestyProxyBufferingEnabled": validateBooleanOption, + "OpenRestyGzipEnabled": validateBooleanOption, + "OpenRestyCacheEnabled": validateBooleanOption, + "OpenRestyCacheLockEnabled": validateBooleanOption, + "OpenRestyProxyBuffers": validateOpenRestyProxyBuffers, + "OpenRestyLargeClientHeaderBuffers": validateOpenRestyProxyBuffers, + "OpenRestyProxyBufferSize": validateOpenRestySizeValue, + "OpenRestyProxyBusyBuffersSize": validateOpenRestySizeValue, + "OpenRestyCacheMaxSize": validateOpenRestySizeValue, + "OpenRestyClientMaxBodySize": validateOpenRestySizeValue, + "OpenRestyCachePath": validateOpenRestyCachePath, + "OpenRestyCacheLevels": validateOpenRestyCacheLevels, + "OpenRestyCacheInactive": validateOpenRestyDurationToken, + "OpenRestyCacheLockTimeout": validateOpenRestyDurationToken, + "OpenRestyCacheKeyTemplate": validateOpenRestyCacheKeyTemplate, + "OpenRestyCacheUseStale": validateOpenRestyCacheUseStale, + "OpenRestyMainConfigTemplate": validateOpenRestyMainConfigTemplate, +} + +func validateOpenRestyOption(key, value string) error { + trimmed := strings.TrimSpace(value) + if validator, ok := openRestyOptionValidators[key]; ok { + return validator(key, trimmed) + } + return nil +} + +func validateOpenRestyDefaultServerReturnStatus(key, trimmed string) error { + if err := validatePositiveIntegerOption(key, trimmed); err != nil { + return err + } + statusCode, _ := strconv.Atoi(trimmed) + if statusCode < 100 || statusCode > 999 { + return fmt.Errorf("%s 必须在 100 到 999 之间", key) + } + return nil +} + +func validateOpenRestyWorkerProcesses(key, trimmed string) error { + if trimmed == "auto" { + return nil + } + return validatePositiveIntegerOption(key, trimmed) +} + +func validateOpenRestyGzipCompLevel(key, trimmed string) error { + if err := validatePositiveIntegerOption(key, trimmed); err != nil { + return err + } + level, _ := strconv.Atoi(trimmed) + if level > maxOpenRestyGzipCompLevel { + return fmt.Errorf("%s 不能大于 %d", key, maxOpenRestyGzipCompLevel) + } + return nil +} + +func validateOpenRestyEventsUse(key, trimmed string) error { + if trimmed == "" { + return nil + } + switch trimmed { + case "epoll", "kqueue", "poll", "select", "rtsig", "/dev/poll", "eventport": + return nil + default: + return fmt.Errorf("%s 仅支持 epoll、kqueue、poll、select、rtsig、/dev/poll、eventport 或留空", key) + } +} + +func validateOpenRestyResolvers(key, trimmed string) error { + if trimmed == "" { + return nil + } + if !regexp.MustCompile(`^[a-zA-Z0-9.:\-\s]+$`).MatchString(trimmed) { + return fmt.Errorf("%s 包含非法字符,请填入有效的 IP 地址或域名,以空格分隔", key) + } + return nil +} + +func validateOpenRestyProxyBuffers(key, trimmed string) error { + if openRestyProxyBuffersPattern.MatchString(trimmed) { + return nil + } + return fmt.Errorf("%s 格式必须类似 \"16 16k\"", key) +} + +func validateOpenRestySizeValue(key, trimmed string) error { + if openRestySizePattern.MatchString(trimmed) { + return nil + } + return fmt.Errorf("%s 格式必须为整数或带 k/m/g 单位的大小值", key) +} + +func validateOpenRestyCachePath(key, trimmed string) error { + if strings.ContainsAny(trimmed, "\r\n\t") { + return fmt.Errorf("%s 不能包含换行或制表符", key) + } + return nil +} + +func validateOpenRestyCacheLevels(key, trimmed string) error { + if openRestyCacheLevelsPattern.MatchString(trimmed) { + return nil + } + return fmt.Errorf("%s 格式必须类似 \"1:2\" 或 \"1:2:2\"", key) +} + +func validateOpenRestyDurationToken(key, trimmed string) error { + if openRestyDurationTokenPattern.MatchString(trimmed) { + return nil + } + return fmt.Errorf("%s 格式必须为带单位的时长,例如 30m 或 5s", key) +} + +func validateOpenRestyCacheKeyTemplate(key, trimmed string) error { + if trimmed == "" { + return fmt.Errorf("%s 不能为空", key) + } + if strings.ContainsAny(trimmed, "\r\n") { + return fmt.Errorf("%s 不能包含换行", key) + } + return nil +} + +func validateOpenRestyCacheUseStale(key, trimmed string) error { + if trimmed == "" { + return fmt.Errorf("%s 不能为空", key) + } + allowedTokens := map[string]struct{}{ + "error": {}, "timeout": {}, "invalid_header": {}, "updating": {}, + "http_500": {}, "http_502": {}, "http_503": {}, "http_504": {}, + "http_403": {}, "http_404": {}, "http_429": {}, "off": {}, + } + for _, token := range strings.Fields(trimmed) { + if _, ok := allowedTokens[token]; !ok { + return fmt.Errorf("%s 包含不支持的值 %q", key, token) + } + } + return nil +} + +func validateOpenRestyMainConfigTemplate(key, value string) error { + if strings.TrimSpace(value) == "" { + return fmt.Errorf("%s 不能为空", key) + } + return nil +} diff --git a/internal/apps/openflare/option/routers.go b/internal/apps/openflare/option/routers.go index a4a8c2b0..64838662 100644 --- a/internal/apps/openflare/option/routers.go +++ b/internal/apps/openflare/option/routers.go @@ -32,7 +32,7 @@ func GetStatusHandler(c *gin.Context) { c.JSON(http.StatusOK, response.OK(view)) } -// getNoticeHandler 获取系统公告。 +// GetNoticeHandler 获取系统公告。 // @Summary 获取系统公告 // @Description 返回 OpenFlare 控制台公告文本,无需登录 // @Tags openflare-option @@ -41,7 +41,6 @@ func GetStatusHandler(c *gin.Context) { // @Failure 400 {object} response.Any "参数错误" // @Failure 500 {object} response.Any "内部错误" // @Router /api/v1/d/notice [get] -// GetNoticeHandler returns the notice content. func GetNoticeHandler(c *gin.Context) { notice, err := getNotice(c.Request.Context()) if apiutil.AbortBadRequestOnError(c, err) { @@ -50,7 +49,7 @@ func GetNoticeHandler(c *gin.Context) { c.JSON(http.StatusOK, response.OK(notice)) } -// listOptionsHandler 列出全部配置项。 +// ListOptionsHandler 列出全部配置项。 // @Summary 列出 OpenFlare 配置项 // @Description 返回全部非敏感 OpenFlare 配置项,需要管理员权限 // @Tags openflare-option @@ -62,7 +61,6 @@ func GetNoticeHandler(c *gin.Context) { // @Failure 404 {object} response.Any "无权限或不存在" // @Failure 500 {object} response.Any "内部错误" // @Router /api/v1/d/option [get] -// ListOptionsHandler lists OpenFlare options. func ListOptionsHandler(c *gin.Context) { options, err := listOptions(c.Request.Context()) if apiutil.AbortBadRequestOnError(c, err) { @@ -71,7 +69,7 @@ func ListOptionsHandler(c *gin.Context) { c.JSON(http.StatusOK, response.OK(options)) } -// updateOptionHandler 更新单个配置项。 +// UpdateOptionHandler 更新单个配置项。 // @Summary 更新 OpenFlare 配置项 // @Description 更新单个 OpenFlare 配置项,需要管理员权限 // @Tags openflare-option @@ -85,7 +83,6 @@ func ListOptionsHandler(c *gin.Context) { // @Failure 404 {object} response.Any "无权限或不存在" // @Failure 500 {object} response.Any "内部错误" // @Router /api/v1/d/option/update [post] -// UpdateOptionHandler updates a single option. func UpdateOptionHandler(c *gin.Context) { var option model.OpenFlareOption if !apiutil.BindJSON(c, &option) { @@ -97,7 +94,7 @@ func UpdateOptionHandler(c *gin.Context) { c.JSON(http.StatusOK, response.OKNil()) } -// updateOptionsBatchHandler 批量更新配置项。 +// UpdateOptionsBatchHandler 批量更新配置项。 // @Summary 批量更新 OpenFlare 配置项 // @Description 批量更新多个 OpenFlare 配置项,需要管理员权限 // @Tags openflare-option @@ -111,7 +108,6 @@ func UpdateOptionHandler(c *gin.Context) { // @Failure 404 {object} response.Any "无权限或不存在" // @Failure 500 {object} response.Any "内部错误" // @Router /api/v1/d/option/update-batch [post] -// UpdateOptionsBatchHandler updates options in batch. func UpdateOptionsBatchHandler(c *gin.Context) { var payload optionBatchPayload if !apiutil.BindJSON(c, &payload) { @@ -123,7 +119,7 @@ func UpdateOptionsBatchHandler(c *gin.Context) { c.JSON(http.StatusOK, response.OKNil()) } -// lookupGeoIPHandler 查询 GeoIP 信息。 +// LookupGeoIPHandler 查询 GeoIP 信息。 // @Summary GeoIP 地址查询 // @Description 按提供商与 IP 查询地理位置信息,需要管理员权限 // @Tags openflare-option @@ -137,7 +133,6 @@ func UpdateOptionsBatchHandler(c *gin.Context) { // @Failure 404 {object} response.Any "无权限或不存在" // @Failure 500 {object} response.Any "内部错误" // @Router /api/v1/d/option/geoip/lookup [post] -// LookupGeoIPHandler performs a GeoIP lookup. func LookupGeoIPHandler(c *gin.Context) { var request geoIPLookupRequest if !apiutil.BindJSON(c, &request) { @@ -150,7 +145,7 @@ func LookupGeoIPHandler(c *gin.Context) { c.JSON(http.StatusOK, response.OK(view)) } -// cleanupDatabaseHandler 清理可观测性数据库数据。 +// CleanupDatabaseHandler 清理可观测性数据库数据。 // @Summary 清理可观测性数据库 // @Description 按目标与保留天数清理可观测性相关数据表,需要管理员权限 // @Tags openflare-option @@ -164,7 +159,6 @@ func LookupGeoIPHandler(c *gin.Context) { // @Failure 404 {object} response.Any "无权限或不存在" // @Failure 500 {object} response.Any "内部错误" // @Router /api/v1/d/option/database/cleanup [post] -// CleanupDatabaseHandler cleans up observability data. func CleanupDatabaseHandler(c *gin.Context) { var input databaseCleanupInput if err := bindOptionalJSON(c.Request.Body, &input); err != nil { @@ -178,7 +172,7 @@ func CleanupDatabaseHandler(c *gin.Context) { c.JSON(http.StatusOK, response.OK(result)) } -// syncUptimeKumaHandler 同步 Uptime Kuma 监控。 +// SyncUptimeKumaHandler 同步 Uptime Kuma 监控。 // @Summary 同步 Uptime Kuma // @Description 将 OpenFlare 节点同步到 Uptime Kuma,需要管理员权限 // @Tags openflare-option @@ -191,7 +185,6 @@ func CleanupDatabaseHandler(c *gin.Context) { // @Failure 404 {object} response.Any "无权限或不存在" // @Failure 500 {object} response.Any "内部错误" // @Router /api/v1/d/uptimekuma/sync [post] -// SyncUptimeKumaHandler triggers UptimeKuma sync. func SyncUptimeKumaHandler(c *gin.Context) { if apiutil.AbortBadRequestOnError(c, syncUptimeKuma(c.Request.Context())) { return @@ -204,4 +197,4 @@ func bindOptionalJSON(body io.Reader, target any) error { return err } return nil -} \ No newline at end of file +} diff --git a/internal/apps/openflare/option/validate.go b/internal/apps/openflare/option/validate.go index b90a70da..12fa2d20 100644 --- a/internal/apps/openflare/option/validate.go +++ b/internal/apps/openflare/option/validate.go @@ -4,6 +4,7 @@ package option import ( + "errors" "fmt" "regexp" "strconv" @@ -13,6 +14,8 @@ import ( "github.com/Rain-kl/Wavelet/internal/model" ) +const maxOpenRestyGzipCompLevel = 9 + var ( openRestySizePattern = regexp.MustCompile(`^\d+[kKmMgG]?$`) openRestyProxyBuffersPattern = regexp.MustCompile(`^\d+\s+\d+[kKmMgG]?$`) @@ -20,6 +23,8 @@ var ( openRestyDurationTokenPattern = regexp.MustCompile(`^\d+[smhdwSMHDW]$`) ) +const optionValueTrue = "true" + func buildOptionValidationState(options []model.OpenFlareOption) map[string]string { model.OptionMapRWMutex.RLock() state := make(map[string]string, len(model.OptionMap)+len(options)) @@ -37,11 +42,11 @@ func buildOptionValidationState(options []model.OpenFlareOption) map[string]stri func validateOptionWithState(option model.OpenFlareOption, state map[string]string) error { switch option.Key { case "GitHubOAuthEnabled": - if option.Value == "true" && strings.TrimSpace(state["GitHubClientId"]) == "" { + if option.Value == optionValueTrue && strings.TrimSpace(state["GitHubClientId"]) == "" { return fmt.Errorf("无法启用 GitHub OAuth,请先填入 GitHub Client ID 以及 GitHub Client Secret!") } case "WeChatAuthEnabled": - if option.Value == "true" && strings.TrimSpace(state["WeChatServerAddress"]) == "" { + if option.Value == optionValueTrue && strings.TrimSpace(state["WeChatServerAddress"]) == "" { return fmt.Errorf("无法启用微信登录,请先填入微信登录相关配置信息!") } } @@ -71,7 +76,7 @@ func validatePositiveIntegerOption(key, value string) error { func validateBooleanOption(key, value string) error { switch value { - case "true", "false": + case optionValueTrue, "false": return nil default: return fmt.Errorf("%s 必须为 true 或 false", key) @@ -112,171 +117,81 @@ func validateUptimeKumaOption(key, value string, state map[string]string) error trimmed := strings.TrimSpace(value) switch key { case "UptimeKumaEnabled": - if err := validateBooleanOption(key, trimmed); err != nil { - return err - } - if trimmed == "true" { - url := strings.TrimSpace(state["UptimeKumaUrl"]) - username := strings.TrimSpace(state["UptimeKumaUsername"]) - password := strings.TrimSpace(state["UptimeKumaPassword"]) - if url == "" { - return fmt.Errorf("启用 Uptime Kuma 时地址不能为空") - } - if username == "" { - return fmt.Errorf("启用 Uptime Kuma 时用户名不能为空") - } - if password == "" && model.UptimeKumaPassword == "" { - return fmt.Errorf("启用 Uptime Kuma 时密码不能为空") - } - } + return validateUptimeKumaEnabled(key, trimmed, state) case "UptimeKumaUsername": - if trimmed == "" && state["UptimeKumaEnabled"] == "true" { - return fmt.Errorf("启用 Uptime Kuma 时用户名不能为空") - } + return validateUptimeKumaUsername(trimmed, state) case "UptimeKumaUrl": - if trimmed != "" && !strings.HasPrefix(trimmed, "http://") && !strings.HasPrefix(trimmed, "https://") { - return fmt.Errorf("Uptime Kuma 地址必须以 http:// 或 https:// 开头") - } + return validateUptimeKumaURL(trimmed) case "UptimeKumaMonitorScope": - if trimmed != "all" && trimmed != "selected" { - return fmt.Errorf("监控范围必须为全部站点 (all) 或选择站点 (selected)") - } + return validateUptimeKumaMonitorScope(trimmed) case "UptimeKumaSyncInterval", "UptimeKumaInterval", "UptimeKumaRetryInterval", "UptimeKumaTimeout": return validatePositiveIntegerOption(key, trimmed) case "UptimeKumaRetry": - intValue, err := strconv.Atoi(trimmed) - if err != nil || intValue < 0 { - return fmt.Errorf("%s 必须为大于等于 0 的整数", key) - } + return validateUptimeKumaRetry(key, trimmed) } return nil } -func validateOpenRestyOption(key, value string) error { - trimmed := strings.TrimSpace(value) +func validateUptimeKumaEnabled(key, trimmed string, state map[string]string) error { + if err := validateBooleanOption(key, trimmed); err != nil { + return err + } + if trimmed != optionValueTrue { + return nil + } + url := strings.TrimSpace(state["UptimeKumaUrl"]) + username := strings.TrimSpace(state["UptimeKumaUsername"]) + password := strings.TrimSpace(state["UptimeKumaPassword"]) + if url == "" { + return fmt.Errorf("启用 Uptime Kuma 时地址不能为空") + } + if username == "" { + return fmt.Errorf("启用 Uptime Kuma 时用户名不能为空") + } + if password == "" && model.UptimeKumaPassword == "" { + return fmt.Errorf("启用 Uptime Kuma 时密码不能为空") + } + return nil +} - switch key { - case "OpenRestyDefaultServerReturnStatus": - if err := validatePositiveIntegerOption(key, trimmed); err != nil { - return err - } - statusCode, _ := strconv.Atoi(trimmed) - if statusCode < 100 || statusCode > 999 { - return fmt.Errorf("%s 必须在 100 到 999 之间", key) - } - case "OpenRestyWorkerProcesses": - if trimmed == "auto" { - return nil - } - return validatePositiveIntegerOption(key, trimmed) - case "OpenRestyWorkerConnections", - "OpenRestyWorkerRlimitNofile", - "OpenRestyKeepaliveTimeout", - "OpenRestyKeepaliveRequests", - "OpenRestyClientHeaderTimeout", - "OpenRestyClientBodyTimeout", - "OpenRestySendTimeout", - "OpenRestyProxyConnectTimeout", - "OpenRestyProxySendTimeout", - "OpenRestyProxyReadTimeout", - "OpenRestyGzipMinLength": - return validatePositiveIntegerOption(key, trimmed) - case "OpenRestyGzipCompLevel": - if err := validatePositiveIntegerOption(key, trimmed); err != nil { - return err - } - level, _ := strconv.Atoi(trimmed) - if level > 9 { - return fmt.Errorf("%s 不能大于 9", key) - } - case "OpenRestyEventsUse": - if trimmed == "" { - return nil - } - switch trimmed { - case "epoll", "kqueue", "poll", "select", "rtsig", "/dev/poll", "eventport": - return nil - default: - return fmt.Errorf("%s 仅支持 epoll、kqueue、poll、select、rtsig、/dev/poll、eventport 或留空", key) - } - case "OpenRestyResolvers": - if trimmed == "" { - return nil - } - if !regexp.MustCompile(`^[a-zA-Z0-9.:\-\s]+$`).MatchString(trimmed) { - return fmt.Errorf("%s 包含非法字符,请填入有效的 IP 地址或域名,以空格分隔", key) - } - case "OpenRestyEventsMultiAcceptEnabled", - "OpenRestyWebsocketEnabled", - "OpenRestyHTTP3Enabled", - "OpenRestyProxyRequestBufferingEnabled", - "OpenRestyProxyBufferingEnabled", - "OpenRestyGzipEnabled", - "OpenRestyCacheEnabled", - "OpenRestyCacheLockEnabled": - return validateBooleanOption(key, trimmed) - case "OpenRestyProxyBuffers", "OpenRestyLargeClientHeaderBuffers": - if openRestyProxyBuffersPattern.MatchString(trimmed) { - return nil - } - return fmt.Errorf("%s 格式必须类似 \"16 16k\"", key) - case "OpenRestyProxyBufferSize", "OpenRestyProxyBusyBuffersSize", "OpenRestyCacheMaxSize", "OpenRestyClientMaxBodySize": - if openRestySizePattern.MatchString(trimmed) { - return nil - } - return fmt.Errorf("%s 格式必须为整数或带 k/m/g 单位的大小值", key) - case "OpenRestyCachePath": - if strings.ContainsAny(trimmed, "\r\n\t") { - return fmt.Errorf("%s 不能包含换行或制表符", key) - } - case "OpenRestyCacheLevels": - if openRestyCacheLevelsPattern.MatchString(trimmed) { - return nil - } - return fmt.Errorf("%s 格式必须类似 \"1:2\" 或 \"1:2:2\"", key) - case "OpenRestyCacheInactive", "OpenRestyCacheLockTimeout": - if openRestyDurationTokenPattern.MatchString(trimmed) { - return nil - } - return fmt.Errorf("%s 格式必须为带单位的时长,例如 30m 或 5s", key) - case "OpenRestyCacheKeyTemplate": - if trimmed == "" { - return fmt.Errorf("%s 不能为空", key) - } - if strings.ContainsAny(trimmed, "\r\n") { - return fmt.Errorf("%s 不能包含换行", key) - } - case "OpenRestyCacheUseStale": - if trimmed == "" { - return fmt.Errorf("%s 不能为空", key) - } - allowedTokens := map[string]struct{}{ - "error": {}, "timeout": {}, "invalid_header": {}, "updating": {}, - "http_500": {}, "http_502": {}, "http_503": {}, "http_504": {}, - "http_403": {}, "http_404": {}, "http_429": {}, "off": {}, - } - for _, token := range strings.Fields(trimmed) { - if _, ok := allowedTokens[token]; !ok { - return fmt.Errorf("%s 包含不支持的值 %q", key, token) - } - } - case "OpenRestyMainConfigTemplate": - if strings.TrimSpace(value) == "" { - return fmt.Errorf("%s 不能为空", key) - } +func validateUptimeKumaUsername(trimmed string, state map[string]string) error { + if trimmed == "" && state["UptimeKumaEnabled"] == optionValueTrue { + return fmt.Errorf("启用 Uptime Kuma 时用户名不能为空") + } + return nil +} + +func validateUptimeKumaURL(trimmed string) error { + if trimmed != "" && !strings.HasPrefix(trimmed, "http://") && !strings.HasPrefix(trimmed, "https://") { + return fmt.Errorf("uptime Kuma 地址必须以 http:// 或 https:// 开头") + } + return nil +} + +func validateUptimeKumaMonitorScope(trimmed string) error { + if trimmed != "all" && trimmed != "selected" { + return fmt.Errorf("监控范围必须为全部站点 (all) 或选择站点 (selected)") + } + return nil +} + +func validateUptimeKumaRetry(key, trimmed string) error { + intValue, err := strconv.Atoi(trimmed) + if err != nil || intValue < 0 { + return fmt.Errorf("%s 必须为大于等于 0 的整数", key) } return nil } func validateOptions(options []model.OpenFlareOption) error { if len(options) == 0 { - return fmt.Errorf(errInvalidParams) + return errors.New(errInvalidParams) } state := buildOptionValidationState(options) for _, option := range options { if strings.TrimSpace(option.Key) == "" { - return fmt.Errorf(errInvalidParams) + return errors.New(errInvalidParams) } if err := validateOptionWithState(option, state); err != nil { return err diff --git a/internal/apps/openflare/origin/errs.go b/internal/apps/openflare/origin/errs.go index 43188b49..630414ff 100644 --- a/internal/apps/openflare/origin/errs.go +++ b/internal/apps/openflare/origin/errs.go @@ -1,6 +1,7 @@ // Copyright 2026 Arctel.net // SPDX-License-Identifier: Apache-2.0 +// Package origin defines shared error messages for origin management. package origin const ( diff --git a/internal/apps/openflare/origin/helpers.go b/internal/apps/openflare/origin/helpers.go index 93fbc19d..7638da96 100644 --- a/internal/apps/openflare/origin/helpers.go +++ b/internal/apps/openflare/origin/helpers.go @@ -12,6 +12,8 @@ import ( "unicode" ) +const maxOriginHostnameLength = 253 + func normalizeOriginAddress(raw string) string { return strings.ToLower(strings.TrimSpace(raw)) } @@ -29,7 +31,7 @@ func validateOriginAddress(address string) error { if ip := net.ParseIP(address); ip != nil { return nil } - if len(address) > 253 { + if len(address) > maxOriginHostnameLength { return errors.New(errOriginAddressInvalid) } labels := strings.Split(address, ".") diff --git a/internal/apps/openflare/pages/errs.go b/internal/apps/openflare/pages/errs.go index 412a81e7..cdf26b3f 100644 --- a/internal/apps/openflare/pages/errs.go +++ b/internal/apps/openflare/pages/errs.go @@ -1,27 +1,28 @@ // Copyright 2026 Arctel.net // SPDX-License-Identifier: Apache-2.0 +// Package pages provides logics and management for OpenFlare static page deployments. package pages const ( - errPagesProjectNotFound = "Pages 项目不存在" - errPagesSlugExists = "Pages 项目标识已存在" - errPagesNameRequired = "Pages 项目名称不能为空" - errPagesSlugInvalid = "Pages 项目标识只能包含小写字母、数字和连字符" - errPagesDeleteReferenced = "Pages 项目已被规则引用,不能删除" - errPagesDeploymentNotFound = "Pages 部署不存在" - errPagesDeploymentMismatch = "Pages 部署不属于该项目" + errPagesProjectNotFound = "pages 项目不存在" + errPagesSlugExists = "pages 项目标识已存在" + errPagesNameRequired = "pages 项目名称不能为空" + errPagesSlugInvalid = "pages 项目标识只能包含小写字母、数字和连字符" + errPagesDeleteReferenced = "pages 项目已被规则引用,不能删除" + errPagesDeploymentNotFound = "pages 部署不存在" + errPagesDeploymentMismatch = "pages 部署不属于该项目" errPagesDeleteActiveDeploy = "不能删除当前激活的 Pages 部署" errPagesPackageMissing = "缺少 Pages 部署包" - errPagesPackageNotZip = "Pages 部署包必须是 .zip 文件" - errPagesPackageInvalidZip = "Pages 部署包不是有效 zip 文件" - errPagesPackageEmpty = "Pages 部署包不能为空" + errPagesPackageNotZip = "pages 部署包必须是 .zip 文件" + errPagesPackageInvalidZip = "pages 部署包不是有效 zip 文件" + errPagesPackageEmpty = "pages 部署包不能为空" errPagesAPIProxyPathRequired = "启用 API 反代时,匹配路径不能为空" errPagesAPIProxyPathPrefix = "API 反代匹配路径必须以 '/' 开头" - errPagesAPIProxyPassRequired = "启用 API 反代时,后端服务地址不能为空" - errPagesAPIProxyPassInvalid = "API 反代后端服务地址必须是有效的 HTTP/HTTPS URL" - errPagesPackagePathEmpty = "Pages 部署包路径为空" - errPagesPackageUploadMissing = "Pages 部署包上传记录不存在" - errPagesPackageNotInActiveConfig = "Pages 部署尚未进入激活配置" + errPagesAPIProxyPassRequired = "启用 API 反代时,后端服务地址不能为空" //nolint:gosec // false positive: this is an error message, not hardcoded credentials + errPagesAPIProxyPassInvalid = "API 反代后端服务地址必须是有效的 HTTP/HTTPS URL" //nolint:gosec // false positive: this is an error message, not hardcoded credentials + errPagesPackagePathEmpty = "pages 部署包路径为空" + errPagesPackageUploadMissing = "pages 部署包上传记录不存在" + errPagesPackageNotInActiveConfig = "pages 部署尚未进入激活配置" errPagesInvalidSnapshotFormat = "配置快照格式无效" ) diff --git a/internal/apps/openflare/pages/helpers.go b/internal/apps/openflare/pages/helpers.go index 7515f798..4bf081b9 100644 --- a/internal/apps/openflare/pages/helpers.go +++ b/internal/apps/openflare/pages/helpers.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "io" + "math" "mime/multipart" "os" "path" @@ -24,11 +25,14 @@ import ( ) const ( - pagesMaxDeploymentFiles = 1000 - pagesMaxDeploymentBytes = 100 * 1024 * 1024 - defaultPagesEntryFile = "index.html" - defaultPagesFallbackPath = "/index.html" + pagesMaxDeploymentFiles = 1000 + pagesMaxDeploymentBytes = 100 * 1024 * 1024 + defaultPagesEntryFile = "index.html" + defaultPagesFallbackPath = "/index.html" pagesDeploymentUploadType = "openflare_pages_deployment" + mimeTypeApplicationZip = "application/zip" + pagesMaxPathLength = 512 + bytesPerKiB = 1024 ) var pagesSlugPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,126}[a-z0-9]$|^[a-z0-9]$`) @@ -71,15 +75,15 @@ func validateAndNormalizePagesRootDir(raw string) (string, error) { if value == "" { return "", nil } - if len(value) > 512 { - return "", errors.New("Pages 根目录长度不能超过 512") + if len(value) > pagesMaxPathLength { + return "", errors.New("pages 根目录长度不能超过 512") // error 消息首字母小写 } if strings.Contains(value, "\\") || strings.ContainsAny(value, "\"';") { - return "", errors.New("Pages 根目录包含不支持的字符") + return "", errors.New("pages 根目录包含不支持的字符") } for _, r := range value { if r <= 0x20 || r == 0x7f { - return "", errors.New("Pages 根目录不能包含空白或控制字符") + return "", errors.New("pages 根目录不能包含空白或控制字符") } } cleaned := path.Clean(filepath.ToSlash(value)) @@ -88,7 +92,7 @@ func validateAndNormalizePagesRootDir(raw string) (string, error) { } for _, segment := range strings.Split(cleaned, "/") { if segment == "." || segment == ".." { - return "", errors.New("Pages 根目录不能包含 . 或 .. 路径段") + return "", errors.New("pages 根目录不能包含 . 或 .. 路径段") } } return strings.TrimPrefix(cleaned, "/"), nil @@ -99,34 +103,34 @@ func normalizePagesFallbackPath(raw string) (string, error) { if value == "" { value = defaultPagesFallbackPath } - if len(value) > 512 { - return "", errors.New("SPA fallback 回退路径长度不能超过 512") + if len(value) > pagesMaxPathLength { + return "", errors.New("spa fallback 回退路径长度不能超过 512") } if !strings.HasPrefix(value, "/") { - return "", errors.New("SPA fallback 回退路径必须以 / 开头") + return "", errors.New("spa fallback 回退路径必须以 / 开头") } if value == "/" || strings.HasSuffix(value, "/") { - return "", errors.New("SPA fallback 回退路径必须指向具体文件") + return "", errors.New("spa fallback 回退路径必须指向具体文件") } if strings.Contains(value, "\\") || strings.ContainsAny(value, "\"';") { - return "", errors.New("SPA fallback 回退路径包含不支持的字符") + return "", errors.New("spa fallback 回退路径包含不支持的字符") } for _, r := range value { if r <= 0x20 || r == 0x7f { - return "", errors.New("SPA fallback 回退路径不能包含空白或控制字符") + return "", errors.New("spa fallback 回退路径不能包含空白或控制字符") } } for _, segment := range strings.Split(value, "/") { if segment == "." || segment == ".." { - return "", errors.New("SPA fallback 回退路径不能包含 . 或 .. 路径段") + return "", errors.New("spa fallback 回退路径不能包含 . 或 .. 路径段") } } cleaned := path.Clean(value) if cleaned == "." || !strings.HasPrefix(cleaned, "/") { - return "", errors.New("SPA fallback 回退路径不合法") + return "", errors.New("spa fallback 回退路径不合法") } if cleaned == "/" || strings.HasSuffix(cleaned, "/") { - return "", errors.New("SPA fallback 回退路径必须指向具体文件") + return "", errors.New("spa fallback 回退路径必须指向具体文件") } return cleaned, nil } @@ -152,12 +156,12 @@ func persistPagesUploadTemp(fileHeader *multipart.FileHeader) (string, string, i if err != nil { return "", "", 0, err } - defer file.Close() + defer func() { _ = file.Close() }() temp, err := os.CreateTemp("", "openflare-pages-*.zip") if err != nil { return "", "", 0, err } - defer temp.Close() + defer func() { _ = temp.Close() }() hash := sha256.New() limited := io.LimitReader(file, pagesMaxDeploymentBytes+1) written, err := io.Copy(io.MultiWriter(temp, hash), limited) @@ -167,7 +171,7 @@ func persistPagesUploadTemp(fileHeader *multipart.FileHeader) (string, string, i } if written > pagesMaxDeploymentBytes { _ = os.Remove(temp.Name()) - return "", "", 0, fmt.Errorf("Pages 部署包不能超过 %d MiB", pagesMaxDeploymentBytes/1024/1024) + return "", "", 0, fmt.Errorf("pages 部署包不能超过 %d MiB", pagesMaxDeploymentBytes/bytesPerKiB/bytesPerKiB) } return temp.Name(), hex.EncodeToString(hash.Sum(nil)), written, nil } @@ -180,11 +184,11 @@ func ingestPagesDeploymentPackage( projectSlug string, fileName string, ) (upload.IngestResult, error) { - file, err := os.Open(tempPath) + file, err := os.Open(tempPath) //nolint:gosec // tempPath is a validated pages deployment staging file if err != nil { return upload.IngestResult{}, err } - defer file.Close() + defer func() { _ = file.Close() }() systemUser := repository.GetSystemUser(ctx) accessMode := 0 @@ -193,7 +197,7 @@ func ingestPagesDeploymentPackage( Reader: file, Size: size, FileName: fileName, - MimeType: "application/zip", + MimeType: mimeTypeApplicationZip, Extension: "zip", Hash: checksum, Type: pagesDeploymentUploadType, @@ -270,7 +274,7 @@ func inspectPagesZip(zipPath string, rootDir string, entryFile string) (*deploym if err != nil { return nil, errors.New(errPagesPackageInvalidZip) } - defer reader.Close() + defer func() { _ = reader.Close() }() commonPrefix, err := findCommonRootPrefix(reader.File) if err != nil { @@ -298,18 +302,18 @@ func inspectPagesZip(zipPath string, rootDir string, entryFile string) (*deploym normalizedPath = strings.TrimPrefix(normalizedPath, commonPrefix) } if item.FileInfo().Mode()&os.ModeSymlink != 0 { - return nil, fmt.Errorf("Pages 部署包不支持符号链接: %s", normalizedPath) + return nil, fmt.Errorf("pages 部署包不支持符号链接: %s", normalizedPath) } if item.UncompressedSize64 > pagesMaxDeploymentBytes { - return nil, fmt.Errorf("Pages 文件过大: %s", normalizedPath) + return nil, fmt.Errorf("pages 文件过大: %s", normalizedPath) } manifest.FileCount++ if manifest.FileCount > pagesMaxDeploymentFiles { - return nil, fmt.Errorf("Pages 部署文件数不能超过 %d", pagesMaxDeploymentFiles) + return nil, fmt.Errorf("pages 部署文件数不能超过 %d", pagesMaxDeploymentFiles) } manifest.TotalSize += int64(item.UncompressedSize64) if manifest.TotalSize > pagesMaxDeploymentBytes { - return nil, fmt.Errorf("Pages 部署展开后不能超过 %d MiB", pagesMaxDeploymentBytes/1024/1024) + return nil, fmt.Errorf("pages 部署展开后不能超过 %d MiB", pagesMaxDeploymentBytes/bytesPerKiB/bytesPerKiB) } checksum, err := checksumZipFile(item) if err != nil { @@ -328,7 +332,7 @@ func inspectPagesZip(zipPath string, rootDir string, entryFile string) (*deploym return nil, errors.New(errPagesPackageEmpty) } if !entrySeen { - return nil, fmt.Errorf("Pages 部署包缺少入口文件 %s", targetEntryPath) + return nil, fmt.Errorf("pages 部署包缺少入口文件 %s", targetEntryPath) } return manifest, nil } @@ -342,29 +346,38 @@ func normalizePagesZipPath(raw string) (string, bool, error) { return "", true, nil } if strings.HasPrefix(name, "/") || path.IsAbs(name) { - return "", false, fmt.Errorf("Pages 部署包不能包含绝对路径: %s", raw) + return "", false, fmt.Errorf("pages 部署包不能包含绝对路径: %s", raw) } cleaned := path.Clean(name) if cleaned == "." { return "", true, nil } if cleaned == ".." || strings.HasPrefix(cleaned, "../") || strings.Contains(cleaned, "/../") { - return "", false, fmt.Errorf("Pages 部署包路径不能逃逸目录: %s", raw) + return "", false, fmt.Errorf("pages 部署包路径不能逃逸目录: %s", raw) } return cleaned, false, nil } +func pagesZipEntryCopyLimit(size uint64) (int64, error) { + if size == 0 || size > pagesMaxDeploymentBytes || size > uint64(math.MaxInt64) { + return 0, errors.New("pages file size out of bounds") + } + return int64(size), nil //nolint:gosec // size is bounded to math.MaxInt64 above +} + func checksumZipFile(item *zip.File) (string, error) { file, err := item.Open() if err != nil { return "", err } - defer file.Close() + defer func() { _ = file.Close() }() hash := sha256.New() - if _, err = io.Copy(hash, file); err != nil { + limit, err := pagesZipEntryCopyLimit(item.UncompressedSize64) + if err != nil { + return "", err + } + if _, err = io.CopyN(hash, file, limit); err != nil { return "", err } return hex.EncodeToString(hash.Sum(nil)), nil } - - diff --git a/internal/apps/openflare/pages/logics.go b/internal/apps/openflare/pages/logics.go index 5e9e38d2..c5bb0cf9 100644 --- a/internal/apps/openflare/pages/logics.go +++ b/internal/apps/openflare/pages/logics.go @@ -256,7 +256,7 @@ func UploadDeployment(ctx context.Context, projectID uint, fileHeader *multipart if err != nil { return nil, err } - defer os.Remove(tempPath) + defer func() { _ = os.Remove(tempPath) }() manifest, err := inspectPagesZip(tempPath, rootDir, entryFile) if err != nil { return nil, err @@ -370,10 +370,10 @@ func OpenDeploymentPackage(ctx context.Context, deploymentID uint) (*storage.Obj } obj, err := uploadstorage.OpenStoredObject(ctx, &uploadRecord) if err != nil { - return nil, "", fmt.Errorf("Pages 部署包不存在: %w", err) + return nil, "", fmt.Errorf("pages 部署包不存在: %w", err) } if obj.ContentType == "" { - obj.ContentType = "application/zip" + obj.ContentType = mimeTypeApplicationZip } return obj, fileName, nil } @@ -382,17 +382,17 @@ func OpenDeploymentPackage(ctx context.Context, deploymentID uint) (*storage.Obj } file, err := os.Open(deployment.ArtifactPath) if err != nil { - return nil, "", fmt.Errorf("Pages 部署包不存在: %w", err) + return nil, "", fmt.Errorf("pages 部署包不存在: %w", err) } info, err := file.Stat() if err != nil { _ = file.Close() - return nil, "", fmt.Errorf("Pages 部署包不存在: %w", err) + return nil, "", fmt.Errorf("pages 部署包不存在: %w", err) } return &storage.Object{ Body: file, ContentLength: info.Size(), - ContentType: "application/zip", + ContentType: mimeTypeApplicationZip, }, fileName, nil } diff --git a/internal/apps/openflare/proxy_route/build_helpers.go b/internal/apps/openflare/proxy_route/build_helpers.go new file mode 100644 index 00000000..d275227e --- /dev/null +++ b/internal/apps/openflare/proxy_route/build_helpers.go @@ -0,0 +1,178 @@ +// Copyright 2026 Arctel.net +// SPDX-License-Identifier: Apache-2.0 + +// Package proxy_route provides helpers for building proxy route configurations. +package proxy_route + +import ( + "context" + "encoding/json" + "errors" + "strings" + + "github.com/Rain-kl/Wavelet/internal/model" +) + +type proxyRouteJSONFields struct { + cacheRulesJSON string + upstreamsJSON string + customHeadersJSON string + certIDsJSON string + domainCertIDsJSON string + domainsJSON string +} + +func resolveProxyRouteUpstreams(ctx context.Context, upstreamType string, input Input) (string, *uint, []string, error) { + switch upstreamType { + case proxyRouteUpstreamTypeTunnel, proxyRouteUpstreamTypePages: + if upstreamType == proxyRouteUpstreamTypePages { + if err := validatePagesRouteInput(ctx, input.PagesProjectID); err != nil { + return "", nil, nil, err + } + } + originURL := "http://127.0.0.1" + return originURL, nil, []string{originURL}, nil + default: + originURL, originID, err := resolveProxyRoutePrimaryOrigin(ctx, input) + if err != nil { + return "", nil, nil, err + } + upstreams, err := normalizeUpstreams(originURL, input.Upstreams) + if err != nil { + return "", nil, nil, err + } + return originURL, originID, upstreams, nil + } +} + +func marshalProxyRouteJSONFields( + domains []string, + upstreams []string, + cacheRules []string, + customHeaders []CustomHeaderInput, + certIDs []uint, + domainCertIDs []uint, +) (*proxyRouteJSONFields, error) { + cacheRulesJSON, err := json.Marshal(cacheRules) + if err != nil { + return nil, err + } + upstreamsJSON, err := json.Marshal(upstreams) + if err != nil { + return nil, err + } + customHeadersJSON, err := json.Marshal(customHeaders) + if err != nil { + return nil, err + } + certIDsJSON, err := json.Marshal(certIDs) + if err != nil { + return nil, err + } + domainCertIDsJSON, err := json.Marshal(domainCertIDs) + if err != nil { + return nil, err + } + domainsJSON, err := json.Marshal(domains) + if err != nil { + return nil, err + } + return &proxyRouteJSONFields{ + cacheRulesJSON: string(cacheRulesJSON), + upstreamsJSON: string(upstreamsJSON), + customHeadersJSON: string(customHeadersJSON), + certIDsJSON: string(certIDsJSON), + domainCertIDsJSON: string(domainCertIDsJSON), + domainsJSON: string(domainsJSON), + }, nil +} + +func normalizeProxyRouteHTTPSInput(input *Input) { + if input.EnableHTTPS { + return + } + input.RedirectHTTP = false + input.CertID = nil + input.CertIDs = nil + input.DomainCertIDs = nil +} + +func normalizeProxyRouteBasicAuth(input *Input) error { + if !input.BasicAuthEnabled { + input.BasicAuthUsername = "" + input.BasicAuthPassword = "" + return nil + } + input.BasicAuthUsername = strings.TrimSpace(input.BasicAuthUsername) + input.BasicAuthPassword = strings.TrimSpace(input.BasicAuthPassword) + if input.BasicAuthUsername == "" || input.BasicAuthPassword == "" { + return errors.New(errProxyRouteBasicAuth) + } + return nil +} + +func populateProxyRouteFields( + route *model.ProxyRoute, + input Input, + siteName, domain string, + jsonFields *proxyRouteJSONFields, + originID *uint, + upstreams []string, + originHost, remark, cachePolicy string, + limitConnPerServer, limitConnPerIP int, + limitRate, upstreamType string, +) { + route.SiteName = siteName + route.Domain = domain + route.Domains = jsonFields.domainsJSON + route.OriginID = originID + route.OriginURL = upstreams[0] + route.OriginHost = originHost + route.Upstreams = jsonFields.upstreamsJSON + route.Enabled = input.Enabled + route.EnableHTTPS = input.EnableHTTPS + route.CertID = input.CertID + route.CertIDs = jsonFields.certIDsJSON + route.DomainCertIDs = jsonFields.domainCertIDsJSON + route.RedirectHTTP = input.RedirectHTTP + route.LimitConnPerServer = limitConnPerServer + route.LimitConnPerIP = limitConnPerIP + route.LimitRate = limitRate + route.CacheEnabled = input.CacheEnabled + route.CachePolicy = normalizeCachePolicy(input.CacheEnabled, cachePolicy) + route.CacheRules = jsonFields.cacheRulesJSON + route.CustomHeaders = jsonFields.customHeadersJSON + route.BasicAuthEnabled = input.BasicAuthEnabled + route.BasicAuthUsername = input.BasicAuthUsername + route.BasicAuthPassword = input.BasicAuthPassword + route.Remark = remark + route.UpstreamType = upstreamType +} + +func applyProxyRouteUpstreamType(ctx context.Context, route *model.ProxyRoute, upstreamType string, input Input) error { + switch upstreamType { + case proxyRouteUpstreamTypeTunnel: + tunnelNodeID, err := normalizeTunnelNodeID(input.TunnelNodeID, input.TunnelID) + if err != nil { + return err + } + if err := validateTunnelRouteInput(ctx, tunnelNodeID, input.TunnelTargetAddr, input.TunnelTargetProtocol); err != nil { + return err + } + route.TunnelNodeID = tunnelNodeID + route.TunnelTargetAddr = strings.TrimSpace(input.TunnelTargetAddr) + route.TunnelTargetProtocol = normalizeTunnelTargetProtocol(input.TunnelTargetProtocol) + route.PagesProjectID = nil + case proxyRouteUpstreamTypePages: + route.TunnelNodeID = nil + route.TunnelTargetAddr = "" + route.TunnelTargetProtocol = "" + route.PagesProjectID = input.PagesProjectID + default: + route.TunnelNodeID = nil + route.TunnelTargetAddr = "" + route.TunnelTargetProtocol = "" + route.PagesProjectID = nil + } + return nil +} diff --git a/internal/apps/openflare/proxy_route/cert_helpers.go b/internal/apps/openflare/proxy_route/cert_helpers.go new file mode 100644 index 00000000..de505514 --- /dev/null +++ b/internal/apps/openflare/proxy_route/cert_helpers.go @@ -0,0 +1,71 @@ +// Copyright 2026 Arctel.net +// SPDX-License-Identifier: Apache-2.0 + +package proxy_route + +import ( + "context" + "errors" +) + +func normalizeExplicitDomainCertIDs(ctx context.Context, domains []string, rawDomainCertIDs []uint) ([]uint, []uint, *uint, error) { + if len(rawDomainCertIDs) != len(domains) { + return nil, nil, nil, errors.New(errProxyRouteCertDomainLength) + } + + normalizedDomainCertIDs := make([]uint, len(rawDomainCertIDs)) + uniqueCertIDs := make([]uint, 0, len(rawDomainCertIDs)) + seen := make(map[uint]struct{}, len(rawDomainCertIDs)) + hasAssignedCertificate := false + for index, item := range rawDomainCertIDs { + if item == 0 { + continue + } + if _, err := lookupTLSCertificateByID(ctx, item); err != nil { + return nil, nil, nil, errors.New(errProxyRouteCertNotFound) + } + normalizedDomainCertIDs[index] = item + hasAssignedCertificate = true + if _, ok := seen[item]; ok { + continue + } + seen[item] = struct{}{} + uniqueCertIDs = append(uniqueCertIDs, item) + } + if !hasAssignedCertificate { + return nil, nil, nil, errors.New(errProxyRouteCertRequired) + } + + primaryCertID := &uniqueCertIDs[0] + return normalizedDomainCertIDs, uniqueCertIDs, primaryCertID, nil +} + +func normalizeDerivedDomainCertIDs( + ctx context.Context, + domains []string, + normalizedCertIDs []uint, +) ([]uint, []uint, *uint, error) { + switch { + case len(normalizedCertIDs) == 0: + return nil, nil, nil, errors.New(errProxyRouteCertRequired) + case len(normalizedCertIDs) == 1: + domainCertIDs := make([]uint, len(domains)) + for index := range domainCertIDs { + domainCertIDs[index] = normalizedCertIDs[0] + } + primaryCertID := &normalizedCertIDs[0] + return domainCertIDs, normalizedCertIDs, primaryCertID, nil + case len(normalizedCertIDs) == len(domains): + domainCertIDs := make([]uint, len(normalizedCertIDs)) + copy(domainCertIDs, normalizedCertIDs) + primaryCertID := &normalizedCertIDs[0] + return domainCertIDs, normalizedCertIDs, primaryCertID, nil + default: + domainCertIDs, err := deriveDomainCertIDsFromCertificateSet(ctx, domains, normalizedCertIDs) + if err != nil { + return nil, nil, nil, err + } + primaryCertID := &normalizedCertIDs[0] + return domainCertIDs, normalizedCertIDs, primaryCertID, nil + } +} diff --git a/internal/apps/openflare/proxy_route/errs.go b/internal/apps/openflare/proxy_route/errs.go index e458bd87..b72b9579 100644 --- a/internal/apps/openflare/proxy_route/errs.go +++ b/internal/apps/openflare/proxy_route/errs.go @@ -42,9 +42,9 @@ const ( errProxyRouteTunnelAddrReq = "tunnel_target_addr is required for tunnel upstream" errProxyRouteTunnelProtocol = "tunnel_target_protocol must be http or https" errProxyRoutePagesProjectReq = "pages_project_id is required for Pages upstream" - errProxyRoutePagesNotFound = "Pages 项目不存在" - errProxyRoutePagesDisabled = "Pages 项目未启用" - errProxyRoutePagesNoDeploy = "Pages 项目没有激活部署" + errProxyRoutePagesNotFound = "pages 项目不存在" + errProxyRoutePagesDisabled = "pages 项目未启用" + errProxyRoutePagesNoDeploy = "pages 项目没有激活部署" errProxyRouteOriginSchemeOnly = "源站协议仅支持 http 或 https" errProxyRouteOriginPort = "端口格式不合法" errProxyRouteOriginPortEmpty = "端口不能为空" diff --git a/internal/apps/openflare/proxy_route/helpers.go b/internal/apps/openflare/proxy_route/helpers.go index 4abe3673..9fc704d1 100644 --- a/internal/apps/openflare/proxy_route/helpers.go +++ b/internal/apps/openflare/proxy_route/helpers.go @@ -30,6 +30,13 @@ const ( proxyRouteCachePolicySuffix = "suffix" proxyRouteCachePolicyPathPrefix = "path_prefix" proxyRouteCachePolicyPathExact = "path_exact" + proxyRouteSchemeHTTP = "http" + proxyRouteSchemeHTTPS = "https" + proxyRouteUpstreamTypeTunnel = "tunnel" + proxyRouteUpstreamTypePages = "pages" + + maxOriginHostnameLength = 253 + originURIPathQueryParts = 2 ) type tlsCertificateRow struct { @@ -100,7 +107,7 @@ func validateOriginAddress(address string) error { if ip := net.ParseIP(address); ip != nil { return nil } - if len(address) > 253 { + if len(address) > maxOriginHostnameLength { return errors.New(errProxyRouteOriginInvalid) } labels := strings.Split(address, ".") @@ -136,7 +143,7 @@ func normalizeOriginPort(raw string) (string, error) { func normalizeOriginScheme(raw string) (string, error) { scheme := strings.ToLower(strings.TrimSpace(raw)) switch scheme { - case "http", "https": + case proxyRouteSchemeHTTP, proxyRouteSchemeHTTPS: return scheme, nil default: return "", errors.New(errProxyRouteOriginSchemeOnly) @@ -187,7 +194,7 @@ func buildOriginURLFromParts(scheme, address, port, uri string) (string, error) if strings.HasPrefix(normalizedURI, "?") { parsed.RawQuery = strings.TrimPrefix(normalizedURI, "?") } else { - pathQuery := strings.SplitN(normalizedURI, "?", 2) + pathQuery := strings.SplitN(normalizedURI, "?", originURIPathQueryParts) parsed.Path = pathQuery[0] if len(pathQuery) > 1 { parsed.RawQuery = pathQuery[1] @@ -465,65 +472,14 @@ func normalizeProxyRouteDomainCertificateIDs( } if len(rawDomainCertIDs) > 0 { - if len(rawDomainCertIDs) != len(domains) { - return nil, nil, nil, errors.New(errProxyRouteCertDomainLength) - } - - normalizedDomainCertIDs := make([]uint, len(rawDomainCertIDs)) - uniqueCertIDs := make([]uint, 0, len(rawDomainCertIDs)) - seen := make(map[uint]struct{}, len(rawDomainCertIDs)) - hasAssignedCertificate := false - for index, item := range rawDomainCertIDs { - if item == 0 { - continue - } - if _, err := lookupTLSCertificateByID(ctx, item); err != nil { - return nil, nil, nil, errors.New(errProxyRouteCertNotFound) - } - normalizedDomainCertIDs[index] = item - hasAssignedCertificate = true - if _, ok := seen[item]; ok { - continue - } - seen[item] = struct{}{} - uniqueCertIDs = append(uniqueCertIDs, item) - } - if !hasAssignedCertificate { - return nil, nil, nil, errors.New(errProxyRouteCertRequired) - } - - primaryCertID := &uniqueCertIDs[0] - return normalizedDomainCertIDs, uniqueCertIDs, primaryCertID, nil + return normalizeExplicitDomainCertIDs(ctx, domains, rawDomainCertIDs) } normalizedCertIDs, err := normalizeProxyRouteCertificateIDs(ctx, enableHTTPS, certID, certIDs) if err != nil { return nil, nil, nil, err } - - switch { - case len(normalizedCertIDs) == 0: - return nil, nil, nil, errors.New(errProxyRouteCertRequired) - case len(normalizedCertIDs) == 1: - domainCertIDs := make([]uint, len(domains)) - for index := range domainCertIDs { - domainCertIDs[index] = normalizedCertIDs[0] - } - primaryCertID := &normalizedCertIDs[0] - return domainCertIDs, normalizedCertIDs, primaryCertID, nil - case len(normalizedCertIDs) == len(domains): - domainCertIDs := make([]uint, len(normalizedCertIDs)) - copy(domainCertIDs, normalizedCertIDs) - primaryCertID := &normalizedCertIDs[0] - return domainCertIDs, normalizedCertIDs, primaryCertID, nil - default: - domainCertIDs, err := deriveDomainCertIDsFromCertificateSet(ctx, domains, normalizedCertIDs) - if err != nil { - return nil, nil, nil, err - } - primaryCertID := &normalizedCertIDs[0] - return domainCertIDs, normalizedCertIDs, primaryCertID, nil - } + return normalizeDerivedDomainCertIDs(ctx, domains, normalizedCertIDs) } func validateProxyRouteDomainCertificateCoverage(ctx context.Context, domains []string, domainCertIDs []uint) error { @@ -637,65 +593,6 @@ func hasStructuredOriginInput(input Input) bool { strings.TrimSpace(input.OriginURI) != "" } -func resolveProxyRoutePrimaryOrigin(ctx context.Context, input Input) (string, *uint, error) { - if hasStructuredOriginInput(input) { - scheme, err := normalizeOriginScheme(input.OriginScheme) - if err != nil { - return "", nil, err - } - port, err := normalizeOriginPort(input.OriginPort) - if err != nil { - return "", nil, err - } - uri, err := normalizeOriginURI(input.OriginURI) - if err != nil { - return "", nil, err - } - if input.OriginID != nil && *input.OriginID != 0 { - origin, err := model.GetOriginByID(ctx, *input.OriginID) - if err != nil { - return "", nil, errors.New(errProxyRouteOriginNotFound) - } - originURL, err := buildOriginURLFromParts(scheme, origin.Address, port, uri) - if err != nil { - return "", nil, err - } - return originURL, &origin.ID, nil - } - - address := normalizeOriginAddress(input.OriginAddress) - if err := validateOriginAddress(address); err != nil { - return "", nil, err - } - originURL, err := buildOriginURLFromParts(scheme, address, port, uri) - if err != nil { - return "", nil, err - } - origin, err := getOrCreateOriginByAddress(ctx, address) - if err != nil { - return "", nil, err - } - return originURL, &origin.ID, nil - } - - originURL := strings.TrimSpace(input.OriginURL) - if originURL == "" { - return "", nil, errors.New(errProxyRouteOriginEmpty) - } - address, err := extractOriginAddress(originURL) - if err != nil { - return "", nil, err - } - origin, findErr := model.GetOriginByAddress(ctx, address) - if findErr == nil { - return originURL, &origin.ID, nil - } - if !errors.Is(findErr, gorm.ErrRecordNotFound) { - return "", nil, findErr - } - return originURL, nil, nil -} - func normalizeCustomHeaders(headers []CustomHeaderInput) ([]CustomHeaderInput, error) { if len(headers) == 0 { return []CustomHeaderInput{}, nil @@ -942,7 +839,7 @@ func validateOriginURL(raw string) error { if err != nil { return errors.New(errProxyRouteOriginInvalid) } - if parsed.Scheme != "http" && parsed.Scheme != "https" { + if parsed.Scheme != proxyRouteSchemeHTTP && parsed.Scheme != proxyRouteSchemeHTTPS { return errors.New(errProxyRouteOriginScheme) } if parsed.Host == "" { @@ -996,7 +893,7 @@ func validateTunnelRouteInput(ctx context.Context, tunnelNodeID *uint, targetAdd return errors.New(errProxyRouteTunnelAddrReq) } switch strings.ToLower(strings.TrimSpace(targetProtocol)) { - case "", "http", "https": + case "", proxyRouteSchemeHTTP, proxyRouteSchemeHTTPS: return nil default: return errors.New(errProxyRouteTunnelProtocol) @@ -1025,10 +922,10 @@ func validatePagesRouteInput(ctx context.Context, projectID *uint) error { func normalizeUpstreamType(raw string) string { switch strings.ToLower(strings.TrimSpace(raw)) { - case "tunnel": - return "tunnel" - case "pages": - return "pages" + case proxyRouteUpstreamTypeTunnel: + return proxyRouteUpstreamTypeTunnel + case proxyRouteUpstreamTypePages: + return proxyRouteUpstreamTypePages default: return "direct" } @@ -1036,9 +933,9 @@ func normalizeUpstreamType(raw string) string { func normalizeTunnelTargetProtocol(raw string) string { switch strings.ToLower(strings.TrimSpace(raw)) { - case "https": - return "https" + case proxyRouteSchemeHTTPS: + return proxyRouteSchemeHTTPS default: - return "http" + return proxyRouteSchemeHTTP } } diff --git a/internal/apps/openflare/proxy_route/logics.go b/internal/apps/openflare/proxy_route/logics.go index dff97981..01037a78 100644 --- a/internal/apps/openflare/proxy_route/logics.go +++ b/internal/apps/openflare/proxy_route/logics.go @@ -5,7 +5,6 @@ package proxy_route import ( "context" - "encoding/json" "errors" "strings" "time" @@ -168,28 +167,9 @@ func buildProxyRoute(ctx context.Context, route *model.ProxyRoute, input Input) siteName := normalizeProxyRouteSiteNameInput(route, input.SiteName, domain) upstreamType := normalizeUpstreamType(input.UpstreamType) - var originURL string - var originID *uint - var upstreams []string - - if upstreamType == "tunnel" { - originURL = "http://127.0.0.1" - upstreams = []string{originURL} - } else if upstreamType == "pages" { - if err := validatePagesRouteInput(ctx, input.PagesProjectID); err != nil { - return nil, err - } - originURL = "http://127.0.0.1" - upstreams = []string{originURL} - } else { - originURL, originID, err = resolveProxyRoutePrimaryOrigin(ctx, input) - if err != nil { - return nil, err - } - upstreams, err = normalizeUpstreams(originURL, input.Upstreams) - if err != nil { - return nil, err - } + _, originID, upstreams, err := resolveProxyRouteUpstreams(ctx, upstreamType, input) + if err != nil { + return nil, err } originHost := strings.TrimSpace(input.OriginHost) remark := strings.TrimSpace(input.Remark) @@ -215,25 +195,7 @@ func buildProxyRoute(ctx context.Context, route *model.ProxyRoute, input Input) return nil, err } - cacheRulesJSON, err := json.Marshal(cacheRules) - if err != nil { - return nil, err - } - upstreamsJSON, err := json.Marshal(upstreams) - if err != nil { - return nil, err - } - customHeadersJSON, err := json.Marshal(customHeaders) - if err != nil { - return nil, err - } - - if !input.EnableHTTPS { - input.RedirectHTTP = false - input.CertID = nil - input.CertIDs = nil - input.DomainCertIDs = nil - } + normalizeProxyRouteHTTPSInput(&input) domainCertIDs, certIDs, primaryCertID, err := normalizeProxyRouteDomainCertificateIDs( ctx, domains, @@ -248,15 +210,7 @@ func buildProxyRoute(ctx context.Context, route *model.ProxyRoute, input Input) if err := validateProxyRouteDomainCertificateCoverage(ctx, domains, domainCertIDs); err != nil { return nil, err } - certIDsJSON, err := json.Marshal(certIDs) - if err != nil { - return nil, err - } - domainCertIDsJSON, err := json.Marshal(domainCertIDs) - if err != nil { - return nil, err - } - domainsJSON, err := json.Marshal(domains) + jsonFields, err := marshalProxyRouteJSONFields(domains, upstreams, cacheRules, customHeaders, certIDs, domainCertIDs) if err != nil { return nil, err } @@ -277,67 +231,31 @@ func buildProxyRoute(ctx context.Context, route *model.ProxyRoute, input Input) return nil, errors.New(errProxyRouteRedirectHTTP) } - if input.BasicAuthEnabled { - input.BasicAuthUsername = strings.TrimSpace(input.BasicAuthUsername) - input.BasicAuthPassword = strings.TrimSpace(input.BasicAuthPassword) - if input.BasicAuthUsername == "" || input.BasicAuthPassword == "" { - return nil, errors.New(errProxyRouteBasicAuth) - } - } else { - input.BasicAuthUsername = "" - input.BasicAuthPassword = "" + if err := normalizeProxyRouteBasicAuth(&input); err != nil { + return nil, err } if route == nil { route = &model.ProxyRoute{} } - route.SiteName = siteName - route.Domain = domain - route.Domains = string(domainsJSON) - route.OriginID = originID - route.OriginURL = upstreams[0] - route.OriginHost = originHost - route.Upstreams = string(upstreamsJSON) - route.Enabled = input.Enabled - route.EnableHTTPS = input.EnableHTTPS - route.CertID = input.CertID - route.CertIDs = string(certIDsJSON) - route.DomainCertIDs = string(domainCertIDsJSON) - route.RedirectHTTP = input.RedirectHTTP - route.LimitConnPerServer = limitConnPerServer - route.LimitConnPerIP = limitConnPerIP - route.LimitRate = limitRate - route.CacheEnabled = input.CacheEnabled - route.CachePolicy = normalizeCachePolicy(input.CacheEnabled, cachePolicy) - route.CacheRules = string(cacheRulesJSON) - route.CustomHeaders = string(customHeadersJSON) - route.BasicAuthEnabled = input.BasicAuthEnabled - route.BasicAuthUsername = input.BasicAuthUsername - route.BasicAuthPassword = input.BasicAuthPassword - route.Remark = remark - route.UpstreamType = upstreamType - if upstreamType == "tunnel" { - tunnelNodeID, err := normalizeTunnelNodeID(input.TunnelNodeID, input.TunnelID) - if err != nil { - return nil, err - } - if err := validateTunnelRouteInput(ctx, tunnelNodeID, input.TunnelTargetAddr, input.TunnelTargetProtocol); err != nil { - return nil, err - } - route.TunnelNodeID = tunnelNodeID - route.TunnelTargetAddr = strings.TrimSpace(input.TunnelTargetAddr) - route.TunnelTargetProtocol = normalizeTunnelTargetProtocol(input.TunnelTargetProtocol) - route.PagesProjectID = nil - } else if upstreamType == "pages" { - route.TunnelNodeID = nil - route.TunnelTargetAddr = "" - route.TunnelTargetProtocol = "" - route.PagesProjectID = input.PagesProjectID - } else { - route.TunnelNodeID = nil - route.TunnelTargetAddr = "" - route.TunnelTargetProtocol = "" - route.PagesProjectID = nil + populateProxyRouteFields( + route, + input, + siteName, + domain, + jsonFields, + originID, + upstreams, + originHost, + remark, + cachePolicy, + limitConnPerServer, + limitConnPerIP, + limitRate, + upstreamType, + ) + if err := applyProxyRouteUpstreamType(ctx, route, upstreamType, input); err != nil { + return nil, err } return route, nil } diff --git a/internal/apps/openflare/proxy_route/origin_helpers.go b/internal/apps/openflare/proxy_route/origin_helpers.go new file mode 100644 index 00000000..c0899ac6 --- /dev/null +++ b/internal/apps/openflare/proxy_route/origin_helpers.go @@ -0,0 +1,85 @@ +// Copyright 2026 Arctel.net +// SPDX-License-Identifier: Apache-2.0 + +package proxy_route + +import ( + "context" + "errors" + "strings" + + "github.com/Rain-kl/Wavelet/internal/model" + "gorm.io/gorm" +) + +func resolveStructuredOriginInput(ctx context.Context, input Input) (string, *uint, error) { + scheme, err := normalizeOriginScheme(input.OriginScheme) + if err != nil { + return "", nil, err + } + port, err := normalizeOriginPort(input.OriginPort) + if err != nil { + return "", nil, err + } + uri, err := normalizeOriginURI(input.OriginURI) + if err != nil { + return "", nil, err + } + if input.OriginID != nil && *input.OriginID != 0 { + return resolveOriginByID(ctx, scheme, port, uri, *input.OriginID) + } + return resolveOriginByAddress(ctx, scheme, port, uri, input.OriginAddress) +} + +func resolveOriginByID(ctx context.Context, scheme, port, uri string, originID uint) (string, *uint, error) { + origin, err := model.GetOriginByID(ctx, originID) + if err != nil { + return "", nil, errors.New(errProxyRouteOriginNotFound) + } + originURL, err := buildOriginURLFromParts(scheme, origin.Address, port, uri) + if err != nil { + return "", nil, err + } + return originURL, &origin.ID, nil +} + +func resolveOriginByAddress(ctx context.Context, scheme, port, uri, rawAddress string) (string, *uint, error) { + address := normalizeOriginAddress(rawAddress) + if err := validateOriginAddress(address); err != nil { + return "", nil, err + } + originURL, err := buildOriginURLFromParts(scheme, address, port, uri) + if err != nil { + return "", nil, err + } + origin, err := getOrCreateOriginByAddress(ctx, address) + if err != nil { + return "", nil, err + } + return originURL, &origin.ID, nil +} + +func resolveLegacyOriginInput(ctx context.Context, originURL string) (string, *uint, error) { + if originURL == "" { + return "", nil, errors.New(errProxyRouteOriginEmpty) + } + address, err := extractOriginAddress(originURL) + if err != nil { + return "", nil, err + } + origin, findErr := model.GetOriginByAddress(ctx, address) + if findErr == nil { + return originURL, &origin.ID, nil + } + if !errors.Is(findErr, gorm.ErrRecordNotFound) { + return "", nil, findErr + } + return originURL, nil, nil +} + +func resolveProxyRoutePrimaryOrigin(ctx context.Context, input Input) (string, *uint, error) { + if hasStructuredOriginInput(input) { + return resolveStructuredOriginInput(ctx, input) + } + return resolveLegacyOriginInput(ctx, strings.TrimSpace(input.OriginURL)) +} diff --git a/internal/apps/openflare/relay/errs.go b/internal/apps/openflare/relay/errs.go index 3786f403..230240ec 100644 --- a/internal/apps/openflare/relay/errs.go +++ b/internal/apps/openflare/relay/errs.go @@ -1,9 +1,11 @@ // Copyright 2026 Arctel.net // SPDX-License-Identifier: Apache-2.0 +// Package relay provides relay node management and authentication for the OpenFlare platform. package relay const ( + //nolint:gosec // error message text, not a credential errAgentTokenInvalid = "无权进行此操作,Agent Token 无效" errRelayNodeTypeMismatch = "此节点不是 TunnelRelay 类型" ) diff --git a/internal/apps/openflare/relay/helpers.go b/internal/apps/openflare/relay/helpers.go index cb276640..49fe80e4 100644 --- a/internal/apps/openflare/relay/helpers.go +++ b/internal/apps/openflare/relay/helpers.go @@ -10,12 +10,17 @@ import ( "github.com/Rain-kl/Wavelet/internal/model" ) +const ( + relayStatusUnhealthy = "unhealthy" + releaseChannelStable = "stable" +) + func normalizeRelayStatus(status string) string { switch strings.ToLower(strings.TrimSpace(status)) { case "healthy": return "healthy" - case "unhealthy": - return "unhealthy" + case relayStatusUnhealthy: + return relayStatusUnhealthy default: return "unknown" } @@ -25,7 +30,7 @@ func normalizeReleaseChannel(channel string) string { if strings.ToLower(strings.TrimSpace(channel)) == "preview" { return "preview" } - return "stable" + return releaseChannelStable } func resolveReportedNodeIP(reportedIP string, remoteAddr string) string { @@ -98,7 +103,7 @@ func BuildSettings(node *model.OpenFlareNode, updateNow bool, updateChannel, upd autoUpdate = node.AutoUpdateEnabled } if strings.TrimSpace(updateChannel) == "" { - updateChannel = "stable" + updateChannel = releaseChannelStable } return &Settings{ HeartbeatInterval: model.AgentHeartbeatInterval, diff --git a/internal/apps/openflare/relay/logics.go b/internal/apps/openflare/relay/logics.go index 2935c55c..0b728eb7 100644 --- a/internal/apps/openflare/relay/logics.go +++ b/internal/apps/openflare/relay/logics.go @@ -41,7 +41,7 @@ func Heartbeat(ctx context.Context, node *model.OpenFlareNode, payload Heartbeat "last_seen_at": now, "status": nodeStatusOnline, "update_requested": false, - "update_channel": "stable", + "update_channel": releaseChannelStable, "update_tag": "", } if payload.Name != "" && strings.TrimSpace(node.Name) == "" { @@ -55,7 +55,7 @@ func Heartbeat(ctx context.Context, node *model.OpenFlareNode, payload Heartbeat if !previous.UpdateRequested { delete(changes, "update_requested") } - if previous.UpdateChannel == "stable" { + if previous.UpdateChannel == releaseChannelStable { delete(changes, "update_channel") } if previous.UpdateTag == "" { @@ -66,7 +66,7 @@ func Heartbeat(ctx context.Context, node *model.OpenFlareNode, payload Heartbeat node.ExtVersion = payload.ExtVersion node.RelayStatus = payload.RelayStatus node.UpdateRequested = false - node.UpdateChannel = "stable" + node.UpdateChannel = releaseChannelStable node.UpdateTag = "" lastSeen := now node.LastSeenAt = &lastSeen diff --git a/internal/apps/openflare/relay/middleware.go b/internal/apps/openflare/relay/middleware.go index 7af89005..69c449fb 100644 --- a/internal/apps/openflare/relay/middleware.go +++ b/internal/apps/openflare/relay/middleware.go @@ -16,8 +16,8 @@ import ( const ctxRelayNodeKey = "relay_node" -// RelayAuth authenticates relay requests using X-Agent-Token and verifies tunnel_relay type. -func RelayAuth() gin.HandlerFunc { +// Auth authenticates relay requests using X-Agent-Token and verifies tunnel_relay type. +func Auth() gin.HandlerFunc { return func(c *gin.Context) { token := strings.TrimSpace(c.GetHeader("X-Agent-Token")) node, err := authenticateAccessToken(c.Request.Context(), token) @@ -46,4 +46,4 @@ func authenticateAccessToken(ctx context.Context, token string) (*model.OpenFlar return nil, err } return node, nil -} \ No newline at end of file +} diff --git a/internal/apps/openflare/relay/middleware_test.go b/internal/apps/openflare/relay/middleware_test.go index 163e0ec1..58298fb3 100644 --- a/internal/apps/openflare/relay/middleware_test.go +++ b/internal/apps/openflare/relay/middleware_test.go @@ -55,7 +55,7 @@ func TestRelayAuthMissingToken(t *testing.T) { gin.SetMode(gin.TestMode) engine := gin.New() engine.Use(response.ErrorHandlerMiddleware()) - engine.GET("/relay/test", RelayAuth(), func(c *gin.Context) { + engine.GET("/relay/test", Auth(), func(c *gin.Context) { c.Status(http.StatusOK) }) @@ -74,7 +74,7 @@ func TestRelayAuthRejectsWrongNodeType(t *testing.T) { gin.SetMode(gin.TestMode) engine := gin.New() engine.Use(response.ErrorHandlerMiddleware()) - engine.GET("/relay/test", RelayAuth(), func(c *gin.Context) { + engine.GET("/relay/test", Auth(), func(c *gin.Context) { c.Status(http.StatusOK) }) @@ -93,7 +93,7 @@ func TestRelayAuthAcceptsTunnelRelay(t *testing.T) { gin.SetMode(gin.TestMode) engine := gin.New() - engine.GET("/relay/test", RelayAuth(), func(c *gin.Context) { + engine.GET("/relay/test", Auth(), func(c *gin.Context) { authNode, ok := c.Get(ctxRelayNodeKey) require.True(t, ok) assert.Equal(t, node.NodeID, authNode.(*model.OpenFlareNode).NodeID) diff --git a/internal/apps/openflare/relay/observability.go b/internal/apps/openflare/relay/observability.go index 2c188514..92f15efb 100644 --- a/internal/apps/openflare/relay/observability.go +++ b/internal/apps/openflare/relay/observability.go @@ -24,7 +24,7 @@ func reconcileRelayHealthEvents(ctx context.Context, nodeID string, relayStatus relayFrpsUnhealthyEventType: {}, } events := []agent.NodeHealthEvent{} - if relayStatus == "unhealthy" { + if relayStatus == relayStatusUnhealthy { events = append(events, agent.NodeHealthEvent{ EventType: relayFrpsUnhealthyEventType, Severity: "critical", diff --git a/internal/apps/openflare/relay/protocol_alias.go b/internal/apps/openflare/relay/protocol_alias.go index c130cd3a..b801ad9d 100644 --- a/internal/apps/openflare/relay/protocol_alias.go +++ b/internal/apps/openflare/relay/protocol_alias.go @@ -5,8 +5,17 @@ package relay import pkgprotocol "github.com/Rain-kl/Wavelet/pkg/protocol" +// ProxyStat is an alias for protocol.RelayProxyStat. type ProxyStat = pkgprotocol.RelayProxyStat + +// HeartbeatPayload is an alias for protocol.RelayHeartbeatPayload. type HeartbeatPayload = pkgprotocol.RelayHeartbeatPayload + +// Config is an alias for protocol.RelayConfig. type Config = pkgprotocol.RelayConfig + +// Settings is an alias for protocol.RelaySettings. type Settings = pkgprotocol.RelaySettings -type HeartbeatResponse = pkgprotocol.RelayHeartbeatResponse \ No newline at end of file + +// HeartbeatResponse is an alias for protocol.RelayHeartbeatResponse. +type HeartbeatResponse = pkgprotocol.RelayHeartbeatResponse diff --git a/internal/apps/openflare/tasks/database_cleanup.go b/internal/apps/openflare/tasks/database_cleanup.go index 18cc8007..34a02b6c 100644 --- a/internal/apps/openflare/tasks/database_cleanup.go +++ b/internal/apps/openflare/tasks/database_cleanup.go @@ -90,7 +90,7 @@ func CleanupDatabaseObservability(ctx context.Context, input DatabaseCleanupInpu } // RunDatabaseAutoCleanupOnce runs retention-based cleanup for all observability targets. -func RunDatabaseAutoCleanupOnce(now time.Time) (*DatabaseAutoCleanupSummary, error) { +func RunDatabaseAutoCleanupOnce(ctx context.Context, now time.Time) (*DatabaseAutoCleanupSummary, error) { if !model.DatabaseAutoCleanupEnabled { return nil, nil } @@ -99,7 +99,6 @@ func RunDatabaseAutoCleanupOnce(now time.Time) (*DatabaseAutoCleanupSummary, err } retentionDays := model.DatabaseAutoCleanupRetentionDays - ctx := context.Background() results := make([]DatabaseCleanupResult, 0, len(databaseCleanupTargets)) for _, target := range []string{ DatabaseCleanupTargetAccessLogs, diff --git a/internal/apps/openflare/tasks/database_cleanup_test.go b/internal/apps/openflare/tasks/database_cleanup_test.go index 3c899849..cfaf405e 100644 --- a/internal/apps/openflare/tasks/database_cleanup_test.go +++ b/internal/apps/openflare/tasks/database_cleanup_test.go @@ -134,7 +134,7 @@ func TestRunDatabaseAutoCleanupOnceDeletesAllObservabilityTargets(t *testing.T) model.DatabaseAutoCleanupRetentionDays = previousRetentionDays }) - summary, err := RunDatabaseAutoCleanupOnce(now) + summary, err := RunDatabaseAutoCleanupOnce(ctx, now) require.NoError(t, err) require.NotNil(t, summary) require.Len(t, summary.Results, 3) diff --git a/internal/apps/openflare/tls/acme/client.go b/internal/apps/openflare/tls/acme/client.go index c3c06eeb..7b1487e4 100644 --- a/internal/apps/openflare/tls/acme/client.go +++ b/internal/apps/openflare/tls/acme/client.go @@ -1,6 +1,7 @@ // Copyright 2026 Arctel.net // SPDX-License-Identifier: Apache-2.0 +// Package acme implements ACME certificate issuance and renewal. package acme import ( @@ -25,22 +26,27 @@ import ( "github.com/go-acme/lego/v4/registration" ) -// AcmeUser implements lego's user interface. -type AcmeUser struct { +const dnsChallengePrecheckDelay = 20 * time.Second + +// User implements lego's registration.User interface for ACME account management. +type User struct { Email string Registration *registration.Resource key crypto.PrivateKey } -func (u *AcmeUser) GetEmail() string { +// GetEmail returns the email address associated with this ACME account. +func (u *User) GetEmail() string { return u.Email } -func (u *AcmeUser) GetRegistration() *registration.Resource { +// GetRegistration returns the ACME account registration resource. +func (u *User) GetRegistration() *registration.Resource { return u.Registration } -func (u *AcmeUser) GetPrivateKey() crypto.PrivateKey { +// GetPrivateKey returns the private key used to authenticate with the ACME server. +func (u *User) GetPrivateKey() crypto.PrivateKey { return u.key } @@ -88,7 +94,7 @@ func encodePrivateKey(key crypto.PrivateKey) (string, error) { } // GetOrCreateLegoClient returns a configured lego client and optional new account credentials. -func GetOrCreateLegoClient(acmeEmail, privateKeyPEM, accountURL string, keyAlgorithm string) (*lego.Client, *AcmeUser, string, string, error) { +func GetOrCreateLegoClient(acmeEmail, privateKeyPEM, accountURL string, keyAlgorithm string) (*lego.Client, *User, string, string, error) { var privateKey crypto.PrivateKey var err error var newPrivateKeyPEM string @@ -111,7 +117,7 @@ func GetOrCreateLegoClient(acmeEmail, privateKeyPEM, accountURL string, keyAlgor } } - user := &AcmeUser{ + user := &User{ Email: acmeEmail, key: privateKey, } @@ -197,12 +203,12 @@ func SetupDNSProvider(client *lego.Client, dnsType, dnsAuth string, dns1, dns2 s } if disableCNAME { - opts = append(opts, dns01.DisableCompletePropagationRequirement()) + opts = append(opts, dns01.DisableAuthoritativeNssPropagationRequirement()) } if skipDNS { - opts = append(opts, dns01.WrapPreCheck(func(domain, fqdn, value string, check dns01.PreCheckFunc) (bool, error) { - time.Sleep(20 * time.Second) + opts = append(opts, dns01.WrapPreCheck(func(_, _, _ string, _ dns01.PreCheckFunc) (bool, error) { + time.Sleep(dnsChallengePrecheckDelay) return true, nil })) } diff --git a/internal/apps/openflare/tls/acme_obtain_test.go b/internal/apps/openflare/tls/acme_obtain_test.go index 53d59b23..93bd7b85 100644 --- a/internal/apps/openflare/tls/acme_obtain_test.go +++ b/internal/apps/openflare/tls/acme_obtain_test.go @@ -58,7 +58,7 @@ func TestApplyCertificateReturnsApplying(t *testing.T) { Name: "Test ACME Cert", PrimaryDomain: "example.com", OtherDomains: "*.example.com", - DnsAccountID: dnsAccount.ID, + DNSAccountID: dnsAccount.ID, KeyAlgorithm: "RSA2048", AutoRenew: true, }) diff --git a/internal/apps/openflare/tls/errs.go b/internal/apps/openflare/tls/errs.go index d90d93b4..17df3709 100644 --- a/internal/apps/openflare/tls/errs.go +++ b/internal/apps/openflare/tls/errs.go @@ -1,6 +1,7 @@ // Copyright 2026 Arctel.net // SPDX-License-Identifier: Apache-2.0 +// Package tls defines shared error messages for certificate management. package tls const ( diff --git a/internal/apps/openflare/tls/helpers.go b/internal/apps/openflare/tls/helpers.go index 98a7f056..839aa8c2 100644 --- a/internal/apps/openflare/tls/helpers.go +++ b/internal/apps/openflare/tls/helpers.go @@ -31,7 +31,7 @@ func readMultipartFile(fileHeader *multipart.FileHeader) (string, error) { if err != nil { return "", err } - defer file.Close() + defer func() { _ = file.Close() }() data, err := io.ReadAll(file) if err != nil { return "", err diff --git a/internal/apps/openflare/tls/logics.go b/internal/apps/openflare/tls/logics.go index 7c32c2ff..61842687 100644 --- a/internal/apps/openflare/tls/logics.go +++ b/internal/apps/openflare/tls/logics.go @@ -32,7 +32,7 @@ type CertificateContent struct { Remark string `json:"remark"` Provider string `json:"provider"` AcmeAccountID uint `json:"acme_account_id"` - DnsAccountID uint `json:"dns_account_id"` + DNSAccountID uint `json:"dns_account_id"` KeyAlgorithm string `json:"key_algorithm"` AutoRenew bool `json:"auto_renew"` PrimaryDomain string `json:"primary_domain"` @@ -50,7 +50,7 @@ type ApplyInput struct { Name string `json:"name"` Remark string `json:"remark"` AcmeAccountID uint `json:"acme_account_id"` - DnsAccountID uint `json:"dns_account_id"` + DNSAccountID uint `json:"dns_account_id"` KeyAlgorithm string `json:"key_algorithm"` AutoRenew bool `json:"auto_renew"` PrimaryDomain string `json:"primary_domain"` @@ -96,7 +96,7 @@ func GetCertificateContent(ctx context.Context, id uint) (*CertificateContent, e Remark: certificate.Remark, Provider: certificate.Provider, AcmeAccountID: certificate.AcmeAccountID, - DnsAccountID: certificate.DnsAccountID, + DNSAccountID: certificate.DNSAccountID, KeyAlgorithm: certificate.KeyAlgorithm, AutoRenew: certificate.AutoRenew, PrimaryDomain: certificate.PrimaryDomain, @@ -179,7 +179,7 @@ func DeleteCertificate(ctx context.Context, id uint) error { // ApplyCertificate 申请 ACME 证书。 func ApplyCertificate(ctx context.Context, input ApplyInput) (*model.TLSCertificate, error) { cert := &model.TLSCertificate{ - Provider: "acme", + Provider: tlsProviderACME, CertPEM: " ", KeyPEM: " ", } @@ -195,7 +195,8 @@ func ApplyCertificate(ctx context.Context, input ApplyInput) (*model.TLSCertific } go func(c *model.TLSCertificate) { - _ = obtainTLSCertificate(context.Background(), c) + asyncCtx := context.WithoutCancel(ctx) + _ = obtainTLSCertificate(asyncCtx, c) }(cert) return sanitizeCertificateForResponse(cert), nil @@ -207,7 +208,7 @@ func UpdateACMECertificate(ctx context.Context, id uint, input ApplyInput) (*mod if err != nil { return nil, err } - if cert.Provider != "acme" { + if cert.Provider != tlsProviderACME { return nil, errors.New(errCertificateOnlyACME) } fillAcmeCertificateFields(cert, input) @@ -222,7 +223,8 @@ func UpdateACMECertificate(ctx context.Context, id uint, input ApplyInput) (*mod } go func(c *model.TLSCertificate) { - _ = obtainTLSCertificate(context.Background(), c) + asyncCtx := context.WithoutCancel(ctx) + _ = obtainTLSCertificate(asyncCtx, c) }(cert) return sanitizeCertificateForResponse(cert), nil @@ -237,7 +239,7 @@ func ConvertCertificateToACME(ctx context.Context, id uint, input ApplyInput) (* if cert.Provider != "upload" { return nil, errors.New(errCertificateOnlyUploadConvert) } - if cert.ApplyStatus == "applying" { + if cert.ApplyStatus == tlsApplyStatusApplying { return nil, errors.New(errCertificateAlreadyApplying) } fillAcmeCertificateFields(cert, input) @@ -253,17 +255,18 @@ func ConvertCertificateToACME(ctx context.Context, id uint, input ApplyInput) (* } go func(c *model.TLSCertificate) { - if err := obtainTLSCertificate(context.Background(), c); err != nil { + asyncCtx := context.WithoutCancel(ctx) + if err := obtainTLSCertificate(asyncCtx, c); err != nil { return } - latest, err := model.GetTLSCertificateByID(context.Background(), c.ID) + latest, err := model.GetTLSCertificateByID(asyncCtx, c.ID) if err != nil { return } - latest.Provider = "acme" - latest.ApplyStatus = "ready" + latest.Provider = tlsProviderACME + latest.ApplyStatus = tlsApplyStatusReady latest.ApplyMessage = "" - _ = model.SaveTLSCertificate(context.Background(), latest) + _ = model.SaveTLSCertificate(asyncCtx, latest) }(cert) return sanitizeCertificateForResponse(cert), nil @@ -275,15 +278,16 @@ func RenewCertificate(ctx context.Context, id uint) (*model.TLSCertificate, erro if err != nil { return nil, err } - if cert.Provider != "acme" { + if cert.Provider != tlsProviderACME { return nil, errors.New(errCertificateOnlyACMERenew) } go func(c *model.TLSCertificate) { - _ = obtainTLSCertificate(context.Background(), c) + asyncCtx := context.WithoutCancel(ctx) + _ = obtainTLSCertificate(asyncCtx, c) }(cert) - cert.ApplyStatus = "applying" + cert.ApplyStatus = tlsApplyStatusApplying cert.ApplyMessage = "" if err := model.SaveTLSCertificate(ctx, cert); err != nil { return nil, err @@ -362,7 +366,7 @@ func GetDefaultAcmeAccount(ctx context.Context) (*model.AcmeAccount, error) { return sanitizeAcmeAccountForResponse(account), nil } -func buildCertificate(ctx context.Context, existing *model.TLSCertificate, input CertificateInput) (*model.TLSCertificate, error) { +func buildCertificate(_ context.Context, existing *model.TLSCertificate, input CertificateInput) (*model.TLSCertificate, error) { name := strings.TrimSpace(input.Name) certPEM := strings.TrimSpace(input.CertPEM) keyPEM := strings.TrimSpace(input.KeyPEM) @@ -391,7 +395,7 @@ func buildCertificate(ctx context.Context, existing *model.TLSCertificate, input if existing == nil { existing = &model.TLSCertificate{ Provider: "upload", - ApplyStatus: "ready", + ApplyStatus: tlsApplyStatusReady, } } existing.Name = name @@ -407,7 +411,7 @@ func fillAcmeCertificateFields(cert *model.TLSCertificate, input ApplyInput) { cert.Name = strings.TrimSpace(input.Name) cert.Remark = strings.TrimSpace(input.Remark) cert.AcmeAccountID = input.AcmeAccountID - cert.DnsAccountID = input.DnsAccountID + cert.DNSAccountID = input.DNSAccountID cert.KeyAlgorithm = input.KeyAlgorithm cert.AutoRenew = input.AutoRenew cert.PrimaryDomain = strings.TrimSpace(input.PrimaryDomain) @@ -416,7 +420,7 @@ func fillAcmeCertificateFields(cert *model.TLSCertificate, input ApplyInput) { cert.SkipDNS = input.SkipDNS cert.DNS1 = strings.TrimSpace(input.DNS1) cert.DNS2 = strings.TrimSpace(input.DNS2) - cert.ApplyStatus = "applying" + cert.ApplyStatus = tlsApplyStatusApplying } func ensureCertificateNotReferenced(ctx context.Context, id uint) error { @@ -457,26 +461,26 @@ func sanitizeCertificateForResponse(certificate *model.TLSCertificate) *model.TL if certificate == nil { return nil } - copy := *certificate - copy.CertPEM = "" - copy.KeyPEM = "" - return © + certCopy := *certificate + certCopy.CertPEM = "" + certCopy.KeyPEM = "" + return &certCopy } func sanitizeDNSAccountForResponse(account *model.DNSAccount) *model.DNSAccount { if account == nil { return nil } - copy := *account - copy.Authorization = "" - return © + certCopy := *account + certCopy.Authorization = "" + return &certCopy } func sanitizeAcmeAccountForResponse(account *model.AcmeAccount) *model.AcmeAccount { if account == nil { return nil } - copy := *account - copy.PrivateKey = "" - return © + certCopy := *account + certCopy.PrivateKey = "" + return &certCopy } diff --git a/internal/apps/openflare/tls/managed_domain.go b/internal/apps/openflare/tls/managed_domain.go index 3e583de6..14ca3295 100644 --- a/internal/apps/openflare/tls/managed_domain.go +++ b/internal/apps/openflare/tls/managed_domain.go @@ -17,6 +17,9 @@ import ( const ( managedDomainMatchTypeExact = "exact" managedDomainMatchTypeWildcard = "wildcard" + + maxManagedDomainLength = 253 + minManagedDomainLabelCount = 2 ) // ManagedDomainInput 托管域名创建/更新请求。 @@ -182,11 +185,11 @@ func validateHostname(domain string) error { if domain == "" { return errors.New(errManagedDomainRequired) } - if len(domain) > 253 { + if len(domain) > maxManagedDomainLength { return errors.New(errManagedDomainInvalid) } labels := strings.Split(domain, ".") - if len(labels) < 2 { + if len(labels) < minManagedDomainLabelCount { return errors.New(errManagedDomainInvalid) } for _, label := range labels { diff --git a/internal/apps/openflare/tls/obtain.go b/internal/apps/openflare/tls/obtain.go index 223845f9..1904f29f 100644 --- a/internal/apps/openflare/tls/obtain.go +++ b/internal/apps/openflare/tls/obtain.go @@ -13,7 +13,12 @@ import ( "github.com/Rain-kl/Wavelet/internal/model" ) -const acmeRenewLeadTime = 7 * 24 * time.Hour +const ( + acmeRenewLeadTime = 7 * 24 * time.Hour + tlsProviderACME = "acme" + tlsApplyStatusApplying = "applying" + tlsApplyStatusReady = "ready" +) var obtainTLSCertificate = obtainCertificate @@ -27,24 +32,17 @@ func SetObtainCertificateFuncForTest(fn func(context.Context, *model.TLSCertific } func obtainCertificate(ctx context.Context, cert *model.TLSCertificate) error { - cert.ApplyStatus = "applying" + cert.ApplyStatus = tlsApplyStatusApplying if err := model.SaveTLSCertificate(ctx, cert); err != nil { return err } - acmeAccount, err := model.GetAcmeAccountByID(ctx, cert.AcmeAccountID) + acmeAccount, err := resolveAcmeAccount(ctx, cert) if err != nil { - acmeAccount, err = model.GetDefaultAcmeAccount(ctx) - if err != nil { - return updateCertError(ctx, cert, fmt.Sprintf("Failed to get ACME account: %v", err)) - } - cert.AcmeAccountID = acmeAccount.ID - if err := model.SaveTLSCertificate(ctx, cert); err != nil { - return err - } + return updateCertError(ctx, cert, fmt.Sprintf("Failed to get ACME account: %v", err)) } - dnsAccount, err := model.GetDNSAccountByID(ctx, cert.DnsAccountID) + dnsAccount, err := model.GetDNSAccountByID(ctx, cert.DNSAccountID) if err != nil { return updateCertError(ctx, cert, fmt.Sprintf("Failed to get DNS account: %v", err)) } @@ -75,47 +73,18 @@ func obtainCertificate(ctx context.Context, cert *model.TLSCertificate) error { domains, ) - if (newPrivateKeyPEM != "" && acmePrivateKey != newPrivateKeyPEM) || (newAccountURL != "" && acmeAccount.URL != newAccountURL) { - if newPrivateKeyPEM != "" { - sealedKey, sealErr := sealSensitive(newPrivateKeyPEM) - if sealErr != nil { - return updateCertError(ctx, cert, fmt.Sprintf("Failed to seal ACME account key: %v", sealErr)) - } - acmeAccount.PrivateKey = sealedKey - } - if newAccountURL != "" { - acmeAccount.URL = newAccountURL - } - if acmeAccount.ID == 0 { - if dbErr := model.CreateAcmeAccountRecord(ctx, acmeAccount); dbErr != nil { - return updateCertError(ctx, cert, fmt.Sprintf("Failed to create ACME account: %v", dbErr)) - } - } else if dbErr := model.SaveAcmeAccount(ctx, acmeAccount); dbErr != nil { - return updateCertError(ctx, cert, fmt.Sprintf("Failed to save ACME account: %v", dbErr)) - } - cert.AcmeAccountID = acmeAccount.ID - if err := model.SaveTLSCertificate(ctx, cert); err != nil { - return err - } + if err := persistAcmeAccountUpdates(ctx, cert, acmeAccount, newAccountURL, newPrivateKeyPEM, acmePrivateKey); err != nil { + return updateCertError(ctx, cert, err.Error()) } if err != nil { return updateCertError(ctx, cert, err.Error()) } - sealedKey, err := sealSensitive(result.KeyPEM) - if err != nil { - return updateCertError(ctx, cert, fmt.Sprintf("Failed to seal certificate key: %v", err)) + if err := saveObtainedCertificate(ctx, cert, result); err != nil { + return updateCertError(ctx, cert, err.Error()) } - - cert.CertPEM = result.CertPEM - cert.KeyPEM = sealedKey - cert.NotBefore = result.NotBefore - cert.NotAfter = result.NotAfter - cert.ApplyStatus = "ready" - cert.ApplyMessage = "" - - return model.SaveTLSCertificate(ctx, cert) + return nil } func updateCertError(ctx context.Context, cert *model.TLSCertificate, message string) error { @@ -155,7 +124,7 @@ func splitAcmeDomains(primaryDomain, otherDomains string) []string { func CertificatesDueForRenewal(certificates []model.TLSCertificate, now time.Time) []model.TLSCertificate { due := make([]model.TLSCertificate, 0) for _, cert := range certificates { - if !cert.AutoRenew || cert.Provider != "acme" || cert.ApplyStatus == "applying" { + if !cert.AutoRenew || cert.Provider != tlsProviderACME || cert.ApplyStatus == tlsApplyStatusApplying { continue } if cert.NotAfter.IsZero() { diff --git a/internal/apps/openflare/tls/obtain_helpers.go b/internal/apps/openflare/tls/obtain_helpers.go new file mode 100644 index 00000000..904df2ee --- /dev/null +++ b/internal/apps/openflare/tls/obtain_helpers.go @@ -0,0 +1,74 @@ +// Copyright 2026 Arctel.net +// SPDX-License-Identifier: Apache-2.0 + +package tls + +import ( + "context" + "fmt" + + "github.com/Rain-kl/Wavelet/internal/apps/openflare/tls/acme" + "github.com/Rain-kl/Wavelet/internal/model" +) + +func resolveAcmeAccount(ctx context.Context, cert *model.TLSCertificate) (*model.AcmeAccount, error) { + acmeAccount, err := model.GetAcmeAccountByID(ctx, cert.AcmeAccountID) + if err == nil { + return acmeAccount, nil + } + acmeAccount, err = model.GetDefaultAcmeAccount(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get ACME account: %w", err) + } + cert.AcmeAccountID = acmeAccount.ID + if err := model.SaveTLSCertificate(ctx, cert); err != nil { + return nil, err + } + return acmeAccount, nil +} + +func persistAcmeAccountUpdates( + ctx context.Context, + cert *model.TLSCertificate, + acmeAccount *model.AcmeAccount, + newAccountURL, newPrivateKeyPEM, acmePrivateKey string, +) error { + accountChanged := (newPrivateKeyPEM != "" && acmePrivateKey != newPrivateKeyPEM) || + (newAccountURL != "" && acmeAccount.URL != newAccountURL) + if !accountChanged { + return nil + } + if newPrivateKeyPEM != "" && acmePrivateKey != newPrivateKeyPEM { + sealedKey, sealErr := sealSensitive(newPrivateKeyPEM) + if sealErr != nil { + return fmt.Errorf("failed to seal ACME account key: %w", sealErr) + } + acmeAccount.PrivateKey = sealedKey + } + if newAccountURL != "" { + acmeAccount.URL = newAccountURL + } + if acmeAccount.ID == 0 { + if dbErr := model.CreateAcmeAccountRecord(ctx, acmeAccount); dbErr != nil { + return fmt.Errorf("failed to create ACME account: %w", dbErr) + } + } else if dbErr := model.SaveAcmeAccount(ctx, acmeAccount); dbErr != nil { + return fmt.Errorf("failed to save ACME account: %w", dbErr) + } + cert.AcmeAccountID = acmeAccount.ID + return model.SaveTLSCertificate(ctx, cert) +} + +func saveObtainedCertificate(ctx context.Context, cert *model.TLSCertificate, result *acme.CertificateResult) error { + sealedKey, err := sealSensitive(result.KeyPEM) + if err != nil { + return fmt.Errorf("failed to seal certificate key: %w", err) + } + cert.CertPEM = result.CertPEM + cert.KeyPEM = sealedKey + cert.NotBefore = result.NotBefore + cert.NotAfter = result.NotAfter + cert.ApplyStatus = tlsApplyStatusReady + cert.ApplyMessage = "" + return model.SaveTLSCertificate(ctx, cert) +} diff --git a/internal/apps/openflare/uptimekuma/client.go b/internal/apps/openflare/uptimekuma/client.go index 199fc9fa..193d14aa 100644 --- a/internal/apps/openflare/uptimekuma/client.go +++ b/internal/apps/openflare/uptimekuma/client.go @@ -1,6 +1,7 @@ // Copyright 2026 Arctel.net // SPDX-License-Identifier: Apache-2.0 +// Package uptimekuma provides a Socket.IO client and sync implementation for Uptime Kuma. package uptimekuma import ( @@ -16,28 +17,30 @@ import ( "time" ) -// UptimeKumaMonitor represents a monitor entry from Uptime Kuma. -type UptimeKumaMonitor struct { - ID int `json:"id"` - Name string `json:"name"` - Url string `json:"url"` - Type string `json:"type"` - Interval int `json:"interval"` - MaxRetries int `json:"maxretries"` - RetryInterval int `json:"retryInterval"` - Timeout int `json:"timeout"` - Tags []UptimeKumaTag `json:"tags"` +const emitAckTimeout = 10 * time.Second + +// Monitor represents a monitor entry from Uptime Kuma. +type Monitor struct { + ID int `json:"id"` + Name string `json:"name"` + URL string `json:"url"` + Type string `json:"type"` + Interval int `json:"interval"` + MaxRetries int `json:"maxretries"` + RetryInterval int `json:"retryInterval"` + Timeout int `json:"timeout"` + Tags []Tag `json:"tags"` } -// UptimeKumaTag represents a tag attached to a monitor. -type UptimeKumaTag struct { +// Tag represents a tag attached to a monitor. +type Tag struct { ID int `json:"tag_id"` Name string `json:"name"` Color string `json:"color"` } -// UptimeKumaTagItem represents a tag returned by getTags. -type UptimeKumaTagItem struct { +// TagItem represents a tag returned by getTags. +type TagItem struct { ID int `json:"id"` Name string `json:"name"` Color string `json:"color"` @@ -55,7 +58,7 @@ type SocketIOClient struct { closeOnce sync.Once monitorListMutex sync.RWMutex - monitorList map[string]UptimeKumaMonitor + monitorList map[string]Monitor monitorListChan chan struct{} monitorListOnce sync.Once @@ -76,7 +79,7 @@ func NewSocketIOClient(baseURL string) *SocketIOClient { ackChanMap: make(map[int]chan string), doneChan: make(chan struct{}), monitorListChan: make(chan struct{}), - monitorList: make(map[string]UptimeKumaMonitor), + monitorList: make(map[string]Monitor), ctx: ctx, cancel: cancel, } @@ -95,7 +98,7 @@ func (c *SocketIOClient) Connect() error { slog.Error("Uptime Kuma handshake connection failed", "url", u, "error", err) return fmt.Errorf("handshake request failed: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() bs, err := io.ReadAll(resp.Body) if err != nil { @@ -130,7 +133,7 @@ func (c *SocketIOClient) Connect() error { slog.Error("Uptime Kuma namespace connect request failed", "sid", c.sid, "error", err) return fmt.Errorf("namespace connect failed: %w", err) } - respConnect.Body.Close() + _ = respConnect.Body.Close() slog.Debug("Namespace connected successfully to Uptime Kuma", "sid", c.sid) go c.pollLoop() @@ -164,7 +167,7 @@ func (c *SocketIOClient) pollLoop() { } bs, err := io.ReadAll(resp.Body) - resp.Body.Close() + _ = resp.Body.Close() if err != nil { slog.Error("Failed to read Uptime Kuma polling body", "sid", c.sid, "error", err) c.err = err @@ -218,7 +221,7 @@ func (c *SocketIOClient) sendPong() { req.Header.Set("Content-Type", "text/plain;charset=UTF-8") resp, err := c.httpClient.Do(req) if err == nil { - resp.Body.Close() + _ = resp.Body.Close() } } @@ -232,7 +235,7 @@ func (c *SocketIOClient) handleEvent(payload string) { return } if eventName == "monitorList" { - var list map[string]UptimeKumaMonitor + var list map[string]Monitor if err := json.Unmarshal(arr[1], &list); err == nil { c.monitorListMutex.Lock() c.monitorList = list @@ -309,13 +312,13 @@ func (c *SocketIOClient) Emit(event string, args ...any) (string, error) { slog.Error("Failed to send Emit request", "event", event, "ackID", id, "error", err) return "", err } - resp.Body.Close() + _ = resp.Body.Close() select { case result := <-ch: slog.Debug("Received Ack for event", "event", event, "ackID", id, "response", result) return result, nil - case <-time.After(10 * time.Second): + case <-time.After(emitAckTimeout): c.ackMutex.Lock() delete(c.ackChanMap, id) c.ackMutex.Unlock() @@ -344,11 +347,11 @@ func (c *SocketIOClient) GetMonitorListChan() <-chan struct{} { } // GetMonitorList returns a copy of the current monitor list. -func (c *SocketIOClient) GetMonitorList() map[string]UptimeKumaMonitor { +func (c *SocketIOClient) GetMonitorList() map[string]Monitor { c.monitorListMutex.RLock() defer c.monitorListMutex.RUnlock() - m := make(map[string]UptimeKumaMonitor, len(c.monitorList)) + m := make(map[string]Monitor, len(c.monitorList)) for k, v := range c.monitorList { m[k] = v } @@ -372,7 +375,7 @@ func ParseAckResponse(response string, target any) error { if errMsg == "" { errMsg = "unknown error from Uptime Kuma" } - return fmt.Errorf("Uptime Kuma error response: %s", errMsg) + return fmt.Errorf("uptime Kuma error response: %s", errMsg) } } diff --git a/internal/apps/openflare/uptimekuma/sync.go b/internal/apps/openflare/uptimekuma/sync.go index a8310386..dc8989ca 100644 --- a/internal/apps/openflare/uptimekuma/sync.go +++ b/internal/apps/openflare/uptimekuma/sync.go @@ -10,17 +10,18 @@ import ( "log/slog" "strings" "sync/atomic" - "time" "github.com/Rain-kl/Wavelet/internal/model" ) +const uptimeKumaTagOpenFlare = "OpenFlare" + var isSyncing atomic.Bool // SyncToUptimeKuma synchronizes enabled proxy routes to Uptime Kuma monitors. func SyncToUptimeKuma(ctx context.Context) error { if !model.UptimeKumaEnabled { - return fmt.Errorf("Uptime Kuma integration is disabled") + return fmt.Errorf("uptime Kuma integration is disabled") } if !isSyncing.CompareAndSwap(false, true) { @@ -28,14 +29,9 @@ func SyncToUptimeKuma(ctx context.Context) error { } defer isSyncing.Store(false) - kumaURL := strings.TrimSpace(model.UptimeKumaUrl) - kumaUsername := strings.TrimSpace(model.UptimeKumaUsername) - kumaPassword := strings.TrimSpace(model.UptimeKumaPassword) - if kumaURL == "" || kumaUsername == "" || kumaPassword == "" { - return fmt.Errorf( - "Uptime Kuma URL, username, or password is not configured (URL: %q, Username: %q, PasswordLength: %d)", - kumaURL, kumaUsername, len(kumaPassword), - ) + kumaURL, kumaUsername, kumaPassword, err := validateUptimeKumaConfig() + if err != nil { + return err } slog.Info("Starting Uptime Kuma sync process", @@ -54,88 +50,20 @@ func SyncToUptimeKuma(ctx context.Context) error { return err } - slog.Debug("Connecting to Uptime Kuma socket endpoint", "url", kumaURL) - client := NewSocketIOClient(kumaURL) - if err := client.Connect(); err != nil { - slog.Error("Failed to connect to Uptime Kuma endpoint", "url", kumaURL, "error", err) - return fmt.Errorf("failed to connect to Uptime Kuma: %w", err) + client, err := connectAndLoginUptimeKuma(kumaURL, kumaUsername, kumaPassword) + if err != nil { + return err } defer client.Close() - slog.Debug("Sending login request to Uptime Kuma", "username", kumaUsername) - loginPayload := map[string]string{ - "username": kumaUsername, - "password": kumaPassword, - } - loginAck, err := client.Emit("login", loginPayload) - if err != nil { - slog.Error("Failed to send login request to Uptime Kuma", "username", kumaUsername, "error", err) - return fmt.Errorf("login request failed: %w", err) - } - - var loginResult struct { - Ok bool `json:"ok"` - } - if err := ParseAckResponse(loginAck, &loginResult); err != nil || !loginResult.Ok { - slog.Error("Uptime Kuma login verification failed", "username", kumaUsername, "error", err) - return fmt.Errorf("login failed: %w", err) - } - slog.Debug("Successfully logged into Uptime Kuma", "username", kumaUsername) - - slog.Debug("Waiting for monitor list push from Uptime Kuma") - select { - case <-client.GetMonitorListChan(): - slog.Debug("Received monitor list from Uptime Kuma") - case <-time.After(5 * time.Second): - slog.Error("Timeout waiting for Uptime Kuma monitorList push event") - return fmt.Errorf("timeout waiting for monitorList event from Uptime Kuma") - } - openFlareTagID, err := ensureOpenFlareTag(client) if err != nil { return err } existingOpenFlareMonitors := filterOpenFlareMonitors(client.GetMonitorList(), openFlareTagID) - expectedSitesMap := make(map[string]bool) - - for _, route := range expectedRoutes { - expectedSitesMap[route.SiteName] = true - targetURL, urlErr := routeMonitorURL(route) - if urlErr != nil { - slog.Error("Failed to resolve monitor URL", "name", route.SiteName, "error", urlErr) - continue - } - - existing, exists := existingOpenFlareMonitors[route.SiteName] - if !exists { - if err := createMonitor(client, route.SiteName, targetURL, openFlareTagID); err != nil { - slog.Error("Failed to add monitor to Uptime Kuma", "name", route.SiteName, "error", err) - } - continue - } - - if monitorNeedsUpdate(existing, targetURL) { - if err := updateMonitor(client, existing.ID, route.SiteName, targetURL); err != nil { - slog.Error("Failed to edit monitor in Uptime Kuma", "name", route.SiteName, "error", err) - } - } - } - - for name, monitor := range existingOpenFlareMonitors { - if expectedSitesMap[name] { - continue - } - slog.Info("Deleting monitor in Uptime Kuma", "name", name, "monitorID", monitor.ID) - deleteAck, err := client.Emit("deleteMonitor", monitor.ID) - if err != nil { - slog.Error("Failed to delete monitor in Uptime Kuma", "name", name, "monitorID", monitor.ID, "error", err) - continue - } - if err := ParseAckResponse(deleteAck, nil); err != nil { - slog.Error("Failed to parse delete monitor result", "name", name, "monitorID", monitor.ID, "error", err) - } - } + expectedSitesMap := syncRouteMonitors(client, expectedRoutes, existingOpenFlareMonitors, openFlareTagID) + removeStaleMonitors(client, existingOpenFlareMonitors, expectedSitesMap) return nil } @@ -178,8 +106,8 @@ func ensureOpenFlareTag(client *SocketIOClient) (int, error) { } var tagsResult struct { - Ok bool `json:"ok"` - Tags []UptimeKumaTagItem `json:"tags"` + Ok bool `json:"ok"` + Tags []TagItem `json:"tags"` } if err := ParseAckResponse(tagsAck, &tagsResult); err != nil { slog.Error("Failed to parse tags response from Uptime Kuma", "error", err) @@ -187,7 +115,7 @@ func ensureOpenFlareTag(client *SocketIOClient) (int, error) { } for _, tag := range tagsResult.Tags { - if tag.Name == "OpenFlare" { + if tag.Name == uptimeKumaTagOpenFlare { slog.Debug("Found existing OpenFlare tag", "tag_id", tag.ID) return tag.ID, nil } @@ -195,7 +123,7 @@ func ensureOpenFlareTag(client *SocketIOClient) (int, error) { slog.Debug("OpenFlare tag not found, creating new tag") addTagAck, err := client.Emit("addTag", map[string]string{ - "name": "OpenFlare", + "name": uptimeKumaTagOpenFlare, "color": "#4f46e5", }) if err != nil { @@ -218,12 +146,12 @@ func ensureOpenFlareTag(client *SocketIOClient) (int, error) { return tagResult.Tag.ID, nil } -func filterOpenFlareMonitors(monitors map[string]UptimeKumaMonitor, openFlareTagID int) map[string]UptimeKumaMonitor { - existingOpenFlareMonitors := make(map[string]UptimeKumaMonitor) +func filterOpenFlareMonitors(monitors map[string]Monitor, openFlareTagID int) map[string]Monitor { + existingOpenFlareMonitors := make(map[string]Monitor) for _, monitor := range monitors { hasOpenFlareTag := false for _, tag := range monitor.Tags { - if tag.Name == "OpenFlare" || tag.ID == openFlareTagID { + if tag.Name == uptimeKumaTagOpenFlare || tag.ID == openFlareTagID { hasOpenFlareTag = true break } @@ -295,8 +223,8 @@ func monitorPayload(id int, name, targetURL string) map[string]any { return payload } -func monitorNeedsUpdate(existing UptimeKumaMonitor, targetURL string) bool { - return existing.Url != targetURL || +func monitorNeedsUpdate(existing Monitor, targetURL string) bool { + return existing.URL != targetURL || existing.Interval != model.UptimeKumaInterval || existing.MaxRetries != model.UptimeKumaRetry || existing.RetryInterval != model.UptimeKumaRetryInterval || diff --git a/internal/apps/openflare/uptimekuma/sync_helpers.go b/internal/apps/openflare/uptimekuma/sync_helpers.go new file mode 100644 index 00000000..428bec4b --- /dev/null +++ b/internal/apps/openflare/uptimekuma/sync_helpers.go @@ -0,0 +1,112 @@ +// Copyright 2026 Arctel.net +// SPDX-License-Identifier: Apache-2.0 + +package uptimekuma + +import ( + "fmt" + "log/slog" + "strings" + "time" + + "github.com/Rain-kl/Wavelet/internal/model" +) + +const monitorListWaitTimeout = 5 * time.Second + +func validateUptimeKumaConfig() (string, string, string, error) { + kumaURL := strings.TrimSpace(model.UptimeKumaURL) + kumaUsername := strings.TrimSpace(model.UptimeKumaUsername) + kumaPassword := strings.TrimSpace(model.UptimeKumaPassword) + if kumaURL == "" || kumaUsername == "" || kumaPassword == "" { + return kumaURL, kumaUsername, kumaPassword, fmt.Errorf( + "uptime Kuma URL, username, or password is not configured (URL: %q, Username: %q, PasswordLength: %d)", + kumaURL, kumaUsername, len(kumaPassword), + ) + } + return kumaURL, kumaUsername, kumaPassword, nil +} + +func connectAndLoginUptimeKuma(kumaURL, kumaUsername, kumaPassword string) (*SocketIOClient, error) { + slog.Debug("Connecting to Uptime Kuma socket endpoint", "url", kumaURL) + client := NewSocketIOClient(kumaURL) + if err := client.Connect(); err != nil { + slog.Error("Failed to connect to Uptime Kuma endpoint", "url", kumaURL, "error", err) + return nil, fmt.Errorf("failed to connect to Uptime Kuma: %w", err) + } + + slog.Debug("Sending login request to Uptime Kuma", "username", kumaUsername) + loginAck, err := client.Emit("login", map[string]string{ + "username": kumaUsername, + "password": kumaPassword, + }) + if err != nil { + client.Close() + slog.Error("Failed to send login request to Uptime Kuma", "username", kumaUsername, "error", err) + return nil, fmt.Errorf("login request failed: %w", err) + } + + var loginResult struct { + Ok bool `json:"ok"` + } + if err := ParseAckResponse(loginAck, &loginResult); err != nil || !loginResult.Ok { + client.Close() + slog.Error("Uptime Kuma login verification failed", "username", kumaUsername, "error", err) + return nil, fmt.Errorf("login failed: %w", err) + } + slog.Debug("Successfully logged into Uptime Kuma", "username", kumaUsername) + + slog.Debug("Waiting for monitor list push from Uptime Kuma") + select { + case <-client.GetMonitorListChan(): + slog.Debug("Received monitor list from Uptime Kuma") + case <-time.After(monitorListWaitTimeout): + client.Close() + slog.Error("Timeout waiting for Uptime Kuma monitorList push event") + return nil, fmt.Errorf("timeout waiting for monitorList event from Uptime Kuma") + } + return client, nil +} + +func syncRouteMonitors(client *SocketIOClient, expectedRoutes []*model.ProxyRoute, existingMonitors map[string]Monitor, openFlareTagID int) map[string]bool { + expectedSitesMap := make(map[string]bool, len(expectedRoutes)) + for _, route := range expectedRoutes { + expectedSitesMap[route.SiteName] = true + targetURL, urlErr := routeMonitorURL(route) + if urlErr != nil { + slog.Error("Failed to resolve monitor URL", "name", route.SiteName, "error", urlErr) + continue + } + + existing, exists := existingMonitors[route.SiteName] + if !exists { + if err := createMonitor(client, route.SiteName, targetURL, openFlareTagID); err != nil { + slog.Error("Failed to add monitor to Uptime Kuma", "name", route.SiteName, "error", err) + } + continue + } + if monitorNeedsUpdate(existing, targetURL) { + if err := updateMonitor(client, existing.ID, route.SiteName, targetURL); err != nil { + slog.Error("Failed to edit monitor in Uptime Kuma", "name", route.SiteName, "error", err) + } + } + } + return expectedSitesMap +} + +func removeStaleMonitors(client *SocketIOClient, existingMonitors map[string]Monitor, expectedSitesMap map[string]bool) { + for name, monitor := range existingMonitors { + if expectedSitesMap[name] { + continue + } + slog.Info("Deleting monitor in Uptime Kuma", "name", name, "monitorID", monitor.ID) + deleteAck, err := client.Emit("deleteMonitor", monitor.ID) + if err != nil { + slog.Error("Failed to delete monitor in Uptime Kuma", "name", name, "monitorID", monitor.ID, "error", err) + continue + } + if err := ParseAckResponse(deleteAck, nil); err != nil { + slog.Error("Failed to parse delete monitor result", "name", name, "monitorID", monitor.ID, "error", err) + } + } +} diff --git a/internal/apps/openflare/uptimekuma/sync_test.go b/internal/apps/openflare/uptimekuma/sync_test.go index 3bbabaa3..6c399957 100644 --- a/internal/apps/openflare/uptimekuma/sync_test.go +++ b/internal/apps/openflare/uptimekuma/sync_test.go @@ -131,7 +131,7 @@ func setupSyncTestDB(t *testing.T) func() { func backupUptimeKumaConfig() func() { oldEnabled := model.UptimeKumaEnabled - oldURL := model.UptimeKumaUrl + oldURL := model.UptimeKumaURL oldUsername := model.UptimeKumaUsername oldPassword := model.UptimeKumaPassword oldScope := model.UptimeKumaMonitorScope @@ -143,7 +143,7 @@ func backupUptimeKumaConfig() func() { return func() { model.UptimeKumaEnabled = oldEnabled - model.UptimeKumaUrl = oldURL + model.UptimeKumaURL = oldURL model.UptimeKumaUsername = oldUsername model.UptimeKumaPassword = oldPassword model.UptimeKumaMonitorScope = oldScope @@ -228,7 +228,7 @@ func TestSyncToUptimeKumaSuccess(t *testing.T) { defer server.Close() model.UptimeKumaEnabled = true - model.UptimeKumaUrl = server.URL + model.UptimeKumaURL = server.URL model.UptimeKumaUsername = "admin" model.UptimeKumaPassword = "password" model.UptimeKumaMonitorScope = "all" @@ -313,7 +313,7 @@ func TestSyncToUptimeKumaSelectedScope(t *testing.T) { defer server.Close() model.UptimeKumaEnabled = true - model.UptimeKumaUrl = server.URL + model.UptimeKumaURL = server.URL model.UptimeKumaUsername = "admin" model.UptimeKumaPassword = "password" model.UptimeKumaMonitorScope = "selected" diff --git a/internal/apps/openflare/waf/errs.go b/internal/apps/openflare/waf/errs.go index 697033f3..99bdfa9d 100644 --- a/internal/apps/openflare/waf/errs.go +++ b/internal/apps/openflare/waf/errs.go @@ -1,9 +1,5 @@ // Copyright 2026 Arctel.net // SPDX-License-Identifier: Apache-2.0 +// Package waf defines shared error messages for WAF management. package waf - -const ( - errWAFRuleGroupNotFound = "WAF 规则组不存在" - errWAFIPGroupNotFound = "IP 组不存在" -) diff --git a/internal/apps/openflare/waf/ip_group_sync.go b/internal/apps/openflare/waf/ip_group_sync.go index e46d72da..d23dbbc7 100644 --- a/internal/apps/openflare/waf/ip_group_sync.go +++ b/internal/apps/openflare/waf/ip_group_sync.go @@ -95,7 +95,7 @@ func syncOpenFlareWAFIPGroup(ctx context.Context, group *model.OpenFlareWAFIPGro } func syncIPGroupSubscription(ctx context.Context, group *model.OpenFlareWAFIPGroup, now time.Time) (*IPGroupSyncResult, error) { - content, err := downloadIPGroupSubscription(group.SubscriptionURL) + content, err := downloadIPGroupSubscription(ctx, group.SubscriptionURL) if err != nil { recordIPGroupSyncFailure(ctx, group, now, err) return nil, err @@ -266,7 +266,7 @@ func evaluateParsedIPGroupAutoConfig(ctx context.Context, config ipGroupAutoConf if item.StatusCode >= 400 && item.StatusCode < 500 { acc.clientErrorCount++ } - if item.StatusCode >= 500 { + if item.StatusCode >= http.StatusInternalServerError { acc.serverErrorCount++ } if hostIsIPLiteral(item.Host) { @@ -341,16 +341,20 @@ func hostIsIPLiteral(value string) bool { return ok } -func downloadIPGroupSubscription(rawURL string) ([]byte, error) { +func downloadIPGroupSubscription(ctx context.Context, rawURL string) ([]byte, error) { if err := validateSubscriptionURL(rawURL); err != nil { return nil, err } client := http.Client{Timeout: 15 * time.Second} - resp, err := client.Get(rawURL) + req, err := http.NewRequestWithContext(ctx, "GET", rawURL, nil) if err != nil { return nil, fmt.Errorf("下载订阅失败: %w", err) } - defer resp.Body.Close() + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("下载订阅失败: %w", err) + } + defer func() { _ = resp.Body.Close() }() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("订阅返回状态码 %d", resp.StatusCode) } diff --git a/internal/apps/openflare/waf/logics.go b/internal/apps/openflare/waf/logics.go index 3837d7b5..8d9cae07 100644 --- a/internal/apps/openflare/waf/logics.go +++ b/internal/apps/openflare/waf/logics.go @@ -8,10 +8,8 @@ import ( "encoding/json" "errors" "fmt" - "net" "net/netip" "net/url" - "regexp" "sort" "strings" "time" @@ -38,6 +36,9 @@ const ( defaultWAFIPGroupAutoLookbackMinutes = 60 minWAFIPGroupSyncIntervalMinutes = 5 maxWAFIPGroupSyncIntervalMinutes = 43200 + + minPoWSessionTTLSeconds = 60 + minPoWChallengeTTLSeconds = 30 ) // RuleGroupInput is the create/update payload for WAF rule groups. @@ -986,82 +987,34 @@ func defaultPoWConfig() PoWConfig { } func normalizePoWConfig(enabled bool, raw string) (PoWConfig, error) { - if !enabled { - return defaultPoWConfig(), nil + cfg, err := parsePoWConfigRaw(enabled, raw) + if err != nil { + return cfg, err } - - cfg := defaultPoWConfig() - text := strings.TrimSpace(raw) - if text != "" && text != "{}" { - if err := json.Unmarshal([]byte(text), &cfg); err != nil { - return cfg, errors.New("pow_config 格式无效") - } + if err := validatePoWCoreSettings(cfg); err != nil { + return cfg, err } - - if cfg.Difficulty < 1 || cfg.Difficulty > 16 { - return cfg, errors.New("pow_config.difficulty 必须在 1-16 之间") + if err := validatePoWCIDRs(cfg.Whitelist.IPCidrs, "白名单"); err != nil { + return cfg, err } - if !powAlgorithmValues[cfg.Algorithm] { - return cfg, errors.New("pow_config.algorithm 必须为 fast 或 slow") + if err := validatePoWCIDRs(cfg.Blacklist.IPCidrs, "黑名单"); err != nil { + return cfg, err } - if cfg.SessionTTL < 60 { - return cfg, errors.New("pow_config.session_ttl 不能小于 60 秒") + if err := validatePoWPathRegexes(cfg.Whitelist.PathRegexes, "白名单"); err != nil { + return cfg, err } - if cfg.ChallengeTTL < 30 { - return cfg, errors.New("pow_config.challenge_ttl 不能小于 30 秒") + if err := validatePoWPathRegexes(cfg.Blacklist.PathRegexes, "黑名单"); err != nil { + return cfg, err } - - for _, cidr := range cfg.Whitelist.IPCidrs { - if _, _, err := net.ParseCIDR(cidr); err != nil { - return cfg, fmt.Errorf("pow_config 白名单 IP CIDR 格式无效: %s", cidr) - } + if err := validatePoWIPs(cfg.Whitelist.IPs, "白名单"); err != nil { + return cfg, err } - for _, cidr := range cfg.Blacklist.IPCidrs { - if _, _, err := net.ParseCIDR(cidr); err != nil { - return cfg, fmt.Errorf("pow_config 黑名单 IP CIDR 格式无效: %s", cidr) - } + if err := validatePoWIPs(cfg.Blacklist.IPs, "黑名单"); err != nil { + return cfg, err } - - for _, re := range cfg.Whitelist.PathRegexes { - if _, err := regexp.Compile(re); err != nil { - return cfg, fmt.Errorf("pow_config 白名单路径正则格式无效: %s", re) - } + if err := validatePoWListMutualExclusion(cfg); err != nil { + return cfg, err } - for _, re := range cfg.Blacklist.PathRegexes { - if _, err := regexp.Compile(re); err != nil { - return cfg, fmt.Errorf("pow_config 黑名单路径正则格式无效: %s", re) - } - } - - for _, ip := range cfg.Whitelist.IPs { - if net.ParseIP(ip) == nil { - return cfg, fmt.Errorf("pow_config 白名单 IP 格式无效: %s", ip) - } - } - for _, ip := range cfg.Blacklist.IPs { - if net.ParseIP(ip) == nil { - return cfg, fmt.Errorf("pow_config 黑名单 IP 格式无效: %s", ip) - } - } - - type dimension struct { - name string - wl []string - bl []string - } - dimensions := []dimension{ - {"IP", cfg.Whitelist.IPs, cfg.Blacklist.IPs}, - {"IP CIDR", cfg.Whitelist.IPCidrs, cfg.Blacklist.IPCidrs}, - {"路径", cfg.Whitelist.Paths, cfg.Blacklist.Paths}, - {"路径正则", cfg.Whitelist.PathRegexes, cfg.Blacklist.PathRegexes}, - {"User-Agent", cfg.Whitelist.UserAgents, cfg.Blacklist.UserAgents}, - } - for _, dim := range dimensions { - if len(dim.wl) > 0 && len(dim.bl) > 0 { - return cfg, fmt.Errorf("pow_config %s 不能同时配置白名单和黑名单", dim.name) - } - } - return cfg, nil } @@ -1111,11 +1064,11 @@ func parseIPGroupAutoConfig(raw json.RawMessage) (ipGroupAutoConfig, error) { if config.LookbackMinutes <= 0 { config.LookbackMinutes = defaultWAFIPGroupAutoLookbackMinutes } - if config.LookbackMinutes < 5 { - config.LookbackMinutes = 5 + if config.LookbackMinutes < minWAFIPGroupSyncIntervalMinutes { + config.LookbackMinutes = minWAFIPGroupSyncIntervalMinutes } - if config.LookbackMinutes > 43200 { - config.LookbackMinutes = 43200 + if config.LookbackMinutes > maxWAFIPGroupSyncIntervalMinutes { + config.LookbackMinutes = maxWAFIPGroupSyncIntervalMinutes } if config.TTL == 0 { config.TTL = -1 diff --git a/internal/apps/openflare/waf/pow_helpers.go b/internal/apps/openflare/waf/pow_helpers.go new file mode 100644 index 00000000..242100df --- /dev/null +++ b/internal/apps/openflare/waf/pow_helpers.go @@ -0,0 +1,92 @@ +// Copyright 2026 Arctel.net +// SPDX-License-Identifier: Apache-2.0 + +package waf + +import ( + "encoding/json" + "errors" + "fmt" + "net" + "regexp" + "strings" +) + +func parsePoWConfigRaw(enabled bool, raw string) (PoWConfig, error) { + if !enabled { + return defaultPoWConfig(), nil + } + cfg := defaultPoWConfig() + text := strings.TrimSpace(raw) + if text == "" || text == "{}" { + return cfg, nil + } + if err := json.Unmarshal([]byte(text), &cfg); err != nil { + return cfg, errors.New("pow_config 格式无效") + } + return cfg, nil +} + +func validatePoWCoreSettings(cfg PoWConfig) error { + if cfg.Difficulty < 1 || cfg.Difficulty > 16 { + return errors.New("pow_config.difficulty 必须在 1-16 之间") + } + if !powAlgorithmValues[cfg.Algorithm] { + return errors.New("pow_config.algorithm 必须为 fast 或 slow") + } + if cfg.SessionTTL < minPoWSessionTTLSeconds { + return errors.New("pow_config.session_ttl 不能小于 60 秒") + } + if cfg.ChallengeTTL < minPoWChallengeTTLSeconds { + return errors.New("pow_config.challenge_ttl 不能小于 30 秒") + } + return nil +} + +func validatePoWCIDRs(cidrs []string, listName string) error { + for _, cidr := range cidrs { + if _, _, err := net.ParseCIDR(cidr); err != nil { + return fmt.Errorf("pow_config %s IP CIDR 格式无效: %s", listName, cidr) + } + } + return nil +} + +func validatePoWPathRegexes(regexes []string, listName string) error { + for _, re := range regexes { + if _, err := regexp.Compile(re); err != nil { + return fmt.Errorf("pow_config %s路径正则格式无效: %s", listName, re) + } + } + return nil +} + +func validatePoWIPs(ips []string, listName string) error { + for _, ip := range ips { + if net.ParseIP(ip) == nil { + return fmt.Errorf("pow_config %s IP 格式无效: %s", listName, ip) + } + } + return nil +} + +func validatePoWListMutualExclusion(cfg PoWConfig) error { + type dimension struct { + name string + wl []string + bl []string + } + dimensions := []dimension{ + {"IP", cfg.Whitelist.IPs, cfg.Blacklist.IPs}, + {"IP CIDR", cfg.Whitelist.IPCidrs, cfg.Blacklist.IPCidrs}, + {"路径", cfg.Whitelist.Paths, cfg.Blacklist.Paths}, + {"路径正则", cfg.Whitelist.PathRegexes, cfg.Blacklist.PathRegexes}, + {"User-Agent", cfg.Whitelist.UserAgents, cfg.Blacklist.UserAgents}, + } + for _, dim := range dimensions { + if len(dim.wl) > 0 && len(dim.bl) > 0 { + return fmt.Errorf("pow_config %s 不能同时配置白名单和黑名单", dim.name) + } + } + return nil +} diff --git a/internal/apps/openflare/websocket/agent_hub.go b/internal/apps/openflare/websocket/agent_hub.go index 9850700c..5bc306bd 100644 --- a/internal/apps/openflare/websocket/agent_hub.go +++ b/internal/apps/openflare/websocket/agent_hub.go @@ -1,6 +1,7 @@ // Copyright 2026 Arctel.net // SPDX-License-Identifier: Apache-2.0 +// Package websocket manages persistent WebSocket connections between the OpenFlare server and its agents. package websocket import ( @@ -67,7 +68,7 @@ func ServeAgent(c *gin.Context, nodeID string, onStatus AgentStatusHandler) { nodeID: nodeID, remoteAddr: c.Request.RemoteAddr, conn: conn, - send: make(chan Message, 16), + send: make(chan Message, wsChannelBuf), done: make(chan struct{}), onStatus: onStatus, } @@ -202,15 +203,15 @@ func (c *agentClient) readPump() { } func agentWSReadTimeout() time.Duration { - timeout := 90 * time.Second - if timeout < 30*time.Second { - return 30 * time.Second + timeout := wsReadDeadline + if timeout < minAgentWSReadTimeout { + return minAgentWSReadTimeout } return timeout } func (c *agentClient) writePump() { - ticker := time.NewTicker(30 * time.Second) + ticker := time.NewTicker(wsPingInterval) defer ticker.Stop() for { @@ -218,7 +219,7 @@ func (c *agentClient) writePump() { case <-c.done: return case message := <-c.send: - _ = c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second)) + _ = c.conn.SetWriteDeadline(time.Now().Add(wsWriteDeadline)) if err := c.conn.WriteJSON(message); err != nil { slog.Debug("agent ws write failed", "node_id", c.nodeID, "error", err) c.close() diff --git a/internal/apps/openflare/websocket/consts.go b/internal/apps/openflare/websocket/consts.go new file mode 100644 index 00000000..c8a20c2b --- /dev/null +++ b/internal/apps/openflare/websocket/consts.go @@ -0,0 +1,14 @@ +// Copyright 2026 Arctel.net +// SPDX-License-Identifier: Apache-2.0 + +package websocket + +import "time" + +const ( + wsChannelBuf = 16 + wsPingInterval = 30 * time.Second + wsReadDeadline = 90 * time.Second + wsWriteDeadline = 10 * time.Second + minAgentWSReadTimeout = 30 * time.Second +) diff --git a/internal/apps/openflare/websocket/flared_hub.go b/internal/apps/openflare/websocket/flared_hub.go index c151a964..57913e95 100644 --- a/internal/apps/openflare/websocket/flared_hub.go +++ b/internal/apps/openflare/websocket/flared_hub.go @@ -4,7 +4,6 @@ package websocket import ( - "encoding/json" "log/slog" "sync" "time" @@ -58,7 +57,7 @@ func ServeFlared(c *gin.Context, nodeID string) { client := &flaredClient{ nodeID: nodeID, conn: conn, - send: make(chan Message, 16), + send: make(chan Message, wsChannelBuf), done: make(chan struct{}), } defaultFlaredHub.register(client) @@ -136,37 +135,11 @@ func SendFlaredPong(nodeID string) bool { } func (c *flaredClient) readPump() { - defer c.close() - _ = c.conn.SetReadDeadline(time.Now().Add(90 * time.Second)) - c.conn.SetPongHandler(func(string) error { - return c.conn.SetReadDeadline(time.Now().Add(90 * time.Second)) - }) - - for { - _, data, err := c.conn.ReadMessage() - if err != nil { - slog.Debug("flared ws read closed", "node_id", c.nodeID, "error", err) - return - } - - var message Message - if err = json.Unmarshal(data, &message); err != nil { - slog.Debug("flared ws invalid message", "node_id", c.nodeID, "error", err) - continue - } - - switch message.Type { - case messageTypePing: - _ = SendFlaredPong(c.nodeID) - case flaredMessageTypePong: - default: - slog.Debug("flared ws unsupported message", "node_id", c.nodeID, "type", message.Type) - } - } + runReadPump(c.nodeID, c.conn, c.close, "flared ws", SendFlaredPong, flaredMessageTypePong) } func (c *flaredClient) writePump() { - ticker := time.NewTicker(30 * time.Second) + ticker := time.NewTicker(wsPingInterval) defer ticker.Stop() for { @@ -174,7 +147,7 @@ func (c *flaredClient) writePump() { case <-c.done: return case message := <-c.send: - _ = c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second)) + _ = c.conn.SetWriteDeadline(time.Now().Add(wsWriteDeadline)) if err := c.conn.WriteJSON(message); err != nil { slog.Debug("flared ws write failed", "node_id", c.nodeID, "error", err) c.close() diff --git a/internal/apps/openflare/websocket/read_pump.go b/internal/apps/openflare/websocket/read_pump.go new file mode 100644 index 00000000..d8da94e8 --- /dev/null +++ b/internal/apps/openflare/websocket/read_pump.go @@ -0,0 +1,49 @@ +// Copyright 2026 Arctel.net +// SPDX-License-Identifier: Apache-2.0 + +package websocket + +import ( + "encoding/json" + "log/slog" + "time" + + "github.com/gorilla/websocket" +) + +func runReadPump( + nodeID string, + conn *websocket.Conn, + closeFn func(), + logLabel string, + sendPong func(string) bool, + clientPongType string, +) { + defer closeFn() + _ = conn.SetReadDeadline(time.Now().Add(wsReadDeadline)) + conn.SetPongHandler(func(string) error { + return conn.SetReadDeadline(time.Now().Add(wsReadDeadline)) + }) + + for { + _, data, err := conn.ReadMessage() + if err != nil { + slog.Debug(logLabel+" read closed", "node_id", nodeID, "error", err) + return + } + + var message Message + if err = json.Unmarshal(data, &message); err != nil { + slog.Debug(logLabel+" invalid message", "node_id", nodeID, "error", err) + continue + } + + switch message.Type { + case messageTypePing: + _ = sendPong(nodeID) + case clientPongType: + default: + slog.Debug(logLabel+" unsupported message", "node_id", nodeID, "type", message.Type) + } + } +} diff --git a/internal/apps/openflare/websocket/relay_hub.go b/internal/apps/openflare/websocket/relay_hub.go index b27fc046..c9795183 100644 --- a/internal/apps/openflare/websocket/relay_hub.go +++ b/internal/apps/openflare/websocket/relay_hub.go @@ -4,7 +4,6 @@ package websocket import ( - "encoding/json" "log/slog" "sync" "time" @@ -52,7 +51,7 @@ func ServeRelay(c *gin.Context, nodeID string) { client := &relayClient{ nodeID: nodeID, conn: conn, - send: make(chan Message, 16), + send: make(chan Message, wsChannelBuf), done: make(chan struct{}), } defaultRelayHub.register(client) @@ -117,37 +116,11 @@ func SendRelayPong(nodeID string) bool { } func (c *relayClient) readPump() { - defer c.close() - _ = c.conn.SetReadDeadline(time.Now().Add(90 * time.Second)) - c.conn.SetPongHandler(func(string) error { - return c.conn.SetReadDeadline(time.Now().Add(90 * time.Second)) - }) - - for { - _, data, err := c.conn.ReadMessage() - if err != nil { - slog.Debug("relay ws read closed", "node_id", c.nodeID, "error", err) - return - } - - var message Message - if err = json.Unmarshal(data, &message); err != nil { - slog.Debug("relay ws invalid message", "node_id", c.nodeID, "error", err) - continue - } - - switch message.Type { - case messageTypePing: - _ = SendRelayPong(c.nodeID) - case messageTypePong: - default: - slog.Debug("relay ws unsupported message", "node_id", c.nodeID, "type", message.Type) - } - } + runReadPump(c.nodeID, c.conn, c.close, "relay ws", SendRelayPong, messageTypePong) } func (c *relayClient) writePump() { - ticker := time.NewTicker(30 * time.Second) + ticker := time.NewTicker(wsPingInterval) defer ticker.Stop() for { @@ -155,7 +128,7 @@ func (c *relayClient) writePump() { case <-c.done: return case message := <-c.send: - _ = c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second)) + _ = c.conn.SetWriteDeadline(time.Now().Add(wsWriteDeadline)) if err := c.conn.WriteJSON(message); err != nil { slog.Debug("relay ws write failed", "node_id", c.nodeID, "error", err) c.close() diff --git a/internal/apps/relay/config/config.go b/internal/apps/relay/config/config.go index 41ae3dac..082d3973 100644 --- a/internal/apps/relay/config/config.go +++ b/internal/apps/relay/config/config.go @@ -1,3 +1,4 @@ +// Package config loads and persists relay daemon configuration. package config import ( @@ -12,8 +13,16 @@ import ( "github.com/Rain-kl/Wavelet/internal/apps/edge/nodeip" ) +const ( + defaultHeartbeatInterval = 10 * time.Second + defaultRequestTimeout = 10 * time.Second + configFilePerm = 0o600 +) + +// MillisecondDuration is a JSON-friendly duration type shared with edge config. type MillisecondDuration = edgeconfig.MillisecondDuration +// Config holds relay daemon settings loaded from file and environment. type Config struct { ServerURL string `json:"server_url"` AgentToken string `json:"agent_token"` @@ -28,8 +37,9 @@ type Config struct { configPath string } +// Load reads configuration from path, applying environment overrides and defaults. func Load(path string) (*Config, error) { - data, err := os.ReadFile(path) + data, err := os.ReadFile(path) //nolint:gosec // path is the relay config file from startup if err != nil && !os.IsNotExist(err) { return nil, err } @@ -105,10 +115,10 @@ func applyDefaults(cfg *Config, baseDir string) { cfg.StatePath = filepath.Join(cfg.DataDir, "relay-state.json") } if cfg.HeartbeatInterval <= 0 { - cfg.HeartbeatInterval = MillisecondDuration(10 * time.Second) + cfg.HeartbeatInterval = MillisecondDuration(defaultHeartbeatInterval) } if cfg.RequestTimeout <= 0 { - cfg.RequestTimeout = MillisecondDuration(10 * time.Second) + cfg.RequestTimeout = MillisecondDuration(defaultRequestTimeout) } } @@ -125,6 +135,7 @@ func validate(cfg *Config) error { return nil } +// InitialAuthToken returns the agent or discovery token used for authentication. func (cfg *Config) InitialAuthToken() string { if cfg == nil { return "" @@ -135,6 +146,7 @@ func (cfg *Config) InitialAuthToken() string { return strings.TrimSpace(cfg.DiscoveryToken) } +// Save writes the current configuration back to the loaded config path. func (cfg *Config) Save() error { if cfg == nil { return errors.New("config 不能为空") @@ -146,7 +158,5 @@ func (cfg *Config) Save() error { if err != nil { return err } - return os.WriteFile(cfg.configPath, data, 0o644) + return os.WriteFile(cfg.configPath, data, configFilePerm) } - - diff --git a/internal/apps/relay/config/version.go b/internal/apps/relay/config/version.go index f356490f..2b9656fb 100644 --- a/internal/apps/relay/config/version.go +++ b/internal/apps/relay/config/version.go @@ -1,3 +1,4 @@ package config +// Version is the relay daemon build version string. var Version = "dev" diff --git a/internal/apps/relay/frps/manager.go b/internal/apps/relay/frps/manager.go index 521b1dba..40118bfb 100644 --- a/internal/apps/relay/frps/manager.go +++ b/internal/apps/relay/frps/manager.go @@ -1,3 +1,6 @@ +// Package frps manages the lifecycle of the frps reverse-proxy process: +// rendering its TOML config, supervising the child process with exponential- +// backoff restarts, and exposing runtime status to the heartbeat subsystem. package frps import ( @@ -15,6 +18,19 @@ import ( service "github.com/Rain-kl/Wavelet/pkg/protocol" ) +const ( + statusUnhealthy = "unhealthy" + + defaultFrpsWebServerPort = 17500 + frpsSupervisorPollInterval = 100 * time.Millisecond + frpsOrphanProcessCleanupDelay = 500 * time.Millisecond + frpsDataDirPerm = 0o750 + frpsConfigFilePerm = 0o600 + frpsPidFilePerm = 0o600 +) + +// Manager controls a single frps child process. It renders TOML configuration, +// supervises the process with automatic restarts, and reports runtime status. type Manager struct { frpsPath string dataDir string @@ -31,6 +47,7 @@ type Manager struct { stopping bool } +// RuntimeStatus is a snapshot of the frps process state at the time of the call. type RuntimeStatus struct { Status string LastError string @@ -41,6 +58,8 @@ type RuntimeStatus struct { ProcessAlive bool } +// NewManager constructs a Manager that will run frpsPath and store its PID file +// and generated configuration under dataDir. func NewManager(frpsPath string, dataDir string, agentToken string) *Manager { return &Manager{ frpsPath: frpsPath, @@ -52,11 +71,9 @@ func NewManager(frpsPath string, dataDir string, agentToken string) *Manager { } } -func (m *Manager) GetVersion() string { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - cmd := exec.CommandContext(ctx, m.frpsPath, "-v") +// GetVersion executes frps -v and returns the trimmed version string. +func (m *Manager) GetVersion(ctx context.Context) string { + cmd := exec.CommandContext(ctx, m.frpsPath, "-v") //nolint:gosec // frpsPath is the configured trusted frps binary location out, err := cmd.CombinedOutput() if err != nil { slog.Error("failed to get frps version", "error", err) @@ -65,12 +82,16 @@ func (m *Manager) GetVersion() string { return strings.TrimSpace(string(out)) } +// GetStatus returns the current status string (e.g. "healthy", "unhealthy") under +// a read lock. func (m *Manager) GetStatus() string { m.mu.RLock() defer m.mu.RUnlock() return m.status } +// GetRuntimeStatus returns a RuntimeStatus snapshot without blocking the +// supervisor goroutine for more than the duration of a read lock. func (m *Manager) GetRuntimeStatus() RuntimeStatus { m.mu.RLock() status := m.status @@ -89,7 +110,10 @@ func (m *Manager) GetRuntimeStatus() RuntimeStatus { } } -func (m *Manager) UpdateConfig(cfg *service.RelayConfig) { +// UpdateConfig applies a new RelayConfig. If the configuration has not changed +// and frps is already running, this is a no-op. Otherwise the existing process +// is killed and a new supervisor goroutine is started. +func (m *Manager) UpdateConfig(ctx context.Context, cfg *service.RelayConfig) { if cfg == nil { return } @@ -109,11 +133,11 @@ func (m *Manager) UpdateConfig(cfg *service.RelayConfig) { generation := m.generation if err := m.renderConfig(cfg); err != nil { slog.Error("failed to render frps config", "error", err) - m.status = "unhealthy" + m.status = statusUnhealthy m.lastError = err.Error() return } - go m.supervise(generation) + go m.supervise(ctx, generation) } return } @@ -132,27 +156,27 @@ func (m *Manager) UpdateConfig(cfg *service.RelayConfig) { if err := m.renderConfig(cfg); err != nil { slog.Error("failed to render frps config", "error", err) - m.status = "unhealthy" + m.status = statusUnhealthy m.lastError = err.Error() return } - go m.supervise(generation) + go m.supervise(ctx, generation) } func (m *Manager) renderConfig(cfg *service.RelayConfig) error { - if err := os.MkdirAll(m.dataDir, 0755); err != nil { + if err := os.MkdirAll(m.dataDir, frpsDataDirPerm); err != nil { return err } var buf bytes.Buffer - buf.WriteString(fmt.Sprintf("bindPort = %d\n", cfg.BindPort)) + fmt.Fprintf(&buf, "bindPort = %d\n", cfg.BindPort) if cfg.VhostHTTPPort > 0 { - buf.WriteString(fmt.Sprintf("vhostHTTPPort = %d\n", cfg.VhostHTTPPort)) + fmt.Fprintf(&buf, "vhostHTTPPort = %d\n", cfg.VhostHTTPPort) } if cfg.AuthToken != "" { buf.WriteString("[auth]\n") buf.WriteString("method = \"token\"\n") - buf.WriteString(fmt.Sprintf("token = \"%s\"\n", cfg.AuthToken)) + fmt.Fprintf(&buf, "token = \"%s\"\n", cfg.AuthToken) } // WebServer configuration @@ -162,19 +186,20 @@ func (m *Manager) renderConfig(cfg *service.RelayConfig) error { } else { buf.WriteString("addr = \"127.0.0.1\"\n") } - buf.WriteString(fmt.Sprintf("port = %d\n", 17500)) + fmt.Fprintf(&buf, "port = %d\n", defaultFrpsWebServerPort) buf.WriteString("user = \"admin\"\n") password := m.agentToken if password == "" { password = "admin" } - buf.WriteString(fmt.Sprintf("password = \"%s\"\n", password)) + fmt.Fprintf(&buf, "password = \"%s\"\n", password) - return os.WriteFile(m.configPath, buf.Bytes(), 0644) + return os.WriteFile(m.configPath, buf.Bytes(), frpsConfigFilePerm) } -func (m *Manager) supervise(generation uint64) { +func (m *Manager) supervise(ctx context.Context, generation uint64) { + procCtx := context.WithoutCancel(ctx) backoff := 1 * time.Second const maxBackoff = 60 * time.Second @@ -187,13 +212,13 @@ func (m *Manager) supervise(generation uint64) { ensureNoOrphanProcess(m.pidPath) - cmd := exec.Command(m.frpsPath, "-c", m.configPath) + cmd := exec.CommandContext(procCtx, m.frpsPath, "-c", m.configPath) //nolint:gosec // frpsPath and configPath are managed trusted locations cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Start() if err != nil { - m.status = "unhealthy" + m.status = statusUnhealthy m.lastError = fmt.Sprintf("failed to start: %v", err) slog.Error("failed to start frps", "error", err, "generation", generation) m.mu.Unlock() @@ -201,14 +226,14 @@ func (m *Manager) supervise(generation uint64) { if !m.sleepOrInterrupt(generation, backoff) { return } - backoff = backoff * 2 + backoff *= 2 if backoff > maxBackoff { backoff = maxBackoff } continue } - _ = os.WriteFile(m.pidPath, []byte(fmt.Sprintf("%d", cmd.Process.Pid)), 0644) + _ = os.WriteFile(m.pidPath, []byte(fmt.Sprintf("%d", cmd.Process.Pid)), frpsPidFilePerm) m.cmd = cmd m.status = "healthy" @@ -222,7 +247,7 @@ func (m *Manager) supervise(generation uint64) { m.mu.Lock() if m.cmd == cmd { m.cmd = nil - m.status = "unhealthy" + m.status = statusUnhealthy if waitErr != nil { m.lastError = fmt.Sprintf("exited with error: %v", waitErr) } else { @@ -244,7 +269,7 @@ func (m *Manager) supervise(generation uint64) { if !m.sleepOrInterrupt(generation, backoff) { return } - backoff = backoff * 2 + backoff *= 2 if backoff > maxBackoff { backoff = maxBackoff } @@ -252,24 +277,24 @@ func (m *Manager) supervise(generation uint64) { } func (m *Manager) sleepOrInterrupt(generation uint64, d time.Duration) bool { - ticker := time.NewTicker(100 * time.Millisecond) + ticker := time.NewTicker(frpsSupervisorPollInterval) defer ticker.Stop() deadline := time.Now().Add(d) for time.Now().Before(deadline) { - select { - case <-ticker.C: - m.mu.RLock() - interrupted := m.stopping || m.generation != generation - m.mu.RUnlock() - if interrupted { - return false - } + <-ticker.C + m.mu.RLock() + interrupted := m.stopping || m.generation != generation + m.mu.RUnlock() + if interrupted { + return false } } return true } +// Stop signals the supervisor to cease restarting frps and kills the running +// process if one exists. func (m *Manager) Stop() { m.mu.Lock() defer m.mu.Unlock() @@ -280,11 +305,11 @@ func (m *Manager) Stop() { m.cmd = nil } _ = os.Remove(m.pidPath) - m.status = "unhealthy" + m.status = statusUnhealthy } func ensureNoOrphanProcess(pidPath string) { - data, err := os.ReadFile(pidPath) + data, err := os.ReadFile(pidPath) //nolint:gosec // pidPath is a managed internal path, not user input if err != nil { return } @@ -300,7 +325,7 @@ func ensureNoOrphanProcess(pidPath string) { slog.Warn("attempting to kill potentially orphan process", "pid", pid, "pid_path", pidPath) _ = process.Kill() // Wait a little bit to ensure the OS has reclaimed ports - time.Sleep(500 * time.Millisecond) + time.Sleep(frpsOrphanProcessCleanupDelay) } _ = os.Remove(pidPath) } diff --git a/internal/apps/relay/frps/manager_test.go b/internal/apps/relay/frps/manager_test.go index 546239dc..c11ddd9b 100644 --- a/internal/apps/relay/frps/manager_test.go +++ b/internal/apps/relay/frps/manager_test.go @@ -1,6 +1,7 @@ package frps import ( + "context" "fmt" "os" "os/exec" @@ -93,7 +94,7 @@ func TestStartProcessSuccess(t *testing.T) { WebServerEnabled: false, } - m.UpdateConfig(cfg) + m.UpdateConfig(context.Background(), cfg) assertStatusEventually(t, m, "healthy", 2*time.Second) @@ -117,7 +118,7 @@ func TestStartProcessFailureAndBackoff(t *testing.T) { WebServerEnabled: false, } - m.UpdateConfig(cfg) + m.UpdateConfig(context.Background(), cfg) assertStatusEventually(t, m, "unhealthy", 2*time.Second) @@ -158,7 +159,7 @@ func TestUnexpectedExitAndAutorestart(t *testing.T) { WebServerEnabled: false, } - m.UpdateConfig(cfg) + m.UpdateConfig(context.Background(), cfg) assertStatusEventually(t, m, "unhealthy", 2*time.Second) @@ -189,7 +190,7 @@ func TestBackoffReset(t *testing.T) { WebServerEnabled: false, } - m.UpdateConfig(cfg) + m.UpdateConfig(context.Background(), cfg) // Crashed once, backoff is 2s assertStatusEventually(t, m, "unhealthy", 2*time.Second) @@ -232,7 +233,7 @@ func TestImmediateRestartOnSameConfigDeadProcess(t *testing.T) { WebServerEnabled: false, } - m.UpdateConfig(cfg) + m.UpdateConfig(context.Background(), cfg) // Let it crash assertStatusEventually(t, m, "unhealthy", 2*time.Second) @@ -241,7 +242,7 @@ func TestImmediateRestartOnSameConfigDeadProcess(t *testing.T) { writeControl(t, dir, 0, 5) // Send same config block to trigger immediate restart bypass of backoff sleep - m.UpdateConfig(cfg) + m.UpdateConfig(context.Background(), cfg) // Check if it started immediately assertStatusEventually(t, m, "healthy", 2*time.Second) @@ -261,7 +262,7 @@ func TestSupervisorGenerationInterrupt(t *testing.T) { WebServerEnabled: false, } - m.UpdateConfig(cfg) + m.UpdateConfig(context.Background(), cfg) assertStatusEventually(t, m, "healthy", 2*time.Second) @@ -281,7 +282,7 @@ func TestSupervisorGenerationInterrupt(t *testing.T) { AuthToken: "test-auth", WebServerEnabled: false, } - m.UpdateConfig(cfg2) + m.UpdateConfig(context.Background(), cfg2) assertStatusEventually(t, m, "healthy", 2*time.Second) @@ -338,7 +339,7 @@ func TestUpdateConfigKillsOrphanProcessBeforeRestart(t *testing.T) { WebServerEnabled: false, } - m.UpdateConfig(cfg) + m.UpdateConfig(context.Background(), cfg) assertCommandExitedEventually(t, orphan, 2*time.Second) assertStatusEventually(t, m, "healthy", 2*time.Second) diff --git a/internal/apps/relay/heartbeat/service.go b/internal/apps/relay/heartbeat/service.go index 8dd91444..b42311ad 100644 --- a/internal/apps/relay/heartbeat/service.go +++ b/internal/apps/relay/heartbeat/service.go @@ -1,19 +1,22 @@ +// Package heartbeat sends periodic relay node status to the control plane. package heartbeat import ( "context" "log/slog" + edgeheartbeat "github.com/Rain-kl/Wavelet/internal/apps/edge/heartbeat" "github.com/Rain-kl/Wavelet/internal/apps/relay/config" "github.com/Rain-kl/Wavelet/internal/apps/relay/frps" "github.com/Rain-kl/Wavelet/internal/apps/relay/httpclient" "github.com/Rain-kl/Wavelet/internal/apps/relay/observability" "github.com/Rain-kl/Wavelet/internal/apps/relay/state" - edgeheartbeat "github.com/Rain-kl/Wavelet/internal/apps/edge/heartbeat" "github.com/Rain-kl/Wavelet/internal/apps/relay/updater" service "github.com/Rain-kl/Wavelet/pkg/protocol" ) +// Service sends periodic heartbeat payloads to the server, updates the frps +// configuration from the server response, and triggers auto-update checks. type Service struct { client *httpclient.Client frpsManager *frps.Manager @@ -22,6 +25,8 @@ type Service struct { updater *updater.Service } +// New constructs a Service using the provided HTTP client, frps manager, +// configuration, and persistent state store. func New(client *httpclient.Client, manager *frps.Manager, cfg *config.Config, stateStore *state.Store) *Service { return &Service{ client: client, @@ -32,6 +37,7 @@ func New(client *httpclient.Client, manager *frps.Manager, cfg *config.Config, s } } +// Run starts the heartbeat loop and blocks until ctx is cancelled. func (s *Service) Run(ctx context.Context) { edgeheartbeat.RunLoop(ctx, s.config.HeartbeatInterval.Duration(), s.doHeartbeat) } @@ -42,7 +48,7 @@ func (s *Service) doHeartbeat(ctx context.Context) { runtimeStatus := s.frpsManager.GetRuntimeStatus() payload := service.RelayHeartbeatPayload{ Version: config.Version, - ExtVersion: s.frpsManager.GetVersion(), + ExtVersion: s.frpsManager.GetVersion(ctx), RelayStatus: runtimeStatus.Status, FrpsConnCount: runtimeStatus.Connections, FrpsProxyCount: runtimeStatus.ProxyCount, @@ -63,10 +69,12 @@ func (s *Service) doHeartbeat(ctx context.Context) { slog.Debug("heartbeat succeeded") // Update configs if changed - s.frpsManager.UpdateConfig(resp.RelayConfig) + if resp != nil { + s.frpsManager.UpdateConfig(ctx, resp.RelayConfig) - if resp != nil && resp.RelaySettings != nil { - edgeheartbeat.TryAutoUpdate(ctx, s.updater, relaySettingsToAutoUpdate(resp.RelaySettings), "relay") + if resp.RelaySettings != nil { + edgeheartbeat.TryAutoUpdate(ctx, s.updater, relaySettingsToAutoUpdate(resp.RelaySettings), "relay") + } } } diff --git a/internal/apps/relay/httpclient/client.go b/internal/apps/relay/httpclient/client.go index 641e4c7f..0d584be0 100644 --- a/internal/apps/relay/httpclient/client.go +++ b/internal/apps/relay/httpclient/client.go @@ -1,3 +1,4 @@ +// Package httpclient provides an HTTP client for relay control-plane communication. package httpclient import ( @@ -8,21 +9,25 @@ import ( service "github.com/Rain-kl/Wavelet/pkg/protocol" ) +// APIResponse wraps a control-plane API response with an optional error message. type APIResponse[T any] struct { ErrorMsg string `json:"error_msg"` Data T `json:"data"` } +// Client sends authenticated requests to the relay control-plane API. type Client struct { base *edgehttp.Client } +// New creates a relay HTTP client with the given base URL, token, and timeout. func New(baseURL string, token string, timeout time.Duration) *Client { return &Client{ base: edgehttp.New(baseURL, token, timeout, "X-Agent-Token"), } } +// Heartbeat sends a relay heartbeat payload to the control plane. func (c *Client) Heartbeat(ctx context.Context, payload service.RelayHeartbeatPayload) (*service.RelayHeartbeatResponse, error) { resp := APIResponse[service.RelayHeartbeatResponse]{} if err := c.base.PostJSON(ctx, "/api/v1/relay/heartbeat", payload, &resp); err != nil { @@ -34,6 +39,7 @@ func (c *Client) Heartbeat(ctx context.Context, payload service.RelayHeartbeatPa return &resp.Data, nil } +// SetToken updates the authentication token used for API requests. func (c *Client) SetToken(token string) { c.base.SetToken(token) -} \ No newline at end of file +} diff --git a/internal/apps/relay/observability/collector.go b/internal/apps/relay/observability/collector.go index b2d4117c..1a6a45d0 100644 --- a/internal/apps/relay/observability/collector.go +++ b/internal/apps/relay/observability/collector.go @@ -1,3 +1,4 @@ +// Package observability collects relay node profile data for heartbeat reporting. package observability import ( @@ -16,6 +17,8 @@ import ( service "github.com/Rain-kl/Wavelet/pkg/protocol" ) +// BuildProfile collects the node's system profile and returns it only when the +// fingerprint has changed since the last heartbeat, avoiding redundant uploads. func BuildProfile(cfg *config.Config, stateStore *state.Store) *service.AgentNodeSystemProfile { profile := collectProfile(cfg) if profile == nil || stateStore == nil { @@ -36,6 +39,9 @@ func BuildProfile(cfg *config.Config, stateStore *state.Store) *service.AgentNod return profile } +// BuildSnapshot captures a point-in-time metric snapshot including memory, +// disk, network I/O, and CPU usage computed from a delta against the last +// persisted CPU stat. func BuildSnapshot(cfg *config.Config, stateStore *state.Store) *service.AgentNodeMetricSnapshot { now := time.Now().UTC() metric := &service.AgentNodeMetricSnapshot{CapturedAtUnix: now.Unix()} @@ -67,6 +73,8 @@ func BuildSnapshot(cfg *config.Config, stateStore *state.Store) *service.AgentNo return metric } +// BuildHealthEvents converts a RuntimeStatus into a list of health events. +// An empty slice is returned when frps is healthy. func BuildHealthEvents(status frps.RuntimeStatus) []service.AgentNodeHealthEvent { if strings.TrimSpace(status.Status) == "healthy" { return []service.AgentNodeHealthEvent{} @@ -110,4 +118,4 @@ func fingerprintProfile(profile *service.AgentNodeSystemProfile) string { } sum := sha256.Sum256(raw) return hex.EncodeToString(sum[:]) -} \ No newline at end of file +} diff --git a/internal/apps/relay/relay/runner.go b/internal/apps/relay/relay/runner.go index f6a0c64e..658e8dfe 100644 --- a/internal/apps/relay/relay/runner.go +++ b/internal/apps/relay/relay/runner.go @@ -1,3 +1,4 @@ +// Package relay implements the relay node daemon runtime loop. package relay import ( @@ -15,15 +16,17 @@ import ( service "github.com/Rain-kl/Wavelet/pkg/protocol" ) +// Runner manages the relay process. type Runner struct { Config *config.Config StateStore *state.Store HeartbeatService *heartbeat.Service FrpsManager *frps.Manager WebSocketService *wsclient.Client - HttpClient *httpclient.Client + HTTPClient *httpclient.Client } +// Run starts the relay process by initiating the heartbeat and WS reconnection loop. func (r *Runner) Run(ctx context.Context) error { go r.HeartbeatService.Run(ctx) @@ -41,7 +44,7 @@ type relayWSHandler struct { runner *Runner } -func (h *relayWSHandler) OnConnect(ctx context.Context) error { +func (h *relayWSHandler) OnConnect(_ context.Context) error { return nil } @@ -53,7 +56,7 @@ func (h *relayWSHandler) HandleMessage(ctx context.Context, msg wsclient.WSMessa slog.Error("failed to unmarshal relay_config", "error", err) return nil } - h.runner.FrpsManager.UpdateConfig(&cfg) + h.runner.FrpsManager.UpdateConfig(ctx, &cfg) default: slog.Debug("ignored unknown ws message type", "type", msg.Type) } @@ -71,4 +74,4 @@ func (r *Runner) handleConnection(ctx context.Context, conn edgerunner.WSConnect return } _ = wsConn.RunReceiveLoop(ctx, &relayWSHandler{runner: r}) -} \ No newline at end of file +} diff --git a/internal/apps/relay/state/store.go b/internal/apps/relay/state/store.go index 84832803..dff9634b 100644 --- a/internal/apps/relay/state/store.go +++ b/internal/apps/relay/state/store.go @@ -1,3 +1,4 @@ +// Package state persists relay runtime state to disk. package state import ( @@ -7,11 +8,15 @@ import ( "sync" ) +const relayStateFilePerm = 0o600 + +// Store reads and writes relay state from a JSON file. type Store struct { path string mu sync.RWMutex } +// State holds persisted relay metrics and authentication state. type State struct { LastAuthToken string `json:"last_auth_token"` LastProfileFingerprint string `json:"last_profile_fingerprint"` @@ -20,12 +25,14 @@ type State struct { LastMetricAtUnix int64 `json:"last_metric_at_unix"` } +// NewStore creates a state store backed by the file at path. func NewStore(path string) *Store { return &Store{ path: path, } } +// Load reads the persisted state from disk, returning an empty state if the file is missing. func (s *Store) Load() (*State, error) { s.mu.RLock() defer s.mu.RUnlock() @@ -45,6 +52,7 @@ func (s *Store) Load() (*State, error) { return &state, nil } +// Save writes state to disk. func (s *Store) Save(state *State) error { s.mu.Lock() defer s.mu.Unlock() @@ -55,5 +63,5 @@ func (s *Store) Save(state *State) error { } slog.Debug("saving relay state") - return os.WriteFile(s.path, data, 0644) + return os.WriteFile(s.path, data, relayStateFilePerm) } diff --git a/internal/apps/relay/updater/updater.go b/internal/apps/relay/updater/updater.go index 3536cda2..d413f2c0 100644 --- a/internal/apps/relay/updater/updater.go +++ b/internal/apps/relay/updater/updater.go @@ -1,3 +1,4 @@ +// Package updater provides relay self-update integration with the edge updater. package updater import ( @@ -5,13 +6,17 @@ import ( "github.com/Rain-kl/Wavelet/internal/apps/relay/config" ) +// Service is an alias for the edge updater Service. type Service = edgeupdater.Service + +// UpdateOptions is an alias for the edge updater UpdateOptions. type UpdateOptions = edgeupdater.UpdateOptions +// New creates and initializes a new updater Service for the relay. func New() *Service { return edgeupdater.New(edgeupdater.Config{ LocalVersion: config.Version, AssetPrefix: "openflare-relay", LogLabel: "relay", }) -} \ No newline at end of file +} diff --git a/internal/apps/relay/wsclient/client.go b/internal/apps/relay/wsclient/client.go index e05d7c73..d71d83cc 100644 --- a/internal/apps/relay/wsclient/client.go +++ b/internal/apps/relay/wsclient/client.go @@ -1,3 +1,4 @@ +// Package wsclient provides a WebSocket client for relay control-plane communication. package wsclient import ( @@ -7,24 +8,33 @@ import ( edgews "github.com/Rain-kl/Wavelet/internal/apps/edge/wsclient" ) +// WSMessage is a WebSocket message exchanged with the control plane. type WSMessage = edgews.WSMessage + +// MessageHandler processes incoming WebSocket messages. type MessageHandler = edgews.MessageHandler + +// Connection represents an active WebSocket connection. type Connection = edgews.Connection +// Client connects to the relay WebSocket endpoint on the control plane. type Client struct { inner *edgews.Client } +// New creates a WebSocket client for the relay control-plane endpoint. func New(baseURL, token string, timeout time.Duration) *Client { return &Client{ inner: edgews.New(edgews.PresetRelay, baseURL, token, timeout), } } +// SetToken updates the authentication token used for the WebSocket connection. func (c *Client) SetToken(token string) { c.inner.SetToken(token) } +// Connect establishes a WebSocket connection to the control plane. func (c *Client) Connect(ctx context.Context) (*Connection, error) { return c.inner.Connect(ctx) -} \ No newline at end of file +} diff --git a/internal/model/auth_source.go b/internal/model/auth_source.go index d1d65d74..579a63cd 100644 --- a/internal/model/auth_source.go +++ b/internal/model/auth_source.go @@ -19,6 +19,16 @@ const ( AuthSourceTypeOIDC = "oidc" ) +// Shared GORM column name constants used across model package. +const ( + colName = "name" + colEnabled = "enabled" + colRemark = "remark" + tableOfProxyRoutes = "of_proxy_routes" + optionKeyUptimeKumaPassword = "UptimeKumaPassword" + optionValueTrue = "true" +) + var authSourceNamePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9_-]{0,79}$`) // AuthSource 认证源实体 @@ -192,7 +202,7 @@ func UpdateAuthSource(ctx context.Context, source *AuthSource, keepSecret bool) return err } return db.DB(ctx).Model(¤t).Updates(map[string]any{ - "name": source.Name, + colName: source.Name, "type": source.Type, "display_name": source.DisplayName, "is_active": source.IsActive, diff --git a/internal/model/openflare_access_log.go b/internal/model/openflare_access_log.go index ee93f442..338ca74b 100644 --- a/internal/model/openflare_access_log.go +++ b/internal/model/openflare_access_log.go @@ -5,11 +5,19 @@ package model import ( "context" + "math" "sort" "strings" "time" ) +const ( + columnRemoteAddr = "remote_addr" + columnHost = "host" + sortOrderAsc = "asc" + secondsPerMinute = 60 +) + type openFlareAccessLogBucketAggregateRow struct { BucketEpoch int64 `gorm:"column:bucket_epoch"` RequestCount int64 `gorm:"column:request_count"` @@ -147,7 +155,7 @@ func ListOpenFlareAccessLogIPTrend(ctx context.Context, query OpenFlareAccessLog Host: query.Host, Since: query.Since, } - bucketSeconds := int64(query.BucketMinutes * 60) + bucketSeconds := int64(query.BucketMinutes * secondsPerMinute) if bucketSeconds <= 0 { bucketSeconds = 1800 } @@ -182,7 +190,7 @@ func DeleteOpenFlareAccessLogsByNodeBefore(ctx context.Context, nodeID string, c func buildOpenFlareAccessLogBucketRows(ctx context.Context, query OpenFlareAccessLogBucketQuery) ([]*OpenFlareAccessLogBucketRow, error) { filter := openFlareAccessLogQueryFromBucket(query) - bucketSeconds := int64(query.FoldMinutes * 60) + bucketSeconds := int64(query.FoldMinutes * secondsPerMinute) if bucketSeconds <= 0 { bucketSeconds = 180 } @@ -216,7 +224,7 @@ func buildOpenFlareAccessLogBucketRows(ctx context.Context, query OpenFlareAcces accumulator.serverErrorCount += partial.ServerErrorCount } - for _, column := range []string{"remote_addr", "host"} { + for _, column := range []string{columnRemoteAddr, columnHost} { dimensions, err := currentAccessLogStore().BucketDimensions(ctx, filter, column, bucketSeconds) if err != nil { return nil, err @@ -235,9 +243,9 @@ func buildOpenFlareAccessLogBucketRows(ctx context.Context, query OpenFlareAcces continue } switch column { - case "remote_addr": + case columnRemoteAddr: accumulator.uniqueIPs[trimmed] = struct{}{} - case "host": + case columnHost: accumulator.uniqueHosts[trimmed] = struct{}{} } } @@ -346,7 +354,7 @@ func openFlareAccessLogQueryFromBucket(query OpenFlareAccessLogBucketQuery) Open } func sortOpenFlareAccessLogBucketIPRows(items []*OpenFlareAccessLogBucketIPRow, sortBy string, sortOrder string) { - desc := openFlareAccessLogNormalizeSortOrder(sortOrder) != "asc" + desc := openFlareAccessLogNormalizeSortOrder(sortOrder) != sortOrderAsc sort.Slice(items, func(i, j int) bool { left := items[i] right := items[j] @@ -376,7 +384,7 @@ func sortOpenFlareAccessLogBucketIPRows(items []*OpenFlareAccessLogBucketIPRow, } func sortOpenFlareAccessLogBucketRows(items []*OpenFlareAccessLogBucketRow, sortBy string, sortOrder string) { - desc := openFlareAccessLogNormalizeSortOrder(sortOrder) != "asc" + desc := openFlareAccessLogNormalizeSortOrder(sortOrder) != sortOrderAsc sort.Slice(items, func(i, j int) bool { left := items[i] right := items[j] @@ -401,7 +409,7 @@ func sortOpenFlareAccessLogBucketRows(items []*OpenFlareAccessLogBucketRow, sort } func sortOpenFlareAccessLogIPSummaryRows(items []*OpenFlareAccessLogIPSummaryRow, sortBy string, sortOrder string) { - desc := openFlareAccessLogNormalizeSortOrder(sortOrder) != "asc" + desc := openFlareAccessLogNormalizeSortOrder(sortOrder) != sortOrderAsc sort.Slice(items, func(i, j int) bool { left := items[i] right := items[j] @@ -451,12 +459,30 @@ func openFlareAccessLogPaginateBounds(total int, page int, pageSize int) (int, i } func openFlareAccessLogNormalizeSortOrder(sortOrder string) string { - if strings.EqualFold(strings.TrimSpace(sortOrder), "asc") { - return "asc" + if strings.EqualFold(strings.TrimSpace(sortOrder), sortOrderAsc) { + return sortOrderAsc } return "desc" } +func openFlareAccessLogStatusCodeToInt32(code int) int32 { + switch { + case code > math.MaxInt32: + return math.MaxInt32 + case code < math.MinInt32: + return math.MinInt32 + default: + return int32(code) + } +} + +func openFlareAccessLogUintToInt64(value uint) int64 { + if value > math.MaxInt64 { + return math.MaxInt64 + } + return int64(value) +} + func openFlareAccessLogCompareInt64(left int64, right int64) int { switch { case left > right: @@ -467,4 +493,3 @@ func openFlareAccessLogCompareInt64(left int64, right int64) int { return 0 } } - diff --git a/internal/model/openflare_access_log_store.go b/internal/model/openflare_access_log_store.go index 37532678..6d135ec9 100644 --- a/internal/model/openflare_access_log_store.go +++ b/internal/model/openflare_access_log_store.go @@ -221,7 +221,7 @@ func toAnalyticsNodeAccessLog(record *OpenFlareAccessLog) analyticsmodel.NodeAcc Region: record.Region, Host: record.Host, Path: record.Path, - StatusCode: int32(record.StatusCode), + StatusCode: openFlareAccessLogStatusCodeToInt32(record.StatusCode), CreatedAt: record.CreatedAt, } } @@ -242,4 +242,4 @@ func fromAnalyticsNodeAccessLogs(rows []analyticsmodel.NodeAccessLog) []*OpenFla } } return result -} \ No newline at end of file +} diff --git a/internal/model/openflare_access_log_store_memory.go b/internal/model/openflare_access_log_store_memory.go index 9ca71659..704b82a9 100644 --- a/internal/model/openflare_access_log_store_memory.go +++ b/internal/model/openflare_access_log_store_memory.go @@ -133,9 +133,9 @@ func (s *memoryAccessLogStore) BucketDimensions(_ context.Context, filter OpenFl for _, row := range rows { var value string switch column { - case "remote_addr": + case columnRemoteAddr: value = strings.TrimSpace(row.RemoteAddr) - case "host": + case columnHost: value = strings.TrimSpace(row.Host) default: continue @@ -345,7 +345,7 @@ func cloneAccessLogSlice(rows []*OpenFlareAccessLog) []*OpenFlareAccessLog { } func sortOpenFlareAccessLogRows(items []*OpenFlareAccessLog, sortBy string, sortOrder string) { - desc := openFlareAccessLogNormalizeSortOrder(sortOrder) != "asc" + desc := openFlareAccessLogNormalizeSortOrder(sortOrder) != sortOrderAsc sort.Slice(items, func(i, j int) bool { left := items[i] right := items[j] @@ -356,9 +356,9 @@ func sortOpenFlareAccessLogRows(items []*OpenFlareAccessLog, sortBy string, sort switch strings.TrimSpace(sortBy) { case "status_code": compare = left.StatusCode - right.StatusCode - case "remote_addr": + case columnRemoteAddr: compare = strings.Compare(left.RemoteAddr, right.RemoteAddr) - case "host": + case columnHost: compare = strings.Compare(left.Host, right.Host) case "path": compare = strings.Compare(left.Path, right.Path) @@ -369,11 +369,11 @@ func sortOpenFlareAccessLogRows(items []*OpenFlareAccessLog, sortBy string, sort compare = openFlareAccessLogCompareInt64(left.LoggedAt.Unix(), right.LoggedAt.Unix()) } if compare == 0 { - compare = openFlareAccessLogCompareInt64(int64(left.ID), int64(right.ID)) + compare = openFlareAccessLogCompareInt64(openFlareAccessLogUintToInt64(left.ID), openFlareAccessLogUintToInt64(right.ID)) } if desc { return compare > 0 } return compare < 0 }) -} \ No newline at end of file +} diff --git a/internal/model/openflare_observability.go b/internal/model/openflare_observability.go index d0d8125f..95d52ac9 100644 --- a/internal/model/openflare_observability.go +++ b/internal/model/openflare_observability.go @@ -285,56 +285,39 @@ func isMissingTableError(err error) bool { strings.Contains(msg, "does not exist") } -// ListOpenFlareMetricSnapshotsSince returns metric snapshots since the given time. -func ListOpenFlareMetricSnapshotsSince(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareMetricSnapshot, error) { +func listOpenFlareSince[T any](ctx context.Context, nodeID string, since time.Time, limit int, orderBy string, sinceColumn string) ([]*T, error) { conn := db.DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } - query := conn.Model(&OpenFlareMetricSnapshot{}).Order("captured_at desc, id desc") + query := conn.Model(new(T)).Order(orderBy) if nodeID != "" { query = query.Where("node_id = ?", nodeID) } if !since.IsZero() { - query = query.Where("captured_at >= ?", since) + query = query.Where(sinceColumn+" >= ?", since) } if limit > 0 { query = query.Limit(limit) } - var rows []*OpenFlareMetricSnapshot + var rows []*T if err := query.Find(&rows).Error; err != nil { if isMissingTableError(err) { - return []*OpenFlareMetricSnapshot{}, nil + return []*T{}, nil } return nil, err } return rows, nil } +// ListOpenFlareMetricSnapshotsSince returns metric snapshots since the given time. +func ListOpenFlareMetricSnapshotsSince(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareMetricSnapshot, error) { + return listOpenFlareSince[OpenFlareMetricSnapshot](ctx, nodeID, since, limit, "captured_at desc, id desc", "captured_at") +} + // ListOpenFlareRequestReportsSince returns request reports since the given time. func ListOpenFlareRequestReportsSince(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareRequestReport, error) { - conn := db.DB(ctx) - if conn == nil { - return nil, errors.New(errDatabaseNotInitialized) - } - query := conn.Model(&OpenFlareRequestReport{}).Order("window_ended_at desc, id desc") - if nodeID != "" { - query = query.Where("node_id = ?", nodeID) - } - if !since.IsZero() { - query = query.Where("window_ended_at >= ?", since) - } - if limit > 0 { - query = query.Limit(limit) - } - var rows []*OpenFlareRequestReport - if err := query.Find(&rows).Error; err != nil { - if isMissingTableError(err) { - return []*OpenFlareRequestReport{}, nil - } - return nil, err - } - return rows, nil + return listOpenFlareSince[OpenFlareRequestReport](ctx, nodeID, since, limit, "window_ended_at desc, id desc", "window_ended_at") } // ListOpenFlareActiveHealthEvents returns active health events across all nodes. @@ -474,78 +457,15 @@ func GetOpenFlareNodeSystemProfile(ctx context.Context, nodeID string) (*OpenFla // ListOpenFlareNodeObservationOpenresty returns openresty observations. func ListOpenFlareNodeObservationOpenresty(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareNodeObservationOpenresty, error) { - conn := db.DB(ctx) - if conn == nil { - return nil, errors.New(errDatabaseNotInitialized) - } - query := conn.Model(&OpenFlareNodeObservationOpenresty{}).Order("captured_at desc, id desc") - if nodeID != "" { - query = query.Where("node_id = ?", nodeID) - } - if !since.IsZero() { - query = query.Where("captured_at >= ?", since) - } - if limit > 0 { - query = query.Limit(limit) - } - var rows []*OpenFlareNodeObservationOpenresty - if err := query.Find(&rows).Error; err != nil { - if isMissingTableError(err) { - return []*OpenFlareNodeObservationOpenresty{}, nil - } - return nil, err - } - return rows, nil + return listOpenFlareSince[OpenFlareNodeObservationOpenresty](ctx, nodeID, since, limit, "captured_at desc, id desc", "captured_at") } // ListOpenFlareNodeObservationFrpc returns frpc observations. func ListOpenFlareNodeObservationFrpc(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareNodeObservationFrpc, error) { - conn := db.DB(ctx) - if conn == nil { - return nil, errors.New(errDatabaseNotInitialized) - } - query := conn.Model(&OpenFlareNodeObservationFrpc{}).Order("captured_at desc, id desc") - if nodeID != "" { - query = query.Where("node_id = ?", nodeID) - } - if !since.IsZero() { - query = query.Where("captured_at >= ?", since) - } - if limit > 0 { - query = query.Limit(limit) - } - var rows []*OpenFlareNodeObservationFrpc - if err := query.Find(&rows).Error; err != nil { - if isMissingTableError(err) { - return []*OpenFlareNodeObservationFrpc{}, nil - } - return nil, err - } - return rows, nil + return listOpenFlareSince[OpenFlareNodeObservationFrpc](ctx, nodeID, since, limit, "captured_at desc, id desc", "captured_at") } // ListOpenFlareNodeObservationFrps returns frps observations. func ListOpenFlareNodeObservationFrps(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareNodeObservationFrps, error) { - conn := db.DB(ctx) - if conn == nil { - return nil, errors.New(errDatabaseNotInitialized) - } - query := conn.Model(&OpenFlareNodeObservationFrps{}).Order("captured_at desc, id desc") - if nodeID != "" { - query = query.Where("node_id = ?", nodeID) - } - if !since.IsZero() { - query = query.Where("captured_at >= ?", since) - } - if limit > 0 { - query = query.Limit(limit) - } - var rows []*OpenFlareNodeObservationFrps - if err := query.Find(&rows).Error; err != nil { - if isMissingTableError(err) { - return []*OpenFlareNodeObservationFrps{}, nil - } - return nil, err - } - return rows, nil + return listOpenFlareSince[OpenFlareNodeObservationFrps](ctx, nodeID, since, limit, "captured_at desc, id desc", "captured_at") } diff --git a/internal/model/openflare_option.go b/internal/model/openflare_option.go index 14fa7130..623769a2 100644 --- a/internal/model/openflare_option.go +++ b/internal/model/openflare_option.go @@ -34,81 +34,81 @@ var ( StartTime = time.Now().Unix() // Hot-reload mirrors for frequently read options (legacy OpenFlare keys). - SystemName = "OpenFlare" - ServerAddress = "" - Footer = "" - HomePageLink = "" - PasswordLoginEnabled = true - CapLoginEnabled = true - PasswordRegisterEnabled = false - EmailVerificationEnabled = false - GitHubOAuthEnabled = false - WeChatAuthEnabled = false - GitHubClientId = "" - GitHubClientSecret = "" - WeChatServerAddress = "" - WeChatServerToken = "" - WeChatAccountQRCodeImageURL = "" - SMTPServer = "" - SMTPPort = 587 - SMTPAccount = "" - SMTPToken = "" - AgentDiscoveryToken = "" - AgentHeartbeatInterval = 10000 - AgentWebsocketUpgradeEnabled = true - NodeOfflineThreshold = 2 * time.Minute - AgentUpdateRepo = "Rain-kl/OpenFlare" - GeoIPProvider = "ipinfo" - DatabaseAutoCleanupEnabled = false - DatabaseAutoCleanupRetentionDays = 30 - UptimeKumaEnabled = false - UptimeKumaUrl = "" - UptimeKumaUsername = "" - UptimeKumaPassword = "" - UptimeKumaMonitorScope = "all" - UptimeKumaSelectedSites = "" - UptimeKumaSyncInterval = 5 - UptimeKumaInterval = 60 - UptimeKumaRetry = 0 - UptimeKumaRetryInterval = 60 - UptimeKumaTimeout = 48 - OpenRestyDefaultServerReturnStatus = 421 - OpenRestyWorkerProcesses = "auto" - OpenRestyWorkerConnections = 4096 - OpenRestyWorkerRlimitNofile = 65535 - OpenRestyEventsUse = "epoll" - OpenRestyEventsMultiAcceptEnabled = true - OpenRestyKeepaliveTimeout = 20 - OpenRestyKeepaliveRequests = 1000 - OpenRestyClientHeaderTimeout = 15 - OpenRestyClientBodyTimeout = 15 - OpenRestyClientMaxBodySize = "64m" - OpenRestyLargeClientHeaderBuffers = "4 16k" - OpenRestySendTimeout = 30 - OpenRestyResolvers = "" - OpenRestyProxyConnectTimeout = 3 - OpenRestyProxySendTimeout = 60 - OpenRestyProxyReadTimeout = 60 - OpenRestyWebsocketEnabled = true - OpenRestyHTTP3Enabled = true - OpenRestyProxyRequestBufferingEnabled = false - OpenRestyProxyBufferingEnabled = true - OpenRestyProxyBuffers = "16 16k" - OpenRestyProxyBufferSize = "8k" - OpenRestyProxyBusyBuffersSize = "64k" - OpenRestyGzipEnabled = true - OpenRestyGzipMinLength = 1024 - OpenRestyGzipCompLevel = 5 - OpenRestyCacheEnabled = false - OpenRestyCachePath = "" - OpenRestyCacheLevels = "1:2" - OpenRestyCacheInactive = "30m" - OpenRestyCacheMaxSize = "1g" - OpenRestyCacheKeyTemplate = "$scheme$host$request_uri" - OpenRestyCacheLockEnabled = true - OpenRestyCacheLockTimeout = "5s" - OpenRestyCacheUseStale = "error timeout updating http_500 http_502 http_503 http_504" - OpenRestyMainConfigTemplate = defaultOpenRestyMainConfigTemplate + SystemName = "OpenFlare" + ServerAddress = "" + Footer = "" + HomePageLink = "" + PasswordLoginEnabled = true + CapLoginEnabled = true + PasswordRegisterEnabled = false + EmailVerificationEnabled = false + GitHubOAuthEnabled = false + WeChatAuthEnabled = false + GitHubClientID = "" + GitHubClientSecret = "" + WeChatServerAddress = "" + WeChatServerToken = "" + WeChatAccountQRCodeImageURL = "" + SMTPServer = "" + SMTPPort = 587 + SMTPAccount = "" + SMTPToken = "" + AgentDiscoveryToken = "" + AgentHeartbeatInterval = 10000 + AgentWebsocketUpgradeEnabled = true + NodeOfflineThreshold = 2 * time.Minute + AgentUpdateRepo = "Rain-kl/OpenFlare" + GeoIPProvider = "ipinfo" + DatabaseAutoCleanupEnabled = false + DatabaseAutoCleanupRetentionDays = 30 + UptimeKumaEnabled = false + UptimeKumaURL = "" + UptimeKumaUsername = "" + UptimeKumaPassword = "" + UptimeKumaMonitorScope = "all" + UptimeKumaSelectedSites = "" + UptimeKumaSyncInterval = 5 + UptimeKumaInterval = 60 + UptimeKumaRetry = 0 + UptimeKumaRetryInterval = 60 + UptimeKumaTimeout = 48 + OpenRestyDefaultServerReturnStatus = 421 + OpenRestyWorkerProcesses = "auto" + OpenRestyWorkerConnections = 4096 + OpenRestyWorkerRlimitNofile = 65535 + OpenRestyEventsUse = "epoll" + OpenRestyEventsMultiAcceptEnabled = true + OpenRestyKeepaliveTimeout = 20 + OpenRestyKeepaliveRequests = 1000 + OpenRestyClientHeaderTimeout = 15 + OpenRestyClientBodyTimeout = 15 + OpenRestyClientMaxBodySize = "64m" + OpenRestyLargeClientHeaderBuffers = "4 16k" + OpenRestySendTimeout = 30 + OpenRestyResolvers = "" + OpenRestyProxyConnectTimeout = 3 + OpenRestyProxySendTimeout = 60 + OpenRestyProxyReadTimeout = 60 + OpenRestyWebsocketEnabled = true + OpenRestyHTTP3Enabled = true + OpenRestyProxyRequestBufferingEnabled = false + OpenRestyProxyBufferingEnabled = true + OpenRestyProxyBuffers = "16 16k" + OpenRestyProxyBufferSize = "8k" + OpenRestyProxyBusyBuffersSize = "64k" + OpenRestyGzipEnabled = true + OpenRestyGzipMinLength = 1024 + OpenRestyGzipCompLevel = 5 + OpenRestyCacheEnabled = false + OpenRestyCachePath = "" + OpenRestyCacheLevels = "1:2" + OpenRestyCacheInactive = "30m" + OpenRestyCacheMaxSize = "1g" + OpenRestyCacheKeyTemplate = "$scheme$host$request_uri" + OpenRestyCacheLockEnabled = true + OpenRestyCacheLockTimeout = "5s" + OpenRestyCacheUseStale = "error timeout updating http_500 http_502 http_503 http_504" + OpenRestyMainConfigTemplate = defaultOpenRestyMainConfigTemplate ) const defaultOpenRestyMainConfigTemplate = `# This file is generated by OpenFlare. Do not edit manually. @@ -183,9 +183,9 @@ func DefaultOpenFlareOptions() map[string]string { "GeoIPProvider": GeoIPProvider, "DatabaseAutoCleanupEnabled": strconv.FormatBool(DatabaseAutoCleanupEnabled), "UptimeKumaEnabled": strconv.FormatBool(UptimeKumaEnabled), - "UptimeKumaUrl": UptimeKumaUrl, + "UptimeKumaUrl": UptimeKumaURL, "UptimeKumaUsername": UptimeKumaUsername, - "UptimeKumaPassword": UptimeKumaPassword, + optionKeyUptimeKumaPassword: UptimeKumaPassword, "UptimeKumaMonitorScope": UptimeKumaMonitorScope, "UptimeKumaSelectedSites": UptimeKumaSelectedSites, "UptimeKumaSyncInterval": strconv.Itoa(UptimeKumaSyncInterval), @@ -229,7 +229,7 @@ func DefaultOpenFlareOptions() map[string]string { "OpenRestyCacheLockEnabled": strconv.FormatBool(OpenRestyCacheLockEnabled), "OpenRestyCacheLockTimeout": OpenRestyCacheLockTimeout, "OpenRestyCacheUseStale": OpenRestyCacheUseStale, - "OpenRestyMainConfigTemplate": OpenRestyMainConfigTemplate, + "OpenRestyMainConfigTemplate": OpenRestyMainConfigTemplate, } } @@ -271,7 +271,7 @@ func UpdateOpenFlareOptions(ctx context.Context, options []OpenFlareOption) erro if err := db.DB(ctx).Transaction(func(tx *gorm.DB) error { for _, item := range options { - if item.Key == "UptimeKumaPassword" && strings.TrimSpace(item.Value) == "" { + if item.Key == optionKeyUptimeKumaPassword && strings.TrimSpace(item.Value) == "" { continue } option := OpenFlareOption{Key: item.Key} @@ -289,7 +289,7 @@ func UpdateOpenFlareOptions(ctx context.Context, options []OpenFlareOption) erro } for _, item := range options { - if item.Key == "UptimeKumaPassword" && strings.TrimSpace(item.Value) == "" { + if item.Key == optionKeyUptimeKumaPassword && strings.TrimSpace(item.Value) == "" { continue } applyOptionMap(item.Key, item.Value) @@ -316,112 +316,13 @@ func ResetOptionMapForTest() { func applyOptionMap(key, value string) { OptionMapRWMutex.Lock() + defer OptionMapRWMutex.Unlock() if OptionMap == nil { OptionMap = make(map[string]string) } OptionMap[key] = value - if strings.HasSuffix(key, "Enabled") { - boolValue := value == "true" - switch key { - case "PasswordRegisterEnabled": - PasswordRegisterEnabled = boolValue - case "PasswordLoginEnabled": - PasswordLoginEnabled = boolValue - case "CapLoginEnabled": - CapLoginEnabled = boolValue - case "EmailVerificationEnabled": - EmailVerificationEnabled = boolValue - case "GitHubOAuthEnabled": - GitHubOAuthEnabled = boolValue - case "WeChatAuthEnabled": - WeChatAuthEnabled = boolValue - } - } - switch key { - case "SMTPServer": - SMTPServer = value - case "SMTPPort": - if intValue, err := strconv.Atoi(value); err == nil { - SMTPPort = intValue - } - case "SMTPAccount": - SMTPAccount = value - case "SMTPToken": - SMTPToken = value - case "ServerAddress": - ServerAddress = value - case "GitHubClientId": - GitHubClientId = value - case "GitHubClientSecret": - GitHubClientSecret = value - case "Footer": - Footer = value - case "HomePageLink": - HomePageLink = value - case "SystemName": - SystemName = value - case "WeChatServerAddress": - WeChatServerAddress = value - case "WeChatServerToken": - WeChatServerToken = value - case "WeChatAccountQRCodeImageURL": - WeChatAccountQRCodeImageURL = value - case "AgentDiscoveryToken": - AgentDiscoveryToken = value - case "AgentHeartbeatInterval": - if v, err := strconv.Atoi(value); err == nil && v > 0 { - AgentHeartbeatInterval = v - } - case "AgentWebsocketUpgradeEnabled": - AgentWebsocketUpgradeEnabled = value == "true" - case "NodeOfflineThreshold": - if v, err := strconv.Atoi(value); err == nil && v > 0 { - NodeOfflineThreshold = time.Duration(v) * time.Millisecond - } - case "AgentUpdateRepo": - if value != "" { - AgentUpdateRepo = value - } - case "GeoIPProvider": - GeoIPProvider = value - case "UptimeKumaEnabled": - UptimeKumaEnabled = value == "true" - case "UptimeKumaUrl": - UptimeKumaUrl = value - case "UptimeKumaUsername": - UptimeKumaUsername = value - case "UptimeKumaPassword": - UptimeKumaPassword = value - case "UptimeKumaMonitorScope": - UptimeKumaMonitorScope = value - case "UptimeKumaSelectedSites": - UptimeKumaSelectedSites = value - case "UptimeKumaSyncInterval": - if v, err := strconv.Atoi(value); err == nil && v > 0 { - UptimeKumaSyncInterval = v - } - case "UptimeKumaInterval": - if v, err := strconv.Atoi(value); err == nil && v > 0 { - UptimeKumaInterval = v - } - case "UptimeKumaRetry": - if v, err := strconv.Atoi(value); err == nil && v >= 0 { - UptimeKumaRetry = v - } - case "UptimeKumaRetryInterval": - if v, err := strconv.Atoi(value); err == nil && v > 0 { - UptimeKumaRetryInterval = v - } - case "UptimeKumaTimeout": - if v, err := strconv.Atoi(value); err == nil && v > 0 { - UptimeKumaTimeout = v - } - case "DatabaseAutoCleanupEnabled": - DatabaseAutoCleanupEnabled = value == "true" - case "DatabaseAutoCleanupRetentionDays": - if v, err := strconv.Atoi(value); err == nil && v >= 1 { - DatabaseAutoCleanupRetentionDays = v - } - } - OptionMapRWMutex.Unlock() + applyEnabledOptionMirror(key, value) + applyStringOptionMirror(key, value) + applyIntegerOptionMirror(key, value) + applySpecialOptionMirror(key, value) } diff --git a/internal/model/openflare_option_apply.go b/internal/model/openflare_option_apply.go new file mode 100644 index 00000000..b2b22523 --- /dev/null +++ b/internal/model/openflare_option_apply.go @@ -0,0 +1,96 @@ +package model + +import ( + "strconv" + "strings" + "time" +) + +var enabledOptionAppliers = map[string]func(bool){ + "PasswordRegisterEnabled": func(v bool) { PasswordRegisterEnabled = v }, + "PasswordLoginEnabled": func(v bool) { PasswordLoginEnabled = v }, + "CapLoginEnabled": func(v bool) { CapLoginEnabled = v }, + "EmailVerificationEnabled": func(v bool) { EmailVerificationEnabled = v }, + "GitHubOAuthEnabled": func(v bool) { GitHubOAuthEnabled = v }, + "WeChatAuthEnabled": func(v bool) { WeChatAuthEnabled = v }, + "AgentWebsocketUpgradeEnabled": func(v bool) { AgentWebsocketUpgradeEnabled = v }, + "UptimeKumaEnabled": func(v bool) { UptimeKumaEnabled = v }, + "DatabaseAutoCleanupEnabled": func(v bool) { DatabaseAutoCleanupEnabled = v }, +} + +var stringOptionAppliers = map[string]func(string){ + "SMTPServer": func(v string) { SMTPServer = v }, + "SMTPAccount": func(v string) { SMTPAccount = v }, + "SMTPToken": func(v string) { SMTPToken = v }, + "ServerAddress": func(v string) { ServerAddress = v }, + "GitHubClientId": func(v string) { GitHubClientID = v }, + "GitHubClientSecret": func(v string) { GitHubClientSecret = v }, + "Footer": func(v string) { Footer = v }, + "HomePageLink": func(v string) { HomePageLink = v }, + "SystemName": func(v string) { SystemName = v }, + "WeChatServerAddress": func(v string) { WeChatServerAddress = v }, + "WeChatServerToken": func(v string) { WeChatServerToken = v }, + "WeChatAccountQRCodeImageURL": func(v string) { WeChatAccountQRCodeImageURL = v }, + "AgentDiscoveryToken": func(v string) { AgentDiscoveryToken = v }, + "GeoIPProvider": func(v string) { GeoIPProvider = v }, + "UptimeKumaUrl": func(v string) { UptimeKumaURL = v }, + "UptimeKumaUsername": func(v string) { UptimeKumaUsername = v }, + optionKeyUptimeKumaPassword: func(v string) { UptimeKumaPassword = v }, + "UptimeKumaMonitorScope": func(v string) { UptimeKumaMonitorScope = v }, + "UptimeKumaSelectedSites": func(v string) { UptimeKumaSelectedSites = v }, +} + +func applyEnabledOptionMirror(key, value string) { + applier, ok := enabledOptionAppliers[key] + if !ok { + return + } + applier(value == optionValueTrue) +} + +func applyStringOptionMirror(key, value string) { + if applier, ok := stringOptionAppliers[key]; ok { + applier(value) + } +} + +type intOptionApplier struct { + apply func(int) + min int +} + +var integerOptionAppliers = map[string]intOptionApplier{ + "SMTPPort": {apply: func(v int) { SMTPPort = v }}, + "AgentHeartbeatInterval": {apply: func(v int) { AgentHeartbeatInterval = v }, min: 1}, + "UptimeKumaSyncInterval": {apply: func(v int) { UptimeKumaSyncInterval = v }, min: 1}, + "UptimeKumaInterval": {apply: func(v int) { UptimeKumaInterval = v }, min: 1}, + "UptimeKumaRetry": {apply: func(v int) { UptimeKumaRetry = v }, min: 0}, + "UptimeKumaRetryInterval": {apply: func(v int) { UptimeKumaRetryInterval = v }, min: 1}, + "UptimeKumaTimeout": {apply: func(v int) { UptimeKumaTimeout = v }, min: 1}, + "DatabaseAutoCleanupRetentionDays": {apply: func(v int) { DatabaseAutoCleanupRetentionDays = v }, min: 1}, +} + +func applyIntegerOptionMirror(key, value string) { + applier, ok := integerOptionAppliers[key] + if !ok { + return + } + intValue, err := strconv.Atoi(value) + if err != nil || intValue < applier.min { + return + } + applier.apply(intValue) +} + +func applySpecialOptionMirror(key, value string) { + switch key { + case "NodeOfflineThreshold": + if v, err := strconv.Atoi(value); err == nil && v > 0 { + NodeOfflineThreshold = time.Duration(v) * time.Millisecond + } + case "AgentUpdateRepo": + if strings.TrimSpace(value) != "" { + AgentUpdateRepo = value + } + } +} diff --git a/internal/model/openflare_origin.go b/internal/model/openflare_origin.go index 1d285bd3..67c16750 100644 --- a/internal/model/openflare_origin.go +++ b/internal/model/openflare_origin.go @@ -44,7 +44,7 @@ type OriginProxyRoute struct { // TableName 表名。 func (OriginProxyRoute) TableName() string { - return "of_proxy_routes" + return tableOfProxyRoutes } // HasProxyRoutesTable 判断代理规则表是否已迁移。 diff --git a/internal/model/openflare_pages.go b/internal/model/openflare_pages.go index 1643037b..e35006be 100644 --- a/internal/model/openflare_pages.go +++ b/internal/model/openflare_pages.go @@ -10,6 +10,7 @@ import ( "github.com/Rain-kl/Wavelet/internal/db" ) +// Pages deployment status constants. const ( PagesDeploymentStatusUploaded = "uploaded" PagesDeploymentStatusActive = "active" diff --git a/internal/model/openflare_proxy_route.go b/internal/model/openflare_proxy_route.go index f60cc87b..4a584bf6 100644 --- a/internal/model/openflare_proxy_route.go +++ b/internal/model/openflare_proxy_route.go @@ -48,7 +48,7 @@ type ProxyRoute struct { // TableName 表名。 func (ProxyRoute) TableName() string { - return "of_proxy_routes" + return tableOfProxyRoutes } // ListProxyRoutes 列出全部代理规则。 @@ -84,7 +84,7 @@ func UpdateProxyRouteRecord(ctx context.Context, route *ProxyRoute) error { "origin_url": route.OriginURL, "origin_host": route.OriginHost, "upstreams": route.Upstreams, - "enabled": route.Enabled, + colEnabled: route.Enabled, "enable_https": route.EnableHTTPS, "cert_id": route.CertID, "cert_ids": route.CertIDs, @@ -100,7 +100,7 @@ func UpdateProxyRouteRecord(ctx context.Context, route *ProxyRoute) error { "basic_auth_enabled": route.BasicAuthEnabled, "basic_auth_username": route.BasicAuthUsername, "basic_auth_password": route.BasicAuthPassword, - "remark": route.Remark, + colRemark: route.Remark, "upstream_type": route.UpstreamType, "tunnel_node_id": route.TunnelNodeID, "tunnel_target_addr": route.TunnelTargetAddr, diff --git a/internal/model/openflare_tls.go b/internal/model/openflare_tls.go index 36625609..70ffe712 100644 --- a/internal/model/openflare_tls.go +++ b/internal/model/openflare_tls.go @@ -22,7 +22,7 @@ type TLSCertificate struct { Remark string `json:"remark" gorm:"size:255"` Provider string `json:"provider" gorm:"size:64;default:upload"` AcmeAccountID uint `json:"acme_account_id"` - DnsAccountID uint `json:"dns_account_id"` + DNSAccountID uint `json:"dns_account_id"` KeyAlgorithm string `json:"key_algorithm" gorm:"size:32"` AutoRenew bool `json:"auto_renew"` PrimaryDomain string `json:"primary_domain" gorm:"size:255"` @@ -52,7 +52,7 @@ type TLSProxyRouteRef struct { // TableName 表名。 func (TLSProxyRouteRef) TableName() string { - return "of_proxy_routes" + return tableOfProxyRoutes } // HasTLSProxyRoutesTable 判断代理规则表是否已迁移。 diff --git a/internal/model/openflare_waf.go b/internal/model/openflare_waf.go index 127ecc45..ea887a20 100644 --- a/internal/model/openflare_waf.go +++ b/internal/model/openflare_waf.go @@ -144,7 +144,7 @@ func UpdateOpenFlareWAFRuleGroup(ctx context.Context, group *OpenFlareWAFRuleGro } return conn.Model(&OpenFlareWAFRuleGroup{}).Where("id = ?", group.ID).Updates(map[string]any{ "name": group.Name, - "enabled": group.Enabled, + colEnabled: group.Enabled, "is_global": group.IsGlobal, "block_status_code": group.BlockStatusCode, "block_response_body": group.BlockResponseBody, @@ -158,7 +158,7 @@ func UpdateOpenFlareWAFRuleGroup(ctx context.Context, group *OpenFlareWAFRuleGro "region_blacklist": group.RegionBlacklist, "pow_enabled": group.PoWEnabled, "pow_config": group.PoWConfig, - "remark": group.Remark, + colRemark: group.Remark, }).Error } diff --git a/internal/repository/analytics/node_access_log.go b/internal/repository/analytics/node_access_log.go index bbc5380b..ba460508 100644 --- a/internal/repository/analytics/node_access_log.go +++ b/internal/repository/analytics/node_access_log.go @@ -95,10 +95,10 @@ func CountNodeAccessLogs(ctx context.Context, filter NodeAccessLogFilter) (int64 ipSQL := fmt.Sprintf(` SELECT count() FROM ( - SELECT trim(remote_addr) AS remote_addr + SELECT trim(remote_addr) AS trimmed_remote_addr FROM %s - WHERE %s AND remote_addr != '' - GROUP BY trim(remote_addr) + WHERE %s AND trim(remote_addr) != '' + GROUP BY trimmed_remote_addr )`, tableName, clause) var totalIPs int64 if err := conn.QueryRow(ctx, ipSQL, args...).Scan(&totalIPs); err != nil { @@ -117,11 +117,11 @@ func RegionCountsNodeAccessLogs(ctx context.Context, nodeID string, since time.T clause, args := buildNodeAccessLogFilterClause(filter) tableName := nodeAccessLogTableName() sql := fmt.Sprintf(` -SELECT trim(region) AS region, count() AS count +SELECT trim(region) AS trimmed_region, count() AS count FROM %s WHERE %s AND trim(region) != '' -GROUP BY trim(region) -ORDER BY count DESC, region ASC`, tableName, clause) +GROUP BY trimmed_region +ORDER BY count DESC, trimmed_region ASC`, tableName, clause) if limit > 0 { sql += " LIMIT ?" args = append(args, limit) @@ -141,4 +141,4 @@ ORDER BY count DESC, region ASC`, tableName, clause) result = append(result, item) } return result, nil -} \ No newline at end of file +} diff --git a/internal/repository/analytics/node_access_log_filter.go b/internal/repository/analytics/node_access_log_filter.go index e3a240d9..a59b8b19 100644 --- a/internal/repository/analytics/node_access_log_filter.go +++ b/internal/repository/analytics/node_access_log_filter.go @@ -9,6 +9,8 @@ import ( "time" ) +const nodeAccessLogFilterClauseCapacity = 6 + // NodeAccessLogFilter scopes ClickHouse node access log queries. type NodeAccessLogFilter struct { NodeID string @@ -24,8 +26,8 @@ type NodeAccessLogFilter struct { } func buildNodeAccessLogFilterClause(filter NodeAccessLogFilter) (string, []any) { - parts := make([]string, 0, 6) - args := make([]any, 0, 6) + parts := make([]string, 0, nodeAccessLogFilterClauseCapacity) + args := make([]any, 0, nodeAccessLogFilterClauseCapacity) if trimmed := strings.TrimSpace(filter.NodeID); trimmed != "" { parts = append(parts, "node_id = ?") args = append(args, trimmed) @@ -102,4 +104,4 @@ func nodeAccessLogEpochExpr() string { func nodeAccessLogTableName() string { return "of_node_access_logs" -} \ No newline at end of file +} diff --git a/internal/repository/analytics/node_access_log_stats.go b/internal/repository/analytics/node_access_log_stats.go index a43c9308..fbdb9d14 100644 --- a/internal/repository/analytics/node_access_log_stats.go +++ b/internal/repository/analytics/node_access_log_stats.go @@ -139,7 +139,7 @@ func IPAggregatesNodeAccessLogs(ctx context.Context, filter NodeAccessLogFilter, tableName := nodeAccessLogTableName() sql := fmt.Sprintf(` SELECT - trim(remote_addr) AS remote_addr, + trim(remote_addr) AS trimmed_remote_addr, count() AS request_count, countIf(status_code < 400) AS success_count, countIf(status_code >= 400 AND status_code < 500) AS client_error_count, @@ -147,7 +147,7 @@ SELECT max(%s) AS last_seen_epoch FROM %s WHERE %s AND trim(remote_addr) != '' -GROUP BY trim(remote_addr)`, lastSeenExpr, tableName, queryClause) +GROUP BY trimmed_remote_addr`, lastSeenExpr, tableName, queryClause) rows, err := conn.Query(ctx, sql, queryArgs...) if err != nil { return nil, fmt.Errorf("ip aggregates node access logs: %w", err) @@ -183,13 +183,13 @@ func IPSummariesNodeAccessLogs(ctx context.Context, filter NodeAccessLogFilter, tableName := nodeAccessLogTableName() sql := fmt.Sprintf(` SELECT - trim(remote_addr) AS remote_addr, + trim(remote_addr) AS trimmed_remote_addr, count() AS total_requests, sum(%s) AS recent_requests, max(%s) AS last_seen_epoch FROM %s WHERE %s AND trim(remote_addr) != '' -GROUP BY trim(remote_addr)`, recentClause, lastSeenExpr, tableName, clause) +GROUP BY trimmed_remote_addr`, recentClause, lastSeenExpr, tableName, clause) rows, err := conn.Query(ctx, sql, queryArgs...) if err != nil { return nil, fmt.Errorf("ip summaries node access logs: %w", err) @@ -239,4 +239,4 @@ ORDER BY bucket_epoch ASC`, bucketExpr, tableName, clause) result = append(result, item) } return result, nil -} \ No newline at end of file +} diff --git a/internal/router/v1/openflare/register_agent.go b/internal/router/v1/openflare/register_agent.go index 3d4e911b..9d9d350d 100644 --- a/internal/router/v1/openflare/register_agent.go +++ b/internal/router/v1/openflare/register_agent.go @@ -12,15 +12,15 @@ func registerAgentRoutes(apiV1Router *gin.RouterGroup) { agentRoute := apiV1Router.Group("/agent") { discoveryRoute := agentRoute.Group("/") - discoveryRoute.Use(agent.AgentRegisterAuth()) + discoveryRoute.Use(agent.RegisterAuth()) { discoveryRoute.POST("/nodes/register", agent.RegisterHandler) } authorizedRoute := agentRoute.Group("/") - authorizedRoute.Use(agent.AgentAuth()) + authorizedRoute.Use(agent.Auth()) { - authorizedRoute.GET("/ws", agent.AgentWebSocketHandler) + authorizedRoute.GET("/ws", agent.WebSocketHandler) authorizedRoute.POST("/nodes/heartbeat", agent.HeartbeatHandler) authorizedRoute.GET("/config-versions/active", agent.GetActiveConfigHandler) authorizedRoute.GET("/pages/deployments/:deployment_id/package", agent.DownloadPagesPackageHandler) diff --git a/internal/router/v1/openflare/register_relay_flared.go b/internal/router/v1/openflare/register_relay_flared.go index 377961ec..f45a9834 100644 --- a/internal/router/v1/openflare/register_relay_flared.go +++ b/internal/router/v1/openflare/register_relay_flared.go @@ -11,7 +11,7 @@ import ( func registerRelayRoutes(apiV1Router *gin.RouterGroup) { relayRoute := apiV1Router.Group("/relay") - relayRoute.Use(relay.RelayAuth()) + relayRoute.Use(relay.Auth()) { relayRoute.POST("/heartbeat", relay.PostHeartbeat) relayRoute.GET("/ws", relay.GetWebSocket) diff --git a/pkg/geoip/emptyProvider.go b/pkg/geoip/emptyProvider.go index c0cfb7cb..6d689a0d 100644 --- a/pkg/geoip/emptyProvider.go +++ b/pkg/geoip/emptyProvider.go @@ -5,21 +5,30 @@ import ( "net" ) +// EmptyProvider is a no-op GeoIP backend used before a real provider is configured. type EmptyProvider struct{} +// Name returns the provider identifier. func (e *EmptyProvider) Name() string { return "EmptyProvider" } +// Initialize prepares the empty provider for use. func (e *EmptyProvider) Initialize() error { return nil } -func (e *EmptyProvider) GetGeoInfo(ip net.IP) (*GeoInfo, error) { + +// GetGeoInfo reports that no GeoIP provider has been configured. +func (e *EmptyProvider) GetGeoInfo(_ net.IP) (*GeoInfo, error) { return nil, fmt.Errorf("you are using an empty GeoIP provider, please set a valid provider") } + +// UpdateDatabase reports that no GeoIP provider has been configured. func (e *EmptyProvider) UpdateDatabase() error { return fmt.Errorf("you are using an empty GeoIP provider, please set a valid provider") } + +// Close releases resources held by the empty provider. func (e *EmptyProvider) Close() error { return nil } diff --git a/pkg/geoip/geoip.go b/pkg/geoip/geoip.go index 606aa69e..aab42ce6 100644 --- a/pkg/geoip/geoip.go +++ b/pkg/geoip/geoip.go @@ -1,3 +1,4 @@ +// Package geoip resolves geographic information for IP addresses. package geoip import ( @@ -12,19 +13,27 @@ import ( ristretto "github.com/dgraph-io/ristretto/v2" ) -var CurrentProvider GeoIPService +// CurrentProvider is the active GeoIP backend used by package-level helpers. +var CurrentProvider Service var geoCache *providerCache var providerMutex sync.RWMutex var providerFactory = newProvider +// Supported GeoIP provider identifiers. const ( + // ProviderDisabled disables GeoIP lookups. ProviderDisabled = "disabled" ProviderMaxMind = "mmdb" ProviderIPAPI = "ip-api" ProviderGeoJS = "geojs" ProviderIPInfo = "ipinfo" + + defaultGeoCacheDuration = 48 * time.Hour + isoCountryCodeLength = 2 + geoipDataDirPerm = 0o750 ) +// GeoInfo contains normalized geographic metadata for an IP address. type GeoInfo struct { ISOCode string Name string @@ -34,11 +43,11 @@ type GeoInfo struct { func init() { CurrentProvider = &EmptyProvider{} - geoCache = newProviderCache(48 * time.Hour) + geoCache = newProviderCache(defaultGeoCacheDuration) } -// GeoIPService 接口定义了获取地理位置信息的核心方法。 -type GeoIPService interface { +// Service defines the core GeoIP lookup operations implemented by providers. +type Service interface { Name() string GetGeoInfo(ip net.IP) (*GeoInfo, error) UpdateDatabase() error @@ -94,8 +103,9 @@ func (c *providerCache) Flush() { c.items.Clear() } +// GetRegionUnicodeEmoji returns the regional indicator emoji for a two-letter ISO code. func GetRegionUnicodeEmoji(isoCode string) string { - if len(isoCode) != 2 { + if len(isoCode) != isoCountryCodeLength { return "" } isoCode = strings.ToUpper(isoCode) @@ -104,11 +114,12 @@ func GetRegionUnicodeEmoji(isoCode string) string { return "" } - rune1 := rune(0x1F1E6 + (rune(isoCode[0]) - 'A')) - rune2 := rune(0x1F1E6 + (rune(isoCode[1]) - 'A')) + rune1 := 0x1F1E6 + (rune(isoCode[0]) - 'A') + rune2 := 0x1F1E6 + (rune(isoCode[1]) - 'A') return string(rune1) + string(rune2) } +// InitGeoIP configures the active GeoIP provider. func InitGeoIP(provider string) { providerName := normalizeProvider(provider) nextProvider, err := providerFactory(providerName) @@ -124,6 +135,7 @@ func InitGeoIP(provider string) { slog.Info("GeoIP provider configured", "provider", CurrentProvider.Name()) } +// GetGeoInfo looks up geographic information for ip using the active provider. func GetGeoInfo(ip net.IP) (*GeoInfo, error) { if ip == nil { return nil, fmt.Errorf("IP address cannot be nil") @@ -142,6 +154,7 @@ func GetGeoInfo(ip net.IP) (*GeoInfo, error) { return info, err } +// LookupGeoInfoWithProvider looks up geographic information using a temporary provider. func LookupGeoInfoWithProvider(providerName string, ip net.IP) (*GeoInfo, error) { if ip == nil { return nil, fmt.Errorf("IP address cannot be nil") @@ -160,6 +173,7 @@ func LookupGeoInfoWithProvider(providerName string, ip net.IP) (*GeoInfo, error) return provider.GetGeoInfo(ip) } +// UpdateDatabase refreshes the active provider database and clears cached lookups. func UpdateDatabase() error { err := getProvider().UpdateDatabase() if err == nil { @@ -169,6 +183,7 @@ func UpdateDatabase() error { return err } +// IsValidProvider reports whether provider names a supported GeoIP backend. func IsValidProvider(provider string) bool { switch normalizeProvider(provider) { case ProviderDisabled, ProviderMaxMind, ProviderIPAPI, ProviderGeoJS, ProviderIPInfo: @@ -186,7 +201,7 @@ func normalizeProvider(provider string) string { return normalized } -func newProvider(provider string) (GeoIPService, error) { +func newProvider(provider string) (Service, error) { switch provider { case ProviderDisabled: return &EmptyProvider{}, nil @@ -203,7 +218,7 @@ func newProvider(provider string) (GeoIPService, error) { } } -func setProvider(provider GeoIPService) { +func setProvider(provider Service) { providerMutex.Lock() previous := CurrentProvider CurrentProvider = provider @@ -216,7 +231,7 @@ func setProvider(provider GeoIPService) { } } -func getProvider() GeoIPService { +func getProvider() Service { providerMutex.RLock() defer providerMutex.RUnlock() if CurrentProvider == nil { @@ -229,11 +244,13 @@ func float64Pointer(value float64) *float64 { return &value } -func ProviderFactoryForTest() func(string) (GeoIPService, error) { +// ProviderFactoryForTest returns the provider factory used by package helpers. +func ProviderFactoryForTest() func(string) (Service, error) { return providerFactory } -func SetProviderFactoryForTest(factory func(string) (GeoIPService, error)) { +// SetProviderFactoryForTest replaces the provider factory for tests. +func SetProviderFactoryForTest(factory func(string) (Service, error)) { if factory == nil { providerFactory = newProvider return diff --git a/pkg/geoip/geoip_test.go b/pkg/geoip/geoip_test.go index c3726d0a..d606cfed 100644 --- a/pkg/geoip/geoip_test.go +++ b/pkg/geoip/geoip_test.go @@ -82,7 +82,7 @@ func TestIsValidProvider(t *testing.T) { func TestLookupGeoInfoWithProviderUsesTemporaryProvider(t *testing.T) { previousFactory := providerFactory - providerFactory = func(provider string) (GeoIPService, error) { + providerFactory = func(provider string) (Service, error) { return &fakeProvider{}, nil } defer func() { diff --git a/pkg/geoip/geojs.go b/pkg/geoip/geojs.go index c3de263b..7196f0e0 100644 --- a/pkg/geoip/geojs.go +++ b/pkg/geoip/geojs.go @@ -1,6 +1,7 @@ package geoip import ( + "context" "encoding/json" "fmt" "net" @@ -8,7 +9,7 @@ import ( "time" ) -// GeoJSService 使用 geojs.io 服务实现 GeoIPService 接口。 +// GeoJSService resolves geographic information using the geojs.io service. type GeoJSService struct { Client *http.Client } @@ -44,11 +45,15 @@ func (s *GeoJSService) GetGeoInfo(ip net.IP) (*GeoInfo, error) { // GeoJS 的 API 端点 apiURL := fmt.Sprintf("https://get.geojs.io/v1/ip/geo/%s.json", ip.String()) - resp, err := s.Client.Get(apiURL) + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, apiURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request for geojs.io: %w", err) + } + resp, err := s.Client.Do(req) if err != nil { return nil, fmt.Errorf("failed to get geo info from geojs.io: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() // 检查响应状态码 if resp.StatusCode != http.StatusOK { diff --git a/pkg/geoip/ipapi.go b/pkg/geoip/ipapi.go index afbf4412..6378242d 100644 --- a/pkg/geoip/ipapi.go +++ b/pkg/geoip/ipapi.go @@ -1,6 +1,7 @@ package geoip import ( + "context" "encoding/json" "fmt" "net" @@ -8,7 +9,7 @@ import ( "time" ) -// IPAPIService 使用 ip-api.com 服务实现 GeoIPService 接口。 +// IPAPIService resolves geographic information using the ip-api.com service. type IPAPIService struct { Client *http.Client } @@ -32,6 +33,7 @@ type ipAPIResponse struct { Query string `json:"query"` } +// Name returns the provider identifier for the ip-api.com service. func (s *IPAPIService) Name() string { return "ip-api.com" } @@ -50,11 +52,15 @@ func (s *IPAPIService) GetGeoInfo(ip net.IP) (*GeoInfo, error) { // API URL, 使用 fields 参数来仅请求需要的字段 apiURL := fmt.Sprintf("http://ip-api.com/json/%s?fields=status,message,country,countryCode", ip.String()) - resp, err := s.Client.Get(apiURL) + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, apiURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request for ip-api.com: %w", err) + } + resp, err := s.Client.Do(req) if err != nil { return nil, fmt.Errorf("failed to get geo info from ip-api.com: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() var apiResp ipAPIResponse if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil { diff --git a/pkg/geoip/ipinfo.go b/pkg/geoip/ipinfo.go index 416ce3d1..f9efade0 100644 --- a/pkg/geoip/ipinfo.go +++ b/pkg/geoip/ipinfo.go @@ -1,6 +1,7 @@ package geoip import ( + "context" "encoding/json" "fmt" "net" @@ -10,7 +11,7 @@ import ( "time" ) -// IPInfoService 使用 ipinfo.io 服务实现 GeoIPService 接口。 +// IPInfoService resolves geographic information using the ipinfo.io service. type IPInfoService struct { Client *http.Client // 每天 1000 次请求,限制由 IP 地址的所有人共享。 @@ -52,11 +53,15 @@ func (s *IPInfoService) GetGeoInfo(ip net.IP) (*GeoInfo, error) { // API URL: https://ipinfo.io/json (查询自身IP) 或 https://ipinfo.io/YOUR_IP/json apiURL := fmt.Sprintf("https://ipinfo.io/%s/json", ip.String()) - resp, err := s.Client.Get(apiURL) + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, apiURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request for ipinfo.io: %w", err) + } + resp, err := s.Client.Do(req) if err != nil { return nil, fmt.Errorf("failed to get geo info from ipinfo.io: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("ipinfo.io returned non-200 status: %d %s", resp.StatusCode, resp.Status) @@ -98,7 +103,7 @@ func (s *IPInfoService) Close() error { func parseIPInfoCoordinates(value string) (*float64, *float64) { parts := strings.Split(strings.TrimSpace(value), ",") - if len(parts) != 2 { + if len(parts) != isoCountryCodeLength { return nil, nil } diff --git a/pkg/geoip/iputil/iputil.go b/pkg/geoip/iputil/iputil.go index a5f2b1b2..3dc7243c 100644 --- a/pkg/geoip/iputil/iputil.go +++ b/pkg/geoip/iputil/iputil.go @@ -1,3 +1,4 @@ +// Package iputil provides helpers for parsing, normalizing, and scoring IP addresses. package iputil import ( @@ -5,6 +6,12 @@ import ( "strings" ) +const ( + scorePublic = 2 + scorePrivate = 1 +) + +// NormalizeIP parses and normalizes a raw IP address string, preferring the IPv4 form for mapped addresses. func NormalizeIP(raw string) string { trimmed := strings.TrimSpace(raw) if trimmed == "" { @@ -20,6 +27,7 @@ func NormalizeIP(raw string) string { return ip.String() } +// NormalizeRemoteAddr extracts and normalizes the IP address from a host:port remote address string. func NormalizeRemoteAddr(remoteAddr string) string { trimmed := strings.TrimSpace(remoteAddr) if trimmed == "" { @@ -31,6 +39,7 @@ func NormalizeRemoteAddr(remoteAddr string) string { return NormalizeIP(trimmed) } +// IsPublic reports whether the given IP address is a publicly routable unicast address. func IsPublic(ip net.IP) bool { if ip == nil { return false @@ -44,11 +53,13 @@ func IsPublic(ip net.IP) bool { return true } +// IsPublicString parses raw and reports whether it represents a publicly routable IP address. func IsPublicString(raw string) bool { ip := net.ParseIP(strings.TrimSpace(raw)) return IsPublic(ip) } +// Score returns a preference score for the IP address: 2 for public, 1 for private, -1 for invalid or non-unicast. func Score(ip net.IP) int { if ip == nil { return -1 @@ -60,7 +71,7 @@ func Score(ip net.IP) int { return -1 } if IsPublic(ip) { - return 2 + return scorePublic } - return 1 + return scorePrivate } diff --git a/pkg/geoip/mmdb.go b/pkg/geoip/mmdb.go index 5cc4a2ac..94d263c8 100644 --- a/pkg/geoip/mmdb.go +++ b/pkg/geoip/mmdb.go @@ -1,6 +1,7 @@ package geoip import ( + "context" "fmt" "io" "net" @@ -12,47 +13,55 @@ import ( "github.com/oschwald/maxminddb-golang" ) -var GeoIpUrl = "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/GeoLite2-Country.mmdb" -var GeoIpFilePath = "./data/GeoLite2-Country.mmdb" +// GeoIPURL is the default download URL for the MaxMind GeoLite2 country database. +var GeoIPURL = "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/GeoLite2-Country.mmdb" -type GeoIpRecord struct { +// GeoIPFilePath is the default local path for the MaxMind country database file. +var GeoIPFilePath = "./data/GeoLite2-Country.mmdb" + +// Record is the MaxMind database record structure for country lookups. +type Record struct { Country struct { ISOCode string `maxminddb:"iso_code"` Names map[string]string `maxminddb:"names"` } `maxminddb:"country"` } +// MaxMindGeoIPService resolves geographic information using a local MaxMind MMDB database. type MaxMindGeoIPService struct { maxMindDBReader *maxminddb.Reader dbFilePath string mu sync.RWMutex } +// Name returns the provider identifier for the MaxMind database service. func (s *MaxMindGeoIPService) Name() string { return "MaxMind" } +// NewMaxMindGeoIPService creates a MaxMind service using the default database path and URL. func NewMaxMindGeoIPService() (*MaxMindGeoIPService, error) { - return NewMaxMindGeoIPServiceWithConfig(GeoIpFilePath, GeoIpUrl) + return NewMaxMindGeoIPServiceWithConfig(GeoIPFilePath, GeoIPURL) } +// NewMaxMindGeoIPServiceWithConfig creates a MaxMind service with custom database path and download URL. func NewMaxMindGeoIPServiceWithConfig(dbFilePath string, downloadURL string) (*MaxMindGeoIPService, error) { if dbFilePath == "" { - dbFilePath = GeoIpFilePath + dbFilePath = GeoIPFilePath } if downloadURL == "" { - downloadURL = GeoIpUrl + downloadURL = GeoIPURL } service := &MaxMindGeoIPService{ dbFilePath: dbFilePath, } - if err := os.MkdirAll(filepath.Dir(service.dbFilePath), os.ModePerm); err != nil { + if err := os.MkdirAll(filepath.Dir(service.dbFilePath), geoipDataDirPerm); err != nil { return nil, fmt.Errorf("failed to create data directory for MaxMind database: %w", err) } if _, err := os.Stat(service.dbFilePath); os.IsNotExist(err) { - if err := DownloadMaxMindDatabase(service.dbFilePath, downloadURL); err != nil { + if err := DownloadMaxMindDatabase(context.Background(), service.dbFilePath, downloadURL); err != nil { return nil, fmt.Errorf("failed to download initial MaxMind database: %w", err) } } @@ -81,6 +90,7 @@ func (s *MaxMindGeoIPService) initialize() error { return nil } +// GetGeoInfo looks up geographic information for ip in the MaxMind database. func (s *MaxMindGeoIPService) GetGeoInfo(ip net.IP) (*GeoInfo, error) { s.mu.RLock() defer s.mu.RUnlock() @@ -92,7 +102,7 @@ func (s *MaxMindGeoIPService) GetGeoInfo(ip net.IP) (*GeoInfo, error) { return nil, fmt.Errorf("IP address cannot be nil") } - var record GeoIpRecord + var record Record if err := s.maxMindDBReader.Lookup(ip, &record); err != nil { return nil, fmt.Errorf("error looking up IP %s in MaxMind database: %w", ip.String(), err) } @@ -108,36 +118,42 @@ func (s *MaxMindGeoIPService) GetGeoInfo(ip net.IP) (*GeoInfo, error) { return geoInfo, nil } +// UpdateDatabase downloads the latest MaxMind database and reloads the reader. func (s *MaxMindGeoIPService) UpdateDatabase() error { - if err := DownloadMaxMindDatabase(s.dbFilePath, GeoIpUrl); err != nil { + if err := DownloadMaxMindDatabase(context.Background(), s.dbFilePath, GeoIPURL); err != nil { return err } return s.initialize() } -func DownloadMaxMindDatabase(dbFilePath string, downloadURL string) error { +// DownloadMaxMindDatabase downloads the MaxMind database from downloadURL to dbFilePath. +func DownloadMaxMindDatabase(ctx context.Context, dbFilePath string, downloadURL string) error { if dbFilePath == "" { - dbFilePath = GeoIpFilePath + dbFilePath = GeoIPFilePath } if downloadURL == "" { - downloadURL = GeoIpUrl + downloadURL = GeoIPURL } - resp, err := http.Get(downloadURL) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, nil) //nolint:gosec // URL from trusted GeoIP provider config if err != nil { return fmt.Errorf("failed to initiate MaxMind database download: %w", err) } - defer resp.Body.Close() + resp, err := http.DefaultClient.Do(req) + if err != nil { + return fmt.Errorf("failed to initiate MaxMind database download: %w", err) + } + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return fmt.Errorf("failed to download MaxMind database: HTTP status %s", resp.Status) } - if err := os.MkdirAll(filepath.Dir(dbFilePath), os.ModePerm); err != nil { + if err := os.MkdirAll(filepath.Dir(dbFilePath), geoipDataDirPerm); err != nil { return fmt.Errorf("failed to create data directory for MaxMind database update: %w", err) } tempPath := dbFilePath + ".download" - out, err := os.Create(tempPath) + out, err := os.Create(tempPath) //nolint:gosec // tempPath is derived from configured dbFilePath if err != nil { return fmt.Errorf("failed to create MaxMind database file at %s: %w", tempPath, err) } @@ -157,6 +173,7 @@ func DownloadMaxMindDatabase(dbFilePath string, downloadURL string) error { return nil } +// Close closes the MaxMind database reader. func (s *MaxMindGeoIPService) Close() error { s.mu.Lock() defer s.mu.Unlock() diff --git a/pkg/geoip/outboundip.go b/pkg/geoip/outboundip.go index 718b862a..b85aec97 100644 --- a/pkg/geoip/outboundip.go +++ b/pkg/geoip/outboundip.go @@ -29,11 +29,13 @@ type OutboundIPAPIAdapter interface { DecodeIP(io.Reader) (net.IP, error) } +// HTTPOutboundIPStrategy resolves the public egress IP via an HTTP API adapter. type HTTPOutboundIPStrategy struct { Client *http.Client Adapter OutboundIPAPIAdapter } +// NewHTTPOutboundIPStrategy creates a strategy that queries adapter over HTTP. func NewHTTPOutboundIPStrategy(adapter OutboundIPAPIAdapter, client *http.Client) *HTTPOutboundIPStrategy { if client == nil { client = &http.Client{Timeout: defaultOutboundIPLookupTimeout} @@ -44,6 +46,7 @@ func NewHTTPOutboundIPStrategy(adapter OutboundIPAPIAdapter, client *http.Client } } +// Name returns the strategy or adapter identifier. func (s *HTTPOutboundIPStrategy) Name() string { if s == nil || s.Adapter == nil { return "http-outbound-ip" @@ -51,12 +54,13 @@ func (s *HTTPOutboundIPStrategy) Name() string { return s.Adapter.Name() } +// GetOutboundIP queries the configured HTTP endpoint for the current public IP. func (s *HTTPOutboundIPStrategy) GetOutboundIP(ctx context.Context) (net.IP, error) { if s == nil || s.Adapter == nil { return nil, errors.New("outbound IP adapter is nil") } if ctx == nil { - ctx = context.Background() + return nil, errors.New("context is required") } client := s.Client if client == nil { @@ -70,7 +74,7 @@ func (s *HTTPOutboundIPStrategy) GetOutboundIP(ctx context.Context) (net.IP, err if err != nil { return nil, fmt.Errorf("%s request failed: %w", s.Name(), err) } - defer response.Body.Close() + defer func() { _ = response.Body.Close() }() if response.StatusCode != http.StatusOK { return nil, fmt.Errorf("%s returned non-200 status: %d %s", s.Name(), response.StatusCode, response.Status) } @@ -84,6 +88,7 @@ func (s *HTTPOutboundIPStrategy) GetOutboundIP(ctx context.Context) (net.IP, err return ip, nil } +// RealIPCCAdapter decodes public IP responses from realip.cc. type RealIPCCAdapter struct { URL string } @@ -92,14 +97,17 @@ type realIPCCResponse struct { IP string `json:"ip"` } +// NewRealIPCCOutboundIPStrategy creates the default realip.cc lookup strategy. func NewRealIPCCOutboundIPStrategy() *HTTPOutboundIPStrategy { return NewHTTPOutboundIPStrategy(RealIPCCAdapter{}, nil) } +// Name returns the realip.cc adapter identifier. func (a RealIPCCAdapter) Name() string { return "realip.cc" } +// Endpoint returns the realip.cc API URL. func (a RealIPCCAdapter) Endpoint() string { if strings.TrimSpace(a.URL) != "" { return strings.TrimSpace(a.URL) @@ -107,6 +115,7 @@ func (a RealIPCCAdapter) Endpoint() string { return "https://realip.cc" } +// DecodeIP parses a realip.cc JSON response into a public IP address. func (a RealIPCCAdapter) DecodeIP(reader io.Reader) (net.IP, error) { var payload realIPCCResponse if err := json.NewDecoder(reader).Decode(&payload); err != nil { @@ -122,12 +131,14 @@ func (a RealIPCCAdapter) DecodeIP(reader io.Reader) (net.IP, error) { return ip, nil } +// DefaultOutboundIPStrategies returns the built-in public egress IP lookup strategies. func DefaultOutboundIPStrategies() []OutboundIPStrategy { return []OutboundIPStrategy{ NewRealIPCCOutboundIPStrategy(), } } +// GetOutboundIP tries each strategy until one returns a public egress IP. func GetOutboundIP(ctx context.Context, strategies ...OutboundIPStrategy) (net.IP, error) { if len(strategies) == 0 { strategies = DefaultOutboundIPStrategies() diff --git a/pkg/protocol/agent.go b/pkg/protocol/agent.go index b81c9e0e..be9241ec 100644 --- a/pkg/protocol/agent.go +++ b/pkg/protocol/agent.go @@ -1,24 +1,29 @@ +// Package protocol defines the communication protocol between OpenFlare server, agent, and relay components. package protocol import "encoding/json" +// APIResponse is a generic API response wrapper. type APIResponse[T any] struct { ErrorMsg string `json:"error_msg"` Data T `json:"data"` } +// HeartbeatData is the heartbeat request payload from agent. type HeartbeatData struct { AgentSettings *AgentSettings `json:"agent_settings"` ActiveConfig *ActiveConfigMeta `json:"active_config"` WAFIPGroups []WAFIPGroup `json:"waf_ip_groups,omitempty"` } +// HeartbeatResult is the heartbeat response payload. type HeartbeatResult struct { AgentSettings *AgentSettings ActiveConfig *ActiveConfigMeta WAFIPGroups []WAFIPGroup } +// AgentSettings holds agent configuration settings. type AgentSettings struct { HeartbeatInterval int `json:"heartbeat_interval"` WebsocketUpgradeEnabled bool `json:"websocket_upgrade_enabled"` @@ -30,6 +35,7 @@ type AgentSettings struct { RestartOpenrestyNow bool `json:"restart_openresty_now"` } +// WSMessageType constants define WebSocket message types. const ( WSMessageTypeStatus = "status" WSMessageTypeSettings = "settings" @@ -40,16 +46,19 @@ const ( WSMessageTypePong = "pong" ) +// WSMessage represents a WebSocket message. type WSMessage struct { Type string `json:"type"` Payload json.RawMessage `json:"payload,omitempty"` } +// WSOutboundMessage represents an outbound WebSocket message. type WSOutboundMessage struct { Type string `json:"type"` Payload any `json:"payload,omitempty"` } +// WebSocketConnection defines the WebSocket connection interface. type WebSocketConnection interface { URL() string SendStatus(payload NodePayload) error @@ -58,12 +67,14 @@ type WebSocketConnection interface { Close() error } +// OpenrestyStatus constants define OpenResty health status values. const ( OpenrestyStatusHealthy = "healthy" OpenrestyStatusUnhealthy = "unhealthy" OpenrestyStatusUnknown = "unknown" ) +// NodePayload is the agent node registration payload. type NodePayload struct { NodeID string `json:"node_id"` Name string `json:"name"` @@ -84,6 +95,7 @@ type NodePayload struct { WAFIPGroupChecksums map[string]string `json:"waf_ip_group_checksums,omitempty"` } +// NodeSystemProfile describes the system profile of a node. type NodeSystemProfile struct { Hostname string `json:"hostname"` OSName string `json:"os_name"` @@ -98,6 +110,7 @@ type NodeSystemProfile struct { ReportedAtUnix int64 `json:"reported_at_unix"` } +// NodeMetricSnapshot is a metric snapshot of a node. type NodeMetricSnapshot struct { CapturedAtUnix int64 `json:"captured_at_unix"` CPUUsagePercent float64 `json:"cpu_usage_percent"` @@ -111,6 +124,7 @@ type NodeMetricSnapshot struct { NetworkTxBytes int64 `json:"network_tx_bytes"` } +// NodeOpenrestyObservation holds OpenResty observation data. type NodeOpenrestyObservation struct { CapturedAtUnix int64 `json:"captured_at_unix"` OpenrestyRxBytes int64 `json:"openresty_rx_bytes"` @@ -118,6 +132,7 @@ type NodeOpenrestyObservation struct { OpenrestyConnections int64 `json:"openresty_connections"` } +// NodeTrafficReport is a traffic report from agent. type NodeTrafficReport struct { WindowStartedAtUnix int64 `json:"window_started_at_unix"` WindowEndedAtUnix int64 `json:"window_ended_at_unix"` @@ -129,6 +144,7 @@ type NodeTrafficReport struct { SourceCountries map[string]int64 `json:"source_countries"` } +// NodeAccessLog is an access log entry from agent. type NodeAccessLog struct { LoggedAtUnix int64 `json:"logged_at_unix"` RemoteAddr string `json:"remote_addr"` @@ -137,6 +153,7 @@ type NodeAccessLog struct { StatusCode int `json:"status_code"` } +// BufferedObservabilityRecord is a buffered observability record. type BufferedObservabilityRecord struct { WindowStartedAtUnix int64 `json:"window_started_at_unix"` Snapshot *NodeMetricSnapshot `json:"snapshot,omitempty"` @@ -145,6 +162,7 @@ type BufferedObservabilityRecord struct { AccessLogs []NodeAccessLog `json:"access_logs,omitempty"` } +// NodeHealthEvent represents a node health event. type NodeHealthEvent struct { EventType string `json:"event_type"` Severity string `json:"severity"` @@ -153,12 +171,14 @@ type NodeHealthEvent struct { Metadata map[string]string `json:"metadata,omitempty"` } +// RegisterNodeResponse is the node registration response. type RegisterNodeResponse struct { NodeID string `json:"node_id"` AccessToken string `json:"agent_token"` Name string `json:"name"` } +// ActiveConfigResponse is the active configuration response. type ActiveConfigResponse struct { Version string `json:"version"` Checksum string `json:"checksum"` @@ -167,6 +187,7 @@ type ActiveConfigResponse struct { CreatedAt string `json:"created_at"` } +// WAFIPGroup defines a WAF IP group. type WAFIPGroup struct { ID uint `json:"id"` Name string `json:"name"` @@ -176,16 +197,19 @@ type WAFIPGroup struct { Checksum string `json:"checksum"` } +// WAFIPGroupSyncRequest is a WAF IP group sync request. type WAFIPGroupSyncRequest struct { IDs []uint `json:"ids,omitempty"` Checksums map[string]string `json:"checksums,omitempty"` } +// WAFIPGroupSyncResponse is a WAF IP group sync response. type WAFIPGroupSyncResponse struct { Groups []WAFIPGroup `json:"groups"` } +// SupportFile represents a support file for relay. type SupportFile struct { Path string `json:"path"` Content string `json:"content"` -} \ No newline at end of file +} diff --git a/pkg/protocol/server.go b/pkg/protocol/server.go index 8a04ca86..202bcea1 100644 --- a/pkg/protocol/server.go +++ b/pkg/protocol/server.go @@ -1,9 +1,15 @@ package protocol +// AgentNodeSystemProfile is an alias for NodeSystemProfile used by server. type AgentNodeSystemProfile = NodeSystemProfile + +// AgentNodeMetricSnapshot is an alias for NodeMetricSnapshot used by server. type AgentNodeMetricSnapshot = NodeMetricSnapshot + +// AgentNodeHealthEvent is an alias for NodeHealthEvent used by server. type AgentNodeHealthEvent = NodeHealthEvent +// RelayProxyStat holds relay proxy statistics. type RelayProxyStat struct { Name string `json:"name"` Type string `json:"type"` @@ -14,6 +20,7 @@ type RelayProxyStat struct { ClientAddr string `json:"client_addr"` } +// RelayHeartbeatPayload is the relay heartbeat payload. type RelayHeartbeatPayload struct { Version string `json:"version"` ExtVersion string `json:"frp_version"` @@ -29,6 +36,7 @@ type RelayHeartbeatPayload struct { HealthEvents []AgentNodeHealthEvent `json:"health_events,omitempty"` } +// RelayConfig holds relay configuration. type RelayConfig struct { BindPort int `json:"bind_port"` VhostHTTPPort int `json:"vhost_http_port"` @@ -37,6 +45,7 @@ type RelayConfig struct { WebServerEnabled bool `json:"web_server_enabled"` } +// RelaySettings holds relay runtime settings. type RelaySettings struct { HeartbeatInterval int `json:"heartbeat_interval"` WebsocketUpgradeEnabled bool `json:"websocket_upgrade_enabled"` @@ -47,22 +56,26 @@ type RelaySettings struct { UpdateTag string `json:"update_tag"` } +// RelayHeartbeatResponse is the relay heartbeat response. type RelayHeartbeatResponse struct { RelayConfig *RelayConfig `json:"relay_config"` RelaySettings *RelaySettings `json:"relay_settings"` } +// ActiveConfigMeta holds active configuration metadata. type ActiveConfigMeta struct { Version string `json:"version"` Checksum string `json:"checksum"` } +// FlaredConnectedRelay describes a connected relay info for flared. type FlaredConnectedRelay struct { RelayNodeID string `json:"relay_node_id"` Status string `json:"status"` ProxyCount int `json:"proxy_count"` } +// FlaredHeartbeatPayload is the flared heartbeat payload. type FlaredHeartbeatPayload struct { ClientVersion string `json:"client_version"` FrpVersion string `json:"frp_version"` @@ -73,11 +86,13 @@ type FlaredHeartbeatPayload struct { CurrentChecksum string `json:"current_checksum"` } +// FlaredHeartbeatResponse is the flared heartbeat response. type FlaredHeartbeatResponse struct { ActiveConfig *ActiveConfigMeta `json:"active_config"` TunnelSettings *RelaySettings `json:"tunnel_settings"` } +// FlaredTunnelConfigResponse is the flared tunnel configuration response. type FlaredTunnelConfigResponse struct { Version string `json:"version"` Checksum string `json:"checksum"` @@ -85,6 +100,7 @@ type FlaredTunnelConfigResponse struct { Proxies []FlaredProxyEntry `json:"proxies"` } +// FlaredRelayInfo holds flared relay information. type FlaredRelayInfo struct { RelayNodeID string `json:"relay_node_id"` Address string `json:"address"` @@ -92,6 +108,7 @@ type FlaredRelayInfo struct { ProxyURL string `json:"proxy_url"` } +// FlaredProxyEntry represents a flared proxy entry. type FlaredProxyEntry struct { Name string `json:"name"` Type string `json:"type"` @@ -100,6 +117,7 @@ type FlaredProxyEntry struct { CustomDomains []string `json:"custom_domains"` } +// ApplyLogPayload is the apply log payload for flared. type ApplyLogPayload struct { NodeID string `json:"node_id"` Version string `json:"version"` diff --git a/pkg/render/openresty/render.go b/pkg/render/openresty/render.go index 0b83d687..2b71cea4 100644 --- a/pkg/render/openresty/render.go +++ b/pkg/render/openresty/render.go @@ -1,3 +1,4 @@ +// Package openresty renders OpenResty configuration from proxy route definitions. package openresty import ( @@ -16,6 +17,13 @@ import ( "strings" ) +const ( + routeUpstreamTypePages = "pages" + indexHTML = "/index.html" +) + +// RenderJSON parses the given JSON string as a Document and renders the full +// OpenResty configuration bundle, injecting the provided certificate support files. func RenderJSON(sourceJSON string, certificateFiles []SupportFile) (*Result, error) { var doc Document if err := json.Unmarshal([]byte(strings.TrimSpace(sourceJSON)), &doc); err != nil { @@ -24,6 +32,8 @@ func RenderJSON(sourceJSON string, certificateFiles []SupportFile) (*Result, err return Render(doc, certificateFiles) } +// Render produces a complete OpenResty configuration Result from a Document and +// a set of certificate support files. func Render(doc Document, certificateFiles []SupportFile) (*Result, error) { mainConfig := RenderMainConfig(doc.OpenRestyConfig) routeConfig, err := RenderRouteConfig(doc, certificateFiles) @@ -45,6 +55,8 @@ func Render(doc Document, certificateFiles []SupportFile) (*Result, error) { }, nil } +// RenderMainConfig renders the nginx main configuration string from the given +// ConfigSnapshot, falling back to the built-in default template when none is set. func RenderMainConfig(cfg ConfigSnapshot) string { templateText := cfg.MainConfigTemplate if strings.TrimSpace(templateText) == "" { @@ -53,6 +65,8 @@ func RenderMainConfig(cfg ConfigSnapshot) string { return renderMainConfigTemplate(templateText, cfg) } +// ValidateMainConfigTemplate checks that the provided template text is non-empty +// and contains all required OpenResty placeholder tokens. func ValidateMainConfigTemplate(templateText string) error { trimmed := strings.TrimSpace(templateText) if trimmed == "" { @@ -66,6 +80,8 @@ func ValidateMainConfigTemplate(templateText string) error { return nil } +// RenderRouteConfig generates the nginx server-block configuration for all +// routes in the Document, resolving certificate files as needed. func RenderRouteConfig(doc Document, certificateFiles []SupportFile) (string, error) { var builder strings.Builder builder.WriteString("# This file is generated by OpenFlare. Do not edit manually.\n") @@ -83,120 +99,21 @@ func RenderRouteConfig(doc Document, certificateFiles []SupportFile) (string, er cacheConfig := routeCacheConfig{Enabled: route.CacheEnabled, Policy: route.CachePolicy, Rules: route.CacheRules} limitConfig := routeLimitConfig{LimitConnPerServer: route.LimitConnPerServer, LimitConnPerIP: route.LimitConnPerIP, LimitRate: route.LimitRate} powEnabled, _ := getPoWConfigForRoute(route.ID, doc.WAF) - if normalizeRouteUpstreamType(route.UpstreamType) == "pages" { - if route.PagesDeployment == nil { - return "", fmt.Errorf("route %s pages deployment is missing", route.Domain) - } - if !route.EnableHTTPS { - builder.WriteString(renderHTTPPagesServer(serverNames, displayName, route.PagesDeployment, limitConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword)) - continue - } - certIDs := normalizeCertIDs(route.CertID, route.CertIDs) - domainCertIDs := normalizeDomainCertIDs(domains, certIDs, route.DomainCertIDs) - if len(certIDs) == 0 { - return "", fmt.Errorf("路由 %s 未配置证书", route.Domain) - } - httpOnlyDomains := make([]string, 0, len(domains)) - domainsByCertID := make(map[uint][]string, len(certIDs)) - for index, domain := range domains { - if index >= len(domainCertIDs) || domainCertIDs[index] == 0 { - httpOnlyDomains = append(httpOnlyDomains, domain) - continue - } - domainsByCertID[domainCertIDs[index]] = append(domainsByCertID[domainCertIDs[index]], domain) - } - for _, certID := range certIDs { - assignedDomains := domainsByCertID[certID] - if len(assignedDomains) == 0 { - continue - } - certPEM, ok := certificates[certID] - if !ok { - return "", fmt.Errorf("route %s certificate %d does not exist", route.Domain, certID) - } - if err := validateCertificateCoverage(certPEM, assignedDomains); err != nil { - return "", fmt.Errorf("site %s certificate validation failed: %w", displayName, err) - } - } - if route.RedirectHTTP { - if len(httpOnlyDomains) > 0 { - builder.WriteString(renderHTTPPagesServer(renderServerNames(httpOnlyDomains), displayName, route.PagesDeployment, limitConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword)) - } - for _, certID := range certIDs { - if assignedDomains := domainsByCertID[certID]; len(assignedDomains) > 0 { - builder.WriteString(renderHTTPRedirectServer(renderServerNames(assignedDomains))) - } - } - } else { - builder.WriteString(renderHTTPPagesServer(serverNames, displayName, route.PagesDeployment, limitConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword)) - } - for _, certID := range certIDs { - if assignedDomains := domainsByCertID[certID]; len(assignedDomains) > 0 { - builder.WriteString(renderHTTPSPagesServer(renderServerNames(assignedDomains), displayName, certID, route.PagesDeployment, limitConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, doc.OpenRestyConfig)) - } + if normalizeRouteUpstreamType(route.UpstreamType) == routeUpstreamTypePages { + if err := renderPagesRoute(&builder, route, displayName, serverNames, certificates, limitConfig, powEnabled, doc.OpenRestyConfig); err != nil { + return "", err } continue } - upstreams := route.Upstreams - if len(upstreams) == 0 && strings.TrimSpace(route.OriginURL) != "" { - upstreams = []string{route.OriginURL} - } - upstreamConfig := buildRouteUpstreamConfig(route, upstreams) - if upstreamConfig.UsesNamedUpstream { - builder.WriteString(renderNamedUpstreamBlock(upstreamConfig)) - } - if !route.EnableHTTPS { - builder.WriteString(renderHTTPProxyServer(serverNames, displayName, route.OriginURL, route.OriginHost, route.CustomHeaders, cacheConfig, limitConfig, upstreamConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, doc.OpenRestyConfig)) - continue - } - certIDs := normalizeCertIDs(route.CertID, route.CertIDs) - domainCertIDs := normalizeDomainCertIDs(domains, certIDs, route.DomainCertIDs) - if len(certIDs) == 0 { - return "", fmt.Errorf("路由 %s 未配置证书", route.Domain) - } - httpOnlyDomains := make([]string, 0, len(domains)) - domainsByCertID := make(map[uint][]string, len(certIDs)) - for index, domain := range domains { - if index >= len(domainCertIDs) || domainCertIDs[index] == 0 { - httpOnlyDomains = append(httpOnlyDomains, domain) - continue - } - domainsByCertID[domainCertIDs[index]] = append(domainsByCertID[domainCertIDs[index]], domain) - } - for _, certID := range certIDs { - assignedDomains := domainsByCertID[certID] - if len(assignedDomains) == 0 { - continue - } - certPEM, ok := certificates[certID] - if !ok { - return "", fmt.Errorf("route %s certificate %d does not exist", route.Domain, certID) - } - if err := validateCertificateCoverage(certPEM, assignedDomains); err != nil { - return "", fmt.Errorf("site %s certificate validation failed: %w", displayName, err) - } - } - if route.RedirectHTTP { - if len(httpOnlyDomains) > 0 { - builder.WriteString(renderHTTPProxyServer(renderServerNames(httpOnlyDomains), displayName, route.OriginURL, route.OriginHost, route.CustomHeaders, cacheConfig, limitConfig, upstreamConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, doc.OpenRestyConfig)) - } - for _, certID := range certIDs { - if assignedDomains := domainsByCertID[certID]; len(assignedDomains) > 0 { - builder.WriteString(renderHTTPRedirectServer(renderServerNames(assignedDomains))) - } - } - } else { - builder.WriteString(renderHTTPProxyServer(serverNames, displayName, route.OriginURL, route.OriginHost, route.CustomHeaders, cacheConfig, limitConfig, upstreamConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, doc.OpenRestyConfig)) - } - for _, certID := range certIDs { - if assignedDomains := domainsByCertID[certID]; len(assignedDomains) > 0 { - builder.WriteString(renderHTTPSServer(renderServerNames(assignedDomains), displayName, route.OriginURL, route.OriginHost, certID, route.CustomHeaders, cacheConfig, limitConfig, upstreamConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, doc.OpenRestyConfig)) - } + if err := renderProxyRoute(&builder, route, displayName, serverNames, certificates, cacheConfig, limitConfig, powEnabled, doc.OpenRestyConfig); err != nil { + return "", err } } return builder.String(), nil } +// RenderPoWConfig serialises the Proof-of-Work configuration for all enabled +// routes as a JSON string consumed by the OpenResty Lua runtime. func RenderPoWConfig(doc Document) (string, error) { type domainEntry struct { Domains []string `json:"domains"` @@ -218,6 +135,8 @@ func RenderPoWConfig(doc Document) (string, error) { return string(data), err } +// RenderWAFConfig serialises the WAF runtime configuration (rule groups and +// per-site bindings) as a JSON string consumed by the OpenResty Lua runtime. func RenderWAFConfig(snapshot WAFDocument) (string, error) { type wafRuntimeRuleGroup struct { ID uint `json:"id"` @@ -312,6 +231,9 @@ func sortedUniqueUintIDs(values []uint) []uint { return items } +// ChecksumBundle returns a stable SHA-256 hex digest over the combined content +// of the main config, route config, and deduplicated support files, excluding +// the source config JSON file itself. func ChecksumBundle(mainConfig string, routeConfig string, supportFiles []SupportFile) string { var builder strings.Builder builder.WriteString(mainConfig) @@ -333,6 +255,8 @@ func ChecksumBundle(mainConfig string, routeConfig string, supportFiles []Suppor return hex.EncodeToString(sum[:]) } +// DedupeSupportFiles returns a new slice with duplicate paths removed, keeping +// the last occurrence of each path. func DedupeSupportFiles(files []SupportFile) []SupportFile { if len(files) == 0 { return nil @@ -437,21 +361,22 @@ func renderPagesAPIProxyLocationBlock(deployment *PagesDeployment) string { cleanPath := strings.TrimSuffix(path, "/") var builder strings.Builder - builder.WriteString(fmt.Sprintf("\n location %s {\n", cleanPath)) + // 使用 fmt.Fprintf 替代 WriteString(fmt.Sprintf(...))(QF1012) + fmt.Fprintf(&builder, "\n location %s {\n", cleanPath) if rewrite != "" { if !strings.HasPrefix(rewrite, "/") { rewrite = "/" + rewrite } cleanRewrite := strings.TrimSuffix(rewrite, "/") if cleanRewrite == "" { - builder.WriteString(fmt.Sprintf(" rewrite ^%s/(.*)$ /$1 break;\n", regexp.QuoteMeta(cleanPath))) - builder.WriteString(fmt.Sprintf(" rewrite ^%s$ / break;\n", regexp.QuoteMeta(cleanPath))) + fmt.Fprintf(&builder, " rewrite ^%s/(.*)$ /$1 break;\n", regexp.QuoteMeta(cleanPath)) + fmt.Fprintf(&builder, " rewrite ^%s$ / break;\n", regexp.QuoteMeta(cleanPath)) } else { - builder.WriteString(fmt.Sprintf(" rewrite ^%s/(.*)$ %s/$1 break;\n", regexp.QuoteMeta(cleanPath), cleanRewrite)) - builder.WriteString(fmt.Sprintf(" rewrite ^%s$ %s break;\n", regexp.QuoteMeta(cleanPath), cleanRewrite)) + fmt.Fprintf(&builder, " rewrite ^%s/(.*)$ %s/$1 break;\n", regexp.QuoteMeta(cleanPath), cleanRewrite) + fmt.Fprintf(&builder, " rewrite ^%s$ %s break;\n", regexp.QuoteMeta(cleanPath), cleanRewrite) } } - builder.WriteString(fmt.Sprintf(" proxy_pass %s;\n", pass)) + fmt.Fprintf(&builder, " proxy_pass %s;\n", pass) builder.WriteString(" proxy_http_version 1.1;\n") builder.WriteString(" proxy_set_header Host $http_host;\n") builder.WriteString(" proxy_set_header X-Real-IP $remote_addr;\n") @@ -499,7 +424,7 @@ func renderPagesLocationBlock(deployment *PagesDeployment, limitConfig routeLimi var builder strings.Builder builder.WriteString(renderRouteLimitBlock(limitConfig)) if deployment != nil && deployment.SPAFallbackEnabled { - builder.WriteString(fmt.Sprintf(" try_files $uri $uri/ %s;\n", pagesFallbackPath(deployment))) + fmt.Fprintf(&builder, " try_files $uri $uri/ %s;\n", pagesFallbackPath(deployment)) } else { builder.WriteString(" try_files $uri $uri/ =404;\n") } @@ -522,18 +447,18 @@ func pagesEntryFile(deployment *PagesDeployment) string { func pagesFallbackPath(deployment *PagesDeployment) string { if deployment == nil || strings.TrimSpace(deployment.SPAFallbackPath) == "" { - return "/index.html" + return indexHTML } value := filepathToNginxPath(strings.TrimSpace(deployment.SPAFallbackPath)) if !strings.HasPrefix(value, "/") { value = "/" + value } if value == "/" || strings.HasSuffix(value, "/") || strings.Contains(value, "\\") || strings.ContainsAny(value, "\"';") || strings.ContainsAny(value, " \t\r\n") { - return "/index.html" + return indexHTML } for _, segment := range strings.Split(value, "/") { if segment == "." || segment == ".." { - return "/index.html" + return indexHTML } } cleaned := path.Clean(value) @@ -546,13 +471,13 @@ func pagesFallbackPath(deployment *PagesDeployment) string { func renderProxyHeaderBlock(originURL string, originHost string, customHeaders []CustomHeader, upstreamConfig routeUpstreamConfig, cfg ConfigSnapshot) string { var builder strings.Builder if strings.TrimSpace(originHost) != "" { - builder.WriteString(fmt.Sprintf(" proxy_set_header Host %s;\n", quoteNginxStringLiteral(originHost))) + fmt.Fprintf(&builder, " proxy_set_header Host %s;\n", quoteNginxStringLiteral(originHost)) } else { builder.WriteString(" proxy_set_header Host $host;\n") } if upstreamServerName := resolveUpstreamServerName(originURL, originHost); upstreamServerName != "" { builder.WriteString(" proxy_ssl_server_name on;\n") - builder.WriteString(fmt.Sprintf(" proxy_ssl_name %s;\n", quoteNginxStringLiteral(upstreamServerName))) + fmt.Fprintf(&builder, " proxy_ssl_name %s;\n", quoteNginxStringLiteral(upstreamServerName)) } builder.WriteString(" proxy_set_header X-Real-IP $remote_addr;\n") builder.WriteString(" proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n") @@ -566,7 +491,7 @@ func renderProxyHeaderBlock(originURL string, originHost string, customHeaders [ builder.WriteString(" proxy_set_header Connection \"\";\n") } for _, header := range customHeaders { - builder.WriteString(fmt.Sprintf(" proxy_set_header %s %s;\n", header.Key, quoteNginxStringLiteral(header.Value))) + fmt.Fprintf(&builder, " proxy_set_header %s %s;\n", header.Key, quoteNginxStringLiteral(header.Value)) } return builder.String() } @@ -635,13 +560,13 @@ func renderRouteCacheBlock(cacheConfig routeCacheConfig, cfg ConfigSnapshot) str func renderRouteLimitBlock(limitConfig routeLimitConfig) string { var builder strings.Builder if limitConfig.LimitConnPerServer > 0 { - builder.WriteString(fmt.Sprintf(" limit_conn openflare_conn_per_server %d;\n", limitConfig.LimitConnPerServer)) + fmt.Fprintf(&builder, " limit_conn openflare_conn_per_server %d;\n", limitConfig.LimitConnPerServer) } if limitConfig.LimitConnPerIP > 0 { - builder.WriteString(fmt.Sprintf(" limit_conn openflare_conn_per_ip %d;\n", limitConfig.LimitConnPerIP)) + fmt.Fprintf(&builder, " limit_conn openflare_conn_per_ip %d;\n", limitConfig.LimitConnPerIP) } if strings.TrimSpace(limitConfig.LimitRate) != "" { - builder.WriteString(fmt.Sprintf(" limit_rate %s;\n", limitConfig.LimitRate)) + fmt.Fprintf(&builder, " limit_rate %s;\n", limitConfig.LimitRate) } return builder.String() } @@ -700,8 +625,8 @@ func buildRouteUpstreamConfig(route Route, upstreams []string) routeUpstreamConf func normalizeRouteUpstreamType(raw string) string { switch strings.ToLower(strings.TrimSpace(raw)) { - case "pages": - return "pages" + case routeUpstreamTypePages: + return routeUpstreamTypePages default: return "direct" } @@ -709,9 +634,9 @@ func normalizeRouteUpstreamType(raw string) string { func renderNamedUpstreamBlock(upstreamConfig routeUpstreamConfig) string { var builder strings.Builder - builder.WriteString(fmt.Sprintf("upstream %s {\n", upstreamConfig.Name)) + fmt.Fprintf(&builder, "upstream %s {\n", upstreamConfig.Name) for _, server := range upstreamConfig.Servers { - builder.WriteString(fmt.Sprintf(" server %s max_fails=3 fail_timeout=10s;\n", server)) + fmt.Fprintf(&builder, " server %s max_fails=3 fail_timeout=10s;\n", server) } builder.WriteString(" keepalive 128;\n}\n\n") return builder.String() diff --git a/pkg/render/openresty/render_route.go b/pkg/render/openresty/render_route.go new file mode 100644 index 00000000..40d97c42 --- /dev/null +++ b/pkg/render/openresty/render_route.go @@ -0,0 +1,151 @@ +package openresty + +import ( + "fmt" + "strings" +) + +type routeCertPartition struct { + httpOnlyDomains []string + domainsByCertID map[uint][]string +} + +func partitionRouteDomainsByCert(domains []string, certIDs, domainCertIDs []uint) routeCertPartition { + httpOnlyDomains := make([]string, 0, len(domains)) + domainsByCertID := make(map[uint][]string, len(certIDs)) + for index, domain := range domains { + if index >= len(domainCertIDs) || domainCertIDs[index] == 0 { + httpOnlyDomains = append(httpOnlyDomains, domain) + continue + } + domainsByCertID[domainCertIDs[index]] = append(domainsByCertID[domainCertIDs[index]], domain) + } + return routeCertPartition{ + httpOnlyDomains: httpOnlyDomains, + domainsByCertID: domainsByCertID, + } +} + +func validateRouteCertificates(route Route, displayName string, certIDs []uint, partition routeCertPartition, certificates map[uint]string) error { + for _, certID := range certIDs { + assignedDomains := partition.domainsByCertID[certID] + if len(assignedDomains) == 0 { + continue + } + certPEM, ok := certificates[certID] + if !ok { + return fmt.Errorf("route %s certificate %d does not exist", route.Domain, certID) + } + if err := validateCertificateCoverage(certPEM, assignedDomains); err != nil { + return fmt.Errorf("site %s certificate validation failed: %w", displayName, err) + } + } + return nil +} + +func renderPagesRouteHTTPS( + builder *strings.Builder, + serverNames, displayName string, + route Route, + partition routeCertPartition, + certIDs []uint, + limitConfig routeLimitConfig, + powEnabled bool, + cfg ConfigSnapshot, +) { + if route.RedirectHTTP { + if len(partition.httpOnlyDomains) > 0 { + builder.WriteString(renderHTTPPagesServer(renderServerNames(partition.httpOnlyDomains), displayName, route.PagesDeployment, limitConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword)) + } + for _, certID := range certIDs { + if assignedDomains := partition.domainsByCertID[certID]; len(assignedDomains) > 0 { + builder.WriteString(renderHTTPRedirectServer(renderServerNames(assignedDomains))) + } + } + } else { + builder.WriteString(renderHTTPPagesServer(serverNames, displayName, route.PagesDeployment, limitConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword)) + } + for _, certID := range certIDs { + if assignedDomains := partition.domainsByCertID[certID]; len(assignedDomains) > 0 { + builder.WriteString(renderHTTPSPagesServer(renderServerNames(assignedDomains), displayName, certID, route.PagesDeployment, limitConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, cfg)) + } + } +} + +func renderProxyRouteHTTPS( + builder *strings.Builder, + serverNames, displayName string, + route Route, + partition routeCertPartition, + certIDs []uint, + cacheConfig routeCacheConfig, + limitConfig routeLimitConfig, + upstreamConfig routeUpstreamConfig, + powEnabled bool, + cfg ConfigSnapshot, +) { + if route.RedirectHTTP { + if len(partition.httpOnlyDomains) > 0 { + builder.WriteString(renderHTTPProxyServer(renderServerNames(partition.httpOnlyDomains), displayName, route.OriginURL, route.OriginHost, route.CustomHeaders, cacheConfig, limitConfig, upstreamConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, cfg)) + } + for _, certID := range certIDs { + if assignedDomains := partition.domainsByCertID[certID]; len(assignedDomains) > 0 { + builder.WriteString(renderHTTPRedirectServer(renderServerNames(assignedDomains))) + } + } + } else { + builder.WriteString(renderHTTPProxyServer(serverNames, displayName, route.OriginURL, route.OriginHost, route.CustomHeaders, cacheConfig, limitConfig, upstreamConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, cfg)) + } + for _, certID := range certIDs { + if assignedDomains := partition.domainsByCertID[certID]; len(assignedDomains) > 0 { + builder.WriteString(renderHTTPSServer(renderServerNames(assignedDomains), displayName, route.OriginURL, route.OriginHost, certID, route.CustomHeaders, cacheConfig, limitConfig, upstreamConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, cfg)) + } + } +} + +func renderPagesRoute(builder *strings.Builder, route Route, displayName, serverNames string, certificates map[uint]string, limitConfig routeLimitConfig, powEnabled bool, cfg ConfigSnapshot) error { + if route.PagesDeployment == nil { + return fmt.Errorf("route %s pages deployment is missing", route.Domain) + } + if !route.EnableHTTPS { + builder.WriteString(renderHTTPPagesServer(serverNames, displayName, route.PagesDeployment, limitConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword)) + return nil + } + certIDs := normalizeCertIDs(route.CertID, route.CertIDs) + domainCertIDs := normalizeDomainCertIDs(normalizedRouteDomains(route), certIDs, route.DomainCertIDs) + if len(certIDs) == 0 { + return fmt.Errorf("路由 %s 未配置证书", route.Domain) + } + partition := partitionRouteDomainsByCert(normalizedRouteDomains(route), certIDs, domainCertIDs) + if err := validateRouteCertificates(route, displayName, certIDs, partition, certificates); err != nil { + return err + } + renderPagesRouteHTTPS(builder, serverNames, displayName, route, partition, certIDs, limitConfig, powEnabled, cfg) + return nil +} + +func renderProxyRoute(builder *strings.Builder, route Route, displayName, serverNames string, certificates map[uint]string, cacheConfig routeCacheConfig, limitConfig routeLimitConfig, powEnabled bool, cfg ConfigSnapshot) error { + upstreams := route.Upstreams + if len(upstreams) == 0 && strings.TrimSpace(route.OriginURL) != "" { + upstreams = []string{route.OriginURL} + } + upstreamConfig := buildRouteUpstreamConfig(route, upstreams) + if upstreamConfig.UsesNamedUpstream { + builder.WriteString(renderNamedUpstreamBlock(upstreamConfig)) + } + if !route.EnableHTTPS { + builder.WriteString(renderHTTPProxyServer(serverNames, displayName, route.OriginURL, route.OriginHost, route.CustomHeaders, cacheConfig, limitConfig, upstreamConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, cfg)) + return nil + } + certIDs := normalizeCertIDs(route.CertID, route.CertIDs) + domainCertIDs := normalizeDomainCertIDs(normalizedRouteDomains(route), certIDs, route.DomainCertIDs) + if len(certIDs) == 0 { + return fmt.Errorf("路由 %s 未配置证书", route.Domain) + } + partition := partitionRouteDomainsByCert(normalizedRouteDomains(route), certIDs, domainCertIDs) + if err := validateRouteCertificates(route, displayName, certIDs, partition, certificates); err != nil { + return err + } + renderProxyRouteHTTPS(builder, serverNames, displayName, route, partition, certIDs, cacheConfig, limitConfig, upstreamConfig, powEnabled, cfg) + return nil +} diff --git a/pkg/render/openresty/types.go b/pkg/render/openresty/types.go index 9beb4734..4ef6fa2a 100644 --- a/pkg/render/openresty/types.go +++ b/pkg/render/openresty/types.go @@ -1,5 +1,7 @@ package openresty +// Placeholder constants used as sentinel values in rendered OpenResty config +// files; the deploy process replaces them with real paths before reload. const ( CertDirPlaceholder = "__OPENFLARE_CERT_DIR__" RouteConfigPlaceholder = "__OPENFLARE_ROUTE_CONFIG__" @@ -63,16 +65,22 @@ http { } ` +// SupportFile represents an auxiliary file (certificate, WAF config, etc.) +// that is written alongside the main OpenResty configuration. type SupportFile struct { Path string `json:"path"` Content string `json:"content"` } +// CustomHeader is a key/value pair injected as an additional proxy_set_header +// directive for a specific route. type CustomHeader struct { Key string `json:"key"` Value string `json:"value"` } +// PoWListConfig holds the IP, CIDR, path, and user-agent lists used by the +// Proof-of-Work whitelist or blacklist filter. type PoWListConfig struct { IPs []string `json:"ips"` IPCidrs []string `json:"ip_cidrs"` @@ -81,6 +89,8 @@ type PoWListConfig struct { UserAgents []string `json:"user_agents"` } +// PoWConfig holds the full Proof-of-Work challenge parameters for a route, +// including difficulty, algorithm, TTLs, and allow/block lists. type PoWConfig struct { Difficulty int `json:"difficulty"` Algorithm string `json:"algorithm"` @@ -90,6 +100,8 @@ type PoWConfig struct { Blacklist PoWListConfig `json:"blacklist"` } +// Route describes a single proxy or pages site entry in the OpenFlare config +// document, including upstream, TLS, caching, rate-limiting and WAF settings. type Route struct { ID uint `json:"id,omitempty"` SiteName string `json:"site_name,omitempty"` @@ -121,6 +133,8 @@ type Route struct { PagesDeployment *PagesDeployment `json:"pages_deployment,omitempty"` } +// PagesDeployment holds the static-site deployment parameters for a Pages-type +// route, including local root, entry file, SPA fallback, and API proxy options. type PagesDeployment struct { ProjectID uint `json:"project_id"` ProjectSlug string `json:"project_slug"` @@ -137,6 +151,8 @@ type PagesDeployment struct { LocalRoot string `json:"local_root"` } +// WAFRuleGroup defines a WAF rule group with IP/country/region lists, PoW +// integration, and per-group block status configuration. type WAFRuleGroup struct { ID uint `json:"id"` Name string `json:"name"` @@ -156,6 +172,8 @@ type WAFRuleGroup struct { PoWConfig *PoWConfig `json:"pow_config,omitempty"` } +// WAFIPGroup is a named, reusable list of IP addresses or CIDRs that can be +// referenced by multiple WAF rule groups as a whitelist or blacklist. type WAFIPGroup struct { ID uint `json:"id"` Name string `json:"name"` @@ -164,18 +182,24 @@ type WAFIPGroup struct { IPList []string `json:"ip_list,omitempty"` } +// WAFBinding associates a route (by site name) with the WAF rule groups that +// should be enforced for that site. type WAFBinding struct { RouteID uint `json:"route_id"` SiteName string `json:"site_name"` RuleGroupIDs []uint `json:"rule_group_ids"` } +// WAFDocument is the top-level WAF configuration snapshot containing rule +// groups, IP groups, and per-site bindings. type WAFDocument struct { RuleGroups []WAFRuleGroup `json:"rule_groups"` IPGroups []WAFIPGroup `json:"ip_groups,omitempty"` Bindings []WAFBinding `json:"bindings"` } +// ConfigSnapshot holds the full set of OpenResty tuning parameters that are +// rendered into the nginx main configuration template. type ConfigSnapshot struct { DefaultServerReturnStatus int `json:"default_server_return_status"` WorkerProcesses string `json:"worker_processes"` @@ -216,12 +240,16 @@ type ConfigSnapshot struct { MainConfigTemplate string `json:"main_config_template,omitempty"` } +// Document is the top-level input structure for the OpenResty renderer, +// combining routes, OpenResty tuning, and WAF configuration. type Document struct { Routes []Route `json:"routes"` OpenRestyConfig ConfigSnapshot `json:"openresty_config"` WAF WAFDocument `json:"waf"` } +// Result is the output produced by Render, containing the rendered main +// config, route config, support files, and a content checksum. type Result struct { MainConfig string RouteConfig string diff --git a/pkg/utils/format.go b/pkg/utils/format.go index ee6b0f96..b21ae3b5 100644 --- a/pkg/utils/format.go +++ b/pkg/utils/format.go @@ -1,3 +1,4 @@ +// Package utils provides shared formatting and string helper functions. package utils import ( @@ -5,48 +6,59 @@ import ( "strconv" ) +const ( + secondsPerYear = 31104000 // 360 days + secondsPerMonth = 2592000 // 30 days + secondsPerDay = 86400 + secondsPerHour = 3600 + secondsPerMinute = 60 +) + var sizeKB = 1024 var sizeMB = sizeKB * 1024 var sizeGB = sizeMB * 1024 +// Bytes2Size converts a byte count to a human-readable string with unit (B, KB, MB, GB). func Bytes2Size(num int64) string { numStr := "" unit := "B" - if num/int64(sizeGB) > 1 { + switch { + case num/int64(sizeGB) > 1: numStr = fmt.Sprintf("%.2f", float64(num)/float64(sizeGB)) unit = "GB" - } else if num/int64(sizeMB) > 1 { + case num/int64(sizeMB) > 1: numStr = fmt.Sprintf("%d", int(float64(num)/float64(sizeMB))) unit = "MB" - } else if num/int64(sizeKB) > 1 { + case num/int64(sizeKB) > 1: numStr = fmt.Sprintf("%d", int(float64(num)/float64(sizeKB))) unit = "KB" - } else { + default: numStr = fmt.Sprintf("%d", num) } return numStr + " " + unit } +// Seconds2Time converts a number of seconds to a human-readable Chinese duration string. func Seconds2Time(num int) (time string) { - if num/31104000 > 0 { - time += strconv.Itoa(num/31104000) + " 年 " - num %= 31104000 + if num/secondsPerYear > 0 { + time += strconv.Itoa(num/secondsPerYear) + " 年 " + num %= secondsPerYear } - if num/2592000 > 0 { - time += strconv.Itoa(num/2592000) + " 个月 " - num %= 2592000 + if num/secondsPerMonth > 0 { + time += strconv.Itoa(num/secondsPerMonth) + " 个月 " + num %= secondsPerMonth } - if num/86400 > 0 { - time += strconv.Itoa(num/86400) + " 天 " - num %= 86400 + if num/secondsPerDay > 0 { + time += strconv.Itoa(num/secondsPerDay) + " 天 " + num %= secondsPerDay } - if num/3600 > 0 { - time += strconv.Itoa(num/3600) + " 小时 " - num %= 3600 + if num/secondsPerHour > 0 { + time += strconv.Itoa(num/secondsPerHour) + " 小时 " + num %= secondsPerHour } - if num/60 > 0 { - time += strconv.Itoa(num/60) + " 分钟 " - num %= 60 + if num/secondsPerMinute > 0 { + time += strconv.Itoa(num/secondsPerMinute) + " 分钟 " + num %= secondsPerMinute } time += strconv.Itoa(num) + " 秒" return diff --git a/pkg/utils/network.go b/pkg/utils/network.go index 9d2060e7..c1e7af67 100644 --- a/pkg/utils/network.go +++ b/pkg/utils/network.go @@ -6,7 +6,8 @@ import ( "strings" ) -func GetIp() (ip string) { +// GetIP returns the first private IPv4 address found on the local network interfaces. +func GetIP() (ip string) { ips, err := net.InterfaceAddrs() if err != nil { slog.Error("get interface addresses failed", "error", err) @@ -14,21 +15,27 @@ func GetIp() (ip string) { } for _, a := range ips { - if ipNet, ok := a.(*net.IPNet); ok && !ipNet.IP.IsLoopback() { - if ipNet.IP.To4() != nil { - ip = ipNet.IP.String() - if strings.HasPrefix(ip, "10") { - return - } - if strings.HasPrefix(ip, "172") { - return - } - if strings.HasPrefix(ip, "192.168") { - return - } - ip = "" - } + if candidate, ok := privateIPv4FromAddr(a); ok { + return candidate } } return } + +func privateIPv4FromAddr(addr net.Addr) (string, bool) { + ipNet, ok := addr.(*net.IPNet) + if !ok || ipNet.IP.IsLoopback() || ipNet.IP.To4() == nil { + return "", false + } + ip := ipNet.IP.String() + if isPrivateIPv4(ip) { + return ip, true + } + return "", false +} + +func isPrivateIPv4(ip string) bool { + return strings.HasPrefix(ip, "10") || + strings.HasPrefix(ip, "172") || + strings.HasPrefix(ip, "192.168") +} diff --git a/pkg/utils/value.go b/pkg/utils/value.go index e4c09cf6..75f71a2a 100644 --- a/pkg/utils/value.go +++ b/pkg/utils/value.go @@ -2,14 +2,15 @@ package utils import "fmt" +// Interface2String converts a string, int, or float64 value to its string representation. func Interface2String(inter interface{}) string { - switch inter.(type) { + switch v := inter.(type) { case string: - return inter.(string) + return v case int: - return fmt.Sprintf("%d", inter.(int)) + return fmt.Sprintf("%d", v) case float64: - return fmt.Sprintf("%f", inter.(float64)) + return fmt.Sprintf("%f", v) } return "Not Implemented" } diff --git a/pkg/utils/version.go b/pkg/utils/version.go index a3ea780e..1eaeea97 100644 --- a/pkg/utils/version.go +++ b/pkg/utils/version.go @@ -5,6 +5,9 @@ import ( "strings" ) +const gitDescribeMinIdentifiers = 2 + +// VersionInfo holds the parsed components of a semantic version string. type VersionInfo struct { Valid bool IsDev bool @@ -14,6 +17,7 @@ type VersionInfo struct { GitDescribeTail []string } +// ParseVersionInfo parses a version string into a structured VersionInfo. func ParseVersionInfo(version string) VersionInfo { normalized := strings.TrimSpace(strings.TrimPrefix(version, "v")) if normalized == "" || normalized == "dev" { @@ -66,7 +70,7 @@ func ParseVersionInfo(version string) VersionInfo { } func parseGitDescribeIdentifiers(identifiers []string) (int, []string, bool) { - if len(identifiers) < 2 { + if len(identifiers) < gitDescribeMinIdentifiers { return 0, nil, false } distance, err := strconv.Atoi(strings.TrimSpace(identifiers[0])) @@ -109,100 +113,14 @@ func CompareVersions(local, remote string) int { return 0 } - maxLen := len(left.Numbers) - if len(right.Numbers) > maxLen { - maxLen = len(right.Numbers) + if result := compareVersionNumbers(left, right); result != 0 { + return result } - for index := 0; index < maxLen; index++ { - leftValue := 0 - rightValue := 0 - if index < len(left.Numbers) { - leftValue = left.Numbers[index] - } - if index < len(right.Numbers) { - rightValue = right.Numbers[index] - } - if leftValue < rightValue { - return -1 - } - if leftValue > rightValue { - return 1 - } - } - - if left.GitDescribeDistance != right.GitDescribeDistance { - if left.GitDescribeDistance < right.GitDescribeDistance { - return -1 - } - return 1 + if result := compareGitDescribeDistance(left, right); result != 0 { + return result } if left.GitDescribeDistance > 0 || right.GitDescribeDistance > 0 { - maxLen = len(left.GitDescribeTail) - if len(right.GitDescribeTail) > maxLen { - maxLen = len(right.GitDescribeTail) - } - for index := 0; index < maxLen; index++ { - if index >= len(left.GitDescribeTail) { - return -1 - } - if index >= len(right.GitDescribeTail) { - return 1 - } - if left.GitDescribeTail[index] < right.GitDescribeTail[index] { - return -1 - } - if left.GitDescribeTail[index] > right.GitDescribeTail[index] { - return 1 - } - } - return 0 + return compareGitDescribeTails(left, right) } - - if len(left.Prerelease) == 0 && len(right.Prerelease) == 0 { - return 0 - } - if len(left.Prerelease) == 0 { - return 1 - } - if len(right.Prerelease) == 0 { - return -1 - } - - maxLen = len(left.Prerelease) - if len(right.Prerelease) > maxLen { - maxLen = len(right.Prerelease) - } - for index := 0; index < maxLen; index++ { - if index >= len(left.Prerelease) { - return -1 - } - if index >= len(right.Prerelease) { - return 1 - } - leftPart := left.Prerelease[index] - rightPart := right.Prerelease[index] - leftNumber, leftErr := strconv.Atoi(leftPart) - rightNumber, rightErr := strconv.Atoi(rightPart) - switch { - case leftErr == nil && rightErr == nil: - if leftNumber < rightNumber { - return -1 - } - if leftNumber > rightNumber { - return 1 - } - case leftErr == nil: - return -1 - case rightErr == nil: - return 1 - default: - if leftPart < rightPart { - return -1 - } - if leftPart > rightPart { - return 1 - } - } - } - return 0 + return comparePrereleaseIdentifiers(left, right) } diff --git a/pkg/utils/version_compare.go b/pkg/utils/version_compare.go new file mode 100644 index 00000000..1338a189 --- /dev/null +++ b/pkg/utils/version_compare.go @@ -0,0 +1,114 @@ +package utils + +import "strconv" + +func compareVersionNumbers(left, right VersionInfo) int { + maxLen := len(left.Numbers) + if len(right.Numbers) > maxLen { + maxLen = len(right.Numbers) + } + for index := 0; index < maxLen; index++ { + leftValue := 0 + rightValue := 0 + if index < len(left.Numbers) { + leftValue = left.Numbers[index] + } + if index < len(right.Numbers) { + rightValue = right.Numbers[index] + } + if leftValue < rightValue { + return -1 + } + if leftValue > rightValue { + return 1 + } + } + return 0 +} + +func compareGitDescribeDistance(left, right VersionInfo) int { + if left.GitDescribeDistance == right.GitDescribeDistance { + return 0 + } + if left.GitDescribeDistance < right.GitDescribeDistance { + return -1 + } + return 1 +} + +func compareGitDescribeTails(left, right VersionInfo) int { + maxLen := len(left.GitDescribeTail) + if len(right.GitDescribeTail) > maxLen { + maxLen = len(right.GitDescribeTail) + } + for index := 0; index < maxLen; index++ { + if index >= len(left.GitDescribeTail) { + return -1 + } + if index >= len(right.GitDescribeTail) { + return 1 + } + if left.GitDescribeTail[index] < right.GitDescribeTail[index] { + return -1 + } + if left.GitDescribeTail[index] > right.GitDescribeTail[index] { + return 1 + } + } + return 0 +} + +func comparePrereleaseIdentifiers(left, right VersionInfo) int { + if len(left.Prerelease) == 0 && len(right.Prerelease) == 0 { + return 0 + } + if len(left.Prerelease) == 0 { + return 1 + } + if len(right.Prerelease) == 0 { + return -1 + } + + maxLen := len(left.Prerelease) + if len(right.Prerelease) > maxLen { + maxLen = len(right.Prerelease) + } + for index := 0; index < maxLen; index++ { + if index >= len(left.Prerelease) { + return -1 + } + if index >= len(right.Prerelease) { + return 1 + } + if result := comparePrereleasePart(left.Prerelease[index], right.Prerelease[index]); result != 0 { + return result + } + } + return 0 +} + +func comparePrereleasePart(leftPart, rightPart string) int { + leftNumber, leftErr := strconv.Atoi(leftPart) + rightNumber, rightErr := strconv.Atoi(rightPart) + switch { + case leftErr == nil && rightErr == nil: + if leftNumber < rightNumber { + return -1 + } + if leftNumber > rightNumber { + return 1 + } + case leftErr == nil: + return -1 + case rightErr == nil: + return 1 + default: + if leftPart < rightPart { + return -1 + } + if leftPart > rightPart { + return 1 + } + } + return 0 +} diff --git a/pkg/wsclient/client.go b/pkg/wsclient/client.go index 1260f85d..2a567ed4 100644 --- a/pkg/wsclient/client.go +++ b/pkg/wsclient/client.go @@ -1,3 +1,4 @@ +// Package wsclient provides a WebSocket client for agent/server communication. package wsclient import ( @@ -14,6 +15,12 @@ import ( "golang.org/x/net/websocket" ) +const ( + writeDeadlineSecs = 5 + defaultReadDeadlineSecs = 75 +) + +// Config holds the configuration for a WebSocket client connection. type Config struct { BaseURL string Token string @@ -22,27 +29,32 @@ type Config struct { WSPath string // e.g. "/api/relay/ws", "/api/agent/ws", "/api/flared/ws" } +// Client provides methods to connect and communicate over WebSocket. type Client struct { cfg Config } +// WSMessage represents a typed WebSocket message with an optional JSON payload. type WSMessage struct { Type string `json:"type"` Payload json.RawMessage `json:"payload,omitempty"` } +// MessageHandler handles WebSocket connection lifecycle and incoming messages. type MessageHandler interface { OnConnect(ctx context.Context) error HandleMessage(ctx context.Context, msg WSMessage) error OnClose(err error) } +// Connection represents an active WebSocket connection. type Connection struct { Conn *websocket.Conn URL string ReadTimeout time.Duration } +// New creates a new WebSocket client with the given configuration. func New(cfg Config) *Client { cfg.BaseURL = strings.TrimRight(cfg.BaseURL, "/") cfg.Token = strings.TrimSpace(cfg.Token) @@ -53,10 +65,12 @@ func New(cfg Config) *Client { } } +// SetToken updates the authentication token used for the WebSocket connection. func (c *Client) SetToken(token string) { c.cfg.Token = strings.TrimSpace(token) } +// URL returns the WebSocket URL for the configured endpoint, or empty string on error. func (c *Client) URL() string { wsURL, err := c.BuildWebsocketURL() if err != nil { @@ -65,6 +79,7 @@ func (c *Client) URL() string { return wsURL } +// BuildWebsocketURL constructs the WebSocket URL by converting the base URL scheme and appending the WS path. func (c *Client) BuildWebsocketURL() (string, error) { parsed, err := url.Parse(c.cfg.BaseURL) if err != nil { @@ -90,6 +105,7 @@ func (c *Client) BuildWebsocketURL() (string, error) { return parsed.String(), nil } +// Connect establishes a new WebSocket connection to the configured server. func (c *Client) Connect(ctx context.Context) (*Connection, error) { wsURL, err := c.BuildWebsocketURL() if err != nil { @@ -122,6 +138,7 @@ func (c *Client) Connect(ctx context.Context) (*Connection, error) { return &Connection{Conn: conn, URL: wsURL, ReadTimeout: websocketReadTimeout(c.cfg.Timeout)}, nil } +// SendMessage sends a typed message with an optional payload over the WebSocket connection. func (conn *Connection) SendMessage(msgType string, payload any) error { if conn == nil || conn.Conn == nil { return errors.New("ws connection is nil") @@ -137,10 +154,11 @@ func (conn *Connection) SendMessage(msgType string, payload any) error { Payload: payload, } - _ = conn.Conn.SetWriteDeadline(time.Now().Add(5 * time.Second)) + _ = conn.Conn.SetWriteDeadline(time.Now().Add(writeDeadlineSecs * time.Second)) return websocket.JSON.Send(conn.Conn, message) } +// Receive reads a single message from the WebSocket connection into target. func (conn *Connection) Receive(target any) error { if conn == nil || conn.Conn == nil { return errors.New("ws connection is nil") @@ -161,12 +179,13 @@ func (conn *Connection) Receive(target any) error { func websocketReadTimeout(requestTimeout time.Duration) time.Duration { timeout := requestTimeout * 6 - if timeout < 75*time.Second { - return 75 * time.Second + if timeout < defaultReadDeadlineSecs*time.Second { + return defaultReadDeadlineSecs * time.Second } return timeout } +// RunReceiveLoop continuously receives messages and dispatches them to the handler until the context is cancelled. func (conn *Connection) RunReceiveLoop(ctx context.Context, handler MessageHandler) error { doneChan := make(chan struct{}) defer close(doneChan) @@ -214,6 +233,7 @@ func (conn *Connection) RunReceiveLoop(ctx context.Context, handler MessageHandl } } +// Close gracefully closes the WebSocket connection. func (conn *Connection) Close() error { if conn == nil || conn.Conn == nil { return nil