edit metadata of simple resources

This commit is contained in:
Michele Artini 2023-01-25 16:08:32 +01:00
parent 085c3123de
commit 0a8a1ec118
12 changed files with 313 additions and 37 deletions

View File

@ -3,7 +3,7 @@ import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { FormsModule } from '@angular/forms';
import { FormsModule,ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { InfoComponent } from './info/info.component';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@ -26,8 +26,7 @@ import { MatExpansionModule } from '@angular/material/expansion';
import { WfDialog, WfHistoryComponent } from './wf-history/wf-history.component';
import { MatDialogModule } from '@angular/material/dialog';
import {MatSortModule} from '@angular/material/sort';
import { ResourcesComponent } from './resources/resources.component'
import { ResourcesComponent, ResContentDialog, ResCreateNewDialog, ResMetadataDialog } from './resources/resources.component'
@NgModule({
declarations: [
@ -38,7 +37,10 @@ import { ResourcesComponent } from './resources/resources.component'
ProtocolsComponent,
WfHistoryComponent,
WfDialog,
ResourcesComponent
ResourcesComponent,
ResContentDialog,
ResCreateNewDialog,
ResMetadataDialog
],
imports: [
BrowserModule,
@ -60,7 +62,8 @@ import { ResourcesComponent } from './resources/resources.component'
MatTableModule,
MatExpansionModule,
MatDialogModule,
MatSortModule
MatSortModule,
ReactiveFormsModule
],
providers: [],
bootstrap: [AppComponent]

View File

@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { ResourceType,Protocol,WfHistoryEntry } from './model/controller.model';
import { ResourceType,Protocol,WfHistoryEntry,SimpleResource } from './model/controller.model';
import { Observable, Observer } from 'rxjs';
@Injectable({
@ -15,6 +15,10 @@ export class ISService {
return this.client.get<ResourceType[]>("/ajax/resourceTypes");
}
loadResourceType(id:string):Observable<ResourceType> {
return this.client.get<ResourceType>("/ajax/resourceTypes/" + encodeURIComponent(id));
}
loadInfo():Observable<any[]> {
return this.client.get<any[]>("/ajax/info/");
}
@ -23,14 +27,25 @@ export class ISService {
return this.client.get<Protocol[]>("/ajax/protocols/");
}
loadSimpleResources(type:string):Observable<SimpleResource[]> {
return this.client.get<SimpleResource[]>("/ajax/resources/" + encodeURIComponent(type));
}
saveSimpleResourceMedatata(res:SimpleResource):Observable<void> {
return this.client.post<void>('/ajax/resources/' + encodeURIComponent(res.id) + '/metadata', res);
}
deleteSimpleResource(res:SimpleResource):Observable<void> {
return this.client.delete<void>('/ajax/resources/' + encodeURIComponent(res.id));
}
loadWfHistory(total?:number, from?:number, to?:number):Observable<WfHistoryEntry[]> {
let params = new HttpParams();
if (total && total > 0) { params = params.append('total', total); }
if (from && from > 0) { params = params.append('from', from); }
if (to && to > 0) { params = params.append('to', to); }
return this.client.get<WfHistoryEntry[]>('/ajax/wfs/', {params: params});
}

View File

@ -1,7 +1,7 @@
<div class="collapse-buttons">
<button mat-button (click)="accordion.openAll()">Expand All</button>
<button mat-button (click)="accordion.closeAll()">Collapse All</button>
<button mat-button color="primary" (click)="accordion.openAll()">Expand All</button>
<button mat-button color="primary" (click)="accordion.closeAll()">Collapse All</button>
</div>
<mat-accordion multi>

View File

@ -11,12 +11,12 @@ export interface KeyValue {
v: string;
}
export interface Module {
export interface Module {
group: string;
name: string;
versions: string[];
files: string[];
}
}
export interface ProtocolParams {
name:string
@ -42,4 +42,13 @@ export interface WfHistoryEntry {
dsName?: string,
dsApi?: string,
details: Map<string,string>
}
}
export interface SimpleResource {
id: string,
name: string,
type: string,
description?: string,
creationDate?: string,
modificationDate?: string
}

View File

@ -0,0 +1,9 @@
<h1 mat-dialog-title>Edit content</h1>
<div mat-dialog-content>
CONTENT FORM HERE
</div>
<div mat-dialog-actions>
<button mat-stroked-button color="primary" mat-dialog-close>Close</button>
</div>

View File

@ -0,0 +1,35 @@
<form [formGroup]="metadataForm" (ngSubmit)="onSubmit()">
<h1 mat-dialog-title>Edit metadata</h1>
<div mat-dialog-content>
<mat-form-field appearance="fill" style="width: 100%;">
<mat-label>ID</mat-label>
<input matInput formControlName="id" required readonly />
<mat-error *ngIf="metadataForm.get('id')?.invalid">This field should not be empty</mat-error>
</mat-form-field>
<mat-form-field appearance="fill" style="width: 100%;">
<mat-label>Type</mat-label>
<input matInput formControlName="type" required readonly />
<mat-error *ngIf="metadataForm.get('type')?.invalid">This field should not be empty</mat-error>
</mat-form-field>
<mat-form-field appearance="fill" style="width: 100%;">
<mat-label>Name</mat-label>
<input matInput formControlName="name" required />
<mat-error *ngIf="metadataForm.get('name')?.invalid">This field is <strong>required</strong></mat-error>
</mat-form-field>
<mat-form-field appearance="fill" style="width: 100%;">
<mat-label>Description</mat-label>
<textarea matInput formControlName="description" rows="8"></textarea>
</mat-form-field>
</div>
<div mat-dialog-actions>
<button mat-stroked-button color="primary" type="submit" [disabled]="!metadataForm.valid">Submit</button>
<button mat-stroked-button color="primary" mat-dialog-close>Close</button>
</div>
</form>

View File

@ -0,0 +1,9 @@
<h1 mat-dialog-title>New resource</h1>
<div mat-dialog-content>
NEW RESOURCE FORM HERE
</div>
<div mat-dialog-actions>
<button mat-stroked-button color="primary" mat-dialog-close>Close</button>
</div>

View File

@ -1 +1,29 @@
<p>resources works!</p>
<h2>{{type.name}}</h2>
<button mat-stroked-button color="primary" (click)="openNewDialog()">create a new resource</button>
<mat-form-field style="width: 100%; margin-top: 10px;">
<mat-label>Filter</mat-label>
<input matInput (keyup)="applyFilter($event)" placeholder="Filter..." #input />
</mat-form-field>
<mat-card class="example-card" *ngFor="let r of resources" style="margin-top: 10px;">
<mat-card-header>
<mat-card-title title="{{r.id}}">{{r.name}} <span class="badge-label badge-info" style="font-size: 0.7em;">{{type.contentType}}</span></mat-card-title>
</mat-card-header>
<mat-card-content>
<p>{{r.description}}</p>
<p class="muted small">
<b>Id:</b> {{r.id}}<br /> <b>Creation date:</b> {{r.creationDate}}<br /> <b>Modification date:</b> {{r.modificationDate}}
</p>
</mat-card-content>
<mat-card-actions>
<button mat-stroked-button color="primary" (click)="openMetadataDialog(r)">edit metadata</button>
<button mat-stroked-button color="primary" (click)="openContentDialog(r)">edit content</button>
<a href="./api/resources/{{r.id}}/content" mat-stroked-button color="link" target="_blank">raw content</a>
<button mat-stroked-button color="warn" (click)="deleteResource(r)">delete</button>
</mat-card-actions>
</mat-card>

View File

@ -1,10 +1,170 @@
import { Component } from '@angular/core';
import { Component, Inject,AfterViewInit, ViewChild, OnInit } from '@angular/core';
import { ISService } from '../is.service';
import { MatTableDataSource } from '@angular/material/table';
import { MatSort, Sort } from '@angular/material/sort';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { MatDialog, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { ResourceType, SimpleResource } from '../model/controller.model';
import {FormControl, FormGroup, FormGroupDirective, NgForm, Validators} from '@angular/forms';
import { ResourceLoader } from '@angular/compiler';
@Component({
selector: 'app-resources',
templateUrl: './resources.component.html',
styleUrls: ['./resources.component.css']
})
export class ResourcesComponent {
export class ResourcesComponent implements OnInit {
typeId:string = '';
type:ResourceType = { id: '', name: '', contentType: '', count: 0, simple: true };
resources:SimpleResource[] = [];
constructor(public service: ISService, public route: ActivatedRoute, public newDialog: MatDialog, public contentDialog: MatDialog, public metadataDialog: MatDialog) {
}
ngOnInit() {
this.route.params.subscribe(params => {
this.typeId = params['type'];
this.service.loadResourceType(this.typeId).subscribe({
next: (data: ResourceType) => this.type = data,
error: error => console.log(error),
complete: () => console.log("Completed")
});
this.reload()
});
}
reload() {
if (this.typeId) {
console.log('reload');
this.service.loadSimpleResources(this.typeId).subscribe({
next: (data: SimpleResource[]) => this.resources = data,
error: error => console.log(error),
complete: () => console.log("Completed")
});
}
}
applyFilter(event: Event) {
const filterValue = (event.target as HTMLInputElement).value.trim().toLowerCase();
//this.historyDatasource.filter = filterValue;
}
openNewDialog(): void {
const dialogRef = this.newDialog.open(ResCreateNewDialog, {
data: {
}
});
dialogRef.afterClosed().subscribe(result => {
if (result) this.reload();
});
}
openMetadataDialog(r:SimpleResource): void {
const dialogRef = this.metadataDialog.open(ResMetadataDialog, {
data: r
});
dialogRef.afterClosed().subscribe(result => {
if (result) this.reload();
});
}
openContentDialog(r:SimpleResource): void {
const dialogRef = this.contentDialog.open(ResContentDialog, {
data: r
});
dialogRef.afterClosed().subscribe(result => {
if (result) this.reload();
});
}
deleteResource(r:SimpleResource) {
if (confirm('Are you sure?')) {
this.service.deleteSimpleResource(r).subscribe({
next: (data: void) => this.reload(),
error: error => console.log(error),
complete: () => console.log("Completed")
});
}
}
}
@Component({
selector: 'res-content-dialog',
templateUrl: 'content-dialog.html',
styleUrls: ['resources.component.css']
})
export class ResContentDialog {
constructor(public dialogRef: MatDialogRef<ResContentDialog>, @Inject(MAT_DIALOG_DATA) public data: SimpleResource, public service: ISService) {
}
onNoClick(): void {
this.dialogRef.close();
}
}
@Component({
selector: 'res-metadata-dialog',
templateUrl: 'metadata-dialog.html',
styleUrls: ['resources.component.css']
})
export class ResMetadataDialog {
metadataForm = new FormGroup({
id: new FormControl('', [Validators.required]),
type: new FormControl('', [Validators.required]),
name: new FormControl('', [Validators.required]),
description : new FormControl('', [])
});
constructor(public dialogRef: MatDialogRef<ResMetadataDialog>, @Inject(MAT_DIALOG_DATA) public data: SimpleResource, public service: ISService) {
this.metadataForm.get('id')?.setValue(data.id);
this.metadataForm.get('type')?.setValue(data.type);
this.metadataForm.get('name')?.setValue(data.name);
if (data.description) {
this.metadataForm.get('description')?.setValue(data.description);
}
}
onSubmit():void {
const res = Object.assign({}, this.data, this.metadataForm.value);
this.service.saveSimpleResourceMedatata(res).subscribe({
next: (data: void) => {
this.dialogRef.close(1)
},
error: error => console.log(error),
complete: () => console.log("Completed")
});
}
onNoClick(): void {
this.dialogRef.close();
}
}
@Component({
selector: 'res-new-dialog',
templateUrl: 'new-dialog.html',
styleUrls: ['resources.component.css']
})
export class ResCreateNewDialog {
constructor(public dialogRef: MatDialogRef<ResCreateNewDialog>, @Inject(MAT_DIALOG_DATA) public resource: string, public service: ISService) {
}
onNoClick(): void {
this.dialogRef.close();
}
}

View File

@ -36,5 +36,5 @@
</div>
<div mat-dialog-actions>
<button mat-button mat-dialog-close>Close</button>
<button mat-stroked-button color="primary" mat-dialog-close>Close</button>
</div>

View File

@ -1,10 +1,10 @@
import { Component, Inject,AfterViewInit, ViewChild } from '@angular/core';
import { Component, Inject,AfterViewInit, OnInit, ViewChild } from '@angular/core';
import { ISService } from '../is.service';
import { MatTableDataSource } from '@angular/material/table';
import { MatSort, Sort } from '@angular/material/sort';
import { WfHistoryEntry } from '../model/controller.model';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { ActivatedRoute, Params } from '@angular/router';
import { Observable, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
import { MatDialog, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { KeyValue } from '../model/controller.model';
@ -14,7 +14,7 @@ import { KeyValue } from '../model/controller.model';
templateUrl: './wf-history.component.html',
styleUrls: ['./wf-history.component.css']
})
export class WfHistoryComponent implements AfterViewInit {
export class WfHistoryComponent implements AfterViewInit , OnInit{
historyDatasource: MatTableDataSource<WfHistoryEntry> = new MatTableDataSource<WfHistoryEntry>([]);
@ -24,21 +24,28 @@ export class WfHistoryComponent implements AfterViewInit {
from: number = -1
to: number = -1
constructor(public service: ISService, public route: ActivatedRoute, public dialog: MatDialog) {
constructor(public service: ISService, public route: ActivatedRoute, public dialog: MatDialog) {
}
let totalP = route.snapshot.paramMap.get('total');
let fromP = route.snapshot.queryParamMap.get('from');
let toP = route.snapshot.queryParamMap.get('to');
if (totalP) { this.total = parseInt(totalP); }
if (fromP) { this.from = parseInt(fromP); }
if (toP) { this.to = parseInt(toP); }
this.service.loadWfHistory(this.total, this.from, this.to).subscribe({
next: (data: WfHistoryEntry[]) => this.historyDatasource.data = data,
error: error => console.log(error),
complete: () => console.log("Completed")
})
ngOnInit() {
combineLatest([ this.route.params, this.route.queryParams ],
(params: Params, queryParams: Params) => ({ params, queryParams })
).subscribe((res: { params: Params; queryParams: Params }) => {
const { params, queryParams} = res;
let totalP = queryParams['total'];
let fromP = queryParams['from'];
let toP = queryParams['to'];
if (totalP) { this.total = parseInt(totalP); }
if (fromP) { this.from = parseInt(fromP); }
if (toP) { this.to = parseInt(toP); }
this.service.loadWfHistory(this.total, this.from, this.to).subscribe({
next: (data: WfHistoryEntry[]) => this.historyDatasource.data = data,
error: error => console.log(error),
complete: () => console.log("Completed")
})
});
}
@ViewChild(MatSort) sort: MatSort | undefined

View File

@ -3,6 +3,9 @@
html, body { height: 100%; }
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
.small { font-size: 0.9em !important; }
.muted { color: darkgray; }
table {
width: 100%;
}
@ -44,8 +47,6 @@ th, td {
padding-bottom: 0.5em !important;
}
.muted { color: darkgray; }
.badge-label {
padding-top: 0.3em;
padding-bottom: 0.3em;