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