feat: добавлена группировка трафика с возможностью переключения
All checks were successful
Build and Deploy Gateway / build-and-deploy (push) Successful in 24s
All checks were successful
Build and Deploy Gateway / build-and-deploy (push) Successful in 24s
This commit is contained in:
@@ -33,11 +33,31 @@ const CATEGORY_BADGE = {
|
||||
other: { cls: '', label: 'other' },
|
||||
};
|
||||
|
||||
const TRAFFIC_GROUP_WINDOW_MS = 60_000;
|
||||
|
||||
function groupTraffic(list) {
|
||||
const out = [];
|
||||
for (const e of list) {
|
||||
const key = `${e.category}|${e.host}|${e.port}|${e.matchedRule || ''}`;
|
||||
const ts = new Date(e.ts).getTime();
|
||||
const last = out[out.length - 1];
|
||||
if (last && last._key === key && ts - last._lastTs < TRAFFIC_GROUP_WINDOW_MS) {
|
||||
last.count += 1;
|
||||
last._lastTs = ts;
|
||||
last.lastTs = e.ts;
|
||||
} else {
|
||||
out.push({ ...e, _key: key, _lastTs: ts, count: 1, lastTs: e.ts });
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function TrafficTab() {
|
||||
const [traffic, setTraffic] = useState([]);
|
||||
const [paused, setPaused] = useState(false);
|
||||
const [filter, setFilter] = useState('all'); // all | direct | vpn | block
|
||||
const [search, setSearch] = useState('');
|
||||
const [grouped, setGrouped] = useState(true);
|
||||
const [autoscroll, setAutoscroll] = useState(true);
|
||||
const containerRef = useRef(null);
|
||||
const pausedRef = useRef(false);
|
||||
@@ -72,8 +92,8 @@ function TrafficTab() {
|
||||
e.matchedRule?.toLowerCase().includes(s),
|
||||
);
|
||||
}
|
||||
return list;
|
||||
}, [traffic, filter, search]);
|
||||
return grouped ? groupTraffic(list) : list;
|
||||
}, [traffic, filter, search, grouped]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!autoscroll || !containerRef.current) return;
|
||||
@@ -102,6 +122,10 @@ function TrafficTab() {
|
||||
<option value="vpn">VPN ({counts.vpn})</option>
|
||||
<option value="block">block ({counts.block})</option>
|
||||
</select>
|
||||
<label className="checkbox">
|
||||
<input type="checkbox" checked={grouped} onChange={(e) => setGrouped(e.target.checked)} />
|
||||
Группировать
|
||||
</label>
|
||||
<label className="checkbox">
|
||||
<input type="checkbox" checked={autoscroll} onChange={(e) => setAutoscroll(e.target.checked)} />
|
||||
Автоскролл
|
||||
@@ -131,6 +155,7 @@ function TrafficTab() {
|
||||
<th>Хост / IP</th>
|
||||
<th style={{ width: 55 }}>Порт</th>
|
||||
<th>Правило</th>
|
||||
<th style={{ width: 40 }}></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -149,6 +174,9 @@ function TrafficTab() {
|
||||
? <span className="badge info" style={{ fontSize: 11 }}>{e.matchedRule}</span>
|
||||
: <span className="muted" style={{ fontSize: 11 }}>—</span>}
|
||||
</td>
|
||||
<td className="muted text-mono" style={{ textAlign: 'right', fontSize: 11 }}>
|
||||
{e.count > 1 && <span className="repeat">×{e.count}</span>}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user