Merge branch 'master' into version-2021.2

This commit is contained in:
Jens Langhammer
2021-02-08 21:33:58 +01:00
41 changed files with 623 additions and 555 deletions

6
web/package-lock.json generated
View File

@ -899,9 +899,9 @@
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"construct-style-sheets-polyfill": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/construct-style-sheets-polyfill/-/construct-style-sheets-polyfill-2.4.6.tgz",
"integrity": "sha512-lU0to7dFDjKslMF+M5NUa4s0RQMBRVyZMXvD/vp7vmjdEPgziTkHSfZHQxfoIvVWajWRJUVJMLfrMwcx8fTh4A=="
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/construct-style-sheets-polyfill/-/construct-style-sheets-polyfill-2.4.9.tgz",
"integrity": "sha512-kPXZXxsp7CTr/Vs29+omUA29wTrFplkdY6jqxyv0DDWC5Ro79WmwpboH2M9KiOclbtn8r81GCFtc7+t7OjRnCw=="
},
"copy-descriptor": {
"version": "0.1.1",

View File

@ -18,7 +18,7 @@
"@types/codemirror": "0.0.108",
"chart.js": "^2.9.4",
"codemirror": "^5.59.2",
"construct-style-sheets-polyfill": "^2.4.6",
"construct-style-sheets-polyfill": "^2.4.9",
"flowchart.js": "^1.15.0",
"lit-element": "^2.4.0",
"lit-html": "^1.3.0",

View File

@ -7,6 +7,13 @@ export interface QueryArguments {
[key: string]: number | string | boolean | null;
}
export interface BaseInheritanceModel {
verbose_name: string;
verbose_name_plural: string;
}
export class Client {
makeUrl(url: string[], query?: QueryArguments): string {
let builtUrl = `/api/${VERSION}/${url.join("/")}/`;

40
web/src/api/Outposts.ts Normal file
View File

@ -0,0 +1,40 @@
import { DefaultClient, PBResponse, QueryArguments } from "./Client";
import { Provider } from "./Providers";
export interface OutpostHealth {
last_seen: number;
version: string;
version_should: string;
version_outdated: boolean;
}
export class Outpost {
pk: string;
name: string;
providers: number[];
providers_obj: Provider[];
service_connection?: string;
_config: QueryArguments;
token_identifier: string;
constructor() {
throw Error();
}
static get(pk: string): Promise<Outpost> {
return DefaultClient.fetch<Outpost>(["outposts", "outposts", pk]);
}
static list(filter?: QueryArguments): Promise<PBResponse<Outpost>> {
return DefaultClient.fetch<PBResponse<Outpost>>(["outposts", "outposts"], filter);
}
static health(pk: string): Promise<OutpostHealth[]> {
return DefaultClient.fetch<OutpostHealth[]>(["outposts", "outposts", pk, "health"]);
}
static adminUrl(rest: string): string {
return `/administration/outposts/${rest}`;
}
}

View File

@ -1,12 +1,14 @@
import { DefaultClient, PBResponse, QueryArguments } from "./Client";
import { DefaultClient, BaseInheritanceModel, PBResponse, QueryArguments } from "./Client";
export class Policy {
export class Policy implements BaseInheritanceModel {
pk: string;
name: string;
constructor() {
throw Error();
}
verbose_name: string;
verbose_name_plural: string;
static get(pk: string): Promise<Policy> {
return DefaultClient.fetch<Policy>(["policies", "all", pk]);

View File

@ -1,6 +1,6 @@
import { DefaultClient, PBResponse, QueryArguments } from "./Client";
import { BaseInheritanceModel, DefaultClient, PBResponse, QueryArguments } from "./Client";
export class Provider {
export class Provider implements BaseInheritanceModel {
pk: number;
name: string;
authorization_flow: string;

View File

@ -1,6 +1,6 @@
import { DefaultClient, PBResponse, QueryArguments } from "./Client";
import { BaseInheritanceModel, DefaultClient, PBResponse, QueryArguments } from "./Client";
export class Source {
export class Source implements BaseInheritanceModel {
pk: string;
name: string;
slug: string;
@ -11,6 +11,8 @@ export class Source {
constructor() {
throw Error();
}
verbose_name: string;
verbose_name_plural: string;
static get(slug: string): Promise<Source> {
return DefaultClient.fetch<Source>(["sources", "all", slug]);
@ -19,4 +21,8 @@ export class Source {
static list(filter?: QueryArguments): Promise<PBResponse<Source>> {
return DefaultClient.fetch<PBResponse<Source>>(["sources", "all"], filter);
}
static adminUrl(rest: string): string {
return `/administration/sources/${rest}`;
}
}

View File

@ -27,7 +27,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
`^/sources/(?<slug>${SLUG_REGEX})$`,
),
new SidebarItem("Providers", "/providers"),
new SidebarItem("Outposts", "/administration/outposts/"),
new SidebarItem("Outposts", "/outposts"),
new SidebarItem("Outpost Service Connections", "/administration/outpost_service_connections/"),
).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser);
@ -41,7 +41,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
new SidebarItem("Flows").children(
new SidebarItem("Flows", "/administration/flows/").activeWhen(`^/flows/(?<slug>${SLUG_REGEX})$`),
new SidebarItem("Stages", "/administration/stages/"),
new SidebarItem("Prompts", "/administration/stages/prompts/"),
new SidebarItem("Prompts", "/administration/stages_prompts/"),
new SidebarItem("Invitations", "/administration/stages/invitations/"),
).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser);

View File

@ -65,13 +65,13 @@ export class EventInfo extends LitElement {
case "model_updated":
case "model_deleted":
return html`
<h3>${gettext("Affected model:")}</h3><hr>
<h3>${gettext("Affected model:")}</h3>
${this.getModelInfo(this.event.context.model as EventContext)}
`;
case "authorize_application":
return html`<div class="pf-l-flex">
<div class="pf-l-flex__item">
<h3>${gettext("Authorized application:")}</h3><hr>
<h3>${gettext("Authorized application:")}</h3>
${this.getModelInfo(this.event.context.authorized_application as EventContext)}
</div>
<div class="pf-l-flex__item">
@ -83,14 +83,15 @@ export class EventInfo extends LitElement {
}), html`<ak-spinner size=${SpinnerSize.Medium}></ak-spinner>`)}
</span>
</div>
</div>`;
</div>
<ak-expand>${this.defaultResponse()}</ak-expand>`;
case "login_failed":
return html`
<h3>${gettext(`Attempted to log in as ${this.event.context.username}`)}</h3>
<ak-expand>${this.defaultResponse()}</ak-expand>`;
case "token_view":
return html`
<h3>${gettext("Token:")}</h3><hr>
<h3>${gettext("Token:")}</h3>
${this.getModelInfo(this.event.context.token as EventContext)}`;
case "property_mapping_exception":
return html`<div class="pf-l-flex">

View File

@ -0,0 +1,49 @@
import { gettext } from "django";
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { until } from "lit-html/directives/until";
import { Outpost } from "../../api/Outposts";
import { COMMON_STYLES } from "../../common/styles";
@customElement("ak-outpost-health")
export class OutpostHealth extends LitElement {
@property()
outpostId?: string;
static get styles(): CSSResult[] {
return COMMON_STYLES;
}
render(): TemplateResult {
if (!this.outpostId) {
return html`<ak-spinner></ak-spinner>`;
}
return html`<ul>${until(Outpost.health(this.outpostId).then((oh) => {
if (oh.length === 0) {
return html`<li>
<ul>
<li role="cell">
<i class="fas fa-question-circle"></i>&nbsp;${gettext("Not available")}
</li>
</ul>
</li>`;
}
return oh.map((h) => {
return html`<li>
<ul>
<li role="cell">
<i class="fas fa-check pf-m-success"></i>&nbsp;${gettext(`Last seen: ${new Date(h.last_seen * 1000).toLocaleTimeString()}`)}
</li>
<li role="cell">
${h.version_outdated ?
html`<i class="fas fa-times pf-m-danger"></i>&nbsp;
${gettext(`${h.version}, should be ${h.version_should}`)}` :
html`<i class="fas fa-check pf-m-success"></i>&nbsp;${gettext(`Version: ${h.version}`)}`}
</li>
</ul>
</li>`;
});
}), html`<ak-spinner></ak-spinner>`)}</ul>`;
}
}

View File

@ -0,0 +1,121 @@
import { gettext } from "django";
import { customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
import { PBResponse } from "../../api/Client";
import { Outpost } from "../../api/Outposts";
import { TableColumn } from "../../elements/table/Table";
import { TablePage } from "../../elements/table/TablePage";
import "./OutpostHealth";
import "../../elements/buttons/SpinnerButton";
import "../../elements/buttons/ModalButton";
@customElement("ak-outpost-list")
export class OutpostListPage extends TablePage<Outpost> {
pageTitle(): string {
return "Outposts";
}
pageDescription(): string | undefined {
return "Outposts are deployments of authentik components to support different environments and protocols, like reverse proxies.";
}
pageIcon(): string {
return "pf-icon pf-icon-zone";
}
searchEnabled(): boolean {
return true;
}
apiEndpoint(page: number): Promise<PBResponse<Outpost>> {
return Outpost.list({
ordering: this.order,
page: page,
search: this.search || "",
});
}
columns(): TableColumn[] {
return [
new TableColumn("Name", "name"),
new TableColumn("Providers"),
new TableColumn("Health and Version"),
new TableColumn(""),
];
}
@property()
order = "name";
row(item: Outpost): TemplateResult[] {
return [
html`${item.name}`,
html`<ul>${item.providers_obj.map((p) => {
return html`<li><a href="#/providers/${p.pk}">${p.name}</a></li>`;
})}</ul>`,
html`<ak-outpost-health outpostId=${item.pk}></ak-outpost-health>`,
html`
<ak-modal-button href="${Outpost.adminUrl(`${item.pk}/update`)}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
${gettext("Edit")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="${Outpost.adminUrl(`${item.pk}/delete`)}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
${gettext("Delete")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button>
<button slot="trigger" class="pf-c-button pf-m-tertiary">
${gettext("View Deployment Info")}
</button>
<div slot="modal">
<div class="pf-c-modal-box__header">
<h1 class="pf-c-title pf-m-2xl" id="modal-title">${gettext("Outpost Deployment Info")}</h1>
</div>
<div class="pf-c-modal-box__body" id="modal-description">
<p><a href="https://goauthentik.io/docs/outposts/outposts/#deploy">${gettext("View deployment documentation")}</a></p>
<form class="pf-c-form">
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">AUTHENTIK_HOST</span>
</label>
<input class="pf-c-form-control" readonly type="text" value="${document.location.toString()}" />
</div>
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">AUTHENTIK_TOKEN</span>
</label>
<div>
<ak-token-copy-button identifier="${item.token_identifier}">
${gettext("Click to copy token")}
</ak-token-copy-button>
</div>
</div>
<h3>${gettext("If your authentik Instance is using a self-signed certificate, set this value.")}</h3>
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">AUTHENTIK_INSECURE</span>
</label>
<input class="pf-c-form-control" readonly type="text" value="true" />
</div>
</form>
</div>
<footer class="pf-c-modal-box__footer pf-m-align-left">
<a class="pf-c-button pf-m-primary">${gettext("Close")}</a>
</footer>
</div>
</ak-modal-button>`,
];
}
renderToolbar(): TemplateResult {
return html`
<ak-modal-button href=${Outpost.adminUrl("create/")}>
<ak-spinner-button slot="trigger" class="pf-m-primary">
${gettext("Create")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
${super.renderToolbar()}
`;
}
}

View File

@ -14,6 +14,7 @@ import "./pages/events/RuleListPage";
import "./pages/providers/ProviderListPage";
import "./pages/providers/ProviderViewPage";
import "./pages/property-mappings/PropertyMappingListPage";
import "./pages/outposts/OutpostListPage";
export const ROUTES: Route[] = [
// Prevent infinite Shell loops
@ -42,4 +43,5 @@ export const ROUTES: Route[] = [
new Route(new RegExp("^/events/transports$"), html`<ak-event-transport-list></ak-event-transport-list>`),
new Route(new RegExp("^/events/rules$"), html`<ak-event-rule-list></ak-event-rule-list>`),
new Route(new RegExp("^/property-mappings$"), html`<ak-property-mapping-list></ak-property-mapping-list>`),
new Route(new RegExp("^/outposts$"), html`<ak-outpost-list></ak-outpost-list>`),
];