oip-common 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/ng-package.json +8 -0
- package/package.json +16 -0
- package/src/api/FolderModule.ts +124 -0
- package/src/api/Menu.ts +134 -0
- package/src/api/Module.ts +92 -0
- package/src/api/Security.ts +40 -0
- package/src/api/Service.ts +57 -0
- package/src/api/data-contracts.ts +186 -0
- package/src/api/http-client.ts +276 -0
- package/src/assets/demo/code.scss +17 -0
- package/src/assets/demo/data/products-small.json +124 -0
- package/src/assets/demo/demo.scss +2 -0
- package/src/assets/demo/flags/flags.scss +740 -0
- package/src/assets/demo/flags/flags_responsive.png +0 -0
- package/src/assets/demo/images/access/asset-access.svg +46 -0
- package/src/assets/demo/images/blocks/hero/hero-1.png +0 -0
- package/src/assets/demo/images/blocks/logos/hyper.svg +3 -0
- package/src/assets/demo/images/error/asset-error.svg +74 -0
- package/src/assets/demo/images/flag/flag_placeholder.png +0 -0
- package/src/assets/demo/images/product/bamboo-watch.jpg +0 -0
- package/src/assets/demo/images/product/black-watch.jpg +0 -0
- package/src/assets/demo/images/product/blue-band.jpg +0 -0
- package/src/assets/demo/images/product/blue-t-shirt.jpg +0 -0
- package/src/assets/demo/images/product/bracelet.jpg +0 -0
- package/src/assets/demo/images/product/brown-purse.jpg +0 -0
- package/src/assets/demo/images/product/chakra-bracelet.jpg +0 -0
- package/src/assets/demo/images/product/galaxy-earrings.jpg +0 -0
- package/src/assets/demo/images/product/game-controller.jpg +0 -0
- package/src/assets/demo/images/product/gaming-set.jpg +0 -0
- package/src/assets/demo/images/product/gold-phone-case.jpg +0 -0
- package/src/assets/demo/images/product/green-earbuds.jpg +0 -0
- package/src/assets/demo/images/product/green-t-shirt.jpg +0 -0
- package/src/assets/demo/images/product/grey-t-shirt.jpg +0 -0
- package/src/assets/demo/images/product/headphones.jpg +0 -0
- package/src/assets/demo/images/product/light-green-t-shirt.jpg +0 -0
- package/src/assets/demo/images/product/lime-band.jpg +0 -0
- package/src/assets/demo/images/product/mini-speakers.jpg +0 -0
- package/src/assets/demo/images/product/painted-phone-case.jpg +0 -0
- package/src/assets/demo/images/product/pink-band.jpg +0 -0
- package/src/assets/demo/images/product/pink-purse.jpg +0 -0
- package/src/assets/demo/images/product/product-placeholder.svg +10 -0
- package/src/assets/demo/images/product/purple-band.jpg +0 -0
- package/src/assets/demo/images/product/purple-gemstone-necklace.jpg +0 -0
- package/src/assets/demo/images/product/purple-t-shirt.jpg +0 -0
- package/src/assets/demo/images/product/shoes.jpg +0 -0
- package/src/assets/demo/images/product/sneakers.jpg +0 -0
- package/src/assets/demo/images/product/teal-t-shirt.jpg +0 -0
- package/src/assets/demo/images/product/yellow-earbuds.jpg +0 -0
- package/src/assets/demo/images/product/yoga-mat.jpg +0 -0
- package/src/assets/demo/images/product/yoga-set.jpg +0 -0
- package/src/assets/favicon.svg +14 -0
- package/src/assets/i18n/app-modules.en.json +23 -0
- package/src/assets/i18n/app-modules.ru.json +23 -0
- package/src/assets/i18n/config.en.json +14 -0
- package/src/assets/i18n/config.ru.json +14 -0
- package/src/assets/layout/_core.scss +24 -0
- package/src/assets/layout/_footer.scss +8 -0
- package/src/assets/layout/_logo.scss +7 -0
- package/src/assets/layout/_main.scss +12 -0
- package/src/assets/layout/_menu.scss +159 -0
- package/src/assets/layout/_mixins.scss +15 -0
- package/src/assets/layout/_preloading.scss +49 -0
- package/src/assets/layout/_responsive.scss +108 -0
- package/src/assets/layout/_topbar.scss +168 -0
- package/src/assets/layout/_typography.scss +68 -0
- package/src/assets/layout/_utils.scss +25 -0
- package/src/assets/layout/layout.scss +14 -0
- package/src/assets/layout/variables/_common.scss +20 -0
- package/src/assets/layout/variables/_dark.scss +5 -0
- package/src/assets/layout/variables/_light.scss +5 -0
- package/src/assets/oip-common.scss +5 -0
- package/src/assets/tailwind.css +3 -0
- package/src/components/app-configurator.component.ts +491 -0
- package/src/components/app-floating-configurator.component.ts +47 -0
- package/src/components/app-modules.component.ts +144 -0
- package/src/components/app.layout.component.ts +130 -0
- package/src/components/auth/access/access.component.ts +42 -0
- package/src/components/auth/error/error.component.ts +42 -0
- package/src/components/auth/login/login.component.ts +120 -0
- package/src/components/auth/unauthorized/unauthorized.component.ts +51 -0
- package/src/components/base-module.component.ts +258 -0
- package/src/components/config.component.ts +131 -0
- package/src/components/db-migration/db-migration.component.ts +162 -0
- package/src/components/db-migration.component.ts +154 -0
- package/src/components/footer.component.ts +17 -0
- package/src/components/logo.component.ts +34 -0
- package/src/components/menu/menu-item-create-dialog.component.ts +119 -0
- package/src/components/menu/menu-item-edit-dialog.component.ts +123 -0
- package/src/components/menu/menu-item.component.ts +295 -0
- package/src/components/menu/menu.component.ts +85 -0
- package/src/components/notfound.component.ts +31 -0
- package/src/components/profile.component.ts +43 -0
- package/src/components/security.component.ts +102 -0
- package/src/components/sidebar.component.ts +12 -0
- package/src/components/top-bar.component.ts +147 -0
- package/src/dtos/context-menu-item.dto.ts +23 -0
- package/src/dtos/edit-module-instance.dto.ts +8 -0
- package/src/dtos/no-settings.dto.ts +4 -0
- package/src/dtos/put-security.dto.ts +6 -0
- package/src/dtos/security.dto.ts +6 -0
- package/src/dtos/top-bar.dto.ts +13 -0
- package/src/events/menu-change.event.ts +23 -0
- package/src/helpers/date.helper.ts +94 -0
- package/src/intercepts/i18n-intercept.service.ts +13 -0
- package/src/modules/http-loader.factory.ts +40 -0
- package/src/modules/secure.pipe.ts +19 -0
- package/src/public-api.ts +42 -0
- package/src/services/app-title.service.ts +22 -0
- package/src/services/app.layout.service.ts +236 -0
- package/src/services/app.menu.service.ts +64 -0
- package/src/services/auth.service.ts +58 -0
- package/src/services/base-data.service.ts +74 -0
- package/src/services/l10n.service.ts +71 -0
- package/src/services/msg.service.ts +76 -0
- package/src/services/security-data.service.ts +19 -0
- package/src/services/security-storage.service.ts +21 -0
- package/src/services/security.service.ts +116 -0
- package/src/services/top-bar.service.ts +44 -0
- package/src/services/user.service.ts +77 -0
- package/src/test.ts +11 -0
- package/src/user-api/UserProfile.ts +85 -0
- package/src/user-api/data-contracts.ts +42 -0
- package/src/user-api/http-client.ts +251 -0
- package/tsconfig.lib.json +12 -0
- package/tsconfig.lib.prod.json +10 -0
- package/tsconfig.spec.json +9 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Component, EventEmitter, inject, Input, Output } from '@angular/core';
|
|
2
|
+
import { ButtonModule } from 'primeng/button';
|
|
3
|
+
import { DialogModule } from 'primeng/dialog';
|
|
4
|
+
import { InputTextModule } from 'primeng/inputtext';
|
|
5
|
+
import { FormsModule } from '@angular/forms';
|
|
6
|
+
import { MenuService, SecurityDataService } from 'oip-common';
|
|
7
|
+
import { TranslatePipe } from '@ngx-translate/core';
|
|
8
|
+
import { EditModuleInstanceDto } from '../../dtos/edit-module-instance.dto';
|
|
9
|
+
import { MultiSelectModule } from 'primeng/multiselect';
|
|
10
|
+
|
|
11
|
+
@Component({
|
|
12
|
+
imports: [ButtonModule, DialogModule, InputTextModule, FormsModule, TranslatePipe, MultiSelectModule],
|
|
13
|
+
selector: 'menu-item-edit-dialog',
|
|
14
|
+
standalone: true,
|
|
15
|
+
template: `
|
|
16
|
+
<p-dialog
|
|
17
|
+
header="{{ 'menuItemEditDialogComponent.header' | translate }}"
|
|
18
|
+
[modal]="true"
|
|
19
|
+
[style]="{ width: '40rem' }"
|
|
20
|
+
[(visible)]="visible">
|
|
21
|
+
<div class="flex items-center gap-4 mb-4 mt-1">
|
|
22
|
+
<label class="font-semibold w-1/3" for="oip-menu-item-edit-dialog-menu-input">
|
|
23
|
+
{{ 'menuItemEditDialogComponent.label' | translate }}
|
|
24
|
+
</label>
|
|
25
|
+
<input
|
|
26
|
+
autocomplete="off"
|
|
27
|
+
class="flex-auto"
|
|
28
|
+
id="oip-menu-item-edit-dialog-menu-input"
|
|
29
|
+
pInputText
|
|
30
|
+
[(ngModel)]="item.label" />
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="flex items-center gap-4 mb-4">
|
|
34
|
+
<label class="font-semibold w-1/3" for="oip-menu-item-edit-dialog-icon">
|
|
35
|
+
{{ 'menuItemEditDialogComponent.icon' | translate }}
|
|
36
|
+
</label>
|
|
37
|
+
<i class="{{ item.icon }}"></i>
|
|
38
|
+
<input class="flex-auto" id="oip-menu-item-edit-dialog-icon" pInputText [(ngModel)]="item.icon" />
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="flex items-center gap-4 mb-4">
|
|
42
|
+
<label class="font-semibold w-1/3" for="security">
|
|
43
|
+
{{ 'menuItemEditDialogComponent.security' | translate }}
|
|
44
|
+
</label>
|
|
45
|
+
<p-multiSelect
|
|
46
|
+
appendTo="body"
|
|
47
|
+
class="flex-auto"
|
|
48
|
+
id="oip-menu-item-edit-dialog-roles-multi-select"
|
|
49
|
+
placeholder="Select roles"
|
|
50
|
+
[maxSelectedLabels]="10"
|
|
51
|
+
[options]="roles"
|
|
52
|
+
[(ngModel)]="item.viewRoles" />
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div class="flex justify-end gap-2">
|
|
56
|
+
<p-button
|
|
57
|
+
id="oip-menu-item-edit-dialog-cancel-edit-button"
|
|
58
|
+
label="{{ 'menuItemEditDialogComponent.cancel' | translate }}"
|
|
59
|
+
severity="secondary"
|
|
60
|
+
(click)="changeVisible()"
|
|
61
|
+
(keydown)="changeVisible()" />
|
|
62
|
+
<p-button
|
|
63
|
+
id="oip-menu-item-edit-dialog-save-edit-button"
|
|
64
|
+
label="{{ 'menuItemEditDialogComponent.save' | translate }}"
|
|
65
|
+
(click)="save()"
|
|
66
|
+
(keydown)="save()" />
|
|
67
|
+
</div>
|
|
68
|
+
</p-dialog>
|
|
69
|
+
`
|
|
70
|
+
})
|
|
71
|
+
export class MenuItemEditDialogComponent {
|
|
72
|
+
private readonly menuService = inject(MenuService);
|
|
73
|
+
private readonly securityDataService = inject(SecurityDataService);
|
|
74
|
+
|
|
75
|
+
@Input() visible!: boolean;
|
|
76
|
+
@Output() visibleChange = new EventEmitter<boolean>();
|
|
77
|
+
|
|
78
|
+
modules: any[] = [];
|
|
79
|
+
roles: string[] = [];
|
|
80
|
+
item: EditModuleInstanceDto = {
|
|
81
|
+
icon: '',
|
|
82
|
+
label: '',
|
|
83
|
+
viewRoles: [''],
|
|
84
|
+
moduleId: 0,
|
|
85
|
+
moduleInstanceId: 0,
|
|
86
|
+
parentId: 0
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
changeVisible() {
|
|
90
|
+
this.visible = !this.visible;
|
|
91
|
+
this.visibleChange.emit(this.visible);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async save() {
|
|
95
|
+
await this.menuService.editModuleInstance(this.item);
|
|
96
|
+
await this.menuService.loadMenu();
|
|
97
|
+
this.hide();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
hide() {
|
|
101
|
+
this.visible = false;
|
|
102
|
+
this.visibleChange.emit(this.visible);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async showDialog() {
|
|
106
|
+
this.item = {
|
|
107
|
+
moduleInstanceId: this.menuService.contextMenuItem?.moduleInstanceId,
|
|
108
|
+
moduleId: this.menuService.contextMenuItem?.moduleId,
|
|
109
|
+
parentId: this.menuService.contextMenuItem?.parentId,
|
|
110
|
+
label: this.menuService.contextMenuItem?.label,
|
|
111
|
+
icon: this.menuService.contextMenuItem?.icon,
|
|
112
|
+
viewRoles: this.menuService.contextMenuItem?.securities
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
this.roles = await this.securityDataService.getRealmRoles();
|
|
116
|
+
this.menuService.getModules().then((data) => {
|
|
117
|
+
this.modules = data;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
this.visible = true;
|
|
121
|
+
this.visibleChange.emit(this.visible);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { ChangeDetectorRef, Component, HostBinding, inject, Input, OnDestroy, OnInit } from '@angular/core';
|
|
2
|
+
import { NavigationEnd, Router, RouterLinkActive, RouterLink } from '@angular/router';
|
|
3
|
+
import { animate, state, style, transition, trigger } from '@angular/animations';
|
|
4
|
+
import { Subscription } from 'rxjs';
|
|
5
|
+
import { filter } from 'rxjs/operators';
|
|
6
|
+
import { LayoutService } from '../../services/app.layout.service';
|
|
7
|
+
import { MenuService } from '../../services/app.menu.service';
|
|
8
|
+
import { RippleModule } from 'primeng/ripple';
|
|
9
|
+
import { NgIf, NgClass, NgFor } from '@angular/common';
|
|
10
|
+
import { ConfirmationService, ContextMenuService, MenuItem, MenuItemCommandEvent, PrimeIcons } from 'primeng/api';
|
|
11
|
+
import { MenuItemCreateDialogComponent } from './menu-item-create-dialog.component';
|
|
12
|
+
import { ContextMenu, ContextMenuModule } from 'primeng/contextmenu';
|
|
13
|
+
import { MsgService } from '../../services/msg.service';
|
|
14
|
+
import { MenuItemEditDialogComponent } from './menu-item-edit-dialog.component';
|
|
15
|
+
import { ContextMenuItemDto } from '../../dtos/context-menu-item.dto';
|
|
16
|
+
import { TranslateService } from '@ngx-translate/core';
|
|
17
|
+
import { ConfirmDialog } from 'primeng/confirmdialog';
|
|
18
|
+
import { Menu } from '../../api/Menu';
|
|
19
|
+
import { MenuDeleteModuleInstanceParams } from '../../api/data-contracts';
|
|
20
|
+
|
|
21
|
+
interface MenuItemComponentTranslation {
|
|
22
|
+
delete: string;
|
|
23
|
+
edit: string;
|
|
24
|
+
new: string;
|
|
25
|
+
deleteItemConfirmHeader: string;
|
|
26
|
+
deleteItemConfirmMessage: string;
|
|
27
|
+
deleteItemSuccessMessage: string;
|
|
28
|
+
deleteItemConfirmRejectButtonPropsLabel: string;
|
|
29
|
+
deleteItemConfirmAcceptButtonPropsLabel: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Component({
|
|
33
|
+
// eslint-disable-next-line @angular-eslint/component-selector
|
|
34
|
+
selector: '[app-menuitem]',
|
|
35
|
+
template: `
|
|
36
|
+
<ng-container>
|
|
37
|
+
<p-confirm-dialog />
|
|
38
|
+
<div
|
|
39
|
+
*ngIf="root && item.visible !== false"
|
|
40
|
+
class="layout-menuitem-root-text"
|
|
41
|
+
(contextmenu)="onContextMenu($event, item)">
|
|
42
|
+
{{ item.label }}
|
|
43
|
+
</div>
|
|
44
|
+
<a
|
|
45
|
+
*ngIf="(!item.routerLink || item.items) && item.visible !== false"
|
|
46
|
+
pRipple
|
|
47
|
+
tabindex="0"
|
|
48
|
+
[attr.href]="item.url"
|
|
49
|
+
[attr.target]="item.target"
|
|
50
|
+
[ngClass]="item.class"
|
|
51
|
+
(click)="itemClick($event)">
|
|
52
|
+
<i class="layout-menuitem-icon" [ngClass]="item.icon"></i>
|
|
53
|
+
<span class="layout-menuitem-text">{{ item.label }}</span>
|
|
54
|
+
<i *ngIf="item.items" class="pi pi-fw pi-angle-down layout-submenu-toggler"></i>
|
|
55
|
+
</a>
|
|
56
|
+
<a
|
|
57
|
+
*ngIf="item.routerLink && !item.items && item.visible !== false"
|
|
58
|
+
pRipple
|
|
59
|
+
routerLinkActive="active-route"
|
|
60
|
+
tabindex="0"
|
|
61
|
+
[attr.target]="item.target"
|
|
62
|
+
[fragment]="item.fragment"
|
|
63
|
+
[ngClass]="item.class"
|
|
64
|
+
[preserveFragment]="item.preserveFragment"
|
|
65
|
+
[queryParams]="item.queryParams"
|
|
66
|
+
[queryParamsHandling]="item.queryParamsHandling"
|
|
67
|
+
[replaceUrl]="item.replaceUrl"
|
|
68
|
+
[routerLink]="item.routerLink"
|
|
69
|
+
[routerLinkActiveOptions]="
|
|
70
|
+
item.routerLinkActiveOptions || {
|
|
71
|
+
paths: 'exact',
|
|
72
|
+
queryParams: 'ignored',
|
|
73
|
+
matrixParams: 'ignored',
|
|
74
|
+
fragment: 'ignored'
|
|
75
|
+
}
|
|
76
|
+
"
|
|
77
|
+
[skipLocationChange]="item.skipLocationChange"
|
|
78
|
+
[state]="item.state"
|
|
79
|
+
(click)="itemClick($event)"
|
|
80
|
+
(contextmenu)="onContextMenu($event, item)">
|
|
81
|
+
<i class="layout-menuitem-icon" [ngClass]="item.icon"></i>
|
|
82
|
+
<span class="layout-menuitem-text">{{ item.label }}</span>
|
|
83
|
+
<i *ngIf="item.items" class="pi pi-fw pi-angle-down layout-submenu-toggler"></i>
|
|
84
|
+
</a>
|
|
85
|
+
|
|
86
|
+
<ul
|
|
87
|
+
*ngIf="item.items && item.visible !== false"
|
|
88
|
+
[@children]="submenuAnimation"
|
|
89
|
+
(contextmenu)="onContextMenu($event, item)">
|
|
90
|
+
<ng-template let-child let-i="index" ngFor [ngForOf]="item.items">
|
|
91
|
+
<li
|
|
92
|
+
app-menuitem
|
|
93
|
+
[class]="child.badgeClass"
|
|
94
|
+
[contextMenu]="contextMenu"
|
|
95
|
+
[index]="i"
|
|
96
|
+
[item]="child"
|
|
97
|
+
[menuItemCreateDialogComponent]="menuItemCreateDialogComponent"
|
|
98
|
+
[menuItemEditDialogComponent]="menuItemEditDialogComponent"
|
|
99
|
+
[parentKey]="key"></li>
|
|
100
|
+
</ng-template>
|
|
101
|
+
</ul>
|
|
102
|
+
</ng-container>
|
|
103
|
+
`,
|
|
104
|
+
animations: [
|
|
105
|
+
trigger('children', [
|
|
106
|
+
state(
|
|
107
|
+
'collapsed',
|
|
108
|
+
style({
|
|
109
|
+
height: '0'
|
|
110
|
+
})
|
|
111
|
+
),
|
|
112
|
+
state(
|
|
113
|
+
'expanded',
|
|
114
|
+
style({
|
|
115
|
+
height: '*'
|
|
116
|
+
})
|
|
117
|
+
),
|
|
118
|
+
transition('collapsed <=> expanded', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)'))
|
|
119
|
+
])
|
|
120
|
+
],
|
|
121
|
+
imports: [NgIf, RippleModule, NgClass, RouterLinkActive, RouterLink, NgFor, ContextMenuModule, ConfirmDialog],
|
|
122
|
+
providers: [ConfirmationService]
|
|
123
|
+
})
|
|
124
|
+
export class MenuItemComponent implements OnInit, OnDestroy {
|
|
125
|
+
private readonly layoutService = inject(LayoutService);
|
|
126
|
+
private readonly translateService = inject(TranslateService);
|
|
127
|
+
private readonly confirmationService = inject(ConfirmationService);
|
|
128
|
+
private readonly msgService = inject(MsgService);
|
|
129
|
+
private readonly menuDataService = inject(Menu);
|
|
130
|
+
|
|
131
|
+
@Input() item: ContextMenuItemDto;
|
|
132
|
+
@Input() index!: number;
|
|
133
|
+
@Input() @HostBinding('class.layout-root-menuitem') root!: boolean;
|
|
134
|
+
@Input() parentKey!: string;
|
|
135
|
+
@Input() menuItemCreateDialogComponent: MenuItemCreateDialogComponent;
|
|
136
|
+
@Input() menuItemEditDialogComponent: MenuItemEditDialogComponent;
|
|
137
|
+
@Input() contextMenu: ContextMenu;
|
|
138
|
+
|
|
139
|
+
private active = false;
|
|
140
|
+
private subscriptions: Subscription[] = [];
|
|
141
|
+
private localization: MenuItemComponentTranslation = {} as MenuItemComponentTranslation;
|
|
142
|
+
|
|
143
|
+
protected key: string = '';
|
|
144
|
+
|
|
145
|
+
constructor(
|
|
146
|
+
private readonly cd: ChangeDetectorRef,
|
|
147
|
+
public router: Router,
|
|
148
|
+
private readonly menuService: MenuService
|
|
149
|
+
) {
|
|
150
|
+
this.subscriptions.push(
|
|
151
|
+
this.menuService.menuSource$.subscribe((value) => {
|
|
152
|
+
Promise.resolve(null).then(() => {
|
|
153
|
+
if (value.routeEvent) {
|
|
154
|
+
this.active = value.key === this.key || value.key.startsWith(this.key + '-');
|
|
155
|
+
} else if (value.key !== this.key && !value.key.startsWith(this.key + '-')) {
|
|
156
|
+
this.active = false;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
})
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
this.subscriptions.push(
|
|
163
|
+
this.menuService.resetSource$.subscribe(() => {
|
|
164
|
+
this.active = false;
|
|
165
|
+
})
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
this.subscriptions.push(
|
|
169
|
+
this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe((params) => {
|
|
170
|
+
if (this.item.routerLink) {
|
|
171
|
+
this.updateActiveStateFromRoute();
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
this.subscriptions.push(
|
|
177
|
+
this.translateService.get('menuItemComponent').subscribe((value: MenuItemComponentTranslation) => {
|
|
178
|
+
this.localization = value;
|
|
179
|
+
})
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
ngOnInit() {
|
|
184
|
+
this.key = this.parentKey ? this.parentKey + '-' + this.index : String(this.index);
|
|
185
|
+
|
|
186
|
+
if (this.item.routerLink) {
|
|
187
|
+
this.updateActiveStateFromRoute();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
updateActiveStateFromRoute() {
|
|
192
|
+
const activeRoute = this.router.isActive(this.item.routerLink[0], {
|
|
193
|
+
paths: 'exact',
|
|
194
|
+
queryParams: 'ignored',
|
|
195
|
+
matrixParams: 'ignored',
|
|
196
|
+
fragment: 'ignored'
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
if (activeRoute) {
|
|
200
|
+
this.menuService.onMenuStateChange({
|
|
201
|
+
key: this.key,
|
|
202
|
+
item: this.item,
|
|
203
|
+
routeEvent: true
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
itemClick(event: Event) {
|
|
209
|
+
// avoid processing disabled items
|
|
210
|
+
if (this.item.disabled) {
|
|
211
|
+
event.preventDefault();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// execute command
|
|
216
|
+
if (this.item.command) {
|
|
217
|
+
this.item.command({ originalEvent: event, item: this.item });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// toggle active state
|
|
221
|
+
if (this.item.items) {
|
|
222
|
+
this.active = !this.active;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
this.menuService.onMenuStateChange({ key: this.key, item: this.item });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
get submenuAnimation() {
|
|
229
|
+
return this.root || this.active ? 'expanded' : 'collapsed';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
@HostBinding('class.active-menuitem')
|
|
233
|
+
get activeClass() {
|
|
234
|
+
return this.active && !this.root;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
ngOnDestroy() {
|
|
238
|
+
this.subscriptions.map((s) => s.unsubscribe());
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private newClick(e: MenuItemCommandEvent) {
|
|
242
|
+
this.menuItemCreateDialogComponent.showDialog();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
onContextMenu($event: MouseEvent, item: any) {
|
|
246
|
+
this.menuService.contextMenuItem = item;
|
|
247
|
+
this.contextMenu.model = [
|
|
248
|
+
{
|
|
249
|
+
label: this.localization.new,
|
|
250
|
+
icon: PrimeIcons.PLUS,
|
|
251
|
+
command: (event) => this.newClick(event)
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
label: this.localization.edit,
|
|
255
|
+
icon: PrimeIcons.FILE_EDIT,
|
|
256
|
+
command: (event) => this.editClick(event)
|
|
257
|
+
},
|
|
258
|
+
{ separator: true },
|
|
259
|
+
{
|
|
260
|
+
label: this.localization.delete,
|
|
261
|
+
icon: PrimeIcons.TRASH,
|
|
262
|
+
command: (event) => this.deleteItem(event)
|
|
263
|
+
}
|
|
264
|
+
];
|
|
265
|
+
this.contextMenu.show($event);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private deleteItem(event: MenuItemCommandEvent) {
|
|
269
|
+
this.confirmationService.confirm({
|
|
270
|
+
header: this.localization.deleteItemConfirmHeader,
|
|
271
|
+
message: this.localization.deleteItemConfirmMessage,
|
|
272
|
+
icon: PrimeIcons.TRASH,
|
|
273
|
+
rejectButtonProps: {
|
|
274
|
+
label: this.localization.deleteItemConfirmRejectButtonPropsLabel,
|
|
275
|
+
severity: 'secondary',
|
|
276
|
+
outlined: true
|
|
277
|
+
},
|
|
278
|
+
acceptButtonProps: {
|
|
279
|
+
label: this.localization.deleteItemConfirmAcceptButtonPropsLabel,
|
|
280
|
+
severity: 'danger'
|
|
281
|
+
},
|
|
282
|
+
accept: async () => {
|
|
283
|
+
await this.menuDataService.menuDeleteModuleInstance({
|
|
284
|
+
id: this.menuService.contextMenuItem?.moduleInstanceId
|
|
285
|
+
} as MenuDeleteModuleInstanceParams);
|
|
286
|
+
this.msgService.success(this.localization.deleteItemSuccessMessage);
|
|
287
|
+
await this.menuService.loadMenu();
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private editClick(event: MenuItemCommandEvent) {
|
|
293
|
+
this.menuItemEditDialogComponent.showDialog();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Component, inject, OnInit, ViewChild } from '@angular/core';
|
|
2
|
+
import { MenuService } from '../../services/app.menu.service';
|
|
3
|
+
import { ButtonModule } from 'primeng/button';
|
|
4
|
+
import { SecurityService } from '../../services/security.service';
|
|
5
|
+
import { ContextMenu, ContextMenuModule } from 'primeng/contextmenu';
|
|
6
|
+
import { DialogModule } from 'primeng/dialog';
|
|
7
|
+
import { MenuItemCommandEvent, PrimeIcons } from 'primeng/api';
|
|
8
|
+
import { InputTextModule } from 'primeng/inputtext';
|
|
9
|
+
import { FormsModule } from '@angular/forms';
|
|
10
|
+
import { MenuItemComponent } from './menu-item.component';
|
|
11
|
+
import { MenuItemCreateDialogComponent } from './menu-item-create-dialog.component';
|
|
12
|
+
import { TranslateService } from '@ngx-translate/core';
|
|
13
|
+
import { MenuItemEditDialogComponent } from './menu-item-edit-dialog.component';
|
|
14
|
+
import { Menu } from '../../api/Menu';
|
|
15
|
+
|
|
16
|
+
@Component({
|
|
17
|
+
imports: [
|
|
18
|
+
MenuItemComponent,
|
|
19
|
+
ButtonModule,
|
|
20
|
+
ContextMenuModule,
|
|
21
|
+
DialogModule,
|
|
22
|
+
InputTextModule,
|
|
23
|
+
MenuItemCreateDialogComponent,
|
|
24
|
+
FormsModule,
|
|
25
|
+
MenuItemEditDialogComponent
|
|
26
|
+
],
|
|
27
|
+
providers: [Menu],
|
|
28
|
+
selector: 'app-menu',
|
|
29
|
+
standalone: true,
|
|
30
|
+
template: ` <div #empty class="layout-sidebar" (contextmenu)="onContextMenu($event)">
|
|
31
|
+
<ul class="layout-menu">
|
|
32
|
+
@for (item of menuService.menu; track item; let i = $index) {
|
|
33
|
+
<ng-container>
|
|
34
|
+
@if (item.separator) {
|
|
35
|
+
<li class="menu-separator"></li>
|
|
36
|
+
} @else {
|
|
37
|
+
<li
|
|
38
|
+
app-menuitem
|
|
39
|
+
[contextMenu]="contextMenu"
|
|
40
|
+
[index]="i"
|
|
41
|
+
[item]="item"
|
|
42
|
+
[menuItemCreateDialogComponent]="menuItemCreateDialogComponent"
|
|
43
|
+
[menuItemEditDialogComponent]="menuItemEditDialogComponent"
|
|
44
|
+
[root]="true"></li>
|
|
45
|
+
}
|
|
46
|
+
</ng-container>
|
|
47
|
+
}
|
|
48
|
+
</ul>
|
|
49
|
+
</div>
|
|
50
|
+
<p-contextMenu [target]="empty" />
|
|
51
|
+
@if (securityService.isAdmin) {
|
|
52
|
+
<menu-item-create-dialog />
|
|
53
|
+
<menu-item-edit-dialog />
|
|
54
|
+
}`
|
|
55
|
+
})
|
|
56
|
+
export class MenuComponent implements OnInit {
|
|
57
|
+
readonly menuService = inject(MenuService);
|
|
58
|
+
readonly securityService = inject(SecurityService);
|
|
59
|
+
readonly translateService = inject(TranslateService);
|
|
60
|
+
|
|
61
|
+
@ViewChild(MenuItemCreateDialogComponent)
|
|
62
|
+
menuItemCreateDialogComponent: MenuItemCreateDialogComponent;
|
|
63
|
+
@ViewChild(MenuItemEditDialogComponent)
|
|
64
|
+
menuItemEditDialogComponent: MenuItemEditDialogComponent;
|
|
65
|
+
@ViewChild(ContextMenu) contextMenu: ContextMenu;
|
|
66
|
+
|
|
67
|
+
ngOnInit() {
|
|
68
|
+
this.menuService.loadMenu().then();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private newClick(e: MenuItemCommandEvent) {
|
|
72
|
+
this.menuItemCreateDialogComponent.showDialog();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
onContextMenu($event: MouseEvent) {
|
|
76
|
+
this.menuService.contextMenuItem = null;
|
|
77
|
+
this.contextMenu.model = [
|
|
78
|
+
{
|
|
79
|
+
label: this.translateService.instant('menuComponent.new'),
|
|
80
|
+
icon: PrimeIcons.PLUS,
|
|
81
|
+
command: (event) => this.newClick(event)
|
|
82
|
+
}
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
import { RouterLink } from '@angular/router';
|
|
3
|
+
import { LogoComponent } from './logo.component';
|
|
4
|
+
import { Button } from 'primeng/button';
|
|
5
|
+
import { AppFloatingConfiguratorComponent } from './app-floating-configurator.component';
|
|
6
|
+
|
|
7
|
+
@Component({
|
|
8
|
+
selector: 'app-notfound',
|
|
9
|
+
template: ` <app-floating-configurator />
|
|
10
|
+
<div class="flex items-center justify-center min-h-screen overflow-hidden">
|
|
11
|
+
<div class="flex flex-col items-center justify-center">
|
|
12
|
+
<div
|
|
13
|
+
style="border-radius: 56px; padding: 0.3rem; background: linear-gradient(180deg, color-mix(in srgb, var(--primary-color), transparent 60%) 10%, var(--surface-ground) 30%)">
|
|
14
|
+
<div
|
|
15
|
+
class="w-full bg-surface-0 dark:bg-surface-900 py-20 px-8 sm:px-20 flex flex-col items-center"
|
|
16
|
+
style="border-radius: 53px">
|
|
17
|
+
<div class="flex flex-col items-center justify-center">
|
|
18
|
+
<logo height="96" width="96"></logo>
|
|
19
|
+
</div>
|
|
20
|
+
<span class="text-primary font-bold text-3xl">404</span>
|
|
21
|
+
<h1 class="text-surface-900 dark:text-surface-0 font-bold text-3xl lg:text-5xl mb-2">Not Found</h1>
|
|
22
|
+
<div class="text-surface-600 dark:text-surface-200 mb-8">Requested resource is not available.</div>
|
|
23
|
+
<p-button id="oip-app-notfound-go-to-home-button" label="Go to home" routerLink="/" />
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>`,
|
|
28
|
+
imports: [RouterLink, LogoComponent, Button, AppFloatingConfiguratorComponent],
|
|
29
|
+
standalone: true
|
|
30
|
+
})
|
|
31
|
+
export class NotfoundComponent {}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Component, inject } from '@angular/core';
|
|
2
|
+
import { FileUploadModule } from 'primeng/fileupload';
|
|
3
|
+
import { ImageModule } from 'primeng/image';
|
|
4
|
+
import { AvatarModule } from 'primeng/avatar';
|
|
5
|
+
import { MsgService, UserService } from 'oip-common';
|
|
6
|
+
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
|
|
7
|
+
|
|
8
|
+
@Component({
|
|
9
|
+
selector: 'user-profile',
|
|
10
|
+
standalone: true,
|
|
11
|
+
imports: [FileUploadModule, ImageModule, ImageModule, FileUploadModule, ImageModule, AvatarModule, TranslatePipe],
|
|
12
|
+
template: `
|
|
13
|
+
<p-avatar
|
|
14
|
+
class="mr-2"
|
|
15
|
+
id="oip-user-profile-photo-avatar"
|
|
16
|
+
shape="circle"
|
|
17
|
+
size="xlarge"
|
|
18
|
+
[image]="userService.photoLoaded ? userService.photo : null" />
|
|
19
|
+
<div class="mt-2">
|
|
20
|
+
<p-fileupload
|
|
21
|
+
accept="image/*"
|
|
22
|
+
chooseIcon="pi pi-upload"
|
|
23
|
+
chooseLabel="{{ 'profileComponent.changePhoto' | translate }}"
|
|
24
|
+
id="oip-user-profile-file-upload"
|
|
25
|
+
maxFileSize="1000000"
|
|
26
|
+
mode="basic"
|
|
27
|
+
name="files"
|
|
28
|
+
url="/api/user-profile/post-user-photo"
|
|
29
|
+
withCredentials="true"
|
|
30
|
+
[auto]="true"
|
|
31
|
+
(onUpload)="onBasicUploadAuto($event)" />
|
|
32
|
+
</div>
|
|
33
|
+
`
|
|
34
|
+
})
|
|
35
|
+
export class ProfileComponent {
|
|
36
|
+
readonly userService = inject(UserService);
|
|
37
|
+
readonly msgService = inject(MsgService);
|
|
38
|
+
readonly translateService = inject(TranslateService);
|
|
39
|
+
|
|
40
|
+
onBasicUploadAuto($event) {
|
|
41
|
+
this.msgService.success(this.translateService.instant('profileComponent.successfullyUploaded'));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Component, inject, Input, OnDestroy, OnInit } from '@angular/core';
|
|
2
|
+
import { MultiSelectModule } from 'primeng/multiselect';
|
|
3
|
+
import { TooltipModule } from 'primeng/tooltip';
|
|
4
|
+
import { ButtonModule } from 'primeng/button';
|
|
5
|
+
import { FormsModule } from '@angular/forms';
|
|
6
|
+
import { MsgService } from './../services/msg.service';
|
|
7
|
+
import { SecurityDataService } from './../services/security-data.service';
|
|
8
|
+
import { PutSecurityDto } from './../dtos/put-security.dto';
|
|
9
|
+
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
|
|
10
|
+
|
|
11
|
+
@Component({
|
|
12
|
+
selector: 'security',
|
|
13
|
+
template: `
|
|
14
|
+
<div class="flex flex-col md:flex-row gap-8">
|
|
15
|
+
<div class="md:w-1/2">
|
|
16
|
+
<div class="card flex flex-col gap-4">
|
|
17
|
+
<div class="font-semibold text-xl">
|
|
18
|
+
{{ 'securityComponent.security' | translate }}
|
|
19
|
+
</div>
|
|
20
|
+
@for (item of securityData; track item.name) {
|
|
21
|
+
<div class="flex flex-col gap-2">
|
|
22
|
+
<label htmlFor="oip-security-multiselect-{{ item.name }}">
|
|
23
|
+
{{ item.name }}
|
|
24
|
+
<span class="pi pi-question-circle" pTooltip="{{ item.description }}" tooltipPosition="right"></span>
|
|
25
|
+
</label>
|
|
26
|
+
<p-multiSelect
|
|
27
|
+
id="oip-security-multiselect-{{ item.name }}"
|
|
28
|
+
placeholder="Select roles"
|
|
29
|
+
[maxSelectedLabels]="10"
|
|
30
|
+
[options]="roles"
|
|
31
|
+
[(ngModel)]="item.roles" />
|
|
32
|
+
</div>
|
|
33
|
+
}
|
|
34
|
+
<div class="flex justify-content-end flex-wrap">
|
|
35
|
+
<p-button
|
|
36
|
+
icon="pi pi-save"
|
|
37
|
+
id="oip-security-save-button"
|
|
38
|
+
label="{{ 'securityComponent.save' | translate }}"
|
|
39
|
+
(click)="saveClick()"
|
|
40
|
+
(keydown)="saveKeyDown($event)" />
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
`,
|
|
46
|
+
imports: [MultiSelectModule, TooltipModule, FormsModule, ButtonModule, TranslatePipe],
|
|
47
|
+
standalone: true
|
|
48
|
+
})
|
|
49
|
+
export class SecurityComponent implements OnInit, OnDestroy {
|
|
50
|
+
private readonly msgService = inject(MsgService);
|
|
51
|
+
private readonly dataService = inject(SecurityDataService);
|
|
52
|
+
private readonly translateService = inject(TranslateService);
|
|
53
|
+
securityData: any[];
|
|
54
|
+
@Input() id: number;
|
|
55
|
+
@Input() controller: string;
|
|
56
|
+
roles: string[] = [];
|
|
57
|
+
|
|
58
|
+
ngOnDestroy(): void {
|
|
59
|
+
// on destroy
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
ngOnInit(): void {
|
|
63
|
+
if (!this.id) {
|
|
64
|
+
this.msgService.error('Module id not passed!');
|
|
65
|
+
}
|
|
66
|
+
if (!this.controller) {
|
|
67
|
+
this.msgService.error('Controller not passed!');
|
|
68
|
+
}
|
|
69
|
+
this.dataService.getSecurity(this.controller, this.id).then(
|
|
70
|
+
(result) => {
|
|
71
|
+
this.securityData = result;
|
|
72
|
+
},
|
|
73
|
+
(error) => this.msgService.error(error)
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
this.dataService.getRealmRoles().then(
|
|
77
|
+
(result) => {
|
|
78
|
+
this.roles = result;
|
|
79
|
+
},
|
|
80
|
+
(error) => this.msgService.error(error)
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
saveClick() {
|
|
85
|
+
const request: PutSecurityDto = {
|
|
86
|
+
id: this.id,
|
|
87
|
+
securities: this.securityData
|
|
88
|
+
};
|
|
89
|
+
this.dataService.saveSecurity(this.controller, request).then(
|
|
90
|
+
(result) => {
|
|
91
|
+
this.msgService.success(this.translateService.instant('securityComponent.savedSecurity'));
|
|
92
|
+
},
|
|
93
|
+
(error) => this.msgService.error(error)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
saveKeyDown($event: KeyboardEvent) {
|
|
98
|
+
if ($event.key === 'Enter' || $event.key === 'Space') {
|
|
99
|
+
this.saveKeyDown(null);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
import { MenuComponent } from './menu/menu.component';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'app-sidebar',
|
|
6
|
+
template: ` <app-menu></app-menu>`,
|
|
7
|
+
standalone: true,
|
|
8
|
+
imports: [MenuComponent]
|
|
9
|
+
})
|
|
10
|
+
export class SidebarComponent {
|
|
11
|
+
constructor() {}
|
|
12
|
+
}
|