mirror of
https://github.com/goauthentik/authentik.git
synced 2026-06-17 19:09:11 +03:00
rbac: Add show all to roles tab, add role tab to groups (#19097)
* improve sort order and inherit visual * Update web/src/admin/groups/GroupViewPage.ts Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com> Signed-off-by: Connor Peshek <connor@connorpeshek.me> * Update web/src/admin/users/UserViewPage.ts Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com> Signed-off-by: Connor Peshek <connor@connorpeshek.me> * Update web/src/admin/roles/RelatedRoleList.ts Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com> Signed-off-by: Connor Peshek <connor@connorpeshek.me> * Update web/src/admin/roles/RelatedRoleList.ts Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com> Signed-off-by: Connor Peshek <connor@connorpeshek.me> * Update web/src/admin/roles/RelatedRoleList.ts Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com> Signed-off-by: Connor Peshek <connor@connorpeshek.me> * Update web/src/admin/roles/RelatedRoleList.ts Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com> Signed-off-by: Connor Peshek <connor@connorpeshek.me> * setup include inherited roles and fix returning nothing * update api calls * fix rendering error * do not use set * change from exception handling * go off query param * fix wording * fix linting error for new group api structure --------- Signed-off-by: Connor Peshek <connor@connorpeshek.me> Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
This commit is contained in:
@@ -85,6 +85,7 @@ class GroupSerializer(ModelSerializer):
|
||||
source="roles",
|
||||
required=False,
|
||||
)
|
||||
inherited_roles_obj = SerializerMethodField(allow_null=True)
|
||||
num_pk = IntegerField(read_only=True)
|
||||
|
||||
@property
|
||||
@@ -108,6 +109,13 @@ class GroupSerializer(ModelSerializer):
|
||||
return True
|
||||
return str(request.query_params.get("include_parents", "false")).lower() == "true"
|
||||
|
||||
@property
|
||||
def _should_include_inherited_roles(self) -> bool:
|
||||
request: Request = self.context.get("request", None)
|
||||
if not request:
|
||||
return True
|
||||
return str(request.query_params.get("include_inherited_roles", "false")).lower() == "true"
|
||||
|
||||
@extend_schema_field(PartialUserSerializer(many=True))
|
||||
def get_users_obj(self, instance: Group) -> list[PartialUserSerializer] | None:
|
||||
if not self._should_include_users:
|
||||
@@ -126,6 +134,15 @@ class GroupSerializer(ModelSerializer):
|
||||
return None
|
||||
return RelatedGroupSerializer(instance.parents, many=True).data
|
||||
|
||||
@extend_schema_field(RoleSerializer(many=True))
|
||||
def get_inherited_roles_obj(self, instance: Group) -> list | None:
|
||||
"""Return only inherited roles from ancestor groups (excludes direct roles)"""
|
||||
if not self._should_include_inherited_roles:
|
||||
return None
|
||||
direct_role_pks = instance.roles.values_list("pk", flat=True)
|
||||
inherited_roles = instance.all_roles().exclude(pk__in=direct_role_pks)
|
||||
return RoleSerializer(inherited_roles, many=True).data
|
||||
|
||||
def validate_is_superuser(self, superuser: bool):
|
||||
"""Ensure that the user creating this group has permissions to set the superuser flag"""
|
||||
request: Request = self.context.get("request", None)
|
||||
@@ -167,6 +184,7 @@ class GroupSerializer(ModelSerializer):
|
||||
"attributes",
|
||||
"roles",
|
||||
"roles_obj",
|
||||
"inherited_roles_obj",
|
||||
"children",
|
||||
"children_obj",
|
||||
]
|
||||
@@ -289,6 +307,7 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
|
||||
OpenApiParameter("include_users", bool, default=True),
|
||||
OpenApiParameter("include_children", bool, default=False),
|
||||
OpenApiParameter("include_parents", bool, default=False),
|
||||
OpenApiParameter("include_inherited_roles", bool, default=False),
|
||||
]
|
||||
)
|
||||
def list(self, request, *args, **kwargs):
|
||||
@@ -299,6 +318,7 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
|
||||
OpenApiParameter("include_users", bool, default=True),
|
||||
OpenApiParameter("include_children", bool, default=False),
|
||||
OpenApiParameter("include_parents", bool, default=False),
|
||||
OpenApiParameter("include_inherited_roles", bool, default=False),
|
||||
]
|
||||
)
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.http import Http404
|
||||
from django_filters.filters import AllValuesMultipleFilter, BooleanFilter
|
||||
from django_filters.filters import AllValuesMultipleFilter, BooleanFilter, CharFilter, NumberFilter
|
||||
from django_filters.filterset import FilterSet
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_field
|
||||
@@ -22,7 +22,7 @@ from authentik.blueprints.api import ManagedSerializer
|
||||
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
|
||||
from authentik.core.models import User
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.rbac.decorators import permission_required
|
||||
from authentik.rbac.models import Role, get_permission_choices
|
||||
|
||||
@@ -65,15 +65,63 @@ class RoleSerializer(ManagedSerializer, ModelSerializer):
|
||||
|
||||
|
||||
class RoleFilterSet(FilterSet):
|
||||
"""Filter for PropertyMapping"""
|
||||
"""Filter for Role"""
|
||||
|
||||
managed = extend_schema_field(OpenApiTypes.STR)(AllValuesMultipleFilter(field_name="managed"))
|
||||
|
||||
managed__isnull = BooleanFilter(field_name="managed", lookup_expr="isnull")
|
||||
|
||||
inherited = BooleanFilter(
|
||||
method="filter_inherited",
|
||||
label="Include inherited roles (requires users or ak_groups filter)",
|
||||
)
|
||||
|
||||
users = extend_schema_field(OpenApiTypes.INT)(
|
||||
NumberFilter(
|
||||
method="filter_users",
|
||||
label="Filter by user (use with inherited=true for all roles)",
|
||||
)
|
||||
)
|
||||
|
||||
ak_groups = extend_schema_field(OpenApiTypes.UUID)(
|
||||
CharFilter(
|
||||
method="filter_ak_groups",
|
||||
label="Filter by group (use with inherited=true for all roles)",
|
||||
)
|
||||
)
|
||||
|
||||
def filter_inherited(self, queryset, name, value):
|
||||
"""This filter is handled by filter_users and filter_ak_groups"""
|
||||
return queryset
|
||||
|
||||
def filter_users(self, queryset, name, value):
|
||||
"""Filter roles by user, optionally including inherited roles"""
|
||||
user = User.objects.filter(pk=value).first()
|
||||
if not user:
|
||||
return queryset.none()
|
||||
|
||||
include_inherited = self.data.get("inherited", "").lower() == "true"
|
||||
if include_inherited:
|
||||
return user.all_roles()
|
||||
return queryset.filter(users=user)
|
||||
|
||||
def filter_ak_groups(self, queryset, name, value):
|
||||
"""Filter roles by group, optionally including inherited roles"""
|
||||
group = Group.objects.filter(pk=value).first()
|
||||
if not group:
|
||||
return queryset.none()
|
||||
|
||||
include_inherited = self.data.get("inherited", "").lower() == "true"
|
||||
if include_inherited:
|
||||
return group.all_roles()
|
||||
return queryset.filter(ak_groups=group)
|
||||
|
||||
class Meta:
|
||||
model = Role
|
||||
fields = ["name", "users", "managed"]
|
||||
fields = [
|
||||
"name",
|
||||
"managed",
|
||||
]
|
||||
|
||||
|
||||
class RoleViewSet(UsedByMixin, ModelViewSet):
|
||||
|
||||
@@ -165,7 +165,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
||||
for _, u := range g.UsersObj {
|
||||
if flag.UserPk == u.Pk {
|
||||
// TODO: Is there a better way to clone this object?
|
||||
fg := api.NewGroup(g.Pk, g.NumPk, g.Name, []api.RelatedGroup{}, []api.PartialUser{u}, []api.Role{}, []string{}, []api.RelatedGroup{})
|
||||
fg := api.NewGroup(g.Pk, g.NumPk, g.Name, []api.RelatedGroup{}, []api.PartialUser{u}, []api.Role{}, nil, []string{}, []api.RelatedGroup{})
|
||||
fg.SetUsers([]int32{flag.UserPk})
|
||||
fg.SetAttributes(g.Attributes)
|
||||
fg.SetIsSuperuser(*g.IsSuperuser)
|
||||
|
||||
+28
-5
@@ -3385,6 +3385,11 @@ paths:
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
- in: query
|
||||
name: include_inherited_roles
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
- in: query
|
||||
name: include_parents
|
||||
schema:
|
||||
@@ -3478,6 +3483,11 @@ paths:
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
- in: query
|
||||
name: include_inherited_roles
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
- in: query
|
||||
name: include_parents
|
||||
schema:
|
||||
@@ -20047,6 +20057,16 @@ paths:
|
||||
operationId: rbac_roles_list
|
||||
description: Role viewset
|
||||
parameters:
|
||||
- in: query
|
||||
name: ak_groups
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- in: query
|
||||
name: inherited
|
||||
schema:
|
||||
type: boolean
|
||||
description: Include inherited roles (requires users or ak_groups filter)
|
||||
- in: query
|
||||
name: managed
|
||||
schema:
|
||||
@@ -20067,11 +20087,7 @@ paths:
|
||||
- in: query
|
||||
name: users
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
explode: true
|
||||
style: form
|
||||
type: integer
|
||||
tags:
|
||||
- rbac
|
||||
security:
|
||||
@@ -38770,6 +38786,12 @@ components:
|
||||
items:
|
||||
$ref: '#/components/schemas/Role'
|
||||
readOnly: true
|
||||
inherited_roles_obj:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Role'
|
||||
readOnly: true
|
||||
nullable: true
|
||||
children:
|
||||
type: array
|
||||
items:
|
||||
@@ -38785,6 +38807,7 @@ components:
|
||||
required:
|
||||
- children
|
||||
- children_obj
|
||||
- inherited_roles_obj
|
||||
- name
|
||||
- num_pk
|
||||
- parents_obj
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import "#admin/groups/GroupForm";
|
||||
import "#admin/groups/RelatedUserList";
|
||||
import "#admin/rbac/ObjectPermissionsPage";
|
||||
import "#admin/roles/RelatedRoleList";
|
||||
import "#components/ak-status-label";
|
||||
import "#components/events/ObjectChangelog";
|
||||
import "#elements/CodeMirror";
|
||||
@@ -21,7 +22,7 @@ import { setPageDetails } from "#components/ak-page-navbar";
|
||||
import { CoreApi, Group, RbacPermissionsAssignedByRolesListModelEnum } from "@goauthentik/api";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { CSSResult, html, nothing, PropertyValues } from "lit";
|
||||
import { CSSResult, html, nothing, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
@@ -43,6 +44,7 @@ export class GroupViewPage extends AKElement {
|
||||
.coreGroupsRetrieve({
|
||||
groupUuid: id,
|
||||
includeUsers: false,
|
||||
includeInheritedRoles: true,
|
||||
})
|
||||
.then((group) => {
|
||||
this.group = group;
|
||||
@@ -137,6 +139,34 @@ export class GroupViewPage extends AKElement {
|
||||
</a>
|
||||
</li>`;
|
||||
})}
|
||||
${(this.group.inheritedRolesObj ?? []).map(
|
||||
(role) => {
|
||||
return html`<li>
|
||||
<a
|
||||
href=${`#/identity/roles/${role.pk}`}
|
||||
>${role.name}
|
||||
</a>
|
||||
<pf-tooltip
|
||||
position="top"
|
||||
content=${msg(
|
||||
"Inherited from parent group",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
class="pf-c-label pf-m-outline pf-m-cyan"
|
||||
style="margin-left: 0.5rem;"
|
||||
>
|
||||
<span
|
||||
class="pf-c-label__content"
|
||||
>${msg(
|
||||
"Inherited",
|
||||
)}</span
|
||||
>
|
||||
</span>
|
||||
</pf-tooltip>
|
||||
</li>`;
|
||||
},
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</dd>
|
||||
@@ -203,6 +233,15 @@ export class GroupViewPage extends AKElement {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section
|
||||
role="tabpanel"
|
||||
tabindex="0"
|
||||
slot="page-roles"
|
||||
id="page-roles"
|
||||
aria-label="${msg("Roles")}"
|
||||
>
|
||||
${this.renderTabRoles(this.group)}
|
||||
</section>
|
||||
<ak-rbac-object-permission-page
|
||||
role="tabpanel"
|
||||
tabindex="0"
|
||||
@@ -216,6 +255,42 @@ export class GroupViewPage extends AKElement {
|
||||
</main>`;
|
||||
}
|
||||
|
||||
protected renderTabRoles(group: Group): TemplateResult {
|
||||
return html`
|
||||
<ak-tabs pageIdentifier="groupRoles" vertical>
|
||||
<div
|
||||
role="tabpanel"
|
||||
tabindex="0"
|
||||
slot="page-assigned-roles"
|
||||
id="page-assigned-roles"
|
||||
aria-label=${msg("Assigned Roles")}
|
||||
class="pf-c-page__main-section pf-m-no-padding-mobile"
|
||||
>
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<ak-role-related-list .targetGroup=${group}> </ak-role-related-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
role="tabpanel"
|
||||
tabindex="0"
|
||||
slot="page-all-roles"
|
||||
id="page-all-roles"
|
||||
aria-label=${msg("All Roles")}
|
||||
class="pf-c-page__main-section pf-m-no-padding-mobile"
|
||||
>
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<ak-role-related-list .targetGroup=${group} showInherited>
|
||||
</ak-role-related-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ak-tabs>
|
||||
`;
|
||||
}
|
||||
|
||||
updated(changed: PropertyValues<this>) {
|
||||
super.updated(changed);
|
||||
setPageDetails({
|
||||
|
||||
@@ -14,14 +14,16 @@ import { PaginatedResponse, Table, TableColumn } from "#elements/table/Table";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
import { ifPresent } from "#elements/utils/attributes";
|
||||
|
||||
import { RbacApi, Role, User } from "@goauthentik/api";
|
||||
import { Group, RbacApi, Role, User } from "@goauthentik/api";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { html, nothing, TemplateResult } from "lit";
|
||||
import { html, nothing, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-role-related-add")
|
||||
export class RelatedRoleAdd extends Form<{ roles: string[] }> {
|
||||
#api = new RbacApi(DEFAULT_CONFIG);
|
||||
|
||||
@property({ attribute: false })
|
||||
public user: User | null = null;
|
||||
|
||||
@@ -36,7 +38,7 @@ export class RelatedRoleAdd extends Form<{ roles: string[] }> {
|
||||
await Promise.all(
|
||||
data.roles.map((role) => {
|
||||
if (!this.user) return Promise.resolve();
|
||||
return new RbacApi(DEFAULT_CONFIG).rbacRolesAddUserCreate({
|
||||
return this.#api.rbacRolesAddUserCreate({
|
||||
uuid: role,
|
||||
userAccountSerializerForRoleRequest: {
|
||||
pk: this.user.pk,
|
||||
@@ -86,6 +88,8 @@ export class RelatedRoleAdd extends Form<{ roles: string[] }> {
|
||||
|
||||
@customElement("ak-role-related-list")
|
||||
export class RelatedRoleList extends Table<Role> {
|
||||
#api = new RbacApi(DEFAULT_CONFIG);
|
||||
|
||||
checkbox = true;
|
||||
clearOnRefresh = true;
|
||||
protected override searchEnabled = true;
|
||||
@@ -96,19 +100,54 @@ export class RelatedRoleList extends Table<Role> {
|
||||
@property({ attribute: false })
|
||||
public targetUser: User | null = null;
|
||||
|
||||
@property({ attribute: false })
|
||||
public targetGroup: Group | null = null;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public showInherited = false;
|
||||
|
||||
willUpdate(changedProperties: PropertyValues<this>) {
|
||||
super.willUpdate(changedProperties);
|
||||
if (changedProperties.has("showInherited")) {
|
||||
// Disable checkboxes in showInherited mode (view-only)
|
||||
this.checkbox = !this.showInherited;
|
||||
}
|
||||
}
|
||||
|
||||
async apiEndpoint(): Promise<PaginatedResponse<Role>> {
|
||||
return new RbacApi(DEFAULT_CONFIG).rbacRolesList({
|
||||
...(await this.defaultEndpointConfig()),
|
||||
users: this.targetUser ? [this.targetUser.pk] : [],
|
||||
const config = await this.defaultEndpointConfig();
|
||||
|
||||
if (this.targetGroup) {
|
||||
return this.#api.rbacRolesList({
|
||||
...config,
|
||||
akGroups: this.targetGroup.pk,
|
||||
inherited: this.showInherited,
|
||||
});
|
||||
}
|
||||
|
||||
return this.#api.rbacRolesList({
|
||||
...config,
|
||||
users: this.targetUser?.pk,
|
||||
inherited: this.showInherited,
|
||||
});
|
||||
}
|
||||
|
||||
protected columns: TableColumn[] = [
|
||||
[msg("Name"), "name"],
|
||||
[msg("Actions"), null, msg("Row Actions")],
|
||||
];
|
||||
protected get columns(): TableColumn[] {
|
||||
// Hide actions column in showInherited mode (view-only)
|
||||
if (this.showInherited) {
|
||||
return [[msg("Name"), "name"]];
|
||||
}
|
||||
return [
|
||||
[msg("Name"), "name"],
|
||||
[msg("Actions"), null, msg("Row Actions")],
|
||||
];
|
||||
}
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
renderToolbarSelected(): SlottedTemplateResult {
|
||||
// Don't render Remove button in showInherited mode (view-only)
|
||||
if (this.showInherited) {
|
||||
return nothing;
|
||||
}
|
||||
const disabled = !this.selectedElements.length;
|
||||
return html`<ak-forms-delete-bulk
|
||||
objectLabel=${msg("Role(s)")}
|
||||
@@ -120,7 +159,7 @@ export class RelatedRoleList extends Table<Role> {
|
||||
.objects=${this.selectedElements}
|
||||
.delete=${(item: Role) => {
|
||||
if (!this.targetUser) return;
|
||||
return new RbacApi(DEFAULT_CONFIG).rbacRolesRemoveUserCreate({
|
||||
return this.#api.rbacRolesRemoveUserCreate({
|
||||
uuid: item.pk,
|
||||
userAccountSerializerForRoleRequest: {
|
||||
pk: this.targetUser.pk,
|
||||
@@ -134,10 +173,46 @@ export class RelatedRoleList extends Table<Role> {
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
protected isInherited(role: Role): boolean {
|
||||
if (this.targetGroup) {
|
||||
// For groups, check if role is in direct roles
|
||||
if (!this.targetGroup.roles) return false;
|
||||
|
||||
return !this.targetGroup.roles.includes(role.pk);
|
||||
}
|
||||
|
||||
if (this.targetUser) {
|
||||
// For users, check if role is in direct roles
|
||||
|
||||
if (!this.targetUser.roles) return false;
|
||||
|
||||
return !this.targetUser.roles.includes(role.pk);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
row(item: Role): SlottedTemplateResult[] {
|
||||
const inherited = this.showInherited && this.isInherited(item);
|
||||
const inheritedTooltip = this.targetGroup
|
||||
? msg("Inherited from parent group")
|
||||
: msg("Inherited from group");
|
||||
const nameCell = html`<a href="#/identity/roles/${item.pk}">${item.name}</a> ${inherited
|
||||
? html`<pf-tooltip position="top" content=${inheritedTooltip}>
|
||||
<span class="pf-c-label pf-m-outline pf-m-cyan">
|
||||
<span class="pf-c-label__content"> ${msg("Inherited")}</span>
|
||||
</span>
|
||||
</pf-tooltip>`
|
||||
: nothing}`;
|
||||
|
||||
// Hide actions in showInherited mode (view-only)
|
||||
if (this.showInherited) {
|
||||
return [nameCell];
|
||||
}
|
||||
|
||||
return [
|
||||
html`<a href="#/identity/roles/${item.pk}">${item.name}</a>`,
|
||||
html` <ak-forms-modal>
|
||||
nameCell,
|
||||
html`<ak-forms-modal>
|
||||
<span slot="submit">${msg("Update")}</span>
|
||||
<span slot="header">${msg("Update Role")}</span>
|
||||
<ak-role-form slot="form" .instancePk=${item.pk}> </ak-role-form>
|
||||
@@ -151,6 +226,10 @@ export class RelatedRoleList extends Table<Role> {
|
||||
}
|
||||
|
||||
renderToolbar(): TemplateResult {
|
||||
// Hide add buttons in showInherited mode (view-only)
|
||||
if (this.showInherited || this.targetGroup) {
|
||||
return html`${super.renderToolbar()}`;
|
||||
}
|
||||
return html`
|
||||
${this.targetUser
|
||||
? html`<ak-forms-modal>
|
||||
|
||||
@@ -49,7 +49,9 @@ export class RoleSelectModal extends TableModal<Role> {
|
||||
renderModalInner(): SlottedTemplateResult {
|
||||
return html`<section class="pf-c-modal-box__header pf-c-page__main-section pf-m-light">
|
||||
<div class="pf-c-content">
|
||||
<h1 class="pf-c-title pf-m-2xl">${msg("Assign User to Groups")}</h1>
|
||||
<h1 class="pf-c-title pf-m-2xl">
|
||||
${msg("Select roles to attach to the user")}
|
||||
</h1>
|
||||
</div>
|
||||
</section>
|
||||
<section class="pf-c-modal-box__body pf-m-light">${this.renderTable()}</section>
|
||||
|
||||
@@ -370,6 +370,42 @@ export class UserViewPage extends WithCapabilitiesConfig(WithSession(AKElement))
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected renderTabRoles(user: User): TemplateResult {
|
||||
return html`
|
||||
<ak-tabs pageIdentifier="userRoles" vertical>
|
||||
<div
|
||||
role="tabpanel"
|
||||
tabindex="0"
|
||||
slot="page-assigned-roles"
|
||||
id="page-assigned-roles"
|
||||
aria-label=${msg("Assigned Roles")}
|
||||
class="pf-c-page__main-section pf-m-no-padding-mobile"
|
||||
>
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<ak-role-related-list .targetUser=${user}> </ak-role-related-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
role="tabpanel"
|
||||
tabindex="0"
|
||||
slot="page-all-roles"
|
||||
id="page-all-roles"
|
||||
aria-label=${msg("All Roles")}
|
||||
class="pf-c-page__main-section pf-m-no-padding-mobile"
|
||||
>
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<ak-role-related-list .targetUser=${user} showInherited>
|
||||
</ak-role-related-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ak-tabs>
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.user) {
|
||||
return nothing;
|
||||
@@ -452,13 +488,8 @@ export class UserViewPage extends WithCapabilitiesConfig(WithSession(AKElement))
|
||||
slot="page-roles"
|
||||
id="page-roles"
|
||||
aria-label=${msg("Roles")}
|
||||
class="pf-c-page__main-section pf-m-no-padding-mobile"
|
||||
>
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<ak-role-related-list .targetUser=${this.user}> </ak-role-related-list>
|
||||
</div>
|
||||
</div>
|
||||
${this.renderTabRoles(this.user)}
|
||||
</div>
|
||||
<div
|
||||
role="tabpanel"
|
||||
|
||||
Reference in New Issue
Block a user