Compare commits
No commits in common. 'c8ce2aca6078dc89182bf8ac6732e1194118779f' and '7e267b0074c1985f8109abf91eff62bd48314729' have entirely different histories.
c8ce2aca60
...
7e267b0074
125 changed files with 2177 additions and 3623 deletions
File diff suppressed because it is too large
Load Diff
@ -1,36 +0,0 @@ |
|||||||
import { Component, Inject } from '@angular/core'; |
|
||||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog'; |
|
||||||
import { AssignedJobByIdModel } from 'src/app/models'; |
|
||||||
import { ApiTableService } from '../../../services'; |
|
||||||
|
|
||||||
@Component({ |
|
||||||
selector: 'assign-job-dialog', |
|
||||||
templateUrl: 'assign-job-dialog.component.html', |
|
||||||
styleUrls: [] |
|
||||||
}) |
|
||||||
export class AssignJobDialogComponent { |
|
||||||
rows: string[] = []; |
|
||||||
selected_rows: string[] = []; |
|
||||||
|
|
||||||
constructor( |
|
||||||
@Inject(MAT_DIALOG_DATA) public agent_id: string, |
|
||||||
private dataSource: ApiTableService, |
|
||||||
) { |
|
||||||
dataSource.getJobs().subscribe(resp => { |
|
||||||
this.rows = resp.map(j => `${j.id} ${j.alias}`) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
assignSelectedJobs() { |
|
||||||
const assigned_jobs: AssignedJobByIdModel[] = this.selected_rows.map(row => { |
|
||||||
const job_id = row.split(' ', 1)[0]; |
|
||||||
return { |
|
||||||
job_id: job_id, |
|
||||||
agent_id: this.agent_id |
|
||||||
} |
|
||||||
}); |
|
||||||
this.dataSource.createResult(assigned_jobs).subscribe(_ => { |
|
||||||
alert("Created") |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
@ -1,5 +0,0 @@ |
|||||||
export * from './agent-info-dialog/agent-info-dialog.component'; |
|
||||||
export * from './result-info-dialog/result-info-dialog.component'; |
|
||||||
export * from './job-info-dialog/job-info-dialog.component'; |
|
||||||
export * from './assign-job-dialog/assign-job-dialog.component'; |
|
||||||
export * from './payload-info-dialog/payload-info-dialog.component'; |
|
@ -1,48 +0,0 @@ |
|||||||
<h2 mat-dialog-title *ngIf="isPreview">Job info</h2> |
|
||||||
<h2 mat-dialog-title *ngIf="!isPreview">Editing job info</h2> |
|
||||||
<mat-dialog-content> |
|
||||||
<div class="info-dialog-forms-box-smol"> |
|
||||||
<mat-form-field class="info-dlg-field"> |
|
||||||
<mat-label>ID</mat-label> |
|
||||||
<input matInput disabled value="{{data.meta.id}}"> |
|
||||||
</mat-form-field> |
|
||||||
<mat-form-field class="info-dlg-field"> |
|
||||||
<mat-label>Alias</mat-label> |
|
||||||
<input matInput [readonly]="isPreview" [(ngModel)]="data.meta.alias"> |
|
||||||
</mat-form-field> |
|
||||||
<mat-form-field class="info-dlg-field"> |
|
||||||
<mat-label>Args</mat-label> |
|
||||||
<input matInput [readonly]="isPreview" [(ngModel)]="data.meta.argv"> |
|
||||||
</mat-form-field> |
|
||||||
</div> |
|
||||||
<div class="info-dialog-forms-box-smol"> |
|
||||||
<mat-form-field class="info-dlg-field"> |
|
||||||
<mat-label>Type</mat-label> |
|
||||||
<input matInput [readonly]="isPreview" [(ngModel)]="data.meta.exec_type"> |
|
||||||
</mat-form-field> |
|
||||||
<mat-form-field class="info-dlg-field"> |
|
||||||
<mat-label>Platform</mat-label> |
|
||||||
<input matInput [readonly]="isPreview" [(ngModel)]="data.meta.target_platforms"> |
|
||||||
</mat-form-field> |
|
||||||
<mat-form-field class="info-dlg-field"> |
|
||||||
<mat-label>Schedule</mat-label> |
|
||||||
<input matInput [readonly]="isPreview" [(ngModel)]="data.meta.schedule"> |
|
||||||
</mat-form-field> |
|
||||||
</div> |
|
||||||
<div class="info-dialog-forms-box"> |
|
||||||
<mat-form-field class="info-dlg-field"> |
|
||||||
<mat-label>Payload</mat-label> |
|
||||||
<mat-select [disabled]="isPreview" [(value)]="data.meta.payload_id"> |
|
||||||
<mat-option *ngFor="let pld of allPayloads" [value]="pld[0]">{{ pld[1] }}</mat-option> |
|
||||||
</mat-select> |
|
||||||
</mat-form-field> |
|
||||||
</div> |
|
||||||
<div class="info-dialog-forms-box"> |
|
||||||
<payload-overview *ngIf="data.payload" [preview]="true" [payload]="data.payload.data"></payload-overview> |
|
||||||
</div> |
|
||||||
</mat-dialog-content> |
|
||||||
<mat-dialog-actions align="end"> |
|
||||||
<button mat-raised-button *ngIf="isPreview" (click)="isPreview = false">Edit</button> |
|
||||||
<button mat-raised-button *ngIf="!isPreview" (click)="updateJob()">Save</button> |
|
||||||
<button mat-button mat-dialog-close>Cancel</button> |
|
||||||
</mat-dialog-actions> |
|
@ -1,28 +0,0 @@ |
|||||||
import { Component, Inject } from '@angular/core'; |
|
||||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog'; |
|
||||||
import { EventEmitter } from '@angular/core'; |
|
||||||
import { Job, JobModel } from '../../../models/job.model'; |
|
||||||
import { ApiTableService } from 'src/app/services'; |
|
||||||
|
|
||||||
@Component({ |
|
||||||
selector: 'job-info-dialog', |
|
||||||
templateUrl: 'job-info-dialog.component.html', |
|
||||||
styleUrls: ['../base-info-dialog.component.less'] |
|
||||||
}) |
|
||||||
export class JobInfoDialogComponent { |
|
||||||
//[id, name]
|
|
||||||
isPreview = true; |
|
||||||
allPayloads: [string | null, string][] = [[null, "none"]]; |
|
||||||
|
|
||||||
onSave = new EventEmitter<JobModel>(); |
|
||||||
|
|
||||||
constructor(@Inject(MAT_DIALOG_DATA) public data: Job, dataSource: ApiTableService) { |
|
||||||
dataSource.getPayloads().subscribe(resp => { |
|
||||||
this.allPayloads = this.allPayloads.concat(resp.map(r => [r.id, r.name])) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
updateJob() { |
|
||||||
this.onSave.emit(this.data.meta); |
|
||||||
} |
|
||||||
} |
|
@ -1,20 +0,0 @@ |
|||||||
<h2 mat-dialog-title>New payload</h2> |
|
||||||
<mat-dialog-content> |
|
||||||
<div class="info-dialog-forms-box-smol"> |
|
||||||
<mat-form-field class="info-dlg-field" cdkFocusInitial> |
|
||||||
<mat-label>Name</mat-label> |
|
||||||
<input matInput [(ngModel)]="payload.name"> |
|
||||||
</mat-form-field> |
|
||||||
<input type="file" class="file-input" (change)="onFileSelected($event)" #fileUpload> |
|
||||||
</div> |
|
||||||
<div class="info-dialog-forms-box"> |
|
||||||
<mat-form-field class="info-dlg-field" *ngIf="!uploadMode"> |
|
||||||
<mat-label>Data</mat-label> |
|
||||||
<textarea matInput [(ngModel)]="decodedPayload"></textarea> |
|
||||||
</mat-form-field> |
|
||||||
</div> |
|
||||||
</mat-dialog-content> |
|
||||||
<mat-dialog-actions align="end"> |
|
||||||
<button mat-raised-button (click)="save()">Save</button> |
|
||||||
<button mat-button mat-dialog-close>Close</button> |
|
||||||
</mat-dialog-actions> |
|
@ -1,43 +0,0 @@ |
|||||||
import { Component, EventEmitter, Inject } from '@angular/core'; |
|
||||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog'; |
|
||||||
import { NewPayloadModel } from 'src/app/models/payload.model'; |
|
||||||
|
|
||||||
@Component({ |
|
||||||
selector: 'new-payload-dialog', |
|
||||||
templateUrl: 'new-payload-dialog.component.html', |
|
||||||
styleUrls: ['../base-info-dialog.component.less'] |
|
||||||
}) |
|
||||||
export class NewPayloadDialogComponent { |
|
||||||
decodedPayload = ""; |
|
||||||
uploadMode = false; |
|
||||||
onSave = new EventEmitter<NewPayloadModel>(); |
|
||||||
|
|
||||||
constructor(@Inject(MAT_DIALOG_DATA) public payload: NewPayloadModel) { } |
|
||||||
|
|
||||||
save() { |
|
||||||
if (this.payload.data.length == 0) { |
|
||||||
this.payload.data = Array.from(new TextEncoder().encode(this.decodedPayload)); |
|
||||||
} |
|
||||||
this.onSave.emit(this.payload); |
|
||||||
} |
|
||||||
|
|
||||||
onFileSelected(event: any) { |
|
||||||
const file: File = event.target.files[0]; |
|
||||||
if (file) { |
|
||||||
this.uploadMode = true |
|
||||||
const reader = new FileReader(); |
|
||||||
reader.onload = e => { |
|
||||||
this.payload.name = file.name; |
|
||||||
const result = e.target?.result; |
|
||||||
if (result instanceof ArrayBuffer) { |
|
||||||
const d = Array.from(new Uint8Array(result)); |
|
||||||
this.payload.data = d; |
|
||||||
console.log(this.payload.data) |
|
||||||
} else { |
|
||||||
alert!("no file") |
|
||||||
} |
|
||||||
} |
|
||||||
reader.readAsArrayBuffer(file) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,34 +0,0 @@ |
|||||||
<h2 mat-dialog-title *ngIf="isPreview">Payload</h2> |
|
||||||
<h2 mat-dialog-title *ngIf="!isPreview">Editing payload</h2> |
|
||||||
<mat-dialog-content> |
|
||||||
<div class="info-dialog-forms-box"> |
|
||||||
<div class="info-dialog-forms-box-smol"> |
|
||||||
<mat-form-field class="info-dlg-field" cdkFocusInitial> |
|
||||||
<mat-label>ID</mat-label> |
|
||||||
<input matInput disabled value="{{payload.id}}"> |
|
||||||
</mat-form-field> |
|
||||||
<mat-form-field class="info-dlg-field"> |
|
||||||
<mat-label>Name</mat-label> |
|
||||||
<input matInput [readonly]="isPreview" [(ngModel)]="payload.name"> |
|
||||||
</mat-form-field> |
|
||||||
</div> |
|
||||||
<div class="info-dialog-forms-box-smol"> |
|
||||||
<mat-form-field class="info-dlg-field"> |
|
||||||
<mat-label>MIME-type</mat-label> |
|
||||||
<input matInput disabled value="{{payload.mime_type}}"> |
|
||||||
</mat-form-field> |
|
||||||
<mat-form-field class="info-dlg-field"> |
|
||||||
<mat-label>Size</mat-label> |
|
||||||
<input matInput disabled value="{{payload.size}}"> |
|
||||||
</mat-form-field> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
<div class="info-dialog-forms-box"> |
|
||||||
<payload-overview [preview]="isPreview" [payload]="payload.data"></payload-overview> |
|
||||||
</div> |
|
||||||
</mat-dialog-content> |
|
||||||
<mat-dialog-actions align="end"> |
|
||||||
<button mat-raised-button *ngIf="isPreview" (click)="isPreview = false">Edit</button> |
|
||||||
<button mat-raised-button *ngIf="!isPreview" (click)="updatePayload()">Save</button> |
|
||||||
<button mat-button mat-dialog-close>Close</button> |
|
||||||
</mat-dialog-actions> |
|
@ -1,20 +0,0 @@ |
|||||||
import { Component, EventEmitter, Inject } from '@angular/core'; |
|
||||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog'; |
|
||||||
import { PayloadModel } from 'src/app/models/payload.model'; |
|
||||||
|
|
||||||
@Component({ |
|
||||||
selector: 'payload-info-dialog', |
|
||||||
templateUrl: 'payload-info-dialog.component.html', |
|
||||||
styleUrls: ['../base-info-dialog.component.less'] |
|
||||||
}) |
|
||||||
export class PayloadInfoDialogComponent { |
|
||||||
isPreview = true; |
|
||||||
|
|
||||||
onSave = new EventEmitter<PayloadModel>(); |
|
||||||
|
|
||||||
constructor(@Inject(MAT_DIALOG_DATA) public payload: PayloadModel) { } |
|
||||||
|
|
||||||
updatePayload() { |
|
||||||
this.onSave.emit(this.payload); |
|
||||||
} |
|
||||||
} |
|
@ -1,34 +0,0 @@ |
|||||||
import { Component, OnInit } from '@angular/core'; |
|
||||||
import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar'; |
|
||||||
import { ErrorService } from 'src/app/services/error.service'; |
|
||||||
|
|
||||||
@Component({ |
|
||||||
selector: 'global-error', |
|
||||||
templateUrl: './global-error.component.html', |
|
||||||
styleUrls: ['./global-error.component.less'] |
|
||||||
}) |
|
||||||
export class GlobalErrorComponent implements OnInit { |
|
||||||
|
|
||||||
constructor( |
|
||||||
private snackBar: MatSnackBar, |
|
||||||
private errorService: ErrorService |
|
||||||
) { } |
|
||||||
|
|
||||||
ngOnInit() { |
|
||||||
this.errorService.error$.subscribe(err => { |
|
||||||
const _config = (duration: number): MatSnackBarConfig => { |
|
||||||
return { |
|
||||||
horizontalPosition: 'right', |
|
||||||
verticalPosition: 'bottom', |
|
||||||
duration |
|
||||||
} |
|
||||||
} |
|
||||||
const error = true; |
|
||||||
const cfg = error ? _config(0) : _config(2000) |
|
||||||
|
|
||||||
if (err != '') { |
|
||||||
this.snackBar.open(err, 'Ok', cfg) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
@ -1,6 +0,0 @@ |
|||||||
<mat-form-field class="info-dlg-field" floatLabel="always"> |
|
||||||
<mat-label>Payload data</mat-label> |
|
||||||
<textarea matInput cdkTextareaAutosize *ngIf="!isTooBigPayload" [readonly]="isPreview" [(ngModel)]="decodedPayload"> |
|
||||||
</textarea> |
|
||||||
<input matInput *ngIf="isTooBigPayload" disabled placeholder="Payload is too big to display"> |
|
||||||
</mat-form-field> |
|
@ -1,21 +0,0 @@ |
|||||||
import { Component, Input, OnInit } from '@angular/core'; |
|
||||||
|
|
||||||
@Component({ |
|
||||||
selector: 'payload-overview', |
|
||||||
templateUrl: './payload-overview.component.html', |
|
||||||
styleUrls: ['./payload-overview.component.less'] |
|
||||||
}) |
|
||||||
export class PayloadOverviewComponent implements OnInit { |
|
||||||
@Input() payload: number[] | null = null; |
|
||||||
@Input("preview") isPreview = true; |
|
||||||
isTooBigPayload = false; |
|
||||||
decodedPayload = ""; |
|
||||||
|
|
||||||
ngOnInit() { |
|
||||||
if (this.payload !== null) { |
|
||||||
this.decodedPayload = new TextDecoder().decode(new Uint8Array(this.payload)) |
|
||||||
} else { |
|
||||||
this.isTooBigPayload = true |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,42 +0,0 @@ |
|||||||
import { Component, OnInit } from '@angular/core'; |
|
||||||
import { TableComponent } from '../base-table/base-table.component'; |
|
||||||
import { AgentModel, Area } from '../../../models'; |
|
||||||
import { AssignJobDialogComponent, AgentInfoDialogComponent } from '../../dialogs'; |
|
||||||
|
|
||||||
@Component({ |
|
||||||
selector: 'agent-table', |
|
||||||
templateUrl: './agent-table.component.html', |
|
||||||
styleUrls: ['../base-table/base-table.component.less'], |
|
||||||
}) |
|
||||||
export class AgentComponent extends TableComponent<AgentModel> implements OnInit { |
|
||||||
area = 'agents' as Area |
|
||||||
displayedColumns = ['id', 'alias', 'username', 'hostname', 'last_active', 'actions'] |
|
||||||
|
|
||||||
showItemDialog(id: string) { |
|
||||||
this.dataSource.getAgent(id).subscribe(resp => { |
|
||||||
const dialog = this.infoDialog.open(AgentInfoDialogComponent, { |
|
||||||
data: resp, |
|
||||||
width: '1000px', |
|
||||||
}); |
|
||||||
|
|
||||||
const saveSub = dialog.componentInstance.onSave.subscribe(result => { |
|
||||||
this.dataSource.updateAgent(result).subscribe(_ => { |
|
||||||
alert('Saved') |
|
||||||
this.loadTableData() |
|
||||||
}) |
|
||||||
}) |
|
||||||
|
|
||||||
dialog.afterClosed().subscribe(result => { |
|
||||||
saveSub.unsubscribe() |
|
||||||
this.router.navigate(['.'], { relativeTo: this.route }) |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
assignJobs(id: string) { |
|
||||||
const dialog = this.infoDialog.open(AssignJobDialogComponent, { |
|
||||||
data: id, |
|
||||||
width: '1000px', |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
@ -1,59 +0,0 @@ |
|||||||
import { OnInit, Directive, Component } from '@angular/core'; |
|
||||||
import { ApiTableService } from '../../..'; |
|
||||||
import { MatTableDataSource } from '@angular/material/table'; |
|
||||||
import { MatDialog } from '@angular/material/dialog'; |
|
||||||
import { ApiModel, Area } from '../../../models'; |
|
||||||
import { ActivatedRoute, Router } from '@angular/router'; |
|
||||||
|
|
||||||
@Directive() |
|
||||||
export abstract class TableComponent<T extends ApiModel> implements OnInit { |
|
||||||
abstract area: Area; |
|
||||||
table_data: MatTableDataSource<T> = new MatTableDataSource; |
|
||||||
isLoadingResults = true; |
|
||||||
|
|
||||||
constructor( |
|
||||||
public dataSource: ApiTableService, |
|
||||||
public infoDialog: MatDialog, |
|
||||||
public route: ActivatedRoute, |
|
||||||
public router: Router, |
|
||||||
) { } |
|
||||||
|
|
||||||
ngOnInit() { |
|
||||||
this.loadTableData(); |
|
||||||
this.route.queryParams.subscribe(params => { |
|
||||||
const id = params['id'] |
|
||||||
const new_item = params['new'] |
|
||||||
if (id) { |
|
||||||
this.showItemDialog(id); |
|
||||||
} |
|
||||||
if (new_item) { |
|
||||||
this.showItemDialog(null); |
|
||||||
} |
|
||||||
}) |
|
||||||
//interval(10000).subscribe(_ => this.loadTableData());
|
|
||||||
} |
|
||||||
|
|
||||||
loadTableData() { |
|
||||||
this.isLoadingResults = true; |
|
||||||
|
|
||||||
this.dataSource.getMany(this.area).subscribe(resp => { |
|
||||||
this.isLoadingResults = false; |
|
||||||
this.table_data.data = resp; |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
applyFilter(event: Event) { |
|
||||||
const filterValue = (event.target as HTMLInputElement).value; |
|
||||||
this.table_data.filter = filterValue.trim().toLowerCase(); |
|
||||||
} |
|
||||||
|
|
||||||
deleteItem(id: string) { |
|
||||||
if (confirm(`Delete ${id}?`)) { |
|
||||||
this.dataSource.delete(id, this.area).subscribe(_ => { }) |
|
||||||
this.loadTableData() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
abstract displayedColumns: string[]; |
|
||||||
abstract showItemDialog(id: string | null): void; |
|
||||||
} |
|
@ -1,5 +0,0 @@ |
|||||||
export * from './agent-table/agent-table.component'; |
|
||||||
export * from './base-table/base-table.component'; |
|
||||||
export * from './job-table/job-table.component'; |
|
||||||
export * from './payload-table/payload-table.component'; |
|
||||||
export * from './result-table/result-table.component'; |
|
@ -1,72 +0,0 @@ |
|||||||
import { Component, OnInit } from '@angular/core'; |
|
||||||
import { TableComponent } from '../base-table/base-table.component'; |
|
||||||
import { Area, JobModel, Job } from '../../../models'; |
|
||||||
import { JobInfoDialogComponent } from '../../dialogs'; |
|
||||||
import { Observable } from 'rxjs'; |
|
||||||
|
|
||||||
@Component({ |
|
||||||
selector: 'job-table', |
|
||||||
templateUrl: './job-table.component.html', |
|
||||||
styleUrls: ['../base-table/base-table.component.less'], |
|
||||||
providers: [{ provide: 'area', useValue: 'jobs' }] |
|
||||||
}) |
|
||||||
export class JobComponent extends TableComponent<JobModel> { |
|
||||||
area = 'jobs' as Area; |
|
||||||
displayedColumns = ['id', 'alias', 'platform', 'schedule', 'exec_type', 'actions'] |
|
||||||
|
|
||||||
showItemDialog(id: string | null) { |
|
||||||
const is_new_job = id === null; |
|
||||||
|
|
||||||
var dialogData$: Observable<Job>; |
|
||||||
|
|
||||||
if (is_new_job) { |
|
||||||
dialogData$ = new Observable(subscriber => { |
|
||||||
var defaultJob: Job = { |
|
||||||
meta: { |
|
||||||
alias: null, |
|
||||||
argv: '', |
|
||||||
exec_type: 'shell', |
|
||||||
target_platforms: '*', |
|
||||||
payload_id: null, |
|
||||||
schedule: null |
|
||||||
}, |
|
||||||
payload: null |
|
||||||
}; |
|
||||||
subscriber.next(defaultJob) |
|
||||||
}) |
|
||||||
} else { |
|
||||||
dialogData$ = this.dataSource.getJob(id) |
|
||||||
} |
|
||||||
|
|
||||||
dialogData$.subscribe(dialogData => { |
|
||||||
const dialog = this.infoDialog.open(JobInfoDialogComponent, { |
|
||||||
data: dialogData, |
|
||||||
width: '1000px', |
|
||||||
}); |
|
||||||
|
|
||||||
dialog.componentInstance.isPreview = !is_new_job; |
|
||||||
|
|
||||||
const saveSub = dialog.componentInstance.onSave.subscribe(result => { |
|
||||||
if (is_new_job) { |
|
||||||
this.dataSource.create(dialogData.meta, this.area) |
|
||||||
.subscribe(_ => { |
|
||||||
alert("Created") |
|
||||||
this.loadTableData() |
|
||||||
}) |
|
||||||
} else { |
|
||||||
this.dataSource.updateJob(result) |
|
||||||
.subscribe(_ => { |
|
||||||
alert("Updated") |
|
||||||
this.loadTableData() |
|
||||||
}) |
|
||||||
} |
|
||||||
dialog.close() |
|
||||||
}) |
|
||||||
|
|
||||||
dialog.afterClosed().subscribe(result => { |
|
||||||
saveSub.unsubscribe() |
|
||||||
this.router.navigate(['.'], { relativeTo: this.route }) |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
@ -1,63 +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)="applyFilter($event)" #input> |
|
||||||
</mat-form-field> |
|
||||||
<button id="refresh_btn" mat-raised-button color="primary" (click)="loadTableData()">Refresh</button> |
|
||||||
<button id="new_btn" mat-raised-button color="primary" routerLink='.' [queryParams]="{new: true}">Add |
|
||||||
payload</button> |
|
||||||
|
|
||||||
|
|
||||||
<table mat-table fixedLayout="true" [dataSource]="table_data" class="data-table" matSort matSortActive="id" |
|
||||||
matSortDisableClear matSortDirection="desc"> |
|
||||||
|
|
||||||
<ng-container matColumnDef="name"> |
|
||||||
<th mat-header-cell *matHeaderCellDef>Name</th> |
|
||||||
<td mat-cell *matCellDef="let row"> |
|
||||||
{{row.name}} |
|
||||||
</td> |
|
||||||
</ng-container> |
|
||||||
|
|
||||||
<ng-container matColumnDef="mime_type"> |
|
||||||
<th mat-header-cell *matHeaderCellDef>MIME-type</th> |
|
||||||
<td mat-cell *matCellDef="let row"> |
|
||||||
{{row.mime_type}} |
|
||||||
</td> |
|
||||||
</ng-container> |
|
||||||
|
|
||||||
<ng-container matColumnDef="size"> |
|
||||||
<th mat-header-cell *matHeaderCellDef>Size</th> |
|
||||||
<td mat-cell *matCellDef="let row"> |
|
||||||
{{row.size}} |
|
||||||
</td> |
|
||||||
</ng-container> |
|
||||||
|
|
||||||
<ng-container matColumnDef="actions"> |
|
||||||
<th mat-header-cell *matHeaderCellDef></th> |
|
||||||
<td mat-cell *matCellDef="let row"> |
|
||||||
<button mat-icon-button routerLink='.' [queryParams]="{id: row.id}"> |
|
||||||
<mat-icon>more_horiz</mat-icon> |
|
||||||
</button> |
|
||||||
| |
|
||||||
<button mat-icon-button (click)="deleteItem(row.id)"> |
|
||||||
<mat-icon>delete</mat-icon> |
|
||||||
</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,67 +0,0 @@ |
|||||||
import { Component } from '@angular/core'; |
|
||||||
import { Area } from 'src/app/models'; |
|
||||||
import { NewPayloadModel, PayloadModel } from 'src/app/models/payload.model'; |
|
||||||
import { PayloadInfoDialogComponent } from '../../dialogs'; |
|
||||||
import { NewPayloadDialogComponent } from '../../dialogs/new-payload-dialog/new-payload-dialog.component'; |
|
||||||
import { TableComponent } from '../base-table/base-table.component'; |
|
||||||
|
|
||||||
@Component({ |
|
||||||
selector: 'payload-table', |
|
||||||
templateUrl: './payload-table.component.html', |
|
||||||
styleUrls: ['../base-table/base-table.component.less'], |
|
||||||
providers: [{ provide: 'area', useValue: 'payloads' }] |
|
||||||
}) |
|
||||||
export class PayloadComponent extends TableComponent<PayloadModel> { |
|
||||||
area = 'payloads' as Area |
|
||||||
displayedColumns = ["name", "mime_type", "size", 'actions']; |
|
||||||
|
|
||||||
showItemDialog(id: string | null) { |
|
||||||
if (id === null) { |
|
||||||
const payload: NewPayloadModel = { |
|
||||||
name: "", |
|
||||||
data: [] |
|
||||||
} |
|
||||||
const dialog = this.infoDialog.open(NewPayloadDialogComponent, { |
|
||||||
data: payload, |
|
||||||
width: '1000px', |
|
||||||
}); |
|
||||||
|
|
||||||
dialog.componentInstance.onSave.subscribe(result => { |
|
||||||
this.dataSource.createPayload(result) |
|
||||||
.subscribe(_ => { |
|
||||||
alert("Created") |
|
||||||
this.loadTableData() |
|
||||||
}) |
|
||||||
|
|
||||||
dialog.close() |
|
||||||
}) |
|
||||||
|
|
||||||
dialog.afterClosed().subscribe(_ => { |
|
||||||
this.router.navigate(['.'], { relativeTo: this.route }) |
|
||||||
}) |
|
||||||
|
|
||||||
} else { |
|
||||||
this.dataSource.getPayload(id as string).subscribe(resp => { |
|
||||||
const dialog = this.infoDialog.open(PayloadInfoDialogComponent, { |
|
||||||
data: resp, |
|
||||||
width: '1000px', |
|
||||||
}); |
|
||||||
|
|
||||||
const saveSub = dialog.componentInstance.onSave.subscribe(result => { |
|
||||||
this.dataSource.updatePayload(result) |
|
||||||
.subscribe(_ => { |
|
||||||
alert("Updated") |
|
||||||
this.loadTableData() |
|
||||||
}) |
|
||||||
dialog.close() |
|
||||||
}) |
|
||||||
|
|
||||||
dialog.afterClosed().subscribe(_ => { |
|
||||||
saveSub.unsubscribe() |
|
||||||
this.router.navigate(['.'], { relativeTo: this.route }) |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,36 +0,0 @@ |
|||||||
import { Component, OnInit } from '@angular/core'; |
|
||||||
import { TableComponent } from '../base-table/base-table.component'; |
|
||||||
import { Area, ResultModel } from '../../../models'; |
|
||||||
import { ResultInfoDialogComponent } from '../../dialogs'; |
|
||||||
|
|
||||||
@Component({ |
|
||||||
selector: 'results-table', |
|
||||||
templateUrl: './result-table.component.html', |
|
||||||
styleUrls: ['../base-table/base-table.component.less'], |
|
||||||
providers: [{ provide: 'area', useValue: 'map' }] |
|
||||||
}) |
|
||||||
export class ResultComponent extends TableComponent<ResultModel> { |
|
||||||
area = 'map' as Area |
|
||||||
displayedColumns = [ |
|
||||||
'id', |
|
||||||
'alias', |
|
||||||
'agent_id', |
|
||||||
'job_id', |
|
||||||
'state', |
|
||||||
'last_updated', |
|
||||||
'actions' |
|
||||||
]; |
|
||||||
|
|
||||||
showItemDialog(id: string) { |
|
||||||
this.dataSource.getResult(id).subscribe(resp => { |
|
||||||
const dialog = this.infoDialog.open(ResultInfoDialogComponent, { |
|
||||||
data: resp, |
|
||||||
width: '1000px', |
|
||||||
}); |
|
||||||
|
|
||||||
dialog.afterClosed().subscribe(_ => { |
|
||||||
this.router.navigate(['.'], { relativeTo: this.route }) |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
@ -1,6 +1,6 @@ |
|||||||
import { UTCDate } from "."; |
import { UTCDate, ApiModel } from "."; |
||||||
|
|
||||||
export interface AgentModel { |
export interface AgentModel extends ApiModel { |
||||||
alias: string | null, |
alias: string | null, |
||||||
hostname: string, |
hostname: string, |
||||||
host_info: string, |
host_info: string, |
@ -0,0 +1,14 @@ |
|||||||
|
export * from './agent.model'; |
||||||
|
export * from './result.model'; |
||||||
|
export * from './job.model'; |
||||||
|
|
||||||
|
export interface UTCDate { |
||||||
|
secs_since_epoch: number, |
||||||
|
nanos_since_epoch: number |
||||||
|
} |
||||||
|
|
||||||
|
export abstract class ApiModel { } |
||||||
|
|
||||||
|
export interface Empty extends ApiModel { } |
||||||
|
|
||||||
|
export type Area = "agents" | "jobs" | "map"; |
@ -0,0 +1,11 @@ |
|||||||
|
import { ApiModel } from "."; |
||||||
|
|
||||||
|
export interface JobModel extends ApiModel { |
||||||
|
alias: string, |
||||||
|
argv: string, |
||||||
|
id: string, |
||||||
|
exec_type: string, |
||||||
|
platform: string, |
||||||
|
payload: number[] | null, |
||||||
|
schedule: string | null, |
||||||
|
} |
@ -1,18 +1,13 @@ |
|||||||
import { UTCDate } from "."; |
import { UTCDate, ApiModel } from "."; |
||||||
|
|
||||||
export interface ResultModel { |
export interface ResultModel extends ApiModel { |
||||||
agent_id: string, |
agent_id: string, |
||||||
alias: string, |
alias: string, |
||||||
created: UTCDate, |
created: UTCDate, |
||||||
id: string, |
id: string, |
||||||
job_id: string, |
job_id: string, |
||||||
result: number[] | null, |
result: number[], |
||||||
state: "Queued" | "Running" | "Finished", |
state: "Queued" | "Running" | "Finished", |
||||||
retcode: number | null, |
retcode: number | null, |
||||||
updated: UTCDate, |
updated: UTCDate, |
||||||
} |
|
||||||
|
|
||||||
export interface AssignedJobByIdModel { |
|
||||||
job_id: string, |
|
||||||
agent_id: string |
|
||||||
} |
} |
@ -0,0 +1,53 @@ |
|||||||
|
import { environment } from 'src/environments/environment'; |
||||||
|
import { HttpClient } from '@angular/common/http'; |
||||||
|
import { firstValueFrom } from 'rxjs'; |
||||||
|
import { ApiModel, Empty, Area } from '../models'; |
||||||
|
|
||||||
|
interface ServerResponse<T extends ApiModel> { |
||||||
|
status: "ok" | "err", |
||||||
|
data: T | string |
||||||
|
} |
||||||
|
|
||||||
|
export class ApiTableService<T extends ApiModel> { |
||||||
|
area: Area; |
||||||
|
|
||||||
|
constructor(private http: HttpClient, area: Area) { |
||||||
|
this.area = area; |
||||||
|
} |
||||||
|
|
||||||
|
requestUrl = `${environment.server}/cmd/`; |
||||||
|
|
||||||
|
async req<R extends ApiModel>(cmd: string): Promise<ServerResponse<R>> { |
||||||
|
return await firstValueFrom(this.http.post<ServerResponse<R>>(this.requestUrl, cmd)) |
||||||
|
} |
||||||
|
|
||||||
|
async getOne(id: string, area: string = this.area): Promise<ServerResponse<T>> { |
||||||
|
const resp = await this.req<T[]>(`${area} read ${id}`) |
||||||
|
if (resp.data.length === 0) { |
||||||
|
return { |
||||||
|
status: 'err', |
||||||
|
data: `${id} not found in ${area}` |
||||||
|
} |
||||||
|
} |
||||||
|
return { |
||||||
|
status: resp.status, |
||||||
|
data: resp.data[0] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async getMany(): Promise<ServerResponse<T[]>> { |
||||||
|
return await this.req(`${this.area} read`) |
||||||
|
} |
||||||
|
|
||||||
|
async update(item: T): Promise<ServerResponse<Empty>> { |
||||||
|
return await this.req(`${this.area} update '${JSON.stringify(item)}'`) |
||||||
|
} |
||||||
|
|
||||||
|
async delete(id: string): Promise<ServerResponse<Empty>> { |
||||||
|
return await this.req(`${this.area} delete ${id}`) |
||||||
|
} |
||||||
|
|
||||||
|
async create(item: string): Promise<ServerResponse<string[]>> { |
||||||
|
return await this.req(`${this.area} create ${item}`) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
import { Component, OnDestroy, OnInit } from '@angular/core'; |
||||||
|
import { TablesComponent } from './table.component'; |
||||||
|
import { AgentModel } from '../models'; |
||||||
|
import { AgentInfoDialogComponent } from './dialogs/agent_info.component'; |
||||||
|
import { HttpErrorResponse } from '@angular/common/http'; |
||||||
|
import { AssignJobDialogComponent } from './dialogs'; |
||||||
|
|
||||||
|
@Component({ |
||||||
|
selector: 'agent-table', |
||||||
|
templateUrl: './agent.component.html', |
||||||
|
styleUrls: ['./table.component.less'] |
||||||
|
}) |
||||||
|
export class AgentComponent extends TablesComponent<AgentModel> implements OnInit { |
||||||
|
|
||||||
|
//dialogSubscr!: Subscription;
|
||||||
|
area = 'agents' as const; |
||||||
|
|
||||||
|
displayedColumns = ['id', 'alias', 'username', 'hostname', 'last_active', 'actions'] |
||||||
|
|
||||||
|
show_item_dialog(id: string) { |
||||||
|
this.data_source!.getOne(id).then(resp => { |
||||||
|
if (resp.status === 'ok') { |
||||||
|
const dialog = this.infoDialog.open(AgentInfoDialogComponent, { |
||||||
|
data: resp.data as AgentModel, |
||||||
|
width: '1000px', |
||||||
|
}); |
||||||
|
|
||||||
|
const saveSub = dialog.componentInstance.onSave.subscribe(result => { |
||||||
|
this.data_source!.update(result).then(_ => { |
||||||
|
this.openSnackBar('Saved', false) |
||||||
|
this.loadTableData() |
||||||
|
}) |
||||||
|
.catch((err: HttpErrorResponse) => this.openSnackBar(err.error)) |
||||||
|
}) |
||||||
|
|
||||||
|
dialog.afterClosed().subscribe(result => { |
||||||
|
saveSub.unsubscribe() |
||||||
|
this.router.navigate(['.'], { relativeTo: this.route }) |
||||||
|
}) |
||||||
|
} else { |
||||||
|
this.openSnackBar(resp.data) |
||||||
|
} |
||||||
|
}).catch((err: HttpErrorResponse) => this.openSnackBar(err.error)) |
||||||
|
} |
||||||
|
|
||||||
|
assignJobs(id: string) { |
||||||
|
const dialog = this.infoDialog.open(AssignJobDialogComponent, { |
||||||
|
data: id, |
||||||
|
width: '1000px', |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -1,12 +1,12 @@ |
|||||||
import { Component, Inject } from '@angular/core'; |
import { Component, Inject } from '@angular/core'; |
||||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog'; |
import { MAT_DIALOG_DATA } from '@angular/material/dialog'; |
||||||
import { AgentModel } from '../../../models/agent.model'; |
import { AgentModel } from '../../models/agent.model'; |
||||||
import { EventEmitter } from '@angular/core'; |
import { EventEmitter } from '@angular/core'; |
||||||
|
|
||||||
@Component({ |
@Component({ |
||||||
selector: 'agent-info-dialog', |
selector: 'agent-info-dialog', |
||||||
templateUrl: 'agent-info-dialog.component.html', |
templateUrl: 'agent-info-dialog.html', |
||||||
styleUrls: ['../base-info-dialog.component.less'] |
styleUrls: ['info-dialog.component.less'] |
||||||
}) |
}) |
||||||
export class AgentInfoDialogComponent { |
export class AgentInfoDialogComponent { |
||||||
is_preview = true; |
is_preview = true; |
@ -0,0 +1,33 @@ |
|||||||
|
import { Component, Inject } from '@angular/core'; |
||||||
|
import { MAT_DIALOG_DATA } from '@angular/material/dialog'; |
||||||
|
import { HttpClient } from '@angular/common/http'; |
||||||
|
import { ApiTableService } from '../../services'; |
||||||
|
import { JobModel } from '../../models'; |
||||||
|
import { MatListOption } from '@angular/material/list'; |
||||||
|
|
||||||
|
@Component({ |
||||||
|
selector: 'assign-job-dialog', |
||||||
|
templateUrl: 'assign-job-dialog.html', |
||||||
|
styleUrls: [] |
||||||
|
}) |
||||||
|
export class AssignJobDialogComponent { |
||||||
|
rows: string[] = []; |
||||||
|
selected_rows: string[] = []; |
||||||
|
|
||||||
|
constructor(@Inject(MAT_DIALOG_DATA) public agent_id: string, private http: HttpClient) { |
||||||
|
new ApiTableService(http, "jobs").getMany().then(result => { |
||||||
|
if (result.status == "ok") { |
||||||
|
const jobs = result.data as JobModel[] |
||||||
|
this.rows = jobs.map(j => `${j.id} ${j.alias}`) |
||||||
|
} else { |
||||||
|
alert(result.data as string) |
||||||
|
} |
||||||
|
}).catch(err => alert(err)) |
||||||
|
} |
||||||
|
|
||||||
|
assignSelectedJobs() { |
||||||
|
const job_ids = this.selected_rows.map(row => row.split(' ', 1)[0]).join(' '); |
||||||
|
const request = `${this.agent_id} ${job_ids}` |
||||||
|
new ApiTableService(this.http, "map").create(request).catch(err => alert(err)) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,4 @@ |
|||||||
|
export * from './agent_info.component'; |
||||||
|
export * from './result_info.component'; |
||||||
|
export * from './job_info.component'; |
||||||
|
export * from './assign_job.component'; |
@ -0,0 +1,44 @@ |
|||||||
|
<h2 mat-dialog-title *ngIf="is_preview">Job info</h2> |
||||||
|
<h2 mat-dialog-title *ngIf="!is_preview">Editing job info</h2> |
||||||
|
<mat-dialog-content> |
||||||
|
<div class="info-dialog-forms-box-smol"> |
||||||
|
<mat-form-field class="info-dlg-field" cdkFocusInitial> |
||||||
|
<mat-label>ID</mat-label> |
||||||
|
<input matInput disabled value="{{data.id}}"> |
||||||
|
</mat-form-field> |
||||||
|
<mat-form-field class="info-dlg-field"> |
||||||
|
<mat-label>Alias</mat-label> |
||||||
|
<input matInput [readonly]="is_preview" [(ngModel)]="data.alias"> |
||||||
|
</mat-form-field> |
||||||
|
<mat-form-field class="info-dlg-field"> |
||||||
|
<mat-label>Args</mat-label> |
||||||
|
<input matInput [readonly]="is_preview" [(ngModel)]="data.argv"> |
||||||
|
</mat-form-field> |
||||||
|
</div> |
||||||
|
<div class="info-dialog-forms-box-smol"> |
||||||
|
<mat-form-field class="info-dlg-field"> |
||||||
|
<mat-label>Type</mat-label> |
||||||
|
<input matInput [readonly]="is_preview" [(ngModel)]="data.exec_type"> |
||||||
|
</mat-form-field> |
||||||
|
<mat-form-field class="info-dlg-field"> |
||||||
|
<mat-label>Platform</mat-label> |
||||||
|
<input matInput [readonly]="is_preview" [(ngModel)]="data.platform"> |
||||||
|
</mat-form-field> |
||||||
|
<mat-form-field class="info-dlg-field"> |
||||||
|
<mat-label>Schedule</mat-label> |
||||||
|
<input matInput [readonly]="is_preview" [(ngModel)]="data.schedule"> |
||||||
|
</mat-form-field> |
||||||
|
</div> |
||||||
|
<div class="info-dialog-forms-box"> |
||||||
|
<mat-form-field class="info-dlg-field"> |
||||||
|
<mat-label>Payload</mat-label> |
||||||
|
<textarea matInput cdkTextareaAutosize [readonly]="is_preview" [(ngModel)]="decodedPayload"> |
||||||
|
</textarea> |
||||||
|
</mat-form-field> |
||||||
|
</div> |
||||||
|
</mat-dialog-content> |
||||||
|
<mat-dialog-actions align="end"> |
||||||
|
<button mat-raised-button *ngIf="is_preview" (click)="is_preview = false">Edit</button> |
||||||
|
<button mat-raised-button *ngIf="!is_preview" (click)="updateJob()">Save</button> |
||||||
|
<button mat-button mat-dialog-close>Cancel</button> |
||||||
|
</mat-dialog-actions> |
@ -0,0 +1,30 @@ |
|||||||
|
import { Component, Inject } from '@angular/core'; |
||||||
|
import { MAT_DIALOG_DATA } from '@angular/material/dialog'; |
||||||
|
import { EventEmitter } from '@angular/core'; |
||||||
|
import { JobModel } from '../../models/job.model'; |
||||||
|
|
||||||
|
@Component({ |
||||||
|
selector: 'job-info-dialog', |
||||||
|
templateUrl: 'job-info-dialog.html', |
||||||
|
styleUrls: ['info-dialog.component.less'] |
||||||
|
}) |
||||||
|
export class JobInfoDialogComponent { |
||||||
|
is_preview = true; |
||||||
|
decodedPayload: string; |
||||||
|
onSave = new EventEmitter(); |
||||||
|
|
||||||
|
constructor(@Inject(MAT_DIALOG_DATA) public data: JobModel) { |
||||||
|
if (data.payload !== null) { |
||||||
|
this.decodedPayload = new TextDecoder().decode(new Uint8Array(data.payload)) |
||||||
|
} else { |
||||||
|
this.decodedPayload = "" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
updateJob() { |
||||||
|
if (this.decodedPayload.length > 0) { |
||||||
|
this.data.payload = Array.from(new TextEncoder().encode(this.decodedPayload)) |
||||||
|
} |
||||||
|
this.onSave.emit(this.data); |
||||||
|
} |
||||||
|
} |
@ -1,11 +1,11 @@ |
|||||||
import { Component, Inject } from '@angular/core'; |
import { Component, Inject } from '@angular/core'; |
||||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog'; |
import { MAT_DIALOG_DATA } from '@angular/material/dialog'; |
||||||
import { ResultModel } from '../../../models/result.model'; |
import { ResultModel } from '../../models/result.model'; |
||||||
|
|
||||||
@Component({ |
@Component({ |
||||||
selector: 'result-info-dialog', |
selector: 'result-info-dialog', |
||||||
templateUrl: 'result-info-dialog.component.html', |
templateUrl: 'result-info-dialog.html', |
||||||
styleUrls: ['../base-info-dialog.component.less'] |
styleUrls: ['info-dialog.component.less'] |
||||||
}) |
}) |
||||||
export class ResultInfoDialogComponent { |
export class ResultInfoDialogComponent { |
||||||
decodedResult: string; |
decodedResult: string; |
@ -0,0 +1,3 @@ |
|||||||
|
export * from './agent.component'; |
||||||
|
export * from './job.component'; |
||||||
|
export * from './result.component'; |
@ -0,0 +1,59 @@ |
|||||||
|
import { Component, OnInit } from '@angular/core'; |
||||||
|
import { TablesComponent } from './table.component'; |
||||||
|
import { JobModel } from '../models'; |
||||||
|
import { JobInfoDialogComponent } from './dialogs'; |
||||||
|
import { HttpErrorResponse } from '@angular/common/http'; |
||||||
|
|
||||||
|
@Component({ |
||||||
|
selector: 'job-table', |
||||||
|
templateUrl: './job.component.html', |
||||||
|
styleUrls: ['./table.component.less'] |
||||||
|
}) |
||||||
|
export class JobComponent extends TablesComponent<JobModel> { |
||||||
|
area = 'jobs' as const; |
||||||
|
displayedColumns = ['id', 'alias', 'platform', 'schedule', 'exec_type', 'actions'] |
||||||
|
|
||||||
|
show_item_dialog(id: string | null) { |
||||||
|
const show_dlg = (id: string, edit: boolean) => { |
||||||
|
this.data_source!.getOne(id).then(resp => { |
||||||
|
if (resp.status === 'ok') { |
||||||
|
var dialog = this.infoDialog.open(JobInfoDialogComponent, { |
||||||
|
data: resp.data as JobModel, |
||||||
|
width: '1000px', |
||||||
|
}); |
||||||
|
if (edit) { |
||||||
|
dialog.componentInstance.is_preview = false |
||||||
|
} |
||||||
|
|
||||||
|
const saveSub = dialog.componentInstance.onSave.subscribe(result => { |
||||||
|
this.data_source!.update(result) |
||||||
|
.then(_ => { |
||||||
|
this.openSnackBar("Saved", false) |
||||||
|
this.loadTableData() |
||||||
|
}) |
||||||
|
.catch((err: HttpErrorResponse) => this.openSnackBar(err.error)) |
||||||
|
}) |
||||||
|
|
||||||
|
dialog.afterClosed().subscribe(result => { |
||||||
|
saveSub.unsubscribe() |
||||||
|
this.router.navigate(['.'], { relativeTo: this.route }) |
||||||
|
}) |
||||||
|
} else { |
||||||
|
this.openSnackBar(resp.data) |
||||||
|
} |
||||||
|
}).catch((err: any) => this.openSnackBar(err)) |
||||||
|
} |
||||||
|
|
||||||
|
if (id) { |
||||||
|
show_dlg(id, false) |
||||||
|
} else { |
||||||
|
this.data_source!.create('"{}"').then(resp => { |
||||||
|
if (resp.status === 'ok') { |
||||||
|
show_dlg(resp.data[0], true) |
||||||
|
} else { |
||||||
|
this.openSnackBar(resp.data) |
||||||
|
} |
||||||
|
}).catch((err: HttpErrorResponse) => this.openSnackBar(err.error)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,41 @@ |
|||||||
|
import { Component, OnInit } from '@angular/core'; |
||||||
|
import { TablesComponent } from './table.component'; |
||||||
|
import { ResultModel } from '../models'; |
||||||
|
import { ResultInfoDialogComponent } from './dialogs'; |
||||||
|
import { HttpErrorResponse } from '@angular/common/http'; |
||||||
|
|
||||||
|
@Component({ |
||||||
|
selector: 'results-table', |
||||||
|
templateUrl: './result.component.html', |
||||||
|
styleUrls: ['./table.component.less'] |
||||||
|
}) |
||||||
|
export class ResultComponent extends TablesComponent<ResultModel> { |
||||||
|
area = 'map' as const; |
||||||
|
|
||||||
|
displayedColumns = [ |
||||||
|
'id', |
||||||
|
'alias', |
||||||
|
'agent_id', |
||||||
|
'job_id', |
||||||
|
'state', |
||||||
|
'last_updated', |
||||||
|
'actions' |
||||||
|
]; |
||||||
|
|
||||||
|
show_item_dialog(id: string) { |
||||||
|
this.data_source!.getOne(id).then(resp => { |
||||||
|
if (resp.status === 'ok') { |
||||||
|
const dialog = this.infoDialog.open(ResultInfoDialogComponent, { |
||||||
|
data: resp.data as ResultModel, |
||||||
|
width: '1000px', |
||||||
|
}); |
||||||
|
|
||||||
|
dialog.afterClosed().subscribe(result => { |
||||||
|
this.router.navigate(['.'], { relativeTo: this.route }) |
||||||
|
}) |
||||||
|
} else { |
||||||
|
this.openSnackBar(resp.data) |
||||||
|
} |
||||||
|
}).catch((err: HttpErrorResponse) => this.openSnackBar(err.message)) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,84 @@ |
|||||||
|
import { OnInit, Directive } from '@angular/core'; |
||||||
|
import { HttpClient } from '@angular/common/http'; |
||||||
|
import { ApiTableService } from '../'; |
||||||
|
import { MatTableDataSource } from '@angular/material/table'; |
||||||
|
import { MatDialog } from '@angular/material/dialog'; |
||||||
|
import { ApiModel, Area } from '../models'; |
||||||
|
import { ActivatedRoute, Router } from '@angular/router'; |
||||||
|
import { interval } from 'rxjs'; |
||||||
|
import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar'; |
||||||
|
|
||||||
|
@Directive() |
||||||
|
export abstract class TablesComponent<T extends ApiModel> implements OnInit { |
||||||
|
abstract area: Area; |
||||||
|
data_source!: ApiTableService<T>; |
||||||
|
table_data!: MatTableDataSource<T>; |
||||||
|
|
||||||
|
isLoadingResults = true; |
||||||
|
|
||||||
|
constructor( |
||||||
|
public httpClient: HttpClient, |
||||||
|
public infoDialog: MatDialog, |
||||||
|
public route: ActivatedRoute, |
||||||
|
public router: Router, |
||||||
|
public snackBar: MatSnackBar |
||||||
|
) { |
||||||
|
this.table_data = new MatTableDataSource; |
||||||
|
} |
||||||
|
|
||||||
|
ngOnInit() { |
||||||
|
this.data_source = new ApiTableService(this.httpClient, this.area); |
||||||
|
this.loadTableData(); |
||||||
|
this.route.queryParams.subscribe(params => { |
||||||
|
const id = params['id'] |
||||||
|
const new_agent = params['new'] |
||||||
|
if (id) { |
||||||
|
this.show_item_dialog(id); |
||||||
|
} |
||||||
|
if (new_agent) { |
||||||
|
this.show_item_dialog(null); |
||||||
|
} |
||||||
|
}) |
||||||
|
//interval(10000).subscribe(_ => this.loadTableData());
|
||||||
|
} |
||||||
|
|
||||||
|
async loadTableData() { |
||||||
|
this.isLoadingResults = true; |
||||||
|
//possibly needs try/catch
|
||||||
|
const data = await this.data_source!.getMany(); |
||||||
|
this.isLoadingResults = false; |
||||||
|
|
||||||
|
if (typeof data.data !== 'string') { |
||||||
|
this.table_data.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(); |
||||||
|
} |
||||||
|
|
||||||
|
deleteItem(id: string) { |
||||||
|
if (confirm(`Delete ${id}?`)) { |
||||||
|
this.data_source!.delete(id).catch(this.openSnackBar) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
openSnackBar(message: any, error: boolean = true) { |
||||||
|
const msg = JSON.stringify(message) |
||||||
|
const _config = (duration: number): MatSnackBarConfig => { |
||||||
|
return { |
||||||
|
horizontalPosition: 'right', |
||||||
|
verticalPosition: 'bottom', |
||||||
|
duration |
||||||
|
} |
||||||
|
} |
||||||
|
const cfg = error ? _config(0) : _config(2000) |
||||||
|
this.snackBar.open(msg, 'Ok', cfg); |
||||||
|
} |
||||||
|
|
||||||
|
abstract displayedColumns: string[]; |
||||||
|
abstract show_item_dialog(id: string | null): void; |
||||||
|
} |
@ -1,20 +0,0 @@ |
|||||||
import { AgentModel } from './agent.model'; |
|
||||||
import { JobModel } from './job.model'; |
|
||||||
import { PayloadModel } from './payload.model'; |
|
||||||
import { ResultModel } from './result.model'; |
|
||||||
|
|
||||||
export * from './agent.model'; |
|
||||||
export * from './result.model'; |
|
||||||
export * from './job.model'; |
|
||||||
export * from './payload.model'; |
|
||||||
|
|
||||||
export interface UTCDate { |
|
||||||
secs_since_epoch: number, |
|
||||||
nanos_since_epoch: number |
|
||||||
} |
|
||||||
|
|
||||||
export type Area = "agents" | "jobs" | "map" | "payloads"; |
|
||||||
|
|
||||||
export type ApiModel = AgentModel | JobModel | ResultModel | PayloadModel | Empty; |
|
||||||
|
|
||||||
export interface Empty { } |
|
@ -1,16 +0,0 @@ |
|||||||
import { PayloadModel } from './' |
|
||||||
|
|
||||||
export interface JobModel { |
|
||||||
alias: string | null, |
|
||||||
argv: string, |
|
||||||
id?: string, |
|
||||||
exec_type: string, |
|
||||||
target_platforms: string, |
|
||||||
payload_id: string | null, |
|
||||||
schedule: string | null, |
|
||||||
} |
|
||||||
|
|
||||||
export interface Job { |
|
||||||
meta: JobModel, |
|
||||||
payload: PayloadModel | null, |
|
||||||
} |
|
@ -1,12 +0,0 @@ |
|||||||
export interface PayloadModel { |
|
||||||
id: string, |
|
||||||
mime_type: string, |
|
||||||
name: string, |
|
||||||
size: number, |
|
||||||
data: number[] | null |
|
||||||
} |
|
||||||
|
|
||||||
export interface NewPayloadModel { |
|
||||||
name: string, |
|
||||||
data: number[] |
|
||||||
} |
|
@ -1,142 +0,0 @@ |
|||||||
import { environment } from 'src/environments/environment'; |
|
||||||
import { HttpClient, HttpErrorResponse } from '@angular/common/http'; |
|
||||||
import { Observable, map, catchError, throwError } from 'rxjs'; |
|
||||||
import { ApiModel, PayloadModel, Empty, Area, AgentModel, JobModel, ResultModel, Job, NewPayloadModel, AssignedJobByIdModel } from '../models'; |
|
||||||
import { Injectable, Inject } from '@angular/core'; |
|
||||||
import { ErrorService } from './error.service'; |
|
||||||
|
|
||||||
type Status = "ok" | "err"; |
|
||||||
|
|
||||||
interface ServerResponse<T extends ApiModel> { |
|
||||||
status: Status, |
|
||||||
data: T | string |
|
||||||
} |
|
||||||
|
|
||||||
@Injectable({ |
|
||||||
providedIn: 'root' |
|
||||||
}) |
|
||||||
export class ApiTableService { |
|
||||||
|
|
||||||
constructor( |
|
||||||
private http: HttpClient, |
|
||||||
private errorService: ErrorService |
|
||||||
) { |
|
||||||
} |
|
||||||
|
|
||||||
requestUrl = `${environment.server}/cmd/`; |
|
||||||
|
|
||||||
req<R extends ApiModel>(cmd: string): Observable<ServerResponse<R>> { |
|
||||||
return this.http.post<ServerResponse<R>>(this.requestUrl, cmd) |
|
||||||
} |
|
||||||
|
|
||||||
getOne<T extends ApiModel>(id: string, area: Area, brief: 'yes' | 'no' | 'auto' | null = null): Observable<T> { |
|
||||||
const request = `${area} read ${id}` + (brief !== null ? `-b=${brief}` : '') |
|
||||||
const resp = this.req<T[]>(request).pipe( |
|
||||||
map(resp => { |
|
||||||
if (resp.data.length === 0) { |
|
||||||
return { |
|
||||||
status: 'err' as Status, |
|
||||||
data: `${id} not found in ${area}` |
|
||||||
} |
|
||||||
} |
|
||||||
return { |
|
||||||
status: resp.status, |
|
||||||
data: resp.data[0] |
|
||||||
} |
|
||||||
})); |
|
||||||
return this.filterErrStatus(resp) |
|
||||||
} |
|
||||||
|
|
||||||
getAgent(id: string): Observable<AgentModel> { |
|
||||||
return this.getOne(id, 'agents') |
|
||||||
} |
|
||||||
|
|
||||||
getJob(id: string): Observable<Job> { |
|
||||||
return this.getOne(id, 'jobs') |
|
||||||
} |
|
||||||
|
|
||||||
getResult(id: string): Observable<ResultModel> { |
|
||||||
return this.getOne(id, 'map') |
|
||||||
} |
|
||||||
|
|
||||||
getPayload(id: string): Observable<PayloadModel> { |
|
||||||
return this.getOne(id, 'payloads') |
|
||||||
} |
|
||||||
|
|
||||||
getMany(area: Area): Observable<any[]> { |
|
||||||
return this.filterErrStatus(this.req(`${area} read`)) |
|
||||||
} |
|
||||||
|
|
||||||
getAgents(): Observable<AgentModel[]> { |
|
||||||
return this.getMany('agents') |
|
||||||
} |
|
||||||
|
|
||||||
getJobs(): Observable<JobModel[]> { |
|
||||||
return this.getMany('jobs') |
|
||||||
} |
|
||||||
|
|
||||||
getResults(): Observable<ResultModel[]> { |
|
||||||
return this.getMany('map') |
|
||||||
} |
|
||||||
|
|
||||||
getPayloads(): Observable<PayloadModel[]> { |
|
||||||
return this.getMany('payloads') |
|
||||||
} |
|
||||||
|
|
||||||
update<T extends ApiModel>(item: T, area: Area): Observable<Empty> { |
|
||||||
return this.filterErrStatus(this.req(`${area} update '${JSON.stringify(item)}'`)) |
|
||||||
} |
|
||||||
|
|
||||||
updateAgent(item: AgentModel): Observable<Empty> { |
|
||||||
return this.update(item, 'agents') |
|
||||||
} |
|
||||||
|
|
||||||
updateJob(item: JobModel): Observable<Empty> { |
|
||||||
return this.update(item, 'jobs') |
|
||||||
} |
|
||||||
|
|
||||||
updateResult(item: ResultModel): Observable<Empty> { |
|
||||||
return this.update(item, 'map') |
|
||||||
} |
|
||||||
|
|
||||||
updatePayload(item: PayloadModel): Observable<Empty> { |
|
||||||
return this.update(item, 'payloads') |
|
||||||
} |
|
||||||
|
|
||||||
delete(id: string, area: Area): Observable<Empty> { |
|
||||||
return this.filterErrStatus(this.req(`${area} delete ${id}`)) |
|
||||||
} |
|
||||||
|
|
||||||
create<T extends ApiModel>(item: T | null, area: Area): Observable<string[]> { |
|
||||||
var serialized = '"{}"' |
|
||||||
if (item) { |
|
||||||
serialized = JSON.stringify(item); |
|
||||||
} |
|
||||||
return this.filterErrStatus(this.req(`${area} create '${serialized}'`)) |
|
||||||
} |
|
||||||
|
|
||||||
createResult(item: AssignedJobByIdModel[]): Observable<string[]> { |
|
||||||
return this.create(item, 'map') |
|
||||||
} |
|
||||||
|
|
||||||
createPayload(item: NewPayloadModel): Observable<string[]> { |
|
||||||
return this.create(item, 'payloads') |
|
||||||
} |
|
||||||
|
|
||||||
filterErrStatus<R extends ApiModel>(obs: Observable<ServerResponse<R>>): Observable<R> { |
|
||||||
return obs.pipe( |
|
||||||
map(r => { |
|
||||||
if (r.status == 'err') { |
|
||||||
throw new Error(r.data as string) |
|
||||||
} |
|
||||||
return r.data as R |
|
||||||
}), |
|
||||||
catchError(this.errorHandler.bind(this))) |
|
||||||
} |
|
||||||
|
|
||||||
errorHandler(err: HttpErrorResponse, caught: any) { |
|
||||||
var error = err.error.data !== undefined ? JSON.stringify(err.error.data) : err.message; |
|
||||||
this.errorService.handle(error); |
|
||||||
return throwError(() => new Error()); |
|
||||||
} |
|
||||||
} |
|
@ -1,17 +0,0 @@ |
|||||||
import { Injectable } from '@angular/core'; |
|
||||||
import { Subject } from 'rxjs'; |
|
||||||
|
|
||||||
@Injectable({ |
|
||||||
providedIn: 'root' |
|
||||||
}) |
|
||||||
export class ErrorService { |
|
||||||
error$ = new Subject<string>(); |
|
||||||
|
|
||||||
handle(msg: string) { |
|
||||||
this.error$.next(msg) |
|
||||||
} |
|
||||||
|
|
||||||
clear() { |
|
||||||
this.handle('') |
|
||||||
} |
|
||||||
} |
|
@ -1,4 +1,4 @@ |
|||||||
FROM rust:1.72 |
FROM rust:1.67 |
||||||
|
|
||||||
RUN rustup target add x86_64-unknown-linux-musl |
RUN rustup target add x86_64-unknown-linux-musl |
||||||
RUN mkdir -p /tests && chmod 777 /tests |
RUN mkdir -p /tests && chmod 777 /tests |
||||||
|
@ -1,3 +1,3 @@ |
|||||||
FROM alpine:3.17 |
FROM alpine:3.17 |
||||||
|
|
||||||
RUN apk add iproute2 bash file |
RUN apk add iproute2 bash |
@ -1,38 +0,0 @@ |
|||||||
use super::connections::*; |
|
||||||
use super::run_async; |
|
||||||
use u_lib::{api::HttpClient, messaging::Reportable, models::*, types::Id}; |
|
||||||
|
|
||||||
pub struct RegisteredAgent { |
|
||||||
pub id: Id, |
|
||||||
} |
|
||||||
|
|
||||||
#[fixture] |
|
||||||
#[once] |
|
||||||
pub fn registered_agent(client: &HttpClient) -> RegisteredAgent { |
|
||||||
run_async(async { |
|
||||||
let agent = Agent::with_current_platform(); |
|
||||||
let agent_id = agent.id; |
|
||||||
println!("registering agent {agent_id}"); |
|
||||||
debug!("registering agent1 {agent_id}"); |
|
||||||
let resp = client |
|
||||||
.get_personal_jobs(agent_id) |
|
||||||
.await |
|
||||||
.unwrap() |
|
||||||
.pop() |
|
||||||
.unwrap(); |
|
||||||
let job_id = resp.job_id; |
|
||||||
let job = client.get_job(job_id, Brief::No).await.unwrap(); |
|
||||||
|
|
||||||
assert_eq!(job.meta.alias, Some("agent_hello".to_string())); |
|
||||||
|
|
||||||
let mut agent_data = AssignedJob::from((&job.meta, resp)); |
|
||||||
agent_data.set_result(&agent); |
|
||||||
|
|
||||||
client |
|
||||||
.report([Reportable::Assigned(agent_data)]) |
|
||||||
.await |
|
||||||
.unwrap(); |
|
||||||
|
|
||||||
RegisteredAgent { id: agent_id } |
|
||||||
}) |
|
||||||
} |
|
@ -1,28 +0,0 @@ |
|||||||
use super::env::*; |
|
||||||
use super::run_async; |
|
||||||
|
|
||||||
pub use u_lib::api::HttpClient; |
|
||||||
use u_lib::db::unpooled; |
|
||||||
pub use u_lib::db::PgConnection; |
|
||||||
|
|
||||||
#[fixture] |
|
||||||
#[once] |
|
||||||
pub fn client(env_default: EndpointsEnv) -> HttpClient { |
|
||||||
run_async(HttpClient::new(&env_default.u_server, None)).unwrap() |
|
||||||
} |
|
||||||
|
|
||||||
#[fixture] |
|
||||||
#[once] |
|
||||||
pub fn client_panel(env_access: AccessEnv) -> HttpClient { |
|
||||||
run_async(HttpClient::new( |
|
||||||
&env_access.u_server, |
|
||||||
Some(env_access.admin_auth_token), |
|
||||||
)) |
|
||||||
.unwrap() |
|
||||||
} |
|
||||||
|
|
||||||
#[fixture] |
|
||||||
#[once] |
|
||||||
pub fn db(env_db: DBEnv) -> PgConnection { |
|
||||||
unpooled(&env_db) |
|
||||||
} |
|
@ -1,16 +0,0 @@ |
|||||||
pub use u_lib::config::{AccessEnv, DBEnv, EndpointsEnv}; |
|
||||||
|
|
||||||
#[fixture] |
|
||||||
pub fn env_default() -> EndpointsEnv { |
|
||||||
EndpointsEnv::load() |
|
||||||
} |
|
||||||
|
|
||||||
#[fixture] |
|
||||||
pub fn env_access() -> AccessEnv { |
|
||||||
AccessEnv::load().unwrap() |
|
||||||
} |
|
||||||
|
|
||||||
#[fixture] |
|
||||||
pub fn env_db() -> DBEnv { |
|
||||||
DBEnv::load().unwrap() |
|
||||||
} |
|
@ -1,17 +0,0 @@ |
|||||||
pub mod agent; |
|
||||||
pub mod connections; |
|
||||||
pub mod env; |
|
||||||
|
|
||||||
use std::future::Future; |
|
||||||
use std::thread; |
|
||||||
use tokio::runtime::Runtime; |
|
||||||
|
|
||||||
// tokio runtime cannot be created inside another runtime,
|
|
||||||
// so i create a separate non-'static thread not to interfere
|
|
||||||
fn run_async<R: Send>(fut: impl Future<Output = R> + Send) -> R { |
|
||||||
thread::scope(|s| { |
|
||||||
s.spawn(|| Runtime::new().unwrap().block_on(fut)) |
|
||||||
.join() |
|
||||||
.expect("async task failed") |
|
||||||
}) |
|
||||||
} |
|
@ -1,4 +0,0 @@ |
|||||||
pub mod jobs; |
|
||||||
pub mod panel; |
|
||||||
|
|
||||||
pub use panel::Panel; |
|
@ -1,97 +0,0 @@ |
|||||||
// get_personal_jobs(&self, url_param: Id)
|
|
||||||
// report(&self, payload: impl OneOrVec<messaging::Reportable>)
|
|
||||||
// dl(&self, file: String)
|
|
||||||
// get_job(&self, job: Id)
|
|
||||||
// get_jobs(&self)
|
|
||||||
// get_agents(&self, agent: Option<Id>)
|
|
||||||
// update_agent(&self, agent: Agent)
|
|
||||||
// update_job(&self, job: FatJob)
|
|
||||||
// update_result(&self, result: AssignedJob)
|
|
||||||
// upload_jobs(&self, payload: impl OneOrVec<FatJob>)
|
|
||||||
// del(&self, item: Id)
|
|
||||||
// assign_jobs(&self, agent: Id, job_idents: impl OneOrVec<String>)
|
|
||||||
// get_agent_jobs(&self, agent: Option<Id>)
|
|
||||||
// ping(&self)
|
|
||||||
|
|
||||||
use crate::fixtures::connections::*; |
|
||||||
use std::iter::repeat; |
|
||||||
use u_lib::models::{Brief, RawJob, RawPayload, MAX_READABLE_PAYLOAD_SIZE}; |
|
||||||
|
|
||||||
#[rstest] |
|
||||||
#[tokio::test] |
|
||||||
async fn jobs_upload_update_get_del(client_panel: &HttpClient) { |
|
||||||
let job_alias = "henlo"; |
|
||||||
let mut job = RawJob::default() |
|
||||||
.with_shell("/bin/bash {}") |
|
||||||
.with_raw_payload("echo henlo") |
|
||||||
.with_alias(job_alias) |
|
||||||
.try_into_job() |
|
||||||
.unwrap(); |
|
||||||
|
|
||||||
let job_id = job.meta.id; |
|
||||||
|
|
||||||
client_panel.upload_jobs([&job]).await.unwrap(); |
|
||||||
|
|
||||||
let fetched_job = client_panel.get_full_job(job_id).await.unwrap(); |
|
||||||
assert_eq!(job, fetched_job); |
|
||||||
|
|
||||||
let new_alias = "henlo2".to_string(); |
|
||||||
job.meta.alias = Some(new_alias.clone()); |
|
||||||
client_panel.update_job(&job.meta).await.unwrap(); |
|
||||||
|
|
||||||
let fetched_job = client_panel.get_full_job(job_id).await.unwrap(); |
|
||||||
assert_eq!( |
|
||||||
fetched_job.payload.as_ref().unwrap().data.as_ref().unwrap(), |
|
||||||
b"echo henlo" |
|
||||||
); |
|
||||||
assert_eq!(fetched_job.meta.alias, Some(new_alias)); |
|
||||||
|
|
||||||
client_panel.del(job_id).await.unwrap(); |
|
||||||
|
|
||||||
let not_found_err = client_panel.get_brief_job(job_id).await.unwrap_err(); |
|
||||||
assert!(not_found_err.to_string().contains("404 Not Found")) |
|
||||||
} |
|
||||||
|
|
||||||
#[rstest] |
|
||||||
#[tokio::test] |
|
||||||
async fn payloads_upload_update_get_del(client_panel: &HttpClient) { |
|
||||||
let name = "test1".to_string(); |
|
||||||
let data = b"qweasdzxc".to_vec(); |
|
||||||
let payload = RawPayload { |
|
||||||
name: Some(name.clone()), |
|
||||||
data: data.clone(), |
|
||||||
}; |
|
||||||
|
|
||||||
client_panel.upload_payload(&payload).await.unwrap(); |
|
||||||
|
|
||||||
let mut fetched_payload = client_panel.get_payload(&name, Brief::No).await.unwrap(); |
|
||||||
let fetched_payload_auto = client_panel.get_payload(&name, Brief::Auto).await.unwrap(); |
|
||||||
|
|
||||||
assert_eq!(fetched_payload, fetched_payload_auto); |
|
||||||
assert_eq!(fetched_payload.data.unwrap(), data); |
|
||||||
|
|
||||||
let new_size = MAX_READABLE_PAYLOAD_SIZE + 1; |
|
||||||
let big_data = repeat(1u8).take(new_size as usize).collect::<Vec<_>>(); |
|
||||||
|
|
||||||
fetched_payload.data = Some(big_data.clone()); |
|
||||||
client_panel.update_payload(&fetched_payload).await.unwrap(); |
|
||||||
|
|
||||||
let fetched_big_payload = client_panel.get_payload(&name, Brief::Yes).await.unwrap(); |
|
||||||
let fetched_big_payload_auto = client_panel.get_payload(&name, Brief::Auto).await.unwrap(); |
|
||||||
|
|
||||||
assert_eq!(fetched_big_payload, fetched_big_payload_auto); |
|
||||||
assert_eq!(fetched_big_payload.size, new_size); |
|
||||||
assert!(fetched_big_payload.data.is_none()); |
|
||||||
|
|
||||||
let fetched_big_payload_full = client_panel.get_payload(&name, Brief::No).await.unwrap(); |
|
||||||
|
|
||||||
assert_eq!(fetched_big_payload_full.data.unwrap(), big_data); |
|
||||||
|
|
||||||
client_panel.del(fetched_big_payload_full.id).await.unwrap(); |
|
||||||
|
|
||||||
let not_found_err = client_panel |
|
||||||
.get_payload(&name, Brief::Yes) |
|
||||||
.await |
|
||||||
.unwrap_err(); |
|
||||||
assert!(not_found_err.to_string().contains("404 Not Found")) |
|
||||||
} |
|
@ -1,14 +0,0 @@ |
|||||||
mod fixtures; |
|
||||||
mod helpers; |
|
||||||
mod integration_tests; |
|
||||||
|
|
||||||
#[macro_use] |
|
||||||
extern crate rstest; |
|
||||||
|
|
||||||
#[macro_use] |
|
||||||
extern crate tracing; |
|
||||||
|
|
||||||
#[ctor::ctor] |
|
||||||
fn __init() { |
|
||||||
u_lib::logging::init_logger(None); |
|
||||||
} |
|
@ -0,0 +1,36 @@ |
|||||||
|
use crate::helpers::ENV; |
||||||
|
use u_lib::{ |
||||||
|
api::ClientHandler, config::get_self_id, jobs::fat_meta_to_thin, messaging::Reportable, |
||||||
|
models::*, types::Id, |
||||||
|
}; |
||||||
|
|
||||||
|
pub struct RegisteredAgent { |
||||||
|
pub id: Id, |
||||||
|
} |
||||||
|
|
||||||
|
impl RegisteredAgent { |
||||||
|
pub async fn unregister(self) { |
||||||
|
let cli = ClientHandler::new(&ENV.u_server, None).await.unwrap(); |
||||||
|
cli.del(self.id).await.unwrap(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[fixture] |
||||||
|
pub async fn register_agent() -> RegisteredAgent { |
||||||
|
let cli = ClientHandler::new(&ENV.u_server, None).await.unwrap(); |
||||||
|
let agent_id = get_self_id(); |
||||||
|
println!("registering agent {agent_id}"); |
||||||
|
let resp = cli |
||||||
|
.get_personal_jobs(agent_id) |
||||||
|
.await |
||||||
|
.unwrap() |
||||||
|
.pop() |
||||||
|
.unwrap(); |
||||||
|
let job_id = resp.job_id; |
||||||
|
let job = cli.get_job(job_id).await.unwrap(); |
||||||
|
assert_eq!(job.alias, Some("agent_hello".to_string())); |
||||||
|
let mut agent_data = AssignedJob::from((&fat_meta_to_thin(job).unwrap(), resp)); |
||||||
|
agent_data.set_result(&Agent::with_id(agent_id)); |
||||||
|
cli.report(Reportable::Assigned(agent_data)).await.unwrap(); |
||||||
|
RegisteredAgent { id: agent_id } |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
pub mod agent; |
@ -0,0 +1,9 @@ |
|||||||
|
pub mod jobs; |
||||||
|
pub mod panel; |
||||||
|
|
||||||
|
pub use panel::Panel; |
||||||
|
|
||||||
|
use once_cell::sync::Lazy; |
||||||
|
use u_lib::config::EndpointsEnv; |
||||||
|
|
||||||
|
pub static ENV: Lazy<EndpointsEnv> = Lazy::new(|| EndpointsEnv::load()); |
@ -1,15 +1,14 @@ |
|||||||
use crate::fixtures::env::*; |
use crate::helpers::ENV; |
||||||
use u_lib::config::MASTER_PORT; |
use u_lib::config::MASTER_PORT; |
||||||
|
|
||||||
#[rstest] |
|
||||||
#[tokio::test] |
#[tokio::test] |
||||||
async fn non_auth_connection_dropped(env_default: EndpointsEnv) { |
async fn non_auth_connection_dropped() { |
||||||
let client = reqwest::ClientBuilder::new() |
let client = reqwest::ClientBuilder::new() |
||||||
.danger_accept_invalid_certs(true) |
.danger_accept_invalid_certs(true) |
||||||
.build() |
.build() |
||||||
.unwrap(); |
.unwrap(); |
||||||
match client |
match client |
||||||
.get(format!("https://{}:{}", &env_default.u_server, MASTER_PORT)) |
.get(format!("https://{}:{}", &ENV.u_server, MASTER_PORT)) |
||||||
.send() |
.send() |
||||||
.await |
.await |
||||||
{ |
{ |
@ -1,3 +1,2 @@ |
|||||||
mod behaviour; |
mod behaviour; |
||||||
mod connection; |
mod connection; |
||||||
mod endpoints; |
|
@ -0,0 +1,6 @@ |
|||||||
|
mod fixtures; |
||||||
|
mod helpers; |
||||||
|
mod integration; |
||||||
|
|
||||||
|
#[macro_use] |
||||||
|
extern crate rstest; |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue