mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-06-07 01:31:35 +00:00
Permission management, create add permission component and apiInformation service, which parse swagger api information schema
This commit is contained in:
parent
2664911455
commit
65f1d45dc5
@ -300,6 +300,9 @@ import { PermissionEditorValidateDialogComponent } from './components/role-manag
|
|||||||
import { PermissionsManagementComponent } from './components/permissions-management/permissions-management.component';
|
import { PermissionsManagementComponent } from './components/permissions-management/permissions-management.component';
|
||||||
import { PermissionEditLineComponent } from '@components/permissions-management/permission-edit-line/permission-edit-line.component';
|
import { PermissionEditLineComponent } from '@components/permissions-management/permission-edit-line/permission-edit-line.component';
|
||||||
import {MatSlideToggleModule} from '@angular/material/slide-toggle';
|
import {MatSlideToggleModule} from '@angular/material/slide-toggle';
|
||||||
|
import {MatAutocompleteModule} from "@angular/material/autocomplete";
|
||||||
|
import {PathAutoCompleteComponent} from './components/permissions-management/add-permission-line/path-auto-complete/path-auto-complete.component';
|
||||||
|
import {FilterCompletePipe} from './components/permissions-management/add-permission-line/path-auto-complete/filter-complete.pipe';
|
||||||
import { AddPermissionLineComponent } from './components/permissions-management/add-permission-line/add-permission-line.component';
|
import { AddPermissionLineComponent } from './components/permissions-management/add-permission-line/add-permission-line.component';
|
||||||
import { MethodButtonComponent } from './components/permissions-management/method-button/method-button.component';
|
import { MethodButtonComponent } from './components/permissions-management/method-button/method-button.component';
|
||||||
import { ActionButtonComponent } from './components/permissions-management/action-button/action-button.component';
|
import { ActionButtonComponent } from './components/permissions-management/action-button/action-button.component';
|
||||||
@ -523,7 +526,9 @@ import {MatFormFieldModule} from "@angular/material/form-field";
|
|||||||
AddPermissionLineComponent,
|
AddPermissionLineComponent,
|
||||||
MethodButtonComponent,
|
MethodButtonComponent,
|
||||||
ActionButtonComponent,
|
ActionButtonComponent,
|
||||||
DeletePermissionDialogComponent
|
DeletePermissionDialogComponent,
|
||||||
|
PathAutoCompleteComponent,
|
||||||
|
FilterCompletePipe
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
@ -537,16 +542,17 @@ import {MatFormFieldModule} from "@angular/material/form-field";
|
|||||||
NgxElectronModule,
|
NgxElectronModule,
|
||||||
FileUploadModule,
|
FileUploadModule,
|
||||||
MatSidenavModule,
|
MatSidenavModule,
|
||||||
|
MatFormFieldModule,
|
||||||
ResizableModule,
|
ResizableModule,
|
||||||
DragAndDropModule,
|
DragAndDropModule,
|
||||||
DragDropModule,
|
DragDropModule,
|
||||||
NgxChildProcessModule,
|
NgxChildProcessModule,
|
||||||
MatFormFieldModule,
|
|
||||||
MATERIAL_IMPORTS,
|
MATERIAL_IMPORTS,
|
||||||
NgCircleProgressModule.forRoot(),
|
NgCircleProgressModule.forRoot(),
|
||||||
OverlayModule,
|
OverlayModule,
|
||||||
MatSlideToggleModule,
|
MatSlideToggleModule,
|
||||||
MatCheckboxModule,
|
MatCheckboxModule,
|
||||||
|
MatAutocompleteModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SettingsService,
|
SettingsService,
|
||||||
|
@ -1,19 +1,48 @@
|
|||||||
<mat-form-field appearance="fill">
|
<div class="box-border">
|
||||||
<mat-label>Type</mat-label>
|
<div *ngIf="edit; else add">
|
||||||
<mat-select [(ngModel)]="selectedType" (ngModelChange)="changeType($event)">
|
<div class="edit-mode">
|
||||||
<mat-option *ngFor="let type of objectTypes" [value]="type">{{type}}</mat-option>
|
<div class="information-box">
|
||||||
</mat-select>
|
<div>
|
||||||
|
<app-path-auto-complete
|
||||||
|
[server]="server"
|
||||||
|
(update)="permission.path = $event"></app-path-auto-complete>
|
||||||
|
</div>
|
||||||
|
<div class="methods">
|
||||||
|
<app-action-button
|
||||||
|
[disabled]="false"
|
||||||
|
[action]="permission.action"></app-action-button>
|
||||||
|
<div *ngFor="let method of apiInformation.getMethods(permission.path) | async">
|
||||||
|
<app-method-button
|
||||||
|
[name]="method"
|
||||||
|
[disabled]="false"
|
||||||
|
(update)="updateMethod($event)"></app-method-button>
|
||||||
|
</div>
|
||||||
|
<div class="description">
|
||||||
|
<mat-form-field>
|
||||||
|
<input
|
||||||
|
[(ngModel)]="permission.description"
|
||||||
|
matInput
|
||||||
|
type="text"
|
||||||
|
placeholder="Description"/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
<mat-form-field appearance="fill">
|
</div>
|
||||||
<mat-select>
|
</div>
|
||||||
<mat-option *ngFor="let elt of elements" [value]="elt">{{elt.name ? elt.name : elt.filename}}</mat-option>
|
<div class="button-box">
|
||||||
</mat-select>
|
<button mat-button (click)="reset()">
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<button mat-button matTooltip="Save Changes" (click)="onSave()" color="primary">
|
|
||||||
<mat-icon>check_circle</mat-icon>
|
|
||||||
</button>
|
|
||||||
<button mat-button matTooltip="Cancel Changes" color="warn" (click)="onCancel()">
|
|
||||||
<mat-icon>cancel</mat-icon>
|
<mat-icon>cancel</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
<button mat-button (click)="save()">
|
||||||
|
<mat-icon>done</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ng-template #add>
|
||||||
|
<div class="not-edit">
|
||||||
|
<button mat-button (click)="edit = true">
|
||||||
|
<mat-icon>add</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
.box-border {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 20px;
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-mode {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.information-box {
|
||||||
|
margin-left: 10px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.information-box > div {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.methods {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description > mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-edit {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
}
|
@ -10,11 +10,12 @@
|
|||||||
*
|
*
|
||||||
* Author: Sylvain MATHIEU, Elise LEBEAU
|
* Author: Sylvain MATHIEU, Elise LEBEAU
|
||||||
*/
|
*/
|
||||||
import {Component, Input, OnInit, Output} from '@angular/core';
|
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
|
||||||
import {ProjectService} from "@services/project.service";
|
|
||||||
import {Server} from "@models/server";
|
import {Server} from "@models/server";
|
||||||
import {ComputeService} from "@services/compute.service";
|
import {ApiInformationService} from "@services/api-information.service";
|
||||||
import EventEmitter from "events";
|
import {Methods, Permission, PermissionActions} from "@models/api/permission";
|
||||||
|
import {PermissionsService} from "@services/permissions.service";
|
||||||
|
import {ToasterService} from "@services/toaster.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-add-permission-line',
|
selector: 'app-add-permission-line',
|
||||||
@ -23,51 +24,56 @@ import EventEmitter from "events";
|
|||||||
})
|
})
|
||||||
export class AddPermissionLineComponent implements OnInit {
|
export class AddPermissionLineComponent implements OnInit {
|
||||||
|
|
||||||
objectTypes = ['projects', 'images', 'templates', 'computes']
|
|
||||||
elements = [];
|
|
||||||
selectedType = 'projects';
|
|
||||||
@Input() server: Server;
|
@Input() server: Server;
|
||||||
|
@Output() addPermissionEvent = new EventEmitter<void>();
|
||||||
|
permission: Permission = {
|
||||||
|
action: PermissionActions.ALLOW,
|
||||||
|
description: "",
|
||||||
|
methods: [],
|
||||||
|
path: "/",
|
||||||
|
};
|
||||||
|
edit = false;
|
||||||
|
|
||||||
@Output() addPermissionEvent = new EventEmitter();
|
constructor(public apiInformation: ApiInformationService,
|
||||||
|
private permissionService: PermissionsService,
|
||||||
|
private toasterService: ToasterService) {
|
||||||
|
|
||||||
constructor(private projectService: ProjectService,
|
}
|
||||||
private computeService: ComputeService) { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.projectService.list(this.server)
|
|
||||||
.subscribe(elts => {
|
|
||||||
this.elements = elts;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
changeType(value) {
|
|
||||||
console.log(value);
|
|
||||||
this.selectedType = value;
|
|
||||||
switch (this.selectedType) {
|
|
||||||
case 'projects':
|
|
||||||
this.projectService.list(this.server)
|
|
||||||
.subscribe(elts => {
|
|
||||||
this.elements = elts;
|
|
||||||
})
|
|
||||||
break;
|
|
||||||
case 'computes':
|
|
||||||
this.computeService.getComputes(this.server)
|
|
||||||
.subscribe(elts => {
|
|
||||||
this.elements = elts;
|
|
||||||
})
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log("TODO");
|
|
||||||
this.elements = [];
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
updateMethod(data: { name: Methods; enable: boolean }) {
|
||||||
|
const set = new Set(this.permission.methods);
|
||||||
|
if (data.enable) {
|
||||||
|
set.add(data.name);
|
||||||
|
} else {
|
||||||
|
set.delete(data.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSave() {
|
this.permission.methods = Array.from(set);
|
||||||
this.addPermissionEvent.emit('save');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onCancel() {
|
reset() {
|
||||||
this.addPermissionEvent.emit('cancel');
|
this.permission = {
|
||||||
|
action: PermissionActions.ALLOW,
|
||||||
|
description: "",
|
||||||
|
methods: [],
|
||||||
|
path: "/",
|
||||||
|
};
|
||||||
|
|
||||||
|
this.edit = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
this.permissionService.add(this.server, this.permission)
|
||||||
|
.subscribe(() => {
|
||||||
|
this.toasterService.success(`permission was created`);
|
||||||
|
this.reset();
|
||||||
|
}, (error) => {
|
||||||
|
this.toasterService.error(error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
import { FilterCompletePipe } from './filter-complete.pipe';
|
||||||
|
|
||||||
|
describe('FilterCompletePipe', () => {
|
||||||
|
it('create an instance', () => {
|
||||||
|
const pipe = new FilterCompletePipe();
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,17 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import {IFormatedList} from "@services/api-information.service";
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'filterComplete'
|
||||||
|
})
|
||||||
|
export class FilterCompletePipe implements PipeTransform {
|
||||||
|
|
||||||
|
transform(value: IFormatedList[], searchText: string): IFormatedList[] {
|
||||||
|
if (!searchText || searchText === '') { return value; }
|
||||||
|
|
||||||
|
return value.filter((v) => {
|
||||||
|
return v.name.includes(searchText) || v.id.includes(searchText);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
<div class="path">
|
||||||
|
<div>Path: /</div>
|
||||||
|
<div *ngFor="let p of path">{{p}}/</div>
|
||||||
|
<div class="path-edit-line">
|
||||||
|
<div>
|
||||||
|
<div *ngIf="mode === 'SELECT'">
|
||||||
|
<mat-select (valueChange)="valueChanged($event)" class="edit-area">
|
||||||
|
<mat-option *ngFor="let value of values" value="{{value}}">{{value}}</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="mode === 'COMPLETE'">
|
||||||
|
<input matInput
|
||||||
|
autofocus
|
||||||
|
class="complete edit-area"
|
||||||
|
aria-label="find"
|
||||||
|
[(ngModel)]="completeField"
|
||||||
|
[matAutocomplete]="auto">
|
||||||
|
<mat-autocomplete #auto="matAutocomplete">
|
||||||
|
<mat-option [value]="'*'">*</mat-option>
|
||||||
|
<mat-option *ngFor="let data of completeData | filterComplete: completeField"
|
||||||
|
[value]="data.id">
|
||||||
|
<span>{{data.name}}</span> |
|
||||||
|
<small>{{data.id}}</small>
|
||||||
|
</mat-option>
|
||||||
|
</mat-autocomplete>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="command-button">
|
||||||
|
<mat-icon (click)="removePrevious()" *ngIf="path.length > 0">cancel</mat-icon>
|
||||||
|
<mat-icon (click)="getNext()" *ngIf="!this.mode">add_circle_outline</mat-icon>
|
||||||
|
<mat-icon
|
||||||
|
matTooltip="validate data"
|
||||||
|
(click)="validComplete()"
|
||||||
|
*ngIf="this.mode === 'COMPLETE'">check_circle</mat-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,22 @@
|
|||||||
|
.path {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-select {
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-area {
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-button {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-edit-line {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { PathAutoCompleteComponent } from './path-auto-complete.component';
|
||||||
|
|
||||||
|
describe('PathAutoCompleteComponent', () => {
|
||||||
|
let component: PathAutoCompleteComponent;
|
||||||
|
let fixture: ComponentFixture<PathAutoCompleteComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ PathAutoCompleteComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(PathAutoCompleteComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,74 @@
|
|||||||
|
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
|
||||||
|
import {ApiInformationService, IFormatedList} from "@services/api-information.service";
|
||||||
|
import {Server} from "@models/server";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-path-auto-complete',
|
||||||
|
templateUrl: './path-auto-complete.component.html',
|
||||||
|
styleUrls: ['./path-auto-complete.component.scss']
|
||||||
|
})
|
||||||
|
export class PathAutoCompleteComponent implements OnInit {
|
||||||
|
|
||||||
|
|
||||||
|
@Output() update = new EventEmitter<string>();
|
||||||
|
@Input() server: Server;
|
||||||
|
path: string[] = [];
|
||||||
|
values: string[] = [];
|
||||||
|
private completeData: IFormatedList[];
|
||||||
|
public mode: 'SELECT' | 'COMPLETE' | undefined;
|
||||||
|
completeField: string;
|
||||||
|
|
||||||
|
constructor(private apiInformationService: ApiInformationService) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePath(value: string) {
|
||||||
|
this.path.push(value);
|
||||||
|
this.update.emit(`/${this.path.join('/')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
popPath() {
|
||||||
|
this.path.pop();
|
||||||
|
this.update.emit(`/${this.path.join('/')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
getNext() {
|
||||||
|
this.apiInformationService
|
||||||
|
.getPathNextElement(this.path)
|
||||||
|
.subscribe((next: string[]) => {
|
||||||
|
this.values = next;
|
||||||
|
this.mode = 'SELECT';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removePrevious() {
|
||||||
|
if (this.mode) {
|
||||||
|
return this.mode = undefined;
|
||||||
|
}
|
||||||
|
if (this.path.length > 0) {
|
||||||
|
return this.popPath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
valueChanged(value: string) {
|
||||||
|
if (value.match(this.apiInformationService.bracketIdRegex)) {
|
||||||
|
this.apiInformationService.getListByObjectId(this.server, value)
|
||||||
|
.subscribe((data) => {
|
||||||
|
this.mode = 'COMPLETE';
|
||||||
|
this.completeData = data;
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.updatePath(value);
|
||||||
|
this.mode = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validComplete() {
|
||||||
|
this.updatePath(this.completeField);
|
||||||
|
this.mode = undefined;
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,13 @@
|
|||||||
<div class="content" *ngIf="isReady; else loading">
|
<div class="content" *ngIf="isReady; else loading">
|
||||||
<div class="default-header">
|
<div class="add">
|
||||||
<div class="row">
|
<app-add-permission-line
|
||||||
<button class="col" mat-raised-button color="primary" (click)="addPermission()" class="add-button" *ngIf="!newPermissionEdit" >
|
[server]="server"
|
||||||
Add Permission
|
(addPermissionEvent)="refresh()"></app-add-permission-line>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="permission-content default-content">
|
<div class="permission-content default-content">
|
||||||
<!--<ng-template #dynamic></ng-template>-->
|
<!--<ng-template #dynamic></ng-template>-->
|
||||||
<app-add-permission-line [server]="server" (addPermissionEvent)="updateList($event)" *ngIf="newPermissionEdit"></app-add-permission-line>
|
<app-add-permission-line [server]="server" (addPermissionEvent)="updateList($event)"
|
||||||
|
*ngIf="newPermissionEdit"></app-add-permission-line>
|
||||||
<div *ngFor="let permission of permissions">
|
<div *ngFor="let permission of permissions">
|
||||||
<app-permission-add-edit-line
|
<app-permission-add-edit-line
|
||||||
[permission]="permission"
|
[permission]="permission"
|
||||||
|
@ -18,3 +18,13 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
width: 175px;
|
width: 175px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.add {
|
||||||
|
/* display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-right: 20px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
align-items: center;*/
|
||||||
|
}
|
||||||
|
@ -29,7 +29,7 @@ export interface Permission {
|
|||||||
path: string;
|
path: string;
|
||||||
action: PermissionActions;
|
action: PermissionActions;
|
||||||
description: string;
|
description: string;
|
||||||
created_at: string;
|
created_at?: string;
|
||||||
updated_at: string;
|
updated_at?: string;
|
||||||
permission_id: string;
|
permission_id?: string;
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,10 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {HttpClient} from "@angular/common/http";
|
import {HttpClient} from "@angular/common/http";
|
||||||
import {Observable, ReplaySubject} from "rxjs";
|
import {Observable, ReplaySubject} from "rxjs";
|
||||||
import {map} from "rxjs/operators";
|
import {map, switchMap} from "rxjs/operators";
|
||||||
import {Methods} from "@models/api/permission";
|
import {Methods} from "@models/api/permission";
|
||||||
|
import {HttpServer} from "@services/http-server.service";
|
||||||
|
import {Server} from "@models/server";
|
||||||
|
|
||||||
export interface IPathDict {
|
export interface IPathDict {
|
||||||
methods: ('POST' | 'GET' | 'PUT' | 'DELETE' | 'HEAD' | 'PATH')[];
|
methods: ('POST' | 'GET' | 'PUT' | 'DELETE' | 'HEAD' | 'PATH')[];
|
||||||
@ -23,37 +25,93 @@ export interface IPathDict {
|
|||||||
subPaths: string[];
|
subPaths: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IApiObject {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IQueryObject {
|
||||||
|
id: string;
|
||||||
|
text: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFormatedList {
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class ApiInformationService {
|
export class ApiInformationService {
|
||||||
private data: ReplaySubject<IPathDict[]> = new ReplaySubject<IPathDict[]>(1);
|
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient) {
|
private allowed = ['projects', 'images', 'templates', 'computes', 'symbols', 'notifications'];
|
||||||
|
private data: ReplaySubject<IPathDict[]> = new ReplaySubject<IPathDict[]>(1);
|
||||||
|
private objs: ReplaySubject<IApiObject[]> = new ReplaySubject<IApiObject[]>(1);
|
||||||
|
public readonly bracketIdRegex = new RegExp("\{(.*?)\}");
|
||||||
|
public readonly finalBracketIdRegex = new RegExp("\{(.*?)\}$");
|
||||||
|
|
||||||
|
constructor(private httpClient: HttpClient,
|
||||||
|
private httpServer: HttpServer) {
|
||||||
this.loadLocalInformation();
|
this.loadLocalInformation();
|
||||||
|
|
||||||
this.data.subscribe((data) => {
|
this.data.subscribe((data) => {
|
||||||
localStorage.setItem('api-definition', JSON.stringify(data));
|
localStorage.setItem('api-definition', JSON.stringify(data));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.objs.subscribe((data) => {
|
||||||
|
localStorage.setItem('api-definition-objs', JSON.stringify(data));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
this.httpClient
|
this.httpClient
|
||||||
.get(`https://apiv3.gns3.net/openapi.json`)
|
.get(`https://apiv3.gns3.net/openapi.json`)
|
||||||
.subscribe((openapi: any) => {
|
.subscribe((openapi: any) => {
|
||||||
const data = this.apiModelAdapter(openapi);
|
const objs = this.apiObjectModelAdapter(openapi);
|
||||||
|
const data = this.apiPathModelAdapter(openapi);
|
||||||
this.data.next(data);
|
this.data.next(data);
|
||||||
|
this.objs.next(objs);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private apiModelAdapter(openapi: any): IPathDict[] {
|
private apiObjectModelAdapter(openapi: any): IApiObject[] {
|
||||||
|
|
||||||
|
function haveGetMethod(path: string): boolean {
|
||||||
|
const obj = openapi.paths[path];
|
||||||
|
if (obj) {
|
||||||
|
const methods = Object.keys(obj);
|
||||||
|
return methods.includes("get");
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractId(originalPath: string): IApiObject {
|
||||||
|
const d = originalPath.split('/');
|
||||||
|
|
||||||
|
const name = d.pop();
|
||||||
|
const path = d.join('/');
|
||||||
|
|
||||||
|
return {name, path};
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = Object.keys(openapi.paths);
|
||||||
|
return keys
|
||||||
|
.filter((path: string) => path.match(this.finalBracketIdRegex))
|
||||||
|
.filter(haveGetMethod)
|
||||||
|
.map(extractId)
|
||||||
|
.filter((object) => haveGetMethod(object.path));
|
||||||
|
}
|
||||||
|
|
||||||
|
private apiPathModelAdapter(openapi: any): IPathDict[] {
|
||||||
const keys = Object.keys(openapi.paths);
|
const keys = Object.keys(openapi.paths);
|
||||||
return keys
|
return keys
|
||||||
.map(path => {
|
.map(path => {
|
||||||
const subPaths = path.split('/').filter(elem => !(elem === '' || elem === 'v3'));
|
const subPaths = path.split('/').filter(elem => !(elem === '' || elem === 'v3'));
|
||||||
return {originalPath: path, path: subPaths.join('/'), subPaths};
|
return {originalPath: path, path: subPaths.join('/'), subPaths};
|
||||||
})
|
})
|
||||||
|
.filter(d => this.allowed.includes(d.subPaths[0]))
|
||||||
.map(path => {
|
.map(path => {
|
||||||
//FIXME
|
//FIXME
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -69,7 +127,6 @@ export class ApiInformationService {
|
|||||||
.pipe(
|
.pipe(
|
||||||
map((data: IPathDict[]) => {
|
map((data: IPathDict[]) => {
|
||||||
const availableMethods = new Set<string>();
|
const availableMethods = new Set<string>();
|
||||||
|
|
||||||
data.forEach((p: IPathDict) => {
|
data.forEach((p: IPathDict) => {
|
||||||
p.methods.forEach(method => availableMethods.add(method));
|
p.methods.forEach(method => availableMethods.add(method));
|
||||||
});
|
});
|
||||||
@ -83,24 +140,24 @@ export class ApiInformationService {
|
|||||||
.asObservable()
|
.asObservable()
|
||||||
.pipe(
|
.pipe(
|
||||||
map((data) => {
|
map((data) => {
|
||||||
|
const splinted = path
|
||||||
|
.split('/')
|
||||||
|
.filter(elem => !(elem === '' || elem === 'v3'));
|
||||||
|
|
||||||
const splinted = path.split('/').filter(elem => !(elem === '' || elem === 'v3'));
|
|
||||||
let remains = data;
|
let remains = data;
|
||||||
splinted.forEach((value, index) => {
|
splinted.forEach((value, index) => {
|
||||||
if (value === '*') {
|
if (value === '*') {
|
||||||
return remains;
|
return;
|
||||||
}
|
}
|
||||||
remains = remains.filter((val => {
|
let matchUrl = remains.filter(val => val.subPaths[index]?.includes(value));
|
||||||
if (!val.subPaths[index]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (val.subPaths[index].includes('{')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return val.subPaths[index] === value;
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
|
if (matchUrl.length === 0) {
|
||||||
|
matchUrl = remains.filter(val => val.subPaths[index]?.match(this.bracketIdRegex));
|
||||||
|
}
|
||||||
|
|
||||||
|
remains = matchUrl;
|
||||||
|
|
||||||
|
});
|
||||||
return remains;
|
return remains;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -111,5 +168,63 @@ export class ApiInformationService {
|
|||||||
if (data) {
|
if (data) {
|
||||||
this.data.next(data);
|
this.data.next(data);
|
||||||
}
|
}
|
||||||
|
const obj = JSON.parse(localStorage.getItem('api-definition-objs'));
|
||||||
|
if (obj) {
|
||||||
|
this.objs.next(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPathNextElement(path: string[]): Observable<string[]> {
|
||||||
|
|
||||||
|
return this.getPath(path.join('/'))
|
||||||
|
.pipe(map((paths: IPathDict[]) => {
|
||||||
|
const set = new Set<string>();
|
||||||
|
paths.forEach((p) => {
|
||||||
|
if (p.subPaths[path.length]) {
|
||||||
|
set.add(p.subPaths[path.length]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(set);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
getListByObjectId(server: Server, value: string) {
|
||||||
|
|
||||||
|
function findElement(data: IApiObject[]): IApiObject {
|
||||||
|
const elem = data.find(d => d.name === value);
|
||||||
|
if (!elem) {
|
||||||
|
throw new Error('entry not found');
|
||||||
|
}
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.objs.pipe(
|
||||||
|
map(findElement),
|
||||||
|
switchMap(elem => {
|
||||||
|
const url = `${server.protocol}//${server.host}:${server.port}${elem.path}`;
|
||||||
|
return this.httpClient.get<any[]>(url, {headers: {Authorization: `Bearer ${server.authToken}`}});
|
||||||
|
}
|
||||||
|
),
|
||||||
|
map(response => {
|
||||||
|
|
||||||
|
if (response.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = Object.keys(response[0]);
|
||||||
|
const idKey = keys.find(k => k.match(/_id$|filename/));
|
||||||
|
const nameKey = keys.find(k => k.match(/name/));
|
||||||
|
|
||||||
|
return response.map(o => {
|
||||||
|
return {
|
||||||
|
id: o[idKey],
|
||||||
|
name: o[nameKey]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user