diff --git a/frontend/src/app/main-table/cost-table/cost-table.component.html b/frontend/src/app/main-table/cost-table/cost-table.component.html
index 6756cf4d..bf355492 100644
--- a/frontend/src/app/main-table/cost-table/cost-table.component.html
+++ b/frontend/src/app/main-table/cost-table/cost-table.component.html
@@ -9,7 +9,10 @@
-
+
-
+
{{ formatCost(cost.cpuCost + cost.ramCost) }} |
{{ formatCost(cost.gpuCost) }} |
{{ formatCost(cost.pvCost) }} |
-
+ |
{{ formatCost(cost.totalCost) }}
|
+
+
+
+ {{ "costTable.errMessage" | translate }}
+
+
diff --git a/frontend/src/app/main-table/cost-table/cost-table.component.scss b/frontend/src/app/main-table/cost-table/cost-table.component.scss
index c1337000..77c95371 100644
--- a/frontend/src/app/main-table/cost-table/cost-table.component.scss
+++ b/frontend/src/app/main-table/cost-table/cost-table.component.scss
@@ -3,13 +3,21 @@ p {
padding-right: 16px;
}
+.costerror {
+ padding: 20px;
+ padding-left: 24px;
+ color: #cc0033;
+}
+
.header p {
padding: 12px 0 0px;
font-weight: 400;
font-size: 20px;
}
-.mat-cell, .mat-header-cell, .mat-footer-cell {
+.mat-cell,
+.mat-header-cell,
+.mat-footer-cell {
padding-left: 12px;
padding-right: 195px;
}
diff --git a/frontend/src/app/main-table/cost-table/cost-table.component.ts b/frontend/src/app/main-table/cost-table/cost-table.component.ts
index be2bc3d8..6ca37dd8 100644
--- a/frontend/src/app/main-table/cost-table/cost-table.component.ts
+++ b/frontend/src/app/main-table/cost-table/cost-table.component.ts
@@ -1,17 +1,36 @@
-import { Component, Input } from "@angular/core";
-import { AggregateCostResponse } from 'src/app/services/kubecost.service';
+import {Component, Input} from "@angular/core";
+import {AggregateCostResponse} from "src/app/services/kubecost.service";
+
+enum AsyncStatus {
+ PENDING,
+ SUCCESS,
+ FAILURE
+}
@Component({
selector: "app-cost-table",
templateUrl: "./cost-table.component.html",
styleUrls: ["./cost-table.component.scss", "../main-table.component.scss"]
})
-export class CostTableComponent{
- @Input() aggregatedCost: AggregateCostResponse;
- @Input() currNamespace:string;
+export class CostTableComponent {
+ @Input() aggregatedCost: AggregateCostResponse;
+ @Input() currNamespace: string;
+
+ AsyncStatus = AsyncStatus;
formatCost(value: number): string {
- return '$' + (value > 0 ? Math.max(value, 0.01) : 0).toFixed(2)
+ return "$" + (value > 0 ? Math.max(value, 0.01) : 0).toFixed(2);
}
+ getStatus(): AsyncStatus {
+ if (this.aggregatedCost == null) {
+ return AsyncStatus.PENDING;
+ }
+
+ if (this.aggregatedCost instanceof Error) {
+ return AsyncStatus.FAILURE;
+ }
+
+ return AsyncStatus.SUCCESS;
+ }
}
diff --git a/frontend/src/app/main-table/main-table.component.html b/frontend/src/app/main-table/main-table.component.html
index 657387d0..4c61bbee 100644
--- a/frontend/src/app/main-table/main-table.component.html
+++ b/frontend/src/app/main-table/main-table.component.html
@@ -16,24 +16,22 @@
-
-
-
-
-
+
+
+
diff --git a/frontend/src/app/main-table/main-table.component.ts b/frontend/src/app/main-table/main-table.component.ts
index 7d8049bf..1deb98a9 100644
--- a/frontend/src/app/main-table/main-table.component.ts
+++ b/frontend/src/app/main-table/main-table.component.ts
@@ -5,11 +5,11 @@ import {KubernetesService} from "src/app/services/kubernetes.service";
import {Subscription} from "rxjs";
import {isEqual} from "lodash";
import {first} from "rxjs/operators";
-import { KubecostService, AggregateCostResponse } from 'src/app/services/kubecost.service';
+import {AggregateCostResponse, KubecostService} from "src/app/services/kubecost.service";
import {ExponentialBackoff} from "src/app/utils/polling";
import {Volume, Resource} from "../utils/types";
-import {PvcWithStatus} from "./volumes-table/volume-table.component"
+import {PvcWithStatus} from "./volumes-table/volume-table.component";
@Component({
selector: "app-main-table",
@@ -24,15 +24,14 @@ export class MainTableComponent implements OnInit {
pvcs: Volume[] = [];
pvcProperties: PvcWithStatus[] = [];
- aggregatedCost: AggregateCostResponse = null;
-
subscriptions = new Subscription();
poller: ExponentialBackoff;
+ aggregatedCost: AggregateCostResponse = null;
constructor(
public ns: NamespaceService,
private k8s: KubernetesService,
- private kubecostService: KubecostService,
+ private kubecostService: KubecostService
) {}
ngOnInit() {
@@ -46,7 +45,10 @@ export class MainTableComponent implements OnInit {
this.k8s.getResource(this.currNamespace).toPromise(),
this.k8s.getVolumes(this.currNamespace).toPromise()
]).then(([notebooks, volumes]) => {
- if (!isEqual(notebooks, this.resources) || !isEqual(volumes, this.pvcs)) {
+ if (
+ !isEqual(notebooks, this.resources) ||
+ !isEqual(volumes, this.pvcs)
+ ) {
this.poller.reset();
}
this.resources = notebooks;
@@ -92,7 +94,8 @@ export class MainTableComponent implements OnInit {
getAggregatedCost() {
this.kubecostService.getAggregateCost(this.currNamespace).subscribe(
- aggCost => this.aggregatedCost = aggCost
- )
+ aggCost => (this.aggregatedCost = aggCost),
+ error => (this.aggregatedCost = error)
+ );
}
}
diff --git a/frontend/src/app/main-table/volumes-table/volume-table.component.html b/frontend/src/app/main-table/volumes-table/volume-table.component.html
index 3a61c012..6352bf37 100644
--- a/frontend/src/app/main-table/volumes-table/volume-table.component.html
+++ b/frontend/src/app/main-table/volumes-table/volume-table.component.html
@@ -3,7 +3,7 @@
storage
{{ "volumeTable.title" | translate }}
-
+
diff --git a/frontend/src/app/main-table/volumes-table/volume-table.component.ts b/frontend/src/app/main-table/volumes-table/volume-table.component.ts
index 250857a9..2be7b88b 100644
--- a/frontend/src/app/main-table/volumes-table/volume-table.component.ts
+++ b/frontend/src/app/main-table/volumes-table/volume-table.component.ts
@@ -9,7 +9,7 @@ import {TranslateService} from "@ngx-translate/core";
export type PvcWithStatus = {
pvc: Volume;
mountedBy: string | null;
-}
+};
enum PvcStatus {
DELETING,
diff --git a/frontend/src/app/services/kubecost.service.ts b/frontend/src/app/services/kubecost.service.ts
index 90d8a4bc..e0e42210 100644
--- a/frontend/src/app/services/kubecost.service.ts
+++ b/frontend/src/app/services/kubecost.service.ts
@@ -1,84 +1,73 @@
-import { Injectable } from "@angular/core";
-import { HttpClient, HttpErrorResponse } from "@angular/common/http";
+import {Injectable} from "@angular/core";
+import {HttpClient, HttpErrorResponse} from "@angular/common/http";
-import { Observable, throwError } from "rxjs";
-import { tap, catchError } from "rxjs/operators";
-import { environment } from "src/environments/environment";
-
-import {
- Resp,
- SnackType
-} from "../utils/types";
-import { SnackBarService } from "./snack-bar.service";
+import {Observable, throwError} from "rxjs";
+import {tap, catchError} from "rxjs/operators";
+import {environment} from "src/environments/environment";
+import {Resp} from "../utils/types";
export type AggregateCostResponse = {
- code: number,
+ code: number;
data: {
[namespace: string]: {
- aggregation: string,
- environment: string,
- cpuAllocationAverage: number,
- cpuCost: number,
- cpuEfficiency: number,
- efficiency: number,
- gpuAllocationAverage: number,
- gpuCost: number,
- ramAllocationAverage: number,
- ramCost: number,
- ramEfficiency: number,
- pvAllocationAverage: number,
- pvCost: number,
- networkCost: number,
- sharedCost: number,
- totalCost: number
- }
- },
- message: string
-}
+ aggregation: string;
+ environment: string;
+ cpuAllocationAverage: number;
+ cpuCost: number;
+ cpuEfficiency: number;
+ efficiency: number;
+ gpuAllocationAverage: number;
+ gpuCost: number;
+ ramAllocationAverage: number;
+ ramCost: number;
+ ramEfficiency: number;
+ pvAllocationAverage: number;
+ pvCost: number;
+ networkCost: number;
+ sharedCost: number;
+ totalCost: number;
+ };
+ };
+ message: string;
+};
@Injectable()
export class KubecostService {
-
- constructor(private http: HttpClient, private snackBar: SnackBarService) { }
+ constructor(private http: HttpClient) {}
getAggregateCost(ns: string): Observable {
- const url = environment.apiUrl + `/api/namespaces/${ns}/cost/aggregated`
-
- return this.http.get(url, {
- params: {
- aggregation: 'namespace',
- namespace: ns,
- window: '1d'
- }
- }).pipe(
- tap(res => this.handleBackendError(res)),
- catchError(err => this.handleError(err))
- );
- }
+ const url = environment.apiUrl + `/api/namespaces/${ns}/cost/aggregated`;
+ return this.http
+ .get(url, {
+ params: {
+ aggregation: "namespace",
+ namespace: ns,
+ window: "1d"
+ }
+ })
+ .pipe(
+ tap(res => this.handleBackendError(res)),
+ catchError(err => this.handleError(err))
+ ) as Observable;
+ }
- // ---------------------------Error Handling----------------------------------
- private handleBackendError(response: { code: number }) {
- if (response.code < 200 || response.code >= 300) {
+ private handleBackendError(response: AggregateCostResponse | Resp) {
+ if (this.isResp(response) || response.code < 200 || response.code >= 300) {
throw response;
}
}
- private handleError(error: HttpErrorResponse | Resp): Observable {
- // The backend returned an unsuccessful response code.
- // The response body may contain clues as to what went wrong,
- if (error instanceof HttpErrorResponse) {
- this.snackBar.show(
- `${error.status}: There was an error trying to connect ` +
- `to the backend API. ${error.message}`,
- SnackType.Error
- );
- return throwError(error.message);
- } else {
- // Backend error thrown from handleBackendError
- const backendError = error as Resp;
- this.snackBar.show(backendError.log, SnackType.Error);
- return throwError(backendError.log);
- }
+ private handleError(
+ error: HttpErrorResponse | AggregateCostResponse | Resp
+ ): Observable {
+ const message = this.isResp(error) ? error.log : error.message;
+ return throwError(new Error(message));
+ }
+
+ private isResp(
+ obj: HttpErrorResponse | AggregateCostResponse | Resp
+ ): obj is Resp {
+ return (obj as Resp).success !== undefined;
}
}
diff --git a/frontend/src/assets/i18n/en.json b/frontend/src/assets/i18n/en.json
index 1fc7803d..6068ee6f 100644
--- a/frontend/src/assets/i18n/en.json
+++ b/frontend/src/assets/i18n/en.json
@@ -5,7 +5,8 @@
"thCompute": "Compute",
"thGpus": "GPUs",
"thStorage": "Storage",
- "thTotal": "Total"
+ "thTotal": "Total",
+ "errMessage": "Failed to retrieve cost information"
},
"namespaceSelect": {
"lblSelectNamespace": "Select Namespace",
diff --git a/frontend/src/assets/i18n/fr.json b/frontend/src/assets/i18n/fr.json
index cd587bb4..6e195ad7 100644
--- a/frontend/src/assets/i18n/fr.json
+++ b/frontend/src/assets/i18n/fr.json
@@ -5,7 +5,8 @@
"thCompute": "Calculé",
"thGpus": "GPUs",
"thStorage": "Stockage",
- "thTotal": "Total"
+ "thTotal": "Total",
+ "errMessage": "Échec de la récupération des informations des coûts."
},
"namespaceSelect": {
"lblSelectNamespace": "Sélectionner l'espace de noms",