web: use generated API Client (#616)

* api: fix types for config API

* api: remove broken swagger UI

* admin: re-fix system task enum

* events: make event optional

* events: fix Schema for notification transport test

* flows: use APIView for Flow Executor

* core: fix schema for Metrics APIs

* web: rewrite to use generated API client

* web: generate API Client in CI

* admin: use x_cord and y_cord to prevent yaml issues

* events: fix linting errors

* web: don't lint generated code

* core: fix fields not being required in TypeSerializer

* flows: fix missing permission_classes

* web: cleanup

* web: fix rendering of graph on Overview page

* web: cleanup imports

* core: fix missing background image filter

* flows: fix flows not advancing properly

* stages/*: fix warnings during get_challenge

* web: send Flow response as JSON instead of FormData

* web: fix styles for horizontal tabs

* web: add base chart class and custom chart for application view

* root: generate ts client for e2e tests

* web: don't attempt to connect to websocket in selenium tests

* web: fix UserTokenList not being included in the build

* web: fix styling for static token list

* web: fix CSRF Token missing

* stages/authenticator_static: fix error when disable static tokens

* core: fix display issue when updating user info

* web: fix Flow executor not showing spinner when redirecting
This commit is contained in:
Jens L
2021-03-08 11:14:00 +01:00
committed by GitHub
parent 1c6d498621
commit 2852fa3c5e
146 changed files with 1593 additions and 1882 deletions

View File

@ -4,3 +4,8 @@ node_modules
dist
# don't lint nyc coverage output
coverage
# don't lint generated code
src/api/apis
src/api/models
src/api/index.ts
src/api/runtime.ts

View File

@ -10,6 +10,25 @@ variables:
branchName: ${{ replace(variables['Build.SourceBranchName'], 'refs/heads/', '') }}
stages:
- stage: generate
jobs:
- job: swagger_generate
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
inputs:
versionSpec: '12.x'
displayName: 'Install Node.js'
- task: CmdLine@2
inputs:
script: |
docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/src/api --additional-properties=typescriptThreePlus=true
- task: PublishPipelineArtifact@1
inputs:
targetPath: 'web/src/api/'
artifact: 'ts_swagger_client'
publishLocation: 'pipeline'
- stage: lint
jobs:
- job: eslint
@ -20,6 +39,11 @@ stages:
inputs:
versionSpec: '12.x'
displayName: 'Install Node.js'
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'ts_swagger_client'
path: "web/src/api/"
- task: Npm@1
inputs:
command: 'install'
@ -37,6 +61,11 @@ stages:
inputs:
versionSpec: '12.x'
displayName: 'Install Node.js'
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'ts_swagger_client'
path: "web/src/api/"
- task: Npm@1
inputs:
command: 'install'
@ -56,6 +85,11 @@ stages:
inputs:
versionSpec: '12.x'
displayName: 'Install Node.js'
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'ts_swagger_client'
path: "web/src/api/"
- task: Npm@1
inputs:
command: 'install'
@ -71,16 +105,21 @@ stages:
pool:
vmImage: 'ubuntu-latest'
steps:
- task: Bash@3
inputs:
targetType: 'inline'
script: |
python ./scripts/az_do_set_branch.py
- task: Docker@2
inputs:
containerRegistry: 'beryjuorg-harbor'
repository: 'authentik/static'
command: 'buildAndPush'
Dockerfile: 'web/Dockerfile'
tags: "gh-$(branchName)"
buildContext: 'web/'
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'ts_swagger_client'
path: "web/src/api/"
- task: Bash@3
inputs:
targetType: 'inline'
script: |
python ./scripts/az_do_set_branch.py
- task: Docker@2
inputs:
containerRegistry: 'beryjuorg-harbor'
repository: 'authentik/static'
command: 'buildAndPush'
Dockerfile: 'web/Dockerfile'
tags: "gh-$(branchName)"
buildContext: 'web/'

4
web/src/api/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
apis/**
models/**
index.ts
runtime.ts

View File

@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md

View File

@ -0,0 +1,167 @@
apis/AdminApi.ts
apis/CoreApi.ts
apis/CryptoApi.ts
apis/EventsApi.ts
apis/FlowsApi.ts
apis/OutpostsApi.ts
apis/PoliciesApi.ts
apis/PropertymappingsApi.ts
apis/ProvidersApi.ts
apis/RootApi.ts
apis/SourcesApi.ts
apis/StagesApi.ts
apis/index.ts
index.ts
models/Application.ts
models/AuthenticateWebAuthnStage.ts
models/AuthenticatorStaticStage.ts
models/AuthenticatorTOTPStage.ts
models/AuthenticatorValidateStage.ts
models/Cache.ts
models/CaptchaStage.ts
models/CertificateData.ts
models/CertificateKeyPair.ts
models/Challenge.ts
models/Config.ts
models/ConsentStage.ts
models/Coordinate.ts
models/DenyStage.ts
models/DockerServiceConnection.ts
models/DummyPolicy.ts
models/DummyStage.ts
models/EmailStage.ts
models/ErrorDetail.ts
models/Event.ts
models/EventMatcherPolicy.ts
models/EventTopPerUser.ts
models/ExpressionPolicy.ts
models/Flow.ts
models/FlowDiagram.ts
models/FlowStageBinding.ts
models/Group.ts
models/GroupMembershipPolicy.ts
models/HaveIBeenPwendPolicy.ts
models/IPReputation.ts
models/IdentificationStage.ts
models/InlineResponse200.ts
models/InlineResponse2001.ts
models/InlineResponse20010.ts
models/InlineResponse20011.ts
models/InlineResponse20012.ts
models/InlineResponse20013.ts
models/InlineResponse20014.ts
models/InlineResponse20015.ts
models/InlineResponse20016.ts
models/InlineResponse20017.ts
models/InlineResponse20018.ts
models/InlineResponse20019.ts
models/InlineResponse2002.ts
models/InlineResponse20020.ts
models/InlineResponse20021.ts
models/InlineResponse20022.ts
models/InlineResponse20023.ts
models/InlineResponse20024.ts
models/InlineResponse20025.ts
models/InlineResponse20026.ts
models/InlineResponse20027.ts
models/InlineResponse20028.ts
models/InlineResponse20029.ts
models/InlineResponse2003.ts
models/InlineResponse20030.ts
models/InlineResponse20031.ts
models/InlineResponse20032.ts
models/InlineResponse20033.ts
models/InlineResponse20034.ts
models/InlineResponse20035.ts
models/InlineResponse20036.ts
models/InlineResponse20037.ts
models/InlineResponse20038.ts
models/InlineResponse20039.ts
models/InlineResponse2004.ts
models/InlineResponse20040.ts
models/InlineResponse20041.ts
models/InlineResponse20042.ts
models/InlineResponse20043.ts
models/InlineResponse20044.ts
models/InlineResponse20045.ts
models/InlineResponse20046.ts
models/InlineResponse20047.ts
models/InlineResponse20048.ts
models/InlineResponse20049.ts
models/InlineResponse2005.ts
models/InlineResponse20050.ts
models/InlineResponse20051.ts
models/InlineResponse20052.ts
models/InlineResponse20053.ts
models/InlineResponse20054.ts
models/InlineResponse20055.ts
models/InlineResponse20056.ts
models/InlineResponse20057.ts
models/InlineResponse20058.ts
models/InlineResponse20059.ts
models/InlineResponse2006.ts
models/InlineResponse20060.ts
models/InlineResponse2007.ts
models/InlineResponse2008.ts
models/InlineResponse2009.ts
models/InlineResponse200Pagination.ts
models/Invitation.ts
models/InvitationStage.ts
models/KubernetesServiceConnection.ts
models/LDAPPropertyMapping.ts
models/LDAPSource.ts
models/LDAPSourceSyncStatus.ts
models/LoginMetrics.ts
models/Notification.ts
models/NotificationRule.ts
models/NotificationRuleGroup.ts
models/NotificationRuleGroupParent.ts
models/NotificationRuleTransports.ts
models/NotificationTransport.ts
models/NotificationTransportTest.ts
models/OAuth2Provider.ts
models/OAuth2ProviderSetupURLs.ts
models/OAuthSource.ts
models/OpenIDConnectConfiguration.ts
models/Outpost.ts
models/OutpostHealth.ts
models/PasswordExpiryPolicy.ts
models/PasswordPolicy.ts
models/PasswordStage.ts
models/Policy.ts
models/PolicyBinding.ts
models/PolicyBindingPolicy.ts
models/PolicyBindingUser.ts
models/PolicyBindingUserAkGroups.ts
models/PolicyBindingUserGroups.ts
models/PolicyBindingUserSources.ts
models/PolicyBindingUserUserPermissions.ts
models/Prompt.ts
models/PromptStage.ts
models/PropertyMapping.ts
models/Provider.ts
models/ProxyOutpostConfig.ts
models/ProxyProvider.ts
models/ReputationPolicy.ts
models/SAMLMetadata.ts
models/SAMLPropertyMapping.ts
models/SAMLProvider.ts
models/SAMLSource.ts
models/ScopeMapping.ts
models/ServiceConnection.ts
models/ServiceConnectionState.ts
models/Source.ts
models/Stage.ts
models/Task.ts
models/Token.ts
models/TokenView.ts
models/TypeCreate.ts
models/User.ts
models/UserDeleteStage.ts
models/UserLoginStage.ts
models/UserLogoutStage.ts
models/UserReputation.ts
models/UserWriteStage.ts
models/Version.ts
models/index.ts
runtime.ts

View File

@ -0,0 +1 @@
5.1.0-SNAPSHOT

View File

@ -1,32 +0,0 @@
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
import { Provider } from "./Providers";
export class Application {
pk: string;
name: string;
slug: string;
provider?: Provider;
launch_url: string;
meta_launch_url: string;
meta_icon: string;
meta_description: string;
meta_publisher: string;
policies: string[];
constructor() {
throw Error();
}
static get(slug: string): Promise<Application> {
return DefaultClient.fetch<Application>(["core", "applications", slug]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Application>> {
return DefaultClient.fetch<AKResponse<Application>>(["core", "applications"], filter);
}
static adminUrl(rest: string): string {
return `/administration/applications/${rest}`;
}
}

View File

@ -1,26 +0,0 @@
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
export class CertificateKeyPair {
pk: string;
name: string;
fingerprint: string;
cert_expiry: number;
cert_subject: string;
private_key_available: boolean;
constructor() {
throw Error();
}
static get(slug: string): Promise<CertificateKeyPair> {
return DefaultClient.fetch<CertificateKeyPair>(["crypto", "certificatekeypairs", slug]);
}
static list(filter?: QueryArguments): Promise<AKResponse<CertificateKeyPair>> {
return DefaultClient.fetch<AKResponse<CertificateKeyPair>>(["crypto", "certificatekeypairs"], filter);
}
static adminUrl(rest: string): string {
return `/administration/crypto/certificates/${rest}`;
}
}

View File

@ -1,10 +1,3 @@
import { gettext } from "django";
import { showMessage } from "../elements/messages/MessageContainer";
import { getCookie } from "../utils";
import { NotFoundError, RequestError } from "./Error";
export const VERSION = "v2beta";
export interface QueryArguments {
page?: number;
page_size?: number;
@ -20,97 +13,20 @@ export interface BaseInheritanceModel {
}
export class Client {
makeUrl(url: string[], query?: QueryArguments): string {
let builtUrl = `/api/${VERSION}/${url.join("/")}/`;
if (query) {
const queryString = Object.keys(query)
.filter((k) => query[k] !== null)
// we default to a string in query[k] as we've filtered out the null above
// this is just for type-hinting
.map((k) => encodeURIComponent(k) + "=" + encodeURIComponent(query[k] || ""))
.join("&");
builtUrl += `?${queryString}`;
}
return builtUrl;
}
fetch<T>(url: string[], query?: QueryArguments): Promise<T> {
const finalUrl = this.makeUrl(url, query);
return fetch(finalUrl)
.then((r) => {
if (r.status > 300) {
switch (r.status) {
case 404:
throw new NotFoundError(`URL ${finalUrl} not found`);
default:
throw new RequestError(r.statusText);
}
}
return r;
})
.catch((e) => {
showMessage({
level_tag: "error",
message: gettext(`Unexpected error while fetching: ${e.toString()}`),
});
return e;
})
.then((r) => r.json())
.then((r) => <T>r);
}
private writeRequest<T>(url: string[], body: T, method: string, query?: QueryArguments): Promise<T> {
const finalUrl = this.makeUrl(url, query);
const csrftoken = getCookie("authentik_csrf");
const request = new Request(finalUrl, {
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"X-CSRFToken": csrftoken,
},
});
return fetch(request, {
method: method,
mode: "same-origin",
body: JSON.stringify(body),
})
.then((r) => {
if (r.status > 300) {
switch (r.status) {
case 404:
throw new NotFoundError(`URL ${finalUrl} not found`);
default:
throw new RequestError(r.statusText);
}
}
return r;
})
.then((r) => r.json())
.then((r) => <T>r);
}
update<T>(url: string[], body: T, query?: QueryArguments): Promise<T> {
return this.writeRequest(url, body, "PATCH", query);
}
}
export const DefaultClient = new Client();
export interface PBPagination {
export interface AKPagination {
next?: number;
previous?: number;
count: number;
current: number;
total_pages: number;
totalPages: number;
start_index: number;
end_index: number;
startIndex: number;
endIndex: number;
}
export interface AKResponse<T> {
pagination: PBPagination;
pagination: AKPagination;
results: Array<T>;
}

View File

@ -1,42 +1,39 @@
import { DefaultClient } from "./Client";
import * as Sentry from "@sentry/browser";
import { Integrations } from "@sentry/tracing";
import { VERSION } from "../constants";
import { SentryIgnoredError } from "../common/errors";
import { Configuration } from "./runtime";
import { RootApi } from "./apis";
import { Config } from ".";
import { getCookie } from "../utils";
export class Config {
branding_logo: string;
branding_title: string;
error_reporting_enabled: boolean;
error_reporting_environment: string;
error_reporting_send_pii: boolean;
constructor() {
throw Error();
export const DEFAULT_CONFIG = new Configuration({
basePath: "/api/v2beta",
headers: {
"X-CSRFToken": getCookie("authentik_csrf"),
}
});
static get(): Promise<Config> {
return DefaultClient.fetch<Config>(["root", "config"]).then((config) => {
if (config.error_reporting_enabled) {
Sentry.init({
dsn: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
release: `authentik@${VERSION}`,
integrations: [
new Integrations.BrowserTracing(),
],
tracesSampleRate: 0.6,
environment: config.error_reporting_environment,
beforeSend(event: Sentry.Event, hint: Sentry.EventHint) {
if (hint.originalException instanceof SentryIgnoredError) {
return null;
}
return event;
},
});
console.debug("authentik/config: Sentry enabled.");
}
return config;
});
}
export function configureSentry(): Promise<Config> {
return new RootApi(DEFAULT_CONFIG).rootConfigList().then((config) => {
if (config.errorReportingEnabled) {
Sentry.init({
dsn: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
release: `authentik@${VERSION}`,
integrations: [
new Integrations.BrowserTracing(),
],
tracesSampleRate: 0.6,
environment: config.errorReportingEnvironment,
beforeSend(event: Sentry.Event, hint: Sentry.EventHint) {
if (hint.originalException instanceof SentryIgnoredError) {
return null;
}
return event;
},
});
console.debug("authentik/config: Sentry enabled.");
}
return config;
});
}

View File

@ -1,30 +0,0 @@
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
import { Event } from "./Events";
export class Notification {
pk: string;
severity: string;
body: string;
created: string;
event?: Event;
seen: boolean;
constructor() {
throw Error();
}
static get(pk: string): Promise<Notification> {
return DefaultClient.fetch<Notification>(["events", "notifications", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Notification>> {
return DefaultClient.fetch<AKResponse<Notification>>(["events", "notifications"], filter);
}
static markSeen(pk: string): Promise<{seen: boolean}> {
return DefaultClient.update(["events", "notifications", pk], {
"seen": true
});
}
}

View File

@ -1,26 +0,0 @@
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
import { Group } from "./Groups";
export class Rule {
pk: string;
name: string;
transports: string[];
severity: string;
group?: Group;
constructor() {
throw Error();
}
static get(pk: string): Promise<Rule> {
return DefaultClient.fetch<Rule>(["events", "rules", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Rule>> {
return DefaultClient.fetch<AKResponse<Rule>>(["events", "rules"], filter);
}
static adminUrl(rest: string): string {
return `/administration/events/rules/${rest}`;
}
}

View File

@ -1,25 +0,0 @@
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
export class Transport {
pk: string;
name: string;
mode: string;
mode_verbose: string;
webhook_url: string;
constructor() {
throw Error();
}
static get(pk: string): Promise<Transport> {
return DefaultClient.fetch<Transport>(["events", "transports", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Transport>> {
return DefaultClient.fetch<AKResponse<Transport>>(["events", "transports"], filter);
}
static adminUrl(rest: string): string {
return `/administration/events/transports/${rest}`;
}
}

View File

@ -1,4 +1,4 @@
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
import { Event } from "./models";
export interface EventUser {
pk: number;
@ -11,37 +11,7 @@ export interface EventContext {
[key: string]: EventContext | string | number | string[];
}
export class Event {
pk: string;
export interface EventWithContext extends Event {
user: EventUser;
action: string;
app: string;
context: EventContext;
client_ip: string;
created: string;
constructor() {
throw Error();
}
static get(pk: string): Promise<Event> {
return DefaultClient.fetch<Event>(["events", "events", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Event>> {
return DefaultClient.fetch<AKResponse<Event>>(["events", "events"], filter);
}
// events/events/top_per_user/?filter_action=authorize_application
static topForUser(action: string): Promise<TopNEvent[]> {
return DefaultClient.fetch<TopNEvent[]>(["events", "events", "top_per_user"], {
"filter_action": action,
});
}
}
export interface TopNEvent {
application: { [key: string]: string};
counted_events: number;
unique_users: number;
}

View File

@ -1,12 +1,4 @@
import { DefaultClient, AKResponse, QueryArguments, BaseInheritanceModel } from "./Client";
import { TypeCreate } from "./Providers";
export enum ChallengeTypes {
native = "native",
response = "response",
shell = "shell",
redirect = "redirect",
}
import { ChallengeTypeEnum } from "./models";
export interface Error {
code: string;
@ -18,11 +10,12 @@ export interface ErrorDict {
}
export interface Challenge {
type: ChallengeTypes;
type: ChallengeTypeEnum;
component?: string;
title?: string;
response_errors?: ErrorDict;
}
export interface WithUserInfoChallenge extends Challenge {
pending_user: string;
pending_user_avatar: string;
@ -31,6 +24,7 @@ export interface WithUserInfoChallenge extends Challenge {
export interface ShellChallenge extends Challenge {
body: string;
}
export interface RedirectChallenge extends Challenge {
to: string;
}
@ -44,104 +38,3 @@ export enum FlowDesignation {
Recovery = "recovery",
StageConfiguration = "stage_configuration",
}
export class Flow {
pk: string;
policybindingmodel_ptr_id: string;
name: string;
slug: string;
title: string;
designation: FlowDesignation;
background: string;
stages: string[];
policies: string[];
cache_count: number;
constructor() {
throw Error();
}
static get(slug: string): Promise<Flow> {
return DefaultClient.fetch<Flow>(["flows", "instances", slug]);
}
static diagram(slug: string): Promise<{ diagram: string }> {
return DefaultClient.fetch<{ diagram: string }>(["flows", "instances", slug, "diagram"]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Flow>> {
return DefaultClient.fetch<AKResponse<Flow>>(["flows", "instances"], filter);
}
static cached(): Promise<number> {
return DefaultClient.fetch<{ count: number }>(["flows", "instances", "cached"]).then(r => {
return r.count;
});
}
static executor(slug: string): Promise<Challenge> {
return DefaultClient.fetch(["flows", "executor", slug]);
}
static adminUrl(rest: string): string {
return `/administration/flows/${rest}`;
}
}
export class Stage implements BaseInheritanceModel {
pk: string;
name: string;
object_type: string;
verbose_name: string;
verbose_name_plural: string;
flow_set: Flow[];
constructor() {
throw Error();
}
static get(slug: string): Promise<Stage> {
return DefaultClient.fetch<Stage>(["stages", "all", slug]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Stage>> {
return DefaultClient.fetch<AKResponse<Stage>>(["stages", "all"], filter);
}
static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["stages", "all", "types"]);
}
static adminUrl(rest: string): string {
return `/administration/stages/${rest}`;
}
}
export class FlowStageBinding {
pk: string;
policybindingmodel_ptr_id: string;
target: string;
stage: string;
stage_obj: Stage;
evaluate_on_plan: boolean;
re_evaluate_policies: boolean;
order: number;
policies: string[];
constructor() {
throw Error();
}
static get(slug: string): Promise<FlowStageBinding> {
return DefaultClient.fetch<FlowStageBinding>(["flows", "bindings", slug]);
}
static list(filter?: QueryArguments): Promise<AKResponse<FlowStageBinding>> {
return DefaultClient.fetch<AKResponse<FlowStageBinding>>(["flows", "bindings"], filter);
}
static adminUrl(rest: string): string {
return `/administration/stages/bindings/${rest}`;
}
}

View File

@ -1,28 +0,0 @@
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
import { EventContext } from "./Events";
export class Group {
pk: string;
name: string;
is_superuser: boolean;
attributes: EventContext;
parent?: Group;
users: number[];
constructor() {
throw Error();
}
static get(pk: string): Promise<Group> {
return DefaultClient.fetch<Group>(["core", "groups", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Group>> {
return DefaultClient.fetch<AKResponse<Group>>(["core", "groups"], filter);
}
static adminUrl(rest: string): string {
return `/administration/groups/${rest}`;
}
}

View File

@ -1,27 +0,0 @@
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
import { EventContext } from "./Events";
import { User } from "./Users";
export class Invitation {
pk: string;
expires: number;
fixed_date: EventContext;
created_by: User;
constructor() {
throw Error();
}
static get(pk: string): Promise<Invitation> {
return DefaultClient.fetch<Invitation>(["stages", "invitation", "invitations", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Invitation>> {
return DefaultClient.fetch<AKResponse<Invitation>>(["stages", "invitation", "invitations"], filter);
}
static adminUrl(rest: string): string {
return `/administration/stages/invitations/${rest}`;
}
}

View File

@ -1,79 +0,0 @@
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
import { Provider, TypeCreate } 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<AKResponse<Outpost>> {
return DefaultClient.fetch<AKResponse<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}`;
}
}
export interface OutpostServiceConnectionState {
version: string;
healthy: boolean;
}
export class OutpostServiceConnection {
pk: string;
name: string;
local: boolean;
object_type: string;
verbose_name: string;
verbose_name_plural: string;
constructor() {
throw Error();
}
static get(pk: string): Promise<OutpostServiceConnection> {
return DefaultClient.fetch<OutpostServiceConnection>(["outposts", "service_connections", "all", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<OutpostServiceConnection>> {
return DefaultClient.fetch<AKResponse<OutpostServiceConnection>>(["outposts", "service_connections", "all"], filter);
}
static state(pk: string): Promise<OutpostServiceConnectionState> {
return DefaultClient.fetch<OutpostServiceConnectionState>(["outposts", "service_connections", "all", pk, "state"]);
}
static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["outposts", "service_connections", "all", "types"]);
}
static adminUrl(rest: string): string {
return `/administration/outpost_service_connections/${rest}`;
}
}

View File

@ -1,38 +0,0 @@
import { DefaultClient, BaseInheritanceModel, AKResponse, QueryArguments } from "./Client";
import { TypeCreate } from "./Providers";
export class Policy implements BaseInheritanceModel {
pk: string;
name: string;
execution_logging: boolean;
object_type: string;
verbose_name: string;
verbose_name_plural: string;
bound_to: number;
constructor() {
throw Error();
}
static get(pk: string): Promise<Policy> {
return DefaultClient.fetch<Policy>(["policies", "all", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Policy>> {
return DefaultClient.fetch<AKResponse<Policy>>(["policies", "all"], filter);
}
static cached(): Promise<number> {
return DefaultClient.fetch<{ count: number }>(["policies", "all", "cached"]).then(r => {
return r.count;
});
}
static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["policies", "all", "types"]);
}
static adminUrl(rest: string): string {
return `/administration/policies/${rest}`;
}
}

View File

@ -1,31 +0,0 @@
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
import { Group } from "./Groups";
import { Policy } from "./Policies";
import { User } from "./Users";
export class PolicyBinding {
pk: string;
policy?: Policy;
group?: Group;
user?: User;
target: string;
enabled: boolean;
order: number;
timeout: number;
constructor() {
throw Error();
}
static get(pk: string): Promise<PolicyBinding> {
return DefaultClient.fetch<PolicyBinding>(["policies", "bindings", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<PolicyBinding>> {
return DefaultClient.fetch<AKResponse<PolicyBinding>>(["policies", "bindings"], filter);
}
static adminUrl(rest: string): string {
return `/administration/policies/bindings/${rest}`;
}
}

View File

@ -1,30 +0,0 @@
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
import { Stage } from "./Flows";
export class Prompt {
pk: string;
field_key: string;
label: string;
type: string;
required: boolean;
placeholder: string;
order: number;
promptstage_set: Stage[];
constructor() {
throw Error();
}
static get(pk: string): Promise<Prompt> {
return DefaultClient.fetch<Prompt>(["stages", "prompt", "prompts", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Prompt>> {
return DefaultClient.fetch<AKResponse<Prompt>>(["stages", "prompt", "prompts"], filter);
}
static adminUrl(rest: string): string {
return `/administration/stages_prompts/${rest}`;
}
}

View File

@ -1,31 +0,0 @@
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
import { TypeCreate } from "./Providers";
export class PropertyMapping {
pk: string;
name: string;
expression: string;
verbose_name: string;
verbose_name_plural: string;
constructor() {
throw Error();
}
static get(pk: string): Promise<PropertyMapping> {
return DefaultClient.fetch<PropertyMapping>(["propertymappings", "all", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<PropertyMapping>> {
return DefaultClient.fetch<AKResponse<PropertyMapping>>(["propertymappings", "all"], filter);
}
static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["propertymappings", "all", "types"]);
}
static adminUrl(rest: string): string {
return `/administration/property-mappings/${rest}`;
}
}

View File

@ -1,40 +0,0 @@
import { BaseInheritanceModel, DefaultClient, AKResponse, QueryArguments } from "./Client";
export interface TypeCreate {
name: string;
description: string;
link: string;
}
export class Provider implements BaseInheritanceModel {
pk: number;
name: string;
authorization_flow: string;
object_type: string;
assigned_application_slug?: string;
assigned_application_name?: string;
verbose_name: string;
verbose_name_plural: string;
constructor() {
throw Error();
}
static get(id: number): Promise<Provider> {
return DefaultClient.fetch<Provider>(["providers", "all", id.toString()]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Provider>> {
return DefaultClient.fetch<AKResponse<Provider>>(["providers", "all"], filter);
}
static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["providers", "all", "types"]);
}
static adminUrl(rest: string): string {
return `/administration/providers/${rest}`;
}
}

View File

@ -1,34 +0,0 @@
import { BaseInheritanceModel, DefaultClient, AKResponse, QueryArguments } from "./Client";
import { TypeCreate } from "./Providers";
export class Source implements BaseInheritanceModel {
pk: string;
name: string;
slug: string;
enabled: boolean;
authentication_flow: string;
enrollment_flow: string;
constructor() {
throw Error();
}
object_type: string;
verbose_name: string;
verbose_name_plural: string;
static get(slug: string): Promise<Source> {
return DefaultClient.fetch<Source>(["sources", "all", slug]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Source>> {
return DefaultClient.fetch<AKResponse<Source>>(["sources", "all"], filter);
}
static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["sources", "all", "types"]);
}
static adminUrl(rest: string): string {
return `/administration/sources/${rest}`;
}
}

View File

@ -1,33 +0,0 @@
import { DefaultClient, QueryArguments } from "./Client";
export enum TaskStatus {
SUCCESSFUL = 1,
WARNING = 2,
ERROR = 4,
}
export class SystemTask {
task_name: string;
task_description: string;
task_finish_timestamp: number;
status: TaskStatus;
messages: string[];
constructor() {
throw Error();
}
static get(task_name: string): Promise<SystemTask> {
return DefaultClient.fetch<SystemTask>(["admin", "system_tasks", task_name]);
}
static list(filter?: QueryArguments): Promise<SystemTask[]> {
return DefaultClient.fetch<SystemTask[]>(["admin", "system_tasks"], filter);
}
static retry(task_name: string): string {
return DefaultClient.makeUrl(["admin", "system_tasks", task_name, "retry"]);
}
}

View File

@ -1,47 +0,0 @@
import { AKResponse, DefaultClient, QueryArguments } from "./Client";
import { User } from "./Users";
export enum TokenIntent {
INTENT_VERIFICATION = "verification",
INTENT_API = "api",
INTENT_RECOVERY = "recovery",
}
export class Token {
pk: string;
identifier: string;
intent: TokenIntent;
user: User;
description: string;
expires: number;
expiring: boolean;
constructor() {
throw Error();
}
static get(pk: string): Promise<User> {
return DefaultClient.fetch<User>(["core", "tokens", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Token>> {
return DefaultClient.fetch<AKResponse<Token>>(["core", "tokens"], filter);
}
static adminUrl(rest: string): string {
return `/administration/tokens/${rest}`;
}
static userUrl(rest: string): string {
return `/-/user/tokens/${rest}`;
}
static getKey(identifier: string): Promise<string> {
return DefaultClient.fetch<{ key: string }>(["core", "tokens", identifier, "view_key"]).then(
(r) => r.key
);
}
}

View File

@ -1,45 +1,11 @@
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
import { CoreApi } from "./apis";
import { DEFAULT_CONFIG } from "./Config";
import { User } from "./models";
let _globalMePromise: Promise<User>;
export class User {
pk: number;
username: string;
name: string;
is_superuser: boolean;
email: boolean;
avatar: string;
is_active: boolean;
last_login: number;
constructor() {
throw Error();
}
static get(pk: string): Promise<User> {
return DefaultClient.fetch<User>(["core", "users", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<User>> {
return DefaultClient.fetch<AKResponse<User>>(["core", "users"], filter);
}
static adminUrl(rest: string): string {
return `/administration/users/${rest}`;
}
static me(): Promise<User> {
if (!_globalMePromise) {
_globalMePromise = DefaultClient.fetch<User>(["core", "users", "me"]);
}
return _globalMePromise;
}
static count(): Promise<number> {
return DefaultClient.fetch<AKResponse<User>>(["core", "users"], {
"page_size": 1
}).then(r => {
return r.pagination.count;
});
export function me(): Promise<User> {
if (!_globalMePromise) {
_globalMePromise = new CoreApi(DEFAULT_CONFIG).coreUsersMe({});
}
return _globalMePromise;
}

View File

@ -1,17 +0,0 @@
import { DefaultClient } from "./Client";
export class Version {
version_current: string;
version_latest: string;
outdated: boolean;
constructor() {
throw Error();
}
static get(): Promise<Version> {
return DefaultClient.fetch<Version>(["admin", "version"]);
}
}

97
web/src/api/legacy.ts Normal file
View File

@ -0,0 +1,97 @@
export class AdminURLManager {
static applications(rest: string): string {
return `/administration/applications/${rest}`;
}
static cryptoCertificates(rest: string): string {
return `/administration/crypto/certificates/${rest}`;
}
static policies(rest: string): string {
return `/administration/policies/${rest}`;
}
static policyBindings(rest: string): string {
return `/administration/policies/bindings/${rest}`;
}
static providers(rest: string): string {
return `/administration/providers/${rest}`;
}
static propertyMappings(rest: string): string {
return `/administration/property-mappings/${rest}`;
}
static outposts(rest: string): string {
return `/administration/outposts/${rest}`;
}
static outpostServiceConnections(rest: string): string {
return `/administration/outpost_service_connections/${rest}`;
}
static flows(rest: string): string {
return `/administration/flows/${rest}`;
}
static stages(rest: string): string {
return `/administration/stages/${rest}`;
}
static stagePrompts(rest: string): string {
return `/administration/stages_prompts/${rest}`;
}
static stageInvitations(rest: string): string {
return `/administration/stages/invitations/${rest}`;
}
static stageBindings(rest: string): string {
return `/administration/stages/bindings/${rest}`;
}
static sources(rest: string): string {
return `/administration/sources/${rest}`;
}
static tokens(rest: string): string {
return `/administration/tokens/${rest}`;
}
static eventRules(rest: string): string {
return `/administration/events/rules/${rest}`;
}
static eventTransports(rest: string): string {
return `/administration/events/transports/${rest}`;
}
static users(rest: string): string {
return `/administration/users/${rest}`;
}
static groups(rest: string): string {
return `/administration/groups/${rest}`;
}
}
export class UserURLManager {
static tokens(rest: string): string {
return `/-/user/tokens/${rest}`;
}
}
export class AppURLManager {
static sourceSAML(slug: string, rest: string): string {
return `/source/saml/${slug}/${rest}`;
}
static providerSAML(rest: string): string {
return `/application/saml/${rest}`;
}
}

View File

@ -1,43 +0,0 @@
import { DefaultClient } from "../Client";
import { Provider } from "../Providers";
export interface OAuth2SetupURLs {
issuer?: string;
authorize: string;
token: string;
user_info: string;
provider_info?: string;
logout?: string;
}
export class OAuth2Provider extends Provider {
client_type: string
client_id: string;
client_secret: string;
token_validity: string;
include_claims_in_id_token: boolean;
jwt_alg: string;
rsa_key: string;
redirect_uris: string;
sub_mode: string;
issuer_mode: string;
constructor() {
super();
throw Error();
}
static get(id: number): Promise<OAuth2Provider> {
return DefaultClient.fetch<OAuth2Provider>(["providers", "oauth2", id.toString()]);
}
static getLaunchURls(id: number): Promise<OAuth2SetupURLs> {
return DefaultClient.fetch(["providers", "oauth2", id.toString(), "setup_urls"]);
}
static appUrl(rest: string): string {
return `/application/oauth2/${rest}`;
}
}

View File

@ -1,30 +0,0 @@
import { DefaultClient } from "../Client";
import { Provider } from "../Providers";
export class ProxyProvider extends Provider {
internal_host: string;
external_host: string;
internal_host_ssl_validation: boolean
certificate?: string;
skip_path_regex: string;
basic_auth_enabled: boolean;
basic_auth_password_attribute: string;
basic_auth_user_attribute: string;
constructor() {
super();
throw Error();
}
static get(id: number): Promise<ProxyProvider> {
return DefaultClient.fetch<ProxyProvider>(["providers", "proxy", id.toString()]);
}
static getMetadata(id: number): Promise<{ metadata: string }> {
return DefaultClient.fetch(["providers", "proxy", id.toString(), "metadata"]);
}
static appUrl(rest: string): string {
return `/application/proxy/${rest}`;
}
}

View File

@ -1,33 +0,0 @@
import { DefaultClient } from "../Client";
import { Provider } from "../Providers";
export class SAMLProvider extends Provider {
acs_url: string;
audience: string;
issuer: string;
assertion_valid_not_before: string;
assertion_valid_not_on_or_after: string;
session_valid_not_on_or_after: string;
name_id_mapping?: string;
digest_algorithm: string;
signature_algorithm: string;
signing_kp?: string;
verification_kp?: string;
constructor() {
super();
throw Error();
}
static get(id: number): Promise<SAMLProvider> {
return DefaultClient.fetch<SAMLProvider>(["providers", "saml", id.toString()]);
}
static getMetadata(id: number): Promise<{ metadata: string }> {
return DefaultClient.fetch(["providers", "saml", id.toString(), "metadata"]);
}
static appUrl(rest: string): string {
return `/application/saml/${rest}`;
}
}

View File

@ -1,35 +0,0 @@
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

@ -1,22 +0,0 @@
import { DefaultClient } from "../Client";
import { Source } from "../Sources";
export class OAuthSource extends Source {
provider_type: string;
request_token_url: string;
authorization_url: string;
access_token_url: string;
profile_url: string;
consumer_key: string;
callback_url: string;
constructor() {
super();
throw Error();
}
static get(slug: string): Promise<OAuthSource> {
return DefaultClient.fetch<OAuthSource>(["sources", "oauth", slug]);
}
}

View File

@ -1,32 +0,0 @@
import { DefaultClient } from "../Client";
import { Source } from "../Sources";
export class SAMLSource extends Source {
issuer: string;
sso_url: string;
slo_url: string;
allow_idp_initiated: boolean;
name_id_policy: string;
binding_type: string
signing_kp?: string;
digest_algorithm: string;
signature_algorithm: string;
temporary_user_delete_after: string;
constructor() {
super();
throw Error();
}
static get(slug: string): Promise<SAMLSource> {
return DefaultClient.fetch<SAMLSource>(["sources", "saml", slug]);
}
static getMetadata(slug: string): Promise<{ metadata: string }> {
return DefaultClient.fetch(["sources", "saml", slug, "metadata"]);
}
static appUrl(slug: string, rest: string): string {
return `/source/saml/${slug}/${rest}`;
}
}

View File

@ -157,7 +157,7 @@ ak-message {
color: var(--ak-dark-foreground) !important;
}
/* tabs, vertical */
.pf-c-tabs__link {
.pf-c-tabs.pf-m-vertical .pf-c-tabs__link {
background-color: var(--ak-dark-background-light);
}
/* table, on mobile */

View File

@ -1,119 +0,0 @@
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import Chart from "chart.js";
import { DefaultClient } from "../api/Client";
interface TickValue {
value: number;
major: boolean;
}
export interface LoginMetrics {
logins_failed_per_1h: { x: number, y: number }[];
logins_per_1h: { x: number, y: number }[];
}
@customElement("ak-admin-logins-chart")
export class AdminLoginsChart extends LitElement {
@property({type: Array})
url: string[] = [];
chart?: Chart;
static get styles(): CSSResult[] {
return [css`
:host {
position: relative;
height: 100%;
width: 100%;
display: block;
min-height: 25rem;
}
canvas {
width: 100px;
height: 100px;
}
`];
}
constructor() {
super();
window.addEventListener("resize", () => {
if (this.chart) {
this.chart.resize();
}
});
}
firstUpdated(): void {
DefaultClient.fetch<LoginMetrics>(this.url)
.then((r) => {
const canvas = <HTMLCanvasElement>this.shadowRoot?.querySelector("canvas");
if (!canvas) {
console.warn("Failed to get canvas element");
return false;
}
const ctx = canvas.getContext("2d");
if (!ctx) {
console.warn("failed to get 2d context");
return false;
}
this.chart = new Chart(ctx, {
type: "bar",
data: {
datasets: [
{
label: "Failed Logins",
backgroundColor: "rgba(201, 25, 11, .5)",
spanGaps: true,
data: r.logins_failed_per_1h,
},
{
label: "Successful Logins",
backgroundColor: "rgba(189, 229, 184, .5)",
spanGaps: true,
data: r.logins_per_1h,
},
],
},
options: {
maintainAspectRatio: false,
spanGaps: true,
scales: {
xAxes: [
{
stacked: true,
gridLines: {
color: "rgba(0, 0, 0, 0)",
},
type: "time",
offset: true,
ticks: {
callback: function (value, index: number, values) {
const valueStamp = <TickValue>(<unknown>values[index]);
const delta = Date.now() - valueStamp.value;
const ago = Math.round(delta / 1000 / 3600);
return `${ago} Hours ago`;
},
autoSkip: true,
maxTicksLimit: 8,
},
},
],
yAxes: [
{
stacked: true,
gridLines: {
color: "rgba(0, 0, 0, 0)",
},
},
],
},
},
});
});
}
render(): TemplateResult {
return html`<canvas></canvas>`;
}
}

View File

@ -1,4 +1,3 @@
import { getCookie } from "../../utils";
import { customElement, property } from "lit-element";
import { ERROR_CLASS, SUCCESS_CLASS } from "../../constants";
import { SpinnerButton } from "./SpinnerButton";
@ -12,43 +11,33 @@ export class ActionButton extends SpinnerButton {
@property()
method = "POST";
@property({attribute: false})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
apiRequest: () => Promise<any> = () => { throw new Error(); };
callAction(): void {
if (this.isRunning === true) {
return;
}
this.setLoading();
const csrftoken = getCookie("authentik_csrf");
const request = new Request(this.url, {
headers: { "X-CSRFToken": csrftoken },
});
fetch(request, {
method: this.method,
mode: "same-origin",
this.apiRequest().then(() => {
this.setDone(SUCCESS_CLASS);
})
.then((r) => {
if (!r.ok) {
throw r;
}
return r;
})
.then(() => {
this.setDone(SUCCESS_CLASS);
})
.catch((e: Error | Response) => {
if (e instanceof Error) {
.catch((e: Error | Response) => {
if (e instanceof Error) {
showMessage({
level_tag: "error",
message: e.toString()
});
} else {
e.text().then(t => {
showMessage({
level_tag: "error",
message: e.toString()
message: t
});
} else {
e.text().then(t => {
showMessage({
level_tag: "error",
message: t
});
});
}
this.setDone(ERROR_CLASS);
});
});
}
this.setDone(ERROR_CLASS);
});
}
}

View File

@ -3,9 +3,10 @@ import { css, CSSResult, customElement, html, LitElement, property, TemplateResu
import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css";
// @ts-ignore
import ButtonStyle from "@patternfly/patternfly/components/Button/button.css";
import { Token } from "../../api/Tokens";
import { CoreApi } from "../../api";
import { ERROR_CLASS, PRIMARY_CLASS, SUCCESS_CLASS } from "../../constants";
import { ColorStyles } from "../../common/styles";
import { DEFAULT_CONFIG } from "../../api/Config";
@customElement("ak-token-copy-button")
export class TokenCopyButton extends LitElement {
@ -36,8 +37,14 @@ export class TokenCopyButton extends LitElement {
}, 1500);
return;
}
Token.getKey(this.identifier).then((token) => {
navigator.clipboard.writeText(token).then(() => {
new CoreApi(DEFAULT_CONFIG).coreTokensViewKey({
identifier: this.identifier
}).then((token) => {
if (!token.key) {
this.buttonClass = ERROR_CLASS;
return;
}
navigator.clipboard.writeText(token.key).then(() => {
this.buttonClass = SUCCESS_CLASS;
setTimeout(() => {
this.buttonClass = PRIMARY_CLASS;

View File

@ -0,0 +1,41 @@
import { customElement } from "lit-element";
import Chart from "chart.js";
import { AdminApi, LoginMetrics } from "../../api";
import { AKChart } from "./Chart";
import { DEFAULT_CONFIG } from "../../api/Config";
@customElement("ak-charts-admin-login")
export class AdminLoginsChart extends AKChart<LoginMetrics> {
apiRequest(): Promise<LoginMetrics> {
return new AdminApi(DEFAULT_CONFIG).adminMetricsList();
}
getDatasets(data: LoginMetrics): Chart.ChartDataSets[] {
return [
{
label: "Failed Logins",
backgroundColor: "rgba(201, 25, 11, .5)",
spanGaps: true,
data: data.loginsFailedPer1h?.map((cord) => {
return {
x: cord.xCord,
y: cord.yCord,
};
}),
},
{
label: "Successful Logins",
backgroundColor: "rgba(189, 229, 184, .5)",
spanGaps: true,
data: data.loginsPer1h?.map((cord) => {
return {
x: cord.xCord,
y: cord.yCord,
};
}),
},
];
}
}

View File

@ -0,0 +1,32 @@
import { customElement, property } from "lit-element";
import { Coordinate, CoreApi } from "../../api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { AKChart } from "./Chart";
@customElement("ak-charts-application-authorize")
export class ApplicationAuthorizeChart extends AKChart<Coordinate[]> {
@property()
applicationSlug!: string;
apiRequest(): Promise<Coordinate[]> {
return new CoreApi(DEFAULT_CONFIG).coreApplicationsMetrics({ slug: this.applicationSlug });
}
getDatasets(data: Coordinate[]): Chart.ChartDataSets[] {
return [
{
label: "Authorizations",
backgroundColor: "rgba(189, 229, 184, .5)",
spanGaps: true,
data: data.map((cord) => {
return {
x: cord.xCord,
y: cord.yCord,
};
}),
},
];
}
}

View File

@ -0,0 +1,103 @@
import { css, CSSResult, html, LitElement, TemplateResult } from "lit-element";
import Chart from "chart.js";
interface TickValue {
value: number;
major: boolean;
}
export abstract class AKChart<T> extends LitElement {
abstract apiRequest(): Promise<T>;
abstract getDatasets(data: T): Chart.ChartDataSets[];
chart?: Chart;
static get styles(): CSSResult[] {
return [css`
:host {
position: relative;
height: 100%;
width: 100%;
display: block;
min-height: 25rem;
}
canvas {
width: 100px;
height: 100px;
}
`];
}
constructor() {
super();
window.addEventListener("resize", () => {
if (this.chart) {
this.chart.resize();
}
});
}
configureChart(data: T, ctx: CanvasRenderingContext2D): Chart {
return new Chart(ctx, {
type: "bar",
data: {
datasets: this.getDatasets(data),
},
options: {
maintainAspectRatio: false,
spanGaps: true,
scales: {
xAxes: [
{
stacked: true,
gridLines: {
color: "rgba(0, 0, 0, 0)",
},
type: "time",
offset: true,
ticks: {
callback: function (value, index: number, values) {
const valueStamp = <TickValue>(<unknown>values[index]);
const delta = Date.now() - valueStamp.value;
const ago = Math.round(delta / 1000 / 3600);
return `${ago} Hours ago`;
},
autoSkip: true,
maxTicksLimit: 8,
},
},
],
yAxes: [
{
stacked: true,
gridLines: {
color: "rgba(0, 0, 0, 0)",
},
},
],
},
},
});
}
firstUpdated(): void {
this.apiRequest().then((r) => {
const canvas = <HTMLCanvasElement>this.shadowRoot?.querySelector("canvas");
if (!canvas) {
console.warn("Failed to get canvas element");
return false;
}
const ctx = canvas.getContext("2d");
if (!ctx) {
console.warn("failed to get 2d context");
return false;
}
this.chart = this.configureChart(r, ctx);
});
}
render(): TemplateResult {
return html`<canvas></canvas>`;
}
}

View File

@ -35,6 +35,7 @@ export class MessageContainer extends LitElement {
}
connect(): void {
if (navigator.webdriver) return;
const wsUrl = `${window.location.protocol.replace("http", "ws")}//${
window.location.host
}/ws/client/`;

View File

@ -1,7 +1,8 @@
import { gettext } from "django";
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { EventsApi, Notification } from "../../api";
import { AKResponse } from "../../api/Client";
import { Notification } from "../../api/EventNotification";
import { DEFAULT_CONFIG } from "../../api/Config";
import { COMMON_STYLES } from "../../common/styles";
@customElement("ak-notification-drawer")
@ -30,9 +31,9 @@ export class NotificationDrawer extends LitElement {
}
firstUpdated(): void {
Notification.list({
seen: false,
ordering: "-created"
new EventsApi(DEFAULT_CONFIG).eventsNotificationsList({
seen: "false",
ordering: "-created",
}).then(r => {
this.notifications = r;
this.unread = r.results.length;
@ -40,7 +41,6 @@ export class NotificationDrawer extends LitElement {
}
renderItem(item: Notification): TemplateResult {
const created = new Date(parseInt(item.created, 10) * 1000);
let level = "";
switch (item.severity) {
case "notice":
@ -66,15 +66,18 @@ export class NotificationDrawer extends LitElement {
</div>
<div class="pf-c-notification-drawer__list-item-action">
<button class="pf-c-dropdown__toggle pf-m-plain" type="button" @click=${() => {
Notification.markSeen(item.pk).then(() => {
this.firstUpdated();
new EventsApi(DEFAULT_CONFIG).eventsNotificationsPartialUpdate({
uuid: item.pk || "",
data: {
seen: true,
}
});
}}>
<i class="fas fa-times"></i>
</button>
</div>
<p class="pf-c-notification-drawer__list-item-description">${item.body}</p>
<small class="pf-c-notification-drawer__list-item-timestamp">${created.toLocaleString()}</small>
<small class="pf-c-notification-drawer__list-item-timestamp">${item.created?.toLocaleString()}</small>
</li>`;
}

View File

@ -2,16 +2,16 @@ import { gettext } from "django";
import { customElement, html, property, TemplateResult } from "lit-element";
import { AKResponse } from "../../api/Client";
import { Table, TableColumn } from "../../elements/table/Table";
import { PolicyBinding } from "../../api/PolicyBindings";
import { PoliciesApi, PolicyBinding } from "../../api";
import "../../elements/Tabs";
import "../../elements/AdminLoginsChart";
import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton";
import "../../elements/buttons/Dropdown";
import { Policy } from "../../api/Policies";
import { until } from "lit-html/directives/until";
import { PAGE_SIZE } from "../../constants";
import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy";
@customElement("ak-bound-policies-list")
export class BoundPoliciesList extends Table<PolicyBinding> {
@ -19,11 +19,11 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
target?: string;
apiEndpoint(page: number): Promise<AKResponse<PolicyBinding>> {
return PolicyBinding.list({
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsList({
target: this.target || "",
ordering: "order",
page: page,
page_size: PAGE_SIZE,
pageSize: PAGE_SIZE,
});
}
@ -56,13 +56,13 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
html`${item.order}`,
html`${item.timeout}`,
html`
<ak-modal-button href="${PolicyBinding.adminUrl(`${item.pk}/update/`)}">
<ak-modal-button href="${AdminURLManager.policyBindings(`${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="${PolicyBinding.adminUrl(`${item.pk}/delete/`)}">
<ak-modal-button href="${AdminURLManager.policyBindings(`${item.pk}/delete/`)}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
${gettext("Delete")}
</ak-spinner-button>
@ -78,7 +78,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
${gettext("No policies are currently bound to this object.")}
</div>
<div slot="primary">
<ak-modal-button href=${PolicyBinding.adminUrl(`create/?target=${this.target}`)}>
<ak-modal-button href=${AdminURLManager.policyBindings(`create/?target=${this.target}`)}>
<ak-spinner-button slot="trigger" class="pf-m-primary">
${gettext("Bind Policy")}
</ak-spinner-button>
@ -96,7 +96,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(Policy.getTypes().then((types) => {
${until(new PoliciesApi(DEFAULT_CONFIG).policiesAllTypes({}).then((types) => {
return types.map((type) => {
return html`<li>
<ak-modal-button href="${type.link}">
@ -110,7 +110,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
}), html`<ak-spinner></ak-spinner>`)}
</ul>
</ak-dropdown>
<ak-modal-button href=${PolicyBinding.adminUrl(`create/?target=${this.target}`)}>
<ak-modal-button href=${AdminURLManager.policyBindings(`create/?target=${this.target}`)}>
<ak-spinner-button slot="trigger" class="pf-m-primary">
${gettext("Bind Policy")}
</ak-spinner-button>

View File

@ -3,15 +3,17 @@ import { css, CSSResult, customElement, html, LitElement, property, TemplateResu
import PageStyle from "@patternfly/patternfly/components/Page/page.css";
// @ts-ignore
import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css";
import { Config } from "../../api/Config";
import { configureSentry } from "../../api/Config";
import { Config } from "../../api";
import { ifDefined } from "lit-html/directives/if-defined";
export const DefaultConfig: Config = {
branding_logo: " /static/dist/assets/icons/icon_left_brand.svg",
branding_title: "authentik",
brandingLogo: " /static/dist/assets/icons/icon_left_brand.svg",
brandingTitle: "authentik",
error_reporting_enabled: false,
error_reporting_environment: "",
error_reporting_send_pii: false,
errorReportingEnabled: false,
errorReportingEnvironment: "",
errorReportingSendPii: false,
};
@customElement("ak-sidebar-brand")
@ -40,13 +42,13 @@ export class SidebarBrand extends LitElement {
}
firstUpdated(): void {
Config.get().then((c) => (this.config = c));
configureSentry().then((c) => {this.config = c;});
}
render(): TemplateResult {
return html` <a href="#/" class="pf-c-page__header-brand-link">
<div class="pf-c-brand ak-brand">
<img src="${this.config.branding_logo}" alt="authentik icon" loading="lazy" />
<img src="${ifDefined(this.config.brandingLogo)}" alt="authentik icon" loading="lazy" />
</div>
</a>`;
}

View File

@ -5,10 +5,11 @@ import NavStyle from "@patternfly/patternfly/components/Nav/nav.css";
import fa from "@fortawesome/fontawesome-free/css/all.css";
// @ts-ignore
import AvatarStyle from "@patternfly/patternfly/components/Avatar/avatar.css";
import { User } from "../../api/Users";
import { me } from "../../api/Users";
import { until } from "lit-html/directives/until";
import "../notifications/NotificationTrigger";
import { ifDefined } from "lit-html/directives/if-defined";
@customElement("ak-sidebar-user")
export class SidebarUser extends LitElement {
@ -37,8 +38,8 @@ export class SidebarUser extends LitElement {
render(): TemplateResult {
return html`
<a href="#/-/user/" class="pf-c-nav__link user-avatar" id="user-settings">
${until(User.me().then((u) => {
return html`<img class="pf-c-avatar" src="${u.avatar}" alt="" />`;
${until(me().then((u) => {
return html`<img class="pf-c-avatar" src="${ifDefined(u.avatar)}" alt="" />`;
}), html``)}
</a>
<ak-notification-trigger class="pf-c-nav__link user-notifications">

View File

@ -5,7 +5,7 @@ import { COMMON_STYLES } from "../../common/styles";
import "./TablePagination";
import "../EmptyState";
import "../Spinner";
export class TableColumn {

View File

@ -1,12 +1,12 @@
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { COMMON_STYLES } from "../../common/styles";
import { PBPagination } from "../../api/Client";
import { AKPagination } from "../../api/Client";
import { gettext } from "django";
@customElement("ak-table-pagination")
export class TablePagination extends LitElement {
@property({attribute: false})
pages?: PBPagination;
pages?: AKPagination;
@property({attribute: false})
// eslint-disable-next-line
@ -22,8 +22,8 @@ export class TablePagination extends LitElement {
<div class="pf-c-options-menu">
<div class="pf-c-options-menu__toggle pf-m-text pf-m-plain">
<span class="pf-c-options-menu__toggle-text">
${this.pages?.start_index} -
${this.pages?.end_index} of
${this.pages?.startIndex} -
${this.pages?.endIndex} of
${this.pages?.count}
</span>
</div>

View File

@ -1,3 +1,3 @@
import "construct-style-sheets-polyfill";
import "./pages/generic/FlowExecutor";
import "./flows/FlowExecutor";

View File

@ -1,34 +1,34 @@
import { gettext } from "django";
import { LitElement, html, customElement, property, TemplateResult, CSSResult, css } from "lit-element";
import { unsafeHTML } from "lit-html/directives/unsafe-html";
import { getCookie } from "../../utils";
import "../../elements/stages/authenticator_static/AuthenticatorStaticStage";
import "../../elements/stages/authenticator_totp/AuthenticatorTOTPStage";
import "../../elements/stages/authenticator_validate/AuthenticatorValidateStage";
import "../../elements/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
import "../../elements/stages/autosubmit/AutosubmitStage";
import "../../elements/stages/captcha/CaptchaStage";
import "../../elements/stages/consent/ConsentStage";
import "../../elements/stages/email/EmailStage";
import "../../elements/stages/identification/IdentificationStage";
import "../../elements/stages/password/PasswordStage";
import "../../elements/stages/prompt/PromptStage";
import { ShellChallenge, Challenge, ChallengeTypes, Flow, RedirectChallenge } from "../../api/Flows";
import { DefaultClient } from "../../api/Client";
import { IdentificationChallenge } from "../../elements/stages/identification/IdentificationStage";
import { PasswordChallenge } from "../../elements/stages/password/PasswordStage";
import { ConsentChallenge } from "../../elements/stages/consent/ConsentStage";
import { EmailChallenge } from "../../elements/stages/email/EmailStage";
import { AutosubmitChallenge } from "../../elements/stages/autosubmit/AutosubmitStage";
import { PromptChallenge } from "../../elements/stages/prompt/PromptStage";
import { AuthenticatorTOTPChallenge } from "../../elements/stages/authenticator_totp/AuthenticatorTOTPStage";
import { AuthenticatorStaticChallenge } from "../../elements/stages/authenticator_static/AuthenticatorStaticStage";
import { AuthenticatorValidateStageChallenge } from "../../elements/stages/authenticator_validate/AuthenticatorValidateStage";
import { WebAuthnAuthenticatorRegisterChallenge } from "../../elements/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
import { CaptchaChallenge } from "../../elements/stages/captcha/CaptchaStage";
import { COMMON_STYLES } from "../../common/styles";
import { SpinnerSize } from "../../elements/Spinner";
import { StageHost } from "../../elements/stages/base";
import "./stages/authenticator_static/AuthenticatorStaticStage";
import "./stages/authenticator_totp/AuthenticatorTOTPStage";
import "./stages/authenticator_validate/AuthenticatorValidateStage";
import "./stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
import "./stages/autosubmit/AutosubmitStage";
import "./stages/captcha/CaptchaStage";
import "./stages/consent/ConsentStage";
import "./stages/email/EmailStage";
import "./stages/identification/IdentificationStage";
import "./stages/password/PasswordStage";
import "./stages/prompt/PromptStage";
import { ShellChallenge, RedirectChallenge } from "../api/Flows";
import { IdentificationChallenge } from "./stages/identification/IdentificationStage";
import { PasswordChallenge } from "./stages/password/PasswordStage";
import { ConsentChallenge } from "./stages/consent/ConsentStage";
import { EmailChallenge } from "./stages/email/EmailStage";
import { AutosubmitChallenge } from "./stages/autosubmit/AutosubmitStage";
import { PromptChallenge } from "./stages/prompt/PromptStage";
import { AuthenticatorTOTPChallenge } from "./stages/authenticator_totp/AuthenticatorTOTPStage";
import { AuthenticatorStaticChallenge } from "./stages/authenticator_static/AuthenticatorStaticStage";
import { AuthenticatorValidateStageChallenge } from "./stages/authenticator_validate/AuthenticatorValidateStage";
import { WebAuthnAuthenticatorRegisterChallenge } from "./stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
import { CaptchaChallenge } from "./stages/captcha/CaptchaStage";
import { COMMON_STYLES } from "../common/styles";
import { SpinnerSize } from "../elements/Spinner";
import { StageHost } from "./stages/base";
import { Challenge, ChallengeTypeEnum, FlowsApi } from "../api";
import { DEFAULT_CONFIG } from "../api/Config";
@customElement("ak-flow-executor")
export class FlowExecutor extends LitElement implements StageHost {
@ -68,37 +68,30 @@ export class FlowExecutor extends LitElement implements StageHost {
});
}
submit(formData?: FormData): Promise<void> {
const csrftoken = getCookie("authentik_csrf");
const request = new Request(DefaultClient.makeUrl(["flows", "executor", this.flowSlug]), {
headers: {
"X-CSRFToken": csrftoken,
},
});
submit<T>(formData?: T): Promise<void> {
this.loading = true;
return fetch(request, {
method: "POST",
mode: "same-origin",
body: formData,
})
.then((response) => {
return response.json();
})
.then((data) => {
this.challenge = data;
})
.catch((e) => {
this.errorMessage(e);
})
.finally(() => {
this.loading = false;
});
return new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolveRaw({
flowSlug: this.flowSlug,
data: formData || {},
}).then((challengeRaw) => {
return challengeRaw.raw.json();
}).then((data) => {
this.challenge = data;
}).catch((e) => {
this.errorMessage(e);
}).finally(() => {
this.loading = false;
});
}
firstUpdated(): void {
this.loading = true;
Flow.executor(this.flowSlug).then((challenge) => {
this.challenge = challenge;
new FlowsApi(DEFAULT_CONFIG).flowsExecutorGetRaw({
flowSlug: this.flowSlug
}).then((challengeRaw) => {
return challengeRaw.raw.json();
}).then((challenge) => {
this.challenge = challenge as Challenge;
}).catch((e) => {
// Catch JSON or Update errors
this.errorMessage(e);
@ -109,7 +102,7 @@ export class FlowExecutor extends LitElement implements StageHost {
errorMessage(error: string): void {
this.challenge = <ShellChallenge>{
type: ChallengeTypes.shell,
type: ChallengeTypeEnum.Shell,
body: `<style>
.ak-exception {
font-family: monospace;
@ -139,13 +132,13 @@ export class FlowExecutor extends LitElement implements StageHost {
return this.renderLoading();
}
switch (this.challenge.type) {
case ChallengeTypes.redirect:
case ChallengeTypeEnum.Redirect:
console.debug(`authentik/flows: redirecting to ${(this.challenge as RedirectChallenge).to}`);
window.location.assign((this.challenge as RedirectChallenge).to);
return this.renderLoading();
case ChallengeTypes.shell:
case ChallengeTypeEnum.Shell:
return html`${unsafeHTML((this.challenge as ShellChallenge).body)}`;
case ChallengeTypes.native:
case ChallengeTypeEnum.Native:
switch (this.challenge.component) {
case "ak-stage-identification":
return html`<ak-stage-identification .host=${this} .challenge=${this.challenge as IdentificationChallenge}></ak-stage-identification>`;

View File

@ -4,6 +4,7 @@ import { WithUserInfoChallenge } from "../../../api/Flows";
import { COMMON_STYLES } from "../../../common/styles";
import { BaseStage } from "../base";
import "../form";
import "../../../elements/utils/LoadingState";
export interface AuthenticatorStaticChallenge extends WithUserInfoChallenge {
codes: number[];

View File

@ -5,7 +5,8 @@ import { COMMON_STYLES } from "../../../common/styles";
import { BaseStage } from "../base";
import "webcomponent-qr-code";
import "../form";
import { showMessage } from "../../messages/MessageContainer";
import { showMessage } from "../../../elements/messages/MessageContainer";
import "../../../elements/utils/LoadingState";
export interface AuthenticatorTOTPChallenge extends WithUserInfoChallenge {
config_url: string;

View File

@ -36,8 +36,8 @@ export class AuthenticatorValidateStage extends BaseStage implements StageHost {
@property({attribute: false})
selectedDeviceChallenge?: DeviceChallenge;
submit(formData?: FormData): Promise<void> {
return this.host?.submit(formData) || Promise.resolve();
submit<T>(formData?: T): Promise<void> {
return this.host?.submit<T>(formData) || Promise.resolve();
}
static get styles(): CSSResult[] {

View File

@ -4,6 +4,7 @@ import { COMMON_STYLES } from "../../../common/styles";
import { BaseStage } from "../base";
import { AuthenticatorValidateStage, AuthenticatorValidateStageChallenge, DeviceChallenge } from "./AuthenticatorValidateStage";
import "../form";
import "../../../elements/utils/LoadingState";
@customElement("ak-stage-authenticator-validate-code")
export class AuthenticatorValidateStageWebCode extends BaseStage {

View File

@ -1,7 +1,7 @@
import { gettext } from "django";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { COMMON_STYLES } from "../../../common/styles";
import { SpinnerSize } from "../../Spinner";
import { SpinnerSize } from "../../../elements/Spinner";
import { transformAssertionForServer, transformCredentialRequestOptions } from "../authenticator_webauthn/utils";
import { BaseStage } from "../base";
import { AuthenticatorValidateStage, AuthenticatorValidateStageChallenge, DeviceChallenge } from "./AuthenticatorValidateStage";

View File

@ -1,7 +1,7 @@
import { gettext } from "django";
import { customElement, html, property, TemplateResult } from "lit-element";
import { WithUserInfoChallenge } from "../../../api/Flows";
import { SpinnerSize } from "../../Spinner";
import { SpinnerSize } from "../../../elements/Spinner";
import { BaseStage } from "../base";
import { Assertion, transformCredentialCreateOptions, transformNewAssertionForServer } from "./utils";

View File

@ -3,7 +3,8 @@ import { CSSResult, customElement, html, property, TemplateResult } from "lit-el
import { WithUserInfoChallenge } from "../../../api/Flows";
import { COMMON_STYLES } from "../../../common/styles";
import { BaseStage } from "../base";
import "../../Spinner";
import "../../../elements/Spinner";
import "../../../elements/utils/LoadingState";
export interface AutosubmitChallenge extends WithUserInfoChallenge {
url: string;

View File

@ -1,7 +1,7 @@
import { LitElement } from "lit-element";
export interface StageHost {
submit(formData?: FormData): Promise<void>;
submit<T>(formData?: T): Promise<void>;
}
export class BaseStage extends LitElement {
@ -10,8 +10,12 @@ export class BaseStage extends LitElement {
submitForm(e: Event): void {
e.preventDefault();
const object: {
[key: string]: unknown;
} = {};
const form = new FormData(this.shadowRoot?.querySelector("form") || undefined);
this.host?.submit(form);
form.forEach((value, key) => object[key] = value);
this.host?.submit(object);
}
}

View File

@ -2,9 +2,10 @@ import { gettext } from "django";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { WithUserInfoChallenge } from "../../../api/Flows";
import { COMMON_STYLES } from "../../../common/styles";
import { SpinnerSize } from "../../Spinner";
import { SpinnerSize } from "../../../elements/Spinner";
import { BaseStage } from "../base";
import "../form";
import "../../../elements/utils/LoadingState";
export interface CaptchaChallenge extends WithUserInfoChallenge {
site_key: string;

View File

@ -3,6 +3,7 @@ import { CSSResult, customElement, html, property, TemplateResult } from "lit-el
import { WithUserInfoChallenge } from "../../../api/Flows";
import { COMMON_STYLES } from "../../../common/styles";
import { BaseStage } from "../base";
import "../../../elements/utils/LoadingState";
export interface Permission {
name: string;

View File

@ -1,10 +1,11 @@
import { gettext } from "django";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { Challenge } from "../../../api/Flows";
import { Challenge } from "../../../api";
import { COMMON_STYLES } from "../../../common/styles";
import { BaseStage } from "../base";
import "../../../elements/utils/LoadingState";
export type EmailChallenge = Challenge
export type EmailChallenge = Challenge;
@customElement("ak-stage-email")
export class EmailStage extends BaseStage {

View File

@ -1,9 +1,10 @@
import { gettext } from "django";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { Challenge } from "../../../api/Flows";
import { COMMON_STYLES } from "../../../common/styles";
import { BaseStage } from "../base";
import "../form";
import "../../../elements/utils/LoadingState";
import { Challenge } from "../../../api/Flows";
export interface IdentificationChallenge extends Challenge {

View File

@ -4,6 +4,7 @@ import { WithUserInfoChallenge } from "../../../api/Flows";
import { COMMON_STYLES } from "../../../common/styles";
import { BaseStage } from "../base";
import "../form";
import "../../../elements/utils/LoadingState";
export interface PasswordChallenge extends WithUserInfoChallenge {
recovery_url?: string;

View File

@ -1,10 +1,11 @@
import { gettext } from "django";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { unsafeHTML } from "lit-html/directives/unsafe-html";
import { Challenge } from "../../../api/Flows";
import { COMMON_STYLES } from "../../../common/styles";
import { BaseStage } from "../base";
import "../form";
import "../../../elements/utils/LoadingState";
import { Challenge } from "../../../api/Flows";
export interface Prompt {
field_key: string;

View File

@ -1,5 +1,5 @@
import { customElement } from "lit-element";
import { User } from "../api/Users";
import { me } from "../api/Users";
import { SidebarItem } from "../elements/sidebar/Sidebar";
import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "../elements/router/Route";
import { Interface } from "./Interface";
@ -10,7 +10,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
new SidebarItem("Overview", "/administration/overview"),
new SidebarItem("System Tasks", "/administration/system-tasks"),
).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser);
return me().then(u => u.isSuperuser||false);
}),
new SidebarItem("Events").children(
new SidebarItem("Log", "/events/log").activeWhen(
@ -19,7 +19,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
new SidebarItem("Notification Rules", "/events/rules"),
new SidebarItem("Notification Transports", "/events/transports"),
).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser);
return me().then(u => u.isSuperuser||false);
}),
new SidebarItem("Resources").children(
new SidebarItem("Applications", "/core/applications").activeWhen(
@ -34,13 +34,13 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
new SidebarItem("Outposts", "/outpost/outposts"),
new SidebarItem("Outpost Service Connections", "/outpost/service-connections"),
).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser);
return me().then(u => u.isSuperuser||false);
}),
new SidebarItem("Customisation").children(
new SidebarItem("Policies", "/policy/policies"),
new SidebarItem("Property Mappings", "/core/property-mappings"),
).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser);
return me().then(u => u.isSuperuser||false);
}),
new SidebarItem("Flows").children(
new SidebarItem("Flows", "/flow/flows").activeWhen(`^/flow/flows/(?<slug>${SLUG_REGEX})$`),
@ -48,7 +48,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
new SidebarItem("Prompts", "/flow/stages/prompts"),
new SidebarItem("Invitations", "/flow/stages/invitations"),
).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser);
return me().then(u => u.isSuperuser||false);
}),
new SidebarItem("Identity & Cryptography").children(
new SidebarItem("User", "/identity/users"),
@ -56,7 +56,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
new SidebarItem("Certificates", "/crypto/certificates"),
new SidebarItem("Tokens", "/core/tokens"),
).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser);
return me().then(u => u.isSuperuser||false);
}),
];

View File

@ -1,34 +1,13 @@
import "construct-style-sheets-polyfill";
// Elements that are used by SiteShell pages
// And can't dynamically be imported
import "./elements/buttons/ActionButton";
import "./elements/buttons/Dropdown";
import "./elements/buttons/ModalButton";
import "./elements/buttons/SpinnerButton";
import "./elements/buttons/TokenCopyButton";
import "./elements/sidebar/Sidebar";
import "./elements/sidebar/SidebarBrand";
import "./elements/sidebar/SidebarUser";
import "./elements/table/TablePagination";
import "./elements/AdminLoginsChart";
import "./elements/EmptyState";
import "./elements/cards/AggregateCard";
import "./elements/cards/AggregatePromiseCard";
import "./elements/CodeMirror";
import "./elements/messages/MessageContainer";
import "./elements/Spinner";
import "./elements/Tabs";
import "./elements/router/RouterOutlet";
import "./pages/generic/SiteShell";
import "./pages/admin-overview/AdminOverviewPage";
import "./pages/admin-overview/TopApplicationsTable";
import "./pages/applications/ApplicationListPage";
import "./pages/applications/ApplicationViewPage";
import "./pages/tokens/UserTokenList";
import "./pages/LibraryPage";
import "./pages/generic/SiteShell";
import "./interfaces/AdminInterface";

View File

@ -1,8 +1,9 @@
import { gettext } from "django";
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { ifDefined } from "lit-html/directives/if-defined";
import { Application } from "../api/Applications";
import { Application, CoreApi } from "../api";
import { AKResponse } from "../api/Client";
import { DEFAULT_CONFIG } from "../api/Config";
import { COMMON_STYLES } from "../common/styles";
import { loading, truncate } from "../utils";
@ -31,19 +32,19 @@ export class LibraryApplication extends LitElement {
if (!this.application) {
return html`<ak-spinner></ak-spinner>`;
}
return html` <a href="${this.application.launch_url}" class="pf-c-card pf-m-hoverable pf-m-compact">
return html` <a href="${ifDefined(this.application.launchUrl)}" class="pf-c-card pf-m-hoverable pf-m-compact">
<div class="pf-c-card__header">
${this.application.meta_icon
? html`<img class="app-icon pf-c-avatar" src="${ifDefined(this.application.meta_icon)}" alt="Application Icon"/>`
${this.application.metaIcon
? html`<img class="app-icon pf-c-avatar" src="${ifDefined(this.application.metaIcon)}" alt="Application Icon"/>`
: html`<i class="pf-icon pf-icon-arrow"></i>`}
</div>
<div class="pf-c-card__title">
<p id="card-1-check-label">${this.application.name}</p>
<div class="pf-c-content">
<small>${this.application.meta_publisher}</small>
<small>${this.application.metaPublisher}</small>
</div>
</div>
<div class="pf-c-card__body">${truncate(this.application.meta_description, 35)}</div>
<div class="pf-c-card__body">${truncate(this.application.metaDescription, 35)}</div>
</a>`;
}
@ -64,7 +65,9 @@ export class LibraryPage extends LitElement {
}
firstUpdated(): void {
Application.list().then((r) => (this.apps = r));
new CoreApi(DEFAULT_CONFIG).coreApplicationsList({}).then((apps) => {
this.apps = apps;
});
}
renderEmptyState(): TemplateResult {

View File

@ -2,7 +2,7 @@ import { gettext } from "django";
import { CSSResult, customElement, html, LitElement, TemplateResult } from "lit-element";
import { COMMON_STYLES } from "../../common/styles";
import "../../elements/AdminLoginsChart";
import "../../elements/charts/AdminLoginsChart";
import "../../elements/cards/AggregatePromiseCard";
import "./TopApplicationsTable";
import "./cards/AdminStatusCard";
@ -30,7 +30,7 @@ export class AdminOverviewPage extends LitElement {
<section class="pf-c-page__main-section">
<div class="pf-l-gallery pf-m-gutter">
<ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Logins over the last 24 hours" style="grid-column-end: span 3;grid-row-end: span 2;">
<ak-admin-logins-chart .url="${["admin", "metrics"]}"></ak-admin-logins-chart>
<ak-charts-admin-login></ak-charts-admin-login>
</ak-aggregate-card>
<ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Apps with most usage" style="grid-column-end: span 2;grid-row-end: span 3;">
<ak-top-applications-table></ak-top-applications-table>

View File

@ -1,34 +1,39 @@
import { gettext } from "django";
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { Event, TopNEvent } from "../../api/Events";
import { COMMON_STYLES } from "../../common/styles";
import { EventsApi, EventTopPerUser } from "../../api";
import "../../elements/Spinner";
import { DEFAULT_CONFIG } from "../../api/Config";
@customElement("ak-top-applications-table")
export class TopApplicationsTable extends LitElement {
@property({attribute: false})
topN?: TopNEvent[];
topN?: EventTopPerUser[];
static get styles(): CSSResult[] {
return COMMON_STYLES;
}
firstUpdated(): void {
Event.topForUser("authorize_application").then(events => this.topN = events);
new EventsApi(DEFAULT_CONFIG).eventsEventsTopPerUser({
action: "authorize_application",
}).then((events) => {
this.topN = events;
});
}
renderRow(event: TopNEvent): TemplateResult {
renderRow(event: EventTopPerUser): TemplateResult {
return html`<tr role="row">
<td role="cell">
${event.application.name}
</td>
<td role="cell">
${event.counted_events}
${event.countedEvents}
</td>
<td role="cell">
<progress value="${event.counted_events}" max="${this.topN ? this.topN[0].counted_events : 0}"></progress>
<progress value="${event.countedEvents}" max="${this.topN ? this.topN[0].countedEvents : 0}"></progress>
</td>
</tr>`;
}

View File

@ -23,14 +23,14 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
renderInner(): TemplateResult {
return html`<p class="center-value">
${until(this.getPrimaryValue().then((v) => {
this.value = v;
return this.getStatus(v);
}).then((status) => {
return html`<p class="ak-aggregate-card">
<i class="${status.icon}"></i> ${this.renderValue()}
</p>
${status.message ? html`<p class="subtext">${status.message}</p>` : html``}`;
}), html`<ak-spinner size="${SpinnerSize.Large}"></ak-spinner>`)}
this.value = v;
return this.getStatus(v);
}).then((status) => {
return html`<p class="ak-aggregate-card">
<i class="${status.icon}"></i> ${this.renderValue()}
</p>
${status.message ? html`<p class="subtext">${status.message}</p>` : html``}`;
}), html`<ak-spinner size="${SpinnerSize.Large}"></ak-spinner>`)}
</p>`;
}
}

View File

@ -1,14 +1,17 @@
import { gettext } from "django";
import { customElement, html, TemplateResult } from "lit-element";
import { Flow } from "../../../api/Flows";
import { AdminStatus, AdminStatusCard } from "./AdminStatusCard";
import "../../../elements/buttons/ModalButton";
import { FlowsApi } from "../../../api";
import { DEFAULT_CONFIG } from "../../../api/Config";
@customElement("ak-admin-status-card-flow-cache")
export class FlowCacheStatusCard extends AdminStatusCard<number> {
getPrimaryValue(): Promise<number> {
return Flow.cached();
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesCached({}).then((value) => {
return value.count || 0;
});
}
getStatus(value: number): Promise<AdminStatus> {

View File

@ -1,15 +1,18 @@
import { gettext } from "django";
import { customElement } from "lit-element";
import { TemplateResult, html } from "lit-html";
import { Policy } from "../../../api/Policies";
import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
import "../../../elements/buttons/ModalButton";
import { PoliciesApi } from "../../../api";
import { DEFAULT_CONFIG } from "../../../api/Config";
@customElement("ak-admin-status-card-policy-cache")
export class PolicyCacheStatusCard extends AdminStatusCard<number> {
getPrimaryValue(): Promise<number> {
return Policy.cached();
return new PoliciesApi(DEFAULT_CONFIG).policiesAllCached({}).then((value) => {
return value.count || 0;
});
}
getStatus(value: number): Promise<AdminStatus> {

View File

@ -1,17 +1,18 @@
import { gettext } from "django";
import { customElement } from "lit-element";
import { Policy } from "../../../api/Policies";
import { PoliciesApi } from "../../../api";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
@customElement("ak-admin-status-card-policy-unbound")
export class PolicyUnboundStatusCard extends AdminStatusCard<number> {
getPrimaryValue(): Promise<number> {
return Policy.list({
"bindings__isnull": true,
"promptstage__isnull": true,
}).then((response) => {
return response.pagination.count;
return new PoliciesApi(DEFAULT_CONFIG).policiesAllList({
bindingsIsnull: "true",
promptstageIsnull: "true",
}).then((value) => {
return value.pagination.count;
});
}

View File

@ -1,16 +1,17 @@
import { gettext } from "django";
import { customElement } from "lit-element";
import { Provider } from "../../../api/Providers";
import { ProvidersApi } from "../../../api";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
@customElement("ak-admin-status-card-provider")
export class ProviderStatusCard extends AdminStatusCard<number> {
getPrimaryValue(): Promise<number> {
return Provider.list({
"application__isnull": true
}).then((response) => {
return response.pagination.count;
return new ProvidersApi(DEFAULT_CONFIG).providersAllList({
applicationIsnull: "true"
}).then((value) => {
return value.pagination.count;
});
}

View File

@ -1,12 +1,17 @@
import { customElement } from "lit-element";
import { User } from "../../../api/Users";
import { CoreApi } from "../../../api";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
@customElement("ak-admin-status-card-user-count")
export class UserCountStatusCard extends AdminStatusCard<number> {
getPrimaryValue(): Promise<number> {
return User.count();
return new CoreApi(DEFAULT_CONFIG).coreUsersList({
pageSize: 1
}).then((value) => {
return value.pagination.count;
});
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars

View File

@ -1,20 +1,21 @@
import { gettext } from "django";
import { customElement, html, TemplateResult } from "lit-element";
import { Version } from "../../../api/Versions";
import { AdminApi, Version } from "../../../api";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
@customElement("ak-admin-status-version")
export class VersionStatusCard extends AdminStatusCard<Version> {
getPrimaryValue(): Promise<Version> {
return Version.get();
return new AdminApi(DEFAULT_CONFIG).adminVersionList({});
}
getStatus(value: Version): Promise<AdminStatus> {
if (value.outdated) {
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle pf-m-warning",
message: gettext(`${value.version_latest} is available!`),
message: gettext(`${value.versionLatest} is available!`),
});
} else {
return Promise.resolve<AdminStatus>({
@ -25,7 +26,7 @@ export class VersionStatusCard extends AdminStatusCard<Version> {
}
renderValue(): TemplateResult {
return html`${this.value?.version_current}`;
return html`${this.value?.versionCurrent}`;
}
}

View File

@ -1,14 +1,15 @@
import { gettext } from "django";
import { customElement } from "lit-element";
import { DefaultClient, AKResponse } from "../../../api/Client";
import { AdminApi } from "../../../api";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { AdminStatus, AdminStatusCard } from "./AdminStatusCard";
@customElement("ak-admin-status-card-workers")
export class WorkersStatusCard extends AdminStatusCard<number> {
getPrimaryValue(): Promise<number> {
return DefaultClient.fetch<AKResponse<number>>(["admin", "workers"]).then((r) => {
return r.pagination.count;
return new AdminApi(DEFAULT_CONFIG).adminWorkersList({}).then((workers) => {
return workers.pagination.count;
});
}

View File

@ -1,6 +1,5 @@
import { gettext } from "django";
import { customElement, html, property, TemplateResult } from "lit-element";
import { Application } from "../../api/Applications";
import { AKResponse } from "../../api/Client";
import { TablePage } from "../../elements/table/TablePage";
@ -8,6 +7,9 @@ import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton";
import { TableColumn } from "../../elements/table/Table";
import { PAGE_SIZE } from "../../constants";
import { Application, CoreApi } from "../../api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy";
@customElement("ak-application-list")
export class ApplicationListPage extends TablePage<Application> {
@ -28,10 +30,10 @@ export class ApplicationListPage extends TablePage<Application> {
order = "name";
apiEndpoint(page: number): Promise<AKResponse<Application>> {
return Application.list({
return new CoreApi(DEFAULT_CONFIG).coreApplicationsList({
ordering: this.order,
page: page,
page_size: PAGE_SIZE,
pageSize: PAGE_SIZE,
search: this.search || "",
});
}
@ -49,26 +51,26 @@ export class ApplicationListPage extends TablePage<Application> {
row(item: Application): TemplateResult[] {
return [
item.meta_icon ?
html`<img class="app-icon pf-c-avatar" src="${item.meta_icon}" alt="${gettext("Application Icon")}">` :
item.metaIcon ?
html`<img class="app-icon pf-c-avatar" src="${item.metaIcon}" alt="${gettext("Application Icon")}">` :
html`<i class="pf-icon pf-icon-arrow"></i>`,
html`<a href="#/core/applications/${item.slug}">
<div>
${item.name}
</div>
${item.meta_publisher ? html`<small>${item.meta_publisher}</small>` : html``}
${item.metaPublisher ? html`<small>${item.metaPublisher}</small>` : html``}
</a>`,
html`<code>${item.slug}</code>`,
html`${item.provider?.name}`,
html`${item.provider?.verbose_name}`,
html`${item.provider?.verboseName}`,
html`
<ak-modal-button href="${Application.adminUrl(`${item.pk}/update/`)}">
<ak-modal-button href="${AdminURLManager.applications(`${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="${Application.adminUrl(`${item.pk}/delete/`)}">
<ak-modal-button href="${AdminURLManager.applications(`${item.pk}/delete/`)}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
${gettext("Delete")}
</ak-spinner-button>
@ -80,7 +82,7 @@ export class ApplicationListPage extends TablePage<Application> {
renderToolbar(): TemplateResult {
return html`
<ak-modal-button href=${Application.adminUrl("create/")}>
<ak-modal-button href=${AdminURLManager.applications("create/")}>
<ak-spinner-button slot="trigger" class="pf-m-primary">
${gettext("Create")}
</ak-spinner-button>

View File

@ -1,14 +1,15 @@
import { gettext } from "django";
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { Application } from "../../api/Applications";
import { COMMON_STYLES } from "../../common/styles";
import "../../elements/Tabs";
import "../../elements/AdminLoginsChart";
import "../../elements/charts/ApplicationAuthorizeChart";
import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton";
import "../../elements/policies/BoundPoliciesList";
import "../../elements/utils/LoadingState";
import { Application, CoreApi } from "../../api";
import { DEFAULT_CONFIG } from "../../api/Config";
@customElement("ak-application-view")
export class ApplicationViewPage extends LitElement {
@ -19,11 +20,15 @@ export class ApplicationViewPage extends LitElement {
@property()
set applicationSlug(value: string) {
Application.get(value).then((app) => (this.application = app));
new CoreApi(DEFAULT_CONFIG).coreApplicationsRead({
slug: value
}).then((app) => {
this.application = app;
});
}
@property({attribute: false})
application?: Application;
application!: Application;
static get styles(): CSSResult[] {
return COMMON_STYLES.concat(
@ -52,10 +57,10 @@ export class ApplicationViewPage extends LitElement {
return html`<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<img class="pf-icon" src="${this.application?.meta_icon || ""}" />
<img class="pf-icon" src="${this.application?.metaIcon || ""}" />
${this.application?.name}
</h1>
<p>${this.application?.meta_publisher}</p>
<p>${this.application?.metaPublisher}</p>
</div>
</section>
<ak-tabs>
@ -69,9 +74,8 @@ export class ApplicationViewPage extends LitElement {
</div>
<div class="pf-c-card__body">
${this.application ? html`
<ak-admin-logins-chart
.url="${["core", "applications", this.application?.slug, "metrics"]}">
</ak-admin-logins-chart>`: ""}
<ak-charts-application-authorize applicationSlug=${this.application.slug}>
</ak-charts-application-authorize>`: ""}
</div>
</div>
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-2-col">

View File

@ -3,11 +3,14 @@ import { customElement, html, property, TemplateResult } from "lit-element";
import { AKResponse } from "../../api/Client";
import { TablePage } from "../../elements/table/TablePage";
import { CryptoApi, CertificateKeyPair } from "../../api";
import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton";
import { TableColumn } from "../../elements/table/Table";
import { CertificateKeyPair } from "../../api/CertificateKeyPair";
import { PAGE_SIZE } from "../../constants";
import { AdminURLManager } from "../../api/legacy";
import { DEFAULT_CONFIG } from "../../api/Config";
@customElement("ak-crypto-certificatekeypair-list")
export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
@ -30,10 +33,10 @@ export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
order = "name";
apiEndpoint(page: number): Promise<AKResponse<CertificateKeyPair>> {
return CertificateKeyPair.list({
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsList({
ordering: this.order,
page: page,
page_size: PAGE_SIZE,
pageSize: PAGE_SIZE,
search: this.search || "",
});
}
@ -50,16 +53,16 @@ export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
row(item: CertificateKeyPair): TemplateResult[] {
return [
html`${item.name}`,
html`${gettext(item.private_key_available ? "Yes" : "No")}`,
html`${new Date(item.cert_expiry * 1000).toLocaleString()}`,
html`${gettext(item.privateKeyAvailable ? "Yes" : "No")}`,
html`${item.certExpiry?.toLocaleString()}`,
html`
<ak-modal-button href="${CertificateKeyPair.adminUrl(`${item.pk}/update/`)}">
<ak-modal-button href="${AdminURLManager.cryptoCertificates(`${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="${CertificateKeyPair.adminUrl(`${item.pk}/delete/`)}">
<ak-modal-button href="${AdminURLManager.cryptoCertificates(`${item.pk}/delete/`)}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
${gettext("Delete")}
</ak-spinner-button>
@ -73,24 +76,24 @@ export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
return html`
<td role="cell" colspan="3">
<div class="pf-c-table__expandable-row-content">
<dl class="pf-c-description-list pf-m-horizontal">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${gettext("Certificate Fingerprint")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${item.fingerprint}</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("Certificate Subjet")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${item.cert_subject}</div>
</dd>
</div>
</dl>
<dl class="pf-c-description-list pf-m-horizontal">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${gettext("Certificate Fingerprint")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${item.fingerprint}</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("Certificate Subjet")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${item.certSubject}</div>
</dd>
</div>
</dl>
</div>
</td>
<td></td>
@ -99,13 +102,13 @@ export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
renderToolbar(): TemplateResult {
return html`
<ak-modal-button href=${CertificateKeyPair.adminUrl("create/")}>
<ak-modal-button href=${AdminURLManager.cryptoCertificates("create/")}>
<ak-spinner-button slot="trigger" class="pf-m-primary">
${gettext("Create")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href=${CertificateKeyPair.adminUrl("generate/")}>
<ak-modal-button href=${AdminURLManager.cryptoCertificates("generate/")}>
<ak-spinner-button slot="trigger" class="pf-m-secondary">
${gettext("Generate")}
</ak-spinner-button>

View File

@ -1,18 +1,19 @@
import { gettext } from "django";
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { until } from "lit-html/directives/until";
import { Event, EventContext } from "../../api/Events";
import { Flow } from "../../api/Flows";
import { FlowsApi } from "../../api";
import { COMMON_STYLES } from "../../common/styles";
import "../../elements/Spinner";
import "../../elements/Expand";
import { SpinnerSize } from "../../elements/Spinner";
import { EventContext, EventWithContext } from "../../api/Events";
import { DEFAULT_CONFIG } from "../../api/Config";
@customElement("ak-event-info")
export class EventInfo extends LitElement {
@property({attribute: false})
event?: Event;
event!: EventWithContext;
static get styles(): CSSResult[] {
return COMMON_STYLES.concat(
@ -73,15 +74,15 @@ export class EventInfo extends LitElement {
defaultResponse(): TemplateResult {
return html`<div class="pf-l-flex">
<div class="pf-l-flex__item">
<h3>${gettext("Context")}</h3>
<code>${JSON.stringify(this.event?.context, null, 4)}</code>
</div>
<div class="pf-l-flex__item">
<h3>${gettext("User")}</h3>
<code>${JSON.stringify(this.event?.user, null, 4)}</code>
</div>
</div>`;
<div class="pf-l-flex__item">
<h3>${gettext("Context")}</h3>
<code>${JSON.stringify(this.event?.context, null, 4)}</code>
</div>
<div class="pf-l-flex__item">
<h3>${gettext("User")}</h3>
<code>${JSON.stringify(this.event?.user, null, 4)}</code>
</div>
</div>`;
}
render(): TemplateResult {
@ -94,7 +95,7 @@ export class EventInfo extends LitElement {
case "model_deleted":
return html`
<h3>${gettext("Affected model:")}</h3>
${this.getModelInfo(this.event.context.model as EventContext)}
${this.getModelInfo(this.event.context?.model as EventContext)}
`;
case "authorize_application":
return html`<div class="pf-l-flex">
@ -104,8 +105,8 @@ export class EventInfo extends LitElement {
</div>
<div class="pf-l-flex__item">
<h3>${gettext("Using flow")}</h3>
<span>${until(Flow.list({
flow_uuid: this.event.context.flow as string,
<span>${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
flowUuid: this.event.context.flow as string,
}).then(resp => {
return html`<a href="#/flow/flows/${resp.results[0].slug}">${resp.results[0].name}</a>`;
}), html`<ak-spinner size=${SpinnerSize.Medium}></ak-spinner>`)}

View File

@ -1,6 +1,8 @@
import { gettext } from "django";
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { Event } from "../../api/Events";
import { EventsApi } from "../../api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { EventWithContext } from "../../api/Events";
import { COMMON_STYLES } from "../../common/styles";
import "./EventInfo";
@ -13,11 +15,15 @@ export class EventInfoPage extends LitElement {
@property()
set eventID(value: string) {
Event.get(value).then((e) => (this.event = e));
new EventsApi(DEFAULT_CONFIG).eventsEventsRead({
eventUuid: value
}).then((ev) => {
this.event = ev as EventWithContext;
});
}
@property({ attribute: false })
event?: Event;
event!: EventWithContext;
static get styles(): CSSResult[] {
return COMMON_STYLES.concat(css`

View File

@ -1,11 +1,12 @@
import { gettext } from "django";
import { customElement, html, property, TemplateResult } from "lit-element";
import { Event, EventsApi } from "../../api";
import { AKResponse } from "../../api/Client";
import { Event } from "../../api/Events";
import { DEFAULT_CONFIG } from "../../api/Config";
import { EventWithContext } from "../../api/Events";
import { PAGE_SIZE } from "../../constants";
import { TableColumn } from "../../elements/table/Table";
import { TablePage } from "../../elements/table/TablePage";
import { time } from "../../utils";
import "./EventInfo";
@customElement("ak-event-list")
@ -29,10 +30,10 @@ export class EventListPage extends TablePage<Event> {
order = "-created";
apiEndpoint(page: number): Promise<AKResponse<Event>> {
return Event.list({
return new EventsApi(DEFAULT_CONFIG).eventsEventsList({
ordering: this.order,
page: page,
page_size: PAGE_SIZE * 3,
pageSize: PAGE_SIZE * 3,
search: this.search || "",
});
}
@ -45,16 +46,16 @@ export class EventListPage extends TablePage<Event> {
new TableColumn("Client IP", "client_ip"),
];
}
row(item: Event): TemplateResult[] {
row(item: EventWithContext): TemplateResult[] {
return [
html`<div>${item.action}</div>
<small>${item.app}</small>`,
html`<div>${item.user.username}</div>
html`<div>${item.user?.username}</div>
${item.user.on_behalf_of ? html`<small>
${gettext(`On behalf of ${item.user.on_behalf_of.username}`)}
</small>` : html``}`,
html`<span>${time(item.created).toLocaleString()}</span>`,
html`<span>${item.client_ip}</span>`,
html`<span>${item.created?.toLocaleString()}</span>`,
html`<span>${item.clientIp}</span>`,
];
}
@ -62,7 +63,7 @@ export class EventListPage extends TablePage<Event> {
return html`
<td role="cell" colspan="4">
<div class="pf-c-table__expandable-row-content">
<ak-event-info .event=${item}></ak-event-info>
<ak-event-info .event=${item as EventWithContext}></ak-event-info>
</div>
</td>
<td></td>

View File

@ -7,11 +7,13 @@ import "../../elements/policies/BoundPoliciesList";
import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton";
import { TableColumn } from "../../elements/table/Table";
import { Rule } from "../../api/EventRules";
import { PAGE_SIZE } from "../../constants";
import { EventsApi, NotificationRule } from "../../api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy";
@customElement("ak-event-rule-list")
export class RuleListPage extends TablePage<Rule> {
export class RuleListPage extends TablePage<NotificationRule> {
expandable = true;
searchEnabled(): boolean {
@ -30,11 +32,11 @@ export class RuleListPage extends TablePage<Rule> {
@property()
order = "name";
apiEndpoint(page: number): Promise<AKResponse<Rule>> {
return Rule.list({
apiEndpoint(page: number): Promise<AKResponse<NotificationRule>> {
return new EventsApi(DEFAULT_CONFIG).eventsRulesList({
ordering: this.order,
page: page,
page_size: PAGE_SIZE,
pageSize: PAGE_SIZE,
search: this.search || "",
});
}
@ -48,19 +50,19 @@ export class RuleListPage extends TablePage<Rule> {
];
}
row(item: Rule): TemplateResult[] {
row(item: NotificationRule): TemplateResult[] {
return [
html`${item.name}`,
html`${item.severity}`,
html`${item.group?.name || gettext("None (rule disabled)")}`,
html`
<ak-modal-button href="${Rule.adminUrl(`${item.pk}/update/`)}">
<ak-modal-button href="${AdminURLManager.eventRules(`${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="${Rule.adminUrl(`${item.pk}/delete/`)}">
<ak-modal-button href="${AdminURLManager.eventRules(`${item.pk}/delete/`)}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
${gettext("Delete")}
</ak-spinner-button>
@ -72,7 +74,7 @@ export class RuleListPage extends TablePage<Rule> {
renderToolbar(): TemplateResult {
return html`
<ak-modal-button href=${Rule.adminUrl("create/")}>
<ak-modal-button href=${AdminURLManager.eventRules("create/")}>
<ak-spinner-button slot="trigger" class="pf-m-primary">
${gettext("Create")}
</ak-spinner-button>
@ -82,7 +84,7 @@ export class RuleListPage extends TablePage<Rule> {
`;
}
renderExpanded(item: Rule): TemplateResult {
renderExpanded(item: NotificationRule): TemplateResult {
return html`
<td role="cell" colspan="4">
<div class="pf-c-table__expandable-row-content">

View File

@ -1,17 +1,19 @@
import { gettext } from "django";
import { customElement, html, property, TemplateResult } from "lit-element";
import { DefaultClient, AKResponse } from "../../api/Client";
import { AKResponse } from "../../api/Client";
import { TablePage } from "../../elements/table/TablePage";
import "../../elements/buttons/ActionButton";
import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton";
import { TableColumn } from "../../elements/table/Table";
import { Transport } from "../../api/EventTransports";
import { PAGE_SIZE } from "../../constants";
import { EventsApi, NotificationTransport } from "../../api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy";
@customElement("ak-event-transport-list")
export class TransportListPage extends TablePage<Transport> {
export class TransportListPage extends TablePage<NotificationTransport> {
searchEnabled(): boolean {
return true;
}
@ -28,11 +30,11 @@ export class TransportListPage extends TablePage<Transport> {
@property()
order = "name";
apiEndpoint(page: number): Promise<AKResponse<Transport>> {
return Transport.list({
apiEndpoint(page: number): Promise<AKResponse<NotificationTransport>> {
return new EventsApi(DEFAULT_CONFIG).eventsTransportsList({
ordering: this.order,
page: page,
page_size: PAGE_SIZE,
pageSize: PAGE_SIZE,
search: this.search || "",
});
}
@ -45,21 +47,26 @@ export class TransportListPage extends TablePage<Transport> {
];
}
row(item: Transport): TemplateResult[] {
row(item: NotificationTransport): TemplateResult[] {
return [
html`${item.name}`,
html`${item.mode_verbose}`,
html`${item.modeVerbose}`,
html`
<ak-action-button url="${DefaultClient.makeUrl(["events", "transports", item.pk, "test"])}">
<ak-action-button
.apiRequest=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsTransportsTest({
uuid: item.pk || "",
});
}}>
${gettext("Test")}
</ak-action-button>
<ak-modal-button href="${Transport.adminUrl(`${item.pk}/update/`)}">
<ak-modal-button href="${AdminURLManager.eventTransports(`${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="${Transport.adminUrl(`${item.pk}/delete/`)}">
<ak-modal-button href="${AdminURLManager.eventTransports(`${item.pk}/delete/`)}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
${gettext("Delete")}
</ak-spinner-button>
@ -71,7 +78,7 @@ export class TransportListPage extends TablePage<Transport> {
renderToolbar(): TemplateResult {
return html`
<ak-modal-button href=${Transport.adminUrl("create/")}>
<ak-modal-button href=${AdminURLManager.eventTransports("create/")}>
<ak-spinner-button slot="trigger" class="pf-m-primary">
${gettext("Create")}
</ak-spinner-button>

View File

@ -4,14 +4,15 @@ import { AKResponse } from "../../api/Client";
import { Table, TableColumn } from "../../elements/table/Table";
import "../../elements/Tabs";
import "../../elements/AdminLoginsChart";
import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton";
import "../../elements/buttons/Dropdown";
import "../../elements/policies/BoundPoliciesList";
import { FlowStageBinding, Stage } from "../../api/Flows";
import { until } from "lit-html/directives/until";
import { PAGE_SIZE } from "../../constants";
import { FlowsApi, FlowStageBinding, StagesApi } from "../../api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy";
@customElement("ak-bound-stages-list")
export class BoundStagesList extends Table<FlowStageBinding> {
@ -21,11 +22,11 @@ export class BoundStagesList extends Table<FlowStageBinding> {
target?: string;
apiEndpoint(page: number): Promise<AKResponse<FlowStageBinding>> {
return FlowStageBinding.list({
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsList({
target: this.target || "",
ordering: "order",
page: page,
page_size: PAGE_SIZE,
pageSize: PAGE_SIZE,
});
}
@ -41,22 +42,22 @@ export class BoundStagesList extends Table<FlowStageBinding> {
row(item: FlowStageBinding): TemplateResult[] {
return [
html`${item.order}`,
html`${item.stage_obj.name}`,
html`${item.stage_obj.verbose_name}`,
html`${item.stageObj?.name}`,
html`${item.stageObj?.verboseName}`,
html`
<ak-modal-button href="${FlowStageBinding.adminUrl(`${item.pk}/update/`)}">
<ak-modal-button href="${AdminURLManager.stageBindings(`${item.pk}/update/`)}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
${gettext("Edit Binding")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="${Stage.adminUrl(`${item.stage}/update/`)}">
<ak-modal-button href="${AdminURLManager.stages(`${item.stage}/update/`)}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
${gettext("Edit Stage")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="${FlowStageBinding.adminUrl(`${item.pk}/delete/`)}">
<ak-modal-button href="${AdminURLManager.stages(`${item.pk}/delete/`)}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
${gettext("Delete")}
</ak-spinner-button>
@ -73,7 +74,7 @@ export class BoundStagesList extends Table<FlowStageBinding> {
<div class="pf-c-table__expandable-row-content">
<div class="pf-c-content">
<p>${gettext("These policies control when this stage will be applied to the flow.")}</p>
<ak-bound-policies-list .target=${item.policybindingmodel_ptr_id}>
<ak-bound-policies-list .target=${item.policybindingmodelPtrId}>
</ak-bound-policies-list>
</div>
</div>
@ -88,7 +89,7 @@ export class BoundStagesList extends Table<FlowStageBinding> {
${gettext("No stages are currently bound to this flow.")}
</div>
<div slot="primary">
<ak-modal-button href="${FlowStageBinding.adminUrl(`create/?target=${this.target}`)}">
<ak-modal-button href="${AdminURLManager.stageBindings(`create/?target=${this.target}`)}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
${gettext("Bind Stage")}
</ak-spinner-button>
@ -106,7 +107,7 @@ export class BoundStagesList extends Table<FlowStageBinding> {
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(Stage.getTypes().then((types) => {
${until(new StagesApi(DEFAULT_CONFIG).stagesAllTypes({}).then((types) => {
return types.map((type) => {
return html`<li>
<ak-modal-button href="${type.link}">
@ -120,7 +121,7 @@ export class BoundStagesList extends Table<FlowStageBinding> {
}), html`<ak-spinner></ak-spinner>`)}
</ul>
</ak-dropdown>
<ak-modal-button href="${FlowStageBinding.adminUrl(`create/?target=${this.target}`)}">
<ak-modal-button href="${AdminURLManager.stageBindings(`create/?target=${this.target}`)}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
${gettext("Bind Stage")}
</ak-spinner-button>

View File

@ -1,7 +1,8 @@
import { customElement, html, LitElement, property, TemplateResult } from "lit-element";
import FlowChart from "flowchart.js";
import { Flow } from "../../api/Flows";
import { loading } from "../../utils";
import { FlowsApi } from "../../api";
import { DEFAULT_CONFIG } from "../../api/Config";
export const FONT_COLOUR_DARK_MODE = "#fafafa";
export const FONT_COLOUR_LIGHT_MODE = "#151515";
@ -16,8 +17,10 @@ export class FlowDiagram extends LitElement {
@property()
set flowSlug(value: string) {
this._flowSlug = value;
Flow.diagram(value).then((data) => {
this.diagram = FlowChart.parse(data.diagram);
new FlowsApi(DEFAULT_CONFIG).flowsInstancesDiagram({
slug: value,
}).then((data) => {
this.diagram = FlowChart.parse(data.diagram || "");
});
}

View File

@ -1,6 +1,5 @@
import { gettext } from "django";
import { customElement, html, property, TemplateResult } from "lit-element";
import { Flow } from "../../api/Flows";
import { AKResponse } from "../../api/Client";
import { TablePage } from "../../elements/table/TablePage";
@ -8,6 +7,9 @@ import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton";
import { TableColumn } from "../../elements/table/Table";
import { PAGE_SIZE } from "../../constants";
import { Flow, FlowsApi } from "../../api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy";
@customElement("ak-flow-list")
export class FlowListPage extends TablePage<Flow> {
@ -28,10 +30,10 @@ export class FlowListPage extends TablePage<Flow> {
order = "slug";
apiEndpoint(page: number): Promise<AKResponse<Flow>> {
return Flow.list({
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: this.order,
page: page,
page_size: PAGE_SIZE,
pageSize: PAGE_SIZE,
search: this.search || "",
});
}
@ -54,25 +56,25 @@ export class FlowListPage extends TablePage<Flow> {
</a>`,
html`${item.name}`,
html`${item.designation}`,
html`${item.stages.length}`,
html`${item.policies.length}`,
html`${item.stages?.size}`,
html`${item.policies?.size}`,
html`
<ak-modal-button href="${Flow.adminUrl(`${item.pk}/update/`)}">
<ak-modal-button href="${AdminURLManager.flows(`${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="${Flow.adminUrl(`${item.pk}/delete/`)}">
<ak-modal-button href="${AdminURLManager.flows(`${item.pk}/delete/`)}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
${gettext("Delete")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<a class="pf-c-button pf-m-secondary ak-root-link" href="${Flow.adminUrl(`${item.pk}/execute/?next=/%23${window.location.href}`)}">
<a class="pf-c-button pf-m-secondary ak-root-link" href="${AdminURLManager.flows(`${item.pk}/execute/?next=/%23${window.location.href}`)}">
${gettext("Execute")}
</a>
<a class="pf-c-button pf-m-secondary ak-root-link" href="${Flow.adminUrl(`${item.pk}/export/`)}">
<a class="pf-c-button pf-m-secondary ak-root-link" href="${AdminURLManager.flows(`${item.pk}/export/`)}">
${gettext("Export")}
</a>
`,
@ -81,13 +83,13 @@ export class FlowListPage extends TablePage<Flow> {
renderToolbar(): TemplateResult {
return html`
<ak-modal-button href=${Flow.adminUrl("create/")}>
<ak-modal-button href=${AdminURLManager.flows("create/")}>
<ak-spinner-button slot="trigger" class="pf-m-primary">
${gettext("Create")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href=${Flow.adminUrl("import/")}>
<ak-modal-button href=${AdminURLManager.flows("import/")}>
<ak-spinner-button slot="trigger" class="pf-m-secondary">
${gettext("Import")}
</ak-spinner-button>

View File

@ -1,25 +1,29 @@
import { gettext } from "django";
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { COMMON_STYLES } from "../../common/styles";
import { Flow } from "../../api/Flows";
import "../../elements/Tabs";
import "../../elements/AdminLoginsChart";
import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton";
import "../../elements/policies/BoundPoliciesList";
import "./BoundStagesList";
import "./FlowDiagram";
import { Flow, FlowsApi } from "../../api";
import { DEFAULT_CONFIG } from "../../api/Config";
@customElement("ak-flow-view")
export class FlowViewPage extends LitElement {
@property()
set flowSlug(value: string) {
Flow.get(value).then((flow) => (this.flow = flow));
new FlowsApi(DEFAULT_CONFIG).flowsInstancesRead({
slug: value
}).then((flow) => {
this.flow = flow;
});
}
@property({attribute: false})
flow?: Flow;
flow!: Flow;
static get styles(): CSSResult[] {
return COMMON_STYLES.concat(
@ -67,7 +71,7 @@ export class FlowViewPage extends LitElement {
${gettext("These policies control which users can access this flow.")}
</div>
</div>
<ak-bound-policies-list .target=${this.flow.policybindingmodel_ptr_id}>
<ak-bound-policies-list .target=${this.flow.policybindingmodelPtrId}>
</ak-bound-policies-list>
</div>
</div>

View File

@ -6,8 +6,10 @@ import { TablePage } from "../../elements/table/TablePage";
import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton";
import { TableColumn } from "../../elements/table/Table";
import { Group } from "../../api/Groups";
import { PAGE_SIZE } from "../../constants";
import { CoreApi, Group } from "../../api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy";
@customElement("ak-group-list")
export class GroupListPage extends TablePage<Group> {
@ -28,10 +30,10 @@ export class GroupListPage extends TablePage<Group> {
order = "slug";
apiEndpoint(page: number): Promise<AKResponse<Group>> {
return Group.list({
return new CoreApi(DEFAULT_CONFIG).coreGroupsList({
ordering: this.order,
page: page,
page_size: PAGE_SIZE,
pageSize: PAGE_SIZE,
search: this.search || "",
});
}
@ -50,16 +52,16 @@ export class GroupListPage extends TablePage<Group> {
return [
html`${item.name}`,
html`${item.parent || "-"}`,
html`${item.users.length}`,
html`${item.is_superuser ? "Yes" : "No"}`,
html`${item.users.keys.length}`,
html`${item.isSuperuser ? "Yes" : "No"}`,
html`
<ak-modal-button href="${Group.adminUrl(`${item.pk}/update/`)}">
<ak-modal-button href="${AdminURLManager.groups(`${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="${Group.adminUrl(`${item.pk}/delete/`)}">
<ak-modal-button href="${AdminURLManager.groups(`${item.pk}/delete/`)}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
${gettext("Delete")}
</ak-spinner-button>
@ -70,7 +72,7 @@ export class GroupListPage extends TablePage<Group> {
renderToolbar(): TemplateResult {
return html`
<ak-modal-button href=${Group.adminUrl("create/")}>
<ak-modal-button href=${AdminURLManager.groups("create/")}>
<ak-spinner-button slot="trigger" class="pf-m-primary">
${gettext("Create")}
</ak-spinner-button>

View File

@ -1,8 +1,10 @@
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 { OutpostsApi } from "../../api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { COMMON_STYLES } from "../../common/styles";
import "../../elements/Spinner";
@customElement("ak-outpost-health")
export class OutpostHealth extends LitElement {
@ -18,7 +20,9 @@ export class OutpostHealth extends LitElement {
if (!this.outpostId) {
return html`<ak-spinner></ak-spinner>`;
}
return html`<ul>${until(Outpost.health(this.outpostId).then((oh) => {
return html`<ul>${until(new OutpostsApi(DEFAULT_CONFIG).outpostsOutpostsHealth({
uuid: this.outpostId
}).then((oh) => {
if (oh.length === 0) {
return html`<li>
<ul>
@ -32,12 +36,12 @@ export class OutpostHealth extends LitElement {
return html`<li>
<ul>
<li role="cell">
<i class="fas fa-check pf-m-success"></i>${gettext(`Last seen: ${new Date(h.last_seen * 1000).toLocaleTimeString()}`)}
<i class="fas fa-check pf-m-success"></i>${gettext(`Last seen: ${h.lastSeen?.toLocaleTimeString()}`)}
</li>
<li role="cell">
${h.version_outdated ?
${h.versionOutdated ?
html`<i class="fas fa-times pf-m-danger"></i>
${gettext(`${h.version}, should be ${h.version_should}`)}` :
${gettext(`${h.version}, should be ${h.versionShould}`)}` :
html`<i class="fas fa-check pf-m-success"></i>${gettext(`Version: ${h.version}`)}`}
</li>
</ul>

View File

@ -2,7 +2,6 @@ import { gettext } from "django";
import { customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
import { AKResponse } from "../../api/Client";
import { Outpost } from "../../api/Outposts";
import { TableColumn } from "../../elements/table/Table";
import { TablePage } from "../../elements/table/TablePage";
@ -11,6 +10,10 @@ import "../../elements/buttons/SpinnerButton";
import "../../elements/buttons/ModalButton";
import "../../elements/buttons/TokenCopyButton";
import { PAGE_SIZE } from "../../constants";
import { Outpost, OutpostsApi } from "../../api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy";
import { ifDefined } from "lit-html/directives/if-defined";
@customElement("ak-outpost-list")
export class OutpostListPage extends TablePage<Outpost> {
@ -27,10 +30,10 @@ export class OutpostListPage extends TablePage<Outpost> {
return true;
}
apiEndpoint(page: number): Promise<AKResponse<Outpost>> {
return Outpost.list({
return new OutpostsApi(DEFAULT_CONFIG).outpostsOutpostsList({
ordering: this.order,
page: page,
page_size: PAGE_SIZE,
pageSize: PAGE_SIZE,
search: this.search || "",
});
}
@ -49,18 +52,18 @@ export class OutpostListPage extends TablePage<Outpost> {
row(item: Outpost): TemplateResult[] {
return [
html`${item.name}`,
html`<ul>${item.providers_obj.map((p) => {
html`<ul>${item.providersObj?.map((p) => {
return html`<li><a href="#/core/providers/${p.pk}">${p.name}</a></li>`;
})}</ul>`,
html`<ak-outpost-health outpostId=${item.pk}></ak-outpost-health>`,
html`<ak-outpost-health outpostId=${ifDefined(item.pk)}></ak-outpost-health>`,
html`
<ak-modal-button href="${Outpost.adminUrl(`${item.pk}/update/`)}">
<ak-modal-button href="${AdminURLManager.outposts(`${item.pk}/update/`)}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
${gettext("Edit")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>&nbsp;
<ak-modal-button href="${Outpost.adminUrl(`${item.pk}/delete/`)}">
<ak-modal-button href="${AdminURLManager.outposts(`${item.pk}/delete/`)}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
${gettext("Delete")}
</ak-spinner-button>
@ -88,7 +91,7 @@ export class OutpostListPage extends TablePage<Outpost> {
<span class="pf-c-form__label-text">AUTHENTIK_TOKEN</span>
</label>
<div>
<ak-token-copy-button identifier="${item.token_identifier}">
<ak-token-copy-button identifier="${ifDefined(item.tokenIdentifier)}">
${gettext("Click to copy token")}
</ak-token-copy-button>
</div>
@ -112,7 +115,7 @@ export class OutpostListPage extends TablePage<Outpost> {
renderToolbar(): TemplateResult {
return html`
<ak-modal-button href=${Outpost.adminUrl("create/")}>
<ak-modal-button href=${AdminURLManager.outposts("create/")}>
<ak-spinner-button slot="trigger" class="pf-m-primary">
${gettext("Create")}
</ak-spinner-button>

View File

@ -2,7 +2,6 @@ import { gettext } from "django";
import { customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
import { AKResponse } from "../../api/Client";
import { OutpostServiceConnection } from "../../api/Outposts";
import { TableColumn } from "../../elements/table/Table";
import { TablePage } from "../../elements/table/TablePage";
@ -12,9 +11,12 @@ import "../../elements/buttons/ModalButton";
import "../../elements/buttons/Dropdown";
import { until } from "lit-html/directives/until";
import { PAGE_SIZE } from "../../constants";
import { OutpostsApi, ServiceConnection } from "../../api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy";
@customElement("ak-outpost-service-connection-list")
export class OutpostServiceConnectionListPage extends TablePage<OutpostServiceConnection> {
export class OutpostServiceConnectionListPage extends TablePage<ServiceConnection> {
pageTitle(): string {
return "Outpost Service-Connections";
}
@ -28,14 +30,15 @@ export class OutpostServiceConnectionListPage extends TablePage<OutpostServiceCo
return true;
}
apiEndpoint(page: number): Promise<AKResponse<OutpostServiceConnection>> {
return OutpostServiceConnection.list({
apiEndpoint(page: number): Promise<AKResponse<ServiceConnection>> {
return new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllList({
ordering: this.order,
page: page,
page_size: PAGE_SIZE,
pageSize: PAGE_SIZE,
search: this.search || "",
});
}
columns(): TableColumn[] {
return [
new TableColumn("Name", "name"),
@ -49,25 +52,28 @@ export class OutpostServiceConnectionListPage extends TablePage<OutpostServiceCo
@property()
order = "name";
row(item: OutpostServiceConnection): TemplateResult[] {
row(item: ServiceConnection): TemplateResult[] {
return [
html`${item.name}`,
html`${item.verbose_name}`,
html`${item.verboseName}`,
html`${item.local ? "Yes" : "No"}`,
html`${until(OutpostServiceConnection.state(item.pk).then((state) => {
if (state.healthy) {
return html`<i class="fas fa-check pf-m-success"></i> ${state.version}`;
}
return html`<i class="fas fa-times pf-m-danger"></i> ${gettext("Unhealthy")}`;
}), html`<ak-spinner></ak-spinner>`)}`,
html`${until(
new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllState({
uuid: item.pk || ""
}).then((state) => {
if (state.healthy) {
return html`<i class="fas fa-check pf-m-success"></i> ${state.version}`;
}
return html`<i class="fas fa-times pf-m-danger"></i> ${gettext("Unhealthy")}`;
}), html`<ak-spinner></ak-spinner>`)}`,
html`
<ak-modal-button href="${OutpostServiceConnection.adminUrl(`${item.pk}/update/`)}">
<ak-modal-button href="${AdminURLManager.outpostServiceConnections(`${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="${OutpostServiceConnection.adminUrl(`${item.pk}/delete/`)}">
<ak-modal-button href="${AdminURLManager.outpostServiceConnections(`${item.pk}/delete/`)}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
${gettext("Delete")}
</ak-spinner-button>
@ -84,7 +90,7 @@ export class OutpostServiceConnectionListPage extends TablePage<OutpostServiceCo
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(OutpostServiceConnection.getTypes().then((types) => {
${until(new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllTypes({}).then((types) => {
return types.map((type) => {
return html`<li>
<ak-modal-button href="${type.link}">

View File

@ -7,9 +7,11 @@ import "../../elements/buttons/ModalButton";
import "../../elements/buttons/Dropdown";
import "../../elements/buttons/SpinnerButton";
import { TableColumn } from "../../elements/table/Table";
import { Policy } from "../../api/Policies";
import { until } from "lit-html/directives/until";
import { PAGE_SIZE } from "../../constants";
import { PoliciesApi, Policy } from "../../api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy";
@customElement("ak-policy-list")
export class PolicyListPage extends TablePage<Policy> {
@ -30,10 +32,10 @@ export class PolicyListPage extends TablePage<Policy> {
order = "name";
apiEndpoint(page: number): Promise<AKResponse<Policy>> {
return Policy.list({
return new PoliciesApi(DEFAULT_CONFIG).policiesAllList({
ordering: this.order,
page: page,
page_size: PAGE_SIZE,
pageSize: PAGE_SIZE,
search: this.search || "",
});
}
@ -50,29 +52,29 @@ export class PolicyListPage extends TablePage<Policy> {
return [
html`<div>
<div>${item.name}</div>
${item.bound_to > 0 ?
${(item.boundTo || 0) > 0 ?
html`<i class="pf-icon pf-icon-ok"></i>
<small>
${gettext(`Assigned to ${item.bound_to} objects.`)}
${gettext(`Assigned to ${item.boundTo} objects.`)}
</small>`:
html`<i class="pf-icon pf-icon-warning-triangle"></i>
<small>${gettext("Warning: Policy is not assigned.")}</small>`}
</div>`,
html`${item.verbose_name}`,
html`${item.verboseName}`,
html`
<ak-modal-button href="${Policy.adminUrl(`${item.pk}/update/`)}">
<ak-modal-button href="${AdminURLManager.policies(`${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="${Policy.adminUrl(`${item.pk}/test/`)}">
<ak-modal-button href="${AdminURLManager.policies(`${item.pk}/test/`)}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
${gettext("Test")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="${Policy.adminUrl(`${item.pk}/delete/`)}">
<ak-modal-button href="${AdminURLManager.policies(`${item.pk}/delete/`)}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
${gettext("Delete")}
</ak-spinner-button>
@ -90,7 +92,7 @@ export class PolicyListPage extends TablePage<Policy> {
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(Policy.getTypes().then((types) => {
${until(new PoliciesApi(DEFAULT_CONFIG).policiesAllTypes({}).then((types) => {
return types.map((type) => {
return html`<li>
<ak-modal-button href="${type.link}">

View File

@ -1,6 +1,5 @@
import { gettext } from "django";
import { customElement, html, property, TemplateResult } from "lit-element";
import { PropertyMapping } from "../../api/PropertyMapping";
import { AKResponse } from "../../api/Client";
import { TablePage } from "../../elements/table/TablePage";
@ -10,6 +9,9 @@ import "../../elements/buttons/SpinnerButton";
import { TableColumn } from "../../elements/table/Table";
import { until } from "lit-html/directives/until";
import { PAGE_SIZE } from "../../constants";
import { PropertyMapping, PropertymappingsApi } from "../../api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy";
@customElement("ak-property-mapping-list")
export class PropertyMappingListPage extends TablePage<PropertyMapping> {
@ -33,12 +35,12 @@ export class PropertyMappingListPage extends TablePage<PropertyMapping> {
hideManaged = false;
apiEndpoint(page: number): Promise<AKResponse<PropertyMapping>> {
return PropertyMapping.list({
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllList({
ordering: this.order,
page: page,
page_size: PAGE_SIZE,
pageSize: PAGE_SIZE,
search: this.search || "",
managed__isnull: this.hideManaged,
managedIsnull: this.hideManaged.toString(),
});
}
@ -53,21 +55,21 @@ export class PropertyMappingListPage extends TablePage<PropertyMapping> {
row(item: PropertyMapping): TemplateResult[] {
return [
html`${item.name}`,
html`${item.verbose_name}`,
html`${item.verboseName}`,
html`
<ak-modal-button href="${PropertyMapping.adminUrl(`${item.pk}/update/`)}">
<ak-modal-button href="${AdminURLManager.propertyMappings(`${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="${PropertyMapping.adminUrl(`${item.pk}/test/`)}">
<ak-modal-button href="${AdminURLManager.propertyMappings(`${item.pk}/test/`)}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
${gettext("Test")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="${PropertyMapping.adminUrl(`${item.pk}/delete/`)}">
<ak-modal-button href="${AdminURLManager.propertyMappings(`${item.pk}/delete/`)}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
${gettext("Delete")}
</ak-spinner-button>
@ -85,7 +87,7 @@ export class PropertyMappingListPage extends TablePage<PropertyMapping> {
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(PropertyMapping.getTypes().then((types) => {
${until(new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllTypes({}).then((types) => {
return types.map((type) => {
return html`<li>
<ak-modal-button href="${type.link}">

View File

@ -1,7 +1,5 @@
import { gettext } from "django";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { Provider } from "../../api/Providers";
import { OAuth2Provider, OAuth2SetupURLs } from "../../api/providers/OAuth2";
import { COMMON_STYLES } from "../../common/styles";
import "../../elements/buttons/ModalButton";
@ -11,6 +9,9 @@ import "../../elements/Tabs";
import { Page } from "../../elements/Page";
import { convertToTitle } from "../../utils";
import "./RelatedApplicationButton";
import { OAuth2Provider, OAuth2ProviderSetupURLs, ProvidersApi } from "../../api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy";
@customElement("ak-provider-oauth2-view")
export class OAuth2ProviderViewPage extends Page {
@ -26,15 +27,24 @@ export class OAuth2ProviderViewPage extends Page {
@property({type: Number})
set providerID(value: number) {
OAuth2Provider.get(value).then((app) => this.provider = app);
OAuth2Provider.getLaunchURls(value).then((urls) => this.providerUrls = urls);
const api = new ProvidersApi(DEFAULT_CONFIG);
api.providersOauth2Read({
id: value
}).then((prov) => {
this.provider = prov;
});
api.providersOauth2SetupUrls({
id: value
}).then((prov) => {
this.providerUrls = prov;
});
}
@property({ attribute: false })
provider?: OAuth2Provider;
@property({ attribute: false })
providerUrls?: OAuth2SetupURLs;
providerUrls?: OAuth2ProviderSetupURLs;
static get styles(): CSSResult[] {
return COMMON_STYLES;
@ -82,7 +92,7 @@ export class OAuth2ProviderViewPage extends Page {
<span class="pf-c-description-list__text">${gettext("Client type")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${convertToTitle(this.provider.client_type)}</div>
<div class="pf-c-description-list__text">${convertToTitle(this.provider.clientType || "")}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
@ -90,7 +100,7 @@ export class OAuth2ProviderViewPage extends Page {
<span class="pf-c-description-list__text">${gettext("Client ID")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.provider.client_id}</div>
<div class="pf-c-description-list__text">${this.provider.clientId}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
@ -98,13 +108,13 @@ export class OAuth2ProviderViewPage extends Page {
<span class="pf-c-description-list__text">${gettext("Redirect URIs")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.provider.redirect_uris}</div>
<div class="pf-c-description-list__text">${this.provider.redirectUris}</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-modal-button href="${Provider.adminUrl(`${this.provider.pk}/update/`)}">
<ak-modal-button href="${AdminURLManager.providers(`${this.provider.pk}/update/`)}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
${gettext("Edit")}
</ak-spinner-button>
@ -125,7 +135,7 @@ export class OAuth2ProviderViewPage extends Page {
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">${gettext("OpenID Configuration URL")}</span>
</label>
<input class="pf-c-form-control" readonly type="text" value="${this.providerUrls?.provider_info || "-"}" />
<input class="pf-c-form-control" readonly type="text" value="${this.providerUrls?.providerInfo || "-"}" />
</div>
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">
@ -150,7 +160,7 @@ export class OAuth2ProviderViewPage extends Page {
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">${gettext("Userinfo URL")}</span>
</label>
<input class="pf-c-form-control" readonly type="text" value="${this.providerUrls?.user_info || "-"}" />
<input class="pf-c-form-control" readonly type="text" value="${this.providerUrls?.userInfo || "-"}" />
</div>
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">

View File

@ -1,6 +1,5 @@
import { gettext } from "django";
import { customElement, html, property, TemplateResult } from "lit-element";
import { Provider } from "../../api/Providers";
import { AKResponse } from "../../api/Client";
import { TablePage } from "../../elements/table/TablePage";
@ -10,6 +9,9 @@ import "../../elements/buttons/Dropdown";
import { TableColumn } from "../../elements/table/Table";
import { until } from "lit-html/directives/until";
import { PAGE_SIZE } from "../../constants";
import { Provider, ProvidersApi } from "../../api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy";
@customElement("ak-provider-list")
export class ProviderListPage extends TablePage<Provider> {
@ -30,10 +32,10 @@ export class ProviderListPage extends TablePage<Provider> {
order = "name";
apiEndpoint(page: number): Promise<AKResponse<Provider>> {
return Provider.list({
return new ProvidersApi(DEFAULT_CONFIG).providersAllList({
ordering: this.order,
page: page,
page_size: PAGE_SIZE,
pageSize: PAGE_SIZE,
search: this.search || "",
});
}
@ -52,21 +54,21 @@ export class ProviderListPage extends TablePage<Provider> {
html`<a href="#/core/providers/${item.pk}">
${item.name}
</a>`,
item.assigned_application_name ?
item.assignedApplicationName ?
html`<i class="pf-icon pf-icon-ok"></i>
${gettext("Assigned to application ")}
<a href="#/core/applications/${item.assigned_application_slug}">${item.assigned_application_name}</a>` :
<a href="#/core/applications/${item.assignedApplicationSlug}">${item.assignedApplicationName}</a>` :
html`<i class="pf-icon pf-icon-warning-triangle"></i>
${gettext("Warning: Provider not assigned to any application.")}`,
html`${item.verbose_name}`,
html`${item.verboseName}`,
html`
<ak-modal-button href="${Provider.adminUrl(`${item.pk}/update/`)}">
<ak-modal-button href="${AdminURLManager.providers(`${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="${Provider.adminUrl(`${item.pk}/delete/`)}">
<ak-modal-button href="${AdminURLManager.providers(`${item.pk}/delete/`)}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
${gettext("Delete")}
</ak-spinner-button>
@ -84,7 +86,7 @@ export class ProviderListPage extends TablePage<Provider> {
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(Provider.getTypes().then((types) => {
${until(new ProvidersApi(DEFAULT_CONFIG).providersAllTypes({}).then((types) => {
return types.map((type) => {
return html`<li>
<ak-modal-button href="${type.link}">

Some files were not shown because too many files have changed in this diff Show More