fix: resolve all CI lint errors and security audit failures (v0.10.9 Beta)
- DevicesPage: add statusFilter + typeFilter to filtered useMemo deps (was a real bug — filter chips had no effect until unrelated re-render) - DevicesPage: refactor cpuHistories useMemo to pure Object.fromEntries form - DashboardPage: suppress react-hooks/purity for intentional Date.now() chart domain - Sidebar: move GroupHeader from inline arrow fn to module-scope named function (fixes react-hooks/static-components) - axios: bump ^1.6.5 → ^1.16.0 to clear high-severity CVEs (prototype pollution, CRLF injection, NO_PROXY bypass) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
3a7ac1d023
commit
17e989da99
Generated
+7
-7
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "mikrotik-manager-frontend",
|
"name": "mikrotik-manager-frontend",
|
||||||
"version": "0.10.6-beta",
|
"version": "0.10.9-beta",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mikrotik-manager-frontend",
|
"name": "mikrotik-manager-frontend",
|
||||||
"version": "0.10.6-beta",
|
"version": "0.10.9-beta",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/react-query": "^5.17.1",
|
"@tanstack/react-query": "^5.17.1",
|
||||||
"@types/leaflet": "^1.9.21",
|
"@types/leaflet": "^1.9.21",
|
||||||
"@xterm/addon-fit": "^0.11.0",
|
"@xterm/addon-fit": "^0.11.0",
|
||||||
"@xterm/xterm": "^6.0.0",
|
"@xterm/xterm": "^6.0.0",
|
||||||
"@xyflow/react": "^12.10.2",
|
"@xyflow/react": "^12.10.2",
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.16.0",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"date-fns": "^3.2.0",
|
"date-fns": "^3.2.0",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
@@ -2425,12 +2425,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.15.0",
|
"version": "1.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz",
|
||||||
"integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==",
|
"integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.11",
|
"follow-redirects": "^1.16.0",
|
||||||
"form-data": "^4.0.5",
|
"form-data": "^4.0.5",
|
||||||
"proxy-from-env": "^2.1.0"
|
"proxy-from-env": "^2.1.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"@xterm/addon-fit": "^0.11.0",
|
"@xterm/addon-fit": "^0.11.0",
|
||||||
"@xterm/xterm": "^6.0.0",
|
"@xterm/xterm": "^6.0.0",
|
||||||
"@xyflow/react": "^12.10.2",
|
"@xyflow/react": "^12.10.2",
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.16.0",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"date-fns": "^3.2.0",
|
"date-fns": "^3.2.0",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
|
|||||||
@@ -86,6 +86,34 @@ function NavItem({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function GroupHeader({
|
||||||
|
icon: Icon,
|
||||||
|
label,
|
||||||
|
isActive,
|
||||||
|
isOpen,
|
||||||
|
onToggle,
|
||||||
|
}: {
|
||||||
|
icon: React.ElementType;
|
||||||
|
label: string;
|
||||||
|
isActive: boolean;
|
||||||
|
isOpen: boolean;
|
||||||
|
onToggle: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={onToggle}
|
||||||
|
className={clsx(
|
||||||
|
'w-full flex items-center gap-[10px] px-[10px] py-[7px] rounded-[6px] text-[13px] font-medium transition-colors duration-150 border-l-2 border-transparent',
|
||||||
|
isActive ? 'text-accent' : 'text-ink-2 hover:bg-surface-3 hover:text-ink'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon className="w-[15px] h-[15px] flex-shrink-0" />
|
||||||
|
<span className="flex-1 text-left">{label}</span>
|
||||||
|
<ChevronDown className={clsx('w-3.5 h-3.5 transition-transform', isOpen ? 'rotate-0' : '-rotate-90')} />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) {
|
export default function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) {
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -124,33 +152,6 @@ export default function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) {
|
|||||||
onMobileClose();
|
onMobileClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Group header button (Switches / Routers / Wireless / Network Services)
|
|
||||||
const GroupHeader = ({
|
|
||||||
icon: Icon,
|
|
||||||
label,
|
|
||||||
isActive,
|
|
||||||
isOpen,
|
|
||||||
onToggle,
|
|
||||||
}: {
|
|
||||||
icon: React.ElementType;
|
|
||||||
label: string;
|
|
||||||
isActive: boolean;
|
|
||||||
isOpen: boolean;
|
|
||||||
onToggle: () => void;
|
|
||||||
}) => (
|
|
||||||
<button
|
|
||||||
onClick={onToggle}
|
|
||||||
className={clsx(
|
|
||||||
'w-full flex items-center gap-[10px] px-[10px] py-[7px] rounded-[6px] text-[13px] font-medium transition-colors duration-150 border-l-2 border-transparent',
|
|
||||||
isActive ? 'text-accent' : 'text-ink-2 hover:bg-surface-3 hover:text-ink'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Icon className="w-[15px] h-[15px] flex-shrink-0" />
|
|
||||||
<span className="flex-1 text-left">{label}</span>
|
|
||||||
<ChevronDown className={clsx('w-3.5 h-3.5 transition-transform', isOpen ? 'rotate-0' : '-rotate-90')} />
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside
|
<aside
|
||||||
className={clsx(
|
className={clsx(
|
||||||
|
|||||||
@@ -274,14 +274,13 @@ function SummaryView(props: Record<string, any>) {
|
|||||||
router: { label: 'Routers', color: 'var(--violet)' },
|
router: { label: 'Routers', color: 'var(--violet)' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const chartDomain = useMemo<[number, number]>(
|
const chartDomain: [number, number] = (() => {
|
||||||
() => {
|
const ranges: Record<string, number> = { '1h': 1, '6h': 6, '24h': 24, '7d': 168 };
|
||||||
const ranges: Record<string, number> = { '1h': 1, '6h': 6, '24h': 24, '7d': 168 };
|
const hrs = ranges[chartRange] ?? 24;
|
||||||
const hrs = ranges[chartRange] ?? 24;
|
// eslint-disable-next-line react-hooks/purity
|
||||||
return [Date.now() - hrs * 60 * 60 * 1000, Date.now()];
|
const now = Date.now();
|
||||||
},
|
return [now - hrs * 60 * 60 * 1000, now];
|
||||||
[chartRange, clientsOverTime] // eslint-disable-line react-hooks/exhaustive-deps
|
})();
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|||||||
@@ -161,14 +161,15 @@ export default function DevicesPage() {
|
|||||||
enabled: d.status === 'online',
|
enabled: d.status === 'online',
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
const cpuHistories = useMemo(() => {
|
const cpuHistories = useMemo(
|
||||||
const map: Record<number, number[]> = {};
|
() => Object.fromEntries(
|
||||||
devices.forEach((d, i) => {
|
devices.map((d, i) => {
|
||||||
const pts = cpuHistoryResults[i]?.data ?? [];
|
const pts = cpuHistoryResults[i]?.data ?? [];
|
||||||
map[d.id] = pts.map((p: { cpu_load?: number }) => p.cpu_load ?? 0).filter((v: number) => v > 0);
|
return [d.id, pts.map((p: { cpu_load?: number }) => p.cpu_load ?? 0).filter((v: number) => v > 0)];
|
||||||
});
|
})
|
||||||
return map;
|
) as Record<number, number[]>,
|
||||||
}, [devices, cpuHistoryResults]);
|
[devices, cpuHistoryResults],
|
||||||
|
);
|
||||||
|
|
||||||
const filtered = useMemo(() => {
|
const filtered = useMemo(() => {
|
||||||
const base = devices.filter(d => {
|
const base = devices.filter(d => {
|
||||||
@@ -205,7 +206,7 @@ export default function DevicesPage() {
|
|||||||
return deviceSort.dir === 'asc' ? cmp : -cmp;
|
return deviceSort.dir === 'asc' ? cmp : -cmp;
|
||||||
});
|
});
|
||||||
return sorted;
|
return sorted;
|
||||||
}, [devices, search, deviceSort]);
|
}, [devices, search, statusFilter, typeFilter, deviceSort]);
|
||||||
|
|
||||||
const discoveredList = discovered as DiscoveredDevice[];
|
const discoveredList = discovered as DiscoveredDevice[];
|
||||||
const duplicateCount = useMemo(
|
const duplicateCount = useMemo(
|
||||||
|
|||||||
Reference in New Issue
Block a user