RED-3765: wip license view

This commit is contained in:
Dan Percic 2022-05-05 17:20:45 +03:00
parent ef575520a2
commit f9021361b0
12 changed files with 310 additions and 101 deletions

View File

@ -33,7 +33,6 @@ import { TrashScreenComponent } from './screens/trash/trash-screen.component';
import { AuditService } from './services/audit.service';
import { DigitalSignatureService } from './services/digital-signature.service';
import { BaseAdminScreenComponent } from './base-admin-screen/base-admin-screen.component';
import { LicenseReportService } from './services/licence-report.service';
import { RulesService } from './services/rules.service';
import { SmtpConfigService } from './services/smtp-config.service';
import { UploadDictionaryDialogComponent } from './dialogs/upload-dictionary-dialog/upload-dictionary-dialog.component';
@ -49,6 +48,7 @@ import { ConfirmDeleteDossierStateDialogComponent } from './dialogs/confirm-dele
import { TrashTableItemComponent } from './screens/trash/trash-table-item/trash-table-item.component';
import { BaseEntityScreenComponent } from './base-entity-screen/base-entity-screen.component';
import { CloneDossierTemplateDialogComponent } from './dialogs/clone-dossier-template-dialog/clone-dossier-template-dialog.component';
import { SelectLicenceComponent } from './screens/license-information/select-licence/select-licence.component';
const dialogs = [
AddEditDossierTemplateDialogComponent,
@ -103,8 +103,9 @@ const components = [
AddEditDossierStateDialogComponent,
ConfirmDeleteDossierStateDialogComponent,
TrashTableItemComponent,
SelectLicenceComponent,
],
providers: [AdminDialogService, AuditService, DigitalSignatureService, LicenseReportService, RulesService, SmtpConfigService],
providers: [AdminDialogService, AuditService, DigitalSignatureService, RulesService, SmtpConfigService],
imports: [CommonModule, SharedModule, AdminRoutingModule, SharedAdminModule, NgxChartsModule, ColorPickerModule, A11yModule],
})
export class AdminModule {}

View File

@ -0,0 +1,15 @@
import { Color, ScaleType } from '@swimlane/ngx-charts';
export const ComboBarScheme: Color = {
name: 'Combo bar scheme',
selectable: true,
group: ScaleType.Ordinal,
domain: ['#0389ec'],
};
export const LineChartScheme: Color = {
name: 'Line chart scheme',
selectable: true,
group: ScaleType.Ordinal,
domain: ['#dd4d50', '#5ce594', '#0389ec'],
};

View File

@ -0,0 +1,69 @@
export interface ILicenseFeature {
readonly name: string;
readonly type: string;
readonly value: string;
}
export interface ILicense {
readonly id: string;
readonly name: string;
readonly product: string;
readonly licensedTo: string;
readonly licensedToEmail: string;
readonly validFrom: string;
readonly validUntil: string;
readonly features: readonly ILicenseFeature[];
}
export interface ILicenses {
readonly activeLicense: string;
readonly licenses: readonly ILicense[];
}
export const LICENSE_DATA: ILicenses = {
activeLicense: 'guid-1',
licenses: [
{
id: 'guid-1',
name: '1 Year comulative (2022)',
product: 'RedactManager',
licensedTo: 'Customer company name 1',
licensedToEmail: 'customer@example.com',
validFrom: '2022-01-01T00:00:00.000Z',
validUntil: '2022-12-31T23:59:59.999Z',
features: [
{
name: 'pdftron',
type: 'STRING',
value: 'base64 encoded pdftron webviewer license key',
},
{
name: 'processingPages',
type: 'NUMBER',
value: '200000',
},
],
},
{
id: 'guid-2',
name: '2 Year comulative (2021)',
product: 'RedactManager',
licensedTo: 'Customer company name 2',
licensedToEmail: 'customer@example.com',
validFrom: '2021-01-01T00:00:00.000Z',
validUntil: '2021-12-31T23:59:59.999Z',
features: [
{
name: 'pdftron',
type: 'STRING',
value: 'base64 encoded pdftron webviewer license key',
},
{
name: 'processingPages',
type: 'NUMBER',
value: '100000',
},
],
},
],
};

