Security (backend): - env/routes: add requireAdmin to all 6 env endpoints — GET /env was fully open, exposing all secret values to unauthenticated requests - deployments/routes: add requireAdmin to all 4 GET endpoints (deployment history and logs were publicly readable) - health/routes: remove duplicate requireAdmin call from DELETE /health/cache handler body (was already enforced via preHandler) Frontend — auth/api: - system/page: replace raw fetch + localStorage token with apiRequest (mutations now go through CSRF flow) - vm/page: same — replace raw fetch with vmApi from api.ts - api.ts: add vmApi (getHealth, getCleanupLog, runCleanup) + shared VmHealthResult / VmCheck / VmCheckLevel types Shared utilities: - utils.ts: add formatBytes() and getStatusColor() shared helpers - system/page: remove duplicate formatBytes, import from utils - health/page: remove duplicate getStatusColor, import from utils - page.tsx (home): remove duplicate getStatusColor, import from utils UX improvements: - page.tsx: remove Seed Services button from normal header (debug tool) - page.tsx: deploy button now always enabled; shows inline warning banner when service is not 'up' instead of silently disabling the button - metrics: fix bar chart — bars now grow from bottom (flex-col-reverse), add empty state, fix date parsing timezone edge case - sidebar-nav: theme toggle now functional — persists to localStorage and toggles document.documentElement class 'dark' Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
33 lines
1021 B
TypeScript
33 lines
1021 B
TypeScript
import { clsx, type ClassValue } from 'clsx';
|
|
import { twMerge } from 'tailwind-merge';
|
|
|
|
export function cn(...inputs: ClassValue[]) {
|
|
return twMerge(clsx(inputs));
|
|
}
|
|
|
|
/** Format bytes into a human-readable string (B / KB / MB / GB / TB). */
|
|
export function formatBytes(bytes: number): string {
|
|
if (bytes === 0) return '0 B';
|
|
const k = 1024;
|
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
|
|
}
|
|
|
|
/** Tailwind classes for a service/deployment status badge. */
|
|
export function getStatusColor(status: string): string {
|
|
switch (status) {
|
|
case 'up':
|
|
case 'success':
|
|
return 'text-green-600 bg-green-50 border-green-200';
|
|
case 'down':
|
|
case 'failed':
|
|
return 'text-red-600 bg-red-50 border-red-200';
|
|
case 'degraded':
|
|
case 'running':
|
|
return 'text-yellow-600 bg-yellow-50 border-yellow-200';
|
|
default:
|
|
return 'text-gray-600 bg-gray-50 border-gray-200';
|
|
}
|
|
}
|