Kaynağa Gözat

ng: Implementation of service check widget and status timeline bars (on dashboard)

Christian Kahlau 3 yıl önce
ebeveyn
işleme
7a4e57a2b7

+ 3 - 1
ng/src/app/app.module.ts

@@ -18,6 +18,7 @@ import { ServerDataPageComponent } from './pages/server-data-page/server-data-pa
 
 import { BytePipe } from './pipes/byte.pipe';
 import { FaByTypePipe } from './pipes/fa-by-type.pipe';
+import { ServiceChecksWidgetComponent } from './components/service-checks-widget/service-checks-widget.component';
 
 @NgModule({
   declarations: [
@@ -30,7 +31,8 @@ import { FaByTypePipe } from './pipes/fa-by-type.pipe';
     ServerDataChartComponent,
     ServerDataPageComponent,
     ServerMetricsWidgetComponent,
-    ServiceCheckFormComponent
+    ServiceCheckFormComponent,
+    ServiceChecksWidgetComponent
   ],
   imports: [
     AppRoutingModule,

+ 8 - 7
ng/src/app/components/server-metrics-widget/server-metrics-widget.component.html

@@ -13,13 +13,14 @@
         <div *ngIf="type.data.peak" class="progress-bar bg-peak" role="progressbar" [style.width]="type.data.peak - (type.data.avg || 0) + '%'"></div>
         <span *ngIf="type.subtype" class="position-absolute end-0 pe-2">:{{ type.subtype }}</span>
       </div>
-      <ng-template #loading>
-        <div class="progress">
-          <div class="progress-bar bg-progress progress-bar-striped progress-bar-animated text-primary" role="progressbar" style="width: 100%">
-            Loading data
-          </div>
-        </div>
-      </ng-template>
     </div>
   </ng-container>
 </div>
+
+<ng-template #loading>
+  <div class="progress">
+    <div class="progress-bar bg-progress progress-bar-striped progress-bar-animated text-primary" role="progressbar" style="width: 100%">
+      Loading data
+    </div>
+  </div>
+</ng-template>

+ 21 - 0
ng/src/app/components/service-checks-widget/service-checks-widget.component.html

@@ -0,0 +1,21 @@
+<div *ngIf="serviceChecks; else loading" class="d-flex flex-column">
+  <div *ngFor="let check of serviceChecks" class="position-relative mt-1 mb-1">
+    <ng-container *ngIf="check.data; else loading">
+      <div class="progress position-relative">
+        <div class="status-timeline-label">{{ check.title }}</div>
+        <div
+          *ngFor="let data of check.data"
+          [class]="'progress-bar ' + data.statusClass"
+          [title]="data.statusText"
+          [style.width]="data.width + '%'"></div>
+      </div>
+    </ng-container>
+  </div>
+</div>
+<ng-template #loading>
+  <div class="progress">
+    <div class="progress-bar bg-progress progress-bar-striped progress-bar-animated text-primary" role="progressbar" style="width: 100%">
+      Loading data
+    </div>
+  </div>
+</ng-template>

+ 7 - 0
ng/src/app/components/service-checks-widget/service-checks-widget.component.scss

@@ -0,0 +1,7 @@
+.progress {
+  .status-timeline-label {
+    position: absolute;
+    left: 50%;
+    transform: translate(-50%, -2px);
+  }
+}

+ 23 - 0
ng/src/app/components/service-checks-widget/service-checks-widget.component.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ServiceChecksWidgetComponent } from './service-checks-widget.component';
+
+describe('ServiceChecksWidgetComponent', () => {
+  let component: ServiceChecksWidgetComponent;
+  let fixture: ComponentFixture<ServiceChecksWidgetComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ ServiceChecksWidgetComponent ]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(ServiceChecksWidgetComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 103 - 0
ng/src/app/components/service-checks-widget/service-checks-widget.component.ts

@@ -0,0 +1,103 @@
+import { Component, Input } from '@angular/core';
+
+import { HttpCheckStatus, ServiceCheckData } from '../../../../../common/lib/http-check-data.module';
+
+import { ServiceApiService } from 'src/app/services/service-api.service';
+
+type StatusTimelineData = {
+  width: number;
+  statusText: string;
+  statusClass: string;
+};
+
+@Component({
+  selector: 'app-service-checks-widget',
+  templateUrl: './service-checks-widget.component.html',
+  styleUrls: ['./service-checks-widget.component.scss']
+})
+export class ServiceChecksWidgetComponent {
+  @Input() set server(server: ServerConfig) {
+    this._server = server;
+
+    if (!this._serviceChecks) {
+      this.fetchServiceChecks();
+    }
+  }
+  public get server() {
+    return this._server;
+  }
+
+  public get serviceChecks() {
+    return this._serviceChecks;
+  }
+
+  private _server!: ServerConfig;
+  private _serviceChecks?: Array<HttpCheckConfig & { data?: StatusTimelineData[] }>;
+
+  constructor(private serviceApi: ServiceApiService) {}
+
+  private async fetchServiceChecks() {
+    try {
+      this._serviceChecks = await this.serviceApi.loadServiceChecks(this.server.id);
+
+      const end = new Date();
+      const start = new Date(end.getTime() - 1000 * 60 * 60 * 24);
+      const diffMs = end.getTime() - start.getTime();
+      this.serviceChecks?.forEach(async check => {
+        // Query status data of last 24h
+        const rawData = await this.serviceApi.queryServiceData(this.server.id, check.id, start, end);
+
+        // Enhance data for displaying as stacked progress bar
+        const data: Partial<StatusTimelineData>[] = [];
+        if (rawData?.length) {
+          let lastEntry: Partial<StatusTimelineData> | undefined = undefined;
+          const diffPerc = ((rawData[0].time.getTime() - start.getTime()) / diffMs) * 100;
+          if (diffPerc > 0) {
+            lastEntry = {
+              statusText: rawData[0].data.map(dx => dx.message).join(', '),
+              statusClass: this.mapStatusClass(rawData[0])
+            };
+            data.push(lastEntry);
+          }
+          let sumwidth = 0;
+          rawData.forEach((d, i) => {
+            if (lastEntry) {
+              lastEntry.width = ((d.time.getTime() - start.getTime()) / diffMs) * 100 - sumwidth;
+              sumwidth += lastEntry.width;
+            }
+
+            lastEntry = {
+              statusText: d.data.map(dx => dx.message).join(', '),
+              statusClass: this.mapStatusClass(d)
+            };
+            data.push(lastEntry);
+          });
+
+          if (sumwidth < 100 && lastEntry && !lastEntry.width) {
+            lastEntry.width = 100 - sumwidth;
+          }
+        } else {
+          data.push({
+            width: 100,
+            statusClass: 'bg-progress',
+            statusText: '- no data -'
+          });
+        }
+        check.data = data as StatusTimelineData[];
+      });
+    } catch (err) {
+      // TODO
+      console.error(err);
+    }
+  }
+
+  private mapStatusClass(data: ServiceCheckData) {
+    const maxStatus = data.data.reduce((res, d) => (res = Math.max(res, d.status)), 0);
+    switch (maxStatus) {
+      case HttpCheckStatus.OK:
+        return 'bg-peak';
+      default:
+        return 'bg-max';
+    }
+  }
+}

+ 5 - 3
ng/src/app/pages/home-page/home-page.component.html

@@ -29,8 +29,10 @@
       [server]="server">
     </app-server-metrics-widget>
 
-    <div class="p-1 border-end border-bottom" style="grid-column: 3 / span 1" [style.grid-row]="i + 2 + ' / span 1'">
-      ... service checks soon come...
-    </div>
+    <app-service-checks-widget
+      [server]="server"
+      class="d-block p-1 border-end border-bottom"
+      style="grid-column: 3 / span 1"
+      [style.grid-row]="i + 2 + ' / span 1'"></app-service-checks-widget>
   </ng-container>
 </div>