back: - fix broken deps - add connection check to panel - pretty-print panel errors - touch timestamps of updates - simplify u_server api - simplify u_lib api front: - add routing - use async instead of rxjs - define tables' columns in templates, not in ts - use different templates for different tablespull/1/head
parent
7b59031bfe
commit
544c07cf8d
31 changed files with 427 additions and 276 deletions
@ -1,11 +1,6 @@ |
||||
<mat-tab-group animationDuration="0ms" mat-align-tabs="center"> |
||||
<mat-tab label="Agents"> |
||||
<agent-table></agent-table> |
||||
</mat-tab> |
||||
<mat-tab label="Jobs"> |
||||
<job-table></job-table> |
||||
</mat-tab> |
||||
<mat-tab label="Results"> |
||||
<result-table></result-table> |
||||
</mat-tab> |
||||
</mat-tab-group> |
||||
<nav mat-tab-nav-bar animationDuration="0ms" mat-align-tabs="center"> |
||||
<a mat-tab-link routerLink="/agents" routerLinkActive="active" ariaCurrentWhenActive="page">Agents</a> |
||||
<a mat-tab-link routerLink="/jobs" routerLinkActive="active" ariaCurrentWhenActive="page">Jobs</a> |
||||
<a mat-tab-link routerLink="/results" routerLinkActive="active" ariaCurrentWhenActive="page">Results</a> |
||||
</nav> |
||||
<router-outlet></router-outlet> |
@ -0,0 +1,68 @@ |
||||
<div class="mat-elevation-z8"> |
||||
|
||||
<div class="table-container"> |
||||
<div class="loading-shade" *ngIf="isLoadingResults"> |
||||
<mat-spinner *ngIf="isLoadingResults"></mat-spinner> |
||||
</div> |
||||
<mat-form-field appearance="standard"> |
||||
<mat-label>Filter</mat-label> |
||||
<input matInput (keyup)="apply_filter($event)" #input> |
||||
</mat-form-field> |
||||
<button id="refresh_btn" mat-raised-button color="primary" (click)="fetchMany()">Refresh</button> |
||||
|
||||
<table mat-table [dataSource]="table_data" class="data-table" matSort matSortActive="id" matSortDisableClear |
||||
matSortDirection="desc"> |
||||
|
||||
<ng-container matColumnDef="id"> |
||||
<th mat-header-cell *matHeaderCellDef>ID</th> |
||||
<td mat-cell *matCellDef="let row"> |
||||
{{row.id}} |
||||
</td> |
||||
</ng-container> |
||||
|
||||
<ng-container matColumnDef="alias"> |
||||
<th mat-header-cell *matHeaderCellDef>Alias</th> |
||||
<td mat-cell *matCellDef="let row"> |
||||
{{row.alias}} |
||||
</td> |
||||
</ng-container> |
||||
|
||||
<ng-container matColumnDef="username"> |
||||
<th mat-header-cell *matHeaderCellDef>User</th> |
||||
<td mat-cell *matCellDef="let row"> |
||||
{{row.username}} |
||||
</td> |
||||
</ng-container> |
||||
|
||||
<ng-container matColumnDef="hostname"> |
||||
<th mat-header-cell *matHeaderCellDef>Hostname</th> |
||||
<td mat-cell *matCellDef="let row"> |
||||
{{row.hostname}} |
||||
</td> |
||||
</ng-container> |
||||
|
||||
<ng-container matColumnDef="last_active"> |
||||
<th mat-header-cell *matHeaderCellDef>Last active</th> |
||||
<td mat-cell *matCellDef="let row"> |
||||
{{row.last_active.secs_since_epoch * 1000 | date:'long'}} |
||||
</td> |
||||
</ng-container> |
||||
|
||||
<ng-container matColumnDef="actions"> |
||||
<th mat-header-cell *matHeaderCellDef></th> |
||||
<td mat-cell *matCellDef="let row"> |
||||
<button mat-raised-button routerLink='' [queryParams]="{id: row.id}">Info</button> |
||||
</td> |
||||
</ng-container> |
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> |
||||
<tr mat-row class="data-table-row" *matRowDef="let row; columns: displayedColumns;"></tr> |
||||
<tr class="mat-row" *matNoDataRow> |
||||
<td class="mat-cell">No data</td> |
||||
</tr> |
||||
</table> |
||||
</div> |
||||
|
||||
<!-- <mat-paginator [length]="resultsLength" [pageSize]="30" aria-label="Select page of GitHub search results"> |
||||
</mat-paginator> --> |
||||
</div> |
@ -1,57 +1,62 @@ |
||||
import { Component, OnInit } from '@angular/core'; |
||||
import { Component, OnDestroy, OnInit } from '@angular/core'; |
||||
import { TablesComponent } from './table.component'; |
||||
import { AgentModel } from '../models'; |
||||
import { AgentInfoDialogComponent } from './dialogs/agent_info.component'; |
||||
import { HttpClient } from '@angular/common/http'; |
||||
import { MatDialog } from '@angular/material/dialog'; |
||||
import { epochToStr } from '../utils'; |
||||
import { ActivatedRoute, Router } from '@angular/router'; |
||||
import { emitErr } from '../utils'; |
||||
import { Subscription } from 'rxjs'; |
||||
|
||||
@Component({ |
||||
selector: 'agent-table', |
||||
templateUrl: './table.component.html', |
||||
templateUrl: './agent.component.html', |
||||
styleUrls: ['./table.component.less'] |
||||
}) |
||||
export class AgentComponent extends TablesComponent<AgentModel> { |
||||
export class AgentComponent extends TablesComponent<AgentModel> implements OnDestroy, OnInit { |
||||
|
||||
constructor(public override _httpClient: HttpClient, public override info_dlg: MatDialog) { |
||||
dialogSubscr!: Subscription; |
||||
area = 'agents' as const; |
||||
|
||||
displayedColumns = ['id', 'alias', 'username', 'hostname', 'last_active', 'actions'] |
||||
|
||||
constructor( |
||||
public override _httpClient: HttpClient, |
||||
public override info_dlg: MatDialog, |
||||
public route: ActivatedRoute, |
||||
public router: Router |
||||
) { |
||||
super(_httpClient, info_dlg); |
||||
} |
||||
|
||||
area = 'agents' as const; |
||||
override ngOnInit(): void { |
||||
super.ngOnInit() |
||||
this.dialogSubscr = this.route.queryParams.subscribe(params => { |
||||
const id = params['id'] |
||||
if (id) { |
||||
this.show_item_dialog(id); |
||||
} |
||||
}) |
||||
} |
||||
|
||||
columns = [ |
||||
{ |
||||
def: "id", |
||||
name: "ID", |
||||
cell: (cell: AgentModel) => cell.id |
||||
}, |
||||
{ |
||||
def: "alias", |
||||
name: "Alias", |
||||
cell: (cell: AgentModel) => cell.alias ?? "" |
||||
}, |
||||
{ |
||||
def: "username", |
||||
name: "User", |
||||
cell: (cell: AgentModel) => cell.username |
||||
}, |
||||
{ |
||||
def: "hostname", |
||||
name: "Host", |
||||
cell: (cell: AgentModel) => cell.hostname |
||||
}, |
||||
{ |
||||
def: "last_active", |
||||
name: "Last active", |
||||
cell: (cell: AgentModel) => epochToStr(cell.last_active.secs_since_epoch) |
||||
}, |
||||
] |
||||
|
||||
displayedColumns = this.columns.map((c) => c.def); |
||||
|
||||
show_item_dialog(obj: AgentModel) { |
||||
show_item_dialog(id: string) { |
||||
this.data_source!.getOne(id).then(resp => { |
||||
if (resp.status === 'ok') { |
||||
const dialog = this.info_dlg.open(AgentInfoDialogComponent, { |
||||
data: obj |
||||
data: resp.data as AgentModel |
||||
}); |
||||
|
||||
dialog.afterClosed().subscribe(result => { |
||||
this.router.navigate(['.'], { relativeTo: this.route }) |
||||
}) |
||||
} else { |
||||
emitErr(resp.data) |
||||
} |
||||
}).catch(emitErr) |
||||
} |
||||
|
||||
ngOnDestroy(): void { |
||||
this.dialogSubscr.unsubscribe() |
||||
} |
||||
} |
||||
|
@ -0,0 +1,68 @@ |
||||
<div class="mat-elevation-z8"> |
||||
|
||||
<div class="table-container"> |
||||
<div class="loading-shade" *ngIf="isLoadingResults"> |
||||
<mat-spinner *ngIf="isLoadingResults"></mat-spinner> |
||||
</div> |
||||
<mat-form-field appearance="standard"> |
||||
<mat-label>Filter</mat-label> |
||||
<input matInput (keyup)="apply_filter($event)" #input> |
||||
</mat-form-field> |
||||
<button id="refresh_btn" mat-raised-button color="primary" (click)="fetchMany()">Refresh</button> |
||||
|
||||
<table mat-table [dataSource]="table_data" class="data-table" matSort matSortActive="id" matSortDisableClear |
||||
matSortDirection="desc"> |
||||
|
||||
<ng-container matColumnDef="id"> |
||||
<th mat-header-cell *matHeaderCellDef>ID</th> |
||||
<td mat-cell *matCellDef="let row"> |
||||
{{row.id}} |
||||
</td> |
||||
</ng-container> |
||||
|
||||
<ng-container matColumnDef="alias"> |
||||
<th mat-header-cell *matHeaderCellDef>Alias</th> |
||||
<td mat-cell *matCellDef="let row"> |
||||
{{row.alias}} |
||||
</td> |
||||
</ng-container> |
||||
|
||||
<ng-container matColumnDef="argv"> |
||||
<th mat-header-cell *matHeaderCellDef>Cmd-line args</th> |
||||
<td mat-cell *matCellDef="let row"> |
||||
{{row.argv}} |
||||
</td> |
||||
</ng-container> |
||||
|
||||
<ng-container matColumnDef="platform"> |
||||
<th mat-header-cell *matHeaderCellDef>Platform</th> |
||||
<td mat-cell *matCellDef="let row"> |
||||
{{row.platform}} |
||||
</td> |
||||
</ng-container> |
||||
|
||||
<ng-container matColumnDef="payload"> |
||||
<th mat-header-cell *matHeaderCellDef>Payload</th> |
||||
<td mat-cell *matCellDef="let row"> |
||||
{{row.payload}} |
||||
</td> |
||||
</ng-container> |
||||
|
||||
<ng-container matColumnDef="exec_type"> |
||||
<th mat-header-cell *matHeaderCellDef>Type</th> |
||||
<td mat-cell *matCellDef="let row"> |
||||
{{row.exec_type}} |
||||
</td> |
||||
</ng-container> |
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> |
||||
<tr mat-row class="data-table-row" *matRowDef="let row; columns: displayedColumns;"></tr> |
||||
<tr class="mat-row" *matNoDataRow> |
||||
<td class="mat-cell">No data</td> |
||||
</tr> |
||||
</table> |
||||
</div> |
||||
|
||||
<!-- <mat-paginator [length]="resultsLength" [pageSize]="30" aria-label="Select page of GitHub search results"> |
||||
</mat-paginator> --> |
||||
</div> |
@ -0,0 +1,68 @@ |
||||
<div class="mat-elevation-z8"> |
||||
|
||||
<div class="table-container"> |
||||
<div class="loading-shade" *ngIf="isLoadingResults"> |
||||
<mat-spinner *ngIf="isLoadingResults"></mat-spinner> |
||||
</div> |
||||
<mat-form-field appearance="standard"> |
||||
<mat-label>Filter</mat-label> |
||||
<input matInput (keyup)="apply_filter($event)" #input> |
||||
</mat-form-field> |
||||
<button id="refresh_btn" mat-raised-button color="primary" (click)="fetchMany()">Refresh</button> |
||||
|
||||
<table mat-table [dataSource]="table_data" class="data-table" matSort matSortActive="id" matSortDisableClear |
||||
matSortDirection="desc"> |
||||
|
||||
<ng-container matColumnDef="id"> |
||||
<th mat-header-cell *matHeaderCellDef>ID</th> |
||||
<td mat-cell *matCellDef="let row"> |
||||
{{row.id}} |
||||
</td> |
||||
</ng-container> |
||||
|
||||
<ng-container matColumnDef="alias"> |
||||
<th mat-header-cell *matHeaderCellDef>Alias</th> |
||||
<td mat-cell *matCellDef="let row"> |
||||
{{row.alias}} |
||||
</td> |
||||
</ng-container> |
||||
|
||||
<ng-container matColumnDef="agent_id"> |
||||
<th mat-header-cell *matHeaderCellDef>Agent</th> |
||||
<td mat-cell *matCellDef="let row"> |
||||
<a routerLink='/agents' [queryParams]="{id: row.agent_id}">{{row.agent_id}}</a> |
||||
</td> |
||||
</ng-container> |
||||
|
||||
<ng-container matColumnDef="job_id"> |
||||
<th mat-header-cell *matHeaderCellDef>Job</th> |
||||
<td mat-cell *matCellDef="let row"> |
||||
{{row.job_id}} |
||||
</td> |
||||
</ng-container> |
||||
|
||||
<ng-container matColumnDef="state"> |
||||
<th mat-header-cell *matHeaderCellDef>State</th> |
||||
<td mat-cell *matCellDef="let row"> |
||||
{{row.state}} {{(row.state === "Finished") ? '(' + row.retcode + ')' : ''}} |
||||
</td> |
||||
</ng-container> |
||||
|
||||
<ng-container matColumnDef="last_updated"> |
||||
<th mat-header-cell *matHeaderCellDef>ID</th> |
||||
<td mat-cell *matCellDef="let row"> |
||||
{{row.updated.secs_since_epoch * 1000| date:'long'}} |
||||
</td> |
||||
</ng-container> |
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> |
||||
<tr mat-row class="data-table-row" *matRowDef="let row; columns: displayedColumns;"></tr> |
||||
<tr class="mat-row" *matNoDataRow> |
||||
<td class="mat-cell">No data</td> |
||||
</tr> |
||||
</table> |
||||
</div> |
||||
|
||||
<!-- <mat-paginator [length]="resultsLength" [pageSize]="30" aria-label="Select page of GitHub search results"> |
||||
</mat-paginator> --> |
||||
</div> |
@ -1,37 +0,0 @@ |
||||
<div class="mat-elevation-z8"> |
||||
|
||||
<div class="table-container"> |
||||
<div class="loading-shade" *ngIf="isLoadingResults"> |
||||
<mat-spinner *ngIf="isLoadingResults"></mat-spinner> |
||||
</div> |
||||
<mat-form-field appearance="standard"> |
||||
<mat-label>Filter</mat-label> |
||||
<input matInput (keyup)="apply_filter($event)" #input> |
||||
</mat-form-field> |
||||
<button id="refresh_btn" mat-raised-button color="primary" (click)="fetch_many()">Refresh</button> |
||||
|
||||
<table mat-table [dataSource]="table_data" class="data-table" matSort matSortActive="id" matSortDisableClear |
||||
matSortDirection="desc"> |
||||
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.def"> |
||||
<th mat-header-cell *matHeaderCellDef> |
||||
{{column.name}} |
||||
</th> |
||||
<td mat-cell *matCellDef="let row"> |
||||
{{column.cell(row)}} |
||||
</td> |
||||
</ng-container> |
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> |
||||
<tr mat-row class="data-table-row" *matRowDef="let row; columns: displayedColumns;" |
||||
(click)="show_item_dialog(row)"></tr> |
||||
|
||||
<tr class="mat-row" *matNoDataRow> |
||||
<td class="mat-cell">No data</td> |
||||
</tr> |
||||
</table> |
||||
</div> |
||||
|
||||
<!-- <mat-paginator [length]="resultsLength" [pageSize]="30" aria-label="Select page of GitHub search results"> |
||||
</mat-paginator> --> |
||||
</div> |
@ -1,3 +1,7 @@ |
||||
export function epochToStr(epoch: number): string { |
||||
return new Date(epoch * 1000).toLocaleString('en-GB') |
||||
} |
||||
|
||||
export function emitErr(e: any) { |
||||
alert(e) |
||||
} |
Loading…
Reference in new issue