mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-04 14:45:41 +00:00
feat(dashboard): replace 24h charts with Configuration Status and Recent Activity (#785)
* feat(dashboard): replace 24h charts with Configuration Status and Recent Activity The 24-hour CPU/Memory area charts summed per-container metrics normalized to each container's CPU quota, producing numbers that bore no honest relationship to host load. The live ResourceGauges strip already shows accurate host-level stats, making the historical charts both inaccurate and redundant. This commit replaces that row with two side-by-side cards: - **Configuration Status**: aggregates every toggleable feature on the active node (notification agents, alert rules, routing rules, auto-heal, auto-update, webhooks, scheduled tasks, MFA, SSO, vulnerability scanning, cloud backup, and alert thresholds) into a single at-a-glance card. Tier-locked rows display an upgrade indicator instead of a value. Each row is clickable and navigates to the relevant settings section. Data refreshes every 60 s and immediately on state-invalidate events. - **Recent Activity**: lists the ten most recent notification-history events for the active node (deployments, image updates, auto-heal actions, scan findings, cloud backup events, system notices) with category icons and relative timestamps. Refreshes every 30 s. New backend endpoints: - GET /api/dashboard/configuration - per-node feature status with locked/ requiredTier markers so the frontend renders upgrade chips without extra calls. The endpoint sits after authGate and before the remote proxy so remote-node requests are transparently forwarded. - GET /api/dashboard/recent-activity?limit=N - thin wrapper over DatabaseService.getNotificationHistory. - GET /api/fleet/configuration - fleet-wide fan-out using the same Promise.allSettled dead-node-tolerant pattern as /fleet/overview. Exposed as the new "Status" tab on the Fleet page (after Snapshots). Shared utilities: - visibilityInterval and formatCount extracted to frontend/src/lib/utils.ts so the three polling hooks and two components share a single copy. * docs(dashboard): fix stale alt text referencing removed historical charts
This commit is contained in:
@@ -22,6 +22,8 @@ import { isDebugEnabled } from '../utils/debug';
|
||||
import { getErrorMessage } from '../utils/errors';
|
||||
import { CloudBackupService } from '../services/CloudBackupService';
|
||||
import { NotificationService } from '../services/NotificationService';
|
||||
import { buildLocalConfigurationStatus, type ConfigurationStatus } from './dashboard';
|
||||
import { LicenseService } from '../services/LicenseService';
|
||||
|
||||
const updateTracker = FleetUpdateTrackerService.getInstance();
|
||||
const UPDATE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
||||
@@ -329,6 +331,70 @@ fleetRouter.get('/overview', authMiddleware, async (_req: Request, res: Response
|
||||
}
|
||||
});
|
||||
|
||||
interface FleetNodeConfiguration {
|
||||
id: number;
|
||||
name: string;
|
||||
type: 'local' | 'remote';
|
||||
status: 'online' | 'offline';
|
||||
configuration: ConfigurationStatus | null;
|
||||
}
|
||||
|
||||
fleetRouter.get('/configuration', authMiddleware, async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const db = DatabaseService.getInstance();
|
||||
const nodes = db.getNodes();
|
||||
const userId = req.user?.userId ?? 0;
|
||||
const localTier = LicenseService.getInstance().getTier();
|
||||
const localVariant = LicenseService.getInstance().getVariant();
|
||||
|
||||
const results = await Promise.allSettled(
|
||||
nodes.map(async (node: Node): Promise<FleetNodeConfiguration> => {
|
||||
if (node.type === 'local') {
|
||||
return {
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
type: 'local',
|
||||
status: 'online',
|
||||
configuration: buildLocalConfigurationStatus(node.id, userId, localTier, localVariant),
|
||||
};
|
||||
}
|
||||
|
||||
if (!node.api_url || !node.api_token) {
|
||||
return { id: node.id, name: node.name, type: 'remote', status: 'offline', configuration: null };
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await fetch(
|
||||
`${node.api_url.replace(/\/$/, '')}/api/dashboard/configuration`,
|
||||
{ headers: { Authorization: `Bearer ${node.api_token}` }, signal: AbortSignal.timeout(10000) },
|
||||
);
|
||||
const configuration = resp.ok ? (await resp.json() as ConfigurationStatus) : null;
|
||||
return {
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
type: 'remote',
|
||||
status: configuration ? 'online' : 'offline',
|
||||
configuration,
|
||||
};
|
||||
} catch {
|
||||
return { id: node.id, name: node.name, type: 'remote', status: 'offline', configuration: null };
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const fleet: FleetNodeConfiguration[] = results.map((result, i) => {
|
||||
if (result.status === 'fulfilled') return result.value;
|
||||
console.error(`[Fleet] Configuration fetch failed for node ${nodes[i].name}:`, result.reason);
|
||||
return { id: nodes[i].id, name: nodes[i].name, type: nodes[i].type, status: 'offline', configuration: null };
|
||||
});
|
||||
|
||||
res.json(fleet);
|
||||
} catch (error) {
|
||||
console.error('[Fleet] Configuration overview error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch fleet configuration' });
|
||||
}
|
||||
});
|
||||
|
||||
fleetRouter.get('/node/:nodeId/stacks', authMiddleware, async (req: Request, res: Response): Promise<void> => {
|
||||
if (!requirePaid(req, res)) return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user