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. 14
      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. 7
      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 { HttpClientModule } from '@angular/common/http';
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 { AgentInfoDialogComponent } from './core/tables/dialogs';
@ -33,6 +35,8 @@ import { AgentInfoDialogComponent } from './core/tables/dialogs';
MatInputModule,
MatDialogModule,
MatProgressSpinnerModule,
MatIconModule,
FormsModule,
BrowserAnimationsModule
],
providers: [],

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

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

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

@ -44,10 +44,16 @@ export class AgentComponent extends TablesComponent<AgentModel> implements OnDes
this.data_source!.getOne(id).then(resp => {
if (resp.status === 'ok') {
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 => {
saveSub.unsubscribe()
this.router.navigate(['.'], { relativeTo: this.route })
})
} else {
@ -56,6 +62,12 @@ export class AgentComponent extends TablesComponent<AgentModel> implements OnDes
}).catch(emitErr)
}
deleteItem(id: string) {
if (confirm(`Delete ${id}?`)) {
this.data_source!.delete(id).catch(emitErr)
}
}
ngOnDestroy(): void {
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>
<div>
<p>ID: {{data.id}}</p>
<p>Alias: {{data.alias}}</p>
<p>Username: {{data.username}}</p>
<p>Hostname: {{data.hostname}}</p>
<p>Platform: {{data.platform}}</p>
<p>Registration time: {{data.regtime.secs_since_epoch * 1000 | date:'long'}}</p>
<p>Last active time: {{data.last_active.secs_since_epoch * 1000 | date:'long'}}</p>
</div>
<p>
<mat-form-field class="info-dlg-field" cdkFocusInitial>
<mat-label>ID</mat-label>
<input matInput disabled value="{{data.id}}">
</mat-form-field>
</p>
<p>
<mat-form-field class="info-dlg-field">
<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-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>
</mat-dialog-actions>

@ -1,11 +1,23 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { AgentModel } from '../../models/agent.model';
import { EventEmitter } from '@angular/core';
import { Input } from '@angular/core';
@Component({
selector: 'agent-info-dialog',
templateUrl: 'agent-info-dialog.html',
styleUrls: ['info-dialog.component.less']
})
export class AgentInfoDialogComponent {
is_preview = true;
onSave = new EventEmitter();
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>
<input matInput (keyup)="apply_filter($event)" #input>
</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
matSortDirection="desc">

@ -8,7 +8,7 @@
<mat-label>Filter</mat-label>
<input matInput (keyup)="apply_filter($event)" #input>
</mat-form-field>
<button id="refresh_btn" mat-raised-button color="primary" (click)="fetchMany()">Refresh</button>
<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
matSortDirection="desc">

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

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

@ -103,7 +103,7 @@ impl Endpoints {
Reportable::Assigned(mut result) => {
let result_agent_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;
}
result.state = JobState::Finished;

@ -90,7 +90,7 @@ class Compose:
]
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.ALL_CONTAINERS = [self.container_tpl %
(c, 1) for c in self.ALL_IMAGES]

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

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

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

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

Loading…
Cancel
Save