feat: добавлены компоненты для управления конфигурацией и логами
Добавлены новые компоненты для отображения и управления конфигурацией, логами и правилами маршрутизации. Реализована логика для работы с API, включая запросы на получение и сохранение данных. Также добавлены шаблоны правил и утилиты для валидации. Refs: None
This commit is contained in:
127
src/web/components/RuleCard.jsx
Normal file
127
src/web/components/RuleCard.jsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import React from 'react';
|
||||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { ruleErrors, hasErrors } from '../utils/validation.js';
|
||||
|
||||
function listToText(value) {
|
||||
return Array.isArray(value) ? value.join('\n') : '';
|
||||
}
|
||||
|
||||
function textToList(value) {
|
||||
return value
|
||||
.split(/\r?\n|,/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export function RuleCard({ rule, index, total, onUpdate, onRemove }) {
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: rule.id });
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.6 : 1,
|
||||
};
|
||||
const errors = ruleErrors(rule);
|
||||
const errored = hasErrors(errors);
|
||||
|
||||
return (
|
||||
<article ref={setNodeRef} style={style} className={errored ? 'rule-card invalid' : 'rule-card'}>
|
||||
<div className="rule-top">
|
||||
<span className="drag-handle" {...attributes} {...listeners} title="Перетащить">
|
||||
⠿ #{index + 1}/{total}
|
||||
</span>
|
||||
<input
|
||||
value={rule.name}
|
||||
onChange={(event) => onUpdate(rule.id, { name: event.target.value })}
|
||||
placeholder="Название списка"
|
||||
/>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={rule.enabled}
|
||||
onChange={(event) => onUpdate(rule.id, { enabled: event.target.checked })}
|
||||
/>
|
||||
включено
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label className="field">
|
||||
<span>Outbound</span>
|
||||
<select value={rule.outbound} onChange={(event) => onUpdate(rule.id, { outbound: event.target.value })}>
|
||||
<option value="direct">direct (напрямую)</option>
|
||||
<option value="vpn">vpn (через выбранный сервер)</option>
|
||||
<option value="block">block (заблокировать)</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div className="rule-fields">
|
||||
<label className={errors.domains.length ? 'field has-error' : 'field'}>
|
||||
<span>Домены (точное совпадение)</span>
|
||||
<textarea
|
||||
value={listToText(rule.domains)}
|
||||
onChange={(event) => onUpdate(rule.id, { domains: textToList(event.target.value) })}
|
||||
placeholder="riotgames.com"
|
||||
/>
|
||||
{errors.domains.length > 0 && <small className="error">Невалидно: {errors.domains.join(', ')}</small>}
|
||||
</label>
|
||||
<label className={errors.domainSuffixes.length ? 'field has-error' : 'field'}>
|
||||
<span>Суффиксы доменов</span>
|
||||
<textarea
|
||||
value={listToText(rule.domainSuffixes)}
|
||||
onChange={(event) => onUpdate(rule.id, { domainSuffixes: textToList(event.target.value) })}
|
||||
placeholder={'leagueoflegends.com\nriotcdn.net'}
|
||||
/>
|
||||
{errors.domainSuffixes.length > 0 && <small className="error">Невалидно: {errors.domainSuffixes.join(', ')}</small>}
|
||||
</label>
|
||||
<label className={errors.ipCidrs.length ? 'field has-error' : 'field'}>
|
||||
<span>IP CIDR</span>
|
||||
<textarea
|
||||
value={listToText(rule.ipCidrs)}
|
||||
onChange={(event) => onUpdate(rule.id, { ipCidrs: textToList(event.target.value) })}
|
||||
placeholder="104.160.128.0/19"
|
||||
/>
|
||||
{errors.ipCidrs.length > 0 && <small className="error">Невалидно: {errors.ipCidrs.join(', ')}</small>}
|
||||
</label>
|
||||
<label className={errors.ports.length ? 'field has-error' : 'field'}>
|
||||
<span>Порты</span>
|
||||
<textarea
|
||||
value={listToText(rule.ports)}
|
||||
onChange={(event) => onUpdate(rule.id, { ports: textToList(event.target.value) })}
|
||||
placeholder={'5000\n5223'}
|
||||
/>
|
||||
{errors.ports.length > 0 && <small className="error">Невалидно: {errors.ports.join(', ')}</small>}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="rule-footer">
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={(rule.networks || []).includes('tcp')}
|
||||
onChange={(event) => {
|
||||
const set = new Set(rule.networks || []);
|
||||
event.target.checked ? set.add('tcp') : set.delete('tcp');
|
||||
onUpdate(rule.id, { networks: Array.from(set) });
|
||||
}}
|
||||
/>
|
||||
tcp
|
||||
</label>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={(rule.networks || []).includes('udp')}
|
||||
onChange={(event) => {
|
||||
const set = new Set(rule.networks || []);
|
||||
event.target.checked ? set.add('udp') : set.delete('udp');
|
||||
onUpdate(rule.id, { networks: Array.from(set) });
|
||||
}}
|
||||
/>
|
||||
udp
|
||||
</label>
|
||||
<button className="danger-button" type="button" onClick={() => onRemove(rule.id)}>
|
||||
Удалить
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user