* temporarily disabled u_run * clean unused deps a bit * disable daemonizing in release mode because wtf * use one async runtime in u_panel * fix cors issues * fix passing cmd from frontend * initial pretty web interface * remove storing errors in db * check target and payload platform * prepare for cross compile to windows binarypull/1/head
							parent
							
								
									a50e6d242f
								
							
						
					
					
						commit
						c25fa780bf
					
				
				 46 changed files with 550 additions and 308 deletions
			
			
		@ -1,49 +1,11 @@ | 
				
			|||||||
<mat-tab-group animationDuration="0ms" mat-align-tabs="center"> | 
					<mat-tab-group animationDuration="0ms" mat-align-tabs="center"> | 
				
			||||||
  <mat-tab label="Agents"> | 
					  <mat-tab label="Agents"> | 
				
			||||||
    <div class="example-container mat-elevation-z8"> | 
					    <agent-table></agent-table> | 
				
			||||||
      <div class="example-loading-shade" *ngIf="isLoadingResults"> | 
					  </mat-tab> | 
				
			||||||
        <mat-spinner *ngIf="isLoadingResults"></mat-spinner> | 
					  <mat-tab label="Jobs"> | 
				
			||||||
      </div> | 
					    <job-table></job-table> | 
				
			||||||
 | 
					  </mat-tab> | 
				
			||||||
      <div class="example-table-container"> | 
					  <mat-tab label="Results"> | 
				
			||||||
 | 
					    <result-table></result-table> | 
				
			||||||
        <table mat-table [dataSource]="table_data" class="example-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@hostname</th> | 
					 | 
				
			||||||
            <td mat-cell *matCellDef="let row">{{row.username}}@{{row.hostname}}</td> | 
					 | 
				
			||||||
          </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <ng-container matColumnDef="last_active"> | 
					 | 
				
			||||||
            <th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> | 
					 | 
				
			||||||
              Last active | 
					 | 
				
			||||||
            </th> | 
					 | 
				
			||||||
            <td mat-cell *matCellDef="let row">{{row.last_active}}</td> | 
					 | 
				
			||||||
          </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> | 
					 | 
				
			||||||
          <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> | 
					 | 
				
			||||||
        </table> | 
					 | 
				
			||||||
        <button mat-raised-button (click)="fetch_agents()">Refresh</button> | 
					 | 
				
			||||||
      </div> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      <!-- <mat-paginator [length]="resultsLength" [pageSize]="30" aria-label="Select page of GitHub search results"> | 
					 | 
				
			||||||
      </mat-paginator> --> | 
					 | 
				
			||||||
    </div> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  </mat-tab> | 
					  </mat-tab> | 
				
			||||||
  <mat-tab label="Jobs"></mat-tab> | 
					 | 
				
			||||||
  <mat-tab label="Results"></mat-tab> | 
					 | 
				
			||||||
</mat-tab-group> | 
					</mat-tab-group> | 
				
			||||||
@ -1,82 +1,9 @@ | 
				
			|||||||
import { HttpClient } from '@angular/common/http'; | 
					 | 
				
			||||||
import { Component, ViewChild, AfterViewInit } from '@angular/core'; | 
					import { Component, ViewChild, AfterViewInit } from '@angular/core'; | 
				
			||||||
import { timer, Observable, of as observableOf } from 'rxjs'; | 
					 | 
				
			||||||
