mirror of
https://github.com/optim-enterprises-bv/siembol.git
synced 2025-10-30 18:07:47 +00:00
Config-editor-ui: add management view with links and actions (#494)
This commit is contained in:
2
config-editor/config-editor-ui/package-lock.json
generated
2
config-editor/config-editor-ui/package-lock.json
generated
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "rule-editor.ui",
|
"name": "rule-editor.ui",
|
||||||
"version": "2.2.4-dev",
|
"version": "2.2.5-dev",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
@@ -20,17 +20,17 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular-devkit/build-angular": "^13.1.3",
|
"@angular-devkit/build-angular": "^13.2.0",
|
||||||
"@angular/animations": "^13.1.2",
|
"@angular/animations": "^13.1.2",
|
||||||
"@angular/cdk": "^13.1.2",
|
"@angular/cdk": "^13.2.0",
|
||||||
"@angular/common": "^13.1.2",
|
"@angular/common": "^13.2.0",
|
||||||
"@angular/compiler": "^13.1.2",
|
"@angular/compiler": "^13.2.0",
|
||||||
"@angular/core": "^13.1.2",
|
"@angular/core": "^13.2.0",
|
||||||
"@angular/forms": "^13.1.2",
|
"@angular/forms": "^13.2.0",
|
||||||
"@angular/material": "^13.1.2",
|
"@angular/material": "^13.2.0",
|
||||||
"@angular/platform-browser": "^13.1.2",
|
"@angular/platform-browser": "^13.2.0",
|
||||||
"@angular/platform-browser-dynamic": "^13.1.2",
|
"@angular/platform-browser-dynamic": "^13.2.0",
|
||||||
"@angular/router": "^13.1.2",
|
"@angular/router": "^13.2.0",
|
||||||
"@ngx-formly/core": "^6.0.0-next.6",
|
"@ngx-formly/core": "^6.0.0-next.6",
|
||||||
"@ngx-formly/material": "^6.0.0-next.6",
|
"@ngx-formly/material": "^6.0.0-next.6",
|
||||||
"@types/json-schema": "^7.0.8",
|
"@types/json-schema": "^7.0.8",
|
||||||
@@ -57,23 +57,23 @@
|
|||||||
"@angular-eslint/eslint-plugin": "^13.0.1",
|
"@angular-eslint/eslint-plugin": "^13.0.1",
|
||||||
"@angular-eslint/eslint-plugin-template": "^13.0.1",
|
"@angular-eslint/eslint-plugin-template": "^13.0.1",
|
||||||
"@angular-eslint/template-parser": "^13.0.1",
|
"@angular-eslint/template-parser": "^13.0.1",
|
||||||
"@angular/cli": "^13.1.3",
|
"@angular/cli": "^13.2.0",
|
||||||
"@angular/compiler-cli": "^13.1.2",
|
"@angular/compiler-cli": "^13.2.0",
|
||||||
"@angular/language-service": "^13.1.2",
|
"@angular/language-service": "^13.2.0",
|
||||||
"@types/jasmine": "^3.10.3",
|
"@types/jasmine": "^3.10.3",
|
||||||
"@types/node": "^17.0.8",
|
"@types/node": "^17.0.12",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.9.1",
|
"@typescript-eslint/eslint-plugin": "^5.10.1",
|
||||||
"@typescript-eslint/parser": "^5.9.1",
|
"@typescript-eslint/parser": "^5.10.1",
|
||||||
"eslint-config-prettier": "^8.1.0",
|
"eslint-config-prettier": "^8.1.0",
|
||||||
"eslint-plugin-import": "^2.25.4",
|
"eslint-plugin-import": "^2.25.4",
|
||||||
"eslint-plugin-jsdoc": "^37.6.1",
|
"eslint-plugin-jsdoc": "^37.7.0",
|
||||||
"eslint-plugin-prefer-arrow": "^1.2.3",
|
"eslint-plugin-prefer-arrow": "^1.2.3",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"eslint-plugin-sort-keys-fix": "^1.1.2",
|
"eslint-plugin-sort-keys-fix": "^1.1.2",
|
||||||
"jasmine": "^4.0.2",
|
"jasmine": "^4.0.2",
|
||||||
"jasmine-core": "^4.0.0",
|
"jasmine-core": "^4.0.0",
|
||||||
"jasmine-spec-reporter": "^7.0.0",
|
"jasmine-spec-reporter": "^7.0.0",
|
||||||
"karma": "^6.3.11",
|
"karma": "^6.3.12",
|
||||||
"karma-chrome-launcher": "^3.1.0",
|
"karma-chrome-launcher": "^3.1.0",
|
||||||
"karma-cli": "^2.0.0",
|
"karma-cli": "^2.0.0",
|
||||||
"karma-coverage-istanbul-reporter": "^3.0.3",
|
"karma-coverage-istanbul-reporter": "^3.0.3",
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"ts-node": "^10.4.0",
|
"ts-node": "^10.4.0",
|
||||||
"typescript": "~4.5.4",
|
"typescript": "~4.5.4",
|
||||||
"webpack": "^4.46.0"
|
"webpack": "^5.67.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {}
|
"peerDependencies": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ import { ConfigTestingComponent } from './components/testing/config-testing/conf
|
|||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { EditorViewComponent } from './components/editor-view/editor-view.component';
|
import { EditorViewComponent } from './components/editor-view/editor-view.component';
|
||||||
import { HomeViewComponent } from './components/home-view/home-view.component';
|
import { HomeViewComponent } from './components/home-view/home-view.component';
|
||||||
|
import { ManagementViewComponent } from './components/management-view/management-view.component';
|
||||||
import { AdminViewComponent } from './components/admin-view/admin-view.component';
|
import { AdminViewComponent } from './components/admin-view/admin-view.component';
|
||||||
import { AppInitGuard, AuthGuard } from './guards';
|
import { AppInitGuard, AuthGuard } from './guards';
|
||||||
import { AppService } from './services/app.service';
|
import { AppService } from './services/app.service';
|
||||||
@@ -102,6 +103,7 @@ const DEV_PROVIDERS = [...PROD_PROVIDERS];
|
|||||||
ErrorDialogComponent,
|
ErrorDialogComponent,
|
||||||
EditorViewComponent,
|
EditorViewComponent,
|
||||||
HomeViewComponent,
|
HomeViewComponent,
|
||||||
|
ManagementViewComponent,
|
||||||
AdminViewComponent,
|
AdminViewComponent,
|
||||||
NavBarComponent,
|
NavBarComponent,
|
||||||
SideNavComponent,
|
SideNavComponent,
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
<mat-card>
|
<mat-card>
|
||||||
<mat-card-title>
|
<mat-card-title>
|
||||||
<div class="rule-title">{{serviceName | titlecase}} Admin Config</div>
|
<div class="rule-title">{{serviceName | titlecase}} Admin Config</div>
|
||||||
<button
|
|
||||||
class="button"
|
|
||||||
mat-raised-button color="accent"
|
|
||||||
(click)="openApplicationDialog()"
|
|
||||||
>
|
|
||||||
Manage Applications
|
|
||||||
</button>
|
|
||||||
</mat-card-title>
|
</mat-card-title>
|
||||||
<mat-card-subtitle>
|
<mat-card-subtitle>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { Router } from '@angular/router';
|
|||||||
import { SubmitDialogComponent } from '../submit-dialog/submit-dialog.component';
|
import { SubmitDialogComponent } from '../submit-dialog/submit-dialog.component';
|
||||||
import { BlockUI, NgBlockUI } from 'ng-block-ui';
|
import { BlockUI, NgBlockUI } from 'ng-block-ui';
|
||||||
import { AppConfigService } from '@app/services/app-config.service';
|
import { AppConfigService } from '@app/services/app-config.service';
|
||||||
import { ApplicationDialogComponent } from '..';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
@@ -108,10 +107,6 @@ export class AdminComponent implements OnInit, OnDestroy {
|
|||||||
this.form.updateValueAndValidity();
|
this.form.updateValueAndValidity();
|
||||||
}
|
}
|
||||||
|
|
||||||
openApplicationDialog() {
|
|
||||||
this.dialog.open(ApplicationDialogComponent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateAndWrapConfig(config: AdminConfig) {
|
private updateAndWrapConfig(config: AdminConfig) {
|
||||||
//NOTE: in the form we are using wrapping config to handle optionals, unions
|
//NOTE: in the form we are using wrapping config to handle optionals, unions
|
||||||
const configData = this.editorService.adminSchema.wrapConfig(config.configData);
|
const configData = this.editorService.adminSchema.wrapConfig(config.configData);
|
||||||
|
|||||||
@@ -6,16 +6,15 @@ import { HomeComponent, PageNotFoundComponent } from '../../containers';
|
|||||||
|
|
||||||
import { TestCaseHelpComponent } from '../testing/test-case-help/test-case-help.component';
|
import { TestCaseHelpComponent } from '../testing/test-case-help/test-case-help.component';
|
||||||
import { AppService } from '../../services/app.service';
|
import { AppService } from '../../services/app.service';
|
||||||
import { EditorServiceGuard } from '@app/guards';
|
import { AdminGuard, AuthGuard, ConfigEditGuard, EditorServiceGuard } from '@app/guards';
|
||||||
import { ConfigEditGuard } from '@app/guards';
|
|
||||||
import { AppConfigService } from '@app/services/app-config.service';
|
import { AppConfigService } from '@app/services/app-config.service';
|
||||||
import { EditorViewComponent } from '../editor-view/editor-view.component';
|
import { EditorViewComponent } from '../editor-view/editor-view.component';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
import { AuthGuard } from '@app/guards';
|
|
||||||
import { AdminViewComponent } from '../admin-view/admin-view.component';
|
import { AdminViewComponent } from '../admin-view/admin-view.component';
|
||||||
import { AdminGuard } from '@app/guards';
|
|
||||||
import { UserRole } from '@app/model/config-model';
|
import { UserRole } from '@app/model/config-model';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
|
import { HomeViewComponent } from '../home-view/home-view.component';
|
||||||
|
import { ManagementViewComponent } from '../management-view/management-view.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: '',
|
template: '',
|
||||||
@@ -56,16 +55,20 @@ export class AppInitComponent implements OnInit, OnDestroy {
|
|||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
redirectTo: 'home',
|
redirectTo: 'home',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'home',
|
|
||||||
component: LandingPageComponent,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
component: TestCaseHelpComponent,
|
component: TestCaseHelpComponent,
|
||||||
path: 'help/testcase',
|
path: 'help/testcase',
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private homeRoute: Route = {
|
||||||
|
path: 'home',
|
||||||
|
component: LandingPageComponent,
|
||||||
|
children: [
|
||||||
|
{ path: '', component: HomeViewComponent },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
constructor(private router: Router,
|
constructor(private router: Router,
|
||||||
private appService: AppService,
|
private appService: AppService,
|
||||||
private config: AppConfigService) { }
|
private config: AppConfigService) { }
|
||||||
@@ -81,10 +84,14 @@ export class AppInitComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.ngUnsubscribe.complete();
|
||||||
|
}
|
||||||
|
|
||||||
private loadRoutes() {
|
private loadRoutes() {
|
||||||
const routes = this.appRoutes;
|
const routes = this.appRoutes;
|
||||||
this.appService.serviceNames.forEach(s => {
|
this.appService.serviceNames.forEach(s => {
|
||||||
let userRoles = this.appService.getUserServiceRoles(s);
|
const userRoles = this.appService.getUserServiceRoles(s);
|
||||||
let childrenRoutes = [];
|
let childrenRoutes = [];
|
||||||
if (userRoles.includes(UserRole.SERVICE_USER)) {
|
if (userRoles.includes(UserRole.SERVICE_USER)) {
|
||||||
childrenRoutes = cloneDeep(this.configRoutes);
|
childrenRoutes = cloneDeep(this.configRoutes);
|
||||||
@@ -97,14 +104,14 @@ export class AppInitComponent implements OnInit, OnDestroy {
|
|||||||
})
|
})
|
||||||
|
|
||||||
});
|
});
|
||||||
|
if (this.appService.isAdminOfAnyService) {
|
||||||
|
this.homeRoute.children.push({ path: 'management', component: ManagementViewComponent })
|
||||||
|
}
|
||||||
|
routes.push(this.homeRoute);
|
||||||
routes.push({
|
routes.push({
|
||||||
component: PageNotFoundComponent,
|
component: PageNotFoundComponent,
|
||||||
path: '**'
|
path: '**'
|
||||||
});
|
});
|
||||||
this.router.resetConfig(routes);
|
this.router.resetConfig(routes);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
|
||||||
this.ngUnsubscribe.complete();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
<ng-container matColumnDef="restart">
|
<ng-container matColumnDef="restart">
|
||||||
<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 color="primary" [disabled]="restartedApplications.includes(row.topology_name)" (click)="onRestartApplication(row.topology_name, restartedApplication)">Restart</button>
|
<button mat-raised-button color="primary" [disabled]="restartedApplications.includes(row.topology_name) || disableAllRestart === true" (click)="onRestartApplication(row.service_name, row.topology_name, restartedApplication)">Restart</button>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div mat-dialog-actions>
|
<div mat-dialog-actions>
|
||||||
|
<button mat-raised-button class="button-layout" color="accent" [disabled]="disableAllRestart" (click)="openInfoDialog('', confirmRestartAll)">RESTART ALL</button>
|
||||||
<button mat-raised-button class="button-layout" color="accent" (click)="onClickClose()">CLOSE</button>
|
<button mat-raised-button class="button-layout" color="accent" (click)="onClickClose()">CLOSE</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -53,13 +54,21 @@
|
|||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
</mat-accordion>
|
</mat-accordion>
|
||||||
<div mat-dialog-actions>
|
<div mat-dialog-actions>
|
||||||
<button mat-raised-button class="button-layout" color="mat-color($primary)" (click)="onClickCloseAttributes()">CLOSE</button>
|
<button mat-raised-button class="button-layout" color="mat-color($primary)" (click)="onClickCloseInfo()">CLOSE</button>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #restartedApplication let-data>
|
<ng-template #restartedApplication let-data>
|
||||||
State of <b class="bold">{{data}}</b> has been changed, please <b class="bold">wait a few minutes</b> and check storm UI if the new state has been released
|
State of <b class="bold">{{data}}</b> has been changed, please <b class="bold">wait a few minutes</b> and check storm UI if the new state has been released
|
||||||
<div mat-dialog-actions>
|
<div mat-dialog-actions>
|
||||||
<button mat-raised-button class="button-layout" color="accent" (click)="onClickCloseAttributes()">OK</button>
|
<button mat-raised-button class="button-layout" color="accent" (click)="onClickCloseInfo()">OK</button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #confirmRestartAll let-data>
|
||||||
|
Are you sure you want to restart all applications?
|
||||||
|
<div mat-dialog-actions>
|
||||||
|
<button mat-raised-button class="button-layout" color="accent" (click)="restartAllApplications(restartedApplication)">RESTART ALL</button>
|
||||||
|
<button mat-raised-button class="button-layout" color="accent" (click)="onClickCloseInfo()">CANCEL</button>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, TemplateRef } fr
|
|||||||
import { MatDialog, MatDialogRef } from "@angular/material/dialog";
|
import { MatDialog, MatDialogRef } from "@angular/material/dialog";
|
||||||
import { MatTableDataSource } from "@angular/material/table";
|
import { MatTableDataSource } from "@angular/material/table";
|
||||||
import { Application, applicationManagerColumns, displayedApplicationManagerColumns } from "@app/model/config-model";
|
import { Application, applicationManagerColumns, displayedApplicationManagerColumns } from "@app/model/config-model";
|
||||||
import { EditorService } from "@app/services/editor.service";
|
import { AppService } from "@app/services/app.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
@@ -11,19 +11,21 @@ import { EditorService } from "@app/services/editor.service";
|
|||||||
templateUrl: 'application-dialog.component.html',
|
templateUrl: 'application-dialog.component.html',
|
||||||
})
|
})
|
||||||
export class ApplicationDialogComponent {
|
export class ApplicationDialogComponent {
|
||||||
dialogrefAttributes: MatDialogRef<any>;
|
MAX_DIALOG_WIDTH = '800px';
|
||||||
|
dialogrefInfo: MatDialogRef<any>;
|
||||||
dataSource: MatTableDataSource<Application>;
|
dataSource: MatTableDataSource<Application>;
|
||||||
columns = applicationManagerColumns;
|
columns = applicationManagerColumns;
|
||||||
displayedColumns = displayedApplicationManagerColumns;
|
displayedColumns = displayedApplicationManagerColumns;
|
||||||
restartedApplications: string[] = [];
|
restartedApplications: string[] = [];
|
||||||
|
disableAllRestart = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private dialogref: MatDialogRef<ApplicationDialogComponent>,
|
private dialogref: MatDialogRef<ApplicationDialogComponent>,
|
||||||
private service: EditorService,
|
private service: AppService,
|
||||||
private dialog: MatDialog,
|
private dialog: MatDialog,
|
||||||
private cd: ChangeDetectorRef
|
private cd: ChangeDetectorRef
|
||||||
) {
|
) {
|
||||||
this.service.configLoader.getApplications().subscribe(a => {
|
this.service.getAllApplications().subscribe(a => {
|
||||||
this.createTable(a);
|
this.createTable(a);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -32,30 +34,20 @@ export class ApplicationDialogComponent {
|
|||||||
this.dialogref.close();
|
this.dialogref.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
onRestartApplication(applicationName: string, templateRef: TemplateRef<any>) {
|
onRestartApplication(serviceName: string, applicationName: string, templateRef: TemplateRef<any>) {
|
||||||
this.service.configLoader.restartApplication(applicationName).subscribe(a => {
|
this.service.restartApplication(serviceName, applicationName).subscribe(a => {
|
||||||
this.createTable(a);
|
this.createTable(a);
|
||||||
this.restartedApplications.push(applicationName);
|
this.restartedApplications.push(applicationName);
|
||||||
})
|
});
|
||||||
this.dialogrefAttributes = this.dialog.open(
|
this.openInfoDialog(applicationName, templateRef);
|
||||||
templateRef,
|
|
||||||
{
|
|
||||||
data: applicationName,
|
|
||||||
maxWidth: '800px',
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewAttributes(attributes: string[], templateRef: TemplateRef<any>) {
|
onViewAttributes(attributes: string[], templateRef: TemplateRef<any>) {
|
||||||
this.dialogrefAttributes = this.dialog.open(
|
this.openInfoDialog(attributes.map(a => JSON.parse(atob(a))), templateRef);
|
||||||
templateRef,
|
|
||||||
{
|
|
||||||
data: attributes.map(a => JSON.parse(atob(a))),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickCloseAttributes() {
|
onClickCloseInfo() {
|
||||||
this.dialogrefAttributes.close();
|
this.dialogrefInfo.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
applyFilter(event: Event) {
|
applyFilter(event: Event) {
|
||||||
@@ -63,6 +55,25 @@ export class ApplicationDialogComponent {
|
|||||||
this.dataSource.filter = filterValue.trim().toLowerCase();
|
this.dataSource.filter = filterValue.trim().toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
restartAllApplications(templateRef: TemplateRef<any>) {
|
||||||
|
this.service.restartAllApplications().subscribe((apps: Application[]) => {
|
||||||
|
this.createTable(apps);
|
||||||
|
this.onClickCloseInfo();
|
||||||
|
this.disableAllRestart = true;
|
||||||
|
this.cd.markForCheck();
|
||||||
|
this.openInfoDialog("all applications", templateRef);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
openInfoDialog(data: any, templateRef: TemplateRef<any>) {
|
||||||
|
this.dialogrefInfo = this.dialog.open(
|
||||||
|
templateRef,
|
||||||
|
{
|
||||||
|
data,
|
||||||
|
maxWidth: this.MAX_DIALOG_WIDTH,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private createTable(a: Application[]) {
|
private createTable(a: Application[]) {
|
||||||
const filter = this.dataSource?.filter;
|
const filter = this.dataSource?.filter;
|
||||||
this.dataSource = new MatTableDataSource(a);
|
this.dataSource = new MatTableDataSource(a);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<mat-expansion-panel-header>Explore Siembol</mat-expansion-panel-header>
|
<mat-expansion-panel-header>Explore Siembol</mat-expansion-panel-header>
|
||||||
<mat-grid-list cols="4" rowHeight="2:1">
|
<mat-grid-list cols="4" rowHeight="2:1">
|
||||||
<mat-grid-tile *ngFor="let link of homeHelpLinks">
|
<mat-grid-tile *ngFor="let link of homeHelpLinks">
|
||||||
<button class="with-icon" mat-button (click)="openLink(link.link)">
|
<button [matTooltip]="link.link" class="with-icon" mat-button (click)="openLink(link.link)">
|
||||||
<mat-icon>{{link.icon}}</mat-icon>
|
<mat-icon>{{link.icon}}</mat-icon>
|
||||||
{{link.title}}
|
{{link.title}}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
|||||||
import { UrlHistoryService } from '@app/services/url-history.service';
|
import { UrlHistoryService } from '@app/services/url-history.service';
|
||||||
import { AppConfigService } from '@app/services/app-config.service';
|
import { AppConfigService } from '@app/services/app-config.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { HomeHelpLink } from '@app/model/app-config';
|
import { HelpLink } from '@app/model/app-config';
|
||||||
import { ServiceInfo } from '@app/model/config-model';
|
import { ServiceInfo } from '@app/model/config-model';
|
||||||
import { AppService } from '@app/services/app.service';
|
import { AppService } from '@app/services/app.service';
|
||||||
import { parseUrl } from '@app/commons/helper-functions'
|
import { parseUrl } from '@app/commons/helper-functions'
|
||||||
@@ -16,7 +16,7 @@ import { parseUrl } from '@app/commons/helper-functions'
|
|||||||
export class HomeViewComponent implements OnInit {
|
export class HomeViewComponent implements OnInit {
|
||||||
history: string[];
|
history: string[];
|
||||||
root: string;
|
root: string;
|
||||||
homeHelpLinks: HomeHelpLink[];
|
homeHelpLinks: HelpLink[];
|
||||||
userServices: ServiceInfo[];
|
userServices: ServiceInfo[];
|
||||||
|
|
||||||
constructor(private historyService: UrlHistoryService,
|
constructor(private historyService: UrlHistoryService,
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<div class="router-holder" style="height:100vh">
|
||||||
|
<mat-expansion-panel [expanded]="true">
|
||||||
|
<mat-expansion-panel-header>Management Links</mat-expansion-panel-header>
|
||||||
|
<mat-grid-list cols="4" rowHeight="2:1">
|
||||||
|
<mat-grid-tile *ngFor="let link of managementLinks">
|
||||||
|
<button [matTooltip]="link.link" class="with-icon" mat-button (click)="openLink(link.link)">
|
||||||
|
<mat-icon>{{link.icon}}</mat-icon>
|
||||||
|
{{link.title}}
|
||||||
|
</button>
|
||||||
|
</mat-grid-tile>
|
||||||
|
</mat-grid-list>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
<mat-expansion-panel [expanded]="true">
|
||||||
|
<mat-expansion-panel-header>Actions</mat-expansion-panel-header>
|
||||||
|
<mat-grid-list cols="2" rowHeight="2:1">
|
||||||
|
<mat-grid-tile>
|
||||||
|
<button mat-button (click)="openApplicationDialog()">
|
||||||
|
<mat-icon>apps</mat-icon> Manage Appplications
|
||||||
|
</button>
|
||||||
|
</mat-grid-tile>
|
||||||
|
</mat-grid-list>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
.mat-expansion-panel {
|
||||||
|
margin-top: 30px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
max-width: 70vw;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .with-icon .mat-button-wrapper {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
align-items: center !important;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, OnInit } from "@angular/core";
|
||||||
|
import { MatDialog, MatDialogRef } from "@angular/material/dialog";
|
||||||
|
import { HelpLink } from "@app/model/app-config";
|
||||||
|
import { AppConfigService } from "@app/services/app-config.service";
|
||||||
|
import { ApplicationDialogComponent } from "..";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
selector: 're-management-view',
|
||||||
|
styleUrls: ['./management-view.component.scss'],
|
||||||
|
templateUrl: './management-view.component.html',
|
||||||
|
})
|
||||||
|
export class ManagementViewComponent implements OnInit {
|
||||||
|
managementLinks: HelpLink[];
|
||||||
|
dialogref: MatDialogRef<any>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private appConfigService: AppConfigService,
|
||||||
|
private dialog: MatDialog
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.managementLinks = this.appConfigService.managementLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
openLink(link: string) {
|
||||||
|
window.open(link, "_blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
openApplicationDialog() {
|
||||||
|
this.dialog.open(ApplicationDialogComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,9 @@
|
|||||||
</button>
|
</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
</div>
|
</div>
|
||||||
|
<div *ngIf="isManagement">
|
||||||
|
<h1>Management</h1>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div *ngIf="!isHome">
|
<div *ngIf="!isHome">
|
||||||
@@ -67,6 +70,15 @@
|
|||||||
{{repoNames.admin_config_store_directory_name}}
|
{{repoNames.admin_config_store_directory_name}}
|
||||||
</a>
|
</a>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
<button
|
||||||
|
*ngIf="isAdminOfAnyService"
|
||||||
|
mat-icon-button
|
||||||
|
class="right-element"
|
||||||
|
[routerLink]="['/home/management']"
|
||||||
|
[matTooltip]="'Management'"
|
||||||
|
>
|
||||||
|
<mat-icon>settings_application</mat-icon>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
class="right-element"
|
class="right-element"
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit} from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit} from '@angular/core';
|
||||||
import { AppConfigService } from '@app/services/app-config.service';
|
import { AppConfigService } from '@app/services/app-config.service';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable, Subject } from 'rxjs';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { BuildInfoDialogComponent } from '../build-info-dialog/build-info-dialog.component';
|
import { BuildInfoDialogComponent } from '../build-info-dialog/build-info-dialog.component';
|
||||||
import { EditorService } from '../../services/editor.service';
|
import { EditorService } from '../../services/editor.service';
|
||||||
import { AppService } from '../../services/app.service';
|
import { AppService } from '../../services/app.service';
|
||||||
import { Router, ActivatedRoute } from '@angular/router';
|
import { Router, NavigationEnd } from '@angular/router';
|
||||||
import { UserRole, RepositoryLinks, repoNames } from '@app/model/config-model';
|
import { UserRole, RepositoryLinks, repoNames } from '@app/model/config-model';
|
||||||
|
import { startWith, takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
@@ -14,7 +15,8 @@ import { UserRole, RepositoryLinks, repoNames } from '@app/model/config-model';
|
|||||||
styleUrls: ['./nav-bar.component.scss'],
|
styleUrls: ['./nav-bar.component.scss'],
|
||||||
templateUrl: './nav-bar.component.html',
|
templateUrl: './nav-bar.component.html',
|
||||||
})
|
})
|
||||||
export class NavBarComponent implements OnInit {
|
export class NavBarComponent implements OnInit, OnDestroy {
|
||||||
|
ngUnsubscribe = new Subject();
|
||||||
user: string;
|
user: string;
|
||||||
userRoles: string[];
|
userRoles: string[];
|
||||||
serviceName$: Observable<string>;
|
serviceName$: Observable<string>;
|
||||||
@@ -23,14 +25,15 @@ export class NavBarComponent implements OnInit {
|
|||||||
environment: string;
|
environment: string;
|
||||||
isAdminChecked: boolean;
|
isAdminChecked: boolean;
|
||||||
isHome: boolean;
|
isHome: boolean;
|
||||||
|
isManagement: boolean;
|
||||||
repositoryLinks: RepositoryLinks;
|
repositoryLinks: RepositoryLinks;
|
||||||
|
isAdminOfAnyService: boolean;
|
||||||
readonly repoNames = repoNames;
|
readonly repoNames = repoNames;
|
||||||
|
|
||||||
constructor(private config: AppConfigService,
|
constructor(private config: AppConfigService,
|
||||||
private appService: AppService,
|
private appService: AppService,
|
||||||
private editorService: EditorService,
|
private editorService: EditorService,
|
||||||
private dialog: MatDialog,
|
private dialog: MatDialog,
|
||||||
private activeRoute: ActivatedRoute,
|
|
||||||
private router: Router) {
|
private router: Router) {
|
||||||
this.user = this.appService.user;
|
this.user = this.appService.user;
|
||||||
this.serviceName$ = this.editorService.serviceName$;
|
this.serviceName$ = this.editorService.serviceName$;
|
||||||
@@ -40,7 +43,8 @@ export class NavBarComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.serviceName$.subscribe(service => {
|
this.isAdminOfAnyService = this.appService.isAdminOfAnyService;
|
||||||
|
this.serviceName$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(service => {
|
||||||
if (service) {
|
if (service) {
|
||||||
this.userRoles = this.appService.getUserServiceRoles(service);
|
this.userRoles = this.appService.getUserServiceRoles(service);
|
||||||
this.repositoryLinks = this.appService.getServiceRepositoryLink(service);
|
this.repositoryLinks = this.appService.getServiceRepositoryLink(service);
|
||||||
@@ -48,8 +52,15 @@ export class NavBarComponent implements OnInit {
|
|||||||
this.serviceName = service;
|
this.serviceName = service;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.activeRoute.url.subscribe(url => {
|
this.router.events
|
||||||
this.isHome = this.config.isHomePath('/' + url[0].path);
|
.pipe(
|
||||||
|
startWith(this.router),
|
||||||
|
takeUntil(this.ngUnsubscribe))
|
||||||
|
.subscribe(event => {
|
||||||
|
if (event instanceof NavigationEnd || event instanceof Router){
|
||||||
|
this.isHome = this.config.isHomePath(event.url);
|
||||||
|
this.isManagement = this.config.isManagementPath(event.url);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,4 +82,9 @@ export class NavBarComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.ngUnsubscribe.next();
|
||||||
|
this.ngUnsubscribe.complete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</mat-sidenav>
|
</mat-sidenav>
|
||||||
<mat-sidenav-content>
|
<mat-sidenav-content>
|
||||||
<re-home-view></re-home-view>
|
<div class="router-holder">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
</mat-sidenav-content>
|
</mat-sidenav-content>
|
||||||
</mat-sidenav-container>
|
</mat-sidenav-container>
|
||||||
@@ -19,13 +19,14 @@ export interface AppConfig {
|
|||||||
aboutApp: BuildInfo;
|
aboutApp: BuildInfo;
|
||||||
authType: AuthenticationType;
|
authType: AuthenticationType;
|
||||||
authAttributes: Oauth2Attributes | any;
|
authAttributes: Oauth2Attributes | any;
|
||||||
homeHelpLinks?: HomeHelpLink[];
|
homeHelpLinks?: HelpLink[];
|
||||||
|
managementLinks?: HelpLink[];
|
||||||
historyMaxSize?: number;
|
historyMaxSize?: number;
|
||||||
blockingTimeout?: number;
|
blockingTimeout?: number;
|
||||||
useImporters?: boolean;
|
useImporters?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HomeHelpLink {
|
export interface HelpLink {
|
||||||
title: string;
|
title: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
link: string;
|
link: string;
|
||||||
@@ -36,3 +37,5 @@ export interface Oauth2Attributes {
|
|||||||
expiresIntervalMinimum: number;
|
expiresIntervalMinimum: number;
|
||||||
oidcSettings: UserSettings;
|
oidcSettings: UserSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const HOME_REGEX = new RegExp('^\/($|home(\/|$))');
|
||||||
|
|||||||
@@ -208,8 +208,13 @@ export interface Application {
|
|||||||
|
|
||||||
export const applicationManagerColumns = [
|
export const applicationManagerColumns = [
|
||||||
{
|
{
|
||||||
columnDef: 'name',
|
columnDef: 'service_name',
|
||||||
header: 'Name',
|
header: 'Service Name',
|
||||||
|
cell: (t: Application) => `${t.service_name}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
columnDef: 'application_name',
|
||||||
|
header: 'Application Name',
|
||||||
cell: (t: Application) => `${t.topology_name}`,
|
cell: (t: Application) => `${t.topology_name}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -224,4 +229,4 @@ export const applicationManagerColumns = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const displayedApplicationManagerColumns = ["name", "id", "image", "attributes", "restart"];
|
export const displayedApplicationManagerColumns = ["service_name", "application_name", "id", "image", "attributes", "restart"];
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { HttpClientTestingModule } from "@angular/common/http/testing";
|
||||||
|
import { TestBed } from "@angular/core/testing";
|
||||||
|
import { AppConfigService } from "./app-config.service";
|
||||||
|
|
||||||
|
describe('AppConfigService', () => {
|
||||||
|
let service: AppConfigService;
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [HttpClientTestingModule],
|
||||||
|
providers: [AppConfigService],
|
||||||
|
});
|
||||||
|
service = TestBed.inject(AppConfigService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isHomePath', () => {
|
||||||
|
it('should be home path', () => {
|
||||||
|
expect(service.isHomePath('/')).toBeTrue();
|
||||||
|
expect(service.isHomePath('/home')).toBeTrue();
|
||||||
|
expect(service.isHomePath('/home/management')).toBeTrue();
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not be home path', () => {
|
||||||
|
expect(service.isHomePath('/test')).toBeFalse();
|
||||||
|
expect(service.isHomePath('/homee')).toBeFalse();
|
||||||
|
expect(service.isHomePath('/test/management')).toBeFalse();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
} from './authentication.service';
|
} from './authentication.service';
|
||||||
import { Oauth2AuthenticationService } from '@app/services/oauth2-authentication.service';
|
import { Oauth2AuthenticationService } from '@app/services/oauth2-authentication.service';
|
||||||
import { AppConfig, AuthenticationType, BuildInfo } from '../model';
|
import { AppConfig, AuthenticationType, BuildInfo } from '../model';
|
||||||
import { HomeHelpLink } from '@app/model/app-config';
|
import { HelpLink, HOME_REGEX } from '@app/model/app-config';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@@ -44,7 +44,14 @@ export class AppConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isHomePath(path: string): boolean {
|
isHomePath(path: string): boolean {
|
||||||
if (path === '/home' || path === '/') {
|
if (HOME_REGEX.test(path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isManagementPath(path: string): boolean {
|
||||||
|
if (path === '/home/management') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -90,10 +97,14 @@ export class AppConfigService {
|
|||||||
return this._authenticationService;
|
return this._authenticationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
get homeHelpLinks(): HomeHelpLink[] {
|
get homeHelpLinks(): HelpLink[] {
|
||||||
return this._config.homeHelpLinks;
|
return this._config.homeHelpLinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get managementLinks(): HelpLink[] {
|
||||||
|
return this._config.managementLinks;
|
||||||
|
}
|
||||||
|
|
||||||
get historyMaxSize(): number {
|
get historyMaxSize(): number {
|
||||||
return this._config.historyMaxSize ? this._config.historyMaxSize : 5;
|
return this._config.historyMaxSize ? this._config.historyMaxSize : 5;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { TestBed } from '@angular/core/testing';
|
|||||||
import { AppConfigService } from '@app/services/app-config.service';
|
import { AppConfigService } from '@app/services/app-config.service';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||||
import { mockAppContext, mockUserServicesMap, mockAppContextWithTestSchema } from 'testing/appContext';
|
import { mockAppContext, mockAppContextNoAdmin, mockUserServicesMap } from 'testing/appContext';
|
||||||
import { mockUserInfo } from 'testing/user';
|
import { mockUserInfo } from 'testing/user';
|
||||||
import { mockUiMetadataMap } from 'testing/uiMetadataMap';
|
import { mockUiMetadataMap } from 'testing/uiMetadataMap';
|
||||||
import { RepositoryLinks, UserRole } from '@app/model/config-model';
|
import { RepositoryLinks, UserRole } from '@app/model/config-model';
|
||||||
@@ -10,6 +10,46 @@ import { mockTestCasesSchema } from 'testing/testCasesSchema';
|
|||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
|
|
||||||
|
const mockTopology1 =
|
||||||
|
{
|
||||||
|
image: "test-image-alert",
|
||||||
|
attributes: ["test"],
|
||||||
|
topology_name: "myalert1",
|
||||||
|
topology_id: "123",
|
||||||
|
service_name: "myalert",
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockTopology2 =
|
||||||
|
{
|
||||||
|
image: "test-image-parsers",
|
||||||
|
attributes: ["test"],
|
||||||
|
topology_name: "myparsingapp1",
|
||||||
|
topology_id: "456",
|
||||||
|
service_name: "myparsingapp",
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockTopology3 =
|
||||||
|
{
|
||||||
|
image: "test-image-parsers",
|
||||||
|
attributes: ["test"],
|
||||||
|
topology_name: "myparsingapp2",
|
||||||
|
topology_id: "789",
|
||||||
|
service_name: "myparsingapp",
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockTopologies1 = {
|
||||||
|
topologies: [
|
||||||
|
mockTopology1,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockTopologies2 = {
|
||||||
|
topologies: [
|
||||||
|
mockTopology2,
|
||||||
|
mockTopology3,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
const mockRepositories1 = {
|
const mockRepositories1 = {
|
||||||
"rules_repositories": {
|
"rules_repositories": {
|
||||||
"rule_store_url": "https://github.com/test-siembol-config.git",
|
"rule_store_url": "https://github.com/test-siembol-config.git",
|
||||||
@@ -79,9 +119,9 @@ describe('AppService', () => {
|
|||||||
it('should create app context', done => {
|
it('should create app context', done => {
|
||||||
spyOn<any>(service, 'loadUserInfo').and.returnValue(of(cloneDeep(mockAppContext)));
|
spyOn<any>(service, 'loadUserInfo').and.returnValue(of(cloneDeep(mockAppContext)));
|
||||||
spyOn<any>(service, 'loadTestCaseSchema').and.returnValue(of(mockTestCasesSchema));
|
spyOn<any>(service, 'loadTestCaseSchema').and.returnValue(of(mockTestCasesSchema));
|
||||||
|
|
||||||
service.createAppContext().subscribe(context => {
|
service.createAppContext().subscribe(context => {
|
||||||
expect(context.repositoryLinks).toEqual(expectedAllRepositoriesLinks);
|
expect(context.repositoryLinks).toEqual(expectedAllRepositoriesLinks);
|
||||||
|
expect(context.isAdminOfAnyService).toEqual(true);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -92,5 +132,47 @@ describe('AppService', () => {
|
|||||||
const req2 = httpTestingController.expectOne('/api/v1/myparserconfig/configstore/repositories');
|
const req2 = httpTestingController.expectOne('/api/v1/myparserconfig/configstore/repositories');
|
||||||
expect(req2.request.method).toEqual('GET');
|
expect(req2.request.method).toEqual('GET');
|
||||||
req2.flush(mockRepositories2);
|
req2.flush(mockRepositories2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get all applications: admin of one', done => {
|
||||||
|
service.setAppContext(mockAppContext);
|
||||||
|
service.getAllApplications().subscribe(apps => {
|
||||||
|
expect(apps).toEqual([mockTopology1]);
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
||||||
|
const req1 = httpTestingController.expectOne('/api/v1/myalert/topologies');
|
||||||
|
expect(req1.request.method).toEqual('GET');
|
||||||
|
req1.flush(mockTopologies1);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should get all applications: admin of both', done => {
|
||||||
|
const mockAppContext2 = cloneDeep(mockAppContext);
|
||||||
|
mockAppContext2.userServices.push({
|
||||||
|
name: 'myparsingapp',
|
||||||
|
type: 'parsingapp',
|
||||||
|
user_roles: [
|
||||||
|
UserRole.SERVICE_USER,
|
||||||
|
UserRole.SERVICE_ADMIN,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
service.setAppContext(mockAppContext2);
|
||||||
|
service.getAllApplications().subscribe(apps => {
|
||||||
|
expect(apps).toEqual([mockTopology1, mockTopology2, mockTopology3]);
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
||||||
|
const req1 = httpTestingController.expectOne('/api/v1/myalert/topologies');
|
||||||
|
expect(req1.request.method).toEqual('GET');
|
||||||
|
req1.flush(mockTopologies1);
|
||||||
|
|
||||||
|
const req2 = httpTestingController.expectOne('/api/v1/myparsingapp/topologies');
|
||||||
|
expect(req2.request.method).toEqual('GET');
|
||||||
|
req2.flush(mockTopologies2);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not be admin user', () => {
|
||||||
|
service.setAppContext(mockAppContextNoAdmin);
|
||||||
|
expect((service as any).isUserAdminOfAnyService(mockAppContextNoAdmin.userServices)).toBeFalse();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { AppConfigService } from '@app/services/app-config.service';
|
import { AppConfigService } from '@app/services/app-config.service';
|
||||||
import { ServiceInfo, RepositoryLinks, RepositoryLinksWrapper, UserInfo, SchemaInfo, UserRole } from '@app/model/config-model';
|
import { ServiceInfo, RepositoryLinks, RepositoryLinksWrapper, UserInfo, UserRole, SchemaInfo, Application, applications } from '@app/model/config-model';
|
||||||
import { Observable, throwError, BehaviorSubject, forkJoin, of } from 'rxjs';
|
import { Observable, throwError, BehaviorSubject, forkJoin, of } from 'rxjs';
|
||||||
import { JSONSchema7 } from 'json-schema';
|
import { JSONSchema7 } from 'json-schema';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { UiMetadata } from '@app/model/ui-metadata-map';
|
import { UiMetadata } from '@app/model/ui-metadata-map';
|
||||||
import 'rxjs/add/observable/forkJoin';
|
import 'rxjs/add/observable/forkJoin';
|
||||||
import { mergeMap } from 'rxjs/operators';
|
import { map, mergeMap } from 'rxjs/operators';
|
||||||
|
|
||||||
export class AppContext {
|
export class AppContext {
|
||||||
user: string;
|
user: string;
|
||||||
@@ -14,6 +14,7 @@ export class AppContext {
|
|||||||
userServicesMap: Map<string, ServiceInfo>;
|
userServicesMap: Map<string, ServiceInfo>;
|
||||||
testCaseSchema: JSONSchema7;
|
testCaseSchema: JSONSchema7;
|
||||||
repositoryLinks: { [name: string]: RepositoryLinks };
|
repositoryLinks: { [name: string]: RepositoryLinks };
|
||||||
|
isAdminOfAnyService: boolean;
|
||||||
get serviceNames() {
|
get serviceNames() {
|
||||||
return Array.from(this.userServicesMap.keys()).sort();
|
return Array.from(this.userServicesMap.keys()).sort();
|
||||||
}
|
}
|
||||||
@@ -51,6 +52,10 @@ export class AppService {
|
|||||||
return this.appContext.repositoryLinks;
|
return this.appContext.repositoryLinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isAdminOfAnyService() {
|
||||||
|
return this.appContext.isAdminOfAnyService;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(private config: AppConfigService, private http: HttpClient) {}
|
constructor(private config: AppConfigService, private http: HttpClient) {}
|
||||||
|
|
||||||
setAppContext(appContext: AppContext): boolean {
|
setAppContext(appContext: AppContext): boolean {
|
||||||
@@ -72,6 +77,7 @@ export class AppService {
|
|||||||
if (appContext && testCaseSchema && repositoryLinks) {
|
if (appContext && testCaseSchema && repositoryLinks) {
|
||||||
appContext.testCaseSchema = testCaseSchema;
|
appContext.testCaseSchema = testCaseSchema;
|
||||||
appContext.repositoryLinks = repositoryLinks;
|
appContext.repositoryLinks = repositoryLinks;
|
||||||
|
appContext.isAdminOfAnyService = this.isUserAdminOfAnyService(appContext.userServices);
|
||||||
return appContext;
|
return appContext;
|
||||||
}
|
}
|
||||||
throwError('Can not load application context');
|
throwError('Can not load application context');
|
||||||
@@ -96,10 +102,47 @@ export class AppService {
|
|||||||
return this.appContext.userServicesMap.get(serviceName).user_roles;
|
return this.appContext.userServicesMap.get(serviceName).user_roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
restartApplication(serviceName: string, application: string): Observable<Application[]> {
|
||||||
|
return this.http.post<applications>(
|
||||||
|
`${this.config.serviceRoot}api/v1/${serviceName}/topologies/${application}/restart`,
|
||||||
|
null
|
||||||
|
).pipe(map(result => result.topologies));
|
||||||
|
}
|
||||||
|
|
||||||
|
restartAllApplications(): Observable<Application[]> {
|
||||||
|
return this.http.post<applications>(
|
||||||
|
`${this.config.serviceRoot}api/v1/topologies/restart`,
|
||||||
|
null
|
||||||
|
).pipe(map(result => result.topologies));
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllApplications(): Observable<Application[]> {
|
||||||
|
return forkJoin(
|
||||||
|
this.userServices
|
||||||
|
.filter(userService => userService.user_roles.includes(UserRole.SERVICE_ADMIN))
|
||||||
|
.map(userService => this.getServiceApplications(userService.name))
|
||||||
|
).pipe(map(topologies => topologies.flat()));
|
||||||
|
}
|
||||||
|
|
||||||
getServiceRepositoryLink(serviceName: string): RepositoryLinks {
|
getServiceRepositoryLink(serviceName: string): RepositoryLinks {
|
||||||
return this.appContext.repositoryLinks[serviceName];
|
return this.appContext.repositoryLinks[serviceName];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isUserAdminOfAnyService(userServices: ServiceInfo[]): boolean {
|
||||||
|
for (const userService of userServices) {
|
||||||
|
if (userService.user_roles.includes(UserRole.SERVICE_ADMIN)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getServiceApplications(serviceName: string): Observable<Application[]> {
|
||||||
|
return this.http.get<applications>(
|
||||||
|
`${this.config.serviceRoot}api/v1/${serviceName}/topologies`
|
||||||
|
).pipe(map(result => result.topologies));
|
||||||
|
}
|
||||||
|
|
||||||
private getAllRepositoryLinks(userServices: ServiceInfo[]): Observable<{ [name: string]: RepositoryLinks }> {
|
private getAllRepositoryLinks(userServices: ServiceInfo[]): Observable<{ [name: string]: RepositoryLinks }> {
|
||||||
return forkJoin(userServices.map(x => this.getRepositoryLinks(x.name)))
|
return forkJoin(userServices.map(x => this.getRepositoryLinks(x.name)))
|
||||||
.map((links: RepositoryLinks[]) => {
|
.map((links: RepositoryLinks[]) => {
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ import {
|
|||||||
Importers,
|
Importers,
|
||||||
ImportedConfig,
|
ImportedConfig,
|
||||||
ConfigToImport,
|
ConfigToImport,
|
||||||
applications,
|
|
||||||
Application,
|
|
||||||
} from '@model/config-model';
|
} from '@model/config-model';
|
||||||
import { TestCase, TestCaseMap, TestCaseResult, TestCaseWrapper } from '@model/test-case';
|
import { TestCase, TestCaseMap, TestCaseResult, TestCaseWrapper } from '@model/test-case';
|
||||||
import { ADMIN_VERSION_FIELD_NAME, UiMetadata } from '@model/ui-metadata-map';
|
import { ADMIN_VERSION_FIELD_NAME, UiMetadata } from '@model/ui-metadata-map';
|
||||||
@@ -414,19 +412,6 @@ export class ConfigLoaderService {
|
|||||||
.pipe(map(result => result));
|
.pipe(map(result => result));
|
||||||
}
|
}
|
||||||
|
|
||||||
getApplications(): Observable<Application[]> {
|
|
||||||
return this.http.get<applications>(
|
|
||||||
`${this.config.serviceRoot}api/v1/${this.serviceName}/topologies`
|
|
||||||
).pipe(map(result => result.topologies));
|
|
||||||
}
|
|
||||||
|
|
||||||
restartApplication(application: string): Observable<Application[]> {
|
|
||||||
return this.http.post<applications>(
|
|
||||||
`${this.config.serviceRoot}api/v1/${this.serviceName}/topologies/${application}/restart`,
|
|
||||||
null
|
|
||||||
).pipe(map(result => result.topologies.filter(t => t.service_name === this.serviceName)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private testCaseFilesToMap(files: any[]): TestCaseMap {
|
private testCaseFilesToMap(files: any[]): TestCaseMap {
|
||||||
const testCaseMap: TestCaseMap = {};
|
const testCaseMap: TestCaseMap = {};
|
||||||
if (files && files.length > 0) {
|
if (files && files.length > 0) {
|
||||||
|
|||||||
@@ -13,6 +13,15 @@ export const mockUserInfoAlert: ServiceInfo =
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const mockUserInfoAlertNoAdmin: ServiceInfo =
|
||||||
|
{
|
||||||
|
name: 'myalert',
|
||||||
|
type: 'alert',
|
||||||
|
user_roles: [
|
||||||
|
UserRole.SERVICE_USER,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
export const mockUserInfoParser: ServiceInfo =
|
export const mockUserInfoParser: ServiceInfo =
|
||||||
{
|
{
|
||||||
name: 'myparserconfig',
|
name: 'myparserconfig',
|
||||||
@@ -26,10 +35,19 @@ export const mockUserServicesMap = new Map();
|
|||||||
mockUserServicesMap.set('myalert', mockUserInfoAlert);
|
mockUserServicesMap.set('myalert', mockUserInfoAlert);
|
||||||
mockUserServicesMap.set('myparserconfig', mockUserInfoParser);
|
mockUserServicesMap.set('myparserconfig', mockUserInfoParser);
|
||||||
|
|
||||||
|
export const mockUserServicesMapNoAdmin = new Map();
|
||||||
|
mockUserServicesMapNoAdmin.set('myalert', mockUserInfoAlertNoAdmin);
|
||||||
|
mockUserServicesMapNoAdmin.set('myparserconfig', mockUserInfoParser);
|
||||||
|
|
||||||
export const mockAppContext = new AppContext();
|
export const mockAppContext = new AppContext();
|
||||||
mockAppContext.user = 'siembol';
|
mockAppContext.user = 'siembol';
|
||||||
mockAppContext.userServices= [mockUserInfoAlert, mockUserInfoParser];
|
mockAppContext.userServices= [mockUserInfoAlert, mockUserInfoParser];
|
||||||
mockAppContext.userServicesMap = mockUserServicesMap;
|
mockAppContext.userServicesMap = mockUserServicesMap;
|
||||||
|
|
||||||
|
export const mockAppContextNoAdmin = new AppContext();
|
||||||
|
mockAppContextNoAdmin.user = 'siembol';
|
||||||
|
mockAppContextNoAdmin.userServices= [mockUserInfoAlertNoAdmin, mockUserInfoParser];
|
||||||
|
mockAppContextNoAdmin.userServicesMap = mockUserServicesMapNoAdmin;
|
||||||
|
|
||||||
export const mockAppContextWithTestSchema = cloneDeep(mockAppContext);
|
export const mockAppContextWithTestSchema = cloneDeep(mockAppContext);
|
||||||
mockAppContextWithTestSchema.testCaseSchema = mockTestCasesSchema;
|
mockAppContextWithTestSchema.testCaseSchema = mockTestCasesSchema;
|
||||||
|
|||||||
@@ -22,5 +22,17 @@
|
|||||||
"title": "Discussions",
|
"title": "Discussions",
|
||||||
"link": "https://github.com/G-Research/siembol/discussions"
|
"link": "https://github.com/G-Research/siembol/discussions"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"managementLinks": [
|
||||||
|
{
|
||||||
|
"title": "Storm UI",
|
||||||
|
"icon": "apps",
|
||||||
|
"link": "storm.siembol.local"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Enrichment Tables",
|
||||||
|
"icon": "table_chart",
|
||||||
|
"link": "enrichment.siembol.local"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# How to add links to the siembol ui home page
|
# How to add links to the siembol ui home page or the management page
|
||||||
The siembol home page has an 'Explore Siembol' section at the button of its home page, as can be seen in the screenshot below. It is used for quick access to useful resources such as documentation, ticket tracking systems etc. By default there is a link to the documentation and to the issues page on the git repo.
|
|
||||||
|
## Siembol home page
|
||||||
|
The Siembol home page has an 'Explore Siembol' section at the button of its home page, as can be seen in the screenshot below. It is used for quick access to useful resources such as documentation, ticket tracking systems etc. By default there is a link to the documentation and to the issues page on the git repo.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -30,3 +32,9 @@ To add a new link you need three things:
|
|||||||
- the url to which the user will be redirected on clicking the link
|
- the url to which the user will be redirected on clicking the link
|
||||||
- the icon to be displayed; this has to be the name of a material icon (you can find them all here: https://material.io/)
|
- the icon to be displayed; this has to be the name of a material icon (you can find them all here: https://material.io/)
|
||||||
- the title to display below the icon
|
- the title to display below the icon
|
||||||
|
|
||||||
|
## Siembol management page
|
||||||
|
|
||||||
|
The Siembol [management page](./how_to_use_the_management_page.md), which is only accessible by admins of any service, has a 'Management Links' section at the top of the page, similar to the links in the home page. This can be used to add links useful to admins.
|
||||||
|
|
||||||
|
Links are added by the user in the `ui-config.json` file in the same way as for home links, but the key is "managementLinks".
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
# How to manage applications
|
# How to manage applications
|
||||||
Admin users can manage applications from the admin panel by clicking on the top right `Manage Applications` button (see screenshot below).
|
Admin users can manage applications from the actions in the [management page](./how_to_use_the_management_page.md) by clicking on the `Manage Applications` button.
|
||||||
|
|
||||||
<img src="../screenshots/admin_editor.png" alt="drawing"/>
|
This opens up a dialog similar to the own in the screenshot below, showing the running applications for all services the user is an admin for with the service name, application name, id, image and attributes.
|
||||||
|
A single application or all applications can be restarted from here. Please wait a few minutes after restarting applications and check storm UI if the new state has been released
|
||||||
|
|
||||||
This opens up a dialog similar to the own in the screenshot below, showing all the running applications for the service with their name, id, image and attributes.
|
|
||||||
Any application can also be restarted from there.
|
|
||||||
|
|
||||||
<img src="../screenshots/applications_manager.png" alt="drawing"/>
|
<img src="../screenshots/applications_manager.png" alt="drawing"/>
|
||||||
|
|
||||||
If there are multiple applications it is possible to search through them using a filter, like in the screenshot below.
|
If there are multiple applications it is possible to search through them using a filter, like in the screenshot below.
|
||||||
|
|
||||||
<img src="../screenshots/applications_manager_multiple.png" alt="drawing"/>
|
> **_note:_** The `Restart All` button will always restart ALL applications even if there is a filter.
|
||||||
|
|
||||||
|
<img src="../screenshots/applications_manager_filter.png" alt="drawing"/>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
11
docs/siembol_ui/how-tos/how_to_use_the_management_page.md
Normal file
11
docs/siembol_ui/how-tos/how_to_use_the_management_page.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# How to use the management page
|
||||||
|
|
||||||
|
The management page can only be accessed by an admin of any service. It has two sections: one with management links and one with various actions. It can be accessed by clicking on the cog on the top right of the navigation bar.
|
||||||
|
|
||||||
|
<img src="../screenshots/management_page.png" alt="drawing"/>
|
||||||
|
|
||||||
|
## Links
|
||||||
|
The management links are links useful to only admins. To add management links see [here](./how_to_add_links_to_siembol_ui_home_page.md).
|
||||||
|
|
||||||
|
## Actions
|
||||||
|
Currently there is only one admin action: managing the running applications. This opens up a dialog which is explained in detail [here](./how_to_manage_applications.md).
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 59 KiB |
BIN
docs/siembol_ui/screenshots/applications_manager_filter.png
Normal file
BIN
docs/siembol_ui/screenshots/applications_manager_filter.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 86 KiB |
BIN
docs/siembol_ui/screenshots/management_page.png
Normal file
BIN
docs/siembol_ui/screenshots/management_page.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
Reference in New Issue
Block a user