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.
Files changed (127) hide show
  1. package/README.md +25 -0
  2. package/ng-package.json +8 -0
  3. package/package.json +16 -0
  4. package/src/api/FolderModule.ts +124 -0
  5. package/src/api/Menu.ts +134 -0
  6. package/src/api/Module.ts +92 -0
  7. package/src/api/Security.ts +40 -0
  8. package/src/api/Service.ts +57 -0
  9. package/src/api/data-contracts.ts +186 -0
  10. package/src/api/http-client.ts +276 -0
  11. package/src/assets/demo/code.scss +17 -0
  12. package/src/assets/demo/data/products-small.json +124 -0
  13. package/src/assets/demo/demo.scss +2 -0
  14. package/src/assets/demo/flags/flags.scss +740 -0
  15. package/src/assets/demo/flags/flags_responsive.png +0 -0
  16. package/src/assets/demo/images/access/asset-access.svg +46 -0
  17. package/src/assets/demo/images/blocks/hero/hero-1.png +0 -0
  18. package/src/assets/demo/images/blocks/logos/hyper.svg +3 -0
  19. package/src/assets/demo/images/error/asset-error.svg +74 -0
  20. package/src/assets/demo/images/flag/flag_placeholder.png +0 -0
  21. package/src/assets/demo/images/product/bamboo-watch.jpg +0 -0
  22. package/src/assets/demo/images/product/black-watch.jpg +0 -0
  23. package/src/assets/demo/images/product/blue-band.jpg +0 -0
  24. package/src/assets/demo/images/product/blue-t-shirt.jpg +0 -0
  25. package/src/assets/demo/images/product/bracelet.jpg +0 -0
  26. package/src/assets/demo/images/product/brown-purse.jpg +0 -0
  27. package/src/assets/demo/images/product/chakra-bracelet.jpg +0 -0
  28. package/src/assets/demo/images/product/galaxy-earrings.jpg +0 -0
  29. package/src/assets/demo/images/product/game-controller.jpg +0 -0
  30. package/src/assets/demo/images/product/gaming-set.jpg +0 -0
  31. package/src/assets/demo/images/product/gold-phone-case.jpg +0 -0
  32. package/src/assets/demo/images/product/green-earbuds.jpg +0 -0
  33. package/src/assets/demo/images/product/green-t-shirt.jpg +0 -0
  34. package/src/assets/demo/images/product/grey-t-shirt.jpg +0 -0
  35. package/src/assets/demo/images/product/headphones.jpg +0 -0
  36. package/src/assets/demo/images/product/light-green-t-shirt.jpg +0 -0
  37. package/src/assets/demo/images/product/lime-band.jpg +0 -0
  38. package/src/assets/demo/images/product/mini-speakers.jpg +0 -0
  39. package/src/assets/demo/images/product/painted-phone-case.jpg +0 -0
  40. package/src/assets/demo/images/product/pink-band.jpg +0 -0
  41. package/src/assets/demo/images/product/pink-purse.jpg +0 -0
  42. package/src/assets/demo/images/product/product-placeholder.svg +10 -0
  43. package/src/assets/demo/images/product/purple-band.jpg +0 -0
  44. package/src/assets/demo/images/product/purple-gemstone-necklace.jpg +0 -0
  45. package/src/assets/demo/images/product/purple-t-shirt.jpg +0 -0
  46. package/src/assets/demo/images/product/shoes.jpg +0 -0
  47. package/src/assets/demo/images/product/sneakers.jpg +0 -0
  48. package/src/assets/demo/images/product/teal-t-shirt.jpg +0 -0
  49. package/src/assets/demo/images/product/yellow-earbuds.jpg +0 -0
  50. package/src/assets/demo/images/product/yoga-mat.jpg +0 -0
  51. package/src/assets/demo/images/product/yoga-set.jpg +0 -0
  52. package/src/assets/favicon.svg +14 -0
  53. package/src/assets/i18n/app-modules.en.json +23 -0
  54. package/src/assets/i18n/app-modules.ru.json +23 -0
  55. package/src/assets/i18n/config.en.json +14 -0
  56. package/src/assets/i18n/config.ru.json +14 -0
  57. package/src/assets/layout/_core.scss +24 -0
  58. package/src/assets/layout/_footer.scss +8 -0
  59. package/src/assets/layout/_logo.scss +7 -0
  60. package/src/assets/layout/_main.scss +12 -0
  61. package/src/assets/layout/_menu.scss +159 -0
  62. package/src/assets/layout/_mixins.scss +15 -0
  63. package/src/assets/layout/_preloading.scss +49 -0
  64. package/src/assets/layout/_responsive.scss +108 -0
  65. package/src/assets/layout/_topbar.scss +168 -0
  66. package/src/assets/layout/_typography.scss +68 -0
  67. package/src/assets/layout/_utils.scss +25 -0
  68. package/src/assets/layout/layout.scss +14 -0
  69. package/src/assets/layout/variables/_common.scss +20 -0
  70. package/src/assets/layout/variables/_dark.scss +5 -0
  71. package/src/assets/layout/variables/_light.scss +5 -0
  72. package/src/assets/oip-common.scss +5 -0
  73. package/src/assets/tailwind.css +3 -0
  74. package/src/components/app-configurator.component.ts +491 -0
  75. package/src/components/app-floating-configurator.component.ts +47 -0
  76. package/src/components/app-modules.component.ts +144 -0
  77. package/src/components/app.layout.component.ts +130 -0
  78. package/src/components/auth/access/access.component.ts +42 -0
  79. package/src/components/auth/error/error.component.ts +42 -0
  80. package/src/components/auth/login/login.component.ts +120 -0
  81. package/src/components/auth/unauthorized/unauthorized.component.ts +51 -0
  82. package/src/components/base-module.component.ts +258 -0
  83. package/src/components/config.component.ts +131 -0
  84. package/src/components/db-migration/db-migration.component.ts +162 -0
  85. package/src/components/db-migration.component.ts +154 -0
  86. package/src/components/footer.component.ts +17 -0
  87. package/src/components/logo.component.ts +34 -0
  88. package/src/components/menu/menu-item-create-dialog.component.ts +119 -0
  89. package/src/components/menu/menu-item-edit-dialog.component.ts +123 -0
  90. package/src/components/menu/menu-item.component.ts +295 -0
  91. package/src/components/menu/menu.component.ts +85 -0
  92. package/src/components/notfound.component.ts +31 -0
  93. package/src/components/profile.component.ts +43 -0
  94. package/src/components/security.component.ts +102 -0
  95. package/src/components/sidebar.component.ts +12 -0
  96. package/src/components/top-bar.component.ts +147 -0
  97. package/src/dtos/context-menu-item.dto.ts +23 -0
  98. package/src/dtos/edit-module-instance.dto.ts +8 -0
  99. package/src/dtos/no-settings.dto.ts +4 -0
  100. package/src/dtos/put-security.dto.ts +6 -0
  101. package/src/dtos/security.dto.ts +6 -0
  102. package/src/dtos/top-bar.dto.ts +13 -0
  103. package/src/events/menu-change.event.ts +23 -0
  104. package/src/helpers/date.helper.ts +94 -0
  105. package/src/intercepts/i18n-intercept.service.ts +13 -0
  106. package/src/modules/http-loader.factory.ts +40 -0
  107. package/src/modules/secure.pipe.ts +19 -0
  108. package/src/public-api.ts +42 -0
  109. package/src/services/app-title.service.ts +22 -0
  110. package/src/services/app.layout.service.ts +236 -0
  111. package/src/services/app.menu.service.ts +64 -0
  112. package/src/services/auth.service.ts +58 -0
  113. package/src/services/base-data.service.ts +74 -0
  114. package/src/services/l10n.service.ts +71 -0
  115. package/src/services/msg.service.ts +76 -0
  116. package/src/services/security-data.service.ts +19 -0
  117. package/src/services/security-storage.service.ts +21 -0
  118. package/src/services/security.service.ts +116 -0
  119. package/src/services/top-bar.service.ts +44 -0
  120. package/src/services/user.service.ts +77 -0
  121. package/src/test.ts +11 -0
  122. package/src/user-api/UserProfile.ts +85 -0
  123. package/src/user-api/data-contracts.ts +42 -0
  124. package/src/user-api/http-client.ts +251 -0
  125. package/tsconfig.lib.json +12 -0
  126. package/tsconfig.lib.prod.json +10 -0
  127. 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
+ }