Skip to content

Commit

Permalink
Merge pull request #96 from EliasDeHondt/websockets
Browse files Browse the repository at this point in the history
Websockets
  • Loading branch information
EliasDeHondt authored Feb 27, 2025
2 parents 6cef251 + e19a684 commit 197c6f3
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 63 deletions.
48 changes: 48 additions & 0 deletions App/Backend/cmd/kubernetes/handlers/MetricsSocket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package handlers

import (
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"log"
"net/http"
"time"
)

var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
origin := r.Header.Get("Origin")
return origin == GetFrontendIP()
},
}

func HandleMetricsSocket(ctx *gin.Context) {
conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
if err != nil {
log.Println("Error upgrading metrics socket:", err)
return
}
defer func(conn *websocket.Conn) {
err := conn.Close()
if err != nil {
log.Println("Error closing metrics socket:", err)
}
}(conn)

for {
stats, err := c.GetTotalUsage()
if err != nil {
log.Println("Error getting metrics stats:", err)
return
}

err = conn.WriteJSON(stats)
if err != nil {
log.Println("Error writing metrics stats:", err)
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Println("WebSocket connection closed by client.")
}
break
}
time.Sleep(500 * time.Millisecond)
}
}
7 changes: 5 additions & 2 deletions App/Backend/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import (
"time"
)

var frontendUrl = handlers.GetFrontendIP()

