Commit ee8ba7ed authored by Vitaly Lipatov's avatar Vitaly Lipatov

route-health/web-api: proxy status dots in sidebar via Telegraf

- route-health.sh: query net_response from InfluxDB for SOCKS5 proxy status, include per-gateway proxy field and global proxy map in health.json - route-web-api.py: add proxy-dot indicators in egw/ogw/routers sidebar sections, updated from health.json proxy data - Telegraf net_response on telegraf CT (682) checks port 1080 on all gateway IPs every 30s Co-Authored-By: 's avatarClaude Opus 4.6 (1M context) <noreply@anthropic.com>
parent 0c55d482
......@@ -42,7 +42,8 @@ add_json_group() {
HEALTH_DATA=$(mktemp)
VPN_DATA=$(mktemp)
IPERF_DATA=$(mktemp)
trap 'rm -f "$HEALTH_DATA" "$VPN_DATA" "$IPERF_DATA"' EXIT
PROXY_DATA=$(mktemp)
trap 'rm -f "$HEALTH_DATA" "$VPN_DATA" "$IPERF_DATA" "$PROXY_DATA"' EXIT
response=$(curl -sG "$INFLUXDB_URL" \
--data-urlencode "db=$INFLUXDB_DB" \
......@@ -59,6 +60,11 @@ iperf_response=$(curl -sG "$INFLUXDB_URL" \
--data-urlencode "q=SELECT last(success), last(recv_bps) FROM iperf3 WHERE time > now() - 10m GROUP BY gateway" \
--max-time 5 2>/dev/null)
proxy_response=$(curl -sG "$INFLUXDB_URL" \
--data-urlencode "db=$INFLUXDB_DB" \
--data-urlencode "q=SELECT last(result_code) FROM net_response WHERE time > now() - 1m GROUP BY gateway" \
--max-time 5 2>/dev/null)
if [ -z "$response" ] ; then
log "ERROR: Failed to query InfluxDB"
exit 1
......@@ -70,6 +76,8 @@ echo "$response" | jq -r '.results[0].series[]? | "\(.tags.gateway) \(.values[0]
echo "$vpn_response" | jq -r '.results[0].series[]? | "\(.tags.gateway) \(.values[0][1])"' > "$VPN_DATA" 2>/dev/null
# iperf3: "gateway success recv_bps"
echo "$iperf_response" | jq -r '.results[0].series[]? | "\(.tags.gateway) \(.values[0][1]) \(.values[0][2])"' > "$IPERF_DATA" 2>/dev/null
# proxy: "gateway result_code" (0=up, 1+=down)
echo "$proxy_response" | jq -r '.results[0].series[]? | "\(.tags.gateway) \(.values[0][1])"' > "$PROXY_DATA" 2>/dev/null
if [ ! -s "$HEALTH_DATA" ] ; then
log "ERROR: No health data from InfluxDB"
......@@ -299,17 +307,14 @@ for routes_dir in "$ROUTES_DIR" "$ROUTES6_DIR" ; do
iperf_s=$(grep "^${tag} " "$IPERF_DATA" 2>/dev/null | awk '{print $2}')
iperf_mark="" ; [ "$iperf_s" = "0" ] && iperf_mark=",iperf=FAIL"
# Check SOCKS5 proxy (port 1080) on gateway
proxy_ok=""
if timeout 2 bash -c "echo | nc -w1 $gw_ip 1080" >/dev/null 2>&1 ; then
proxy_ok=1
fi
proxy_code=$(grep "^${tag} " "$PROXY_DATA" 2>/dev/null | awk '{print $2}')
json_gw="{\"tag\":\"$tag\",\"ip\":\"$gw_ip\",\"status\":\"$st\""
[ -n "$loss" ] && json_gw="$json_gw,\"loss\":$loss"
[ "$vpn" = "0" ] && json_gw="$json_gw,\"vpn_down\":true"
[ "$iperf_s" = "0" ] && json_gw="$json_gw,\"iperf_fail\":true"
[ -n "$proxy_ok" ] && json_gw="$json_gw,\"proxy\":true"
[ "$proxy_code" = "0" ] && json_gw="$json_gw,\"proxy\":true"
[ -n "$proxy_code" ] && [ "$proxy_code" != "0" ] && json_gw="$json_gw,\"proxy\":false"
json_gw="$json_gw}"
json_gws="${json_gws:+$json_gws,}$json_gw"
......@@ -401,10 +406,21 @@ for routes_dir in "$ROUTES_DIR" "$ROUTES6_DIR" ; do
done
done
# Build proxy status map from net_response data
PROXY_JSON=""
while IFS=' ' read -r ptag pcode ; do
[ -z "$ptag" ] && continue
if [ "$pcode" = "0" ] ; then
PROXY_JSON="${PROXY_JSON:+$PROXY_JSON,}\"$ptag\":true"
else
PROXY_JSON="${PROXY_JSON:+$PROXY_JSON,}\"$ptag\":false"
fi
done < "$PROXY_DATA"
# Write health JSON
if [ -n "$JSON_ENTRIES" ] ; then
mkdir -p "$STATE_DIR"
echo "{\"timestamp\":$(date +%s),\"groups\":[$JSON_ENTRIES]}" > "$HEALTH_JSON.tmp"
echo "{\"timestamp\":$(date +%s),\"groups\":[$JSON_ENTRIES],\"proxy\":{$PROXY_JSON}}" > "$HEALTH_JSON.tmp"
mv "$HEALTH_JSON.tmp" "$HEALTH_JSON"
fi
......
......@@ -514,8 +514,13 @@ HTML_PAGE = """\
.sidebar-section h3 { font-size: 0.85em; color: #666; margin-bottom: 8px;
padding-bottom: 6px; border-bottom: 1px solid #eee; }
.sidebar-item { font-family: monospace; font-size: 0.75em; padding: 2px 0;
color: #333; display: flex; justify-content: space-between; }
.sidebar-item span.ip { color: #999; flex-shrink: 0; margin-left: 4px; }
color: #333; display: flex; align-items: center; }
.sidebar-item span.ip { color: #999; flex-shrink: 0; margin-left: auto; }
.proxy-dot { width: 6px; height: 6px; border-radius: 50%; display: inline-block;
margin-right: 5px; flex-shrink: 0; }
.proxy-dot.up { background: #27ae60; }
.proxy-dot.down { background: #e74c3c; }
.proxy-dot.unknown { background: #ccc; }
.sidebar-note { font-size: 0.75em; color: #999; margin-top: 8px; font-style: italic; }
.health-group { margin-bottom: 6px; }
.health-group-name { font-family: monospace; font-size: 0.8em; font-weight: bold; color: #333; }
......@@ -671,36 +676,36 @@ HTML_PAGE = """\
<aside class="sidebar">
<div class="sidebar-section">
<h3>egw (через VDS)</h3>
<div class="sidebar-item" title="AmneziaWG">amneziawg.hetzner <span class="ip">.116</span></div>
<div class="sidebar-item" title="AmneziaWG">amneziawg.sprintbox <span class="ip">.114</span></div>
<div class="sidebar-item" title="Cloak + OpenVPN">cloak.ovpn.hetzner <span class="ip">.132</span></div>
<div class="sidebar-item" title="Cloak + OpenVPN">cloak.ovpn.sprintbox <span class="ip">.113</span></div>
<div class="sidebar-item" title="GRE tunnel">gre.hetzner <span class="ip">.122</span></div>
<div class="sidebar-item" title="GRE tunnel">gre.sprintbox <span class="ip">.123</span></div>
<div class="sidebar-item" title="GRE tunnel">gre.vdska <span class="ip">.127</span></div>
<div class="sidebar-item" title="IKEv2 IPsec">ikev2.hetzner <span class="ip">.120</span></div>
<div class="sidebar-item" title="IKEv2 IPsec">ikev2.sprintbox <span class="ip">.115</span></div>
<div class="sidebar-item" title="IKEv2 IPsec">ikev2.vdska <span class="ip">.131</span></div>
<div class="sidebar-item" title="OpenConnect VPN">openconnect.hetzner <span class="ip">.112</span></div>
<div class="sidebar-item" title="OpenVPN">ovpn.hetzner <span class="ip">.118</span></div>
<div class="sidebar-item" title="OpenVPN">ovpn.sprintbox <span class="ip">.119</span></div>
<div class="sidebar-item" title="OpenVPN">ovpn.vdska <span class="ip">.128</span></div>
<div class="sidebar-item" title="Cloudflare WARP">warp <span class="ip">.134</span></div>
<div class="sidebar-item" title="Xray/VLESS">xray.hetzner <span class="ip">.125</span></div>
<div class="sidebar-item" title="Xray/VLESS">xray.sprintbox <span class="ip">.117</span></div>
<div class="sidebar-item" id="gw-116" title="AmneziaWG"><span class="proxy-dot unknown"></span>amneziawg.hetzner <span class="ip">.116</span></div>
<div class="sidebar-item" id="gw-114" title="AmneziaWG"><span class="proxy-dot unknown"></span>amneziawg.sprintbox <span class="ip">.114</span></div>
<div class="sidebar-item" id="gw-132" title="Cloak + OpenVPN"><span class="proxy-dot unknown"></span>cloak.ovpn.hetzner <span class="ip">.132</span></div>
<div class="sidebar-item" id="gw-113" title="Cloak + OpenVPN"><span class="proxy-dot unknown"></span>cloak.ovpn.sprintbox <span class="ip">.113</span></div>
<div class="sidebar-item" id="gw-122" title="GRE tunnel"><span class="proxy-dot unknown"></span>gre.hetzner <span class="ip">.122</span></div>
<div class="sidebar-item" id="gw-123" title="GRE tunnel"><span class="proxy-dot unknown"></span>gre.sprintbox <span class="ip">.123</span></div>
<div class="sidebar-item" id="gw-127" title="GRE tunnel"><span class="proxy-dot unknown"></span>gre.vdska <span class="ip">.127</span></div>
<div class="sidebar-item" id="gw-120" title="IKEv2 IPsec"><span class="proxy-dot unknown"></span>ikev2.hetzner <span class="ip">.120</span></div>
<div class="sidebar-item" id="gw-115" title="IKEv2 IPsec"><span class="proxy-dot unknown"></span>ikev2.sprintbox <span class="ip">.115</span></div>
<div class="sidebar-item" id="gw-131" title="IKEv2 IPsec"><span class="proxy-dot unknown"></span>ikev2.vdska <span class="ip">.131</span></div>
<div class="sidebar-item" id="gw-112" title="OpenConnect VPN"><span class="proxy-dot unknown"></span>openconnect.hetzner <span class="ip">.112</span></div>
<div class="sidebar-item" id="gw-118" title="OpenVPN"><span class="proxy-dot unknown"></span>ovpn.hetzner <span class="ip">.118</span></div>
<div class="sidebar-item" id="gw-119" title="OpenVPN"><span class="proxy-dot unknown"></span>ovpn.sprintbox <span class="ip">.119</span></div>
<div class="sidebar-item" id="gw-128" title="OpenVPN"><span class="proxy-dot unknown"></span>ovpn.vdska <span class="ip">.128</span></div>
<div class="sidebar-item" id="gw-134" title="Cloudflare WARP"><span class="proxy-dot unknown"></span>warp <span class="ip">.134</span></div>
<div class="sidebar-item" id="gw-125" title="Xray/VLESS"><span class="proxy-dot unknown"></span>xray.hetzner <span class="ip">.125</span></div>
<div class="sidebar-item" id="gw-117" title="Xray/VLESS"><span class="proxy-dot unknown"></span>xray.sprintbox <span class="ip">.117</span></div>
</div>
<div class="sidebar-section">
<h3>ogw (без VDS)</h3>
<div class="sidebar-item" title="ByeDPI bypass">bydpi <span class="ip">.129</span></div>
<div class="sidebar-item" title="GRE tunnel">gre.beget <span class="ip">.124</span></div>
<div class="sidebar-item" title="IKEv2 IPsec">ikev2.beget <span class="ip">.130</span></div>
<div class="sidebar-item" title="nfqws DPI bypass">nfqws <span class="ip">.126</span></div>
<div class="sidebar-item" id="gw-129" title="ByeDPI bypass"><span class="proxy-dot unknown"></span>bydpi <span class="ip">.129</span></div>
<div class="sidebar-item" id="gw-124" title="GRE tunnel"><span class="proxy-dot unknown"></span>gre.beget <span class="ip">.124</span></div>
<div class="sidebar-item" id="gw-130" title="IKEv2 IPsec"><span class="proxy-dot unknown"></span>ikev2.beget <span class="ip">.130</span></div>
<div class="sidebar-item" id="gw-126" title="nfqws DPI bypass"><span class="proxy-dot unknown"></span>nfqws.ogw <span class="ip">.126</span></div>
</div>
<div class="sidebar-section">
<h3>Маршрутизаторы</h3>
<div class="sidebar-item" title="Прямой шлюз">dgw <span class="ip">.12</span></div>
<div class="sidebar-item" title="Шлюз-распределитель">egw <span class="ip">.14</span></div>
<div class="sidebar-item" title="Основной шлюз">igw <span class="ip">.13</span></div>
<div class="sidebar-item" id="gw-12" title="Прямой шлюз"><span class="proxy-dot unknown"></span>dgw <span class="ip">.12</span></div>
<div class="sidebar-item" id="gw-14" title="Шлюз-распределитель"><span class="proxy-dot unknown"></span>egw <span class="ip">.14</span></div>
<div class="sidebar-item" id="gw-13" title="Основной шлюз"><span class="proxy-dot unknown"></span>igw <span class="ip">.13</span></div>
</div>
<div class="sidebar-note">SOCKS5 :1080 на всех</div>
<div class="sidebar-section" id="health-section" style="display:none">
......@@ -1137,6 +1142,19 @@ refresh();
}
container.appendChild(div);
}
// Update proxy dots in sidebar from d.proxy map
if (d.proxy) {
document.querySelectorAll('.sidebar-item .proxy-dot').forEach(dot => {
const item = dot.parentElement;
// Extract gateway name from text content (first word before space)
const text = item.textContent.trim().split(/\s/)[0];
// Try exact match, then check with common suffixes removed
const tag = text in d.proxy ? text : null;
if (tag !== null) {
dot.className = 'proxy-dot ' + (d.proxy[tag] ? 'up' : 'down');
}
});
}
if (d.timestamp) {
const ago = Math.round(Date.now()/1000 - d.timestamp);
container.appendChild(el('div', 'sidebar-note', ago + 's ago'));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment