web: add new sources view

This commit is contained in:
Jens Langhammer 2021-02-09 16:17:59 +01:00
parent ad91abe9de
commit 5dab198c47
8 changed files with 319 additions and 50 deletions

View File

@ -1,8 +0,0 @@
{% load humanize %}
{% load i18n %}
{% if last_sync %}
<i class="fas fa-check pf-m-success"></i> {% blocktrans with last_sync=last_sync|naturaltime %}Synced {{ last_sync }}.{% endblocktrans %}
{% else %}
<i class="fas fa-times pf-m-danger"></i> Not synced yet/Sync in Progress
{% endif %}

View File

@ -0,0 +1,35 @@
import { DefaultClient } from "../Client";
import { Source } from "../Sources";
export class LDAPSource extends Source {
server_uri: string;
bind_cn: string;
start_tls: boolean
base_dn: string;
additional_user_dn: string;
additional_group_dn: string;
user_object_filter: string;
group_object_filter: string;
group_membership_field: string;
object_uniqueness_field: string;
sync_users: boolean;
sync_users_password: boolean;
sync_groups: boolean;
sync_parent_group?: string;
property_mappings: string[];
property_mappings_group: string[];
constructor() {
super();
throw Error();
}
static get(slug: string): Promise<LDAPSource> {
return DefaultClient.fetch<LDAPSource>(["sources", "ldap", slug]);
}
static syncStatus(slug: string): Promise<{ last_sync?: number }> {
return DefaultClient.fetch(["sources", "ldap", slug, "sync_status"]);
}
}

View File

