more like crud

- now can edit and delete agents
- add host info
pull/1/head
plazmoid 2 years ago
parent 544c07cf8d
commit 862fb6b338
  1. 4
      bin/u_panel/src/server/fe/src/app/app.module.ts
  2. 1
      bin/u_panel/src/server/fe/src/app/core/models/agent.model.ts
  3. 6
      bin/u_panel/src/server/fe/src/app/core/services/api.service.ts
  4. 9
      bin/u_panel/src/server/fe/src/app/core/tables/agent.component.html
  5. 14
      bin/u_panel/src/server/fe/src/app/core/tables/agent.component.ts
  6. 69
      bin/u_panel/src/server/fe/src/app/core/tables/dialogs/agent-info-dialog.html
  7. 12
      bin/u_panel/src/server/fe/src/app/core/tables/dialogs/agent_info.component.ts
  8. 3
      bin/u_panel/src/server/fe/src/app/core/tables/dialogs/info-dialog.component.less
  9. 2
      bin/u_panel/src/server/fe/src/app/core/tables/job.component.html
  10. 2
      bin/u_panel/src/server/fe/src/app/core/tables/result.component.html
  11. 4
      bin/u_panel/src/server/fe/src/app/core/tables/table.component.less
  12. 4
      bin/u_panel/src/server/fe/src/app/core/tables/table.component.ts
  13. 2
      bin/u_server/src/handlers.rs
  14. 2
      integration/docker.py
  15. 11
      lib/u_lib/src/models/agent.rs
  16. 1
      lib/u_lib/src/models/schema.rs
  17. 5
      lib/u_lib/src/runner.rs
  18. 1
      migrations/2020-10-24-111622_create_all/up.sql

@ -11,6 +11,8 @@ import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { FormsModule } from '@angular/forms';
import { AgentComponent, JobComponent, ResultComponent } from './core/tables'; import { AgentComponent, JobComponent, ResultComponent } from './core/tables';
import { AgentInfoDialogComponent } from './core/tables/dialogs'; import { AgentInfoDialogComponent } from './core/tables/dialogs';
@ -33,6 +35,8 @@ import { AgentInfoDialogComponent } from './core/tables/dialogs';
MatInputModule, MatInputModule,
MatDialogModule, MatDialogModule,
MatProgressSpinnerModule, MatProgressSpinnerModule,
MatIconModule,
FormsModule,
BrowserAnimationsModule BrowserAnimationsModule
], ],
providers: [], providers: [],

