diff --git a/src/app/directives/common/common-directives.module.ts b/src/app/directives/common/common-directives.module.ts
index ac935ca4f9c..7d4d711894e 100644
--- a/src/app/directives/common/common-directives.module.ts
+++ b/src/app/directives/common/common-directives.module.ts
@@ -1,19 +1,27 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
+import { MatTooltipModule } from '@angular/material/tooltip';
+import { TranslateModule } from '@ngx-translate/core';
import { AutofocusDirective } from 'app/directives/common/autofocus/autofocus.directive';
import { HasRoleDirective } from 'app/directives/common/has-role/has-role.directive';
import { IfNightlyDirective } from 'app/directives/common/if-nightly/if-nightly.directive';
+import { RequiresRolesWrapperComponent } from 'app/directives/common/requires-roles/requires-roles-wrapper.component';
+import { RequiresRolesDirective } from 'app/directives/common/requires-roles/requires-roles.directive';
import { StepActivationDirective } from 'app/directives/common/step-activation.directive';
import { LetDirective } from './app-let.directive';
@NgModule({
imports: [
CommonModule,
+ MatTooltipModule,
+ TranslateModule,
],
declarations: [
LetDirective,
IfNightlyDirective,
HasRoleDirective,
+ RequiresRolesWrapperComponent,
+ RequiresRolesDirective,
AutofocusDirective,
StepActivationDirective,
],
@@ -21,6 +29,8 @@ import { LetDirective } from './app-let.directive';
LetDirective,
IfNightlyDirective,
HasRoleDirective,
+ RequiresRolesWrapperComponent,
+ RequiresRolesDirective,
AutofocusDirective,
StepActivationDirective,
],
diff --git a/src/app/directives/common/requires-roles/requires-roles-wrapper.component.scss b/src/app/directives/common/requires-roles/requires-roles-wrapper.component.scss
new file mode 100644
index 00000000000..149ed6d176c
--- /dev/null
+++ b/src/app/directives/common/requires-roles/requires-roles-wrapper.component.scss
@@ -0,0 +1,20 @@
+
+:host ::ng-deep .role-missing {
+ button {
+ cursor: none;
+ pointer-events: none;
+ }
+
+ .mat-mdc-button,
+ .mdc-button {
+ margin-right: 8px;
+ }
+
+ .mdc-button__label,
+ .mat-icon,
+ .mat-mdc-menu-item-text {
+ opacity: 0.5;
+ text-decoration: line-through;
+ }
+}
+
diff --git a/src/app/directives/common/requires-roles/requires-roles-wrapper.component.ts b/src/app/directives/common/requires-roles/requires-roles-wrapper.component.ts
new file mode 100644
index 00000000000..3ae24bf23b3
--- /dev/null
+++ b/src/app/directives/common/requires-roles/requires-roles-wrapper.component.ts
@@ -0,0 +1,15 @@
+import { Component, Input, TemplateRef } from '@angular/core';
+
+@Component({
+ selector: 'ix-requires-roles-wrapper',
+ template: `
+
+
+
+`,
+ styleUrls: ['./requires-roles-wrapper.component.scss'],
+})
+export class RequiresRolesWrapperComponent {
+ @Input() template: TemplateRef;
+ @Input() class: string;
+}
diff --git a/src/app/directives/common/requires-roles/requires-roles.directive.ts b/src/app/directives/common/requires-roles/requires-roles.directive.ts
new file mode 100644
index 00000000000..004ae93c367
--- /dev/null
+++ b/src/app/directives/common/requires-roles/requires-roles.directive.ts
@@ -0,0 +1,47 @@
+import {
+ ComponentRef, Directive, HostBinding, Input, TemplateRef, ViewContainerRef,
+} from '@angular/core';
+import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
+import { RequiresRolesWrapperComponent } from 'app/directives/common/requires-roles/requires-roles-wrapper.component';
+import { Role } from 'app/enums/role.enum';
+import { AuthService } from 'app/services/auth/auth.service';
+
+@UntilDestroy()
+@Directive({
+ selector: '[ixRequiresRoles]',
+})
+export class RequiresRolesDirective {
+ private wrapperContainer: ComponentRef;
+
+ @Input()
+ set ixRequiresRoles(roles: Role[]) {
+ this.authService.hasRole(roles).pipe(untilDestroyed(this)).subscribe({
+ next: (hasRole) => {
+ if (!hasRole) {
+ this.wrapperContainer = this.viewContainerRef.createComponent(RequiresRolesWrapperComponent);
+ this.wrapperContainer.instance.template = this.templateRef;
+ this.wrapperContainer.instance.class = this.elementClass;
+ } else {
+ this.viewContainerRef.createEmbeddedView(this.templateRef);
+ }
+ },
+ });
+ }
+
+ protected cssClassList: string[] = [];
+
+ @Input('class')
+ @HostBinding('class')
+ get elementClass(): string {
+ return this.cssClassList.join(' ');
+ }
+ set(val: string): void {
+ this.cssClassList = val.split(' ');
+ }
+
+ constructor(
+ private templateRef: TemplateRef,
+ private viewContainerRef: ViewContainerRef,
+ private authService: AuthService,
+ ) { }
+}
diff --git a/src/app/modules/ix-table2/components/ix-table-body/cells/ix-cell-actions/icon-action-config.interface.ts b/src/app/modules/ix-table2/components/ix-table-body/cells/ix-cell-actions/icon-action-config.interface.ts
index 45c38fff4f3..81063dc8ef4 100644
--- a/src/app/modules/ix-table2/components/ix-table-body/cells/ix-cell-actions/icon-action-config.interface.ts
+++ b/src/app/modules/ix-table2/components/ix-table-body/cells/ix-cell-actions/icon-action-config.interface.ts
@@ -1,4 +1,5 @@
import { Observable } from 'rxjs';
+import { Role } from 'app/enums/role.enum';
export interface IconActionConfig {
iconName: string;
@@ -7,4 +8,5 @@ export interface IconActionConfig {
dynamicTooltip?: (row: T) => Observable;
hidden?: (row: T) => Observable;
disabled?: (row: T) => Observable;
+ requiresRoles?: Role[];
}
diff --git a/src/app/modules/ix-table2/components/ix-table-body/cells/ix-cell-actions/ix-cell-actions.component.html b/src/app/modules/ix-table2/components/ix-table-body/cells/ix-cell-actions/ix-cell-actions.component.html
index 578aae8d10f..e443ff18d28 100644
--- a/src/app/modules/ix-table2/components/ix-table-body/cells/ix-cell-actions/ix-cell-actions.component.html
+++ b/src/app/modules/ix-table2/components/ix-table-body/cells/ix-cell-actions/ix-cell-actions.component.html
@@ -2,16 +2,38 @@
-
+
+
+
+
+
+
+
+
diff --git a/src/app/modules/ix-table2/components/ix-table-body/cells/ix-cell-actions/ix-cell-actions.component.ts b/src/app/modules/ix-table2/components/ix-table-body/cells/ix-cell-actions/ix-cell-actions.component.ts
index 5eb9e26e826..5ca2c2e465b 100644
--- a/src/app/modules/ix-table2/components/ix-table-body/cells/ix-cell-actions/ix-cell-actions.component.ts
+++ b/src/app/modules/ix-table2/components/ix-table-body/cells/ix-cell-actions/ix-cell-actions.component.ts
@@ -1,4 +1,5 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { Role } from 'app/enums/role.enum';
import { IconActionConfig } from 'app/modules/ix-table2/components/ix-table-body/cells/ix-cell-actions/icon-action-config.interface';
import { Column, ColumnComponent } from 'app/modules/ix-table2/interfaces/table-column.interface';
@@ -10,6 +11,7 @@ import { Column, ColumnComponent } from 'app/modules/ix-table2/interfaces/table-
})
export class IxCellActionsComponent extends ColumnComponent {
actions: IconActionConfig[];
+ Role = Role;
}
export function actionsColumn(
diff --git a/src/app/modules/ix-table2/ix-table2.module.ts b/src/app/modules/ix-table2/ix-table2.module.ts
index 63dd6cb5b18..da643f92f20 100644
--- a/src/app/modules/ix-table2/ix-table2.module.ts
+++ b/src/app/modules/ix-table2/ix-table2.module.ts
@@ -12,6 +12,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { NgxFilesizeModule } from 'ngx-filesize';
import { CoreComponents } from 'app/core/core-components.module';
import { FormatDateTimePipe } from 'app/core/pipes/format-datetime.pipe';
+import { CommonDirectivesModule } from 'app/directives/common/common-directives.module';
import { IxIconModule } from 'app/modules/ix-icon/ix-icon.module';
import { IxTable2EmptyRowComponent } from 'app/modules/ix-table2/components/ix-empty-row/ix-empty-row.component';
import { IxCellActionsComponent } from 'app/modules/ix-table2/components/ix-table-body/cells/ix-cell-actions/ix-cell-actions.component';
@@ -53,6 +54,7 @@ import { IxTableColumnsSelectorComponent } from './components/ix-table-columns-s
MatSlideToggleModule,
TranslateModule,
NgxFilesizeModule,
+ CommonDirectivesModule,
TestIdModule,
CoreComponents,
MatMenuModule,
diff --git a/src/app/pages/apps/components/docker-images/docker-images-list/docker-images-list.component.html b/src/app/pages/apps/components/docker-images/docker-images-list/docker-images-list.component.html
index 7f8a83897be..cd12297ea6e 100644
--- a/src/app/pages/apps/components/docker-images/docker-images-list/docker-images-list.component.html
+++ b/src/app/pages/apps/components/docker-images/docker-images-list/docker-images-list.component.html
@@ -109,6 +109,7 @@
{{ image.update_available ? ('Update available' | translate) : ('Up to date' | translate) }}