@ -127,6 +127,7 @@ select[multiple] {
.pf-c-card { .pf-c-card {
--pf-c-card--BackgroundColor: var(--ak-dark-background-light); --pf-c-card--BackgroundColor: var(--ak-dark-background-light);
} }
.pf-c-card__title,
.pf-c-card__header-main, .pf-c-card__header-main,
.pf-c-card__body { .pf-c-card__body {
color: var(--ak-dark-foreground); color: var(--ak-dark-foreground);

View File

@ -23,7 +23,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
new SidebarItem("Applications", "/applications").activeWhen( new SidebarItem("Applications", "/applications").activeWhen(
`^/applications/(?<slug>${SLUG_REGEX})$` `^/applications/(?<slug>${SLUG_REGEX})$`
), ),
new SidebarItem("Sources", "/administration/sources/").activeWhen( new SidebarItem("Sources", "/sources").activeWhen(
`^/sources/(?<slug>${SLUG_REGEX})$`, `^/sources/(?<slug>${SLUG_REGEX})$`,
), ),
new SidebarItem("Providers", "/providers"), new SidebarItem("Providers", "/providers"),

View File

@ -23,11 +23,6 @@ export class OAuth2ProviderViewPage extends Page {
return "pf-icon pf-icon-integration"; return "pf-icon pf-icon-integration";
} }
@property()
set args(value: { [key: string]: number }) {
this.providerID = value.id;
}
@property({type: Number}) @property({type: Number})
set providerID(value: number) { set providerID(value: number) {
OAuth2Provider.get(value).then((app) => this.provider = app); OAuth2Provider.get(value).then((app) => this.provider = app);

View File

@ -0,0 +1,131 @@
import { gettext } from "django";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { COMMON_STYLES } from "../../common/styles";
import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton";
import "../../elements/CodeMirror";
import "../../elements/Tabs";
import { Page } from "../../elements/Page";
import { LDAPSource } from "../../api/sources/LDAP";
import { Source } from "../../api/Sources";
import { until } from "lit-html/directives/until";
import { DefaultClient } from "../../api/Client";
@customElement("ak-source-ldap-view")
export class LDAPSourceViewPage extends Page {
pageTitle(): string {
return gettext(`LDAP Source ${this.source?.name || ""}`);
}
pageDescription(): string | undefined {
return;
}
pageIcon(): string {
return "pf-icon pf-icon-middleware";
}
@property({ type: String })
set sourceSlug(slug: string) {
LDAPSource.get(slug).then((s) => this.source = s);
}
@property({ attribute: false })
source?: LDAPSource;
static get styles(): CSSResult[] {
return COMMON_STYLES;
}
constructor() {
super();
this.addEventListener("ak-refresh", () => {
if (!this.source?.slug) return;
this.sourceSlug = this.source?.slug;
});
}
renderContent(): TemplateResult {
if (!this.source) {
return html``;
}
return html`<ak-tabs>
<section slot="page-1" data-tab-title="${gettext("Overview")}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card pf-c-card-aggregate">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-2-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${gettext("Name")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.source.name}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${gettext("Server URI")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.source.server_uri}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${gettext("Base DN")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ul>
<li>${this.source.base_dn}</li>
</ul>
</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-modal-button href="${Source.adminUrl(`${this.source.pk}/update/`)}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
${gettext("Edit")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</div>
</div>
</div>
</div>
</section>
<section slot="page-2" data-tab-title="${gettext("Sync")}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card pf-c-card-aggregate">
<div class="pf-c-card pf-c-card-aggregate">
<div class="pf-c-card__title">
<p>${gettext("Sync status")}</p>
</div>
<div class="pf-c-card__body">
<p>
${until(LDAPSource.syncStatus(this.source.slug).then((ls) => {
if (!ls.last_sync) {
return gettext("Not synced in the last hour, check System tasks.");
}
const syncDate = new Date(ls.last_sync * 1000);
return gettext(`Last sync: ${syncDate.toLocaleString()}`)
}), "loading")}
</p>
</div>
<div class="pf-c-card__footer">
<ak-action-button method="PATCH" url="${DefaultClient.makeUrl(["sources", "ldap", this.source.slug])}">
${gettext("Retry Task")}
</ak-action-button>
</div>
</div>
</div>
</div>
</div>
</section>
</ak-tabs>`;
}
}

View File

@ -0,0 +1,124 @@
import { gettext } from "django";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { COMMON_STYLES } from "../../common/styles";
import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton";
import "../../elements/CodeMirror";
import "../../elements/Tabs";
import { Page } from "../../elements/Page";
import { LDAPSource } from "../../api/sources/LDAP";
import { Source } from "../../api/Sources";
@customElement("ak-source-oauth-view")
export class OAuthSourceViewPage extends Page {
pageTitle(): string {
return gettext(`LDAP Source ${this.source?.name}`);
}
pageDescription(): string | undefined {
return;
}
pageIcon(): string {
return "pf-icon pf-icon-middleware";
}
@property()
set args(value: { [key: string]: string }) {
this.sourceID = value.id;
}
@property({ type: String })
set sourceID(value: string) {
LDAPSource.get(value).then((s) => this.source = s);
}
@property({ attribute: false })
source?: LDAPSource;
static get styles(): CSSResult[] {
return COMMON_STYLES;
}
constructor() {
super();
this.addEventListener("ak-refresh", () => {
if (!this.source?.pk) return;
this.sourceID = this.source?.pk;
});
}
renderContent(): TemplateResult {
if (!this.source) {
return html``;
}
return html`<ak-tabs>
<section slot="page-1" data-tab-title="${gettext("Overview")}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card pf-c-card-aggregate">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-2-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${gettext("Name")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.source.name}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${gettext("Server URI")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.source.server_uri}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${gettext("Base DN")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ul>
<li>${this.source.base_dn}</li>
</ul>
</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-modal-button href="${Source.adminUrl(`${this.source.pk}/update/`)}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
${gettext("Edit")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</div>
</div>
<div class="pf-c-card pf-c-card-aggregate">
<div class="pf-c-card__title">
${gettext("Sync status")}
</div>
<div class="pf-c-card__body">
</div>
</div>
</div>
</div>
</section>
<div slot="page-2" data-tab-title="Policy Bindings" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__header">
<div class="pf-c-card__header-main">
${gettext("These policies control which users can authorize using these policies.")}
</div>
</div>
<ak-bound-policies-list .target=${this.source.pk}>
</ak-bound-policies-list>
</div>
</div>
</ak-tabs>`;
}
}

View File

@ -1,12 +1,12 @@
import { gettext } from "django"; import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { COMMON_STYLES } from "../../common/styles"; import { COMMON_STYLES } from "../../common/styles";
import "../../elements/Tabs";
import "../../elements/AdminLoginsChart";
import "../../elements/buttons/ModalButton"; import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton"; import "../../elements/buttons/SpinnerButton";
import "../../elements/policies/BoundPoliciesList"; import { SpinnerSize } from "../../elements/Spinner";
import "./LDAPSourceViewPage";
import "./OAuthSourceViewPage";
import { Source } from "../../api/Sources"; import { Source } from "../../api/Sources";
@customElement("ak-source-view") @customElement("ak-source-view")
@ -16,48 +16,39 @@ export class SourceViewPage extends LitElement {
this.sourceSlug = value.slug; this.sourceSlug = value.slug;
} }
@property() @property({ type: String })
set sourceSlug(value: string) { set sourceSlug(slug: string) {
Source.get(value).then((source) => (this.source = source)); Source.get(slug).then((app) => (this.source = app));
} }
@property({ attribute: false }) @property({ attribute: false })
source?: Source; source?: Source;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return COMMON_STYLES.concat( return COMMON_STYLES;
css`
img.pf-icon {
max-height: 24px;
}
`
);
} }
render(): TemplateResult { render(): TemplateResult {
if (!this.source) { if (!this.source) {
return html``; return html`<div class="pf-c-empty-state pf-m-full-height">
<div class="pf-c-empty-state__content">
<div class="pf-l-bullseye">
<div class="pf-l-bullseye__item">
<ak-spinner size="${SpinnerSize.XLarge}"></ak-spinner>
</div>
</div>
</div>
</div>`;
}
switch (this.source?.object_type) {
case "ldap":
return html`<ak-source-ldap-view sourceSlug=${this.source.slug}></ak-source-ldap-view>`;
case "oauth2":
return html`<ak-source-oauth-view sourceSlug=${this.source.slug}></ak-source-oauth-view>`;
// case "proxy":
// return html`<ak-provider-proxy-view providerID=${this.source.pk}></ak-provider-proxy-view>`;
default:
return html`<p>Invalid source type ${this.source.object_type}</p>`;
} }
return html`<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-middleware"></i>
${this.source?.name}
</h1>
</div>
</section>
<ak-tabs>
<div slot="page-2" data-tab-title="Policy Bindings" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__header">
<div class="pf-c-card__header-main">
${gettext("These policies control which users can access this application.")}
</div>
</div>
<ak-bound-policies-list .target=${this.source.pk}>
</ak-bound-policies-list>
</div>
</div>
</ak-tabs>`;
} }
} }