import { catchError, map, startWith, switchMap } from 'rxjs/operators'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Agent { | 
					 | 
				
			||||||
  alias: string | null, | 
					 | 
				
			||||||
  hostname: string, | 
					 | 
				
			||||||
  id: string, | 
					 | 
				
			||||||
  is_root: boolean, | 
					 | 
				
			||||||
  is_root_allowed: boolean, | 
					 | 
				
			||||||
  last_active: Date, | 
					 | 
				
			||||||
  platform: string, | 
					 | 
				
			||||||
  regtime: Date, | 
					 | 
				
			||||||
  state: "new" | "active" | "banned", | 
					 | 
				
			||||||
  token: string | null, | 
					 | 
				
			||||||
  username: string, | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({ | 
					@Component({ | 
				
			||||||
  selector: 'app-root', | 
					  selector: 'app-root', | 
				
			||||||
  templateUrl: './app.component.html', | 
					  templateUrl: './app.component.html', | 
				
			||||||
  styleUrls: ['./app.component.less'] | 
					  styleUrls: ['./app.component.less'] | 
				
			||||||
}) | 
					}) | 
				
			||||||
export class AppComponent implements AfterViewInit { | 
					export class AppComponent { | 
				
			||||||
  displayedColumns: string[] = ['id', 'alias', 'username', 'last_active']; | 
					 | 
				
			||||||
  exampleDatabase!: ExampleHttpDatabase | null; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  table_data: Agent[] = []; | 
					 | 
				
			||||||
  isLoadingResults = true; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(private _httpClient: HttpClient) { } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ngAfterViewInit() { | 
					 | 
				
			||||||
    this.exampleDatabase = new ExampleHttpDatabase(this._httpClient); | 
					 | 
				
			||||||
    this.fetch_agents(); | 
					 | 
				
			||||||
    // If the user changes the sort order, reset back to the first page.
 | 
					 | 
				
			||||||
    //this.sort.sortChange.subscribe(() => (this.paginator.pageIndex = 0));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  fetch_agents() { | 
					 | 
				
			||||||
    timer(0) | 
					 | 
				
			||||||
      .pipe( | 
					 | 
				
			||||||
        startWith({}), | 
					 | 
				
			||||||
        switchMap(() => { | 
					 | 
				
			||||||
          this.isLoadingResults = true; | 
					 | 
				
			||||||
          return this.exampleDatabase!.getAgents().pipe(catchError(() => observableOf(null))); | 
					 | 
				
			||||||
        }), | 
					 | 
				
			||||||
        map(data => { | 
					 | 
				
			||||||
          // Flip flag to show that loading has finished.
 | 
					 | 
				
			||||||
          this.isLoadingResults = false; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          if (data === null) { | 
					 | 
				
			||||||
            return []; | 
					 | 
				
			||||||
          } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          // Only refresh the result length if there is new data. In case of rate
 | 
					 | 
				
			||||||
          // limit errors, we do not want to reset the paginator to zero, as that
 | 
					 | 
				
			||||||
          // would prevent users from re-triggering requests.
 | 
					 | 
				
			||||||
          return data.data; | 
					 | 
				
			||||||
        }), | 
					 | 
				
			||||||
      ) | 
					 | 
				
			||||||
      .subscribe(data => { if (typeof data !== 'string') { this.table_data = data } else { alert(`Error: ${data}`) } }); | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface ServerResponse<T> { | 
					 | 
				
			||||||
  status: "ok" | "err", | 
					 | 
				
			||||||
  data: T | string | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ExampleHttpDatabase { | 
					 | 
				
			||||||
  constructor(private _httpClient: HttpClient) { } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  getAgents(): Observable<ServerResponse<Agent[]>> { | 
					 | 
				
			||||||
    const requestUrl = "/cmd/"; | 
					 | 
				
			||||||
    const cmd = "agents list"; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return this._httpClient.post<ServerResponse<Agent[]>>(requestUrl, cmd); | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
} | 
					} | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1 @@ | 
				
			|||||||
 | 
					export * from './services'; 
 | 
				
			||||||
@ -0,0 +1,15 @@ | 
				
			|||||||
 | 
					import { UTCDate } from "."; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface AgentModel { | 
				
			||||||
 | 
					    alias: string | null, | 
				
			||||||
 | 
					    hostname: string, | 
				
			||||||
 | 
					    id: string, | 
				
			||||||
 | 
					    is_root: boolean, | 
				
			||||||
 | 
					    is_root_allowed: boolean, | 
				
			||||||
 | 
					    last_active: UTCDate, | 
				
			||||||
 | 
					    platform: string, | 
				
			||||||
 | 
					    regtime: UTCDate, | 
				
			||||||
 | 
					    state: "new" | "active" | "banned", | 
				
			||||||
 | 
					    token: string | null, | 
				
			||||||
 | 
					    username: string, | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,8 @@ | 
				
			|||||||
 | 
					export * from './agent.model'; | 
				
			||||||
 | 
					export * from './result.model'; | 
				
			||||||
 | 
					export * from './job.model'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface UTCDate { | 
				
			||||||
 | 
					    secs_since_epoch: number, | 
				
			||||||
 | 
					    nanos_since_epoch: number | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,8 @@ | 
				
			|||||||
 | 
					export interface JobModel { | 
				
			||||||
 | 
					    alias: string, | 
				
			||||||
 | 
					    argv: string, | 
				
			||||||
 | 
					    id: string, | 
				
			||||||
 | 
					    exec_type: string, | 
				
			||||||
 | 
					    platform: string, | 
				
			||||||
 | 
					    payload: Uint8Array | null, | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,13 @@ | 
				
			|||||||
 | 
					import { UTCDate } from "."; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ResultModel { | 
				
			||||||
 | 
					    agent_id: string, | 
				
			||||||
 | 
					    alias: string, | 
				
			||||||
 | 
					    created: UTCDate, | 
				
			||||||
 | 
					    id: string, | 
				
			||||||
 | 
					    job_id: string, | 
				
			||||||
 | 
					    result: Uint8Array, | 
				
			||||||
 | 
					    state: "Queued" | "Running" | "Finished", | 
				
			||||||
 | 
					    retcode: number | null, | 
				
			||||||
 | 
					    updated: UTCDate, | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,43 @@ | 
				
			|||||||
 | 
					import { Injectable } from '@angular/core'; | 
				
			||||||
 | 
					import { environment } from 'src/environments/environment'; | 
				
			||||||
 | 
					import { HttpClient } from '@angular/common/http'; | 
				
			||||||
 | 
					import { Observable } from 'rxjs'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ServerResponse<T> { | 
				
			||||||
 | 
					  status: "ok" | "err", | 
				
			||||||
 | 
					  data: T | string | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ApiTableService<T> { | 
				
			||||||
 | 
					  area: string; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(private http: HttpClient, area: string) { | 
				
			||||||
 | 
					    this.area = area; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  requestUrl = `${environment.server}/cmd/`; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  req<R>(cmd: string): Observable<ServerResponse<R>> { | 
				
			||||||
 | 
					    return this.http.post<ServerResponse<R>>(this.requestUrl, cmd); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getOne(id: string): Observable<ServerResponse<T>> { | 
				
			||||||
 | 
					    return this.req(`${this.area} read ${id}`) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getMany(): Observable<ServerResponse<T[]>> { | 
				
			||||||
 | 
					    return this.req(`${this.area} read`) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  update(item: T): Observable<ServerResponse<void>> { | 
				
			||||||
 | 
					    return this.req(`${this.area} update '${JSON.stringify(item)}'`) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  delete(id: string): Observable<ServerResponse<void>> { | 
				
			||||||
 | 
					    return this.req(`${this.area} delete ${id}`) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  create(item: string): Observable<ServerResponse<void>> { | 
				
			||||||
 | 
					    return this.req(`${this.area} create ${item}`) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1 @@ | 
				
			|||||||
 | 
					export * from './api.service' | 
				
			||||||
@ -0,0 +1,41 @@ | 
				
			|||||||
 | 
					import { Component, OnInit } from '@angular/core'; | 
				
			||||||
 | 
					import { TablesComponent } from './table.component'; | 
				
			||||||
 | 
					import { AgentModel } from '../models'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component({ | 
				
			||||||
 | 
					  selector: 'agent-table', | 
				
			||||||
 | 
					  templateUrl: './table.component.html', | 
				
			||||||
 | 
					  styleUrls: ['./table.component.less'] | 
				
			||||||
 | 
					}) | 
				
			||||||
 | 
					export class AgentComponent extends TablesComponent<AgentModel> { | 
				
			||||||
 | 
					  area = 'agents' as const; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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) => `${cell.last_active.secs_since_epoch}` | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					  ] | 
				
			||||||
 | 
					  displayedColumns = this.columns.map((c) => c.def); | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,3 @@ | 
				
			|||||||
 | 
					export * from './agent.component'; | 
				
			||||||
 | 
					export * from './job.component'; | 
				
			||||||
 | 
					export * from './result.component'; | 
				
			||||||
@ -0,0 +1,46 @@ | 
				
			|||||||
 | 
					import { Component, OnInit } from '@angular/core'; | 
				
			||||||
 | 
					import { TablesComponent } from './table.component'; | 
				
			||||||
 | 
					import { JobModel } from '../models'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component({ | 
				
			||||||
 | 
					  selector: 'job-table', | 
				
			||||||
 | 
					  templateUrl: './table.component.html', | 
				
			||||||
 | 
					  styleUrls: ['./table.component.less'] | 
				
			||||||
 | 
					}) | 
				
			||||||
 | 
					export class JobComponent extends TablesComponent<JobModel> { | 
				
			||||||
 | 
					  area = 'jobs' as const; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  columns = [ | 
				
			||||||
 | 
					    { | 
				
			||||||
 | 
					      def: "id", | 
				
			||||||
 | 
					      name: "ID", | 
				
			||||||
 | 
					      cell: (cell: JobModel) => `${cell.id}` | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					    { | 
				
			||||||
 | 
					      def: "alias", | 
				
			||||||
 | 
					      name: "Alias", | 
				
			||||||
 | 
					      cell: (cell: JobModel) => `${cell.alias}` | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					    { | 
				
			||||||
 | 
					      def: "argv", | 
				
			||||||
 | 
					      name: "Cmd-line args", | 
				
			||||||
 | 
					      cell: (cell: JobModel) => `${cell.argv}` | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					    { | 
				
			||||||
 | 
					      def: "platform", | 
				
			||||||
 | 
					      name: "Platform", | 
				
			||||||
 | 
					      cell: (cell: JobModel) => `${cell.platform}` | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					    { | 
				
			||||||
 | 
					      def: "payload", | 
				
			||||||
 | 
					      name: "Payload", | 
				
			||||||
 | 
					      cell: (cell: JobModel) => `${cell.payload}` | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					    { | 
				
			||||||
 | 
					      def: "etype", | 
				
			||||||
 | 
					      name: "Type", | 
				
			||||||
 | 
					      cell: (cell: JobModel) => `${cell.exec_type}` | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					  ] | 
				
			||||||
 | 
					  displayedColumns = this.columns.map((c) => c.def); | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,46 @@ | 
				
			|||||||
 | 
					import { Component, OnInit } from '@angular/core'; | 
				
			||||||
 | 
					import { TablesComponent } from './table.component'; | 
				
			||||||
 | 
					import { ResultModel } from '../models'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component({ | 
				
			||||||
 | 
					  selector: 'result-table', | 
				
			||||||
 | 
					  templateUrl: './table.component.html', | 
				
			||||||
 | 
					  styleUrls: ['./table.component.less'] | 
				
			||||||
 | 
					}) | 
				
			||||||
 | 
					export class ResultComponent extends TablesComponent<ResultModel> { | 
				
			||||||
 | 
					  area = 'map' as const; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  columns = [ | 
				
			||||||
 | 
					    { | 
				
			||||||
 | 
					      def: "id", | 
				
			||||||
 | 
					      name: "ID", | 
				
			||||||
 | 
					      cell: (cell: ResultModel) => `${cell.id}` | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					    { | 
				
			||||||
 | 
					      def: "alias", | 
				
			||||||
 | 
					      name: "Alias", | 
				
			||||||
 | 
					      cell: (cell: ResultModel) => `${cell.alias}` | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					    { | 
				
			||||||
 | 
					      def: "agent_id", | 
				
			||||||
 | 
					      name: "Agent ID", | 
				
			||||||
 | 
					      cell: (cell: ResultModel) => `${cell.agent_id}` | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					    { | 
				
			||||||
 | 
					      def: "job_id", | 
				
			||||||
 | 
					      name: "Job ID", | 
				
			||||||
 | 
					      cell: (cell: ResultModel) => `${cell.job_id}` | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					    { | 
				
			||||||
 | 
					      def: "state", | 
				
			||||||
 | 
					      name: "State", | 
				
			||||||
 | 
					      cell: (cell: ResultModel) => `${cell.state} `.concat((cell.state === "Finished") ? `(${cell.retcode})` : '') | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					    { | 
				
			||||||
 | 
					      def: "last_updated", | 
				
			||||||
 | 
					      name: "Last updated", | 
				
			||||||
 | 
					      cell: (cell: ResultModel) => `${cell.updated.secs_since_epoch}` | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					  ] | 
				
			||||||
 | 
					  displayedColumns = this.columns.map((c) => c.def); | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,36 @@ | 
				
			|||||||
 | 
					<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 *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,24 @@ | 
				
			|||||||
 | 
					.data-table { | 
				
			||||||
 | 
					    width: 100%; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.table-container { | 
				
			||||||
 | 
					    margin: 50px; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.loading-shade { | 
				
			||||||
 | 
					    position: absolute; | 
				
			||||||
 | 
					    top: 0; | 
				
			||||||
 | 
					    left: 0; | 
				
			||||||
 | 
					    bottom: 56px; | 
				
			||||||
 | 
					    right: 0; | 
				
			||||||
 | 
					    background: rgba(0, 0, 0, 0.15); | 
				
			||||||
 | 
					    z-index: 1; | 
				
			||||||
 | 
					    display: flex; | 
				
			||||||
 | 
					    align-items: center; | 
				
			||||||
 | 
					    justify-content: center; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#refresh_btn { | 
				
			||||||
 | 
					    margin-left: 10px; | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,66 @@ | 
				
			|||||||
 | 
					import { Component, OnInit, Directive } from '@angular/core'; | 
				
			||||||
 | 
					import { timer, of as observableOf } from 'rxjs'; | 
				
			||||||
 | 
					import { catchError, map, startWith, switchMap } from 'rxjs/operators'; | 
				
			||||||
 | 
					import { HttpClient } from '@angular/common/http'; | 
				
			||||||
 | 
					import { ApiTableService } from '../'; | 
				
			||||||
 | 
					import { MatTableDataSource } from '@angular/material/table'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Directive() | 
				
			||||||
 | 
					export abstract class TablesComponent<T> implements OnInit { | 
				
			||||||
 | 
					  abstract area: "agents" | "jobs" | "map"; | 
				
			||||||
 | 
					  data_source!: ApiTableService<T> | null; | 
				
			||||||
 | 
					  table_data!: MatTableDataSource<T>; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  isLoadingResults = true; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(private _httpClient: HttpClient) { | 
				
			||||||
 | 
					    this.table_data = new MatTableDataSource; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ngOnInit() { | 
				
			||||||
 | 
					    this.data_source = new ApiTableService(this._httpClient, this.area); | 
				
			||||||
 | 
					    this.fetch_many(); | 
				
			||||||
 | 
					    // If the user changes the sort order, reset back to the first page.
 | 
				
			||||||
 | 
					    //this.sort.sortChange.subscribe(() => (this.paginator.pageIndex = 0));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fetch_many() { | 
				
			||||||
 | 
					    timer(0) | 
				
			||||||
 | 
					      .pipe( | 
				
			||||||
 | 
					        startWith({}), | 
				
			||||||
 | 
					        switchMap(() => { | 
				
			||||||
 | 
					          this.isLoadingResults = true; | 
				
			||||||
 | 
					          return this.data_source!.getMany().pipe(catchError(() => observableOf(null))); | 
				
			||||||
 | 
					        }), | 
				
			||||||
 | 
					        map(data => { | 
				
			||||||
 | 
					          this.isLoadingResults = false; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (data === null) { | 
				
			||||||
 | 
					            return []; | 
				
			||||||
 | 
					          } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // Only refresh the result length if there is new data. In case of rate
 | 
				
			||||||
 | 
					          // limit errors, we do not want to reset the paginator to zero, as that
 | 
				
			||||||
 | 
					          // would prevent users from re-triggering requests.
 | 
				
			||||||
 | 
					          return data.data; | 
				
			||||||
 | 
					        }), | 
				
			||||||
 | 
					      ) | 
				
			||||||
 | 
					      .subscribe(data => { if (typeof data !== 'string') { this.table_data.data = data } else { alert(`Error: ${data}`) } }); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  apply_filter(event: Event) { | 
				
			||||||
 | 
					    const filterValue = (event.target as HTMLInputElement).value; | 
				
			||||||
 | 
					    this.table_data.filter = filterValue.trim().toLowerCase(); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  abstract columns: ColumnDef<T>[]; | 
				
			||||||
 | 
					  abstract displayedColumns: string[]; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ColumnDef<C> = { | 
				
			||||||
 | 
					  def: string, | 
				
			||||||
 | 
					  name: string, | 
				
			||||||
 | 
					  cell: (cell: C) => string | 
				
			||||||
 | 
					} | 
				
			||||||
@ -1,3 +1,4 @@ | 
				
			|||||||
export const environment = { | 
					export const environment = { | 
				
			||||||
  production: true | 
					  production: true, | 
				
			||||||
 | 
					  server: "" | 
				
			||||||
}; | 
					}; | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,22 @@ | 
				
			|||||||
 | 
					use crate::helpers::ENV; | 
				
			||||||
 | 
					use u_lib::config::MASTER_PORT; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[tokio::test] | 
				
			||||||
 | 
					async fn test_non_auth_connection_dropped() { | 
				
			||||||
 | 
					    let client = reqwest::ClientBuilder::new() | 
				
			||||||
 | 
					        .danger_accept_invalid_certs(true) | 
				
			||||||
 | 
					        .build() | 
				
			||||||
 | 
					        .unwrap(); | 
				
			||||||
 | 
					    match client | 
				
			||||||
 | 
					        .get(format!("https://{}:{}", &ENV.u_server, MASTER_PORT)) | 
				
			||||||
 | 
					        .send() | 
				
			||||||
 | 
					        .await | 
				
			||||||
 | 
					    { | 
				
			||||||
 | 
					        Err(e) => { | 
				
			||||||
 | 
					            let err = e.to_string(); | 
				
			||||||
 | 
					            println!("captured err: {err}"); | 
				
			||||||
 | 
					            assert!(err.contains("certificate required")); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        _ => panic!("no error occured on foreign client connection"), | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -1 +1,2 @@ | 
				
			|||||||
mod behaviour; | 
					mod behaviour; | 
				
			||||||
 | 
					mod connection; | 
				
			||||||
 | 
				
			|||||||
@ -1,35 +0,0 @@ | 
				
			|||||||
use crate::models::schema::*; | 
					 | 
				
			||||||
use diesel::{Insertable, Queryable}; | 
					 | 
				
			||||||
use serde::{Deserialize, Serialize}; | 
					 | 
				
			||||||
use std::time::SystemTime; | 
					 | 
				
			||||||
use uuid::Uuid; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Serialize, Deserialize, Clone, Debug, Queryable, Insertable, PartialEq)] | 
					 | 
				
			||||||
#[table_name = "errors"] | 
					 | 
				
			||||||
pub struct AgentError { | 
					 | 
				
			||||||
    pub agent_id: Uuid, | 
					 | 
				
			||||||
    pub created: SystemTime, | 
					 | 
				
			||||||
    pub id: Uuid, | 
					 | 
				
			||||||
    pub msg: String, | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl AgentError { | 
					 | 
				
			||||||
    pub fn from_msg(msg: impl Into<String>, agent_id: Uuid) -> Self { | 
					 | 
				
			||||||
        AgentError { | 
					 | 
				
			||||||
            agent_id, | 
					 | 
				
			||||||
            msg: msg.into(), | 
					 | 
				
			||||||
            ..Default::default() | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Default for AgentError { | 
					 | 
				
			||||||
    fn default() -> Self { | 
					 | 
				
			||||||
        Self { | 
					 | 
				
			||||||
            agent_id: Uuid::new_v4(), | 
					 | 
				
			||||||
            created: SystemTime::now(), | 
					 | 
				
			||||||
            id: Uuid::new_v4(), | 
					 | 
				
			||||||
            msg: String::new(), | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,6 +1,5 @@ | 
				
			|||||||
mod agent; | 
					mod agent; | 
				
			||||||
mod error; | 
					 | 
				
			||||||
mod jobs; | 
					mod jobs; | 
				
			||||||
pub mod schema; | 
					pub mod schema; | 
				
			||||||
 | 
					
 | 
				
			||||||
pub use crate::models::{agent::*, error::*, jobs::*}; | 
					pub use crate::models::{agent::*, jobs::*}; | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,38 @@ | 
				
			|||||||
 | 
					use guess_host_triple::guess_host_triple; | 
				
			||||||
 | 
					use platforms::{Platform as _Platform, PlatformReq}; | 
				
			||||||
 | 
					use serde::Deserialize; | 
				
			||||||
 | 
					use std::str::FromStr; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Deserialize)] | 
				
			||||||
 | 
					pub struct Platform(String); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Platform { | 
				
			||||||
 | 
					    pub fn current() -> Platform { | 
				
			||||||
 | 
					        Self(guess_host_triple().unwrap_or("unknown").to_string()) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn matches(&self, pf: impl AsRef<str>) -> bool { | 
				
			||||||
 | 
					        match PlatformReq::from_str(pf.as_ref()) { | 
				
			||||||
 | 
					            Ok(p) => p.matches(&_Platform::find(&self.0).unwrap()), | 
				
			||||||
 | 
					            Err(_) => false, | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn check(&self) -> bool { | 
				
			||||||
 | 
					        PlatformReq::from_str(&self.0).is_ok() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn into_string(self) -> String { | 
				
			||||||
 | 
					        self.0 | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn any() -> Platform { | 
				
			||||||
 | 
					        Self(String::from("*")) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Default for Platform { | 
				
			||||||
 | 
					    fn default() -> Self { | 
				
			||||||
 | 
					        Self::any() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
					Loading…
					
					
				
		Reference in new issue