@ -3,6 +3,7 @@ import { UTCDate } from ".";
export interface AgentModel { export interface AgentModel {
alias: string | null, alias: string | null,
hostname: string, hostname: string,
host_info: string,
id: string, id: string,
is_root: boolean, is_root: boolean,
is_root_allowed: boolean, is_root_allowed: boolean,

@ -21,12 +21,12 @@ export class ApiTableService<T> {
return await firstValueFrom(this.http.post<ServerResponse<R>>(this.requestUrl, cmd)) return await firstValueFrom(this.http.post<ServerResponse<R>>(this.requestUrl, cmd))
} }
async getOne(id: string): Promise<ServerResponse<T>> { async getOne(id: string, area: string = this.area): Promise<ServerResponse<T>> {
const resp = await this.req<T[]>(`${this.area} read ${id}`) const resp = await this.req<T[]>(`${area} read ${id}`)
if (resp.data.length === 0) { if (resp.data.length === 0) {
return { return {
status: 'err', status: 'err',
data: `${id} not found in ${this.area}` data: `${id} not found in ${area}`
} }
} }
return { return {

@ -8,7 +8,7 @@
<mat-label>Filter</mat-label> <mat-label>Filter</mat-label>
<input matInput (keyup)="apply_filter($event)" #input> <input matInput (keyup)="apply_filter($event)" #input>
</mat-form-field> </mat-form-field>
<button id="refresh_btn" mat-raised-button color="primary" (click)="fetchMany()">Refresh</button> <button id="refresh_btn" mat-raised-button color="primary" (click)="loadTableData()">Refresh</button>
<table mat-table [dataSource]="table_data" class="data-table" matSort matSortActive="id" matSortDisableClear <table mat-table [dataSource]="table_data" class="data-table" matSort matSortActive="id" matSortDisableClear
matSortDirection="desc"> matSortDirection="desc">
@ -51,7 +51,12 @@
<ng-container matColumnDef="actions"> <ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef></th> <th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let row"> <td mat-cell *matCellDef="let row">
<button mat-raised-button routerLink='' [queryParams]="{id: row.id}">Info</button> <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> </td>
</ng-container> </ng-container>

@ -44,10 +44,16 @@ export class AgentComponent extends TablesComponent<AgentModel> implements OnDes
this.data_source!.getOne(id).then(resp => { this.data_source!.getOne(id).then(resp => {
if (resp.status === 'ok') { if (resp.status === 'ok') {
const dialog = this.info_dlg.open(AgentInfoDialogComponent, { const dialog = this.info_dlg.open(AgentInfoDialogComponent, {
data: resp.data as AgentModel data: resp.data as AgentModel,
width: '500px',
}); });
const saveSub = dialog.componentInstance.onSave.subscribe(result => {
this.data_source!.update(result).then(_ => this.loadTableData()).catch(emitErr)
})
dialog.afterClosed().subscribe(result => { dialog.afterClosed().subscribe(result => {
saveSub.unsubscribe()
this.router.navigate(['.'], { relativeTo: this.route }) this.router.navigate(['.'], { relativeTo: this.route })
}) })
} else { } else {
@ -56,6 +62,12 @@ export class AgentComponent extends TablesComponent<AgentModel> implements OnDes
}).catch(emitErr) }).catch(emitErr)
} }
deleteItem(id: string) {
if (confirm(`Delete ${id}?`)) {
this.data_source!.delete(id).catch(emitErr)
}
}
ngOnDestroy(): void { ngOnDestroy(): void {
this.dialogSubscr.unsubscribe() this.dialogSubscr.unsubscribe()
} }

@ -1,15 +1,64 @@
<h2 mat-dialog-title *ngIf="is_preview">Agent info</h2>
<h2 mat-dialog-title *ngIf="!is_preview">Editing agent info</h2>
<mat-dialog-content> <mat-dialog-content>
<div> <p>
<p>ID: {{data.id}}</p> <mat-form-field class="info-dlg-field" cdkFocusInitial>
<p>Alias: {{data.alias}}</p> <mat-label>ID</mat-label>
<p>Username: {{data.username}}</p> <input matInput disabled value="{{data.id}}">
<p>Hostname: {{data.hostname}}</p> </mat-form-field>
<p>Platform: {{data.platform}}</p> </p>
<p>Registration time: {{data.regtime.secs_since_epoch * 1000 | date:'long'}}</p> <p>
<p>Last active time: {{data.last_active.secs_since_epoch * 1000 | date:'long'}}</p> <mat-form-field class="info-dlg-field">
</div> <mat-label>Alias</mat-label>
<input matInput [readonly]="is_preview" [(ngModel)]="data.alias">
</mat-form-field>
</p>
<p>
<mat-form-field class="info-dlg-field">
<mat-label>Username</mat-label>
<input matInput [readonly]="is_preview" [(ngModel)]="data.username">
</mat-form-field>
</p>
<p>
<mat-form-field class="info-dlg-field">
<mat-label>Hostname</mat-label>
<input matInput [readonly]="is_preview" [(ngModel)]="data.hostname">
</mat-form-field>
</p>
<p>
<mat-form-field class="info-dlg-field">
<mat-label>Host info</mat-label>
<textarea matInput [readonly]="is_preview" [(ngModel)]="data.host_info">
</textarea>
</mat-form-field>
</p>
<p>
<mat-form-field class="info-dlg-field">
<mat-label>Platform</mat-label>
<input matInput [readonly]="is_preview" [(ngModel)]="data.platform">
</mat-form-field>
</p>
<p>
<mat-form-field class="info-dlg-field">
<mat-label>Is root</mat-label>
<input matInput disabled value="{{data.is_root}}">
</mat-form-field>
</p>
<p>
<mat-form-field class="info-dlg-field">
<mat-label>Registration time</mat-label>
<input matInput disabled value="{{data.regtime.secs_since_epoch * 1000 | date:'long'}}">
</mat-form-field>
</p>
<p>
<mat-form-field class="info-dlg-field">
<mat-label>Last active time</mat-label>
<input matInput disabled value="{{data.last_active.secs_since_epoch * 1000 | date:'long'}}">
</mat-form-field>
</p>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions align="end"> <mat-dialog-actions align="end">
<button mat-button cdkFocusInitial>Edit</button> <button mat-raised-button *ngIf="is_preview" (click)="is_preview = false">Edit</button>
<button mat-raised-button *ngIf="!is_preview" (click)="updateAgent()">Save</button>
<button mat-button mat-dialog-close>Cancel</button> <button mat-button mat-dialog-close>Cancel</button>
</mat-dialog-actions> </mat-dialog-actions>

@ -1,11 +1,23 @@
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 { Input } from '@angular/core';
@Component({ @Component({
selector: 'agent-info-dialog', selector: 'agent-info-dialog',
templateUrl: 'agent-info-dialog.html', templateUrl: 'agent-info-dialog.html',
styleUrls: ['info-dialog.component.less']
}) })
export class AgentInfoDialogComponent { export class AgentInfoDialogComponent {
is_preview = true;
onSave = new EventEmitter();
constructor(@Inject(MAT_DIALOG_DATA) public data: AgentModel) { } constructor(@Inject(MAT_DIALOG_DATA) public data: AgentModel) { }
updateAgent() {
console.log(this.data);
this.onSave.emit(this.data);
}
} }

@ -8,7 +8,7 @@
<mat-label>Filter</mat-label> <mat-label>Filter</mat-label>
<input matInput (keyup)="apply_filter($event)" #input> <input matInput (keyup)="apply_filter($event)" #input>
</mat-form-field> </mat-form-field>
<button id="refresh_btn" mat-raised-button color="primary" (click)="fetchMany()">Refresh</button> <button id="refresh_btn" mat-raised-button color="primary" (click)="loadTableData()">Refresh</button>
<table mat-table [dataSource]="table_data" class="data-table" matSort matSortActive="id" matSortDisableClear <table mat-table [dataSource]="table_data" class="data-table" matSort matSortActive="id" matSortDisableClear
matSortDirection="desc"> matSortDirection="desc">

@ -8,7 +8,7 @@
<mat-label>Filter</mat-label> <mat-label>Filter</mat-label>
<input matInput (keyup)="apply_filter($event)" #input> <input matInput (keyup)="apply_filter($event)" #input>
</mat-form-field> </mat-form-field>
<button id="refresh_btn" mat-raised-button color="primary" (click)="fetchMany()">Refresh</button> <button id="refresh_btn" mat-raised-button color="primary" (click)="loadTableData()">Refresh</button>
<table mat-table [dataSource]="table_data" class="data-table" matSort matSortActive="id" matSortDisableClear <table mat-table [dataSource]="table_data" class="data-table" matSort matSortActive="id" matSortDisableClear
matSortDirection="desc"> matSortDirection="desc">

@ -23,6 +23,10 @@
margin-left: 10px; margin-left: 10px;
} }
.data-table-row {
height: 30px;
}
.data-table-row:hover { .data-table-row:hover {
background: whitesmoke; background: whitesmoke;
} }

@ -20,10 +20,10 @@ export abstract class TablesComponent<T> implements OnInit {
ngOnInit() { ngOnInit() {
this.data_source = new ApiTableService(this._httpClient, this.area); this.data_source = new ApiTableService(this._httpClient, this.area);
this.fetchMany(); this.loadTableData();
} }
async fetchMany() { async loadTableData() {
this.isLoadingResults = true; this.isLoadingResults = true;
//possibly needs try/catch //possibly needs try/catch
const data = await this.data_source!.getMany(); const data = await this.data_source!.getMany();

@ -103,7 +103,7 @@ impl Endpoints {
Reportable::Assigned(mut result) => { Reportable::Assigned(mut result) => {
let result_agent_id = &result.agent_id; let result_agent_id = &result.agent_id;
if id != *result_agent_id { if id != *result_agent_id {
warn!("Ids are not equal! actual id: {id}, job id: {result_agent_id}"); warn!("Ids are not equal! actual id: {id}, id from job: {result_agent_id}");
continue; continue;
} }
result.state = JobState::Finished; result.state = JobState::Finished;

@ -90,7 +90,7 @@ class Compose:
] ]
def __init__(self): def __init__(self):
self.container_tpl = 'integration_%s_%d' self.container_tpl = 'integration-%s-%d'
self.cmd_container = self.container_tpl % ('tests_runner', 1) self.cmd_container = self.container_tpl % ('tests_runner', 1)
self.ALL_CONTAINERS = [self.container_tpl % self.ALL_CONTAINERS = [self.container_tpl %
(c, 1) for c in self.ALL_IMAGES] (c, 1) for c in self.ALL_IMAGES]

@ -41,6 +41,7 @@ pub enum AgentState {
pub struct Agent { pub struct Agent {
pub alias: Option<String>, pub alias: Option<String>,
pub hostname: String, pub hostname: String,
pub host_info: String,
pub id: Uuid, pub id: Uuid,
pub ip_gray: Option<String>, pub ip_gray: Option<String>,
pub ip_white: Option<String>, pub ip_white: Option<String>,
@ -86,7 +87,8 @@ impl Agent {
#[cfg(unix)] #[cfg(unix)]
pub async fn gather() -> Self { pub async fn gather() -> Self {
let mut builder = NamedJobRunner::from_shell(vec![ let mut builder = NamedJobRunner::from_shell(vec![
("hostname", "hostname"), ("hostname", "hostnamectl hostname"),
("host_info", "hostnamectl --json=pretty"),
("is_root", "id -u"), ("is_root", "id -u"),
("username", "id -un"), ("username", "id -un"),
]) ])
@ -98,6 +100,7 @@ impl Agent {
Self { Self {
hostname: decoder(builder.pop("hostname")), hostname: decoder(builder.pop("hostname")),
host_info: decoder(builder.pop("host_info")),
is_root: &decoder(builder.pop("is_root")) == "0", is_root: &decoder(builder.pop("is_root")) == "0",
username: decoder(builder.pop("username")), username: decoder(builder.pop("username")),
platform: Platform::current_as_string(), platform: Platform::current_as_string(),
@ -105,11 +108,6 @@ impl Agent {
} }
} }
#[cfg(not(unix))]
pub async fn gather() -> Self {
todo!()
}
pub async fn run() -> Agent { pub async fn run() -> Agent {
Agent::gather().await Agent::gather().await
} }
@ -121,6 +119,7 @@ impl Default for Agent {
alias: None, alias: None,
id: get_self_uid(), id: get_self_uid(),
hostname: String::new(), hostname: String::new(),
host_info: String::new(),
is_root: false, is_root: false,
is_root_allowed: false, is_root_allowed: false,
last_active: SystemTime::now(), last_active: SystemTime::now(),

@ -4,6 +4,7 @@ table! {
agents (id) { agents (id) {
alias -> Nullable<Text>, alias -> Nullable<Text>,
hostname -> Text, hostname -> Text,
host_info -> Text,
id -> Uuid, id -> Uuid,
ip_gray -> Nullable<Text>, ip_gray -> Nullable<Text>,
ip_white -> Nullable<Text>, ip_white -> Nullable<Text>,

@ -52,7 +52,9 @@ impl JobRunner {
.into_iter() .into_iter()
.map(|jm| { .map(|jm| {
let job_id = jm.id; let job_id = jm.id;
if !JobCache::contains(job_id) {
JobCache::insert(jm); JobCache::insert(jm);
}
AssignedJobById { AssignedJobById {
job_id, job_id,
..Default::default() ..Default::default()
@ -166,8 +168,9 @@ impl NamedJobRunner {
let job_metas: Vec<JobMeta> = named_jobs let job_metas: Vec<JobMeta> = named_jobs
.into_vec() .into_vec()
.into_iter() .into_iter()
.map(|(alias, meta)| { .map(|(alias, mut meta)| {
job_names.push(alias); job_names.push(alias);
meta.alias = Some(alias.to_string());
meta meta
}) })
.collect(); .collect();

@ -6,6 +6,7 @@ CREATE TYPE AgentState AS ENUM ('new', 'active', 'banned');
CREATE TABLE IF NOT EXISTS agents ( CREATE TABLE IF NOT EXISTS agents (
alias TEXT, alias TEXT,
hostname TEXT NOT NULL, hostname TEXT NOT NULL,
host_info TEXT NOT NULL,
id UUID NOT NULL DEFAULT uuid_generate_v4(), id UUID NOT NULL DEFAULT uuid_generate_v4(),
ip_gray TEXT, ip_gray TEXT,
ip_white TEXT, ip_white TEXT,

Loading…
Cancel
Save