RED-3800: use same base screen for admin

This commit is contained in:
Dan Percic 2022-07-07 21:13:05 +03:00
parent d96b0b8ffd
commit d1511ca87b
17 changed files with 192 additions and 231 deletions

View File

@ -37,9 +37,6 @@
"@schematics/angular:guard": {
"skipTests": true
},
"@schematics/angular:module": {
"skipTests": true
},
"@schematics/angular:pipe": {
"skipTests": true
},

View File

@ -15,7 +15,6 @@
.full-height {
display: flex;
flex-direction: row;
position: absolute;
bottom: 0;
width: 100%;
height: calc(100% + 50px);

View File

@ -166,7 +166,13 @@ const routes: Routes = [
},
{
path: 'users',
component: UserListingScreenComponent,
component: BaseAdminScreenComponent,
children: [
{
path: '',
component: UserListingScreenComponent,
},
],
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
@ -196,7 +202,13 @@ const routes: Routes = [
},
{
path: 'digital-signature',
component: DigitalSignatureScreenComponent,
component: BaseAdminScreenComponent,
children: [
{
path: '',
component: DigitalSignatureScreenComponent,
},
],
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
@ -205,7 +217,13 @@ const routes: Routes = [
},
{
path: 'audit',
component: AuditScreenComponent,
component: BaseAdminScreenComponent,
children: [
{
path: '',
component: AuditScreenComponent,
},
],
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
@ -214,7 +232,13 @@ const routes: Routes = [
},
{
path: 'general-config',
component: GeneralConfigScreenComponent,
component: BaseAdminScreenComponent,
children: [
{
path: '',
component: GeneralConfigScreenComponent,
},
],
canActivate: [CompositeRouteGuard],
canDeactivate: [PendingChangesGuard],
data: {

View File

@ -1,38 +1,17 @@
<section class="settings">
<div class="overlay-shadow"></div>
<iqser-page-header
(closeAction)="routerHistoryService.navigateToLastDossiersScreen()"
[pageLabel]="'audit' | translate"
[showCloseButton]="currentUser.isUser"
></iqser-page-header>
<redaction-admin-side-nav type="settings"></redaction-admin-side-nav>
<div>
<div class="page-header">
<div class="breadcrumb" translate="audit"></div>
<div class="actions">
<iqser-circle-button
*ngIf="currentUser.isUser"
[tooltip]="'common.close' | translate"
class="ml-6"
icon="iqser:close"
redactionNavigateLastDossiersScreen
tooltipPosition="below"
></iqser-circle-button>
</div>
</div>
<div class="content-inner">
<div class="content-container">
<iqser-table
[headerTemplate]="headerTemplate"
[itemSize]="80"
[noDataIcon]="'iqser:document'"
[noDataText]="'audit-screen.no-data.title' | translate"
[tableColumnConfigs]="tableColumnConfigs"
[totalSize]="logs?.totalHits || 0"
>
</iqser-table>
</div>
</div>
</div>
</section>
<iqser-table
[headerTemplate]="headerTemplate"
[itemSize]="80"
[noDataIcon]="'iqser:document'"
[noDataText]="'audit-screen.no-data.title' | translate"
[tableColumnConfigs]="tableColumnConfigs"
[totalSize]="logs?.totalHits || 0"
></iqser-table>
<ng-template #headerTemplate>
<div class="table-header-actions">
@ -41,20 +20,23 @@
[settings]="{ currentPage: logs?.page || 0, totalPages: totalPages }"
class="mr-0"
></redaction-pagination>
<div class="separator">·</div>
<form [formGroup]="form">
<div class="iqser-input-group w-150 mr-20">
<mat-form-field class="no-label">
<mat-select formControlName="category" (selectionChange)="filterChange()">
<mat-select (selectionChange)="filterChange()" formControlName="category">
<mat-option *ngFor="let category of categories" [value]="category">
{{ translations[category] | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="iqser-input-group w-150">
<mat-form-field class="no-label">
<mat-select formControlName="userId" (selectionChange)="filterChange()">
<mat-select (selectionChange)="filterChange()" formControlName="userId">
<mat-select-trigger>
<redaction-initials-avatar
*ngIf="form.get('userId').value !== ALL_USERS"
@ -74,9 +56,11 @@
</mat-select>
</mat-form-field>
</div>
<div class="separator">·</div>
<div class="iqser-input-group datepicker-wrapper mr-20">
<input [matDatepicker]="fromPicker" formControlName="from" placeholder="dd/mm/yy" (dateChange)="filterChange()" />
<input (dateChange)="filterChange()" [matDatepicker]="fromPicker" formControlName="from" placeholder="dd/mm/yy" />
<mat-datepicker-toggle [for]="fromPicker" matSuffix>
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
</mat-datepicker-toggle>
@ -86,7 +70,7 @@
<div class="mr-20" translate="audit-screen.to"></div>
<div class="iqser-input-group datepicker-wrapper">
<input [matDatepicker]="toPicker" formControlName="to" placeholder="dd/mm/yy" (dateChange)="filterChange()" />
<input (dateChange)="filterChange()" [matDatepicker]="toPicker" formControlName="to" placeholder="dd/mm/yy" />
<mat-datepicker-toggle [for]="toPicker" matSuffix>
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
</mat-datepicker-toggle>

View File

@ -1,4 +1,4 @@
import { Component, forwardRef, Injector, OnDestroy, OnInit } from '@angular/core';
import { Component, forwardRef, inject, Injector, OnDestroy, OnInit } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { applyIntervalConstraints } from '@utils/date-inputs-utils';
import { DefaultListingServices, ListingComponent, LoadingService, TableColumnConfig } from '@iqser/common-ui';
@ -9,6 +9,7 @@ import { Audit, IAudit, IAuditResponse, IAuditSearchRequest } from '@red/domain'
import { AuditService } from '../../services/audit.service';
import { firstValueFrom } from 'rxjs';
import { Dayjs } from 'dayjs';
import { RouterHistoryService } from '../../../../services/router-history.service';
const PAGE_SIZE = 50;
@ -24,6 +25,7 @@ export class AuditScreenComponent extends ListingComponent<Audit> implements OnI
readonly translations = auditCategoriesTranslations;
readonly currentUser = this._userService.currentUser;
readonly form: UntypedFormGroup = this._getForm();
readonly routerHistoryService = inject(RouterHistoryService);
categories: string[] = [];
userIds: Set<string>;
logs: IAuditResponse;

View File

@ -1,56 +1,46 @@
<section class="settings">
<div class="overlay-shadow"></div>
<iqser-page-header
(closeAction)="routerHistoryService.navigateToLastDossiersScreen()"
[pageLabel]="'digital-signature' | translate"
[showCloseButton]="currentUser.isUser"
></iqser-page-header>
<redaction-admin-side-nav type="settings"></redaction-admin-side-nav>
<div class="content-container">
<div class="content-container-content">
<iqser-empty-state
(action)="openConfigureCertificate()"
*ngIf="!digitalSignature"
[buttonIcon]="null"
[buttonLabel]="'digital-signature-screen.no-data.action' | translate"
[text]="'digital-signature-screen.no-data.title' | translate"
icon="iqser:document"
></iqser-empty-state>
<div>
<iqser-page-header
(closeAction)="routerHistoryService.navigateToLastDossiersScreen()"
[pageLabel]="'digital-signature' | translate"
[showCloseButton]="currentUser.isUser"
></iqser-page-header>
<ng-container *ngIf="digitalSignature">
<redaction-pkcs-signature-configuration
*ngIf="currentCertificateType === certificateType.PKCS"
[digitalSignature]="digitalSignature"
></redaction-pkcs-signature-configuration>
<redaction-kms-signature-configuration
*ngIf="currentCertificateType === certificateType.KMS"
[digitalSignature]="digitalSignature"
></redaction-kms-signature-configuration>
</ng-container>
<div class="content-inner">
<div class="content-container">
<div class="content-container-content">
<iqser-empty-state
(action)="openConfigureCertificate()"
*ngIf="!digitalSignature"
[buttonLabel]="'digital-signature-screen.no-data.action' | translate"
[text]="'digital-signature-screen.no-data.title' | translate"
icon="iqser:document"
[buttonIcon]="null"
></iqser-empty-state>
<div [class.hidden]="!digitalSignature" class="changes-box">
<iqser-icon-button
(action)="saveDigitalSignature()"
[disabled]="disabled"
[label]="'digital-signature-screen.action.save' | translate"
[type]="iconButtonTypes.primary"
icon="iqser:check"
></iqser-icon-button>
<ng-container *ngIf="digitalSignature">
<redaction-pkcs-signature-configuration
*ngIf="currentCertificateType === certificateType.PKCS"
[digitalSignature]="digitalSignature"
></redaction-pkcs-signature-configuration>
<redaction-kms-signature-configuration
*ngIf="currentCertificateType === certificateType.KMS"
[digitalSignature]="digitalSignature"
></redaction-kms-signature-configuration>
</ng-container>
<div [class.hidden]="!digitalSignature" class="changes-box">
<iqser-icon-button
(action)="saveDigitalSignature()"
[disabled]="disabled"
[label]="'digital-signature-screen.action.save' | translate"
[type]="iconButtonTypes.primary"
icon="iqser:check"
></iqser-icon-button>
<div
(click)="removeDigitalSignature()"
*ngIf="digitalSignature"
class="all-caps-label cancel"
translate="digital-signature-screen.action.remove"
></div>
</div>
</div>
</div>
<div
(click)="removeDigitalSignature()"
*ngIf="digitalSignature"
class="all-caps-label cancel"
translate="digital-signature-screen.action.remove"
></div>
</div>
</div>
</section>
</div>

View File

@ -1,5 +1,6 @@
<iqser-page-header
(closeAction)="routerHistoryService.navigateToLastDossiersScreen()"
[hideResetButton]="true"
[pageLabel]="'dossier-templates.label' | translate"
[showCloseButton]="currentUser.isUser"
></iqser-page-header>

View File

@ -1,36 +1,17 @@
<section class="settings">
<div class="overlay-shadow"></div>
<iqser-page-header
(closeAction)="routerHistoryService.navigateToLastDossiersScreen()"
[pageLabel]="'configurations' | translate"
[showCloseButton]="true"
></iqser-page-header>
<redaction-admin-side-nav type="settings"></redaction-admin-side-nav>
<div>
<div class="page-header">
<div class="breadcrumb" translate="configurations"></div>
<div class="actions">
<iqser-circle-button
*ngIf="currentUser.isUser"
[tooltip]="'common.close' | translate"
class="ml-6"
icon="iqser:close"
redactionNavigateLastDossiersScreen
tooltipPosition="below"
></iqser-circle-button>
</div>
</div>
<div class="content-inner">
<div class="content-container">
<div class="dialog mb-0">
<redaction-general-config-form></redaction-general-config-form>
</div>
<div class="dialog mt-24 mb-0">
<redaction-system-preferences-form></redaction-system-preferences-form>
</div>
<div class="dialog mt-24">
<redaction-smtp-form></redaction-smtp-form>
</div>
</div>
</div>
<div class="content-container">
<div class="dialog mb-0">
<redaction-general-config-form></redaction-general-config-form>
</div>
</section>
<div class="dialog mt-24 mb-0">
<redaction-system-preferences-form></redaction-system-preferences-form>
</div>
<div class="dialog mt-24">
<redaction-smtp-form></redaction-smtp-form>
</div>
</div>

View File

@ -1,9 +1,10 @@
import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { AfterViewInit, Component, inject, ViewChild } from '@angular/core';
import { UserService } from '@services/user.service';
import { GeneralConfigFormComponent } from './general-config-form/general-config-form.component';
import { SmtpFormComponent } from './smtp-form/smtp-form.component';
import { BaseFormComponent } from '@iqser/common-ui';
import { SystemPreferencesFormComponent } from './system-preferences-form/system-preferences-form.component';
import { RouterHistoryService } from '../../../../services/router-history.service';
@Component({
selector: 'redaction-general-config-screen',
@ -11,17 +12,14 @@ import { SystemPreferencesFormComponent } from './system-preferences-form/system
styleUrls: ['./general-config-screen.component.scss'],
})
export class GeneralConfigScreenComponent extends BaseFormComponent implements AfterViewInit {
readonly currentUser = this._userService.currentUser;
readonly currentUser = inject(UserService).currentUser;
readonly routerHistoryService = inject(RouterHistoryService);
@ViewChild(GeneralConfigFormComponent) generalConfigFormComponent: GeneralConfigFormComponent;
@ViewChild(SystemPreferencesFormComponent) systemPreferencesFormComponent: SystemPreferencesFormComponent;
@ViewChild(SmtpFormComponent) smtpFormComponent: SmtpFormComponent;
children: BaseFormComponent[];
constructor(private readonly _userService: UserService) {
super();
}
get changed(): boolean {
for (const child of this.children) {
if (child.changed) {

View File

@ -52,7 +52,7 @@ export class SmtpFormComponent extends BaseFormComponent implements OnInit, OnDe
await firstValueFrom(this._smtpConfigService.updateSMTPConfiguration(this.form.getRawValue()));
this._initialConfiguration = this.form.getRawValue();
this._loadingService.stop();
this._loadData();
await this._loadData();
}
async testConnection() {

View File

@ -1,58 +1,29 @@
<section class="settings">
<div class="overlay-shadow"></div>
<iqser-page-header
(closeAction)="routerHistoryService.navigateToLastDossiersScreen()"
[buttonConfigs]="buttonConfigs"
[hideResetButton]="true"
[pageLabel]="'user-management' | translate"
[searchPlaceholder]="'user-listing.search' | translate"
[searchPosition]="searchPositions.withActions"
[showCloseButton]="true"
></iqser-page-header>
<redaction-admin-side-nav type="settings"></redaction-admin-side-nav>
<div>
<div class="page-header">
<div class="breadcrumb" translate="user-management"></div>
<div class="actions">
<iqser-input-with-action
[(value)]="searchService.searchValue"
[placeholder]="'user-listing.search' | translate"
></iqser-input-with-action>
<iqser-icon-button
(action)="openAddEditUserDialog($event)"
*ngIf="currentUser.isUserAdmin"
[label]="'user-listing.add-new' | translate"
[type]="iconButtonTypes.primary"
icon="iqser:plus"
></iqser-icon-button>
<iqser-circle-button
*ngIf="currentUser.isUser"
[tooltip]="'common.close' | translate"
class="ml-6"
icon="iqser:close"
redactionNavigateLastDossiersScreen
tooltipPosition="below"
></iqser-circle-button>
</div>
</div>
<div class="content-inner">
<div [class.extended]="collapsedDetails" class="content-container">
<iqser-table
[bulkActions]="bulkActions"
[itemSize]="80"
[noMatchText]="'user-listing.no-match.title' | translate"
[selectionEnabled]="true"
[tableColumnConfigs]="tableColumnConfigs"
emptyColumnWidth="1fr"
></iqser-table>
</div>
<div [class.collapsed]="collapsedDetails" class="right-container" iqserHasScrollbar>
<redaction-users-stats
(toggleCollapse)="collapsedDetails = !collapsedDetails"
[chartConfig]="chartConfig"
></redaction-users-stats>
</div>
</div>
<div class="content-inner">
<div [class.extended]="collapsedDetails" class="content-container">
<iqser-table
[bulkActions]="bulkActions"
[itemSize]="80"
[noMatchText]="'user-listing.no-match.title' | translate"
[selectionEnabled]="true"
[tableColumnConfigs]="tableColumnConfigs"
emptyColumnWidth="1fr"
></iqser-table>
</div>
</section>
<div [class.collapsed]="collapsedDetails" class="right-container" iqserHasScrollbar>
<redaction-users-stats (toggleCollapse)="collapsedDetails = !collapsedDetails" [chartConfig]="chartConfig"></redaction-users-stats>
</div>
</div>
<ng-template #bulkActions>
<iqser-circle-button

View File

@ -13,12 +13,6 @@
}
}
.page-header .actions {
iqser-input-with-action:not(:last-child) {
margin-right: 16px;
}
iqser-icon-button:not(:last-child) {
margin-right: 6px;
}
::ng-deep .page-header .actions > *:not(:last-child) {
margin-right: 6px;
}

View File

@ -1,10 +1,11 @@
import { Component, forwardRef, Injector, OnInit } from '@angular/core';
import { Component, forwardRef, inject, Injector, OnInit } from '@angular/core';
import { UserService } from '@services/user.service';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { TranslateService } from '@ngx-translate/core';
import { DonutChartConfig, User, UserTypes } from '@red/domain';
import { TranslateChartService } from '@services/translate-chart.service';
import {
ButtonConfig,
CircleButtonTypes,
DefaultListingServicesTmp,
EntitiesService,
@ -12,6 +13,7 @@ import {
ListingComponent,
LoadingService,
NestedFilter,
SearchPositions,
TableColumnConfig,
} from '@iqser/common-ui';
import { firstValueFrom, Observable } from 'rxjs';
@ -19,6 +21,14 @@ import { map } from 'rxjs/operators';
import { rolesTranslations } from '@translations/roles-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { userTypeChecker, userTypeFilters } from '../../../../utils';
import { RouterHistoryService } from '../../../../services/router-history.service';
function configToFilter({ key, label }: DonutChartConfig) {
return new NestedFilter({
id: key,
label,
});
}
@Component({
templateUrl: './user-listing-screen.component.html',
@ -33,11 +43,13 @@ import { userTypeChecker, userTypeFilters } from '../../../../utils';
],
})
export class UserListingScreenComponent extends ListingComponent<User> implements OnInit {
readonly routerHistoryService = inject(RouterHistoryService);
readonly searchPositions = SearchPositions;
readonly translations = rolesTranslations;
readonly iconButtonTypes = IconButtonTypes;
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = this.userService.currentUser;
readonly canDeleteSelected$ = this._canDeleteSelected$;
readonly canDeleteSelected$ = this.#canDeleteSelected$;
readonly tableHeaderLabel = _('user-listing.table-header.title');
readonly tableColumnConfigs: TableColumnConfig<User>[] = [
{ label: _('user-listing.table-col-names.name'), width: '2fr' },
@ -45,6 +57,15 @@ export class UserListingScreenComponent extends ListingComponent<User> implement
{ label: _('user-listing.table-col-names.active'), class: 'flex-center' },
{ label: _('user-listing.table-col-names.roles') },
];
readonly buttonConfigs: readonly ButtonConfig[] = [
{
label: _('user-listing.add-new'),
action: (): void => this.openAddEditUserDialog(),
type: IconButtonTypes.primary,
icon: 'iqser:plus',
disabled$: this.userService.currentUser$.pipe(map(user => !user.isAdmin)),
},
];
collapsedDetails = false;
chartConfig: DonutChartConfig[] = [];
@ -59,7 +80,7 @@ export class UserListingScreenComponent extends ListingComponent<User> implement
super(_injector);
}
private get _canDeleteSelected$(): Observable<boolean> {
get #canDeleteSelected$(): Observable<boolean> {
const entities$ = this.listingService.selectedEntities$;
return entities$.pipe(map(all => !all.find(u => u.id === this.currentUser.id)));
}
@ -69,18 +90,18 @@ export class UserListingScreenComponent extends ListingComponent<User> implement
}
async ngOnInit() {
await this._loadData();
await this.#loadData();
}
openAddEditUserDialog($event: MouseEvent, user?: User) {
this._dialogService.openDialog('addEditUser', $event, user, async () => {
await this._loadData();
openAddEditUserDialog(event?: MouseEvent, user?: User) {
this._dialogService.openDialog('addEditUser', event, user, async () => {
await this.#loadData();
});
}
openDeleteUsersDialog(userIds: string[], $event?: MouseEvent) {
this._dialogService.deleteUsers(userIds, $event, async () => {
await this._loadData();
await this.#loadData();
});
}
@ -96,20 +117,20 @@ export class UserListingScreenComponent extends ListingComponent<User> implement
this._loadingService.start();
const requestBody = { ...user, roles: user.isActive ? [] : ['RED_USER'] };
await firstValueFrom(this.userService.updateProfile(requestBody, user.id));
await this._loadData();
await this.#loadData();
}
bulkDelete() {
this.openDeleteUsersDialog(this.listingService.selectedIds as string[]);
}
private async _loadData() {
async #loadData() {
await firstValueFrom(this.userService.loadAll());
this._computeStats();
this.#computeStats();
this._loadingService.stop();
}
private _computeStats() {
#computeStats() {
this.chartConfig = this._translateChartService.translateRoles(
UserTypes.map(type => ({
value: this.allEntities.filter(userTypeFilters[type]).length,
@ -119,24 +140,15 @@ export class UserListingScreenComponent extends ListingComponent<User> implement
})).filter(type => type.value > 0),
);
this._computeAllFilters();
this.#computeAllFilters();
}
private _computeAllFilters() {
const roleFilters = this.chartConfig.map(
config =>
new NestedFilter({
id: config.key,
label: config.label,
}),
);
this.filterService.addFilterGroups([
{
slug: 'roleFilters',
filters: roleFilters,
checker: userTypeChecker,
},
]);
#computeAllFilters() {
const roleFiltersGroup = {
slug: 'roleFilters',
filters: this.chartConfig.map(configToFilter),
checker: userTypeChecker,
};
this.filterService.addFilterGroups([roleFiltersGroup]);
}
}

View File

@ -12,6 +12,10 @@
min-width: fit-content;
}
.content-inner {
position: absolute;
}
.content-container {
position: relative;
}

View File

@ -4,6 +4,10 @@ import { ErrorHandler, Injectable } from '@angular/core';
export class GlobalErrorHandler extends ErrorHandler {
handleError(error: Error): void {
const chunkFailedMessage = /Loading chunk [\d]+ failed/;
console.write(error);
if (error.message.includes('An error happened during access validation')) {
console.log('User is not authorized to access this page');
}
if (chunkFailedMessage.test(error?.message)) {
window.location.reload();

View File

@ -1,7 +1,7 @@
{
"ADMIN_CONTACT_NAME": null,
"ADMIN_CONTACT_URL": null,
"API_URL": "https://dev-08.iqser.cloud/redaction-gateway-v1",
"API_URL": "https://dev-04.iqser.cloud/redaction-gateway-v1",
"APP_NAME": "RedactManager",
"AUTO_READ_TIME": 3,
"BACKEND_APP_VERSION": "4.4.40",
@ -11,7 +11,7 @@
"MAX_RETRIES_ON_SERVER_ERROR": 3,
"OAUTH_CLIENT_ID": "redaction",
"OAUTH_IDP_HINT": null,
"OAUTH_URL": "https://dev-08.iqser.cloud/auth/realms/redaction",
"OAUTH_URL": "https://dev-04.iqser.cloud/auth/realms/redaction",
"RECENT_PERIOD_IN_HOURS": 24,
"SELECTION_MODE": "structural",
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview"

@ -1 +1 @@
Subproject commit 7835b1caf0c28df32abf6a8751a7fcf9d2a9716f
Subproject commit d3fdef1b4cdb16730026633f83c3cd28f0d3358a