1.0.9
This commit is contained in:
1 parent
62636426bf
commit
c185d00dc3
60 files changed
+2830
-918
No files matched your search
@@ -74,19 +74,26 @@ function createDashboardDataStore() {
|
||||
lastFetchTime: Date.now()
|
||||
}));
|
||||
},
|
||||
// Partial update for progressive loading - merges into existing stats
|
||||
// Partial update for progressive loading - deep merges into existing stats
|
||||
// This preserves nested object properties (like containers.pendingUpdates)
|
||||
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
|
||||
// Deep merge: for nested objects, merge properties instead of replacing
|
||||
const mergedStats = { ...t.stats };
|
||||
for (const [key, value] of Object.entries(partialStats)) {
|
||||
const existing = (mergedStats as any)[key];
|
||||
// Deep merge for plain objects (not arrays or null)
|
||||
if (existing && typeof existing === 'object' && !Array.isArray(existing) &&
|
||||
value && typeof value === 'object' && !Array.isArray(value)) {
|
||||
(mergedStats as any)[key] = { ...existing, ...value };
|
||||
} else if (value !== undefined) {
|
||||
(mergedStats as any)[key] = value;
|
||||
}
|
||||
};
|
||||
}
|
||||
return { ...t, stats: mergedStats };
|
||||
}
|
||||
return t;
|
||||
}),
|
||||
|
||||
@@ -88,6 +88,7 @@ export function appendEnvParam(url: string, envId: number | null | undefined): s
|
||||
// Store for environments list with auto-refresh capability
|
||||
function createEnvironmentsStore() {
|
||||
const { subscribe, set, update } = writable<Environment[]>([]);
|
||||
const loaded = writable<boolean>(false); // Tracks if environments have been fetched at least once
|
||||
let loading = false;
|
||||
|
||||
async function fetchEnvironments() {
|
||||
@@ -98,6 +99,7 @@ function createEnvironmentsStore() {
|
||||
if (response.ok) {
|
||||
const data: Environment[] = await response.json();
|
||||
set(data);
|
||||
loaded.set(true);
|
||||
|
||||
// Auto-select environment if none selected or current one no longer exists
|
||||
const current = get(currentEnvironment);
|
||||
@@ -133,6 +135,7 @@ function createEnvironmentsStore() {
|
||||
} else {
|
||||
// Clear environments on permission denied or other errors
|
||||
set([]);
|
||||
loaded.set(true); // Mark as loaded even on error - we've completed the fetch
|
||||
// Also clear the current environment from localStorage
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
currentEnvironment.set(null);
|
||||
@@ -140,6 +143,7 @@ function createEnvironmentsStore() {
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch environments:', error);
|
||||
set([]);
|
||||
loaded.set(true); // Mark as loaded even on error - we've completed the fetch
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
currentEnvironment.set(null);
|
||||
} finally {
|
||||
@@ -156,7 +160,8 @@ function createEnvironmentsStore() {
|
||||
subscribe,
|
||||
refresh: fetchEnvironments,
|
||||
set,
|
||||
update
|
||||
update,
|
||||
loaded // Expose the loaded store for consumers to know when first fetch is complete
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
import { writable, get } from 'svelte/store';
|
||||
import { currentEnvironment, appendEnvParam } from './environment';
|
||||
|
||||
export interface ContainerStats {
|
||||
id: string;
|
||||
name: string;
|
||||
cpuPercent: number;
|
||||
memoryUsage: number;
|
||||
memoryLimit: number;
|
||||
memoryPercent: number;
|
||||
}
|
||||
|
||||
export interface HostInfo {
|
||||
hostname: string;
|
||||
ipAddress: string;
|
||||
platform: string;
|
||||
arch: string;
|
||||
cpus: number;
|
||||
totalMemory: number;
|
||||
freeMemory: number;
|
||||
uptime: number;
|
||||
dockerVersion: string;
|
||||
dockerContainers: number;
|
||||
dockerContainersRunning: number;
|
||||
dockerImages: number;
|
||||
}
|
||||
|
||||
export interface HostMetric {
|
||||
cpu_percent: number;
|
||||
memory_percent: number;
|
||||
memory_used: number;
|
||||
memory_total: number;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
// Historical data settings
|
||||
const MAX_HISTORY = 60; // 10 minutes at 10s intervals (server collects every 10s)
|
||||
const POLL_INTERVAL = 5000; // 5 seconds
|
||||
|
||||
// Stores
|
||||
export const cpuHistory = writable<number[]>([]);
|
||||
export const memoryHistory = writable<number[]>([]);
|
||||
export const containerStats = writable<ContainerStats[]>([]);
|
||||
export const hostInfo = writable<HostInfo | null>(null);
|
||||
export const lastUpdated = writable<Date>(new Date());
|
||||
export const isCollecting = writable<boolean>(false);
|
||||
|
||||
let pollInterval: ReturnType<typeof setInterval> | null = null;
|
||||
let envId: number | null = null;
|
||||
let initialFetchDone = false;
|
||||
|
||||
// Subscribe to environment changes
|
||||
currentEnvironment.subscribe((env) => {
|
||||
envId = env?.id ?? null;
|
||||
// Reset history when environment changes
|
||||
if (initialFetchDone) {
|
||||
cpuHistory.set([]);
|
||||
memoryHistory.set([]);
|
||||
initialFetchDone = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Helper for fetch with timeout
|
||||
async function fetchWithTimeout(url: string, timeout = 5000): Promise<any> {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||
try {
|
||||
const response = await fetch(url, { signal: controller.signal });
|
||||
clearTimeout(timeoutId);
|
||||
return response.json();
|
||||
} catch {
|
||||
clearTimeout(timeoutId);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchStats() {
|
||||
// Don't fetch if no environment is selected
|
||||
if (!envId) return;
|
||||
|
||||
// Fire all fetches independently - don't block on slow ones
|
||||
fetchWithTimeout(appendEnvParam('/api/containers/stats?limit=5', envId), 5000).then(data => {
|
||||
if (Array.isArray(data)) {
|
||||
containerStats.set(data);
|
||||
}
|
||||
});
|
||||
|
||||
fetchWithTimeout(appendEnvParam('/api/host', envId), 5000).then(data => {
|
||||
if (data && !data.error) {
|
||||
hostInfo.set(data);
|
||||
}
|
||||
});
|
||||
|
||||
fetchWithTimeout(appendEnvParam('/api/metrics?limit=60', envId), 5000).then(data => {
|
||||
if (data?.metrics && data.metrics.length > 0) {
|
||||
const metrics: HostMetric[] = data.metrics;
|
||||
const cpuValues = metrics.map(m => m.cpu_percent);
|
||||
const memValues = metrics.map(m => m.memory_percent);
|
||||
|
||||
cpuHistory.set(cpuValues.slice(-MAX_HISTORY));
|
||||
memoryHistory.set(memValues.slice(-MAX_HISTORY));
|
||||
initialFetchDone = true;
|
||||
}
|
||||
});
|
||||
|
||||
lastUpdated.set(new Date());
|
||||
}
|
||||
|
||||
export function startStatsCollection() {
|
||||
if (pollInterval) return; // Already running
|
||||
|
||||
isCollecting.set(true);
|
||||
fetchStats(); // Initial fetch
|
||||
pollInterval = setInterval(fetchStats, POLL_INTERVAL);
|
||||
}
|
||||
|
||||
export function stopStatsCollection() {
|
||||
if (pollInterval) {
|
||||
clearInterval(pollInterval);
|
||||
pollInterval = null;
|
||||
}
|
||||
isCollecting.set(false);
|
||||
}
|
||||
|
||||
// Get current values
|
||||
export function getCurrentCpu(): number {
|
||||
const history = get(cpuHistory);
|
||||
return history.length > 0 ? history[history.length - 1] : 0;
|
||||
}
|
||||
|
||||
export function getCurrentMemory(): number {
|
||||
const history = get(memoryHistory);
|
||||
return history.length > 0 ? history[history.length - 1] : 0;
|
||||
}
|
||||
Reference in new issue
Block a user