Compare commits
9 Commits
imports-fo
...
user-direc
Author | SHA1 | Date | |
---|---|---|---|
f97f4a902c | |||
de8da15293 | |||
cd7d96cc58 | |||
dcaa41716b | |||
910b430d25 | |||
832c00c155 | |||
399fa0120c | |||
3de3c98ed8 | |||
92911d1d0f |
99
authentik/core/api/user_directory.py
Normal file
99
authentik/core/api/user_directory.py
Normal file
@ -0,0 +1,99 @@
|
||||
"""User directory API Views"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from drf_spectacular.utils import extend_schema, inline_serializer
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import SerializerMethodField
|
||||
from rest_framework.serializers import CharField, DictField, ListField, ModelSerializer
|
||||
from rest_framework.views import Request, Response
|
||||
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.rbac.permissions import HasPermission
|
||||
from authentik.tenants.utils import get_current_tenant
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class UserDirectorySerializer(ModelSerializer):
|
||||
"""User Directory Serializer"""
|
||||
|
||||
user_fields = SerializerMethodField()
|
||||
attributes = SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
"pk",
|
||||
"user_fields",
|
||||
"attributes",
|
||||
]
|
||||
|
||||
def get_user_fields(self, obj: User) -> dict[str, Any]:
|
||||
"""Get directory fields"""
|
||||
fields = {}
|
||||
user_directory_fields = get_current_tenant().user_directory_fields
|
||||
for f in ("name", "username", "email", "avatar"):
|
||||
if f in user_directory_fields:
|
||||
fields[f] = getattr(obj, f)
|
||||
if "groups" in user_directory_fields:
|
||||
fields["groups"] = [g.name for g in obj.all_groups().order_by("name")]
|
||||
return fields
|
||||
|
||||
def get_attributes(self, obj: User) -> dict[str, Any]:
|
||||
"""Get directory attributes"""
|
||||
attributes = {}
|
||||
for field in get_current_tenant().user_directory_attributes:
|
||||
path = field.get("attribute", None)
|
||||
if path is not None:
|
||||
attributes[path] = obj.attributes.get(path, None)
|
||||
return attributes
|
||||
|
||||
|
||||
class UserDirectoryViewSet(ReadOnlyModelViewSet):
|
||||
"""User Directory Viewset"""
|
||||
|
||||
queryset = User.objects.none()
|
||||
ordering = ["username"]
|
||||
ordering_fields = ["username", "email", "name"]
|
||||
serializer_class = UserDirectorySerializer
|
||||
permission_classes = [HasPermission("authentik_rbac.view_user_directory")]
|
||||
|
||||
def get_queryset(self):
|
||||
return User.objects.all().exclude(pk=get_anonymous_user().pk).filter(is_active=True)
|
||||
|
||||
@property
|
||||
def search_fields(self):
|
||||
"""Get search fields"""
|
||||
current_tenant = get_current_tenant()
|
||||
return list(
|
||||
f for f in current_tenant.user_directory_fields if f not in ("avatar", "groups")
|
||||
) + list(
|
||||
f"attributes__{attr['attribute']}"
|
||||
for attr in current_tenant.user_directory_attributes
|
||||
if "attribute" in attr
|
||||
)
|
||||
|
||||
@extend_schema(
|
||||
responses={
|
||||
200: inline_serializer(
|
||||
"UserDirectoryFieldsSerializer",
|
||||
{
|
||||
"fields": ListField(child=CharField()),
|
||||
"attributes": ListField(child=DictField(child=CharField())),
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
@action(detail=False, pagination_class=None)
|
||||
def fields(self, request: Request) -> Response:
|
||||
"""Get user directory fields"""
|
||||
return Response(
|
||||
{
|
||||
"fields": request.tenant.user_directory_fields,
|
||||
"attributes": request.tenant.user_directory_attributes,
|
||||
}
|
||||
)
|
@ -17,6 +17,7 @@ from authentik.core.api.providers import ProviderViewSet
|
||||
from authentik.core.api.sources import SourceViewSet, UserSourceConnectionViewSet
|
||||
from authentik.core.api.tokens import TokenViewSet
|
||||
from authentik.core.api.transactional_applications import TransactionalApplicationView
|
||||
from authentik.core.api.user_directory import UserDirectoryViewSet
|
||||
from authentik.core.api.users import UserViewSet
|
||||
from authentik.core.views import apps
|
||||
from authentik.core.views.debug import AccessDeniedView
|
||||
@ -82,6 +83,7 @@ api_urlpatterns = [
|
||||
),
|
||||
("core/groups", GroupViewSet),
|
||||
("core/users", UserViewSet),
|
||||
("core/user_directory", UserDirectoryViewSet),
|
||||
("core/tokens", TokenViewSet),
|
||||
("sources/all", SourceViewSet),
|
||||
("sources/user_connections/all", UserSourceConnectionViewSet),
|
||||
|
@ -4,7 +4,6 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_rbac", "0003_alter_systempermission_options"),
|
||||
]
|
||||
@ -17,6 +16,9 @@ class Migration(migrations.Migration):
|
||||
"managed": False,
|
||||
"permissions": [
|
||||
("view_system_info", "Can view system info"),
|
||||
("view_system_tasks", "Can view system tasks"),
|
||||
("view_user_directory", "Can view users in the user directory"),
|
||||
("run_system_tasks", "Can run system tasks"),
|
||||
("access_admin_interface", "Can access admin interface"),
|
||||
("view_system_settings", "Can view system settings"),
|
||||
("edit_system_settings", "Can edit system settings"),
|
||||
|
@ -67,6 +67,9 @@ class SystemPermission(models.Model):
|
||||
verbose_name_plural = _("System permissions")
|
||||
permissions = [
|
||||
("view_system_info", _("Can view system info")),
|
||||
("view_system_tasks", _("Can view system tasks")),
|
||||
("view_user_directory", _("Can view users in the user directory")),
|
||||
("run_system_tasks", _("Can run system tasks")),
|
||||
("access_admin_interface", _("Can access admin interface")),
|
||||
("view_system_settings", _("Can view system settings")),
|
||||
("edit_system_settings", _("Can edit system settings")),
|
||||
|
@ -23,6 +23,8 @@ class SettingsSerializer(ModelSerializer):
|
||||
"footer_links",
|
||||
"gdpr_compliance",
|
||||
"impersonation",
|
||||
"user_directory_fields",
|
||||
"user_directory_attributes",
|
||||
"default_token_duration",
|
||||
"default_token_length",
|
||||
]
|
||||
|
@ -0,0 +1,30 @@
|
||||
# Generated by Django 5.0.1 on 2024-01-24 14:27
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
import authentik.tenants.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("authentik_tenants", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="tenant",
|
||||
name="user_directory_attributes",
|
||||
field=models.JSONField(
|
||||
blank=True, default=list, help_text="Attributes to show in the user directory."
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="tenant",
|
||||
name="user_directory_fields",
|
||||
field=models.JSONField(
|
||||
blank=True,
|
||||
default=authentik.tenants.models._default_user_directory_fields,
|
||||
help_text="Fields to show in the user directory.",
|
||||
),
|
||||
),
|
||||
]
|
13
authentik/tenants/migrations/0004_merge_20240524_1807.py
Normal file
13
authentik/tenants/migrations/0004_merge_20240524_1807.py
Normal file
@ -0,0 +1,13 @@
|
||||
# Generated by Django 5.0.6 on 2024-05-24 18:07
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_tenants", "0002_tenant_user_directory_and_more"),
|
||||
("authentik_tenants", "0003_alter_tenant_default_token_duration"),
|
||||
]
|
||||
|
||||
operations = []
|
@ -37,6 +37,10 @@ def _validate_schema_name(name):
|
||||
)
|
||||
|
||||
|
||||
def _default_user_directory_fields():
|
||||
return ["avatar", "name", "username", "email", "groups"]
|
||||
|
||||
|
||||
class Tenant(TenantMixin, SerializerModel):
|
||||
"""Tenant"""
|
||||
|
||||
@ -85,6 +89,14 @@ class Tenant(TenantMixin, SerializerModel):
|
||||
impersonation = models.BooleanField(
|
||||
help_text=_("Globally enable/disable impersonation."), default=True
|
||||
)
|
||||
user_directory_fields = models.JSONField(
|
||||
help_text=_("Fields to show in the user directory."),
|
||||
default=_default_user_directory_fields,
|
||||
blank=True,
|
||||
)
|
||||
user_directory_attributes = models.JSONField(
|
||||
help_text=_("Attributes to show in the user directory."), default=list, blank=True
|
||||
)
|
||||
default_token_duration = models.TextField(
|
||||
help_text=_("Default token duration"),
|
||||
default=DEFAULT_TOKEN_DURATION,
|
||||
|
175
schema.yml
175
schema.yml
@ -4537,6 +4537,119 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
description: ''
|
||||
/core/user_directory/:
|
||||
get:
|
||||
operationId: core_user_directory_list
|
||||
description: User Directory Viewset
|
||||
parameters:
|
||||
- name: ordering
|
||||
required: false
|
||||
in: query
|
||||
description: Which field to use when ordering the results.
|
||||
schema:
|
||||
type: string
|
||||
- name: page
|
||||
required: false
|
||||
in: query
|
||||
description: A page number within the paginated result set.
|
||||
schema:
|
||||
type: integer
|
||||
- name: page_size
|
||||
required: false
|
||||
in: query
|
||||
description: Number of results to return per page.
|
||||
schema:
|
||||
type: integer
|
||||
- name: search
|
||||
required: false
|
||||
in: query
|
||||
description: A search term.
|
||||
schema:
|
||||
type: string
|
||||
tags:
|
||||
- core
|
||||
security:
|
||||
- authentik: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PaginatedUserDirectoryList'
|
||||
description: ''
|
||||
'400':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
description: ''
|
||||
'403':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
description: ''
|
||||
/core/user_directory/{id}/:
|
||||
get:
|
||||
operationId: core_user_directory_retrieve
|
||||
description: User Directory Viewset
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
schema:
|
||||
type: integer
|
||||
description: A unique integer value identifying this User.
|
||||
required: true
|
||||
tags:
|
||||
- core
|
||||
security:
|
||||
- authentik: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UserDirectory'
|
||||
description: ''
|
||||
'400':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
description: ''
|
||||
'403':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
description: ''
|
||||
/core/user_directory/fields/:
|
||||
get:
|
||||
operationId: core_user_directory_fields_retrieve
|
||||
description: Get user directory fields
|
||||
tags:
|
||||
- core
|
||||
security:
|
||||
- authentik: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UserDirectoryFields'
|
||||
description: ''
|
||||
'400':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
description: ''
|
||||
'403':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
description: ''
|
||||
/core/users/:
|
||||
get:
|
||||
operationId: core_users_list
|
||||
@ -40859,6 +40972,18 @@ components:
|
||||
required:
|
||||
- pagination
|
||||
- results
|
||||
PaginatedUserDirectoryList:
|
||||
type: object
|
||||
properties:
|
||||
pagination:
|
||||
$ref: '#/components/schemas/Pagination'
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/UserDirectory'
|
||||
required:
|
||||
- pagination
|
||||
- results
|
||||
PaginatedUserList:
|
||||
type: object
|
||||
properties:
|
||||
@ -43614,6 +43739,10 @@ components:
|
||||
impersonation:
|
||||
type: boolean
|
||||
description: Globally enable/disable impersonation.
|
||||
user_directory_fields:
|
||||
description: Fields to show in the user directory.
|
||||
user_directory_attributes:
|
||||
description: Attributes to show in the user directory.
|
||||
default_token_duration:
|
||||
type: string
|
||||
minLength: 1
|
||||
@ -46901,6 +47030,10 @@ components:
|
||||
impersonation:
|
||||
type: boolean
|
||||
description: Globally enable/disable impersonation.
|
||||
user_directory_fields:
|
||||
description: Fields to show in the user directory.
|
||||
user_directory_attributes:
|
||||
description: Attributes to show in the user directory.
|
||||
default_token_duration:
|
||||
type: string
|
||||
description: Default token duration
|
||||
@ -46940,6 +47073,10 @@ components:
|
||||
impersonation:
|
||||
type: boolean
|
||||
description: Globally enable/disable impersonation.
|
||||
user_directory_fields:
|
||||
description: Fields to show in the user directory.
|
||||
user_directory_attributes:
|
||||
description: Attributes to show in the user directory.
|
||||
default_token_duration:
|
||||
type: string
|
||||
minLength: 1
|
||||
@ -48032,6 +48169,44 @@ components:
|
||||
$ref: '#/components/schemas/FlowSetRequest'
|
||||
required:
|
||||
- name
|
||||
UserDirectory:
|
||||
type: object
|
||||
description: User Directory Serializer
|
||||
properties:
|
||||
pk:
|
||||
type: integer
|
||||
readOnly: true
|
||||
title: ID
|
||||
user_fields:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
description: Get directory fields
|
||||
readOnly: true
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
description: Get directory attributes
|
||||
readOnly: true
|
||||
required:
|
||||
- attributes
|
||||
- pk
|
||||
- user_fields
|
||||
UserDirectoryFields:
|
||||
type: object
|
||||
properties:
|
||||
fields:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
attributes:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
required:
|
||||
- attributes
|
||||
- fields
|
||||
UserFieldsEnum:
|
||||
enum:
|
||||
- email
|
||||
|
@ -193,6 +193,42 @@ export class AdminSettingsForm extends Form<SettingsRequest> {
|
||||
help=${msg("Globally enable/disable impersonation.")}
|
||||
>
|
||||
</ak-switch-input>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("User directory fields")}
|
||||
name="userDirectoryFields"
|
||||
>
|
||||
<ak-codemirror
|
||||
mode=${CodeMirrorMode.YAML}
|
||||
.value="${first(this._settings?.userDirectoryFields, [
|
||||
"name",
|
||||
"username",
|
||||
"email",
|
||||
"avatars",
|
||||
"groups",
|
||||
])}"
|
||||
></ak-codemirror>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"This option configures what user fields are shown in the user directory. It must be a valid JSON list and can be used as follows, with all possible values included:",
|
||||
)}
|
||||
<code>["name", "username", "email", "avatars", "groups"]</code>
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("User directory attributes")}
|
||||
name="userDirectoryAttributes"
|
||||
>
|
||||
<ak-codemirror
|
||||
mode=${CodeMirrorMode.YAML}
|
||||
.value="${first(this._settings?.userDirectoryAttributes, [])}"
|
||||
></ak-codemirror>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"This option configures what user attributes are shown in the user directory. It must be a valid JSON list and can be used as follows:",
|
||||
)}
|
||||
<code>[{"attribute": "phone_number", "display_name": "Phone"}]</code>
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-text-input
|
||||
name="defaultTokenDuration"
|
||||
label=${msg("Default token duration")}
|
||||
|
@ -53,7 +53,7 @@ export class BlueprintListPage extends TablePage<BlueprintInstance> {
|
||||
return msg("Automate and template configuration within authentik.");
|
||||
}
|
||||
pageIcon(): string {
|
||||
return "pf-icon pf-icon-blueprint";
|
||||
return "fa fa-user";
|
||||
}
|
||||
|
||||
expandable = true;
|
||||
|
@ -141,6 +141,7 @@ export class PageHeader extends WithBrandConfig(AKElement) {
|
||||
return html` <ak-enterprise-status interface="admin"></ak-enterprise-status>
|
||||
<div class="bar">
|
||||
<button
|
||||
part="sidebar-trigger"
|
||||
class="sidebar-trigger pf-c-button pf-m-plain"
|
||||
@click=${() => {
|
||||
this.dispatchEvent(
|
||||
|
@ -70,14 +70,17 @@ export abstract class TablePage<T> extends Table<T> {
|
||||
</button>`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
renderPageHeader(): TemplateResult {
|
||||
return html`<ak-page-header
|
||||
icon=${this.pageIcon()}
|
||||
header=${this.pageTitle()}
|
||||
description=${ifDefined(this.pageDescription())}
|
||||
>
|
||||
</ak-page-header>
|
||||
${this.renderSectionBefore()}
|
||||
icon=${this.pageIcon()}
|
||||
header=${this.pageTitle()}
|
||||
description=${ifDefined(this.pageDescription())}
|
||||
>
|
||||
</ak-page-header>`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`${this.renderPageHeader()} ${this.renderSectionBefore()}
|
||||
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||
<div class="pf-c-sidebar pf-m-gutter">
|
||||
<div class="pf-c-sidebar__main">
|
||||
|
@ -8,6 +8,10 @@ export const ROUTES: Route[] = [
|
||||
new Route(new RegExp("^/$")).redirect("/library"),
|
||||
new Route(new RegExp("^#.*")).redirect("/library"),
|
||||
new Route(new RegExp("^/library$"), async () => html`<ak-library></ak-library>`),
|
||||
new Route(new RegExp("^/directory"), async () => {
|
||||
await import("@goauthentik/user/user-directory/UserDirectoryPage");
|
||||
return html`<ak-user-directory></ak-user-directory>`;
|
||||
}),
|
||||
new Route(new RegExp("^/settings$"), async () => {
|
||||
await import("@goauthentik/user/user-settings/UserSettingsPage");
|
||||
return html`<ak-user-settings></ak-user-settings>`;
|
||||
|
@ -159,6 +159,13 @@ class UserInterfacePresentation extends AKElement {
|
||||
.otherwise(() => this.me.user.username);
|
||||
}
|
||||
|
||||
get canAccessUserDirectory() {
|
||||
return (
|
||||
this.me.user.isSuperuser ||
|
||||
this.me.user.systemPermissions.includes("can_view_user_directory")
|
||||
);
|
||||
}
|
||||
|
||||
get canAccessAdmin() {
|
||||
return (
|
||||
this.me.user.isSuperuser ||
|
||||
@ -204,6 +211,8 @@ class UserInterfacePresentation extends AKElement {
|
||||
<!-- -->
|
||||
${this.renderNotificationDrawerTrigger()}
|
||||
<!-- -->
|
||||
${this.renderUserDirectory()}
|
||||
<!-- -->
|
||||
${this.renderSettings()}
|
||||
<div class="pf-c-page__header-tools-item">
|
||||
<a
|
||||
@ -355,6 +364,20 @@ class UserInterfacePresentation extends AKElement {
|
||||
</a>`;
|
||||
}
|
||||
|
||||
renderUserDirectory() {
|
||||
if (!this.canAccessUserDirectory) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html` <div class="pf-c-page__header-tools-item">
|
||||
<a class="pf-c-button pf-m-plain" type="button" href="#/directory">
|
||||
<pf-tooltip position="top" content=${msg("User directory")}>
|
||||
<i class="pf-icon pf-icon-project" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</a>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
renderImpersonation() {
|
||||
if (!this.me.original) {
|
||||
return nothing;
|
||||
|
131
web/src/user/user-directory/UserDirectoryPage.ts
Normal file
131
web/src/user/user-directory/UserDirectoryPage.ts
Normal file
@ -0,0 +1,131 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { renderDescriptionList } from "@goauthentik/components/DescriptionList.js";
|
||||
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||
import { TableColumn } from "@goauthentik/elements/table/Table";
|
||||
import { TablePage } from "@goauthentik/elements/table/TablePage";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
|
||||
import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";
|
||||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
||||
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||
|
||||
import { CoreApi, UserDirectory } from "@goauthentik/api";
|
||||
|
||||
const knownFields: Record<string, string> = {
|
||||
avatar: "",
|
||||
username: msg("Username"),
|
||||
name: msg("Name"),
|
||||
email: msg("Email"),
|
||||
};
|
||||
|
||||
type UserFieldAttributes = { display_name: string; attribute: string };
|
||||
|
||||
@customElement("ak-user-directory")
|
||||
export class UserDirectoryPage extends TablePage<UserDirectory> {
|
||||
expandable = true;
|
||||
|
||||
searchEnabled(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
pageTitle(): string {
|
||||
return msg("User Directory");
|
||||
}
|
||||
|
||||
pageDescription(): string {
|
||||
return msg("Display a list of users on this system.");
|
||||
}
|
||||
|
||||
pageIcon(): string {
|
||||
return "pf-icon pf-icon-project";
|
||||
}
|
||||
|
||||
@property()
|
||||
order = "username";
|
||||
|
||||
@state()
|
||||
fields?: string[];
|
||||
|
||||
@state()
|
||||
userFieldAttributes?: object[] = [];
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
...super.styles,
|
||||
PFDescriptionList,
|
||||
PFCard,
|
||||
PFAlert,
|
||||
PFAvatar,
|
||||
css`
|
||||
ak-page-header::part(sidebar-trigger) {
|
||||
display: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
async apiEndpoint(): Promise<PaginatedResponse<UserDirectory>> {
|
||||
const fields = await new CoreApi(DEFAULT_CONFIG).coreUserDirectoryFieldsRetrieve();
|
||||
this.fields = fields.fields;
|
||||
this.userFieldAttributes = fields.attributes;
|
||||
return await new CoreApi(DEFAULT_CONFIG).coreUserDirectoryList(
|
||||
await this.defaultEndpointConfig(),
|
||||
);
|
||||
}
|
||||
|
||||
columns() {
|
||||
return (this.fields ?? [])
|
||||
.filter((item) => item in knownFields)
|
||||
.map((item) =>
|
||||
item === "avatar"
|
||||
? new TableColumn(knownFields[item])
|
||||
: new TableColumn(knownFields[item], item),
|
||||
);
|
||||
}
|
||||
|
||||
row(item: UserDirectory) {
|
||||
return (this.fields ?? [])
|
||||
.filter((field: string) => Object.hasOwn(knownFields, field))
|
||||
.map((field: string) =>
|
||||
field !== "avatar"
|
||||
? html`${item.userFields[field]}`
|
||||
: html` <img
|
||||
class="pf-c-avatar"
|
||||
src=${item.userFields[field]}
|
||||
alt="${msg("Avatar image")}"
|
||||
/>`,
|
||||
);
|
||||
}
|
||||
|
||||
renderExpanded(item: UserDirectory) {
|
||||
const groupDescription =
|
||||
this.fields?.includes("groups") && (item.userFields["groups"] ?? []).length > 0
|
||||
? [
|
||||
[msg("Groups")],
|
||||
item.userFields["groups"].map(
|
||||
(group: string) => html`
|
||||
<div class="pf-c-description-list__text">${group}</div>
|
||||
`,
|
||||
),
|
||||
]
|
||||
: [];
|
||||
|
||||
const userDescriptions = ((this.userFieldAttributes ?? []) as UserFieldAttributes[])
|
||||
.filter(({ attribute }) => attribute !== null)
|
||||
.map(({ display_name, attribute }) => [display_name, item.attributes[attribute]]);
|
||||
|
||||
const toShow = [...groupDescription, ...userDescriptions];
|
||||
|
||||
return toShow.length > 1
|
||||
? html`<td role="cell" colspan="3">
|
||||
<div class="pf-c-table__expandable-row-content">
|
||||
${renderDescriptionList(toShow)}
|
||||
</div>
|
||||
</td>`
|
||||
: html``;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user