View File

@ -38,16 +38,23 @@
<div class="section-title all-caps-label" translate="license-info-screen.licensing-details"></div>
<div class="row">
<div class="flex align-center" translate="license-info-screen.license-title"></div>
<div>
<redaction-select-licence (valueChanges)="licenceChanged($event)"></redaction-select-licence>
</div>
</div>
<div class="row">
<div translate="license-info-screen.licensed-to"></div>
<div>{{ configService.values.LICENSE_CUSTOMER || '-' }}</div>
<div>{{ selectedLicense.licensedTo || '-' }}</div>
</div>
<div class="row">
<div translate="license-info-screen.licensing-period"></div>
<div>
{{ configService.values.LICENSE_START || '-' }} /
{{ configService.values.LICENSE_END || '-' }}
{{ (selectedLicense.validFrom | date: 'dd-MM-YYYY') || '-' }} /
{{ (selectedLicense.validUntil | date: 'dd-MM-YYYY') || '-' }}
</div>
</div>

View File

@ -5,18 +5,38 @@ import { ButtonConfig, IconButtonTypes, LoadingService } from '@iqser/common-ui'
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { UserService } from '@services/user.service';
import { RouterHistoryService } from '@services/router-history.service';
import { LicenseReportService } from '../../services/licence-report.service';
import { LicenseService } from '../../services/licence-report.service';
import { ILicenseReport } from '@red/domain';
import { Color, ScaleType } from '@swimlane/ngx-charts';
import { firstValueFrom } from 'rxjs';
import dayjs from 'dayjs';
import { ComboBarScheme, LineChartScheme } from './constants';
import { ILicense } from './licence';
const monthNames = dayjs.monthsShort();
function toDate(month: number, year: number) {
return dayjs(`01-${month}-${year}`, 'DD-M-YYYY').toDate();
}
interface IMonthYear {
readonly month: number;
readonly year: number;
}
interface IDateRange {
readonly startDate: IMonthYear;
readonly endDate: IMonthYear;
}
@Component({
selector: 'redaction-license-information-screen',
templateUrl: './license-information-screen.component.html',
styleUrls: ['./license-information-screen.component.scss'],
providers: [LicenseService],
})
export class LicenseInformationScreenComponent implements OnInit {
readonly lineChartScheme = LineChartScheme;
readonly comboBarScheme = ComboBarScheme;
readonly currentYear = new Date().getFullYear();
readonly currentUser = this._userService.currentUser;
readonly buttonConfigs: readonly ButtonConfig[] = [
{
@ -29,40 +49,35 @@ export class LicenseInformationScreenComponent implements OnInit {
currentInfo: ILicenseReport = {};
totalInfo: ILicenseReport = {};
unlicensedInfo: ILicenseReport = {};
totalLicensedNumberOfPages = 0;
analysisPercentageOfLicense = 100;
barChart: any[];
lineChartSeries: any[] = [];
lineChartScheme: Color = {
name: 'Line chart scheme',
selectable: true,
group: ScaleType.Ordinal,
domain: ['#dd4d50', '#5ce594', '#0389ec'],
};
comboBarScheme: Color = {
name: 'Combo bar scheme',
selectable: true,
group: ScaleType.Ordinal,
domain: ['#0389ec'],
};
selectedLicense = this.licenseService.getActiveLicense();
totalLicensedNumberOfPages = this.#processingPages;
constructor(
readonly configService: ConfigService,
readonly licenseService: LicenseService,
private readonly _userService: UserService,
private readonly _loadingService: LoadingService,
readonly routerHistoryService: RouterHistoryService,
private readonly _translateService: TranslateService,
private readonly _licenseReportService: LicenseReportService,
) {
_loadingService.start();
}
get currentYear(): number {
return new Date().getFullYear();
get #processingPages() {
const processingPagesFeature = this.selectedLicense.features.find(f => f.name === 'processingPages');
return Number(processingPagesFeature.value ?? '0');
}
async ngOnInit() {
this.totalLicensedNumberOfPages = this.configService.values.LICENSE_PAGE_COUNT || 0;
ngOnInit() {
return this.loadLicenceData();
}
async loadLicenceData() {
// const startDate = dayjs(this.selectedLicense.validFrom, 'DD-MM-YYYY');
// const endDate = dayjs(this.selectedLicense.validUntil, 'DD-MM-YYYY');
const startDate = dayjs(this.configService.values.LICENSE_START, 'DD-MM-YYYY');
const endDate = dayjs(this.configService.values.LICENSE_END, 'DD-MM-YYYY');
@ -72,30 +87,23 @@ export class LicenseInformationScreenComponent implements OnInit {
startDate: startDate.toDate(),
endDate: endDate.toDate(),
};
const promises = [
firstValueFrom(this._licenseReportService.licenseReport(currentConfig)),
firstValueFrom(this._licenseReportService.licenseReport({})),
];
const reports: Promise<ILicenseReport>[] = [this.licenseService.getReport(currentConfig), this.licenseService.getReport({})];
if (endDate.isBefore(dayjs())) {
const unlicensedConfig = {
startDate: endDate.toDate(),
};
promises.push(firstValueFrom(this._licenseReportService.licenseReport(unlicensedConfig)));
reports.push(this.licenseService.getReport(unlicensedConfig));
}
Promise.all(promises).then(reports => {
[this.currentInfo, this.totalInfo, this.unlicensedInfo] = reports;
this._loadingService.stop();
this.analysisPercentageOfLicense =
this.totalLicensedNumberOfPages > 0
? (this.currentInfo.numberOfAnalyzedPages / this.totalLicensedNumberOfPages) * 100
: 100;
});
[this.currentInfo, this.totalInfo, this.unlicensedInfo] = await Promise.all(reports);
this._loadingService.stop();
this.analysisPercentageOfLicense =
this.totalLicensedNumberOfPages > 0 ? (this.currentInfo.numberOfAnalyzedPages / this.totalLicensedNumberOfPages) * 100 : 100;
}
sendMail(): void {
const licenseCustomer = this.configService.values.LICENSE_CUSTOMER;
const licenseCustomer = this.selectedLicense.licensedTo;
const subject = this._translateService.instant('license-info-screen.email.title', {
licenseCustomer,
});
@ -108,77 +116,97 @@ export class LicenseInformationScreenComponent implements OnInit {
pages: this.totalLicensedNumberOfPages,
}),
].join(lineBreak);
window.location.href = `mailto:${this.configService.values.LICENSE_EMAIL}?subject=${subject}&body=${body}`;
window.location.href = `mailto:${this.selectedLicense.licensedToEmail}?subject=${subject}&body=${body}`;
}
licenceChanged(license: ILicense) {
this.selectedLicense = license;
this.totalLicensedNumberOfPages = this.#processingPages;
return this.loadLicenceData();
}
private async _setMonthlyStats(startDate: dayjs.Dayjs, endDate: dayjs.Dayjs) {
const [startMonth, startYear] = [startDate.month(), startDate.year()];
const [endMonth, endYear] = [endDate.month(), endDate.year()];
const startMonth: number = startDate.month();
const startYear: number = startDate.year();
let m: number = startMonth;
let y: number = startYear;
const totalLicensedSeries = [];
const cumulativePagesSeries = [];
const promises = [];
while (m <= endMonth && y <= endYear) {
totalLicensedSeries.push({
name: `${dayjs.monthsShort()[m]} ${y}`,
value: this.totalLicensedNumberOfPages,
});
let nm = m + 1;
let ny = y;
if (nm === 12) {
nm = 0;
ny++;
}
promises.push(
firstValueFrom(
this._licenseReportService.licenseReport({
startDate: dayjs(`01-${m + 1}-${y}`, 'DD-M-YYYY').toDate(),
endDate: dayjs(`01-${nm + 1}-${ny}`, 'DD-M-YYYY').toDate(),
}),
),
);
y = ny;
m = nm;
}
const reports = await Promise.all(promises);
m = startMonth;
y = startYear;
let cumulativePages = 0;
this.barChart = [];
for (const report of reports) {
cumulativePages += report.numberOfAnalyzedPages;
this.barChart.push({
name: `${dayjs.monthsShort()[m]} ${y}`,
value: report.numberOfAnalyzedPages,
});
cumulativePagesSeries.push({
name: `${dayjs.monthsShort()[m]} ${y}`,
value: cumulativePages,
});
m++;
if (m === 12) {
m = 0;
y++;
}
}
const dateRanges = this.#generateDateRanges(startMonth, startYear, endDate.month(), endDate.year());
const reports = await this.#getReports(dateRanges);
this.lineChartSeries = [
{
name: this._translateService.instant('license-info-screen.chart.licensed-total'),
series: totalLicensedSeries,
series: this.#totalLicensedPagesSeries(dateRanges),
},
{
name: this._translateService.instant('license-info-screen.chart.cumulative'),
series: cumulativePagesSeries,
series: this.#setBarChartAndGetCumulativePageSeries(startMonth, startYear, reports),
},
];
}
#setBarChartAndGetCumulativePageSeries(month: number, year: number, reports: ILicenseReport[]) {
let cumulativePages = 0;
const cumulativePagesSeries = [];
this.barChart = [];
for (const report of reports) {
cumulativePages += report.numberOfAnalyzedPages;
const name = `${monthNames[month]} ${year}`;
this.barChart.push({
name,
value: report.numberOfAnalyzedPages,
});
cumulativePagesSeries.push({
name,
value: cumulativePages,
});
month++;
if (month === 12) {
month = 0;
year++;
}
}
return cumulativePagesSeries;
}
#getReports(dateRanges: IDateRange[]) {
const reports = dateRanges.map(dateRange => {
const startDate = toDate(dateRange.startDate.month + 1, dateRange.startDate.year);
const endDate = toDate(dateRange.endDate.month + 1, dateRange.endDate.year);
return this.licenseService.getReport({ startDate, endDate });
});
return Promise.all(reports);
}
#totalLicensedPagesSeries(dateRanges: IDateRange[]) {
return dateRanges.map(dateRange => ({
name: `${monthNames[dateRange.startDate.month]} ${dateRange.startDate.year}`,
value: this.totalLicensedNumberOfPages,
}));
}
#generateDateRanges(month: number, year: number, endMonth: number, endYear: number) {
const dates: IDateRange[] = [];
while (month <= endMonth && year <= endYear) {
let nextMonth = month + 1;
let nextYear = year;
if (nextMonth === 12) {
nextMonth = 0;
nextYear++;
}
dates.push({ startDate: { month, year }, endDate: { month: nextMonth, year: nextYear } });
year = nextYear;
month = nextMonth;
}
return dates;
}
}

