Dashboard: fix/updates (WIP)

Hide dahboard in menu when user doesn't have permission
This commit is contained in:
phil 2024-03-24 12:08:42 +05:30
parent 31165ce3d5
commit 89eae25604
19 changed files with 166 additions and 127 deletions

View file

@ -6,8 +6,6 @@ import { MatTableDataSource } from '@angular/material/table'
import { Observable, forkJoin, of as observableOf } from 'rxjs' import { Observable, forkJoin, of as observableOf } from 'rxjs'
import { map, mergeMap } from 'rxjs/operators' import { map, mergeMap } from 'rxjs/operators'
// import { Apollo, gql } from 'apollo-angular'
const fieldTypeMap = { const fieldTypeMap = {
Int: 'number', Int: 'number',
Float: 'number', Float: 'number',
@ -167,22 +165,22 @@ export class DashboardPageSection {
) {} ) {}
} }
export class DashboardPage { // export class DashboardPage {
constructor( // constructor(
public name: string, // public name: string,
public group: string, // public group: string,
public errors: string = undefined, // public errors: string = undefined,
public description: string = undefined, // public description: string = undefined,
public html: string = undefined, // public html: string = undefined,
public notebook: string = undefined, // public notebook: string = undefined,
public dfData: MatTableDataSource<object> = undefined, // public dfData: MatTableDataSource<object> = undefined,
public plotData: Object = undefined, // public plotData: Object = undefined,
public time: Date = undefined, // public time: Date = undefined,
public attachment: string = undefined, // public attachment: string = undefined,
public expandedPanes: string[] = [], // public expandedPanes: string[] = [],
public sections: DashboardPageSection[] = [] // public sections: DashboardPageSection[] = []
) {} // ) {}
} // }
export class Model { export class Model {
constructor( constructor(
@ -361,62 +359,63 @@ export class ModelDataService {
} }
} }
@Injectable() // @Injectable()
export class DashboardDataService { // export class DashboardDataService {
constructor( // constructor(
// private apollo: Apollo // public dashboardService: DashboardService,
) {} // ) {}
getDashboardPage(group: string, name: string): Observable<DashboardPage> { // getDashboardPage(group: string, name: string): Observable<Dashboard> {
console.warn('Migrate Graphql') // return this.dashboardService.getDashboardPageApiDashboardPageGroupNameGet(
return observableOf() // group, name
// return this.get(dashboardPageQuery, {'name': name, 'group': group}).pipe(map( // )
// res => { // // return this.get(dashboardPageQuery, {'name': name, 'group': group}).pipe(map(
// if (res['errors'] && res['errors'].length > 0) { // // res => {
// return new DashboardPage( // // if (res['errors'] && res['errors'].length > 0) {
// name, // // return new DashboardPage(
// group, // // name,
// res['errors'].map(e => e.message).join(', '), // // group,
// ) // // res['errors'].map(e => e.message).join(', '),
// } // // )
// // }
// let page = res['dashboard_page'] // // let page = res['dashboard_page']
// return new DashboardPage( // // return new DashboardPage(
// page['name'], // // page['name'],
// page['group'], // // page['group'],
// '', // // '',
// page['description'], // // page['description'],
// page['html'], // // page['html'],
// page['notebook'], // // page['notebook'],
// JSON.parse(page['dfData']), // // JSON.parse(page['dfData']),
// JSON.parse(page['plotData']), // // JSON.parse(page['plotData']),
// page['time'], // // page['time'],
// page['attachment'], // // page['attachment'],
// page['expandedPanes'], // // page['expandedPanes'],
// page['sections'] ? page['sections'].map( // // page['sections'] ? page['sections'].map(
// section => new DashboardPageSection( // // section => new DashboardPageSection(
// section['name'], // // section['name'],
// section['plot'] // // section['plot']
// ) // // )
// ) : [] // // ) : []
// ) // // )
// } // // }
// )) // // ))
} // }
get(query, vars: object = {}): Observable<any> { // get(query, vars: object = {}): Observable<any> {
console.warn('Migrate Graphql') // console.warn('Migrate Graphql')
return observableOf() // return observableOf()
// return this.apollo.query({ // // return this.apollo.query({
// query: query, // // query: query,
// variables: vars // // variables: vars
// }).pipe(map( // // }).pipe(map(
// result => { // // result => {
// if (result.errors && result.errors.length > 0) { // // if (result.errors && result.errors.length > 0) {
// return result // // return result
// } // // }
// return result.data // // return result.data
// } // // }
// )) // // ))
} // }
} // }

View file

@ -128,6 +128,7 @@ export class AuthenticationService {
isAuthorized(roles: string[]): Observable<boolean> { isAuthorized(roles: string[]): Observable<boolean> {
// Return true if at least one role in given list matches one role of the authenticated user // Return true if at least one role in given list matches one role of the authenticated user
if (roles.length == 0) return of(true) if (roles.length == 0) return of(true)
if (roles.every(role => role == undefined)) return of(true)
// return this.roles.filter(value => -1 !== roles.indexOf(value.name)).length > 0 // return this.roles.filter(value => -1 !== roles.indexOf(value.name)).length > 0
return this.configService.conf.pipe(map( return this.configService.conf.pipe(map(
conf => conf.bsData?.user?.roles?.filter(value => -1 !== roles.indexOf(value.name)).length > 0 conf => conf.bsData?.user?.roles?.filter(value => -1 !== roles.indexOf(value.name)).length > 0

View file

@ -5,19 +5,20 @@
> >
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title> <mat-panel-title>
{{ menuItem['name'] }} {{ menuItem.name }}
</mat-panel-title> </mat-panel-title>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<mat-list-item <mat-list-item
*ngFor="let item of menuItem['pages']" *ngFor="let item of menuItem.pages"
> >
<button mat-button <button mat-button
matTooltip="{{ item['description'] }}" [hidden]="isHidden(item) | async"
matTooltip="{{ item.description }}"
matTooltipPosition="right" matTooltipPosition="right"
routerLink="/dashboard/{{ item['group'] }}/{{ item['name'] }}" routerLink="/dashboard/{{ item.group }}/{{ item.name }}"
routerLinkActive="active" routerLinkActive="active"
> >
{{ item['name'] }} {{ item.name }}
</button> </button>
</mat-list-item> </mat-list-item>
</mat-expansion-panel> </mat-expansion-panel>

View file

@ -1,8 +1,10 @@
import { Component, OnInit, Input, import { Component, OnInit, Input,
ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core' ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'
import { DashboardService, DashboardGroup } from '../../openapi' import { DashboardService, DashboardGroup, DashboardPageMetaData } from '../../openapi'
import { AuthenticationService } from '../../_services/authentication.service'
import { Observable, map, of } from 'rxjs'
import { Dash } from 'plotly.js-dist-min'
@Component({ @Component({
selector: 'gisaf-dashboard-menu', selector: 'gisaf-dashboard-menu',
@ -15,6 +17,7 @@ export class DashboardMenuComponent implements OnInit {
constructor( constructor(
public dashboardService: DashboardService, public dashboardService: DashboardService,
public authenticationService: AuthenticationService,
private cdr: ChangeDetectorRef, private cdr: ChangeDetectorRef,
) {} ) {}
@ -26,4 +29,10 @@ export class DashboardMenuComponent implements OnInit {
} }
) )
} }
isHidden(dashboard: DashboardPageMetaData): Observable<boolean> {
return this.authenticationService.isAuthorized([dashboard.viewable_role]).pipe(map(
r => !r
))
}
} }

View file

@ -2,16 +2,17 @@ import { Injectable } from '@angular/core'
import { RouterStateSnapshot, ActivatedRouteSnapshot } from '@angular/router' import { RouterStateSnapshot, ActivatedRouteSnapshot } from '@angular/router'
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
import { DashboardDataService } from '../../_services/apollo.service' // import { DashboardDataService } from '../../_services/apollo.service'
import { DashboardService, Dashboard } from '../../openapi'
@Injectable() @Injectable()
export class DashboardPageResolver { export class DashboardPageResolver {
constructor( constructor(
private dashboardDataService: DashboardDataService, private dashboardService: DashboardService,
) {} ) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<object> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Dashboard> {
return this.dashboardDataService.getDashboardPage( return this.dashboardService.getDashboardPageApiDashboardPageGroupNameGet(
route.paramMap.get('group'), route.paramMap.get('group'),
route.paramMap.get('name') route.paramMap.get('name')
) )

View file

@ -6,8 +6,7 @@ import { Observable, of } from 'rxjs'
import { map, startWith } from 'rxjs/operators' import { map, startWith } from 'rxjs/operators'
import { UntypedFormControl } from '@angular/forms' import { UntypedFormControl } from '@angular/forms'
import { Dashboard, DashboardSection } from '../../openapi'
import { DashboardPage, DashboardPageSection } from '../../_services/apollo.service'
const expandDefault = ['attachment', 'plot', 'html'] const expandDefault = ['attachment', 'plot', 'html']
@ -25,12 +24,12 @@ export class Section {
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class DashboardPageSectionsComponent implements OnInit { export class DashboardPageSectionsComponent implements OnInit {
@Input() page: DashboardPage @Input() page: Dashboard
currentSection: DashboardPageSection currentSection: DashboardSection
myControl: UntypedFormControl = new UntypedFormControl() myControl: UntypedFormControl = new UntypedFormControl()
@ViewChild('ipt', { static: true }) ipt: ElementRef @ViewChild('ipt', { static: true }) ipt: ElementRef
//sections: Section[] = [] //sections: Section[] = []
filteredOptions: Observable<DashboardPageSection[]> filteredOptions: Observable<DashboardSection[]>
constructor( constructor(
protected route: ActivatedRoute, protected route: ActivatedRoute,
@ -41,7 +40,7 @@ export class DashboardPageSectionsComponent implements OnInit {
ngOnInit() { ngOnInit() {
this.route.queryParams.subscribe( this.route.queryParams.subscribe(
(params: Params) => { (params: Params) => {
let s: DashboardPageSection[] = this.page.sections.filter(s=>s.name == params['section']) let s: DashboardSection[] = this.page.sections.filter(s=>s.name == params['section'])
if (s.length > 0) { if (s.length > 0) {
this.select(s[0]) this.select(s[0])
} }
@ -81,14 +80,14 @@ export class DashboardPageSectionsComponent implements OnInit {
) )
} }
filter(val: string): DashboardPageSection[] { filter(val: string): DashboardSection[] {
let filter = val.toLowerCase() let filter = val.toLowerCase()
return this.page.sections.filter(option => { return this.page.sections.filter(option => {
return option.name.toLowerCase().indexOf(filter) != -1 return option.name.toLowerCase().indexOf(filter) != -1
}) })
} }
select(section: DashboardPageSection) { select(section: DashboardSection) {
this.currentSection = section this.currentSection = section
this.cdr.markForCheck() this.cdr.markForCheck()
} }

View file

@ -18,6 +18,8 @@ a {
:host ::ng-deep table th.mat-mdc-header-cell { :host ::ng-deep table th.mat-mdc-header-cell {
padding: 0 0.5em; padding: 0 0.5em;
text-transform: capitalize;
font-weight: bolder;
} }
:host ::ng-deep table td.mat-mdc-cell { :host ::ng-deep table td.mat-mdc-cell {

View file

@ -1,14 +1,14 @@
<mat-card appearance="outlined"> <mat-card appearance="outlined">
<mat-card-title> <mat-card-title>
{{ page.group }} / {{ page.name }} {{ page?.group }} / {{ page.name }}
</mat-card-title> </mat-card-title>
<mat-card-subtitle> <mat-card-subtitle>
{{ page.description }} {{ page.description }}
</mat-card-subtitle> </mat-card-subtitle>
<mat-card-content> <mat-card-content>
<p *ngIf='page.errors' class='errors'> <!-- <p *ngIf='page.errors' class='errors'>
Error: {{ page.errors }} Error: {{ page.errors }}
</p> </p> -->
<mat-accordion <mat-accordion
[multi]='multiPanel'> [multi]='multiPanel'>
<mat-expansion-panel *ngIf='page.attachment' <mat-expansion-panel *ngIf='page.attachment'
@ -41,7 +41,7 @@
</mat-expansion-panel-header> </mat-expansion-panel-header>
<div class='item' [innerHTML]='page.html | safeHtml'></div> <div class='item' [innerHTML]='page.html | safeHtml'></div>
</mat-expansion-panel> </mat-expansion-panel>
<mat-expansion-panel *ngIf='page.dfData' <mat-expansion-panel *ngIf='page.dfData.length > 0'
[expanded]="expand.includes('data')"> [expanded]="expand.includes('data')">
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title> <mat-panel-title>
@ -49,17 +49,17 @@
</mat-panel-title> </mat-panel-title>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<table mat-table <table mat-table
[dataSource]='page.dfData'> [dataSource]='getTableData()'>
<ng-container *ngFor='let colName of df_columns' <ng-container *ngFor='let colName of df_columns'
[matColumnDef]='colName'> [matColumnDef]='colName'>
<th mat-header-cell *matHeaderCellDef>{{ colName }}</th> <th mat-header-cell *matHeaderCellDef>{{ colName }}</th>
<td mat-cell *matCellDef="let value"> {{ value[colName] }} </td> <td mat-cell *matCellDef="let value"> {{ formatCell(value[colName]) }} </td>
</ng-container> </ng-container>
<tr mat-header-row *matHeaderRowDef="df_columns; sticky: true"></tr> <tr mat-header-row *matHeaderRowDef="df_columns; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: df_columns"></tr> <tr mat-row *matRowDef="let row; columns: df_columns"></tr>
</table> </table>
</mat-expansion-panel> </mat-expansion-panel>
<gisaf-dashboard-page-sections *ngIf='page.sections.length > 0' [page]="page"> <gisaf-dashboard-page-sections *ngIf='page.sections?.length > 0' [page]="page">
</gisaf-dashboard-page-sections> </gisaf-dashboard-page-sections>
</mat-accordion> </mat-accordion>
</mat-card-content> </mat-card-content>

View file

@ -2,7 +2,8 @@ import { Component, OnInit,
ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core' ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router' import { ActivatedRoute, Router } from '@angular/router'
import { DashboardPage } from '../../_services/apollo.service' import { Dashboard } from '../../openapi'
import { CdkTableDataSourceInput } from '@angular/cdk/table'
const expandDefault = ['attachment', 'plot', 'html'] const expandDefault = ['attachment', 'plot', 'html']
@ -13,7 +14,11 @@ const expandDefault = ['attachment', 'plot', 'html']
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class DashboardPageComponent implements OnInit { export class DashboardPageComponent implements OnInit {
page: DashboardPage page: Dashboard = {
name: '',
group: '',
description: '',
}
multiPanel: boolean = true multiPanel: boolean = true
df_columns: string[] df_columns: string[]
// Default expanded panels // Default expanded panels
@ -40,14 +45,13 @@ export class DashboardPageComponent implements OnInit {
ngOnInit() { ngOnInit() {
this.route.data.subscribe( this.route.data.subscribe(
(data: DashboardPage) => { data => {
let item = data['item'] this.page = data['dashboard']
if (item.dfData) { if (this.page.dfData && this.page.dfData.length > 0) {
this.df_columns = item.dfData['schema']['fields'].map(f => f.name) // this.df_columns = item.dfData['schema']['fields'].map(f => f.name)
item.dfData = item.dfData['data'] this.df_columns = Object.keys(this.page.dfData[0])
} }
this.page = item if (this.page.expandedPanes?.length > 0) {
if (this.page.expandedPanes.length > 0) {
this.expand = this.page.expandedPanes this.expand = this.page.expandedPanes
} }
else { else {
@ -57,4 +61,17 @@ export class DashboardPageComponent implements OnInit {
} }
) )
} }
getTableData(): CdkTableDataSourceInput<object> {
return this.page.dfData as CdkTableDataSourceInput<object>
}
formatCell(value) {
if (typeof value === 'number') {
return value.toFixed(1)
}
else {
return value
}
}
} }

View file

@ -14,7 +14,7 @@ const routes: Routes = [
path: ':group/:name', path: ':group/:name',
component: DashboardPageComponent, component: DashboardPageComponent,
resolve: { resolve: {
item: DashboardPageResolver dashboard: DashboardPageResolver
} }
}, },
] ]

View file

@ -26,7 +26,7 @@ import { PlotlyViaWindowModule } from 'angular-plotly.js'
import { PipesModule } from '../pipes.module' import { PipesModule } from '../pipes.module'
import { DashboardDataService } from '../_services/apollo.service' // import { DashboardDataService } from '../_services/apollo.service'
import { DashboardComponent } from './dashboard.component' import { DashboardComponent } from './dashboard.component'
import { DashboardRoutingModule } from './dashboard-routing.module' import { DashboardRoutingModule } from './dashboard-routing.module'
import { DashboardMenuComponent } from './dashboard-menu/dashboard-menu.component' import { DashboardMenuComponent } from './dashboard-menu/dashboard-menu.component'
@ -69,7 +69,7 @@ import { DashboardHomeComponent } from './dashboard-home/dashboard-home.componen
DashboardHomeComponent, DashboardHomeComponent,
], ],
providers: [ providers: [
DashboardDataService // DashboardDataService
] ]
}) })

View file

@ -27,7 +27,6 @@ import { FlexLayoutModule } from 'ngx-flexible-layout'
import { PipesModule } from '../pipes.module' import { PipesModule } from '../pipes.module'
import { AdminDetailModule } from '../admin/admin-detail/admin-detail.module' import { AdminDetailModule } from '../admin/admin-detail/admin-detail.module'
import { DashboardDataService } from '../_services/apollo.service'
import { InfoDataService } from './info-data.service' import { InfoDataService } from './info-data.service'
import { InfoComponent } from './info.component' import { InfoComponent } from './info.component'
@ -112,7 +111,6 @@ import { DownloaderComponent } from './info-tools/downloader.component'
], ],
providers: [ providers: [
InfoDataService, InfoDataService,
DashboardDataService,
TagsPluginsService, TagsPluginsService,
] ]
}) })

View file

@ -21,7 +21,7 @@ export type OpenAPIConfig = {
export const OpenAPI: OpenAPIConfig = { export const OpenAPI: OpenAPIConfig = {
BASE: '', BASE: '',
VERSION: '2023.4.dev51+g15fe7fa.d20240318', VERSION: '2023.4.dev53+gd539a72.d20240320',
WITH_CREDENTIALS: false, WITH_CREDENTIALS: false,
CREDENTIALS: 'include', CREDENTIALS: 'include',
TOKEN: undefined, TOKEN: undefined,

View file

@ -16,10 +16,11 @@ export type { Body_login_for_access_token_api_token_post } from './models/Body_l
export type { BootstrapData } from './models/BootstrapData'; export type { BootstrapData } from './models/BootstrapData';
export type { CategoryGroup } from './models/CategoryGroup'; export type { CategoryGroup } from './models/CategoryGroup';
export type { CategoryRead } from './models/CategoryRead'; export type { CategoryRead } from './models/CategoryRead';
export type { DashboadPageSectionType } from './models/DashboadPageSectionType'; export type { Dashboard } from './models/Dashboard';
export type { DashboardGroup } from './models/DashboardGroup'; export type { DashboardGroup } from './models/DashboardGroup';
export type { DashboardHome } from './models/DashboardHome'; export type { DashboardHome } from './models/DashboardHome';
export type { DashboardPage_ } from './models/DashboardPage_'; export type { DashboardPageMetaData } from './models/DashboardPageMetaData';
export type { DashboardSection } from './models/DashboardSection';
export type { DataProvider } from './models/DataProvider'; export type { DataProvider } from './models/DataProvider';
export type { Downloader } from './models/Downloader'; export type { Downloader } from './models/Downloader';
export type { Equipment } from './models/Equipment'; export type { Equipment } from './models/Equipment';

View file

@ -2,18 +2,18 @@
/* istanbul ignore file */ /* istanbul ignore file */
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
import type { DashboadPageSectionType } from './DashboadPageSectionType'; import type { DashboardSection } from './DashboardSection';
export type DashboardPage_ = { export type Dashboard = {
name: string; name: string;
group: string; group: string;
description: string; description: string;
time?: (string | null); time?: (string | null);
html?: (string | null); html?: (string | null);
attachment?: (string | null); attachment?: (string | null);
dfData?: (string | null); dfData?: Array<any>;
plotData?: (string | null); plotData?: (string | null);
notebook?: (string | null); notebook?: (string | null);
expandedPanes?: (Array<string> | null); expandedPanes?: (Array<string> | null);
sections?: (Array<DashboadPageSectionType> | null); sections?: (Array<DashboardSection> | null);
}; };

View file

@ -2,9 +2,9 @@
/* istanbul ignore file */ /* istanbul ignore file */
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
import type { DashboardPage_ } from './DashboardPage_'; import type { DashboardPageMetaData } from './DashboardPageMetaData';
export type DashboardGroup = { export type DashboardGroup = {
name: string; name: string;
pages: Array<DashboardPage_>; pages: Array<DashboardPageMetaData>;
}; };

View file

@ -0,0 +1,11 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type DashboardPageMetaData = {
name: string;
group: string;
description: string;
viewable_role?: (string | null);
};

View file

@ -2,7 +2,7 @@
/* istanbul ignore file */ /* istanbul ignore file */
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
export type DashboadPageSectionType = { export type DashboardSection = {
name: string; name: string;
plot: string; plot: string;
}; };

View file

@ -5,9 +5,9 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import type { Observable } from 'rxjs'; import type { Observable } from 'rxjs';
import type { Dashboard } from '../models/Dashboard';
import type { DashboardGroup } from '../models/DashboardGroup'; import type { DashboardGroup } from '../models/DashboardGroup';
import type { DashboardHome } from '../models/DashboardHome'; import type { DashboardHome } from '../models/DashboardHome';
import type { DashboardPage_ } from '../models/DashboardPage_';
import { OpenAPI } from '../core/OpenAPI'; import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request'; import { request as __request } from '../core/request';
@Injectable({ @Injectable({
@ -47,13 +47,13 @@ export class DashboardService {
* Get Dashboard Page * Get Dashboard Page
* @param group * @param group
* @param name * @param name
* @returns DashboardPage_ Successful Response * @returns Dashboard Successful Response
* @throws ApiError * @throws ApiError
*/ */
public getDashboardPageApiDashboardPageGroupNameGet( public getDashboardPageApiDashboardPageGroupNameGet(
group: string, group: string,
name: string, name: string,
): Observable<DashboardPage_> { ): Observable<Dashboard> {
return __request(OpenAPI, this.http, { return __request(OpenAPI, this.http, {
method: 'GET', method: 'GET',
url: '/api/dashboard/page/{group}/{name}', url: '/api/dashboard/page/{group}/{name}',