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:
2GT-Rich
2026-05-15 16:39:07 -07:00
co-authored by Claude Sonnet 4.6
parent 4abc96f015
commit e692a1d7cd
6 changed files with 27 additions and 23 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mikrotik-manager-frontend",
"version": "0.11.0-beta",
"version": "0.11.1-beta",
"private": true,
"type": "module",
"scripts": {
-15
View File
@@ -41,21 +41,6 @@ export default function TopBar({ onMenuClick }: TopBarProps) {
</div>
<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 */}
<button
onClick={toggleTheme}
@@ -44,10 +44,14 @@ function aggregateActions(
): AggregatedRow[] {
const map = new Map<string, AggregatedRow>();
for (const d of deviceData) {
const seenKeys = new Set<string>();
for (const a of d.actions) {
const key = a['name'] ?? '';
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());
@@ -58,10 +62,14 @@ function aggregateRules(
): AggregatedRow[] {
const map = new Map<string, AggregatedRow>();
for (const d of deviceData) {
const seenKeys = new Set<string>();
for (const r of d.rules) {
const key = `${r['topics'] ?? ''}|${r['action'] ?? ''}`;
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());
@@ -469,14 +477,25 @@ export default function NetworkServicesSyslogPage() {
);
targetNames = coverage.map(c => c.deviceName);
} 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(
onlineDevices.map(d =>
devicesToAdd.map(d =>
type === 'action'
? networkServicesApi.addSyslogAction(d.id, data)
: networkServicesApi.addSyslogRule(d.id, data)
)
);
targetNames = onlineDevices.map(d => d.name);
targetNames = devicesToAdd.map(d => d.name);
}
setIsPushing(false); setPendingPush(null);
+1 -1
View File
@@ -1 +1 @@
export const APP_VERSION = 'v0.11.0 Beta';
export const APP_VERSION = 'v0.11.1 Beta';