sources/ldap: add check command to verify ldap connectivity (#7263)
* sources/ldap: add check command to verify ldap connectivity Signed-off-by: Jens Langhammer <jens@goauthentik.io> * default to checking all sources Signed-off-by: Jens Langhammer <jens@goauthentik.io> * start adding an API for ldap connectivity Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add webui for ldap source connection status Signed-off-by: Jens Langhammer <jens@goauthentik.io> * better show sync status, clear previous tasks Signed-off-by: Jens Langhammer <jens@goauthentik.io> * set timeout on redis lock for ldap sync Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix py lint Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix web lint Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@ -44,11 +44,11 @@ export class LDAPSyncStatusChart extends AKChart<SyncStatus[]> {
|
||||
await Promise.all(
|
||||
sources.results.map(async (element) => {
|
||||
try {
|
||||
const health = await api.sourcesLdapSyncStatusList({
|
||||
const health = await api.sourcesLdapSyncStatusRetrieve({
|
||||
slug: element.slug,
|
||||
});
|
||||
|
||||
health.forEach((task) => {
|
||||
health.tasks.forEach((task) => {
|
||||
if (task.status !== TaskStatusEnum.Successful) {
|
||||
metrics.failed += 1;
|
||||
}
|
||||
@ -60,7 +60,7 @@ export class LDAPSyncStatusChart extends AKChart<SyncStatus[]> {
|
||||
metrics.healthy += 1;
|
||||
}
|
||||
});
|
||||
if (health.length < 1) {
|
||||
if (health.tasks.length < 1) {
|
||||
metrics.unsynced += 1;
|
||||
}
|
||||
} catch {
|
||||
|
50
web/src/admin/sources/ldap/LDAPSourceConnectivity.ts
Normal file
50
web/src/admin/sources/ldap/LDAPSourceConnectivity.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { AKElement } from "@goauthentik/app/elements/Base";
|
||||
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import PFList from "@patternfly/patternfly/components/List/list.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
@customElement("ak-source-ldap-connectivity")
|
||||
export class LDAPSourceConnectivity extends AKElement {
|
||||
@property()
|
||||
connectivity?: {
|
||||
[key: string]: {
|
||||
[key: string]: string;
|
||||
};
|
||||
};
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFList];
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
if (!this.connectivity) {
|
||||
return html``;
|
||||
}
|
||||
return html`<ul class="pf-c-list">
|
||||
${Object.keys(this.connectivity).map((serverKey) => {
|
||||
let serverLabel = html`${serverKey}`;
|
||||
if (serverKey === "__all__") {
|
||||
serverLabel = html`<b>${msg("Global status")}</b>`;
|
||||
}
|
||||
const server = this.connectivity![serverKey];
|
||||
const content = html`${serverLabel}: ${server.status}`;
|
||||
let tooltip = html`${content}`;
|
||||
if (server.status === "ok") {
|
||||
tooltip = html`<pf-tooltip position="top">
|
||||
<ul slot="content" class="pf-c-list">
|
||||
<li>${msg("Vendor")}: ${server.vendor}</li>
|
||||
<li>${msg("Version")}: ${server.version}</li>
|
||||
</ul>
|
||||
${content}
|
||||
</pf-tooltip>`;
|
||||
}
|
||||
return html`<li>${tooltip}</li>`;
|
||||
})}
|
||||
</ul>`;
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import "@goauthentik/admin/sources/ldap/LDAPSourceConnectivity";
|
||||
import "@goauthentik/admin/sources/ldap/LDAPSourceForm";
|
||||
import "@goauthentik/app/elements/rbac/ObjectPermissionsPage";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
@ -25,9 +26,9 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import {
|
||||
LDAPSource,
|
||||
LDAPSyncStatus,
|
||||
RbacPermissionsAssignedByUsersListModelEnum,
|
||||
SourcesApi,
|
||||
Task,
|
||||
TaskStatusEnum,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
@ -48,7 +49,7 @@ export class LDAPSourceViewPage extends AKElement {
|
||||
source!: LDAPSource;
|
||||
|
||||
@state()
|
||||
syncState: Task[] = [];
|
||||
syncState?: LDAPSyncStatus;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFPage, PFButton, PFGrid, PFContent, PFCard, PFDescriptionList, PFList];
|
||||
@ -62,6 +63,51 @@ export class LDAPSourceViewPage extends AKElement {
|
||||
});
|
||||
}
|
||||
|
||||
renderSyncStatus(): TemplateResult {
|
||||
if (!this.syncState) {
|
||||
return html`${msg("No sync status.")}`;
|
||||
}
|
||||
if (this.syncState.isRunning) {
|
||||
return html`${msg("Sync currently running.")}`;
|
||||
}
|
||||
if (this.syncState.tasks.length < 1) {
|
||||
return html`${msg("Not synced yet.")}`;
|
||||
}
|
||||
return html`
|
||||
<ul class="pf-c-list">
|
||||
${this.syncState.tasks.map((task) => {
|
||||
let header = "";
|
||||
if (task.status === TaskStatusEnum.Warning) {
|
||||
header = msg("Task finished with warnings");
|
||||
} else if (task.status === TaskStatusEnum.Error) {
|
||||
header = msg("Task finished with errors");
|
||||
} else {
|
||||
header = msg(str`Last sync: ${task.taskFinishTimestamp.toLocaleString()}`);
|
||||
}
|
||||
return html`<li>
|
||||
<p>${task.taskName}</p>
|
||||
<ul class="pf-c-list">
|
||||
<li>${header}</li>
|
||||
${task.messages.map((m) => {
|
||||
return html`<li>${m}</li>`;
|
||||
})}
|
||||
</ul>
|
||||
</li> `;
|
||||
})}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
|
||||
load(): void {
|
||||
new SourcesApi(DEFAULT_CONFIG)
|
||||
.sourcesLdapSyncStatusRetrieve({
|
||||
slug: this.source.slug,
|
||||
})
|
||||
.then((state) => {
|
||||
this.syncState = state;
|
||||
});
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
if (!this.source) {
|
||||
return html``;
|
||||
@ -72,13 +118,7 @@ export class LDAPSourceViewPage extends AKElement {
|
||||
data-tab-title="${msg("Overview")}"
|
||||
class="pf-c-page__main-section pf-m-no-padding-mobile"
|
||||
@activate=${() => {
|
||||
new SourcesApi(DEFAULT_CONFIG)
|
||||
.sourcesLdapSyncStatusList({
|
||||
slug: this.source.slug,
|
||||
})
|
||||
.then((state) => {
|
||||
this.syncState = state;
|
||||
});
|
||||
this.load();
|
||||
}}
|
||||
>
|
||||
<div class="pf-l-grid pf-m-gutter">
|
||||
@ -137,42 +177,25 @@ export class LDAPSourceViewPage extends AKElement {
|
||||
</ak-forms-modal>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
|
||||
<div class="pf-c-card pf-l-grid__item pf-m-2-col">
|
||||
<div class="pf-c-card__title">
|
||||
<p>${msg("Connectivity")}</p>
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
<ak-source-ldap-connectivity
|
||||
.connectivity=${this.source.connectivity}
|
||||
></ak-source-ldap-connectivity>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-card pf-l-grid__item pf-m-10-col">
|
||||
<div class="pf-c-card__title">
|
||||
<p>${msg("Sync status")}</p>
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
${this.syncState.length < 1
|
||||
? html`<p>${msg("Not synced yet.")}</p>`
|
||||
: html`
|
||||
<ul class="pf-c-list">
|
||||
${this.syncState.map((task) => {
|
||||
let header = "";
|
||||
if (task.status === TaskStatusEnum.Warning) {
|
||||
header = msg("Task finished with warnings");
|
||||
} else if (task.status === TaskStatusEnum.Error) {
|
||||
header = msg("Task finished with errors");
|
||||
} else {
|
||||
header = msg(
|
||||
str`Last sync: ${task.taskFinishTimestamp.toLocaleString()}`,
|
||||
);
|
||||
}
|
||||
return html`<li>
|
||||
<p>${task.taskName}</p>
|
||||
<ul class="pf-c-list">
|
||||
<li>${header}</li>
|
||||
${task.messages.map((m) => {
|
||||
return html`<li>${m}</li>`;
|
||||
})}
|
||||
</ul>
|
||||
</li> `;
|
||||
})}
|
||||
</ul>
|
||||
`}
|
||||
</div>
|
||||
<div class="pf-c-card__body">${this.renderSyncStatus()}</div>
|
||||
<div class="pf-c-card__footer">
|
||||
<ak-action-button
|
||||
class="pf-m-secondary"
|
||||
?disabled=${this.syncState?.isRunning}
|
||||
.apiRequest=${() => {
|
||||
return new SourcesApi(DEFAULT_CONFIG)
|
||||
.sourcesLdapPartialUpdate({
|
||||
@ -186,6 +209,7 @@ export class LDAPSourceViewPage extends AKElement {
|
||||
composed: true,
|
||||
}),
|
||||
);
|
||||
this.load();
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
@ -39,9 +39,8 @@ const container = (testItem: TemplateResult) =>
|
||||
export const NumberInput = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const displayChange = (ev: any) => {
|
||||
document.getElementById(
|
||||
"number-message-pad",
|
||||
)!.innerText = `Value selected: ${JSON.stringify(ev.target.value, null, 2)}`;
|
||||
document.getElementById("number-message-pad")!.innerText =
|
||||
`Value selected: ${JSON.stringify(ev.target.value, null, 2)}`;
|
||||
};
|
||||
|
||||
return container(
|
||||
|
@ -46,9 +46,8 @@ export const SwitchInput = () => {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const displayChange = (ev: any) => {
|
||||
document.getElementById(
|
||||
"switch-message-pad",
|
||||
)!.innerText = `Value selected: ${JSON.stringify(ev.target.checked, null, 2)}`;
|
||||
document.getElementById("switch-message-pad")!.innerText =
|
||||
`Value selected: ${JSON.stringify(ev.target.checked, null, 2)}`;
|
||||
};
|
||||
|
||||
return container(
|
||||
|
@ -39,9 +39,8 @@ const container = (testItem: TemplateResult) =>
|
||||
export const TextareaInput = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const displayChange = (ev: any) => {
|
||||
document.getElementById(
|
||||
"textarea-message-pad",
|
||||
)!.innerText = `Value selected: ${JSON.stringify(ev.target.value, null, 2)}`;
|
||||
document.getElementById("textarea-message-pad")!.innerText =
|
||||
`Value selected: ${JSON.stringify(ev.target.value, null, 2)}`;
|
||||
};
|
||||
|
||||
return container(
|
||||
|
@ -54,9 +54,8 @@ const testOptions = [
|
||||
export const ToggleGroup = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const displayChange = (ev: any) => {
|
||||
document.getElementById(
|
||||
"toggle-message-pad",
|
||||
)!.innerText = `Value selected: ${ev.detail.value}`;
|
||||
document.getElementById("toggle-message-pad")!.innerText =
|
||||
`Value selected: ${ev.detail.value}`;
|
||||
};
|
||||
|
||||
return container(
|
||||
|
@ -5,6 +5,7 @@ import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter";
|
||||
|
||||
import { Task, TaskStatus } from "@lit-labs/task";
|
||||
import { css, html } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css";
|
||||
@ -57,6 +58,9 @@ export abstract class BaseTaskButton extends CustomEmitterElement(AKElement) {
|
||||
|
||||
actionTask: Task;
|
||||
|
||||
@property({ type: Boolean })
|
||||
disabled = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.onSuccess = this.onSuccess.bind(this);
|
||||
@ -121,6 +125,7 @@ export abstract class BaseTaskButton extends CustomEmitterElement(AKElement) {
|
||||
part="spinner-button"
|
||||
class="pf-c-button pf-m-progress ${this.buttonClasses}"
|
||||
@click=${this.onClick}
|
||||
?disabled=${this.disabled}
|
||||
>
|
||||
${this.actionTask.render({ pending: () => this.spinner })}
|
||||
<slot></slot>
|
||||
|
Reference in New Issue
Block a user