Allow device routing without pinning
Build and Deploy Gateway / build-and-push (push) Successful in 15s
Build and Deploy Gateway / deploy (push) Successful in 13s

This commit is contained in:
2026-08-07 17:27:13 +03:00
parent 608f8cfcf2
commit dfa9f09695
13 changed files with 114 additions and 61 deletions
+21 -22
View File
@@ -203,7 +203,9 @@ export function DevicesPanel({ open, panelRef, closeRef, onClose }) {
onClick={() => setSortDirection((direction) => direction === 'desc' ? 'asc' : 'desc')}
>
<span>Трафик</span>
<span aria-hidden="true">{sortDirection === 'desc' ? '↓' : '↑'}</span>
<span className="client-devices-sort-icon" aria-hidden="true">
{sortDirection === 'desc' ? '↓' : '↑'}
</span>
</button>
<Tooltip>Сначала {sortDirection === 'desc' ? 'больше' : 'меньше'} трафика</Tooltip>
</span>
@@ -274,16 +276,14 @@ export function DevicesPanel({ open, panelRef, closeRef, onClose }) {
: device.appliedPolicy;
const cannotEnableDirect = device.policyStatus === 'applied'
&& device.appliedPolicy !== 'direct'
&& (!device.pinned || device.confidence === 'ambiguous');
&& device.confidence === 'ambiguous';
const policyTooltip = policyBusy
? `Применяем: ${device.desiredPolicy === 'direct' ? 'полностью напрямую' : 'через правила Gateway'}`
: policyFailed
? `${device.policyError || 'Маршрут не применён'}. Сейчас: ${device.appliedPolicy === 'direct' ? 'напрямую' : 'через Gateway'}. Нажмите, чтобы оставить текущий маршрут`
: policyPending
? 'Gateway должен однозначно распознать устройство. Нажмите, чтобы отменить ожидание'
: !device.pinned
? 'Закрепите устройство, чтобы изменить маршрут'
: device.confidence === 'ambiguous' && device.desiredPolicy !== 'direct'
: device.confidence === 'ambiguous' && device.desiredPolicy !== 'direct'
? 'Маршрут недоступен, пока Gateway видит несколько сетевых адресов одного устройства'
: displayPolicy === 'direct'
? 'Полностью обходит sing-box. Нажмите, чтобы вернуть обработку Gateway'
@@ -296,6 +296,22 @@ export function DevicesPanel({ open, panelRef, closeRef, onClose }) {
className={`client-device is-${device.status}`}
key={device.id}
>
<span className="client-device-pin-wrap client-tooltip-anchor">
<button
className="client-device-pin"
type="button"
aria-pressed={device.pinned}
aria-label={device.pinned ? `Открепить ${title}` : `Закрепить ${title}`}
disabled={saving}
onClick={() => updateDevice(device, { pinned: !device.pinned })}
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M9 3h6l-1 5 3 3v2H7v-2l3-3-1-5ZM12 13v8" />
</svg>
</button>
<Tooltip>{device.pinned ? 'Открепить' : 'Закрепить'}</Tooltip>
</span>
<div className="client-device-heading">
{editing ? (
<form className="client-device-alias" onSubmit={(event) => saveAlias(event, device)}>
@@ -343,23 +359,6 @@ export function DevicesPanel({ open, panelRef, closeRef, onClose }) {
<Tooltip>{trafficLabel}</Tooltip>
</span>}
</span>
<span className="client-device-pin-wrap client-tooltip-anchor">
<button
className="client-device-pin"
type="button"
aria-pressed={device.pinned}
aria-label={device.pinned ? `Открепить ${title}` : `Закрепить ${title}`}
disabled={saving || device.desiredPolicy === 'direct' || device.appliedPolicy === 'direct'}
onClick={() => updateDevice(device, { pinned: !device.pinned })}
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M9 3h6l-1 5 3 3v2H7v-2l3-3-1-5ZM12 13v8" />
</svg>
</button>
<Tooltip>{device.desiredPolicy === 'direct' || device.appliedPolicy === 'direct'
? 'Сначала верните маршрут через Gateway'
: device.pinned ? 'Открепить' : 'Закрепить'}</Tooltip>
</span>
</div>
<div className="client-device-meta">