Add traffic group sorting controls
Build and Deploy Gateway / build-and-push (push) Successful in 30s
Build and Deploy Gateway / deploy (push) Successful in 7s

This commit is contained in:
2026-08-31 05:58:13 +03:00
parent 797c73d35c
commit d060e3bada
5 changed files with 75 additions and 6 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
export const HARBOR_VERSIONS = Object.freeze({
macClient: '0.34.1',
gatewayClient: '0.36.1',
macClient: '0.34.2',
gatewayClient: '0.36.2',
gatewayBackend: '0.36.2',
});
+19 -2
View File
@@ -11,11 +11,13 @@ import { formatByteString } from '../../utils/format.js';
import {
groupTrafficConnections,
reconcileTrafficGroups,
sortTrafficGroups,
trafficGroupMatches,
type DisplayedTrafficGroup,
type TrafficConnectionGroup,
type TrafficQualityFilter,
type TrafficRouteFilter,
type TrafficSort,
} from './trafficRows.js';
const POLL_MS = 1_000;
@@ -310,6 +312,7 @@ export function TrafficPanel({ feature }: { feature: TrafficFeature }) {
const [query, setQuery] = useState('');
const [routeFilter, setRouteFilter] = useState<TrafficRouteFilter>('all');
const [qualityFilter, setQualityFilter] = useState<TrafficQualityFilter>('all');
const [sortMode, setSortMode] = useState<TrafficSort>('popular');
const [retentionSeconds, setRetentionSeconds] = useState<RetentionSeconds>(storedRetentionSeconds);
const [displayedGroups, setDisplayedGroups] = useState<DisplayedTrafficGroup[]>([]);
const [reducedMotion, setReducedMotion] = useState(() => (
@@ -324,9 +327,9 @@ export function TrafficPanel({ feature }: { feature: TrafficFeature }) {
return snapshotTime - Date.parse(connection.closedAt) < retentionSeconds * 1_000;
}), [snapshot, snapshotTime, retentionSeconds]);
const trafficGroups = useMemo(() => groupTrafficConnections(retainedConnections), [retainedConnections]);
const groups = useMemo(() => trafficGroups.filter((group) => (
const groups = useMemo(() => sortTrafficGroups(trafficGroups.filter((group) => (
trafficGroupMatches(group, query, routeFilter, qualityFilter)
)), [trafficGroups, query, routeFilter, qualityFilter]);
)), sortMode), [trafficGroups, query, routeFilter, qualityFilter, sortMode]);
useEffect(() => {
const media = matchMedia('(prefers-reduced-motion: reduce)');
@@ -438,6 +441,20 @@ export function TrafficPanel({ feature }: { feature: TrafficFeature }) {
onClick={() => setQualityFilter(value)}
>{label}</button>)}
</div>
<div className="client-traffic-retention">
<span>Сортировка</span>
<div className="client-traffic-filters" role="group" aria-label="Сортировка соединений">
{([
['popular', 'Популярные'],
['recent', 'Последние'],
] as const).map(([value, label]) => <button
type="button"
key={value}
aria-pressed={sortMode === value}
onClick={() => setSortMode(value)}
>{label}</button>)}
</div>
</div>
<div className="client-traffic-retention">
<span>Показывать завершённые</span>
<div className="client-traffic-filters" role="group" aria-label="Время показа завершённых соединений">
+17
View File
@@ -3,6 +3,7 @@ import { byteString } from '../../utils/format.js';
export type TrafficRouteFilter = 'all' | 'vpn' | 'direct' | 'other';
export type TrafficQualityFilter = 'all' | 'recognized' | 'attention';
export type TrafficSort = 'popular' | 'recent';
export interface TrafficConnectionGroup {
id: string;
@@ -90,6 +91,22 @@ export function groupTrafficConnections(connections: LiveTrafficConnection[]) {
return [...grouped].map(([id, members]) => buildTrafficGroup(id, members));
}
function newestStartedAt(group: TrafficConnectionGroup) {
return group.connections.reduce((latest, connection) => (
connection.startedAt > latest ? connection.startedAt : latest
), '');
}
export function sortTrafficGroups(groups: TrafficConnectionGroup[], mode: TrafficSort) {
return [...groups].sort((left, right) => {
const frequency = right.connections.length - left.connections.length;
const recent = newestStartedAt(right).localeCompare(newestStartedAt(left));
const primary = mode === 'popular' ? frequency || recent : recent || frequency;
if (primary) return primary;
return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
});
}
export function trafficGroupMatches(
group: TrafficConnectionGroup,
query: string,