diff --git a/package.json b/package.json index b626a19f..ebbadeb5 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "hammerjs": "^2.0.8", "ini": "^1.3.5", "material-design-icons": "^3.0.1", + "ng-circle-progress": "^1.5.1", "ng2-file-upload": "^1.3.0", "ngx-device-detector": "^1.3.18", "ngx-electron": "^2.1.1", diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index d2f2dd46..0f7167c2 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -60,6 +60,7 @@ import { TracengTemplateDetailsComponent } from './components/preferences/tracen import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component'; import { Gns3vmComponent } from './components/preferences/gns3vm/gns3vm.component'; import { DirectLinkComponent } from './components/direct-link/direct-link.component'; +import { SystemStatusComponent } from './components/system-status/system-status.component'; const routes: Routes = [ { @@ -74,6 +75,7 @@ const routes: Routes = [ { path: 'settings', component: SettingsComponent }, { path: 'settings/console', component: ConsoleComponent }, { path: 'installed-software', component: InstalledSoftwareComponent }, + { path: 'server/:server_id/systemstatus', component: SystemStatusComponent }, { path: 'server/:server_ip/:server_port/project/:project_id', component: DirectLinkComponent}, { path: 'server/:server_id/project/:project_id/snapshots', component: ListOfSnapshotsComponent }, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a95b4a10..a9dfdd09 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -260,6 +260,10 @@ import { ConfigureGns3VMDialogComponent } from './components/servers/configure-g import { ImportApplianceComponent } from './components/project-map/import-appliance/import-appliance.component'; import { GoogleAnalyticsService } from './services/google-analytics.service'; import { DirectLinkComponent } from './components/direct-link/direct-link.component'; +import { SystemStatusComponent } from './components/system-status/system-status.component'; +import { StatusInfoComponent } from './components/system-status/status-info/status-info.component'; +import { StatusChartComponent } from './components/system-status/status-chart/status-chart.component'; +import { NgCircleProgressModule } from 'ng-circle-progress'; if (environment.production) { Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', { @@ -437,7 +441,10 @@ if (environment.production) { Gns3vmComponent, ConfigureGns3VMDialogComponent, ImportApplianceComponent, - DirectLinkComponent + DirectLinkComponent, + SystemStatusComponent, + StatusInfoComponent, + StatusChartComponent ], imports: [ BrowserModule, @@ -457,7 +464,8 @@ if (environment.production) { DragAndDropModule, DragDropModule, MATERIAL_IMPORTS, - DeviceDetectorModule.forRoot() + DeviceDetectorModule.forRoot(), + NgCircleProgressModule.forRoot() ], providers: [ SettingsService, diff --git a/src/app/components/project-map/project-map.component.html b/src/app/components/project-map/project-map.component.html index d976c9ee..d303bd0e 100644 --- a/src/app/components/project-map/project-map.component.html +++ b/src/app/components/project-map/project-map.component.html @@ -147,6 +147,12 @@ center_focus_strong + + + + diff --git a/src/app/components/system-status/status-chart/status-chart.component.html b/src/app/components/system-status/status-chart/status-chart.component.html new file mode 100644 index 00000000..63aa6801 --- /dev/null +++ b/src/app/components/system-status/status-chart/status-chart.component.html @@ -0,0 +1,155 @@ + +
+ + + + + + + +
+ +
+ + + + + +
+ +
+ + Memory total: {{formatBytes(computeStatistics.statistics.memory_total)}} + + + Memory used: {{formatBytes(computeStatistics.statistics.memory_used)}} + + + Memory free: {{formatBytes(computeStatistics.statistics.memory_free)}} + +
+ +
+ + Swap total: {{formatBytes(computeStatistics.statistics.swap_total)}} + + + Swap used: {{formatBytes(computeStatistics.statistics.swap_used)}} + + + Swap free: {{formatBytes(computeStatistics.statistics.swap_free)}} + +
+
diff --git a/src/app/components/system-status/status-chart/status-chart.component.scss b/src/app/components/system-status/status-chart/status-chart.component.scss new file mode 100644 index 00000000..a639bdc2 --- /dev/null +++ b/src/app/components/system-status/status-chart/status-chart.component.scss @@ -0,0 +1,6 @@ +.wrapper { + width: 100%; + display: flex; + justify-content: space-between; + margin-bottom: 20px; +} diff --git a/src/app/components/system-status/status-chart/status-chart.component.spec.ts b/src/app/components/system-status/status-chart/status-chart.component.spec.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/components/system-status/status-chart/status-chart.component.ts b/src/app/components/system-status/status-chart/status-chart.component.ts new file mode 100644 index 00000000..05647246 --- /dev/null +++ b/src/app/components/system-status/status-chart/status-chart.component.ts @@ -0,0 +1,28 @@ +import { Component, OnInit, Input } from "@angular/core"; +import { ComputeStatistics } from '../../../models/computeStatistics'; + + +@Component({ + selector: 'app-status-chart', + templateUrl: './status-chart.component.html', + styleUrls: ['./status-chart.component.scss'] +}) +export class StatusChartComponent implements OnInit { + @Input() computeStatistics: ComputeStatistics; + + constructor() {} + + ngOnInit() {} + + formatBytes(bytes, decimals = 2) { + if (bytes === 0) return '0 Bytes'; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; + } +} diff --git a/src/app/components/system-status/status-info/status-info.component.html b/src/app/components/system-status/status-info/status-info.component.html new file mode 100644 index 00000000..7e242219 --- /dev/null +++ b/src/app/components/system-status/status-info/status-info.component.html @@ -0,0 +1,4 @@ +
+ {{statistics.compute_name}} + +
diff --git a/src/app/components/system-status/status-info/status-info.component.scss b/src/app/components/system-status/status-info/status-info.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/components/system-status/status-info/status-info.component.spec.ts b/src/app/components/system-status/status-info/status-info.component.spec.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/components/system-status/status-info/status-info.component.ts b/src/app/components/system-status/status-info/status-info.component.ts new file mode 100644 index 00000000..7647db93 --- /dev/null +++ b/src/app/components/system-status/status-info/status-info.component.ts @@ -0,0 +1,47 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute } from '@angular/router'; +import { ComputeService } from '../../../services/compute.service'; +import { ComputeStatistics } from '../../../models/computeStatistics'; +import { ServerService } from '../../../services/server.service'; +import { Server } from '../../../models/server'; +import { ToasterService } from '../../../services/toaster.service'; + + +@Component({ + selector: 'app-status-info', + templateUrl: './status-info.component.html', + styleUrls: ['./status-info.component.scss'] +}) +export class StatusInfoComponent implements OnInit { + public serverId: string = ""; + public computeStatistics: ComputeStatistics[] = []; + + constructor( + private route: ActivatedRoute, + private computeService: ComputeService, + private serverService: ServerService, + private toasterService: ToasterService + ) {} + + ngOnInit() { + this.serverId = this.route.snapshot.paramMap.get("server_id"); + this.getStatistics(); + } + + getStatistics() { + this.serverService.get(Number(this.serverId)).then((server: Server) => { + this.computeService.getStatistics(server).subscribe( + (statistics: ComputeStatistics[]) => { + this.computeStatistics = statistics; + setTimeout(() => + { + this.getStatistics(); + }, + 10000); + }), + error => { + this.toasterService.error('Required server version is 2.3') + } + }); + } +} diff --git a/src/app/components/system-status/system-status.component.html b/src/app/components/system-status/system-status.component.html new file mode 100644 index 00000000..d47624d2 --- /dev/null +++ b/src/app/components/system-status/system-status.component.html @@ -0,0 +1,10 @@ +
+
+
+

System status

+
+
+
+ +
+
diff --git a/src/app/components/system-status/system-status.component.scss b/src/app/components/system-status/system-status.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/components/system-status/system-status.component.spec.ts b/src/app/components/system-status/system-status.component.spec.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/components/system-status/system-status.component.ts b/src/app/components/system-status/system-status.component.ts new file mode 100644 index 00000000..f2915a0a --- /dev/null +++ b/src/app/components/system-status/system-status.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute } from '@angular/router'; + + +@Component({ + selector: 'app-system-status', + templateUrl: './system-status.component.html', + styleUrls: ['./system-status.component.scss'] +}) +export class SystemStatusComponent implements OnInit { + public serverId: string = ""; + + constructor( + private route: ActivatedRoute + ) {} + + ngOnInit() { + this.serverId = this.route.snapshot.paramMap.get("server_id"); + } +} diff --git a/src/app/material.imports.ts b/src/app/material.imports.ts index 267561d2..359be1a5 100644 --- a/src/app/material.imports.ts +++ b/src/app/material.imports.ts @@ -22,7 +22,8 @@ import { MatGridListModule, MatTabsModule, MatTreeModule, - MatBottomSheetModule + MatBottomSheetModule, + MatChipsModule } from '@angular/material'; export const MATERIAL_IMPORTS = [ @@ -49,5 +50,6 @@ export const MATERIAL_IMPORTS = [ MatGridListModule, MatTabsModule, MatTreeModule, - MatBottomSheetModule + MatBottomSheetModule, + MatChipsModule ]; diff --git a/src/app/models/computeStatistics.ts b/src/app/models/computeStatistics.ts new file mode 100644 index 00000000..c5d19031 --- /dev/null +++ b/src/app/models/computeStatistics.ts @@ -0,0 +1,19 @@ +export interface Statistics { + cpu_usage_percent: number; + disk_usage_percent: number; + load_average_percent: number[]; + memory_free: number; + memory_total: number; + memory_usage_percent: number; + memory_used: number; + swap_free: number; + swap_total: number; + swap_usage_percent: number; + swap_used: number; +} + +export interface ComputeStatistics { + compute_id: string; + compute_name: string; + statistics: Statistics; +} diff --git a/src/app/services/compute.service.ts b/src/app/services/compute.service.ts index e6b9bfac..1799e4e2 100644 --- a/src/app/services/compute.service.ts +++ b/src/app/services/compute.service.ts @@ -3,6 +3,7 @@ import { HttpServer } from './http-server.service'; import { Server } from '../models/server'; import { Compute } from '../models/compute'; import { Observable } from 'rxjs'; +import { ComputeStatistics } from '../models/computeStatistics'; @Injectable() export class ComputeService { @@ -15,4 +16,8 @@ export class ComputeService { getUploadPath(server: Server, emulator: string, filename: string) { return `http://${server.host}:${server.port}/v2/${emulator}/images/${filename}`; } + + getStatistics(server: Server): Observable { + return this.httpServer.get(server, `/statistics`) + } } diff --git a/yarn.lock b/yarn.lock index 7e22c407..1a5ac95c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3748,11 +3748,6 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - detect-node@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" @@ -5193,7 +5188,7 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: +iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -6705,15 +6700,6 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" -needle@^2.2.1: - version "2.4.0" - resolved "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c" - integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - negotiator@0.6.2: version "0.6.2" resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" @@ -6724,6 +6710,13 @@ neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1: resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== +ng-circle-progress@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/ng-circle-progress/-/ng-circle-progress-1.5.1.tgz#ea4ea89318754b929dc1550d5779e391d7c5c646" + integrity sha512-gJ0SdWc+TkXckfbhbvUY522yjya3qcc1a3lGvvVyLsUfSOgll8LQaZGDEko84d8dk8vYO83n0O5vP+e8bJTInQ== + dependencies: + tslib "^1.9.0" + ng2-file-upload@^1.3.0: version "1.4.0" resolved "https://registry.npmjs.org/ng2-file-upload/-/ng2-file-upload-1.4.0.tgz#8dea28d573234c52af474ad2a4001b335271e5c4" @@ -6816,22 +6809,6 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-pre-gyp@*: - version "0.14.0" - resolved "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83" - integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4.4.2" - node-releases@^1.1.25, node-releases@^1.1.42: version "1.1.43" resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.43.tgz#2c6ca237f88ce11d49631f11190bb01f8d0549f2" @@ -6964,7 +6941,7 @@ npm-package-arg@^6.0.0, npm-package-arg@^6.1.0: semver "^5.6.0" validate-npm-package-name "^3.0.0" -npm-packlist@^1.1.12, npm-packlist@^1.1.6: +npm-packlist@^1.1.12: version "1.4.7" resolved "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.7.tgz#9e954365a06b80b18111ea900945af4f88ed4848" integrity sha512-vAj7dIkp5NhieaGZxBJB8fF4R0078rqsmhJcAfXZ6O7JJhjhPK96n5Ry1oZcfLXgfun0GWTZPOxaEyqv8GBykQ== @@ -7010,7 +6987,7 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2: +"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0: version "4.1.2" resolved "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== @@ -7846,7 +7823,7 @@ raw-loader@3.1.0: loader-utils "^1.1.0" schema-utils "^2.0.1" -rc@^1.2.7, rc@^1.2.8: +rc@^1.2.8: version "1.2.8" resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -8229,7 +8206,7 @@ rfdc@^1.1.4: resolved "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2" integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug== -rimraf@2, rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3: +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.2, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -9232,7 +9209,7 @@ tar@^2.0.0: fstream "^1.0.12" inherits "2" -tar@^4.4.2, tar@^4.4.8: +tar@^4.4.8: version "4.4.13" resolved "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==