Skip to content
Snippets Groups Projects
Commit 8f824546 authored by Joanna Kaźmierczak's avatar Joanna Kaźmierczak
Browse files

dashboard view

parent 72ca1439
No related branches found
No related tags found
3 merge requests!184Develop,!152Release/1.9.0,!51Draft: Resolve "New portal layout"
......@@ -31,6 +31,7 @@
"ajv": "^6.12.6",
"angular-password-strength-meter": "^11.0.0",
"bootstrap": "^3.4.1",
"chart.js": "^4.4.8",
"document-register-element": "^1.14.10",
"jquery": "^3.6.0",
"lodash": "^4.17.21",
......@@ -3853,6 +3854,12 @@
"resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="
},
"node_modules/@kurkle/color": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
"license": "MIT"
},
"node_modules/@leichtgewicht/ip-codec": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
......@@ -6271,6 +6278,18 @@
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
"dev": true
},
"node_modules/chart.js": {
"version": "4.4.8",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.8.tgz",
"integrity": "sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==",
"license": "MIT",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
"engines": {
"pnpm": ">=8"
}
},
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
......@@ -18401,6 +18420,11 @@
"resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="
},
"@kurkle/color": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="
},
"@leichtgewicht/ip-codec": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
......@@ -20278,6 +20302,14 @@
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
"dev": true
},
"chart.js": {
"version": "4.4.8",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.8.tgz",
"integrity": "sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==",
"requires": {
"@kurkle/color": "^0.3.0"
}
},
"chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
......@@ -36,6 +36,7 @@
"ajv": "^6.12.6",
"angular-password-strength-meter": "^11.0.0",
"bootstrap": "^3.4.1",
"chart.js": "^4.4.8",
"document-register-element": "^1.14.10",
"jquery": "^3.6.0",
"lodash": "^4.17.21",
......
......
import { TestBed, inject} from '@angular/core/testing';
import { DashboardService } from './dashboard.service';
import {HttpClient, HttpHandler} from '@angular/common/http';
import {Observable, of} from 'rxjs';
import {Configuration} from '../model/configuration';
import {AppConfigService} from './appconfig.service';
class MockConfigurationService{
protected uri:string;
constructor() {
this.uri = 'http://localhost/api';
}
public getApiUrl(): string {
return 'http://localhost/api';
}
public getConfiguration():Observable<Configuration>{
return of<Configuration>();
}
public updateConfiguration(configuration:Configuration):Observable<any>{
return of<Configuration>();
}
}
describe('DashboardService', () => {
let service: DashboardService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [DashboardService, HttpHandler, HttpClient, {provide: AppConfigService, useClass: MockConfigurationService}]
});
service = TestBed.inject(DashboardService);
});
it('should be created', inject([DashboardService], (service: DashboardService) =>{
expect(service).toBeTruthy();
}));
});
import { Injectable } from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {AppConfigService} from './appconfig.service';
import {GenericDataService} from './genericdata.service';
@Injectable({
providedIn: 'root'
})
export class DashboardService extends GenericDataService {
constructor(http: HttpClient, appConfig: AppConfigService) {
super(http, appConfig);
}
public getAdmin() {
return this.get(this.appConfig.getApiUrl() + '/dashboard/admin')
}
public getDomainAdmin(domainId?: number) {
return this.get(this.appConfig.getApiUrl() + '/dashboard/domain/' + domainId)
}
}
td{
padding: 10px;
background: transparent;
}
th{
padding: 10px;
}
:host ::ng-deep .p-datatable .p-datatable-thead > tr > th{
border: 1px solid #E0E2E5;
background:transparent;
border-width: 0 0 1px 0;
}
:host ::ng-deep .p-datatable .p-datatable-tbody > tr > td {
text-align: left;
border: 1px solid #E0E2E5;
border-width: 0 0 1px 0;
padding: 1rem 1rem;
}
:host ::ng-deep .p-datatable .p-datatable-tbody > tr{
background: transparent;
}
:host ::ng-deep .p-datatable .p-paginator-bottom{
height: 40px;
background: transparent;
border: none;
margin-top:10px;
}
:host ::ng-deep .p-datatable .p-datatable-tbody > tr{
background: transparent;
}
:host ::ng-deep .p-paginator .p-paginator-pages .p-paginator-page{
transition: unset;
border-radius: 50%;
min-width:3.5rem;
height:3.5rem;
margin:0 5px;
font-size: 14px;
}
:host ::ng-deep .p-paginator-element{
border-radius:50%;
margin:0 5px;
min-width:3.5rem;
height:3.5rem;
font-size: 14px;
}
:host ::ng-deep .p-paginator-icon{
height: 1.5rem;
width: 1.5rem;
}
:host ::ng-deep .p-paginator .p-paginator-pages .p-paginator-page.p-highlight{
background: var(--user-button-background-hover);
}
:host ::ng-deep .p-datatable-wrapper {
max-height: 50vh
}
:host ::ng-deep .p-datatable.p-datatable-scrollable > .p-datatable-wrapper > .p-datatable-table > .p-datatable-thead, .p-datatable.p-datatable-scrollable > .p-datatable-wrapper > .p-datatable-table > .p-datatable-tfoot, .p-datatable.p-datatable-scrollable > .p-datatable-wrapper > .p-scroller-viewport > .p-scroller > .p-datatable-table > .p-datatable-thead, .p-datatable.p-datatable-scrollable > .p-datatable-wrapper > .p-scroller-viewport > .p-scroller > .p-datatable-table > .p-datatable-tfoot{
background: var(--app-background-color);
}
.width-50 {
width: 49%
}
@media screen and (max-width: 1390px){
.width-50 {
width:100%
}
}
<p>admin-dashboard works!</p>
<div *roles="['ROLE_SYSTEM_ADMIN']">
<div style="display: flex; flex-direction:column">
<div style="display: flex; flex-wrap: wrap;">
<div class="background-section" style="flex: 1 1 30%; margin-right: 20px">
<h5 style="font-weight: bold">Current number of user </h5>
<h1 style="font-weight: bold">{{ adminData.userCount}}</h1>
</div>
<div class="background-section" style="flex: 1 1 30%; margin-right: 20px">
<h5 style="font-weight: bold">Current number of domains </h5>
<h1 style="font-weight: bold">{{ adminData.domainsCount}}</h1>
</div>
<div class="background-section" style="flex: 1 1 30% ;">
<h5 style="font-weight: bold">Current number of deployed applications </h5>
<h1 style="font-weight: bold">{{ adminData.instanceCount}}</h1>
</div>
</div>
<div class="grid" style="display: flex; justify-content: space-between;">
<div class="background-section width-50 " style="margin-right: 20px">
<h5 style="font-weight: bold">Application deployments in the last week</h5>
<p-table [value]="instanceCountInPeriodDetails" [scrollable]="true" [style]="{'width': '100%', 'max-height': '50vh'}">
<ng-template pTemplate="header">
<tr>
<th></th>
<th>Name</th>
<th>Version</th>
<th>Domain</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-instance>
<tr>
<td><img style="height: 40px" src="../../../assets/images/app-logo-example.png"/></td>
<td>{{instance.applicationName}}</td>
<td>{{instance.applicationVersion}}</td>
<td>{{instance.domainName}}</td>
</tr>
</ng-template>
</p-table>
</div>
<div class="width-50" >
<div class="background-section" style="">
<h5 style="font-weight: bold">Most popular applications</h5>
<p-chart type="bar" [data]="popularAppsChartData" [options]="basicOptions" width="100%" height="50vh" />
</div>
</div>
</div>
</div>
</div>
<div *roles="['ROLE_DOMAIN_ADMIN','ROLE_SYSTEM_ADMIN']">
<div *ngIf="domainId !== 1">
<div style="display: flex">
<div class="background-section" style="flex: 1 1 50%; margin-right: 20px">
<h5 style="font-weight: bold">Last login to the domain </h5>
<p-table [value]="domainAdminData?.userLogins | keyvalue">
<ng-template pTemplate="header">
<tr>
<th>User</th>
<th>Last login</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-login>
<tr>
<td>{{ login.key }}</td>
<td>{{ formatDate(login.value) }}</td>
</tr>
</ng-template>
</p-table>
</div>
<div class="background-section" style="flex: 1 1 50%;">
<h5 style="font-weight: bold">Number of deployed applications per user </h5>
<p-table [value]="domainAdminData?.applicationDeployed | keyvalue">
<ng-template pTemplate="header">
<tr>
<th>User</th>
<th>Deployment</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-deployment>
<tr>
<td>{{ deployment.key }}</td>
<td>{{ deployment.value }}</td>
</tr>
</ng-template>
</p-table>
</div>
</div>
<div class="background-section" style="">
<h5 style="font-weight: bold">Application status</h5>
<p-table [value]="applicationUpgradeStatus" [paginator]="true" [rows]="4" [scrollable]="true" [style]="{'width': '100%'}">
<ng-template pTemplate="header">
<tr>
<th></th>
<th>Name</th>
<th>Id</th>
<th>Instance name</th>
<th>Version</th>
<th>Need upgrade</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-app>
<tr>
<td><img style="height: 40px" src="../../../assets/images/app-logo-example.png"/></td>
<td>{{app.appName}}</td>
<td>{{app.appId}}</td>
<td>{{app.instanceName}}</td>
<td>{{app.appVersion}}</td>
<td>{{app.upgradePossible}}</td>
</tr>
</ng-template>
</p-table>
</div>
</div>
</div>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AdminDashboardComponent } from './admin-dashboard.component';
describe('AdminDashboardComponent', () => {
let component: AdminDashboardComponent;
let fixture: ComponentFixture<AdminDashboardComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AdminDashboardComponent]
})
.compileComponents();
fixture = TestBed.createComponent(AdminDashboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
// import { ComponentFixture, TestBed } from '@angular/core/testing';
//
// import { AdminDashboardComponent } from './admin-dashboard.component';
//
// describe('AdminDashboardComponent', () => {
// let component: AdminDashboardComponent;
// let fixture: ComponentFixture<AdminDashboardComponent>;
//
// beforeEach(async () => {
// await TestBed.configureTestingModule({
// declarations: [AdminDashboardComponent]
// })
// .compileComponents();
//
// fixture = TestBed.createComponent(AdminDashboardComponent);
// component = fixture.componentInstance;
// fixture.detectChanges();
// });
//
// it('should create', () => {
// expect(component).toBeTruthy();
// });
// });
import { Component } from '@angular/core';
import {DashboardService} from '../../service/dashboard.service';
import {UserDataService} from '../../service/userdata.service';
@Component({
selector: 'app-admin-dashboard',
......@@ -6,5 +8,99 @@ import { Component } from '@angular/core';
styleUrl: './admin-dashboard.component.css'
})
export class AdminDashboardComponent {
popularAppsChartData: any;
basicOptions: any;
adminData: any;
domainAdminData: any;
instanceCountInPeriodDetails: any[] = [];
applicationUpgradeStatus: any[] = [];
domainId;
constructor(protected dashboardService: DashboardService,
private userDataService: UserDataService) {
}
ngOnInit() {
this.userDataService.selectedDomainId.subscribe((domainId) => {
this.domainId = domainId
this.getDomainAdmin()
});
this.dashboardService.getAdmin().subscribe(
(response) => {
this.adminData = response;
this.chartData();
this.instanceCountInPeriodDetails = this.adminData.instanceCountInPeriodDetails;
}
)
const documentStyle = getComputedStyle(document.documentElement);
const textColor = documentStyle.getPropertyValue('--text-color');
const textColorSecondary = documentStyle.getPropertyValue('--text-color-secondary');
const surfaceBorder = documentStyle.getPropertyValue('--surface-border');
this.basicOptions = {
plugins: {
legend: {
labels: {
color: textColor
}
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
color: textColorSecondary,
callback: function(value) {
return Number(value).toFixed(0);
}
},
grid: {
color: surfaceBorder,
drawBorder: false
}
},
x: {
ticks: {
color: textColorSecondary
},
grid: {
color: surfaceBorder,
drawBorder: false
}
}
}
};
}
chartData() {
const appNames = Object.keys(this.adminData.popularApps);
const appValues = Object.values(this.adminData.popularApps);
this.popularAppsChartData = {
labels: appNames,
datasets: [
{
label: 'Count of deployments',
data: appValues,
borderColor: '#42A5F5',
backgroundColor: ['rgba(66, 165, 245, 0.2)', 'rgba(255, 208, 208, 0.7)', 'rgba(115, 104, 193, 0.7)', 'rgba(255, 193, 130, 0.7)', 'rgba(140, 193, 104, 0.7)'],
fill: true
}
]
};
}
formatDate(date: any): string {
return new Date(date).toLocaleString();
}
getDomainAdmin() {
this.dashboardService.getDomainAdmin(this.domainId).subscribe(
(response) => {
this.domainAdminData = response;
this.applicationUpgradeStatus = this.domainAdminData.applicationUpgradeStatus;
}
)
}
}
......@@ -68,6 +68,7 @@ import {CheckboxModule} from 'primeng/checkbox';
import { InputGroupModule } from 'primeng/inputgroup';
import { InputGroupAddonModule } from 'primeng/inputgroupaddon';
import { ButtonModule } from 'primeng/button';
import {ChartModule} from 'primeng/chart';
@NgModule({
......@@ -91,7 +92,8 @@ import { ButtonModule } from 'primeng/button';
CheckboxModule,
InputGroupModule,
InputGroupAddonModule,
ButtonModule
ButtonModule,
ChartModule
],
declarations: [
RateComponent,
......
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment