| import { create } from "zustand"; |
| import type { |
| Profile, |
| Instance, |
| InstanceTab, |
| Agent, |
| ActivityEvent, |
| Settings, |
| } from "../generated/types"; |
| import type { DashboardServerInfo, MonitoringSnapshot } from "../types"; |
|
|
| export interface TabDataPoint { |
| timestamp: number; |
| [instanceId: string]: number; |
| } |
|
|
| export interface MemoryDataPoint { |
| timestamp: number; |
| [instanceId: string]: number; |
| } |
|
|
| export interface ServerDataPoint { |
| timestamp: number; |
| goHeapMB: number; |
| goroutines: number; |
| rateBucketHosts: number; |
| } |
|
|
| interface AppState { |
| |
| profiles: Profile[]; |
| profilesLoading: boolean; |
| setProfiles: (profiles: Profile[]) => void; |
| setProfilesLoading: (loading: boolean) => void; |
|
|
| |
| instances: Instance[]; |
| instancesLoading: boolean; |
| setInstances: (instances: Instance[]) => void; |
| setInstancesLoading: (loading: boolean) => void; |
|
|
| |
| tabsChartData: TabDataPoint[]; |
| memoryChartData: MemoryDataPoint[]; |
| serverChartData: ServerDataPoint[]; |
| currentTabs: Record<string, InstanceTab[]>; |
| currentMemory: Record<string, number>; |
| addChartDataPoint: (point: TabDataPoint) => void; |
| addMemoryDataPoint: (point: MemoryDataPoint) => void; |
| addServerDataPoint: (point: ServerDataPoint) => void; |
| setCurrentTabs: (tabs: Record<string, InstanceTab[]>) => void; |
| setCurrentMemory: (memory: Record<string, number>) => void; |
| applyMonitoringSnapshot: ( |
| snapshot: MonitoringSnapshot, |
| includeMemory: boolean, |
| ) => void; |
|
|
| |
| agents: Agent[]; |
| selectedAgentId: string | null; |
| setAgents: (agents: Agent[]) => void; |
| setSelectedAgentId: (id: string | null) => void; |
|
|
| |
| events: ActivityEvent[]; |
| eventFilter: string; |
| addEvent: (event: ActivityEvent) => void; |
| setEventFilter: (filter: string) => void; |
| clearEvents: () => void; |
|
|
| |
| settings: Settings; |
| setSettings: (settings: Settings) => void; |
|
|
| |
| serverInfo: DashboardServerInfo | null; |
| setServerInfo: (info: DashboardServerInfo | null) => void; |
| } |
|
|
| const defaultSettings: Settings = { |
| screencast: { fps: 1, quality: 30, maxWidth: 800 }, |
| stealth: "light", |
| browser: { blockImages: false, blockMedia: false, noAnimations: false }, |
| monitoring: { memoryMetrics: false, pollInterval: 30 }, |
| }; |
|
|
| const SETTINGS_KEY = "pinchtab_settings"; |
|
|
| function loadSettings(): Settings { |
| try { |
| const saved = localStorage.getItem(SETTINGS_KEY); |
| if (saved) { |
| return { ...defaultSettings, ...JSON.parse(saved) }; |
| } |
| } catch { |
| |
| } |
| return defaultSettings; |
| } |
|
|
| function saveSettings(settings: Settings) { |
| try { |
| localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); |
| } catch { |
| |
| } |
| } |
|
|
| export const useAppStore = create<AppState>((set) => ({ |
| |
| profiles: [], |
| profilesLoading: false, |
| setProfiles: (profiles) => set({ profiles }), |
| setProfilesLoading: (profilesLoading) => set({ profilesLoading }), |
|
|
| |
| instances: [], |
| instancesLoading: false, |
| setInstances: (instances) => set({ instances }), |
| setInstancesLoading: (instancesLoading) => set({ instancesLoading }), |
|
|
| |
| tabsChartData: [], |
| memoryChartData: [], |
| serverChartData: [], |
| currentTabs: {}, |
| currentMemory: {}, |
| addChartDataPoint: (point) => |
| set((state) => ({ |
| tabsChartData: [...state.tabsChartData.slice(-59), point], |
| })), |
| addMemoryDataPoint: (point) => |
| set((state) => ({ |
| memoryChartData: [...state.memoryChartData.slice(-59), point], |
| })), |
| addServerDataPoint: (point) => |
| set((state) => ({ |
| serverChartData: [...state.serverChartData.slice(-59), point], |
| })), |
| setCurrentTabs: (currentTabs) => set({ currentTabs }), |
| setCurrentMemory: (currentMemory) => set({ currentMemory }), |
| applyMonitoringSnapshot: (snapshot, includeMemory) => |
| set((state) => { |
| const runningInstances = snapshot.instances.filter( |
| (instance) => instance?.status === "running", |
| ); |
| const tabDataPoint: TabDataPoint = { timestamp: snapshot.timestamp }; |
| const memDataPoint: MemoryDataPoint = { timestamp: snapshot.timestamp }; |
| const currentTabs: Record<string, InstanceTab[]> = {}; |
| const currentMemory: Record<string, number> = {}; |
|
|
| for (const instance of runningInstances) { |
| const instanceTabs = snapshot.tabs.filter( |
| (tab) => tab.instanceId === instance.id, |
| ); |
| tabDataPoint[instance.id] = instanceTabs.length; |
| currentTabs[instance.id] = instanceTabs; |
|
|
| if (includeMemory) { |
| const metrics = snapshot.metrics.find( |
| (entry) => entry.instanceId === instance.id, |
| ); |
| if (metrics) { |
| memDataPoint[instance.id] = metrics.jsHeapUsedMB; |
| currentMemory[instance.id] = metrics.jsHeapUsedMB; |
| } |
| } |
| } |
|
|
| return { |
| instances: snapshot.instances, |
| currentTabs, |
| currentMemory, |
| tabsChartData: |
| runningInstances.length > 0 |
| ? [...state.tabsChartData.slice(-59), tabDataPoint] |
| : state.tabsChartData, |
| memoryChartData: |
| includeMemory && runningInstances.length > 0 |
| ? [...state.memoryChartData.slice(-59), memDataPoint] |
| : state.memoryChartData, |
| serverChartData: [ |
| ...state.serverChartData.slice(-59), |
| { |
| timestamp: snapshot.timestamp, |
| goHeapMB: snapshot.serverMetrics.goHeapAllocMB, |
| goroutines: snapshot.serverMetrics.goNumGoroutine, |
| rateBucketHosts: snapshot.serverMetrics.rateBucketHosts, |
| }, |
| ], |
| }; |
| }), |
|
|
| |
| agents: [], |
| selectedAgentId: null, |
| setAgents: (agents) => set({ agents }), |
| setSelectedAgentId: (selectedAgentId) => set({ selectedAgentId }), |
|
|
| |
| events: [], |
| eventFilter: "all", |
| addEvent: (event) => |
| set((state) => ({ events: [event, ...state.events].slice(0, 100) })), |
| setEventFilter: (eventFilter) => set({ eventFilter }), |
| clearEvents: () => set({ events: [] }), |
|
|
| |
| settings: loadSettings(), |
| setSettings: (settings) => { |
| saveSettings(settings); |
| set({ settings }); |
| }, |
|
|
| |
| serverInfo: null, |
| setServerInfo: (serverInfo) => set({ serverInfo }), |
| })); |
|
|