View File

@ -0,0 +1,21 @@
<div class="iqser-input-group w-400">
<mat-select (valueChange)="valueChanges.emit($event)" [(ngModel)]="value">
<mat-select-trigger>
<ng-container *ngTemplateOutlet="licenseInfo; context: { license: value }"></ng-container>
</mat-select-trigger>
<mat-option *ngFor="let license of licenseService.licenseData.licenses" [value]="license">
<ng-container *ngTemplateOutlet="licenseInfo; context: { license: this.license }"></ng-container>
</mat-option>
</mat-select>
</div>
<ng-template #licenseInfo let-license="license">
<div class="flex space-between align-center">
<span>{{ license.name }}</span>
<div class="mr-10 flex align-center">
<div [class.green]="license.id === licenseService.activeLicenseId" class="dot mr-4"></div>
<span class="small-label">{{ getStatus(license.id) | translate | uppercase }}</span>
</div>
</div>
</ng-template>

View File

@ -0,0 +1,15 @@
.green {
background: var(--iqser-green-2);
}
.space-between {
justify-content: space-between;
}
.dot {
position: relative;
}
.small-label {
font-weight: 600;
}

View File

@ -0,0 +1,26 @@
import { Component, EventEmitter, Output } from '@angular/core';
import { LicenseService } from '../../../services/licence-report.service';
import { ILicense } from '../licence';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
const translations = {
active: _('license-info-screen.status.active'),
inactive: _('license-info-screen.status.inactive'),
} as const;
@Component({
selector: 'redaction-select-licence',
templateUrl: './select-licence.component.html',
styleUrls: ['./select-licence.component.scss'],
})
export class SelectLicenceComponent {
@Output() readonly valueChanges = new EventEmitter<ILicense>();
value = this.licenseService.getActiveLicense();
constructor(readonly licenseService: LicenseService) {}
getStatus(id) {
return id === this.licenseService.activeLicenseId ? translations.active : translations.inactive;
}
}

