Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .vscode/mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"servers": {
"shadcn": {
"command": "npx",
"args": [
"shadcn@latest",
"mcp"
]
},
"next-devtools": {
"command": "npx",
"args": [
"-y",
"next-devtools-mcp@latest"
]
}
}
}
89 changes: 0 additions & 89 deletions src/app/api/app-status/route.ts

This file was deleted.

61 changes: 0 additions & 61 deletions src/app/api/deployment-status/actions.ts

This file was deleted.

114 changes: 114 additions & 0 deletions src/app/api/deployment-status/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import k3s from "@/server/adapter/kubernetes-api.adapter";
import deploymentLiveStatusService from "@/server/services/deployment-live-status.service";
import { getAuthUserSession, simpleRoute } from "@/server/utils/action-wrapper.utils";
import { V1Deployment } from "@kubernetes/client-node";
import * as k8s from '@kubernetes/client-node';

// Prevents this route's response from being cached
export const dynamic = "force-dynamic";

export async function POST(request: Request) {
return simpleRoute(async () => {

const session = await getAuthUserSession();

const encoder = new TextEncoder();
let shouldStopStreaming = false;
let watchRequest: { abort: () => void } | null = null;

// Fetch all projects and apps to build a lookup map
let appLookup = await deploymentLiveStatusService.getAppLookup(session);

const customReadable = new ReadableStream({
async start(controller) {

const sendData = (data: any) => {
if (shouldStopStreaming) return;
try {
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
} catch (e) {
console.error(`[ENQUEUE ERROR] Error while enqueueing Deployment Status data: `, e);
shouldStopStreaming = true;
controller.close();
}
};

// 1. Send initial state
try {
const initialStatus = await deploymentLiveStatusService.getInitialStatus(appLookup);
sendData(initialStatus);
} catch (e) {
console.error("Error fetching initial status", e);
}

// 2. Watch for changes
const kc = k3s.getKubeConfig();
const watch = new k8s.Watch(kc);
console.log("[START] Starting watch for deployments ");
watchRequest = await watch.watch(
'/apis/apps/v1/deployments',
{},
async (type, apiObj, watchObj) => {
if (shouldStopStreaming) { return; }

const deployment = apiObj as V1Deployment;
const appId = deployment.metadata?.name;
const projectId = deployment.metadata?.namespace;

if (!appId || !projectId) { return; }

// ignore system namespaces
if (['default', 'longhorn-system', 'kube-public', 'kube-system', 'cert-manager'].includes(projectId)) { return; }

// If a new deployment is detected (ADDED) and we don't know about it,
// it might be a newly created app. Refresh the lookup.
if (type === 'ADDED' && !appLookup.has(appId)) {
console.log(`[LiveStatus] New unknown deployment detected for ${appId}, refreshing app lookup`);
appLookup = await deploymentLiveStatusService.getAppLookup(session);
}

const appInfo = appLookup.get(appId);
if (!appInfo) {
return;
}

// Verify namespace matches project ID
if (appInfo.projectId !== projectId) { return; }

let status;
if (type === 'DELETED') {
status = deploymentLiveStatusService.mapDeploymentToStatus(appId, appInfo, undefined);
} else {
status = deploymentLiveStatusService.mapDeploymentToStatus(appId, appInfo, deployment);
}

sendData(status);
},
(err) => {
if (err) console.error('Deploy watch error', err);
console.log('Deploy watch ended');
if (!shouldStopStreaming) {
controller.close();
}
}
);
},
cancel() {
console.log("[LEAVE] Cancelling informer for deployments");
shouldStopStreaming = true;
if (watchRequest && typeof watchRequest.abort === 'function') {
watchRequest.abort();
}
}
});

return new Response(customReadable, {
headers: {
Connection: "keep-alive",
"Content-Encoding": "none",
"Cache-Control": "no-cache, no-transform",
"Content-Type": "text/event-stream; charset=utf-8",
},
});
});
}
12 changes: 11 additions & 1 deletion src/app/monitoring/app-monitoring.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import Link from 'next/link';
import { Button } from '@/components/ui/button';
import { AppMonitoringUsageModel } from '@/shared/model/app-monitoring-usage.model';
import PodStatusIndicator from '@/components/custom/pod-status-indicator';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';

export default function AppRessourceMonitoring({
appsRessourceUsage
Expand Down Expand Up @@ -84,7 +85,16 @@ export default function AppRessourceMonitoring({
<TableCell>{item.projectName}</TableCell>
<TableCell>{item.appName}</TableCell>
<TableCell>
<span className='font-semibold'>{item.cpuUsagePercent.toFixed(3)}%</span> / {item.cpuUsage.toFixed(5)} Cores
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span className='font-semibold'>{item.cpuUsagePercent.toFixed(3)}%</span>
</TooltipTrigger>
<TooltipContent>
<p>{item.cpuUsage.toFixed(5)} Cores</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</TableCell>
<TableCell>{KubeSizeConverter.convertBytesToReadableSize(item.ramUsageBytes)}</TableCell>
<TableCell>
Expand Down
Loading