func main() {
frontendUrl := handlers.GetFrontendIP()
trustedProxies := []string{"10.0.0.0/8"}
frontendUrl = handlers.GetFrontendIP()
trustedProxies := []string{"10.0.0.0/8"}

if frontendUrl == "http://localhost:4200" {
gin.SetMode(gin.DebugMode)
Expand Down Expand Up @@ -56,6 +58,7 @@ func main() {
secured.POST("/createresources", handlers.CreateResourcesHandler)
secured.GET("/namespaces", handlers.GetNamespacesHandler)
secured.GET("/nodenames", handlers.GetNodeNamesHandler)
secured.GET("/statsocket", handlers.HandleMetricsSocket)

err = r.Run(":8082")
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions App/Backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ require (
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
Expand Down
2 changes: 2 additions & 0 deletions App/Backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgY
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
Expand Down
46 changes: 22 additions & 24 deletions App/Frontend/src/app/dashboard/dashboard.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,33 +44,31 @@ <h2>{{ 'DASHBOARD.RAM' | translate }}</h2>
<h2>{{ 'DASHBOARD.DISK' | translate }}</h2>
<span class="dashboard-header-3">
<div class="progress-container">
<div class="progress-bar" [style.width.%]="diskUsagePercentage" [style.background-color]="diskColor"></div>
<div class="progress-bar" [style.width.%]="diskUsagePercentage"
[style.background-color]="diskColor"></div>
</div>
<p class="progress-text">
<ng-container *ngIf="usage; else loadingTemplate">
{{ usage.DiskUsage | byteFormat }} / {{ usage.DiskCapacity | byteFormat }}
<ng-container>
{{ diskUsage | byteFormat }} / {{ diskCapacity | byteFormat }}
</ng-container>
</p>
<ng-template #loadingTemplate>
<app-loading [size]="30"></app-loading>
</ng-template>
</span>
</article>
</section>
</header>
<app-nav class="nav" id="nav"></app-nav>
<main class="dashboard-main" id="dashboard-main" #dashboardMain>
<section class="dashboard-title" id="dashboard-title" #dashboardTitle>K10s</section>
<section class="dashboard-main-section" >
<article class="cursor-move" id="dashboard-main-chart">
<app-spider-web class="spider-web" id="spider-web"></app-spider-web>
</article>
</section>
<section class="dashboard-fullscreen-button cursor-pointer" id="dashboard-fullscreen-button">
<svg class="cursor-pointer" viewBox="0 0 16 16">
<path d="M14 3.414L9.414 8 14 12.586v-2.583h2V16h-6v-1.996h2.59L8 9.414l-4.59 4.59H6V16H0v-5.997h2v2.583L6.586 8 2 3.414v2.588H0V0h16v6.002h-2V3.414zm-1.415-1.413H10V0H6v2H3.415L8 6.586 12.585 2z" />
</svg>
</section>
</main>
<app-footer class="footer" id="footer"></app-footer>
</article>
</section>
</header>
<app-nav class="nav" id="nav"></app-nav>
<main class="dashboard-main" id="dashboard-main" #dashboardMain>
<section class="dashboard-title" id="dashboard-title" #dashboardTitle>K10s</section>
<section class="dashboard-main-section">
<article class="cursor-move" id="dashboard-main-chart">
<app-spider-web class="spider-web" id="spider-web"></app-spider-web>
</article>
</section>
<section class="dashboard-fullscreen-button cursor-pointer" id="dashboard-fullscreen-button">
<svg class="cursor-pointer" viewBox="0 0 16 16">
<path d="M14 3.414L9.414 8 14 12.586v-2.583h2V16h-6v-1.996h2.59L8 9.414l-4.59 4.59H6V16H0v-5.997h2v2.583L6.586 8 2 3.414v2.588H0V0h16v6.002h-2V3.414zm-1.415-1.413H10V0H6v2H3.415L8 6.586 12.585 2z"/>
</svg>
</section>
</main>
<app-footer class="footer" id="footer"></app-footer>
</body>
71 changes: 39 additions & 32 deletions App/Frontend/src/app/dashboard/dashboard.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,25 @@
/* @author K10s Open Source Team */
/**********************************/

import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import { NavComponent } from '../nav/nav.component';
import { FooterComponent } from "../footer/footer.component";
import { TranslatePipe } from "@ngx-translate/core";
import { StatsService } from "../services/stats.service";
import { ByteFormatPipe } from "../byte-format.pipe";
import { Color, NgxChartsModule, ScaleType } from "@swimlane/ngx-charts";
import { LoadingComponent } from "../loading/loading.component";
import { SpiderWebComponent } from "../spider-web/spider-web.component";
import {AfterViewInit, Component, ElementRef, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {NavComponent} from '../nav/nav.component';
import {FooterComponent} from "../footer/footer.component";
import {TranslatePipe} from "@ngx-translate/core";
import {ByteFormatPipe} from "../byte-format.pipe";
import {Color, NgxChartsModule, ScaleType} from "@swimlane/ngx-charts";
import {SpiderWebComponent} from "../spider-web/spider-web.component";
import {StatWebSocketService} from "../services/statWebsocket.service";
import {Metrics} from "../domain/Metrics";

@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css'],
imports: [NavComponent, FooterComponent, TranslatePipe, ByteFormatPipe, NgxChartsModule, SpiderWebComponent, LoadingComponent],
imports: [NavComponent, FooterComponent, TranslatePipe, ByteFormatPipe, NgxChartsModule, SpiderWebComponent],
standalone: true
})

export class DashboardComponent implements AfterViewInit, OnInit {
export class DashboardComponent implements AfterViewInit, OnInit, OnDestroy {
// Fullscreen button
@ViewChild('dashboardMain') dashboardMain!: ElementRef;
@ViewChild('dashboardTitle') dashboardTitle!: ElementRef;
Expand Down Expand Up @@ -58,12 +58,14 @@ export class DashboardComponent implements AfterViewInit, OnInit {
}

// get stats
usage: any = null;
usage: Metrics | undefined = undefined;
memoryChartData: any[] = [];
cpuChartData: any[] = [];
diskUsagePercentage: number = 0.00;
diskUsage: number = 0.00;
diskCapacity: number = 0.00;

colorScheme:Color = {
colorScheme: Color = {
name: 'customScheme',
selectable: true,
group: ScaleType.Ordinal,
Expand All @@ -72,48 +74,48 @@ export class DashboardComponent implements AfterViewInit, OnInit {
colorSchemeCpu: Color = {...this.colorScheme}
diskColor = "";

constructor(private usageService: StatsService) {}

ngOnInit(): void {
this.loadUsage();
constructor(private usageService: StatWebSocketService) {
}

loading: boolean = false;
ngOnInit(): void {
this.usageService.connect()

loadUsage(): void {
this.loading = true;
this.usageService.getStats().subscribe({
this.usageService.getMetrics().subscribe({
next: (data) => {
this.usage = data;
this.updateChartData();
this.updateChartData(data);
this.loading = false;
},
error: (error) => {
console.error(error);
this.loading = false;
this.loading = true;
}
});
})
}

loading: boolean = false;

valueFormatting(usage: number): string {
return usage+`%`;
return usage + `%`;
}

updateChartData(): void {
updateChartData(metrics: Metrics): void {
this.memoryChartData = [
{ name: 'Used', value: parseFloat(this.usage.MemUsage.toFixed(2)) },
{name: 'Used', value: parseFloat(metrics.MemUsage.toFixed(2))},
];
this.cpuChartData = [
{ name: 'Used', value: parseFloat((this.usage?.CpuUsage || 0).toFixed(2)) },
{name: 'Used', value: parseFloat(metrics.CpuUsage.toFixed(2))},
];
this.diskUsagePercentage = (this.usage.DiskUsage / this.usage.DiskCapacity) * 100;
this.diskUsage = metrics.DiskUsage;
this.diskCapacity = metrics.DiskCapacity;
this.diskUsagePercentage = (metrics.DiskUsage / metrics.DiskCapacity) * 100;

this.colorScheme = {
...this.colorScheme,
domain: [this.getUsageColor(this.usage.MemUsage), '#E0E0E0']
domain: [this.getUsageColor(metrics.MemUsage), '#E0E0E0']
};
this.colorSchemeCpu = {
...this.colorScheme,
domain: [this.getUsageColor(this.usage.CpuUsage), '#E0E0E0']
domain: [this.getUsageColor(metrics.CpuUsage), '#E0E0E0']
};
this.diskColor = this.getUsageColor(this.diskUsagePercentage);
}
Expand All @@ -128,4 +130,9 @@ export class DashboardComponent implements AfterViewInit, OnInit {
if (usage < 85) return orange;
return red;
}

ngOnDestroy() {
this.usageService.disconnect();
}

}
54 changes: 54 additions & 0 deletions App/Frontend/src/app/services/statWebsocket.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {Injectable, OnDestroy} from "@angular/core";
import {Subject} from "rxjs";
import {Metrics} from "../domain/Metrics";
import {environment} from "../../environments/environment";

@Injectable({
providedIn: 'root'
})
export class StatWebSocketService implements OnDestroy {
private url = `${environment.BASE_URL}/secured/statsocket`;
private socket!: WebSocket;
private messagesSubject: Subject<Metrics> = new Subject()

constructor() {
}

connect(): void {
this.socket = new WebSocket(this.url);

this.socket.onopen = () => {
}

this.socket.onmessage = (event: MessageEvent) => {
try {
const data: Metrics = JSON.parse(event.data);
this.messagesSubject.next(data)
} catch (error) {
console.error("[WebSocketService] Error parsing data:", error)
}
}

this.socket.onerror = () => {
console.error("[WebSocketService] WebSocket Error: " + this.url);
}

this.socket.onclose = () => {
}
}

disconnect(): void {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.close();
}
}

getMetrics() {
return this.messagesSubject.asObservable();
}

ngOnDestroy() {
this.disconnect();
}

}
12 changes: 7 additions & 5 deletions App/Frontend/src/app/services/stats.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
/* @author K10s Open Source Team */
/**********************************/

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Metrics } from "../domain/Metrics";
import { environment } from "../../environments/environment";
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {Observable} from 'rxjs';
import {Metrics} from "../domain/Metrics";
import {environment} from "../../environments/environment";

@Injectable({
providedIn: 'root'
Expand All @@ -22,4 +22,6 @@ export class StatsService {
getStats(): Observable<Metrics> {
return this.http.get<Metrics>(this.apiUrl, {withCredentials: true});
}


}

0 comments on commit 197c6f3

Please sign in to comment.