steer: вкладка на три туннеля — у каждого своя карточка и своя проверка, разведка отбрасывает точки без HTTPS
Страница steer была рассчитана на один туннель: одна точка входа, один сервер, одна «последняя связь», а «Проверить» смотрело только первый туннель. Теперь: - steer_status отдаёт tunnels[] — у каждого туннеля точка, колония из последней разведки, свежесть рукопожатия и трафик; карточка «Туннели WARP» показывает их по одному, в шапке — «работает N из 3» и колонии живых. Один туннель — прежний вид. - «Проверить» проверяет каждый туннель: HTTPS-трассировка с warp=on — ок; не прошла, но CF-RAY по HTTP отвечает — «соединение есть, но HTTPS через туннель не проходит» (другая поломка и другое действие); иначе — трафик не идёт. Упал один из трёх при живых остальных — замечание, а не поломка: steer переключится сам. - Кнопки названы во множественном числе: «Перезапустить туннели», «Сменить точки входа». - Разведка требует HTTPS через туннель: на части сетей через туннель проходят только мелкие пакеты и порт 80, и точка, годная по рукопожатию, потерям и CF-RAY, сайтов на устройствах не открывала. Такие точки — в последний запас.
This commit is contained in:
1 parent
507060762c
commit
042f272838
1 file changed
+104
-16
+104
-16
@@ -5205,7 +5205,7 @@ _st_warp_ports() { # ИНТЕРФЕЙС КЛЮЧ_УЗЛА АДРЕС
|
|||||||
_st_warp_scan() { # ИНТЕРФЕЙС КЛЮЧ_УЗЛА ЗАНЯТЫЕ_КОЛОНИИ ЗАНЯТЫЕ_АДРЕСА
|
_st_warp_scan() { # ИНТЕРФЕЙС КЛЮЧ_УЗЛА ЗАНЯТЫЕ_КОЛОНИИ ЗАНЯТЫЕ_АДРЕСА
|
||||||
local dev="$1" peer="$2" busy=" $3 " busyip=" $4 " r="$ST_RUN/scan.$1" cand ip ports port loss rtt colo pick f
|
local dev="$1" peer="$2" busy=" $3 " busyip=" $4 " r="$ST_RUN/scan.$1" cand ip ports port loss rtt colo pick f
|
||||||
mkdir -p "$ST_RUN"
|
mkdir -p "$ST_RUN"
|
||||||
: > "$r"; : > "$r.same"; : > "$r.nc"; : > "$r.ru"
|
: > "$r"; : > "$r.same"; : > "$r.nc"; : > "$r.ru"; : > "$r.notls"
|
||||||
# Якоря .1 первыми: на них указывает сам engage.cloudflareclient.com; случайные — за
|
# Якоря .1 первыми: на них указывает сам engage.cloudflareclient.com; случайные — за
|
||||||
# разными колониями.
|
# разными колониями.
|
||||||
cand=$(awk -v p="$ST_WARP_POOLS" -v n="$ST_WARP_RAND" 'BEGIN { srand(); c = split(p, a, " ");
|
cand=$(awk -v p="$ST_WARP_POOLS" -v n="$ST_WARP_RAND" 'BEGIN { srand(); c = split(p, a, " ");
|
||||||
@@ -5225,6 +5225,13 @@ _st_warp_scan() { # ИНТЕРФЕЙС КЛЮЧ_УЗЛА ЗАНЯТЫЕ_КОЛО
|
|||||||
if [ -z "$colo" ]; then
|
if [ -z "$colo" ]; then
|
||||||
echo "$loss $rtt $ip $port ?" >> "$r.nc"; echo " $ip:$port — потери $loss%, $rtt мс, колония не узналась" >&2; continue
|
echo "$loss $rtt $ip $port ?" >> "$r.nc"; echo " $ip:$port — потери $loss%, $rtt мс, колония не узналась" >&2; continue
|
||||||
fi
|
fi
|
||||||
|
# HTTPS через туннель — обязательно. CF-RAY приходит по голому HTTP, а на части сетей
|
||||||
|
# через туннель проходят только мелкие пакеты и порт 80, TLS молчит (brb снял это на
|
||||||
|
# живом роутере). Такая точка «работает» по всем замерам, а устройства через неё сайты
|
||||||
|
# не открывают, — поэтому она уходит в последний запас.
|
||||||
|
if ! curl -s -o /dev/null --interface "$dev" --connect-timeout 4 --max-time 8 https://www.cloudflare.com/cdn-cgi/trace 2>/dev/null; then
|
||||||
|
echo "$loss $rtt $ip $port $colo" >> "$r.notls"; echo " $ip:$port — колония $colo, но HTTPS через туннель не проходит" >&2; continue
|
||||||
|
fi
|
||||||
echo " $ip:$port — потери $loss%, $rtt мс, колония $colo" >&2
|
echo " $ip:$port — потери $loss%, $rtt мс, колония $colo" >&2
|
||||||
if _st_warp_is_ru "$colo"; then echo "$loss $rtt $ip $port $colo" >> "$r.ru"; continue; fi
|
if _st_warp_is_ru "$colo"; then echo "$loss $rtt $ip $port $colo" >> "$r.ru"; continue; fi
|
||||||
case "$busy" in *" $colo "*) echo "$loss $rtt $ip $port $colo" >> "$r.same"; continue ;; esac
|
case "$busy" in *" $colo "*) echo "$loss $rtt $ip $port $colo" >> "$r.same"; continue ;; esac
|
||||||
@@ -5233,12 +5240,13 @@ _st_warp_scan() { # ИНТЕРФЕЙС КЛЮЧ_УЗЛА ЗАНЯТЫЕ_КОЛО
|
|||||||
done
|
done
|
||||||
# Лучшая — одним числовым ключом: потери в старших разрядах, круг в младших (busybox sort
|
# Лучшая — одним числовым ключом: потери в старших разрядах, круг в младших (busybox sort
|
||||||
# не понимает модификаторов у ключей, см. brb/warp.sh).
|
# не понимает модификаторов у ключей, см. brb/warp.sh).
|
||||||
for f in "$r" "$r.same" "$r.nc" "$r.ru"; do
|
for f in "$r" "$r.same" "$r.nc" "$r.ru" "$r.notls"; do
|
||||||
[ -s "$f" ] || continue
|
[ -s "$f" ] || continue
|
||||||
pick=$(awk '{ printf "%d\t%s %s %s\n", $1 * 100000 + $2, $3, $4, $5 }' "$f" | sort -n | head -n1 | cut -f2)
|
pick=$(awk '{ printf "%d\t%s %s %s\n", $1 * 100000 + $2, $3, $4, $5 }' "$f" | sort -n | head -n1 | cut -f2)
|
||||||
case "$f" in
|
case "$f" in
|
||||||
*.same) echo " другой колонии нет — та же, но через другой адрес" >&2 ;;
|
*.same) echo " другой колонии нет — та же, но через другой адрес" >&2 ;;
|
||||||
*.ru) echo " наружных колоний нет — беру российскую (геоблок она не снимает)" >&2 ;;
|
*.ru) echo " наружных колоний нет — беру российскую (геоблок она не снимает)" >&2 ;;
|
||||||
|
*.notls) echo "!! ни через одну точку не проходит HTTPS — беру лучшую из оставшихся" >&2 ;;
|
||||||
esac
|
esac
|
||||||
echo "$pick"
|
echo "$pick"
|
||||||
return 0
|
return 0
|
||||||
@@ -5840,10 +5848,36 @@ steer_status() {
|
|||||||
svc="$svc$sep{\"id\":\"$id\",\"name\":\"$(esc "$(_rb_svc_field "$id" 2)")\",\"on\":$w,\"skip\":$k,\"auto\":\"$r\"}"
|
svc="$svc$sep{\"id\":\"$id\",\"name\":\"$(esc "$(_rb_svc_field "$id" 2)")\",\"on\":$w,\"skip\":$k,\"auto\":\"$r\"}"
|
||||||
sep=","
|
sep=","
|
||||||
done
|
done
|
||||||
printf '{"running":%s,"phase":"%s","blocker":"%s","installed":%s,"stopped":%s,"version":"%s","steer_running":%s,"channels":%s,"warp_up":%s,"warp_colo":"%s","warp_host":"%s","warp_port":"%s","warp_hs_age":"%s","warp_rx":%s,"warp_tx":%s,"autorestart":"%s","dns_conflict":%s,"redbtn_running":%s,"services":[%s]}\n' \
|
printf '{"running":%s,"phase":"%s","blocker":"%s","installed":%s,"stopped":%s,"version":"%s","steer_running":%s,"channels":%s,"warp_up":%s,"warp_colo":"%s","warp_host":"%s","warp_port":"%s","warp_hs_age":"%s","warp_rx":%s,"warp_tx":%s,"autorestart":"%s","dns_conflict":%s,"redbtn_running":%s,"tunnels":%s,"services":[%s]}\n' \
|
||||||
"$running" "$(esc "$phase")" "$blk" "$installed" "$off" "$(esc "$ver")" "$run" "${chans:-0}" "$warp_up" "$(esc "$colo")" \
|
"$running" "$(esc "$phase")" "$blk" "$installed" "$off" "$(esc "$ver")" "$run" "${chans:-0}" "$warp_up" "$(esc "$colo")" \
|
||||||
"$(esc "$host")" "$(esc "$port")" "$age" "${rx:-0}" "${tx:-0}" "$(_st_cron_get)" "$dns" \
|
"$(esc "$host")" "$(esc "$port")" "$age" "${rx:-0}" "${tx:-0}" "$(_st_cron_get)" "$dns" \
|
||||||
"$(_job_alive redbtn && echo true || echo false)" "$svc"
|
"$(_job_alive redbtn && echo true || echo false)" "$(_st_tunnels_json)" "$svc"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Туннели по одному — JSON-массив для страницы steer: у каждого своя точка, колония (из
|
||||||
|
# последней разведки), связь и трафик. Только те, что заведены.
|
||||||
|
_st_tunnels_json() {
|
||||||
|
local n=1 i up hs age rx tx host port colo out="" sep=""
|
||||||
|
while [ "$n" -le "$ST_WARP_N" ]; do
|
||||||
|
i="$(_st_wif "$n")"
|
||||||
|
if _st_owns "net $i"; then
|
||||||
|
up=false; age=""; rx=0; tx=0
|
||||||
|
[ -d "/sys/class/net/$i" ] && up=true
|
||||||
|
host="$(uci -q get "network.${i}_peer.endpoint_host")"
|
||||||
|
port="$(uci -q get "network.${i}_peer.endpoint_port")"
|
||||||
|
colo="$(awk -v i="$i" '$1 == i {print $2; exit}' "$ST_WARP_UP" 2>/dev/null)"
|
||||||
|
if [ "$up" = true ] && command -v awg >/dev/null 2>&1; then
|
||||||
|
hs=$(awg show "$i" latest-handshakes 2>/dev/null | awk '{print $2; exit}')
|
||||||
|
[ "${hs:-0}" -gt 0 ] 2>/dev/null && age=$(( $(date +%s) - hs ))
|
||||||
|
set -- $(awg show "$i" transfer 2>/dev/null | head -n1)
|
||||||
|
rx="${2:-0}"; tx="${3:-0}"
|
||||||
|
fi
|
||||||
|
out="$out$sep{\"n\":$n,\"if\":\"$i\",\"up\":$up,\"colo\":\"$(esc "$colo")\",\"host\":\"$(esc "$host")\",\"port\":\"$(esc "$port")\",\"hs_age\":\"$age\",\"rx\":$rx,\"tx\":$tx}"
|
||||||
|
sep=","
|
||||||
|
fi
|
||||||
|
n=$((n + 1))
|
||||||
|
done
|
||||||
|
printf '[%s]' "$out"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Выбор сервисов с страницы: итоговый набор через запятую.
|
# Выбор сервисов с страницы: итоговый набор через запятую.
|
||||||
@@ -5899,18 +5933,35 @@ steer_action() {
|
|||||||
;;
|
;;
|
||||||
autorestart) _st_cron_set "$mode" ;;
|
autorestart) _st_cron_set "$mode" ;;
|
||||||
diag)
|
diag)
|
||||||
local trace warp="none" colo=""
|
# Каждый туннель — отдельно: «идёт ли трафик» у одного ничего не говорит о других.
|
||||||
|
# Сначала HTTPS-трассировка (она же подтверждает warp=on); не прошла — голый HTTP с
|
||||||
|
# CF-RAY: если он отвечает, данные идут, но TLS через туннель режется — это другая
|
||||||
|
# поломка и другое действие.
|
||||||
|
local trace warp="none" colo="" tun="" tsep="" wi wn wv wc
|
||||||
if _st_installed && [ -n "$(_st_sel)" ] && [ ! -f "$ST_OFF" ]; then
|
if _st_installed && [ -n "$(_st_sel)" ] && [ ! -f "$ST_OFF" ]; then
|
||||||
trace="$(curl -s --interface "$(_st_warp_first)" --connect-timeout 4 --max-time 8 https://www.cloudflare.com/cdn-cgi/trace 2>/dev/null)"
|
warp=off
|
||||||
case "$trace" in *warp=on*|*warp=plus*) warp=on ;; *) warp=off ;; esac
|
wn=1
|
||||||
colo="$(echo "$trace" | sed -n 's/^colo=//p')"
|
while [ "$wn" -le "$ST_WARP_N" ]; do
|
||||||
|
wi="$(_st_wif "$wn")"
|
||||||
|
if _st_owns "net $wi" && [ -d "/sys/class/net/$wi" ]; then
|
||||||
|
trace="$(curl -s --interface "$wi" --connect-timeout 4 --max-time 8 https://www.cloudflare.com/cdn-cgi/trace 2>/dev/null)"
|
||||||
|
wc="$(echo "$trace" | sed -n 's/^colo=//p')"
|
||||||
|
case "$trace" in
|
||||||
|
*warp=on*|*warp=plus*) wv=on; warp=on; [ -n "$colo" ] || colo="$wc" ;;
|
||||||
|
*) wc="$(_st_colo_of "$wi")"; [ -n "$wc" ] && wv=notls || wv=off ;;
|
||||||
|
esac
|
||||||
|
tun="$tun$tsep{\"n\":$wn,\"warp\":\"$wv\",\"colo\":\"$(esc "$wc")\"}"
|
||||||
|
tsep=","
|
||||||
|
fi
|
||||||
|
wn=$((wn + 1))
|
||||||
|
done
|
||||||
fi
|
fi
|
||||||
local d=""
|
local d=""
|
||||||
if command -v steer >/dev/null 2>&1 && [ -s "$ST_STEER_SPEC" ] && _st_owns "steer-spec"; then
|
if command -v steer >/dev/null 2>&1 && [ -s "$ST_STEER_SPEC" ] && _st_owns "steer-spec"; then
|
||||||
d="$(steer diag --spec "$ST_STEER_SPEC" 2>/dev/null | tr '\n' ' ')"
|
d="$(steer diag --spec "$ST_STEER_SPEC" 2>/dev/null | tr '\n' ' ')"
|
||||||
case "$d" in '{'*'}'*) ;; *) d="" ;; esac
|
case "$d" in '{'*'}'*) ;; *) d="" ;; esac
|
||||||
fi
|
fi
|
||||||
printf '{"warp":"%s","colo":"%s","diag":%s}\n' "$warp" "$(esc "$colo")" "${d:-null}"
|
printf '{"warp":"%s","colo":"%s","tunnels":[%s],"diag":%s}\n' "$warp" "$(esc "$colo")" "$tun" "${d:-null}"
|
||||||
;;
|
;;
|
||||||
dns_fix)
|
dns_fix)
|
||||||
do_steer_dns_fix
|
do_steer_dns_fix
|
||||||
@@ -8457,6 +8508,12 @@ function fmtAge(sec) {
|
|||||||
return Math.floor(sec / 3600) + ' ч назад';
|
return Math.floor(sec / 3600) + ' ч назад';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Туннель жив: поднят и рукопожатие было за последние пять минут.
|
||||||
|
function tunnelLive(t) {
|
||||||
|
var a = parseInt(t.hs_age, 10);
|
||||||
|
return t.up && !isNaN(a) && a < 300;
|
||||||
|
}
|
||||||
|
|
||||||
function hh(h) { return (h < 10 ? '0' : '') + h + ':00'; }
|
function hh(h) { return (h < 10 ? '0' : '') + h + ':00'; }
|
||||||
|
|
||||||
return view.extend({
|
return view.extend({
|
||||||
@@ -8572,6 +8629,8 @@ return view.extend({
|
|||||||
function tunnelState() {
|
function tunnelState() {
|
||||||
var age = parseInt(data.warp_hs_age, 10), n = parseInt(data.channels, 10) || 0;
|
var age = parseInt(data.warp_hs_age, 10), n = parseInt(data.channels, 10) || 0;
|
||||||
if (!data.warp_up) return (data.stopped || !n) ? [ 'выключен', 'zm-st-off' ] : [ 'не поднят', 'zm-st-bad' ];
|
if (!data.warp_up) return (data.stopped || !n) ? [ 'выключен', 'zm-st-off' ] : [ 'не поднят', 'zm-st-bad' ];
|
||||||
|
var t = data.tunnels || [], live = t.filter(tunnelLive).length;
|
||||||
|
if (t.length > 1 && live) return [ 'работает ' + live + ' из ' + t.length + (data.warp_colo ? ' · ' + data.warp_colo : ''), live < t.length ? 'zm-st-warn' : 'zm-st-ok' ];
|
||||||
if (!isNaN(age) && age < 300) return [ 'работает' + (data.warp_colo ? ' · ' + data.warp_colo : ''), 'zm-st-ok' ];
|
if (!isNaN(age) && age < 300) return [ 'работает' + (data.warp_colo ? ' · ' + data.warp_colo : ''), 'zm-st-ok' ];
|
||||||
return [ 'нет связи', 'zm-st-warn' ];
|
return [ 'нет связи', 'zm-st-warn' ];
|
||||||
}
|
}
|
||||||
@@ -8726,8 +8785,15 @@ return view.extend({
|
|||||||
checkCard.appendChild(E('p', { 'class': 'zm-hint' }, 'Роутер проверит туннель и спросит сам steer, всё ли на месте.'));
|
checkCard.appendChild(E('p', { 'class': 'zm-hint' }, 'Роутер проверит туннель и спросит сам steer, всё ли на месте.'));
|
||||||
if (diagRes) {
|
if (diagRes) {
|
||||||
var items = [];
|
var items = [];
|
||||||
if (diagRes.warp === 'on') items.push([ 'ok', 'Трафик идёт через WARP' + (diagRes.colo ? ' (сервер ' + diagRes.colo + ')' : ''), '' ]);
|
var tn = diagRes.tunnels || [];
|
||||||
else if (diagRes.warp === 'off') items.push([ 'fail', 'Трафик через WARP не идёт', 'Нажмите «Перезапустить туннель» или «Сменить точку входа» ниже' ]);
|
if (tn.length) tn.forEach(function(t) {
|
||||||
|
var who = tn.length > 1 ? 'Туннель ' + t.n + ': ' : '';
|
||||||
|
if (t.warp === 'on') items.push([ 'ok', who + 'трафик идёт через WARP' + (t.colo ? ' (сервер ' + t.colo + ')' : ''), '' ]);
|
||||||
|
else if (t.warp === 'notls') items.push([ 'warn', who + 'соединение есть, но HTTPS через туннель не проходит', 'Нажмите «Сменить точки входа» ниже' ]);
|
||||||
|
else items.push([ tn.length > 1 && diagRes.warp === 'on' ? 'warn' : 'fail', who + 'трафик через WARP не идёт', 'Нажмите «Перезапустить туннели» или «Сменить точки входа» ниже' ]);
|
||||||
|
});
|
||||||
|
else if (diagRes.warp === 'on') items.push([ 'ok', 'Трафик идёт через WARP' + (diagRes.colo ? ' (сервер ' + diagRes.colo + ')' : ''), '' ]);
|
||||||
|
else if (diagRes.warp === 'off') items.push([ 'fail', 'Трафик через WARP не идёт', 'Нажмите «Перезапустить туннели» или «Сменить точки входа» ниже' ]);
|
||||||
var d = diagRes.diag;
|
var d = diagRes.diag;
|
||||||
if (d && d.checks) d.checks.forEach(function(c) {
|
if (d && d.checks) d.checks.forEach(function(c) {
|
||||||
if (c.verdict === 'ok' || c.verdict === 'warn' || c.verdict === 'fail') items.push([ c.verdict, c.what, c.why ]);
|
if (c.verdict === 'ok' || c.verdict === 'warn' || c.verdict === 'fail') items.push([ c.verdict, c.what, c.why ]);
|
||||||
@@ -8754,7 +8820,24 @@ return view.extend({
|
|||||||
warpCard.innerHTML = '';
|
warpCard.innerHTML = '';
|
||||||
warpCard.style.display = data.installed ? '' : 'none';
|
warpCard.style.display = data.installed ? '' : 'none';
|
||||||
if (!data.installed) return;
|
if (!data.installed) return;
|
||||||
warpCard.appendChild(E('h3', {}, 'Туннель WARP'));
|
var tl = data.tunnels || [];
|
||||||
|
warpCard.appendChild(E('h3', {}, tl.length > 1 ? 'Туннели WARP' : 'Туннель WARP'));
|
||||||
|
if (tl.length > 1) {
|
||||||
|
warpCard.appendChild(E('p', { 'class': 'zm-hint' },
|
||||||
|
'Трафик идёт через самый быстрый живой туннель; упал один — steer сам переключится на другой.'));
|
||||||
|
warpCard.appendChild(E('div', { 'class': 'zm-st-tunnels' }, tl.map(function(t) {
|
||||||
|
var live = tunnelLive(t);
|
||||||
|
return E('div', { 'class': 'zm-st-tunnel' }, [
|
||||||
|
E('div', { 'class': 'zm-st-tunnel-head' }, [
|
||||||
|
E('span', { 'class': 'zm-st-tunnel-name' }, 'WARP ' + t.n + (t.colo ? ' · ' + t.colo : '')),
|
||||||
|
plainBadge(live ? 'zm-ok' : t.up ? 'zm-warn' : 'zm-bad', live ? 'работает' : t.up ? 'нет связи' : 'не поднят')
|
||||||
|
]),
|
||||||
|
row('Точка входа', E('span', {}, t.host ? t.host + ':' + t.port : '—')),
|
||||||
|
row('Последняя связь', E('span', {}, t.up ? fmtAge(t.hs_age) : '—')),
|
||||||
|
row('Получено / отправлено', E('span', {}, zm.fmtSize(+t.rx || 0) + ' / ' + zm.fmtSize(+t.tx || 0)))
|
||||||
|
]);
|
||||||
|
})));
|
||||||
|
} else {
|
||||||
var ts = tunnelState();
|
var ts = tunnelState();
|
||||||
var st = plainBadge(ts[1] === 'zm-st-ok' ? 'zm-ok' : ts[1] === 'zm-st-warn' ? 'zm-warn' : ts[1] === 'zm-st-bad' ? 'zm-bad' : 'zm-off', ts[0].split(' · ')[0]);
|
var st = plainBadge(ts[1] === 'zm-st-ok' ? 'zm-ok' : ts[1] === 'zm-st-warn' ? 'zm-warn' : ts[1] === 'zm-st-bad' ? 'zm-bad' : 'zm-off', ts[0].split(' · ')[0]);
|
||||||
warpCard.appendChild(E('div', { 'class': 'bt-cols' }, [
|
warpCard.appendChild(E('div', { 'class': 'bt-cols' }, [
|
||||||
@@ -8769,15 +8852,16 @@ return view.extend({
|
|||||||
row('Отправлено', E('span', {}, zm.fmtSize(+data.warp_tx || 0)))
|
row('Отправлено', E('span', {}, zm.fmtSize(+data.warp_tx || 0)))
|
||||||
])
|
])
|
||||||
]));
|
]));
|
||||||
|
}
|
||||||
warpCard.appendChild(E('div', { 'class': 'zm-actions' }, [
|
warpCard.appendChild(E('div', { 'class': 'zm-actions' }, [
|
||||||
E('button', { 'class': 'cbi-button', 'click': function() { act('warp_restart', '', 'Перезапускаем туннель'); } }, 'Перезапустить туннель'),
|
E('button', { 'class': 'cbi-button', 'click': function() { act('warp_restart', '', 'Перезапускаем туннели'); } }, 'Перезапустить туннели'),
|
||||||
E('button', { 'class': 'cbi-button', 'click': function() { act('warp_endpoint', '', 'Ищем быструю точку входа'); } }, 'Сменить точку входа'),
|
E('button', { 'class': 'cbi-button', 'click': function() { act('warp_endpoint', '', 'Ищем точки входа'); } }, 'Сменить точки входа'),
|
||||||
E('button', { 'class': 'cbi-button', 'click': function() {
|
E('button', { 'class': 'cbi-button', 'click': function() {
|
||||||
if (!confirm('Пересоздать WARP?\n\nБудут получены новые ключи, туннель переподключится. Помогает, если Cloudflare перестал пускать старые ключи.')) return;
|
if (!confirm('Пересоздать WARP?\n\nБудут получены новые ключи, туннели переподключатся. Помогает, если Cloudflare перестал пускать старые ключи.')) return;
|
||||||
act('warp_recreate', '', 'Пересоздаём WARP');
|
act('warp_recreate', '', 'Пересоздаём WARP');
|
||||||
} }, 'Новые ключи')
|
} }, 'Новые ключи')
|
||||||
]));
|
]));
|
||||||
warpCard.appendChild(E('p', { 'class': 'zm-hint' }, 'Не работает — «Перезапустить туннель». Медленно — «Сменить точку входа». Совсем не помогает — «Новые ключи».'));
|
warpCard.appendChild(E('p', { 'class': 'zm-hint' }, 'Не работает — «Перезапустить туннели». Медленно — «Сменить точки входа». Совсем не помогает — «Новые ключи».'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── автоперезапуск ──
|
// ── автоперезапуск ──
|
||||||
@@ -12259,6 +12343,10 @@ html.zm-theme-dark .zm-svc { background: #22272e; border-color: rgba(255,255,255
|
|||||||
.zm-ab-step-label { font-size: 11.5px; }
|
.zm-ab-step-label { font-size: 11.5px; }
|
||||||
.zm-ab-svc { grid-template-columns: 1fr; }
|
.zm-ab-svc { grid-template-columns: 1fr; }
|
||||||
}
|
}
|
||||||
|
.zm-st-tunnels { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; margin: 10px 0 14px; }
|
||||||
|
.zm-st-tunnel { border: 1px solid rgba(127,127,127,.25); border-radius: 12px; padding: 10px 14px; }
|
||||||
|
.zm-st-tunnel-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; margin-bottom: 6px; }
|
||||||
|
.zm-st-tunnel-name { font-weight: 600; }
|
||||||
ZM_INSTALLER_EOF
|
ZM_INSTALLER_EOF
|
||||||
chmod 0644 '/www/luci-static/resources/view/zapret-manager/style.css'
|
chmod 0644 '/www/luci-static/resources/view/zapret-manager/style.css'
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user