mirror of
https://github.com/khoaliber/dockhand.git
synced 2026-03-07 13:22:54 +00:00
Initial commit
This commit is contained in:
209
lib/stores/dashboard.ts
Normal file
209
lib/stores/dashboard.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
import { writable, get } from 'svelte/store';
|
||||
import type { EnvironmentStats } from '../../routes/api/dashboard/stats/+server';
|
||||
|
||||
// Grid item layout format for svelte-grid
|
||||
export interface GridItem {
|
||||
id: number;
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
[key: string]: unknown; // Allow svelte-grid internal properties
|
||||
}
|
||||
|
||||
export interface DashboardPreferences {
|
||||
gridLayout: GridItem[];
|
||||
}
|
||||
|
||||
const defaultPreferences: DashboardPreferences = {
|
||||
gridLayout: []
|
||||
};
|
||||
|
||||
// Environment info from API
|
||||
interface EnvironmentInfo {
|
||||
id: number;
|
||||
name: string;
|
||||
host?: string;
|
||||
icon: string;
|
||||
socketPath?: string;
|
||||
connectionType?: 'socket' | 'direct' | 'hawser-standard' | 'hawser-edge';
|
||||
}
|
||||
|
||||
// Metrics history point for charts
|
||||
export interface MetricsHistoryPoint {
|
||||
cpu_percent: number;
|
||||
memory_percent: number;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
// Tile item combining environment info and stats
|
||||
export interface TileItem {
|
||||
id: number;
|
||||
stats: EnvironmentStats | null;
|
||||
info: EnvironmentInfo | null;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
// Dashboard data store for caching between navigations
|
||||
export interface DashboardData {
|
||||
tiles: TileItem[];
|
||||
gridItems: GridItem[];
|
||||
lastFetchTime: number | null;
|
||||
initialized: boolean;
|
||||
}
|
||||
|
||||
const defaultDashboardData: DashboardData = {
|
||||
tiles: [],
|
||||
gridItems: [],
|
||||
lastFetchTime: null,
|
||||
initialized: false
|
||||
};
|
||||
|
||||
function createDashboardDataStore() {
|
||||
const { subscribe, set, update } = writable<DashboardData>(defaultDashboardData);
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
setTiles: (tiles: TileItem[]) => {
|
||||
update(data => ({ ...data, tiles, lastFetchTime: Date.now() }));
|
||||
},
|
||||
updateTile: (id: number, updates: Partial<TileItem>) => {
|
||||
update(data => ({
|
||||
...data,
|
||||
tiles: data.tiles.map(t => t.id === id ? { ...t, ...updates } : t),
|
||||
lastFetchTime: Date.now()
|
||||
}));
|
||||
},
|
||||
// Partial update for progressive loading - merges into existing stats
|
||||
updateTilePartial: (id: number, partialStats: Partial<EnvironmentStats>) => {
|
||||
update(data => ({
|
||||
...data,
|
||||
tiles: data.tiles.map(t => {
|
||||
if (t.id === id && t.stats) {
|
||||
return {
|
||||
...t,
|
||||
stats: {
|
||||
...t.stats,
|
||||
...partialStats
|
||||
}
|
||||
};
|
||||
}
|
||||
return t;
|
||||
}),
|
||||
lastFetchTime: Date.now()
|
||||
}));
|
||||
},
|
||||
setGridItems: (gridItems: GridItem[]) => {
|
||||
update(data => ({ ...data, gridItems }));
|
||||
},
|
||||
setInitialized: (initialized: boolean) => {
|
||||
update(data => ({ ...data, initialized }));
|
||||
},
|
||||
markAllLoading: () => {
|
||||
update(data => ({
|
||||
...data,
|
||||
tiles: data.tiles.map(t => ({ ...t, loading: true }))
|
||||
}));
|
||||
},
|
||||
// Invalidate cache to force a fresh fetch on next dashboard visit
|
||||
// Clear tiles so dashboard starts fresh with new data
|
||||
invalidate: () => {
|
||||
update(data => ({
|
||||
...data,
|
||||
tiles: [],
|
||||
lastFetchTime: null,
|
||||
initialized: false
|
||||
}));
|
||||
},
|
||||
reset: () => set(defaultDashboardData),
|
||||
getData: () => get({ subscribe })
|
||||
};
|
||||
}
|
||||
|
||||
export const dashboardData = createDashboardDataStore();
|
||||
|
||||
// Number of columns in the grid
|
||||
export const GRID_COLS = 4;
|
||||
// Row height for tiles - compact tiles (h=1) show basic info, larger tiles show more
|
||||
// At height=2 (default), should fit: header, container counts, health, CPU/mem, resources, events
|
||||
export const GRID_ROW_HEIGHT = 175;
|
||||
|
||||
function createDashboardStore() {
|
||||
const { subscribe, set, update } = writable<DashboardPreferences>(defaultPreferences);
|
||||
let saveTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
let initialized = false;
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
const response = await fetch('/api/dashboard/preferences');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
// Handle migration from old format
|
||||
if (data.gridLayout && Array.isArray(data.gridLayout)) {
|
||||
set({ gridLayout: data.gridLayout });
|
||||
} else {
|
||||
set({ gridLayout: [] });
|
||||
}
|
||||
} else {
|
||||
set({ gridLayout: [] });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load dashboard preferences:', error);
|
||||
set({ gridLayout: [] });
|
||||
} finally {
|
||||
// Always mark as initialized so saves can proceed
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
async function save(prefs: DashboardPreferences) {
|
||||
try {
|
||||
await fetch('/api/dashboard/preferences', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(prefs)
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to save dashboard preferences:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Debounced save - auto-saves 500ms after last change
|
||||
function scheduleSave(prefs: DashboardPreferences) {
|
||||
if (saveTimeout) {
|
||||
clearTimeout(saveTimeout);
|
||||
}
|
||||
saveTimeout = setTimeout(() => {
|
||||
save(prefs);
|
||||
saveTimeout = null;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
load,
|
||||
setGridLayout: (layout: GridItem[]) => {
|
||||
update(prefs => {
|
||||
// Only keep essential properties to avoid storing internal svelte-grid state
|
||||
const cleanLayout = layout.map(item => ({
|
||||
id: item.id,
|
||||
x: item.x,
|
||||
y: item.y,
|
||||
w: item.w,
|
||||
h: item.h
|
||||
}));
|
||||
const newPrefs = { ...prefs, gridLayout: cleanLayout };
|
||||
if (initialized) {
|
||||
scheduleSave(newPrefs);
|
||||
}
|
||||
return newPrefs;
|
||||
});
|
||||
},
|
||||
reset: () => {
|
||||
initialized = false;
|
||||
set(defaultPreferences);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const dashboardPreferences = createDashboardStore();
|
||||
Reference in New Issue
Block a user