web/admin: revamped rbac and user settings tabs (#8299)
* web/admin: fix duplicate RBAC preview banner on permission modal Signed-off-by: Jens Langhammer <jens@goauthentik.io> * switch non-embedded permission page to use vertical tabs Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix some leftover html? Signed-off-by: Jens Langhammer <jens@goauthentik.io> * move stuff into vertical subtab Signed-off-by: Jens Langhammer <jens@goauthentik.io> * show all of users permission tabs on one main tab Signed-off-by: Jens Langhammer <jens@goauthentik.io> * rework role page to match user page Signed-off-by: Jens Langhammer <jens@goauthentik.io> * use separate tabs Signed-off-by: Jens Langhammer <jens@goauthentik.io> * rename role permission tables to match user tables Signed-off-by: Jens Langhammer <jens@goauthentik.io> * rename to credentials and tokens Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add country icon to session list Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add oauth access token list Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add helper to get relative time Signed-off-by: Jens Langhammer <jens@goauthentik.io> * use pfdivider Signed-off-by: Jens Langhammer <jens@goauthentik.io> * replace plain hr with pf-c-divider Signed-off-by: Jens Langhammer <jens@goauthentik.io> * use new logic for showing relative time in charts Signed-off-by: Jens Langhammer <jens@goauthentik.io> * use consistent relative time for event display Signed-off-by: Jens Langhammer <jens@goauthentik.io> * remove more leftovers Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix some alignment issues on the admin dashboard Signed-off-by: Jens Langhammer <jens@goauthentik.io> * update storybook map Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add sanity check to event app lookup Signed-off-by: Jens Langhammer <jens@goauthentik.io> * make api drawer header fixed Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix table padding for toggle Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix notification drawer for user interface Signed-off-by: Jens Langhammer <jens@goauthentik.io> * enable system task search Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix formatting, exclude generated script from formatting Signed-off-by: Jens Langhammer <jens@goauthentik.io> * web: minor fixes There's a renderer (it's not a component, not yet) for producing definition lists without the risk of missing a class or tag. Breaking conditionally rendered components out to make their use easier to identify. * fix prettier Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix outpost form Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix more flaky tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * re-create locale Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add some description for different permission views Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix system task search Signed-off-by: Jens Langhammer <jens@goauthentik.io> * update docs Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io> Co-authored-by: Ken Sternberg <ken@goauthentik.io>
This commit is contained in:
@ -84,6 +84,7 @@ export class AggregateCard extends AKElement {
|
||||
${this.renderInner()}
|
||||
${this.subtext ? html`<p class="subtext">${this.subtext}</p>` : html``}
|
||||
</div>
|
||||
<div class="pf-c-card__footer"> </div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { getRelativeTime } from "@goauthentik/app/common/utils";
|
||||
import { EVENT_REFRESH, EVENT_THEME_CHANGE } from "@goauthentik/common/constants";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/EmptyState";
|
||||
@ -18,7 +19,7 @@ import { ArcElement, BarElement } from "chart.js";
|
||||
import { LinearScale, TimeScale } from "chart.js";
|
||||
import "chartjs-adapter-moment";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
|
||||
@ -161,9 +162,7 @@ export abstract class AKChart<T> extends AKElement {
|
||||
|
||||
timeTickCallback(tickValue: string | number, index: number, ticks: Tick[]): string {
|
||||
const valueStamp = ticks[index];
|
||||
const delta = Date.now() - valueStamp.value;
|
||||
const ago = Math.round(delta / 1000 / 3600);
|
||||
return msg(str`${ago} hour(s) ago`);
|
||||
return getRelativeTime(new Date(valueStamp.value));
|
||||
}
|
||||
|
||||
getOptions(): ChartOptions {
|
||||
|
||||
@ -25,8 +25,11 @@ export class APIDrawer extends AKElement {
|
||||
PFContent,
|
||||
PFDropdown,
|
||||
css`
|
||||
:host {
|
||||
--header-height: 114px;
|
||||
}
|
||||
.pf-c-notification-drawer__header {
|
||||
height: 114px;
|
||||
height: var(--header-height);
|
||||
align-items: center;
|
||||
}
|
||||
.pf-c-notification-drawer__header-action,
|
||||
@ -41,6 +44,9 @@ export class APIDrawer extends AKElement {
|
||||
.pf-c-notification-drawer__body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.pf-c-notification-drawer__list {
|
||||
max-height: calc(100vh - var(--header-height));
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
93
web/src/elements/oauth/UserAccessTokenList.ts
Normal file
93
web/src/elements/oauth/UserAccessTokenList.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { uiConfig } from "@goauthentik/common/ui/config";
|
||||
import "@goauthentik/components/ak-status-label";
|
||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||
import { Table, TableColumn } from "@goauthentik/elements/table/Table";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css";
|
||||
|
||||
import { ExpiringBaseGrantModel, Oauth2Api, TokenModel } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-user-oauth-access-token-list")
|
||||
export class UserOAuthAccessTokenList extends Table<TokenModel> {
|
||||
expandable = true;
|
||||
|
||||
@property({ type: Number })
|
||||
userId?: number;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return super.styles.concat(PFFlex);
|
||||
}
|
||||
|
||||
async apiEndpoint(page: number): Promise<PaginatedResponse<TokenModel>> {
|
||||
return new Oauth2Api(DEFAULT_CONFIG).oauth2AccessTokensList({
|
||||
user: this.userId,
|
||||
ordering: "expires",
|
||||
page: page,
|
||||
pageSize: (await uiConfig()).pagination.perPage,
|
||||
});
|
||||
}
|
||||
|
||||
checkbox = true;
|
||||
order = "-expires";
|
||||
|
||||
columns(): TableColumn[] {
|
||||
return [
|
||||
new TableColumn(msg("Provider"), "provider"),
|
||||
new TableColumn(msg("Revoked?"), "revoked"),
|
||||
new TableColumn(msg("Expires"), "expires"),
|
||||
new TableColumn(msg("Scopes"), "scope"),
|
||||
];
|
||||
}
|
||||
|
||||
renderExpanded(item: TokenModel): TemplateResult {
|
||||
return html` <td role="cell" colspan="4">
|
||||
<div class="pf-c-table__expandable-row-content">
|
||||
<div class="pf-l-flex">
|
||||
<div class="pf-l-flex__item">
|
||||
<h3>${msg("ID Token")}</h3>
|
||||
<pre>${item.idToken}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>`;
|
||||
}
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
return html`<ak-forms-delete-bulk
|
||||
objectLabel=${msg("Refresh Tokens(s)")}
|
||||
.objects=${this.selectedElements}
|
||||
.usedBy=${(item: ExpiringBaseGrantModel) => {
|
||||
return new Oauth2Api(DEFAULT_CONFIG).oauth2RefreshTokensUsedByList({
|
||||
id: item.pk,
|
||||
});
|
||||
}}
|
||||
.delete=${(item: ExpiringBaseGrantModel) => {
|
||||
return new Oauth2Api(DEFAULT_CONFIG).oauth2RefreshTokensDestroy({
|
||||
id: item.pk,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
|
||||
${msg("Delete")}
|
||||
</button>
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
row(item: TokenModel): TemplateResult[] {
|
||||
return [
|
||||
html`<a href="#/core/providers/${item.provider?.pk}"> ${item.provider?.name} </a>`,
|
||||
html`<ak-status-label type="warning" ?good=${item.revoked}></ak-status-label>`,
|
||||
html`${item.expires?.toLocaleString()}`,
|
||||
html`${item.scope.join(", ")}`,
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -13,8 +13,8 @@ import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css";
|
||||
|
||||
import { ExpiringBaseGrantModel, Oauth2Api, TokenModel } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-user-oauth-refresh-list")
|
||||
export class UserOAuthRefreshList extends Table<TokenModel> {
|
||||
@customElement("ak-user-oauth-refresh-token-list")
|
||||
export class UserOAuthRefreshTokenList extends Table<TokenModel> {
|
||||
expandable = true;
|
||||
|
||||
@property({ type: Number })
|
||||
@ -38,6 +38,7 @@ export class ObjectPermissionsPageForm extends ModelForm<unknown, string> {
|
||||
.model=${this.model}
|
||||
.objectPk=${this.objectPk}
|
||||
slot="form"
|
||||
.embedded=${true}
|
||||
>
|
||||
</ak-rbac-object-permission-page>`;
|
||||
}
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
import "@goauthentik/app/admin/roles/RoleAssignedGlobalPermissionsTable";
|
||||
import "@goauthentik/app/admin/roles/RoleAssignedObjectPermissionTable";
|
||||
import "@goauthentik/app/admin/users/UserAssignedGlobalPermissionsTable";
|
||||
import "@goauthentik/app/admin/users/UserAssignedObjectPermissionsTable";
|
||||
import { AKElement } from "@goauthentik/app/elements/Base";
|
||||
import "@goauthentik/app/elements/rbac/RoleObjectPermissionTable";
|
||||
import "@goauthentik/app/elements/rbac/UserObjectPermissionTable";
|
||||
import "@goauthentik/elements/Tabs";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
|
||||
@ -24,20 +28,26 @@ export class ObjectPermissionPage extends AKElement {
|
||||
objectPk?: string | number;
|
||||
|
||||
@property({ type: Boolean })
|
||||
showBanner = true;
|
||||
embedded = false;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
static get styles() {
|
||||
return [PFBase, PFGrid, PFPage, PFCard, PFBanner];
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`${this.showBanner
|
||||
render() {
|
||||
return html`${!this.embedded
|
||||
? html`<div class="pf-c-banner pf-m-info">
|
||||
${msg("RBAC is in preview.")}
|
||||
<a href="mailto:hello@goauthentik.io">${msg("Send us feedback!")}</a>
|
||||
</div>`
|
||||
: html``}
|
||||
<ak-tabs pageIdentifier="permissionPage">
|
||||
: nothing}
|
||||
<ak-tabs pageIdentifier="permissionPage" ?vertical=${!this.embedded}>
|
||||
${this.model === RbacPermissionsAssignedByUsersListModelEnum.CoreUser
|
||||
? this.renderCoreUser()
|
||||
: nothing}
|
||||
${this.model === RbacPermissionsAssignedByUsersListModelEnum.RbacRole
|
||||
? this.renderRbacRole()
|
||||
: nothing}
|
||||
<section
|
||||
slot="page-object-user"
|
||||
data-tab-title="${msg("User Object Permissions")}"
|
||||
@ -45,7 +55,10 @@ export class ObjectPermissionPage extends AKElement {
|
||||
>
|
||||
<div class="pf-l-grid pf-m-gutter">
|
||||
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
|
||||
<div class="pf-c-card__title">User Object Permissions</div>
|
||||
<div class="pf-c-card__title">${msg("User Object Permissions")}</div>
|
||||
<div class="pf-c-card__body">
|
||||
${msg("Permissions set on users which affect this object.")}
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
<ak-rbac-user-object-permission-table
|
||||
.model=${this.model}
|
||||
@ -63,7 +76,10 @@ export class ObjectPermissionPage extends AKElement {
|
||||
>
|
||||
<div class="pf-l-grid pf-m-gutter">
|
||||
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
|
||||
<div class="pf-c-card__title">Role Object Permissions</div>
|
||||
<div class="pf-c-card__title">${msg("Role Object Permissions")}</div>
|
||||
<div class="pf-c-card__body">
|
||||
${msg("Permissions set on roles which affect this object.")}
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
<ak-rbac-role-object-permission-table
|
||||
.model=${this.model}
|
||||
@ -76,4 +92,98 @@ export class ObjectPermissionPage extends AKElement {
|
||||
</section>
|
||||
</ak-tabs>`;
|
||||
}
|
||||
|
||||
renderCoreUser() {
|
||||
return html`
|
||||
<div
|
||||
slot="page-assigned-global-permissions"
|
||||
data-tab-title="${msg("Assigned global permissions")}"
|
||||
>
|
||||
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__title">${msg("Assigned global permissions")}</div>
|
||||
<div class="pf-c-card__body">
|
||||
${msg(
|
||||
"Permissions assigned to this user which affect all object instances of a given type.",
|
||||
)}
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
<ak-user-assigned-global-permissions-table
|
||||
userId=${this.objectPk as number}
|
||||
>
|
||||
</ak-user-assigned-global-permissions-table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div
|
||||
slot="page-assigned-object-permissions"
|
||||
data-tab-title="${msg("Assigned object permissions")}"
|
||||
>
|
||||
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__title">${msg("Assigned object permissions")}</div>
|
||||
<div class="pf-c-card__body">
|
||||
${msg(
|
||||
"Permissions assigned to this user affecting specific object instances.",
|
||||
)}
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
<ak-user-assigned-object-permissions-table
|
||||
userId=${this.objectPk as number}
|
||||
>
|
||||
</ak-user-assigned-object-permissions-table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderRbacRole() {
|
||||
return html`
|
||||
<div
|
||||
slot="page-assigned-global-permissions"
|
||||
data-tab-title="${msg("Assigned global permissions")}"
|
||||
>
|
||||
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__title">${msg("Assigned global permissions")}</div>
|
||||
<div class="pf-c-card__body">
|
||||
${msg(
|
||||
"Permissions assigned to this role which affect all object instances of a given type.",
|
||||
)}
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
<ak-role-assigned-global-permissions-table
|
||||
roleUuid=${this.objectPk as string}
|
||||
>
|
||||
</ak-role-assigned-global-permissions-table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div
|
||||
slot="page-assigned-object-permissions"
|
||||
data-tab-title="${msg("Assigned object permissions")}"
|
||||
>
|
||||
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__title">${msg("Assigned object permissions")}</div>
|
||||
<div class="pf-c-card__body">
|
||||
${msg(
|
||||
"Permissions assigned to this user affecting specific object instances.",
|
||||
)}
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
<ak-role-assigned-object-permissions-table
|
||||
roleUuid=${this.objectPk as string}
|
||||
>
|
||||
</ak-role-assigned-object-permissions-table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,6 +180,12 @@ export abstract class Table<T> extends AKElement implements TableLike {
|
||||
.pf-c-table tbody .pf-c-table__check input {
|
||||
margin-top: calc(var(--pf-c-table__check--input--MarginTop) + 1px);
|
||||
}
|
||||
.pf-c-toolbar__content {
|
||||
row-gap: var(--pf-global--spacer--sm);
|
||||
}
|
||||
.pf-c-toolbar__item .pf-c-input-group {
|
||||
padding: 0 var(--pf-global--spacer--sm);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { getRelativeTime } from "@goauthentik/app/common/utils";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { uiConfig } from "@goauthentik/common/ui/config";
|
||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||
import { Table, TableColumn } from "@goauthentik/elements/table/Table";
|
||||
import getUnicodeFlagIcon from "country-flag-icons/unicode";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { TemplateResult, html } from "lit";
|
||||
@ -31,6 +33,7 @@ export class AuthenticatedSessionList extends Table<AuthenticatedSession> {
|
||||
columns(): TableColumn[] {
|
||||
return [
|
||||
new TableColumn(msg("Last IP"), "last_ip"),
|
||||
new TableColumn(msg("Last used"), "last_used"),
|
||||
new TableColumn(msg("Expires"), "expires"),
|
||||
];
|
||||
}
|
||||
@ -66,10 +69,17 @@ export class AuthenticatedSessionList extends Table<AuthenticatedSession> {
|
||||
row(item: AuthenticatedSession): TemplateResult[] {
|
||||
return [
|
||||
html`<div>
|
||||
${item.current ? html`${msg("(Current session)")} ` : html``}${item.lastIp}
|
||||
${item.current ? html`${msg("(Current session)")} ` : html``}
|
||||
${item.lastIp}
|
||||
${item.geoIp?.country
|
||||
? html` ${getUnicodeFlagIcon(item.geoIp.country)} `
|
||||
: html``}
|
||||
</div>
|
||||
<small>${item.userAgent.userAgent?.family}, ${item.userAgent.os?.family}</small>`,
|
||||
html`${item.expires?.toLocaleString()}`,
|
||||
html`<div>${getRelativeTime(item.lastUsed)}</div>
|
||||
<small>${item.lastUsed?.toLocaleString()}</small>`,
|
||||
html`<div>${getRelativeTime(item.expires || new Date())}</div>
|
||||
<small>${item.expires?.toLocaleString()}</small>`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user