Add WebUI navigation improvements: keyboard shortcuts, hash routing, command palette, cross-entity links

- Keyboard shortcuts: Ctrl+1-4 for tab switching
- URL hash routing: #tab/subtab format with browser back/forward support
- Tab count badges: running targets and active profiles counts
- Cross-entity quick links: clickable references navigate to related cards
- Command palette (Ctrl+K): global search across all entities with keyboard navigation
- Expand/collapse all sections: buttons in sub-tab bars
- Sticky section headers: headers pin while scrolling long card grids
- Improved section filter: better styling with reset button

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 02:40:24 +03:00
parent a82eec7a06
commit f67936c977
16 changed files with 917 additions and 34 deletions

View File

@@ -7,7 +7,7 @@ import { API_BASE, getHeaders, fetchWithAuth, escapeHtml } from '../core/api.js'
import { t } from '../core/i18n.js';
import { showToast, formatUptime } from '../core/ui.js';
import { renderPerfSection, initPerfCharts, startPerfPolling, stopPerfPolling } from './perf-charts.js';
import { startAutoRefresh } from './tabs.js';
import { startAutoRefresh, updateTabBadge } from './tabs.js';
const DASHBOARD_COLLAPSED_KEY = 'dashboard_collapsed';
const MAX_FPS_SAMPLES = 120;
@@ -338,6 +338,7 @@ export async function loadDashboard(forceFullRender = false) {
const running = enriched.filter(t => t.state && t.state.processing);
const stopped = enriched.filter(t => !t.state || !t.state.processing);
updateTabBadge('targets', running.length);
// Check if we can do an in-place metrics update (same targets, not first load)
const newRunningIds = running.map(t => t.id).sort().join(',');
@@ -365,6 +366,7 @@ export async function loadDashboard(forceFullRender = false) {
if (profiles.length > 0) {
const activeProfiles = profiles.filter(p => p.is_active);
const inactiveProfiles = profiles.filter(p => !p.is_active);
updateTabBadge('profiles', activeProfiles.length);
const profileItems = [...activeProfiles, ...inactiveProfiles].map(p => renderDashboardProfile(p)).join('');
dynamicHtml += `<div class="dashboard-section">