1+ <template >
2+ <div class =" compact-mode h-screen flex flex-col theme-tokyo-night dashboard-modern" >
3+ <!-- Background gradient overlay -->
4+ <div class =" fixed inset-0 bg-gradient-to-br from-[#1a1b26] via-[#16161e] to-[#0f0f14] -z-10" ></div >
5+ <div class =" fixed inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIGZpbGw9IiM3YWEyZjciIGZpbGwtb3BhY2l0eT0iMC4wMiI+PGNpcmNsZSBjeD0iMzAiIGN5PSIzMCIgcj0iMSIvPjwvZz48L2c+PC9zdmc+')] -z-10 opacity-50" ></div >
6+
7+ <!-- Filters -->
8+ <FilterPanel
9+ v-if =" showFilters"
10+ class =" short:hidden glass-panel-elevated mx-4 mt-4 rounded-2xl"
11+ :filters =" filters"
12+ @update:filters =" filters = $event"
13+ />
14+
15+ <!-- Live Pulse Chart - Glass Panel -->
16+ <div class =" mx-4 mt-4 mobile:mx-2 mobile:mt-2" >
17+ <div class =" glass-panel rounded-2xl overflow-hidden" >
18+ <LivePulseChart
19+ :events =" events"
20+ :filters =" filters"
21+ :time-range =" currentTimeRange"
22+ @update-unique-apps =" uniqueAppNames = $event"
23+ @update-all-apps =" allAppNames = $event"
24+ @update-time-range =" currentTimeRange = $event"
25+ @update-heat-level =" heatLevel = $event"
26+ @update-events-per-minute =" eventsPerMinute = $event"
27+ @update-agent-counts =" agentCounts = $event"
28+ @clear-events =" handleClearClick"
29+ @toggle-filters =" showFilters = !showFilters"
30+ />
31+ </div >
32+ </div >
33+
34+ <!-- Agent Swim Lane Container - Glass Panel -->
35+ <div v-if =" selectedAgentLanes.length > 0" class =" mx-4 mt-4 mobile:mx-2 mobile:mt-2" >
36+ <div class =" glass-panel rounded-2xl p-4 mobile:p-2" >
37+ <AgentSwimLaneContainer
38+ :selected-agents =" selectedAgentLanes"
39+ :events =" events"
40+ :time-range =" currentTimeRange"
41+ @update:selected-agents =" selectedAgentLanes = $event"
42+ />
43+ </div >
44+ </div >
45+
46+ <!-- Timeline - Glass Panel -->
47+ <div class =" flex flex-col flex-1 overflow-hidden mx-4 my-4 mobile:mx-2 mobile:my-2" >
48+ <div class =" glass-panel rounded-2xl flex-1 overflow-hidden" >
49+ <EventTimeline
50+ :events =" events"
51+ :filters =" filters"
52+ :unique-app-names =" uniqueAppNames"
53+ :all-app-names =" allAppNames"
54+ :heat-level =" heatLevel"
55+ :agent-counts =" agentCounts"
56+ :events-per-minute =" eventsPerMinute"
57+ :time-range =" currentTimeRange"
58+ :time-ranges =" ['1M', '2M', '4M', '8M', '16M']"
59+ v-model:stick-to-bottom =" stickToBottom"
60+ @select-agent =" toggleAgentLane"
61+ @set-time-range =" setTimeRangeFromTimeline"
62+ />
63+ </div >
64+ </div >
65+
66+ <!-- Stick to bottom button - Glass styling -->
67+ <StickScrollButton
68+ class =" short:hidden glass-button"
69+ :stick-to-bottom =" stickToBottom"
70+ @toggle =" stickToBottom = !stickToBottom"
71+ />
72+
73+ <!-- Error message - Glass styling -->
74+ <div
75+ v-if =" error"
76+ class =" fixed bottom-6 left-6 mobile:bottom-4 mobile:left-4 mobile:right-4 glass-panel-elevated rounded-xl px-4 py-3 mobile:px-3 mobile:py-2 border-l-4 border-[var(--theme-accent-error)]"
77+ >
78+ <span class =" text-[var(--theme-accent-error)] font-medium" >{{ error }}</span >
79+ </div >
80+
81+ <!-- Theme Manager -->
82+ <ThemeManager
83+ :is-open =" showThemeManager"
84+ @close =" showThemeManager = false"
85+ />
86+
87+ <!-- Toast Notifications -->
88+ <ToastNotification
89+ v-for =" (toast, index) in toasts"
90+ :key =" toast.id"
91+ :index =" index"
92+ :agent-name =" toast.agentName"
93+ :agent-color =" toast.agentColor"
94+ @dismiss =" dismissToast(toast.id)"
95+ />
96+ </div >
97+ </template >
98+
99+ <script setup lang="ts">
100+ import { ref , watch } from ' vue' ;
101+ import type { TimeRange } from ' ./types' ;
102+ import { useWebSocket } from ' ./composables/useWebSocket' ;
103+ import { useThemes } from ' ./composables/useThemes' ;
104+ import { useEventColors } from ' ./composables/useEventColors' ;
105+ import EventTimeline from ' ./components/EventTimeline.vue' ;
106+ import FilterPanel from ' ./components/FilterPanel.vue' ;
107+ import StickScrollButton from ' ./components/StickScrollButton.vue' ;
108+ import LivePulseChart from ' ./components/LivePulseChart.vue' ;
109+ import ThemeManager from ' ./components/ThemeManager.vue' ;
110+ import ToastNotification from ' ./components/ToastNotification.vue' ;
111+ import AgentSwimLaneContainer from ' ./components/AgentSwimLaneContainer.vue' ;
112+
113+ // WebSocket connection
114+ const { events, isConnected, error, clearEvents } = useWebSocket (' ws://localhost:4000/stream' );
115+
116+ // Theme management (sets up theme system)
117+ useThemes ();
118+
119+ // Event colors
120+ const { getHexColorForApp } = useEventColors ();
121+
122+ // Filters
123+ const filters = ref ({
124+ sourceApp: ' ' ,
125+ sessionId: ' ' ,
126+ eventType: ' '
127+ });
128+
129+ // UI state
130+ const stickToBottom = ref (true );
131+ const showThemeManager = ref (false );
132+ const showFilters = ref (false );
133+ const uniqueAppNames = ref <string []>([]); // Apps active in current time window
134+ const allAppNames = ref <string []>([]); // All apps ever seen in session
135+ const selectedAgentLanes = ref <string []>([]);
136+ const currentTimeRange = ref <TimeRange >(' 1M' ); // Current time range from LivePulseChart
137+ const heatLevel = ref ({ intensity: 0 , color: ' #565f89' , label: ' Low' });
138+ const agentCounts = ref <Record <string , number >>({});
139+ const eventsPerMinute = ref (0 );
140+
141+ // Toast notifications
142+ interface Toast {
143+ id: number ;
144+ agentName: string ;
145+ agentColor: string ;
146+ }
147+ const toasts = ref <Toast []>([]);
148+ let toastIdCounter = 0 ;
149+ const seenAgents = new Set <string >();
150+
151+ // Watch for new agents and show toast
152+ watch (uniqueAppNames , (newAppNames ) => {
153+ // Find agents that are new (not in seenAgents set)
154+ newAppNames .forEach (appName => {
155+ if (! seenAgents .has (appName )) {
156+ seenAgents .add (appName );
157+ // Show toast for new agent
158+ const toast: Toast = {
159+ id: toastIdCounter ++ ,
160+ agentName: appName ,
161+ agentColor: getHexColorForApp (appName )
162+ };
163+ toasts .value .push (toast );
164+ }
165+ });
166+ }, { deep: true });
167+
168+ const dismissToast = (id : number ) => {
169+ const index = toasts .value .findIndex (t => t .id === id );
170+ if (index !== - 1 ) {
171+ toasts .value .splice (index , 1 );
172+ }
173+ };
174+
175+ // Handle agent tag clicks for swim lanes
176+ const toggleAgentLane = (agentName : string ) => {
177+ const index = selectedAgentLanes .value .indexOf (agentName );
178+ if (index >= 0 ) {
179+ // Remove from comparison
180+ selectedAgentLanes .value .splice (index , 1 );
181+ } else {
182+ // Add to comparison
183+ selectedAgentLanes .value .push (agentName );
184+ }
185+ };
186+
187+ // Handle clear button click
188+ const handleClearClick = () => {
189+ clearEvents ();
190+ selectedAgentLanes .value = [];
191+ };
192+
193+ // Handle time range change from EventTimeline
194+ const setTimeRangeFromTimeline = (range : TimeRange ) => {
195+ currentTimeRange .value = range ;
196+ };
197+
198+ // Debug handler for theme manager
199+ const handleThemeManagerClick = () => {
200+ console .log (' Theme manager button clicked!' );
201+ showThemeManager .value = true ;
202+ };
203+ </script >
0 commit comments