Refine routing defaults for global and device fallbacks
All checks were successful
Build and Deploy Gateway / build-and-deploy (push) Successful in 17s
All checks were successful
Build and Deploy Gateway / build-and-deploy (push) Successful in 17s
This commit is contained in:
@@ -18,22 +18,69 @@ const OUTBOUND_KIND = {
|
||||
block: { kind: 'danger', label: 'block' },
|
||||
};
|
||||
|
||||
function DevicesCard({ devices, onAdd, onUpdate, onRemove }) {
|
||||
const DEVICE_MODES = {
|
||||
direct: { kind: 'success', label: 'direct', hint: 'fallback после global rules' },
|
||||
vpn: { kind: 'info', label: 'VPN', hint: 'fallback после global rules' },
|
||||
rules: { kind: 'neutral', label: 'default', hint: 'использует transparent default' },
|
||||
block: { kind: 'danger', label: 'block', hint: 'fallback после global rules' },
|
||||
};
|
||||
|
||||
function DeviceModeSelect({ value, onChange }) {
|
||||
return (
|
||||
<select className="select sm" value={value || 'rules'} onChange={(e) => onChange(e.target.value)}>
|
||||
<option value="direct">direct</option>
|
||||
<option value="vpn">VPN</option>
|
||||
<option value="rules">default</option>
|
||||
<option value="block">block</option>
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
||||
function DevicesCard({ devicesConfig, onDefaultsChange, onAdd, onUpdate, onRemove }) {
|
||||
const devices = devicesConfig?.devices || [];
|
||||
const defaultTransparentMode = devicesConfig?.defaultTransparentMode || devicesConfig?.defaultMode || 'direct';
|
||||
const proxyDefaultMode = devicesConfig?.proxyDefaultMode || 'vpn';
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<h2>Маршрутизация по устройствам</h2>
|
||||
<button className="btn btn-primary sm" onClick={onAdd}>
|
||||
+ Добавить устройство
|
||||
</button>
|
||||
<div>
|
||||
<h2>Устройства</h2>
|
||||
<small className="muted">Global rules применяются первыми. Эти значения — fallback после них.</small>
|
||||
</div>
|
||||
<div className="btn-group">
|
||||
<label className="field" style={{ minWidth: 180, margin: 0 }}>
|
||||
<span className="field-label">Transparent default</span>
|
||||
<select
|
||||
className="select sm"
|
||||
value={defaultTransparentMode}
|
||||
onChange={(e) => onDefaultsChange({ defaultTransparentMode: e.target.value })}
|
||||
>
|
||||
<option value="direct">direct</option>
|
||||
<option value="vpn">VPN</option>
|
||||
<option value="block">block</option>
|
||||
</select>
|
||||
</label>
|
||||
<label className="field" style={{ minWidth: 160, margin: 0 }}>
|
||||
<span className="field-label">Proxy default</span>
|
||||
<select
|
||||
className="select sm"
|
||||
value={proxyDefaultMode}
|
||||
onChange={(e) => onDefaultsChange({ proxyDefaultMode: e.target.value })}
|
||||
>
|
||||
<option value="vpn">VPN</option>
|
||||
<option value="direct">direct</option>
|
||||
<option value="block">block</option>
|
||||
</select>
|
||||
</label>
|
||||
<button className="btn btn-primary sm" onClick={onAdd}>
|
||||
+ Добавить устройство
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<small className="muted" style={{ display: 'block', marginBottom: 8 }}>
|
||||
Правила по source IP — выполняются <strong>до</strong> правил маршрутизации.
|
||||
Укажи IP устройства в сети и куда направлять весь его трафик.
|
||||
</small>
|
||||
{devices.length === 0 ? (
|
||||
<div className="empty-state" style={{ padding: '16px 0' }}>
|
||||
<p style={{ margin: 0 }}>Нет правил по устройствам — все используют общую маршрутизацию.</p>
|
||||
<p style={{ margin: 0 }}>Нет профилей устройств. Неизвестные transparent-устройства используют transparent default.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ overflowX: 'auto' }}>
|
||||
@@ -42,14 +89,16 @@ function DevicesCard({ devices, onAdd, onUpdate, onRemove }) {
|
||||
<tr>
|
||||
<th style={{ width: 40 }}></th>
|
||||
<th>Название</th>
|
||||
<th>IP-адрес(а) устройства</th>
|
||||
<th style={{ width: 130 }}>Маршрут</th>
|
||||
<th style={{ width: 170 }}>IP</th>
|
||||
<th style={{ width: 150 }}>MAC</th>
|
||||
<th style={{ width: 150 }}>Mode</th>
|
||||
<th>Поведение</th>
|
||||
<th style={{ width: 40 }}></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{devices.map((dev) => {
|
||||
const ob = OUTBOUND_KIND[dev.outbound] || OUTBOUND_KIND.direct;
|
||||
const mode = DEVICE_MODES[dev.mode] || DEVICE_MODES.rules;
|
||||
return (
|
||||
<tr key={dev.id} className={dev.enabled !== false ? '' : 'disabled'}>
|
||||
<td>
|
||||
@@ -72,29 +121,27 @@ function DevicesCard({ devices, onAdd, onUpdate, onRemove }) {
|
||||
<td>
|
||||
<input
|
||||
className="input sm"
|
||||
value={(dev.sourceIps || []).join(', ')}
|
||||
onChange={(e) =>
|
||||
onUpdate(dev.id, {
|
||||
sourceIps: e.target.value
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean),
|
||||
})
|
||||
}
|
||||
placeholder="192.168.1.100"
|
||||
style={{ width: '100%', minWidth: 160 }}
|
||||
value={dev.ip || ''}
|
||||
onChange={(e) => onUpdate(dev.id, { ip: e.target.value })}
|
||||
placeholder="192.168.1.50"
|
||||
style={{ width: '100%', minWidth: 140 }}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<select
|
||||
className="select sm"
|
||||
value={dev.outbound || 'direct'}
|
||||
onChange={(e) => onUpdate(dev.id, { outbound: e.target.value })}
|
||||
>
|
||||
<option value="direct">direct</option>
|
||||
<option value="vpn">VPN</option>
|
||||
<option value="block">block</option>
|
||||
</select>
|
||||
<input
|
||||
className="input sm"
|
||||
value={dev.mac || ''}
|
||||
onChange={(e) => onUpdate(dev.id, { mac: e.target.value })}
|
||||
placeholder="опционально"
|
||||
style={{ width: '100%', minWidth: 120 }}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<DeviceModeSelect value={dev.mode} onChange={(mode) => onUpdate(dev.id, { mode })} />
|
||||
</td>
|
||||
<td>
|
||||
<span className={`badge ${mode.kind}`}>{mode.label}</span>
|
||||
<small className="muted" style={{ marginLeft: 8 }}>{mode.hint}</small>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
@@ -195,7 +242,7 @@ function TemplatesModal({ open, onClose, onAdd }) {
|
||||
export function RoutingPage({
|
||||
rules, saveStatus, busy,
|
||||
onAdd, onAddTemplate, onUpdate, onRemove, onSaveNow, onReorder,
|
||||
deviceRules = [], onAddDevice, onUpdateDevice, onRemoveDevice,
|
||||
devicesConfig, onUpdateDeviceDefaults, onAddDevice, onUpdateDevice, onRemoveDevice,
|
||||
}) {
|
||||
const [editingId, setEditingId] = useState(null);
|
||||
const [showTemplates, setShowTemplates] = useState(false);
|
||||
@@ -240,7 +287,8 @@ export function RoutingPage({
|
||||
<RouteChecker />
|
||||
|
||||
<DevicesCard
|
||||
devices={deviceRules}
|
||||
devicesConfig={devicesConfig}
|
||||
onDefaultsChange={onUpdateDeviceDefaults}
|
||||
onAdd={onAddDevice}
|
||||
onUpdate={onUpdateDevice}
|
||||
onRemove={onRemoveDevice}
|
||||
|
||||
Reference in New Issue
Block a user