View File

@ -1,15 +1,24 @@
import { Injectable, Injector } from '@angular/core';
import { GenericService, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
import { ILicenseReport, ILicenseReportRequest } from '@red/domain';
import { firstValueFrom } from 'rxjs';
import { LICENSE_DATA } from '../screens/license-information/licence';
@Injectable()
export class LicenseReportService extends GenericService<ILicenseReport> {
export class LicenseService extends GenericService<ILicenseReport> {
readonly licenseData = LICENSE_DATA;
readonly activeLicenseId = this.licenseData.activeLicense;
constructor(protected readonly _injector: Injector) {
super(_injector, 'report');
}
getActiveLicense() {
return this.licenseData.licenses.find(license => license.id === this.activeLicenseId);
}
@Validate()
licenseReport(@RequiredParam() body: ILicenseReportRequest, limit?: number, offset?: number) {
getReport$(@RequiredParam() body: ILicenseReportRequest, limit?: number, offset?: number) {
const queryParams: QueryParam[] = [];
if (limit) {
queryParams.push({ key: 'limit', value: limit });
@ -21,4 +30,8 @@ export class LicenseReportService extends GenericService<ILicenseReport> {
return this._post(body, `${this._defaultModelPath}/license`, queryParams);
}
getReport(body: ILicenseReportRequest, limit?: number, offset?: number) {
return firstValueFrom(this.getReport$(body, limit, offset));
}
}

View File

@ -1532,11 +1532,16 @@
},
"end-user-license-text": "Die Nutzung dieses Produkts unterliegt den Bedingungen der Endbenutzer-Lizenzvereinbarung für den RedactManager, sofern darin nichts anderweitig festgelegt.",
"end-user-license-title": "Endbenutzer-Lizenzvereinbarung",
"license-title": "",
"licensed-page-count": "Anzahl der lizenzierten Seiten",
"licensed-to": "Lizenziert für",
"licensing-details": "Lizenzdetails",
"licensing-period": "Laufzeit der Lizenz",
"ocr-analyzed-pages": "Mit OCR konvertierte Seiten",
"status": {
"active": "Aktiv",
"inactive": ""
},
"total-analyzed": "Seit {date} insgesamt analysierte Seiten",
"unlicensed-analyzed": "Über Lizenz hinaus analysierte Seiten",
"usage-details": "Nutzungsdetails"

View File

@ -1532,11 +1532,16 @@
},
"end-user-license-text": "The use of this product is subject to the terms of the Redaction End User Agreement, unless otherwise specified therein.",
"end-user-license-title": "End User License Agreement",
"license-title": "License Title",
"licensed-page-count": "Number of licensed pages",
"licensed-to": "Licensed to",
"licensing-details": "Licensing Details",
"licensing-period": "Licensing Period",
"ocr-analyzed-pages": "OCR Analyzed Pages",
"status": {
"active": "Active",
"inactive": "Inactive"
},
"total-analyzed": "Total Analyzed Pages Since {date}",
"unlicensed-analyzed": "Unlicensed Analyzed Pages",
"usage-details": "Usage Details"

View File

@ -98,3 +98,7 @@
}
}
}
.align-center {
align-items: center;
}