fix: syslog coverage count exceeding device total + remove decorative live indicator (v0.11.1 Beta)
- aggregateRules/aggregateActions: deduplicate coverage per device using a per-device seenKeys set, preventing a device with duplicate topic|action rules from inflating the coverage count above total - executePush (add path): skip devices that already have an identical rule/action before pushing to all devices, preventing duplicate RouterOS logging entries from being created in the first place - TopBar: remove hardcoded decorative 'live' indicator that had no backend connection logic Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
4abc96f015
commit
e692a1d7cd
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
A self-hosted, full-stack network management platform for MikroTik devices. Monitor, configure, and manage your entire MikroTik infrastructure — routers, switches, and wireless access points — from a single web interface.
|
A self-hosted, full-stack network management platform for MikroTik devices. Monitor, configure, and manage your entire MikroTik infrastructure — routers, switches, and wireless access points — from a single web interface.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mikrotik-manager-backend",
|
"name": "mikrotik-manager-backend",
|
||||||
"version": "0.11.0-beta",
|
"version": "0.11.1-beta",
|
||||||
"description": "Mikrotik Network Management Platform - Backend API",
|
"description": "Mikrotik Network Management Platform - Backend API",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mikrotik-manager-frontend",
|
"name": "mikrotik-manager-frontend",
|
||||||
"version": "0.11.0-beta",
|
"version": "0.11.1-beta",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -41,21 +41,6 @@ export default function TopBar({ onMenuClick }: TopBarProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 flex-shrink-0">
|
<div className="flex items-center gap-2 flex-shrink-0">
|
||||||
{/* Live indicator */}
|
|
||||||
<span
|
|
||||||
className="hidden sm:flex items-center gap-[6px] px-[10px] py-[4px] rounded-full text-[11px] mono"
|
|
||||||
style={{ color: 'var(--ink-3)', border: '1px solid var(--line)' }}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="w-[6px] h-[6px] rounded-full flex-shrink-0"
|
|
||||||
style={{
|
|
||||||
background: 'var(--good)',
|
|
||||||
boxShadow: '0 0 0 2px rgba(141,224,138,0.2), 0 0 6px rgba(141,224,138,0.5)',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
live
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{/* Theme toggle */}
|
{/* Theme toggle */}
|
||||||
<button
|
<button
|
||||||
onClick={toggleTheme}
|
onClick={toggleTheme}
|
||||||
|
|||||||
@@ -44,10 +44,14 @@ function aggregateActions(
|
|||||||
): AggregatedRow[] {
|
): AggregatedRow[] {
|
||||||
const map = new Map<string, AggregatedRow>();
|
const map = new Map<string, AggregatedRow>();
|
||||||
for (const d of deviceData) {
|
for (const d of deviceData) {
|
||||||
|
const seenKeys = new Set<string>();
|
||||||
for (const a of d.actions) {
|
for (const a of d.actions) {
|
||||||
const key = a['name'] ?? '';
|
const key = a['name'] ?? '';
|
||||||
if (!map.has(key)) map.set(key, { key, sample: a, coverage: [] });
|
if (!map.has(key)) map.set(key, { key, sample: a, coverage: [] });
|
||||||
map.get(key)!.coverage.push({ deviceId: d.deviceId, deviceName: d.deviceName, rowId: a['.id'] ?? '' });
|
if (!seenKeys.has(key)) {
|
||||||
|
map.get(key)!.coverage.push({ deviceId: d.deviceId, deviceName: d.deviceName, rowId: a['.id'] ?? '' });
|
||||||
|
seenKeys.add(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Array.from(map.values());
|
return Array.from(map.values());
|
||||||
@@ -58,10 +62,14 @@ function aggregateRules(
|
|||||||
): AggregatedRow[] {
|
): AggregatedRow[] {
|
||||||
const map = new Map<string, AggregatedRow>();
|
const map = new Map<string, AggregatedRow>();
|
||||||
for (const d of deviceData) {
|
for (const d of deviceData) {
|
||||||
|
const seenKeys = new Set<string>();
|
||||||
for (const r of d.rules) {
|
for (const r of d.rules) {
|
||||||
const key = `${r['topics'] ?? ''}|${r['action'] ?? ''}`;
|
const key = `${r['topics'] ?? ''}|${r['action'] ?? ''}`;
|
||||||
if (!map.has(key)) map.set(key, { key, sample: r, coverage: [] });
|
if (!map.has(key)) map.set(key, { key, sample: r, coverage: [] });
|
||||||
map.get(key)!.coverage.push({ deviceId: d.deviceId, deviceName: d.deviceName, rowId: r['.id'] ?? '' });
|
if (!seenKeys.has(key)) {
|
||||||
|
map.get(key)!.coverage.push({ deviceId: d.deviceId, deviceName: d.deviceName, rowId: r['.id'] ?? '' });
|
||||||
|
seenKeys.add(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Array.from(map.values());
|
return Array.from(map.values());
|
||||||
@@ -469,14 +477,25 @@ export default function NetworkServicesSyslogPage() {
|
|||||||
);
|
);
|
||||||
targetNames = coverage.map(c => c.deviceName);
|
targetNames = coverage.map(c => c.deviceName);
|
||||||
} else {
|
} else {
|
||||||
|
// Skip devices that already have an identical action/rule to avoid duplicates
|
||||||
|
const devicesToAdd = onlineDevices.filter(d => {
|
||||||
|
const deviceSyslog = allDevicesSyslog.find(ds => ds.deviceId === d.id);
|
||||||
|
if (!deviceSyslog) return true;
|
||||||
|
if (type === 'action') {
|
||||||
|
const newName = data['name'] ?? '';
|
||||||
|
return !deviceSyslog.actions.some(a => (a['name'] ?? '') === newName);
|
||||||
|
}
|
||||||
|
const newKey = `${data['topics'] ?? ''}|${data['action'] ?? ''}`;
|
||||||
|
return !deviceSyslog.rules.some(r => `${r['topics'] ?? ''}|${r['action'] ?? ''}` === newKey);
|
||||||
|
});
|
||||||
settled = await Promise.allSettled(
|
settled = await Promise.allSettled(
|
||||||
onlineDevices.map(d =>
|
devicesToAdd.map(d =>
|
||||||
type === 'action'
|
type === 'action'
|
||||||
? networkServicesApi.addSyslogAction(d.id, data)
|
? networkServicesApi.addSyslogAction(d.id, data)
|
||||||
: networkServicesApi.addSyslogRule(d.id, data)
|
: networkServicesApi.addSyslogRule(d.id, data)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
targetNames = onlineDevices.map(d => d.name);
|
targetNames = devicesToAdd.map(d => d.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsPushing(false); setPendingPush(null);
|
setIsPushing(false); setPendingPush(null);
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export const APP_VERSION = 'v0.11.0 Beta';
|
export const APP_VERSION = 'v0.11.1 Beta';
|
||||||
|
|||||||
Reference in New Issue
Block a user