refactor(SettingsPage, TrafficDashboard): rename traffic interface state variables and enhance interface selection logic for improved clarity and functionality
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m1s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m1s
This commit is contained in:
@@ -74,7 +74,7 @@ export default function SettingsPage() {
|
||||
const [pingServicesServerId, setPingServicesServerId] = useState('');
|
||||
const [pingServicesGatewayIp, setPingServicesGatewayIp] = useState('');
|
||||
const [pingServicesCacheSeconds, setPingServicesCacheSeconds] = useState('');
|
||||
const [trafficInterfaceNamesSelected, setTrafficInterfaceNamesSelected] = useState([]);
|
||||
const [trafficInterfacesSelected, setTrafficInterfacesSelected] = useState([]);
|
||||
const [trafficJumphosts, setTrafficJumphosts] = useState([]);
|
||||
const [trafficInterfacesLoading, setTrafficInterfacesLoading] = useState(false);
|
||||
const [trafficInterfacesError, setTrafficInterfacesError] = useState('');
|
||||
@@ -147,13 +147,39 @@ export default function SettingsPage() {
|
||||
fetchTrafficInterfaces();
|
||||
}, [activeSection, trafficJumphosts.length, fetchTrafficInterfaces]);
|
||||
|
||||
const trafficAllInterfaceNames = useMemo(() => {
|
||||
const set = new Set();
|
||||
const getJumphostKey = useCallback((jh) => String(jh?.serverId || jh?.host || jh?.name || ''), []);
|
||||
|
||||
const trafficAllPairs = useMemo(() => {
|
||||
const out = [];
|
||||
for (const jh of trafficJumphosts) {
|
||||
for (const i of jh.interfaces || []) set.add(i.name);
|
||||
const sk = getJumphostKey(jh);
|
||||
for (const i of jh.interfaces || []) {
|
||||
if (i?.name) out.push({ serverKey: sk, interfaceName: i.name });
|
||||
}
|
||||
}
|
||||
return Array.from(set).sort((a, b) => a.localeCompare(b));
|
||||
}, [trafficJumphosts]);
|
||||
return out;
|
||||
}, [trafficJumphosts, getJumphostKey]);
|
||||
|
||||
const isTrafficInterfaceSelected = useCallback(
|
||||
(serverKey, interfaceName) =>
|
||||
trafficInterfacesSelected.some(
|
||||
(p) => p.serverKey === serverKey && p.interfaceName === interfaceName
|
||||
),
|
||||
[trafficInterfacesSelected]
|
||||
);
|
||||
|
||||
const setTrafficInterfaceChecked = useCallback(
|
||||
(serverKey, interfaceName, checked) => {
|
||||
setTrafficInterfacesSelected((prev) => {
|
||||
const next = prev.filter(
|
||||
(p) => !(p.serverKey === serverKey && p.interfaceName === interfaceName)
|
||||
);
|
||||
if (checked) next.push({ serverKey, interfaceName });
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const goToSection = (id) => {
|
||||
setActiveSection(id);
|
||||
@@ -196,10 +222,20 @@ export default function SettingsPage() {
|
||||
? String(data.pingServicesCacheSeconds)
|
||||
: ''
|
||||
);
|
||||
const rawNames = data?.trafficInterfaceNames;
|
||||
setTrafficInterfaceNamesSelected(
|
||||
Array.isArray(rawNames)
|
||||
? rawNames.filter((n) => n != null).map(String)
|
||||
const raw = data?.trafficInterfaces;
|
||||
setTrafficInterfacesSelected(
|
||||
Array.isArray(raw)
|
||||
? raw
|
||||
.filter(
|
||||
(p) =>
|
||||
p &&
|
||||
(p.serverKey != null || p.serverId != null) &&
|
||||
(p.interfaceName != null || p.name != null)
|
||||
)
|
||||
.map((p) => ({
|
||||
serverKey: String(p.serverKey ?? p.serverId ?? ''),
|
||||
interfaceName: String(p.interfaceName ?? p.name ?? ''),
|
||||
}))
|
||||
: []
|
||||
);
|
||||
const e =
|
||||
@@ -274,8 +310,11 @@ export default function SettingsPage() {
|
||||
0,
|
||||
parseInt(pingServicesCacheSeconds, 10) || 0
|
||||
),
|
||||
trafficInterfaceNames: Array.isArray(trafficInterfaceNamesSelected)
|
||||
? trafficInterfaceNamesSelected.filter(Boolean)
|
||||
trafficInterfaces: Array.isArray(trafficInterfacesSelected)
|
||||
? trafficInterfacesSelected.map((p) => ({
|
||||
serverKey: p.serverKey,
|
||||
interfaceName: p.interfaceName,
|
||||
}))
|
||||
: [],
|
||||
};
|
||||
const payload = { settings: mergedSettings, etag };
|
||||
@@ -611,15 +650,15 @@ export default function SettingsPage() {
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-outline-secondary"
|
||||
onClick={() => setTrafficInterfaceNamesSelected([...trafficAllInterfaceNames])}
|
||||
disabled={saving || trafficInterfacesLoading || trafficAllInterfaceNames.length === 0}
|
||||
onClick={() => setTrafficInterfacesSelected([...trafficAllPairs])}
|
||||
disabled={saving || trafficInterfacesLoading || trafficAllPairs.length === 0}
|
||||
>
|
||||
Выбрать все
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-outline-secondary"
|
||||
onClick={() => setTrafficInterfaceNamesSelected([])}
|
||||
onClick={() => setTrafficInterfacesSelected([])}
|
||||
disabled={saving}
|
||||
>
|
||||
Снять все
|
||||
@@ -671,28 +710,30 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!jh.error && (jh.interfaces?.length ?? 0) > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-ghost-secondary"
|
||||
onClick={() => {
|
||||
const names = (jh.interfaces || []).map((i) => i.name);
|
||||
setTrafficInterfaceNamesSelected((prev) => {
|
||||
const next = new Set(prev);
|
||||
const allChecked = names.every((n) => next.has(n));
|
||||
if (allChecked) names.forEach((n) => next.delete(n));
|
||||
else names.forEach((n) => next.add(n));
|
||||
return Array.from(next).sort((a, b) => a.localeCompare(b));
|
||||
});
|
||||
}}
|
||||
disabled={saving}
|
||||
title="Выбрать / снять все на этом сервере"
|
||||
>
|
||||
{(jh.interfaces || []).every((i) => trafficInterfaceNamesSelected.includes(i.name))
|
||||
? 'Снять все'
|
||||
: 'Выбрать все'}
|
||||
</button>
|
||||
)}
|
||||
{!jh.error && (jh.interfaces?.length ?? 0) > 0 && (() => {
|
||||
const sk = getJumphostKey(jh);
|
||||
const pairs = (jh.interfaces || []).map((i) => ({ serverKey: sk, interfaceName: i.name }));
|
||||
const allChecked = pairs.every((p) => isTrafficInterfaceSelected(p.serverKey, p.interfaceName));
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-ghost-secondary"
|
||||
onClick={() => {
|
||||
setTrafficInterfacesSelected((prev) => {
|
||||
const next = prev.filter(
|
||||
(x) => !pairs.some((p) => p.serverKey === x.serverKey && p.interfaceName === x.interfaceName)
|
||||
);
|
||||
if (!allChecked) next.push(...pairs);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
disabled={saving}
|
||||
title="Выбрать / снять все на этом сервере"
|
||||
>
|
||||
{allChecked ? 'Снять все' : 'Выбрать все'}
|
||||
</button>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<div className="card-body">
|
||||
{jh.error && (
|
||||
@@ -705,27 +746,25 @@ export default function SettingsPage() {
|
||||
)}
|
||||
{!jh.error && (jh.interfaces?.length ?? 0) > 0 && (
|
||||
<div className="row g-2">
|
||||
{jh.interfaces.map((iface) => (
|
||||
<div key={iface.name} className="col-12 col-sm-6">
|
||||
<label className="form-check">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={trafficInterfaceNamesSelected.includes(iface.name)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setTrafficInterfaceNamesSelected((prev) => [...prev, iface.name].sort((a, b) => a.localeCompare(b)));
|
||||
} else {
|
||||
setTrafficInterfaceNamesSelected((prev) => prev.filter((n) => n !== iface.name));
|
||||
}
|
||||
}}
|
||||
disabled={saving}
|
||||
aria-label={`Интерфейс ${iface.name}`}
|
||||
/>
|
||||
<span className="form-check-label font-monospace">{iface.name}</span>
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
{jh.interfaces.map((iface) => {
|
||||
const sk = getJumphostKey(jh);
|
||||
const checked = isTrafficInterfaceSelected(sk, iface.name);
|
||||
return (
|
||||
<div key={iface.name} className="col-12 col-sm-6">
|
||||
<label className="form-check">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
onChange={(e) => setTrafficInterfaceChecked(sk, iface.name, e.target.checked)}
|
||||
disabled={saving}
|
||||
aria-label={`Интерфейс ${iface.name}`}
|
||||
/>
|
||||
<span className="form-check-label font-monospace">{iface.name}</span>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -158,13 +158,19 @@ function JumphostCard({ jumphost }) {
|
||||
);
|
||||
}
|
||||
|
||||
/** Нормализовать список имён интерфейсов из настроек (массив, пустой = все) */
|
||||
/** Ключ jumphost для сопоставления с настройками (как в SettingsPage) */
|
||||
function getJumphostKey(jh) {
|
||||
return String(jh?.serverId ?? jh?.host ?? jh?.name ?? '');
|
||||
}
|
||||
|
||||
/** Множество выбранных пар (serverKey + interfaceName). Пустое = учитывать все интерфейсы. */
|
||||
function getTrafficInterfaceFilter(settings) {
|
||||
const raw = settings?.trafficInterfaceNames;
|
||||
if (!raw) return null;
|
||||
const arr = Array.isArray(raw) ? raw : (typeof raw === 'string' ? raw.split(/[\n,]+/).map((s) => s.trim()) : []);
|
||||
const names = arr.filter(Boolean);
|
||||
return names.length > 0 ? new Set(names) : null;
|
||||
const raw = settings?.trafficInterfaces;
|
||||
if (!raw || !Array.isArray(raw)) return null;
|
||||
const pairs = raw
|
||||
.filter((p) => p && (p.serverKey != null || p.serverId != null) && (p.interfaceName != null || p.name != null))
|
||||
.map((p) => `${String(p.serverKey ?? p.serverId ?? '')}\n${String(p.interfaceName ?? p.name ?? '')}`);
|
||||
return pairs.length > 0 ? new Set(pairs) : null;
|
||||
}
|
||||
|
||||
export default function TrafficDashboard() {
|
||||
@@ -201,12 +207,19 @@ export default function TrafficDashboard() {
|
||||
|
||||
const filteredJumphosts = useMemo(() => {
|
||||
if (!interfaceFilter) return jumphosts;
|
||||
return jumphosts.map((jh) => ({
|
||||
...jh,
|
||||
interfaces: Array.isArray(jh.interfaces)
|
||||
? jh.interfaces.filter((i) => i.name != null && interfaceFilter.has(String(i.name).trim()))
|
||||
: [],
|
||||
}));
|
||||
return jumphosts.map((jh) => {
|
||||
const sk = getJumphostKey(jh);
|
||||
return {
|
||||
...jh,
|
||||
interfaces: Array.isArray(jh.interfaces)
|
||||
? jh.interfaces.filter(
|
||||
(i) =>
|
||||
i.name != null &&
|
||||
interfaceFilter.has(`${sk}\n${String(i.name).trim()}`)
|
||||
)
|
||||
: [],
|
||||
};
|
||||
});
|
||||
}, [jumphosts, interfaceFilter]);
|
||||
|
||||
// Суммарная статистика по всем отображаемым системам (без ошибок, с данными)
|
||||
|
||||
Reference in New Issue
Block a user