feat(ui): theme-aware logo, dashboard bar graph, server-side client sorting (v0.14.1-beta)
UI/UX refinements on top of v0.14.0-beta, all validated on the test server: - Logo: new theme-aware compass iconmark (light/dark SVGs swapped via the dark class), sized to 45px; favicon updated to match. Wordmark casing corrected to "MikroTik Manager" (font size unchanged). - Navigation icons enlarged 15 -> 17px. - Dashboard: "Connected clients" line chart converted to a bar chart with a category axis + light downsampling so bars stay readably thick across ranges. - Clients: sorting is now server-side across the whole dataset (was sorting only the current page, so Type/others appeared to do nothing on a uniform page); table uses a fixed column layout + keepPreviousData so columns no longer jump or blank out when sorting/paging. Backend clients route gains whitelisted sort/dir params (safe numeric IP sort, deterministic tiebreaker). - Connections tab: surfaces the live /ip/firewall/connection/tracking state (on/auto/off pill) and clearer empty-state copy explaining auto-mode. Lint/typecheck/build/jest green in both workspaces; npm audit --audit-level=high clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ee9c9ff9f7
commit
d79eb13119
@@ -31,6 +31,20 @@ export default function ConnectionsTab({ deviceId }: { deviceId: number }) {
|
||||
const q = search.trim().toLowerCase();
|
||||
const rows = q ? all.filter(c => JSON.stringify(c).toLowerCase().includes(q)) : all;
|
||||
|
||||
// Conntrack state: yes / no / auto (auto = active only once firewall/NAT rules exist).
|
||||
const tracking = (data?.tracking?.enabled ?? '').toLowerCase();
|
||||
const trackLabel = tracking === 'yes' ? 'on' : tracking === 'auto' ? 'auto' : tracking === 'no' ? 'off' : '';
|
||||
const trackStyle = tracking === 'yes'
|
||||
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400'
|
||||
: tracking === 'no'
|
||||
? 'bg-gray-100 dark:bg-slate-700 text-gray-500 dark:text-slate-400'
|
||||
: 'bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-400';
|
||||
const emptyMessage = tracking === 'no'
|
||||
? 'Connection tracking is disabled on this device, so no connections are recorded. Enable it (or add a firewall/NAT rule) to populate this table.'
|
||||
: tracking === 'auto'
|
||||
? 'Connection tracking is in auto mode — RouterOS only tracks connections once the device has firewall/NAT/mangle rules. This device has none yet, so the table is empty. It also won’t show hardware-switched (L2) traffic, only what the CPU routes/firewalls.'
|
||||
: 'No active connections. Only CPU-processed (routed/firewalled) traffic is tracked — hardware-switched L2 traffic bypasses connection tracking.';
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between gap-3 flex-wrap">
|
||||
@@ -39,6 +53,12 @@ export default function ConnectionsTab({ deviceId }: { deviceId: number }) {
|
||||
<h3 className="text-sm font-semibold text-gray-900 dark:text-white">
|
||||
Active Connections <span className="text-gray-400 font-normal">({total}{total > all.length ? `, showing ${all.length}` : ''})</span>
|
||||
</h3>
|
||||
{trackLabel && (
|
||||
<span className={clsx('inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium', trackStyle)}
|
||||
title={tracking === 'auto' ? 'Auto: tracking activates only when the device has firewall/NAT rules' : tracking === 'no' ? 'Connection tracking is disabled' : 'Connection tracking is active'}>
|
||||
Tracking: {trackLabel}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative">
|
||||
@@ -54,7 +74,7 @@ export default function ConnectionsTab({ deviceId }: { deviceId: number }) {
|
||||
{isLoading ? (
|
||||
<div className="text-center py-8 text-gray-400"><RefreshCw className="w-4 h-4 animate-spin inline mr-2" />Loading connections…</div>
|
||||
) : rows.length === 0 ? (
|
||||
<div className="card p-8 text-center text-gray-400">{all.length === 0 ? 'No active connections (connection tracking may be disabled).' : 'No connections match your filter.'}</div>
|
||||
<div className="card p-8 text-center text-gray-400 max-w-2xl mx-auto text-sm">{all.length === 0 ? emptyMessage : 'No connections match your filter.'}</div>
|
||||
) : (
|
||||
<div className="card overflow-hidden overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
|
||||
@@ -83,7 +83,7 @@ function NavItem({
|
||||
)
|
||||
}
|
||||
>
|
||||
<Icon className="w-[15px] h-[15px] flex-shrink-0" />
|
||||
<Icon className="w-[17px] h-[17px] flex-shrink-0" />
|
||||
<span className={clsx(isCollapsed && 'md:hidden')}>{label}</span>
|
||||
</NavLink>
|
||||
);
|
||||
@@ -110,7 +110,7 @@ function GroupHeader({
|
||||
isActive ? 'text-accent' : 'text-ink-2 hover:bg-surface-3 hover:text-ink'
|
||||
)}
|
||||
>
|
||||
<Icon className="w-[15px] h-[15px] flex-shrink-0" />
|
||||
<Icon className="w-[17px] h-[17px] 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>
|
||||
@@ -176,14 +176,11 @@ export default function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) {
|
||||
)}
|
||||
style={{ borderColor: 'var(--line)' }}
|
||||
>
|
||||
<div
|
||||
className="w-[30px] h-[30px] rounded-[7px] flex items-center justify-center flex-shrink-0"
|
||||
style={{ background: 'linear-gradient(135deg, var(--accent), var(--accent-2))' }}
|
||||
>
|
||||
<Network className="w-4 h-4" style={{ color: 'var(--accent-fg)' }} />
|
||||
</div>
|
||||
{/* Theme-aware logo: light-tile icon in light mode, navy-tile icon in dark mode */}
|
||||
<img src="/logo-light.svg" alt="MikroTik Manager" className="w-[45px] h-[45px] flex-shrink-0 block dark:hidden" />
|
||||
<img src="/logo-dark.svg" alt="MikroTik Manager" className="w-[45px] h-[45px] flex-shrink-0 hidden dark:block" />
|
||||
<div className={clsx(isCollapsed && 'md:hidden')}>
|
||||
<div className="text-[14px] font-semibold leading-none" style={{ color: 'var(--ink)' }}>Mikrotik Manager</div>
|
||||
<div className="text-[14px] font-semibold leading-none" style={{ color: 'var(--ink)' }}>MikroTik Manager</div>
|
||||
<div className="text-[11px] leading-none mt-[3px]" style={{ color: 'var(--ink-3)' }}>{APP_VERSION}</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -222,7 +219,7 @@ export default function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) {
|
||||
)
|
||||
}
|
||||
>
|
||||
<Layers className="w-[15px] h-[15px] flex-shrink-0" />
|
||||
<Layers className="w-[17px] h-[17px] flex-shrink-0" />
|
||||
</NavLink>
|
||||
) : (
|
||||
<>
|
||||
@@ -260,7 +257,7 @@ export default function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) {
|
||||
)
|
||||
}
|
||||
>
|
||||
<Router className="w-[15px] h-[15px] flex-shrink-0" />
|
||||
<Router className="w-[17px] h-[17px] flex-shrink-0" />
|
||||
</NavLink>
|
||||
) : (
|
||||
<>
|
||||
@@ -298,7 +295,7 @@ export default function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) {
|
||||
)
|
||||
}
|
||||
>
|
||||
<Wifi className="w-[15px] h-[15px] flex-shrink-0" />
|
||||
<Wifi className="w-[17px] h-[17px] flex-shrink-0" />
|
||||
</NavLink>
|
||||
) : (
|
||||
<>
|
||||
@@ -337,7 +334,7 @@ export default function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) {
|
||||
)
|
||||
}
|
||||
>
|
||||
<Network className="w-[15px] h-[15px] flex-shrink-0" />
|
||||
<Network className="w-[17px] h-[17px] flex-shrink-0" />
|
||||
</NavLink>
|
||||
) : (
|
||||
<>
|
||||
@@ -405,9 +402,9 @@ export default function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) {
|
||||
style={{ color: 'var(--ink-4)' }}
|
||||
>
|
||||
{isCollapsed ? (
|
||||
<ChevronRight className="w-[15px] h-[15px] flex-shrink-0" />
|
||||
<ChevronRight className="w-[17px] h-[17px] flex-shrink-0" />
|
||||
) : (
|
||||
<><ChevronLeft className="w-[15px] h-[15px] flex-shrink-0" />Collapse</>
|
||||
<><ChevronLeft className="w-[17px] h-[17px] flex-shrink-0" />Collapse</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user