+
+ {/* ── Header ── */}
+
+
+ Attack Timeline
+
+ {loading ? 'loading…' : `${filtered.length} tasks · ${tracks.length} callbacks`}
+
+
+
+
{ setFilter(e.target.value); setSelected(null) }}
+ />
+
+ {ZOOM_LEVELS.map(z => (
+
+ ))}
+
+
+
+
+ {/* ── Scroll area ── */}
+
+ {tracks.length === 0 && !loading && (
+
+ {filterLc ? `no tasks matching "${filter}"` : 'no tasks in this operation'}
+
+ )}
+
+ {tracks.length > 0 && (
+
+
+ {/* Time axis */}
+
+
+ time
+
+
+ {ticks.map(tick => (
+
+ ))}
+
+
+
+ {/* Callback swimlanes */}
+ {tracks.map(track => (
+
+
+
+ {track.host}
+ #{track.displayId}
+
+
+
+ {track.events.map(ev => (
+
+
+ ))}
+
+ )}
+
+
+ {/* ── Selected event detail bar ── */}
+ {selected && (
+
+
+
+ host
+ {selected.callback.host}
+
+
+ callback
+ #{selected.callback.display_id}
+
+
+ task
+ #{selected.display_id}
+
+
+ command
+
+ {selected.command_name}{selected.display_params ? ` ${selected.display_params}` : ''}
+
+
+
+ status
+
+ {selected.status}
+
+
+
+ time
+ {parseTs(selected.timestamp).toLocaleString()}
+
+
+ operator
+ {selected.operator.username}
+
+
+ )}
+
+ )
+}
diff --git a/src/store/index.ts b/src/store/index.ts
index 14f2cfd..abe5d46 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -108,7 +108,7 @@ export interface HecateStore {
setCallbacks: (cbs: Callback[]) => void
currentTasks: Task[]
setCurrentTasks: (tasks: Task[]) => void
- activeRailView: 'overview' | 'callbacks' | 'payloads' | 'services' | 'proxies' | 'credentials' | 'files' | 'attack' | 'logs' | 'report' | 'operations'
+ activeRailView: 'overview' | 'callbacks' | 'payloads' | 'services' | 'proxies' | 'credentials' | 'files' | 'attack' | 'logs' | 'report' | 'operations' | 'timeline'
setActiveRailView: (v: HecateStore['activeRailView']) => void
theme: 'dark' | 'light'
setTheme: (t: 'dark' | 'light') => void
diff --git a/src/views/Dashboard.tsx b/src/views/Dashboard.tsx
index 5dc112b..bb0040e 100644
--- a/src/views/Dashboard.tsx
+++ b/src/views/Dashboard.tsx
@@ -19,6 +19,7 @@ import { OperationsPanel } from '@/components/OperationsPanel/OperationsPane
import { CredentialsPanel } from '@/components/CredentialsPanel/CredentialsPanel'
import { EventLogPanel } from '@/components/EventLogPanel/EventLogPanel'
import { ProxiesPanel } from '@/components/ProxiesPanel/ProxiesPanel'
+import { TimelinePanel } from '@/components/TimelinePanel/TimelinePanel'
import { CallbackToastContainer } from '@/components/Toast/CallbackToast'
import { ProxyToastContainer } from '@/components/Toast/ProxyToast'
import { SettingsPanel } from '@/components/SettingsPanel/SettingsPanel'
@@ -160,7 +161,7 @@ export function Dashboard() {
useWarningBadge()
const activeRailView = useStore((s) => s.activeRailView)
- const isFullPanel = activeRailView === 'overview' || activeRailView === 'payloads' || activeRailView === 'services' || activeRailView === 'proxies' || activeRailView === 'report' || activeRailView === 'files' || activeRailView === 'operations' || activeRailView === 'credentials' || activeRailView === 'logs'
+ const isFullPanel = activeRailView === 'overview' || activeRailView === 'payloads' || activeRailView === 'services' || activeRailView === 'proxies' || activeRailView === 'report' || activeRailView === 'files' || activeRailView === 'operations' || activeRailView === 'credentials' || activeRailView === 'logs' || activeRailView === 'timeline'
return (
@@ -184,6 +185,7 @@ export function Dashboard() {
{activeRailView === 'operations' && }
{activeRailView === 'credentials' && }
{activeRailView === 'logs' && }
+ {activeRailView === 'timeline' && }
) : (
<>