providers/scim: add option to filter out service accounts, parent group (#4862)

* add option to filter out service accounts, parent group

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update docs

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* rename to filter group

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* rework sync card to show scim sync status

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L
2023-03-07 15:39:48 +01:00
committed by GitHub
parent 41d17dc543
commit 9559bc2e1e
16 changed files with 497 additions and 151 deletions

View File

@ -5,8 +5,8 @@ import "@goauthentik/admin/admin-overview/cards/SystemStatusCard";
import "@goauthentik/admin/admin-overview/cards/VersionStatusCard";
import "@goauthentik/admin/admin-overview/cards/WorkerStatusCard";
import "@goauthentik/admin/admin-overview/charts/AdminLoginAuthorizeChart";
import "@goauthentik/admin/admin-overview/charts/LDAPSyncStatusChart";
import "@goauthentik/admin/admin-overview/charts/OutpostStatusChart";
import "@goauthentik/admin/admin-overview/charts/SyncStatusChart";
import { VERSION } from "@goauthentik/common/constants";
import { me } from "@goauthentik/common/users";
import { AKElement } from "@goauthentik/elements/Base";
@ -134,7 +134,7 @@ export class AdminOverviewPage extends AKElement {
>
<ak-aggregate-card
icon="pf-icon pf-icon-zone"
header=${t`Outpost instance status`}
header=${t`Outpost status`}
headerLink="#/outpost/outposts"
>
<ak-admin-status-chart-outpost></ak-admin-status-chart-outpost>
@ -143,12 +143,8 @@ export class AdminOverviewPage extends AKElement {
<div
class="pf-l-grid__item pf-m-12-col pf-m-8-col-on-xl pf-m-4-col-on-2xl graph-container"
>
<ak-aggregate-card
icon="fa fa-sync-alt"
header=${t`LDAP Sync status`}
headerLink="#/core/sources"
>
<ak-admin-status-chart-ldap-sync></ak-admin-status-chart-ldap-sync>
<ak-aggregate-card icon="fa fa-sync-alt" header=${t`Sync status`}>
<ak-admin-status-chart-sync></ak-admin-status-chart-sync>
</ak-aggregate-card>
</div>
<div class="pf-l-grid__item pf-m-12-col row-divider">

View File

@ -1,88 +0,0 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { AKChart } from "@goauthentik/elements/charts/Chart";
import "@goauthentik/elements/forms/ConfirmationForm";
import { ChartData, ChartOptions } from "chart.js";
import { t } from "@lingui/macro";
import { customElement } from "lit/decorators.js";
import { SourcesApi, TaskStatusEnum } from "@goauthentik/api";
interface LDAPSyncStats {
healthy: number;
failed: number;
unsynced: number;
}
@customElement("ak-admin-status-chart-ldap-sync")
export class LDAPSyncStatusChart extends AKChart<LDAPSyncStats> {
getChartType(): string {
return "doughnut";
}
getOptions(): ChartOptions {
return {
plugins: {
legend: {
display: false,
},
},
maintainAspectRatio: false,
};
}
async apiRequest(): Promise<LDAPSyncStats> {
const api = new SourcesApi(DEFAULT_CONFIG);
const sources = await api.sourcesLdapList({});
const metrics: { [key: string]: number } = {
healthy: 0,
failed: 0,
unsynced: 0,
};
await Promise.all(
sources.results.map(async (element) => {
// Each source should have 3 successful tasks, so the worst task overwrites
let sourceKey = "healthy";
try {
const health = await api.sourcesLdapSyncStatusList({
slug: element.slug,
});
health.forEach((task) => {
if (task.status !== TaskStatusEnum.Successful) {
sourceKey = "failed";
}
const now = new Date().getTime();
const maxDelta = 3600000; // 1 hour
if (!health || now - task.taskFinishTimestamp.getTime() > maxDelta) {
sourceKey = "unsynced";
}
});
} catch {
sourceKey = "unsynced";
}
metrics[sourceKey] += 1;
}),
);
this.centerText = sources.pagination.count.toString();
return {
healthy: metrics.healthy,
failed: metrics.failed,
unsynced: sources.pagination.count === 0 ? 1 : metrics.unsynced,
};
}
getChartData(data: LDAPSyncStats): ChartData {
return {
labels: [t`Healthy sources`, t`Failed sources`, t`Unsynced sources`],
datasets: [
{
backgroundColor: ["#3e8635", "#C9190B", "#2b9af3"],
spanGaps: true,
data: [data.healthy, data.failed, data.unsynced],
},
],
};
}
}

View File

@ -1,3 +1,4 @@
import { SyncStatus } from "@goauthentik/admin/admin-overview/charts/SyncStatusChart";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { AKChart } from "@goauthentik/elements/charts/Chart";
import "@goauthentik/elements/forms/ConfirmationForm";
@ -9,14 +10,8 @@ import { customElement } from "lit/decorators.js";
import { OutpostsApi } from "@goauthentik/api";
interface OutpostStats {
healthy: number;
outdated: number;
unhealthy: number;
}
@customElement("ak-admin-status-chart-outpost")
export class OutpostStatusChart extends AKChart<OutpostStats> {
export class OutpostStatusChart extends AKChart<SyncStatus[]> {
getChartType(): string {
return "doughnut";
}
@ -32,47 +27,50 @@ export class OutpostStatusChart extends AKChart<OutpostStats> {
};
}
async apiRequest(): Promise<OutpostStats> {
async apiRequest(): Promise<SyncStatus[]> {
const api = new OutpostsApi(DEFAULT_CONFIG);
const outposts = await api.outpostsInstancesList({});
let healthy = 0;
let outdated = 0;
let unhealthy = 0;
const outpostStats: SyncStatus[] = [];
await Promise.all(
outposts.results.map(async (element) => {
const health = await api.outpostsInstancesHealthList({
uuid: element.pk || "",
});
const singleStats: SyncStatus = {
unsynced: 0,
healthy: 0,
failed: 0,
total: health.length,
label: element.name,
};
if (health.length === 0) {
unhealthy += 1;
singleStats.unsynced += 1;
}
health.forEach((h) => {
if (h.versionOutdated) {
outdated += 1;
singleStats.failed += 1;
} else {
healthy += 1;
singleStats.healthy += 1;
}
});
outpostStats.push(singleStats);
}),
);
this.centerText = outposts.pagination.count.toString();
return {
healthy: healthy,
outdated: outdated,
unhealthy: outposts.pagination.count === 0 ? 1 : unhealthy,
};
return outpostStats;
}
getChartData(data: OutpostStats): ChartData {
getChartData(data: SyncStatus[]): ChartData {
return {
labels: [t`Healthy outposts`, t`Outdated outposts`, t`Unhealthy outposts`],
datasets: [
{
backgroundColor: ["#3e8635", "#f0ab00", "#C9190B"],
datasets: data.map((d) => {
return {
backgroundColor: ["#3e8635", "#C9190B", "#2b9af3"],
spanGaps: true,
data: [data.healthy, data.outdated, data.unhealthy],
},
],
data: [d.healthy, d.failed, d.unsynced],
label: d.label,
};
}),
};
}
}

View File

@ -0,0 +1,138 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { AKChart } from "@goauthentik/elements/charts/Chart";
import "@goauthentik/elements/forms/ConfirmationForm";
import { ChartData, ChartOptions } from "chart.js";
import { t } from "@lingui/macro";
import { customElement } from "lit/decorators.js";
import { ProvidersApi, SourcesApi, TaskStatusEnum } from "@goauthentik/api";
export interface SyncStatus {
healthy: number;
failed: number;
unsynced: number;
total: number;
label: string;
}
@customElement("ak-admin-status-chart-sync")
export class LDAPSyncStatusChart extends AKChart<SyncStatus[]> {
getChartType(): string {
return "doughnut";
}
getOptions(): ChartOptions {
return {
plugins: {
legend: {
display: false,
},
},
maintainAspectRatio: false,
};
}
async ldapStatus(): Promise<SyncStatus> {
const api = new SourcesApi(DEFAULT_CONFIG);
const sources = await api.sourcesLdapList({});
const metrics: { [key: string]: number } = {
healthy: 0,
failed: 0,
unsynced: 0,
};
await Promise.all(
sources.results.map(async (element) => {
try {
const health = await api.sourcesLdapSyncStatusList({
slug: element.slug,
});
health.forEach((task) => {
if (task.status !== TaskStatusEnum.Successful) {
metrics.failed += 1;
}
const now = new Date().getTime();
const maxDelta = 3600000; // 1 hour
if (!health || now - task.taskFinishTimestamp.getTime() > maxDelta) {
metrics.unsynced += 1;
} else {
metrics.healthy += 1;
}
});
} catch {
metrics.unsynced += 1;
}
}),
);
return {
healthy: metrics.healthy,
failed: metrics.failed,
unsynced: sources.pagination.count === 0 ? 1 : metrics.unsynced,
total: sources.pagination.count,
label: t`LDAP Source`,
};
}
async scimStatus(): Promise<SyncStatus> {
const api = new ProvidersApi(DEFAULT_CONFIG);
const providers = await api.providersScimList({});
const metrics: { [key: string]: number } = {
healthy: 0,
failed: 0,
unsynced: 0,
};
await Promise.all(
providers.results.map(async (element) => {
// Each source should have 3 successful tasks, so the worst task overwrites
let sourceKey = "healthy";
try {
const health = await api.providersScimSyncStatusRetrieve({
id: element.pk,
});
if (health.status !== TaskStatusEnum.Successful) {
sourceKey = "failed";
}
const now = new Date().getTime();
const maxDelta = 3600000; // 1 hour
if (!health || now - health.taskFinishTimestamp.getTime() > maxDelta) {
sourceKey = "unsynced";
}
} catch {
sourceKey = "unsynced";
}
metrics[sourceKey] += 1;
}),
);
return {
healthy: metrics.healthy,
failed: metrics.failed,
unsynced: providers.pagination.count === 0 ? 1 : metrics.unsynced,
total: providers.pagination.count,
label: t`SCIM Provider`,
};
}
async apiRequest(): Promise<SyncStatus[]> {
const ldapStatus = await this.ldapStatus();
const scimStatus = await this.scimStatus();
this.centerText = (ldapStatus.total + scimStatus.total).toString();
return [ldapStatus, scimStatus];
}
getChartData(data: SyncStatus[]): ChartData {
return {
labels: [t`Healthy`, t`Failed`, t`Unsynced / N/A`],
datasets: data.map((d) => {
return {
backgroundColor: ["#3e8635", "#C9190B", "#2b9af3"],
spanGaps: true,
data: [d.healthy, d.failed, d.unsynced],
label: d.label,
};
}),
};
}
}