Compare commits
15 Commits
flows/buff
...
web/add-co
Author | SHA1 | Date | |
---|---|---|---|
00c1e17b52 | |||
3c2ce40afd | |||
2aceed285e | |||
cba8e84bbe | |||
d313fd7fb4 | |||
102811508f | |||
16b3ca3715 | |||
8b4e0361c4 | |||
22cb5b7379 | |||
2d0117d096 | |||
035bda4eac | |||
50906214e5 | |||
e505f274b6 | |||
fe52f44dca | |||
3146e5a50f |
@ -20,6 +20,9 @@ from authentik.lib.utils.time import timedelta_from_string
|
|||||||
from authentik.policies.engine import PolicyEngine
|
from authentik.policies.engine import PolicyEngine
|
||||||
from authentik.policies.views import PolicyAccessView
|
from authentik.policies.views import PolicyAccessView
|
||||||
from authentik.providers.rac.models import ConnectionToken, Endpoint, RACProvider
|
from authentik.providers.rac.models import ConnectionToken, Endpoint, RACProvider
|
||||||
|
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
||||||
|
|
||||||
|
PLAN_CONNECTION_SETTINGS = "connection_settings"
|
||||||
|
|
||||||
|
|
||||||
class RACStartView(PolicyAccessView):
|
class RACStartView(PolicyAccessView):
|
||||||
@ -109,10 +112,15 @@ class RACFinalStage(RedirectStage):
|
|||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_challenge(self, *args, **kwargs) -> RedirectChallenge:
|
def get_challenge(self, *args, **kwargs) -> RedirectChallenge:
|
||||||
|
settings = self.executor.plan.context.get(PLAN_CONNECTION_SETTINGS)
|
||||||
|
if not settings:
|
||||||
|
settings = self.executor.plan.context.get(PLAN_CONTEXT_PROMPT, {}).get(
|
||||||
|
PLAN_CONNECTION_SETTINGS
|
||||||
|
)
|
||||||
token = ConnectionToken.objects.create(
|
token = ConnectionToken.objects.create(
|
||||||
provider=self.provider,
|
provider=self.provider,
|
||||||
endpoint=self.endpoint,
|
endpoint=self.endpoint,
|
||||||
settings=self.executor.plan.context.get("connection_settings", {}),
|
settings=settings or {},
|
||||||
session=self.request.session["authenticatedsession"],
|
session=self.request.session["authenticatedsession"],
|
||||||
expires=now() + timedelta_from_string(self.provider.connection_expiry),
|
expires=now() + timedelta_from_string(self.provider.connection_expiry),
|
||||||
expiring=True,
|
expiring=True,
|
||||||
|
109
web/package-lock.json
generated
109
web/package-lock.json
generated
@ -51,6 +51,7 @@
|
|||||||
"lit": "^3.2.0",
|
"lit": "^3.2.0",
|
||||||
"md-front-matter": "^1.0.4",
|
"md-front-matter": "^1.0.4",
|
||||||
"mermaid": "^11.6.0",
|
"mermaid": "^11.6.0",
|
||||||
|
"ninja-keys": "^1.2.2",
|
||||||
"rapidoc": "^9.3.8",
|
"rapidoc": "^9.3.8",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
@ -2587,6 +2588,57 @@
|
|||||||
"@lit/reactive-element": "^1.0.0 || ^2.0.0"
|
"@lit/reactive-element": "^1.0.0 || ^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@material/mwc-icon": {
|
||||||
|
"version": "0.25.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@material/mwc-icon/-/mwc-icon-0.25.3.tgz",
|
||||||
|
"integrity": "sha512-36076AWZIRSr8qYOLjuDDkxej/HA0XAosrj7TS1ZeLlUBnLUtbDtvc1S7KSa0hqez7ouzOqGaWK24yoNnTa2OA==",
|
||||||
|
"deprecated": "MWC beta is longer supported. Please upgrade to @material/web",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"lit": "^2.0.0",
|
||||||
|
"tslib": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@material/mwc-icon/node_modules/@lit/reactive-element": {
|
||||||
|
"version": "1.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz",
|
||||||
|
"integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@lit-labs/ssr-dom-shim": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@material/mwc-icon/node_modules/lit": {
|
||||||
|
"version": "2.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz",
|
||||||
|
"integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@lit/reactive-element": "^1.6.0",
|
||||||
|
"lit-element": "^3.3.0",
|
||||||
|
"lit-html": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@material/mwc-icon/node_modules/lit-element": {
|
||||||
|
"version": "3.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz",
|
||||||
|
"integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@lit-labs/ssr-dom-shim": "^1.1.0",
|
||||||
|
"@lit/reactive-element": "^1.3.0",
|
||||||
|
"lit-html": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@material/mwc-icon/node_modules/lit-html": {
|
||||||
|
"version": "2.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz",
|
||||||
|
"integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/trusted-types": "^2.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@mdx-js/mdx": {
|
"node_modules/@mdx-js/mdx": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz",
|
||||||
@ -16940,6 +16992,12 @@
|
|||||||
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/hotkeys-js": {
|
||||||
|
"version": "3.8.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/hotkeys-js/-/hotkeys-js-3.8.7.tgz",
|
||||||
|
"integrity": "sha512-ckAx3EkUr5XjDwjEHDorHxRO2Kb7z6Z2Sxul4MbBkN8Nho7XDslQsgMJT+CiJ5Z4TgRxxvKHEpuLE3imzqy4Lg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/html-escaper": {
|
"node_modules/html-escaper": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||||
@ -21632,6 +21690,57 @@
|
|||||||
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
|
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/ninja-keys": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ninja-keys/-/ninja-keys-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-ylo8jzKowi3XBHkgHRjBJaKQkl32WRLr7kRiA0ajiku11vHRDJ2xANtTScR5C7XlDwKEOYvUPesCKacUeeLAYw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@material/mwc-icon": "0.25.3",
|
||||||
|
"hotkeys-js": "3.8.7",
|
||||||
|
"lit": "2.2.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ninja-keys/node_modules/@lit/reactive-element": {
|
||||||
|
"version": "1.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz",
|
||||||
|
"integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@lit-labs/ssr-dom-shim": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ninja-keys/node_modules/lit": {
|
||||||
|
"version": "2.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit/-/lit-2.2.6.tgz",
|
||||||
|
"integrity": "sha512-K2vkeGABfSJSfkhqHy86ujchJs3NR9nW1bEEiV+bXDkbiQ60Tv5GUausYN2mXigZn8lC1qXuc46ArQRKYmumZw==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@lit/reactive-element": "^1.3.0",
|
||||||
|
"lit-element": "^3.2.0",
|
||||||
|
"lit-html": "^2.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ninja-keys/node_modules/lit-element": {
|
||||||
|
"version": "3.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz",
|
||||||
|
"integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@lit-labs/ssr-dom-shim": "^1.1.0",
|
||||||
|
"@lit/reactive-element": "^1.3.0",
|
||||||
|
"lit-html": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ninja-keys/node_modules/lit-html": {
|
||||||
|
"version": "2.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz",
|
||||||
|
"integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/trusted-types": "^2.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-abort-controller": {
|
"node_modules/node-abort-controller": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
|
||||||
|
@ -122,6 +122,7 @@
|
|||||||
"lit": "^3.2.0",
|
"lit": "^3.2.0",
|
||||||
"md-front-matter": "^1.0.4",
|
"md-front-matter": "^1.0.4",
|
||||||
"mermaid": "^11.6.0",
|
"mermaid": "^11.6.0",
|
||||||
|
"ninja-keys": "^1.2.2",
|
||||||
"rapidoc": "^9.3.8",
|
"rapidoc": "^9.3.8",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
172
web/src/admin/AdminInterface/AdminCommands.ts
Normal file
172
web/src/admin/AdminInterface/AdminCommands.ts
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
import { navigate } from "@goauthentik/elements/router/RouterOutlet";
|
||||||
|
import { INinjaAction } from "ninja-keys/dist/interfaces/ininja-action.js";
|
||||||
|
|
||||||
|
import { msg } from "@lit/localize";
|
||||||
|
|
||||||
|
export const adminCommands: INinjaAction[] = [
|
||||||
|
{
|
||||||
|
id: msg("Overview"),
|
||||||
|
title: msg("Dashboard"),
|
||||||
|
handler: () => navigate("/administration/overview"),
|
||||||
|
section: msg("Dashboards"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/administration/dashboard/users"),
|
||||||
|
id: msg("User Statistics"),
|
||||||
|
title: msg("User Statistics"),
|
||||||
|
icon: '<i class="pf-icon pf-icon-user"></i>',
|
||||||
|
section: msg("Dashboards"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/administration/system-tasks"),
|
||||||
|
id: msg("System Tasks"),
|
||||||
|
title: msg("System Tasks"),
|
||||||
|
section: msg("Dashboards"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/core/applications"),
|
||||||
|
id: msg("Applications"),
|
||||||
|
title: msg("Applications"),
|
||||||
|
section: msg("Applications"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/core/providers"),
|
||||||
|
id: msg("Providers"),
|
||||||
|
title: msg("Providers"),
|
||||||
|
section: msg("Applications"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/outpost/outposts"),
|
||||||
|
id: msg("Outposts"),
|
||||||
|
title: msg("Outposts"),
|
||||||
|
section: msg("Applications"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/events/log"),
|
||||||
|
id: msg("Logs"),
|
||||||
|
title: msg("Logs"),
|
||||||
|
section: msg("Events"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/events/rules"),
|
||||||
|
id: msg("Notification Rules"),
|
||||||
|
title: msg("Notification Rules"),
|
||||||
|
section: msg("Events"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/events/transports"),
|
||||||
|
id: msg("Notification Transports"),
|
||||||
|
title: msg("Notification Transports"),
|
||||||
|
section: msg("Events"),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
handler: () => navigate("/policy/policies"),
|
||||||
|
id: msg("Policies"),
|
||||||
|
title: msg("Policies"),
|
||||||
|
section: msg("Customization"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/core/property-mappings"),
|
||||||
|
id: msg("Property Mappings"),
|
||||||
|
title: msg("Property Mappings"),
|
||||||
|
section: msg("Customization"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/blueprints/instances"),
|
||||||
|
id: msg("Blueprints"),
|
||||||
|
title: msg("Blueprints"),
|
||||||
|
section: msg("Customization"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/policy/reputation"),
|
||||||
|
id: msg("Reputation scores"),
|
||||||
|
title: msg("Reputation scores"),
|
||||||
|
section: msg("Customization"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/flow/flows"),
|
||||||
|
id: msg("Flows"),
|
||||||
|
title: msg("Flows"),
|
||||||
|
section: msg("Flows"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/flow/stages"),
|
||||||
|
id: msg("Stages"),
|
||||||
|
title: msg("Stages"),
|
||||||
|
section: msg("Flows"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/flow/stages/prompts"),
|
||||||
|
id: msg("Prompts"),
|
||||||
|
title: msg("Prompts"),
|
||||||
|
section: msg("Flows"),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
handler: () => navigate("/identity/users"),
|
||||||
|
id: msg("Users"),
|
||||||
|
title: msg("Users"),
|
||||||
|
section: msg("Directory"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/identity/groups"),
|
||||||
|
id: msg("Groups"),
|
||||||
|
title: msg("Groups"),
|
||||||
|
section: msg("Directory"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/identity/roles"),
|
||||||
|
id: msg("Roles"),
|
||||||
|
title: msg("Roles"),
|
||||||
|
section: msg("Directory"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/core/sources"),
|
||||||
|
id: msg("Federation and Social login"),
|
||||||
|
title: msg("Federation and Social login"),
|
||||||
|
section: msg("Directory"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/core/tokens"),
|
||||||
|
id: msg("Tokens and App passwords"),
|
||||||
|
title: msg("Tokens and App passwords"),
|
||||||
|
section: msg("Directory"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/flow/stages/invitations"),
|
||||||
|
id: msg("Invitations"),
|
||||||
|
title: msg("Invitations"),
|
||||||
|
section: msg("Directory"),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
handler: () => navigate("/core/brands"),
|
||||||
|
id: msg("Brands"),
|
||||||
|
title: msg("Brands"),
|
||||||
|
section: msg("System"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/crypto/certificates"),
|
||||||
|
id: msg("Certificates"),
|
||||||
|
title: msg("Certificates"),
|
||||||
|
section: msg("System"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/outpost/integrations"),
|
||||||
|
id: msg("Outpost Integrations"),
|
||||||
|
title: msg("Outpost Integrations"),
|
||||||
|
section: msg("System"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => navigate("/admin/settings"),
|
||||||
|
id: msg("Settings"),
|
||||||
|
title: msg("Settings"),
|
||||||
|
section: msg("System"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: () => window.location.assign("/if/user/"),
|
||||||
|
id: msg("User interface"),
|
||||||
|
title: msg("Go to my User page"),
|
||||||
|
},
|
||||||
|
];
|
@ -1,5 +1,6 @@
|
|||||||
import "#admin/AdminInterface/AboutModal";
|
import "#admin/AdminInterface/AboutModal";
|
||||||
import type { AboutModal } from "#admin/AdminInterface/AboutModal";
|
import type { AboutModal } from "#admin/AdminInterface/AboutModal";
|
||||||
|
import { adminCommands } from "#admin/AdminInterface/AdminCommands";
|
||||||
import { ROUTES } from "#admin/Routes";
|
import { ROUTES } from "#admin/Routes";
|
||||||
import { EVENT_API_DRAWER_TOGGLE, EVENT_NOTIFICATION_DRAWER_TOGGLE } from "#common/constants";
|
import { EVENT_API_DRAWER_TOGGLE, EVENT_NOTIFICATION_DRAWER_TOGGLE } from "#common/constants";
|
||||||
import { configureSentry } from "#common/sentry/index";
|
import { configureSentry } from "#common/sentry/index";
|
||||||
@ -21,6 +22,7 @@ import { getURLParam, updateURLParams } from "#elements/router/RouteMatch";
|
|||||||
import "#elements/router/RouterOutlet";
|
import "#elements/router/RouterOutlet";
|
||||||
import "#elements/sidebar/Sidebar";
|
import "#elements/sidebar/Sidebar";
|
||||||
import "#elements/sidebar/SidebarItem";
|
import "#elements/sidebar/SidebarItem";
|
||||||
|
import "ninja-keys";
|
||||||
|
|
||||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||||
import { customElement, eventOptions, property, query } from "lit/decorators.js";
|
import { customElement, eventOptions, property, query } from "lit/decorators.js";
|
||||||
@ -119,6 +121,10 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
|
|||||||
.pf-c-drawer__panel {
|
.pf-c-drawer__panel {
|
||||||
z-index: var(--pf-global--ZIndex--xl);
|
z-index: var(--pf-global--ZIndex--xl);
|
||||||
}
|
}
|
||||||
|
ninja-keys {
|
||||||
|
--ninja-z-index: 99999;
|
||||||
|
--ninja-accent-color: var(--ak-accent);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -190,6 +196,11 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
|
|||||||
};
|
};
|
||||||
|
|
||||||
return html` <ak-locale-context>
|
return html` <ak-locale-context>
|
||||||
|
<ninja-keys
|
||||||
|
.data=${adminCommands}
|
||||||
|
noAutoLoadMdicons
|
||||||
|
class="${this.activeTheme === UiThemeEnum.Dark ? "dark" : ""}"
|
||||||
|
></ninja-keys>
|
||||||
<div class="pf-c-page">
|
<div class="pf-c-page">
|
||||||
<ak-page-navbar ?open=${this.sidebarOpen} @sidebar-toggle=${this.sidebarListener}>
|
<ak-page-navbar ?open=${this.sidebarOpen} @sidebar-toggle=${this.sidebarListener}>
|
||||||
<ak-version-banner></ak-version-banner>
|
<ak-version-banner></ak-version-banner>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import "@goauthentik/components/ak-private-textarea-input.js";
|
import "@goauthentik/components/ak-secret-textarea-input.js";
|
||||||
import "@goauthentik/elements/CodeMirror";
|
import "@goauthentik/elements/CodeMirror";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||||
@ -46,7 +46,7 @@ export class CertificateKeyPairForm extends ModelForm<CertificateKeyPair, string
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-private-textarea-input
|
<ak-secret-textarea-input
|
||||||
label=${msg("Certificate")}
|
label=${msg("Certificate")}
|
||||||
name="certificateData"
|
name="certificateData"
|
||||||
input-hint="code"
|
input-hint="code"
|
||||||
@ -54,8 +54,8 @@ export class CertificateKeyPairForm extends ModelForm<CertificateKeyPair, string
|
|||||||
required
|
required
|
||||||
?revealed=${this.instance === undefined}
|
?revealed=${this.instance === undefined}
|
||||||
help=${msg("PEM-encoded Certificate data.")}
|
help=${msg("PEM-encoded Certificate data.")}
|
||||||
></ak-private-textarea-input>
|
></ak-secret-textarea-input>
|
||||||
<ak-private-textarea-input
|
<ak-secret-textarea-input
|
||||||
label=${msg("Private Key")}
|
label=${msg("Private Key")}
|
||||||
name="keyData"
|
name="keyData"
|
||||||
input-hint="code"
|
input-hint="code"
|
||||||
@ -63,7 +63,7 @@ export class CertificateKeyPairForm extends ModelForm<CertificateKeyPair, string
|
|||||||
help=${msg(
|
help=${msg(
|
||||||
"Optional Private Key. If this is set, you can use this keypair for encryption.",
|
"Optional Private Key. If this is set, you can use this keypair for encryption.",
|
||||||
)}
|
)}
|
||||||
></ak-private-textarea-input>`;
|
></ak-secret-textarea-input>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { EVENT_REFRESH_ENTERPRISE } from "@goauthentik/common/constants";
|
import { EVENT_REFRESH_ENTERPRISE } from "@goauthentik/common/constants";
|
||||||
import "@goauthentik/components/ak-private-textarea-input.js";
|
import "@goauthentik/components/ak-secret-textarea-input.js";
|
||||||
import "@goauthentik/elements/CodeMirror";
|
import "@goauthentik/elements/CodeMirror";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||||
@ -62,13 +62,13 @@ export class EnterpriseLicenseForm extends ModelForm<License, string> {
|
|||||||
value="${ifDefined(this.installID)}"
|
value="${ifDefined(this.installID)}"
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-private-textarea-input
|
<ak-secret-textarea-input
|
||||||
name="key"
|
name="key"
|
||||||
?revealed=${this.instance === undefined}
|
?revealed=${this.instance === undefined}
|
||||||
label=${msg("License key")}
|
label=${msg("License key")}
|
||||||
input-hint="code"
|
input-hint="code"
|
||||||
>
|
>
|
||||||
</ak-private-textarea-input>`;
|
</ak-secret-textarea-input>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ import {
|
|||||||
UserMatchingModeToLabel,
|
UserMatchingModeToLabel,
|
||||||
} from "@goauthentik/admin/sources/oauth/utils";
|
} from "@goauthentik/admin/sources/oauth/utils";
|
||||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||||
import "@goauthentik/components/ak-private-text-input.js";
|
import "@goauthentik/components/ak-secret-text-input.js";
|
||||||
import "@goauthentik/components/ak-private-textarea-input.js";
|
import "@goauthentik/components/ak-secret-textarea-input.js";
|
||||||
import "@goauthentik/components/ak-switch-input";
|
import "@goauthentik/components/ak-switch-input";
|
||||||
import "@goauthentik/components/ak-text-input";
|
import "@goauthentik/components/ak-text-input";
|
||||||
import "@goauthentik/components/ak-textarea-input";
|
import "@goauthentik/components/ak-textarea-input";
|
||||||
@ -248,22 +248,22 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
|
|||||||
value=${ifDefined(this.instance?.syncPrincipal)}
|
value=${ifDefined(this.instance?.syncPrincipal)}
|
||||||
help=${msg("Principal used to authenticate to the KDC for syncing.")}
|
help=${msg("Principal used to authenticate to the KDC for syncing.")}
|
||||||
></ak-text-input>
|
></ak-text-input>
|
||||||
<ak-private-text-input
|
<ak-secret-text-input
|
||||||
name="syncPassword"
|
name="syncPassword"
|
||||||
label=${msg("Sync password")}
|
label=${msg("Sync password")}
|
||||||
?revealed=${this.instance === undefined}
|
?revealed=${this.instance === undefined}
|
||||||
help=${msg(
|
help=${msg(
|
||||||
"Password used to authenticate to the KDC for syncing. Optional if Sync keytab or Sync credentials cache is provided.",
|
"Password used to authenticate to the KDC for syncing. Optional if Sync keytab or Sync credentials cache is provided.",
|
||||||
)}
|
)}
|
||||||
></ak-private-text-input>
|
></ak-secret-text-input>
|
||||||
<ak-private-textarea-input
|
<ak-secret-textarea-input
|
||||||
name="syncKeytab"
|
name="syncKeytab"
|
||||||
label=${msg("Sync keytab")}
|
label=${msg("Sync keytab")}
|
||||||
?revealed=${this.instance === undefined}
|
?revealed=${this.instance === undefined}
|
||||||
help=${msg(
|
help=${msg(
|
||||||
"Keytab used to authenticate to the KDC for syncing. Optional if Sync password or Sync credentials cache is provided. Must be base64 encoded or in the form TYPE:residual.",
|
"Keytab used to authenticate to the KDC for syncing. Optional if Sync password or Sync credentials cache is provided. Must be base64 encoded or in the form TYPE:residual.",
|
||||||
)}
|
)}
|
||||||
></ak-private-textarea-input>
|
></ak-secret-textarea-input>
|
||||||
<ak-text-input
|
<ak-text-input
|
||||||
name="syncCcache"
|
name="syncCcache"
|
||||||
label=${msg("Sync credentials cache")}
|
label=${msg("Sync credentials cache")}
|
||||||
@ -285,14 +285,14 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
|
|||||||
"Force the use of a specific server name for SPNEGO. Must be in the form HTTP@domain",
|
"Force the use of a specific server name for SPNEGO. Must be in the form HTTP@domain",
|
||||||
)}
|
)}
|
||||||
></ak-text-input>
|
></ak-text-input>
|
||||||
<ak-private-textarea-input
|
<ak-secret-textarea-input
|
||||||
name="spnegoKeytab"
|
name="spnegoKeytab"
|
||||||
label=${msg("SPNEGO keytab")}
|
label=${msg("SPNEGO keytab")}
|
||||||
?revealed=${this.instance === undefined}
|
?revealed=${this.instance === undefined}
|
||||||
help=${msg(
|
help=${msg(
|
||||||
"Keytab used for SPNEGO. Optional if SPNEGO credentials cache is provided. Must be base64 encoded or in the form TYPE:residual.",
|
"Keytab used for SPNEGO. Optional if SPNEGO credentials cache is provided. Must be base64 encoded or in the form TYPE:residual.",
|
||||||
)}
|
)}
|
||||||
></ak-private-textarea-input>
|
></ak-secret-textarea-input>
|
||||||
<ak-text-input
|
<ak-text-input
|
||||||
name="spnegoCcache"
|
name="spnegoCcache"
|
||||||
label=${msg("SPNEGO credentials cache")}
|
label=${msg("SPNEGO credentials cache")}
|
||||||
|
@ -2,7 +2,7 @@ import "@goauthentik/admin/common/ak-crypto-certificate-search";
|
|||||||
import { placeholderHelperText } from "@goauthentik/admin/helperText";
|
import { placeholderHelperText } from "@goauthentik/admin/helperText";
|
||||||
import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm";
|
import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import "@goauthentik/components/ak-private-text-input.js";
|
import "@goauthentik/components/ak-secret-text-input.js";
|
||||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
@ -260,11 +260,11 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
|
|||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-private-text-input
|
<ak-secret-text-input
|
||||||
label=${msg("Bind Password")}
|
label=${msg("Bind Password")}
|
||||||
name="bindPassword"
|
name="bindPassword"
|
||||||
?revealed=${this.instance === undefined}
|
?revealed=${this.instance === undefined}
|
||||||
></ak-private-text-input>
|
></ak-secret-text-input>
|
||||||
<ak-form-element-horizontal label=${msg("Base DN")} required name="baseDn">
|
<ak-form-element-horizontal label=${msg("Base DN")} required name="baseDn">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -8,8 +8,8 @@ import {
|
|||||||
UserMatchingModeToLabel,
|
UserMatchingModeToLabel,
|
||||||
} from "@goauthentik/admin/sources/oauth/utils";
|
} from "@goauthentik/admin/sources/oauth/utils";
|
||||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||||
import "@goauthentik/components/ak-private-textarea-input.js";
|
|
||||||
import "@goauthentik/components/ak-radio-input";
|
import "@goauthentik/components/ak-radio-input";
|
||||||
|
import "@goauthentik/components/ak-secret-textarea-input.js";
|
||||||
import "@goauthentik/elements/CodeMirror";
|
import "@goauthentik/elements/CodeMirror";
|
||||||
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
||||||
@ -441,14 +441,14 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
|
|||||||
/>
|
/>
|
||||||
<p class="pf-c-form__helper-text">${msg("Also known as Client ID.")}</p>
|
<p class="pf-c-form__helper-text">${msg("Also known as Client ID.")}</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-private-textarea-input
|
<ak-secret-textarea-input
|
||||||
label=${msg("Consumer secret")}
|
label=${msg("Consumer secret")}
|
||||||
name="consumerSecret"
|
name="consumerSecret"
|
||||||
input-hint="code"
|
input-hint="code"
|
||||||
help=${msg("Also known as Client Secret.")}
|
help=${msg("Also known as Client Secret.")}
|
||||||
required
|
required
|
||||||
?revealed=${this.instance === undefined}
|
?revealed=${this.instance === undefined}
|
||||||
></ak-private-textarea-input>
|
></ak-secret-textarea-input>
|
||||||
<ak-form-element-horizontal label=${msg("Scopes")} name="additionalScopes">
|
<ak-form-element-horizontal label=${msg("Scopes")} name="additionalScopes">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
|
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import "@goauthentik/components/ak-private-text-input.js";
|
import "@goauthentik/components/ak-secret-text-input.js";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
import "@goauthentik/elements/forms/SearchSelect";
|
import "@goauthentik/elements/forms/SearchSelect";
|
||||||
@ -95,13 +95,13 @@ export class AuthenticatorDuoStageForm extends BaseStageForm<AuthenticatorDuoSta
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-private-text-input
|
<ak-secret-text-input
|
||||||
name="clientSecret"
|
name="clientSecret"
|
||||||
label=${msg("Secret key")}
|
label=${msg("Secret key")}
|
||||||
input-hint="code"
|
input-hint="code"
|
||||||
required
|
required
|
||||||
?revealed=${this.instance === undefined}
|
?revealed=${this.instance === undefined}
|
||||||
></ak-private-text-input>
|
></ak-secret-text-input>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group>
|
||||||
@ -125,12 +125,12 @@ export class AuthenticatorDuoStageForm extends BaseStageForm<AuthenticatorDuoSta
|
|||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-private-text-input
|
<ak-secret-text-input
|
||||||
name="adminSecretKey"
|
name="adminSecretKey"
|
||||||
label=${msg("Secret key")}
|
label=${msg("Secret key")}
|
||||||
input-hint="code"
|
input-hint="code"
|
||||||
?revealed=${this.instance === undefined}
|
?revealed=${this.instance === undefined}
|
||||||
></ak-private-text-input>
|
></ak-secret-text-input>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group expanded>
|
<ak-form-group expanded>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
|
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import "@goauthentik/components/ak-private-text-input.js";
|
import "@goauthentik/components/ak-secret-text-input.js";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
import "@goauthentik/elements/forms/Radio";
|
import "@goauthentik/elements/forms/Radio";
|
||||||
@ -77,11 +77,11 @@ export class AuthenticatorEmailStageForm extends BaseStageForm<AuthenticatorEmai
|
|||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
|
||||||
<ak-private-text-input
|
<ak-secret-text-input
|
||||||
name="password"
|
name="password"
|
||||||
label=${msg("SMTP Password")}
|
label=${msg("SMTP Password")}
|
||||||
?revealed=${this.instance === undefined}
|
?revealed=${this.instance === undefined}
|
||||||
></ak-private-text-input>
|
></ak-secret-text-input>
|
||||||
|
|
||||||
<ak-form-element-horizontal name="useTls">
|
<ak-form-element-horizontal name="useTls">
|
||||||
<label class="pf-c-switch">
|
<label class="pf-c-switch">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
|
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import "@goauthentik/components/ak-number-input";
|
import "@goauthentik/components/ak-number-input";
|
||||||
import "@goauthentik/components/ak-private-text-input.js";
|
import "@goauthentik/components/ak-secret-text-input.js";
|
||||||
import "@goauthentik/components/ak-switch-input";
|
import "@goauthentik/components/ak-switch-input";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
@ -70,7 +70,7 @@ export class CaptchaStageForm extends BaseStageForm<CaptchaStage> {
|
|||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
|
||||||
<ak-private-text-input
|
<ak-secret-text-input
|
||||||
name="privateKey"
|
name="privateKey"
|
||||||
label=${msg("Private Key")}
|
label=${msg("Private Key")}
|
||||||
input-hint="code"
|
input-hint="code"
|
||||||
@ -79,7 +79,7 @@ export class CaptchaStageForm extends BaseStageForm<CaptchaStage> {
|
|||||||
help=${msg(
|
help=${msg(
|
||||||
"Private key, acquired from https://www.google.com/recaptcha/intro/v3.html.",
|
"Private key, acquired from https://www.google.com/recaptcha/intro/v3.html.",
|
||||||
)}
|
)}
|
||||||
></ak-private-text-input>
|
></ak-secret-text-input>
|
||||||
|
|
||||||
<ak-switch-input
|
<ak-switch-input
|
||||||
name="interactive"
|
name="interactive"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
|
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import "@goauthentik/components/ak-private-text-input.js";
|
import "@goauthentik/components/ak-secret-text-input.js";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
import "@goauthentik/elements/utils/TimeDeltaHelp";
|
import "@goauthentik/elements/utils/TimeDeltaHelp";
|
||||||
@ -73,11 +73,11 @@ export class EmailStageForm extends BaseStageForm<EmailStage> {
|
|||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-private-text-input
|
<ak-secret-text-input
|
||||||
label=${msg("SMTP Password")}
|
label=${msg("SMTP Password")}
|
||||||
name="password"
|
name="password"
|
||||||
?revealed=${this.instance === undefined}
|
?revealed=${this.instance === undefined}
|
||||||
></ak-private-text-input>
|
></ak-secret-text-input>
|
||||||
<ak-form-element-horizontal name="useTls">
|
<ak-form-element-horizontal name="useTls">
|
||||||
<label class="pf-c-switch">
|
<label class="pf-c-switch">
|
||||||
<input
|
<input
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement, type AKElementProps } from "@goauthentik/elements/Base";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement.js";
|
import "@goauthentik/elements/forms/HorizontalFormElement.js";
|
||||||
|
|
||||||
import { TemplateResult, html, nothing } from "lit";
|
import { TemplateResult, html, nothing } from "lit";
|
||||||
@ -6,6 +6,19 @@ import { property } from "lit/decorators.js";
|
|||||||
|
|
||||||
type HelpType = TemplateResult | typeof nothing;
|
type HelpType = TemplateResult | typeof nothing;
|
||||||
|
|
||||||
|
export interface HorizontalLightComponentProps<T> extends AKElementProps {
|
||||||
|
name: string;
|
||||||
|
label?: string;
|
||||||
|
required?: boolean;
|
||||||
|
help?: string;
|
||||||
|
bighelp?: TemplateResult | TemplateResult[];
|
||||||
|
hidden?: boolean;
|
||||||
|
invalid?: boolean;
|
||||||
|
errorMessages?: string[];
|
||||||
|
value?: T;
|
||||||
|
inputHint?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class HorizontalLightComponent<T> extends AKElement {
|
export class HorizontalLightComponent<T> extends AKElement {
|
||||||
// Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but
|
// Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but
|
||||||
// we're not actually using that and, for the meantime, we need the form handlers to be able to
|
// we're not actually using that and, for the meantime, we need the form handlers to be able to
|
||||||
@ -18,37 +31,81 @@ export class HorizontalLightComponent<T> extends AKElement {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name attribute for the form element
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
@property({ type: String, reflect: true })
|
@property({ type: String, reflect: true })
|
||||||
name!: string;
|
name!: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The label for the input control
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
@property({ type: String, reflect: true })
|
@property({ type: String, reflect: true })
|
||||||
label = "";
|
label = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
@property({ type: Boolean, reflect: true })
|
@property({ type: Boolean, reflect: true })
|
||||||
required = false;
|
required = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Help text to display below the form element. Optional
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
@property({ type: String, reflect: true })
|
@property({ type: String, reflect: true })
|
||||||
help = "";
|
help = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended help content. Optional. Expects to be a TemplateResult
|
||||||
|
* @property
|
||||||
|
*/
|
||||||
@property({ type: Object })
|
@property({ type: Object })
|
||||||
bighelp?: TemplateResult | TemplateResult[];
|
bighelp?: TemplateResult | TemplateResult[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
@property({ type: Boolean, reflect: true })
|
@property({ type: Boolean, reflect: true })
|
||||||
hidden = false;
|
hidden = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
@property({ type: Boolean, reflect: true })
|
@property({ type: Boolean, reflect: true })
|
||||||
invalid = false;
|
invalid = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property
|
||||||
|
*/
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
errorMessages: string[] = [];
|
errorMessages: string[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @attribute
|
||||||
|
* @property
|
||||||
|
*/
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
value?: T;
|
value?: T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input hint.
|
||||||
|
* - `code`: uses a monospace font and disables spellcheck & autocomplete
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
@property({ type: String, attribute: "input-hint" })
|
@property({ type: String, attribute: "input-hint" })
|
||||||
inputHint = "";
|
inputHint = "";
|
||||||
|
|
||||||
renderControl() {
|
protected renderControl() {
|
||||||
throw new Error("Must be implemented in a subclass");
|
throw new Error("Must be implemented in a subclass");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
159
web/src/components/ak-hidden-text-input.ts
Normal file
159
web/src/components/ak-hidden-text-input.ts
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
import { bound } from "#elements/decorators/bound";
|
||||||
|
|
||||||
|
import { msg } from "@lit/localize";
|
||||||
|
import { css, html } from "lit";
|
||||||
|
import { customElement, property, query } from "lit/decorators.js";
|
||||||
|
import { classMap } from "lit/directives/class-map.js";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
|
import {
|
||||||
|
HorizontalLightComponent,
|
||||||
|
HorizontalLightComponentProps,
|
||||||
|
} from "./HorizontalLightComponent";
|
||||||
|
import "./ak-visibility-toggle.js";
|
||||||
|
import type { VisibilityToggleProps } from "./ak-visibility-toggle.js";
|
||||||
|
|
||||||
|
type BaseProps = HorizontalLightComponentProps<string> &
|
||||||
|
Pick<VisibilityToggleProps, "showMessage" | "hideMessage">;
|
||||||
|
|
||||||
|
export interface AkHiddenTextInputProps extends BaseProps {
|
||||||
|
revealed: boolean;
|
||||||
|
placeholder?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InputLike = HTMLTextAreaElement | HTMLInputElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @element ak-hidden-text-input
|
||||||
|
* @class AkHiddenTextInput
|
||||||
|
*
|
||||||
|
* A text-input field with a visibility control, so you can show/hide sensitive fields.
|
||||||
|
*
|
||||||
|
* ## CSS Parts
|
||||||
|
* @csspart container - The main container div
|
||||||
|
* @csspart input - The input element
|
||||||
|
* @csspart toggle - The visibility toggle button
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@customElement("ak-hidden-text-input")
|
||||||
|
export class AkHiddenTextInput<T extends InputLike = HTMLInputElement>
|
||||||
|
extends HorizontalLightComponent<string>
|
||||||
|
implements AkHiddenTextInputProps
|
||||||
|
{
|
||||||
|
public static get styles() {
|
||||||
|
return [
|
||||||
|
css`
|
||||||
|
main {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
|
@property({ type: String, reflect: true })
|
||||||
|
public value = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean, reflect: true })
|
||||||
|
public revealed = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text for when the input has no set value
|
||||||
|
*
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
|
@property({ type: String })
|
||||||
|
public placeholder?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify kind of help the browser should try to provide
|
||||||
|
*
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
|
@property({ type: String })
|
||||||
|
public autocomplete?: "none" | AutoFill;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
|
@property({ type: String, attribute: "show-message" })
|
||||||
|
public showMessage = msg("Show field content");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
|
@property({ type: String, attribute: "hide-message" })
|
||||||
|
public hideMessage = msg("Hide field content");
|
||||||
|
|
||||||
|
@query("#main > input")
|
||||||
|
protected inputField!: T;
|
||||||
|
|
||||||
|
@bound
|
||||||
|
private handleToggleVisibility() {
|
||||||
|
this.revealed = !this.revealed;
|
||||||
|
|
||||||
|
// Maintain focus on input after toggle
|
||||||
|
this.updateComplete.then(() => {
|
||||||
|
if (this.inputField && document.activeElement === this) {
|
||||||
|
this.inputField.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Because of the peculiarities of how HorizontalLightComponent works, keeping its content
|
||||||
|
// in the LightDom so the inner components actually inherit styling, the normal `css` options
|
||||||
|
// aren't available. Embedding styles is bad styling, and we'll fix it in the next style
|
||||||
|
// refresh.
|
||||||
|
protected renderInputField(setValue: (ev: InputEvent) => void, code: boolean) {
|
||||||
|
return html` <input
|
||||||
|
style="flex: 1 1 auto; min-width: 0;"
|
||||||
|
part="input"
|
||||||
|
type=${this.revealed ? "text" : "password"}
|
||||||
|
@input=${setValue}
|
||||||
|
value=${ifDefined(this.value)}
|
||||||
|
placeholder=${ifDefined(this.placeholder)}
|
||||||
|
class="${classMap({
|
||||||
|
"pf-c-form-control": true,
|
||||||
|
"pf-m-monospace": code,
|
||||||
|
})}"
|
||||||
|
spellcheck=${code ? "false" : "true"}
|
||||||
|
?required=${this.required}
|
||||||
|
/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override renderControl() {
|
||||||
|
const code = this.inputHint === "code";
|
||||||
|
const setValue = (ev: InputEvent) => {
|
||||||
|
this.value = (ev.target as T).value;
|
||||||
|
};
|
||||||
|
return html` <div style="display: flex; gap: 0.25rem">
|
||||||
|
${this.renderInputField(setValue, code)}
|
||||||
|
<!-- -->
|
||||||
|
<ak-visibility-toggle
|
||||||
|
part="toggle"
|
||||||
|
style="flex: 0 0 auto; align-self: flex-start"
|
||||||
|
?open=${this.revealed}
|
||||||
|
show-message=${this.showMessage}
|
||||||
|
hide-message=${this.hideMessage}
|
||||||
|
@click=${() => (this.revealed = !this.revealed)}
|
||||||
|
></ak-visibility-toggle>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ak-hidden-text-input": AkHiddenTextInput;
|
||||||
|
}
|
||||||
|
}
|
128
web/src/components/ak-hidden-textarea-input.ts
Normal file
128
web/src/components/ak-hidden-textarea-input.ts
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import { css, html } from "lit";
|
||||||
|
import { customElement, property, query } from "lit/decorators.js";
|
||||||
|
import { classMap } from "lit/directives/class-map.js";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
|
import { AkHiddenTextInput, type AkHiddenTextInputProps } from "./ak-hidden-text-input.js";
|
||||||
|
|
||||||
|
export interface AkHiddenTextAreaInputProps extends AkHiddenTextInputProps {
|
||||||
|
/**
|
||||||
|
* Number of visible text lines (rows)
|
||||||
|
*/
|
||||||
|
rows?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of visible character width (cols)
|
||||||
|
*/
|
||||||
|
cols?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How the textarea can be resized
|
||||||
|
*/
|
||||||
|
resize?: "none" | "both" | "horizontal" | "vertical";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether text should wrap
|
||||||
|
*/
|
||||||
|
wrap?: "soft" | "hard" | "off";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @element ak-hidden-text-input
|
||||||
|
* @class AkHiddenTextInput
|
||||||
|
*
|
||||||
|
* A text-input field with a visibility control, so you can show/hide sensitive fields.
|
||||||
|
*
|
||||||
|
* ## CSS Parts
|
||||||
|
* @csspart container - The main container div
|
||||||
|
* @csspart input - The input element
|
||||||
|
* @csspart toggle - The visibility toggle button
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@customElement("ak-hidden-textarea-input")
|
||||||
|
export class AkHiddenTextAreaInput
|
||||||
|
extends AkHiddenTextInput<HTMLTextAreaElement>
|
||||||
|
implements AkHiddenTextAreaInputProps
|
||||||
|
{
|
||||||
|
/* These are mostly just forwarded to the textarea component. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
|
@property({ type: Number })
|
||||||
|
rows?: number = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
|
@property({ type: Number })
|
||||||
|
cols?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*
|
||||||
|
* You want `resize=true` so that the resize value is visible in the component tag, activating
|
||||||
|
* the CSS associated with these values.
|
||||||
|
*/
|
||||||
|
@property({ type: String, reflect: true })
|
||||||
|
resize?: "none" | "both" | "horizontal" | "vertical" = "vertical";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
|
@property({ type: String })
|
||||||
|
wrap?: "soft" | "hard" | "off" = "soft";
|
||||||
|
|
||||||
|
@query("#main > textarea")
|
||||||
|
protected inputField!: HTMLTextAreaElement;
|
||||||
|
|
||||||
|
get displayValue() {
|
||||||
|
const value = this.value ?? "";
|
||||||
|
if (this.revealed) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
.split("\n")
|
||||||
|
.reduce((acc: string[], line: string) => [...acc, "*".repeat(line.length)], [])
|
||||||
|
.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Because of the peculiarities of how HorizontalLightComponent works, keeping its content
|
||||||
|
// in the LightDom so the inner components actually inherit styling, the normal `css` options
|
||||||
|
// aren't available. Embedding styles is bad styling, and we'll fix it in the next style
|
||||||
|
// refresh.
|
||||||
|
protected override renderInputField(setValue: (ev: InputEvent) => void, code: boolean) {
|
||||||
|
const wrap = this.revealed ? this.wrap : "soft";
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<textarea
|
||||||
|
style="flex: 1 1 auto; min-width: 0;"
|
||||||
|
part="textarea"
|
||||||
|
@input=${setValue}
|
||||||
|
placeholder=${ifDefined(this.placeholder)}
|
||||||
|
rows=${ifDefined(this.rows)}
|
||||||
|
cols=${ifDefined(this.cols)}
|
||||||
|
wrap=${ifDefined(wrap)}
|
||||||
|
class=${classMap({
|
||||||
|
"pf-c-form-control": true,
|
||||||
|
"pf-m-monospace": code,
|
||||||
|
})}
|
||||||
|
spellcheck=${code ? "false" : "true"}
|
||||||
|
?required=${this.required}
|
||||||
|
>
|
||||||
|
${this.displayValue}</textarea
|
||||||
|
>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ak-hidden-textarea-input": AkHiddenTextAreaInput;
|
||||||
|
}
|
||||||
|
}
|
@ -8,8 +8,8 @@ import { ifDefined } from "lit/directives/if-defined.js";
|
|||||||
|
|
||||||
import { HorizontalLightComponent } from "./HorizontalLightComponent";
|
import { HorizontalLightComponent } from "./HorizontalLightComponent";
|
||||||
|
|
||||||
@customElement("ak-private-text-input")
|
@customElement("ak-secret-text-input")
|
||||||
export class AkPrivateTextInput extends HorizontalLightComponent<string> {
|
export class AkSecretTextInput extends HorizontalLightComponent<string> {
|
||||||
@property({ type: String, reflect: true })
|
@property({ type: String, reflect: true })
|
||||||
public value = "";
|
public value = "";
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ export class AkPrivateTextInput extends HorizontalLightComponent<string> {
|
|||||||
this.revealed = true;
|
this.revealed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#renderPrivateInput() {
|
#renderSecretInput() {
|
||||||
return html`<div class="pf-c-form__horizontal-group" @click=${() => this.#onReveal()}>
|
return html`<div class="pf-c-form__horizontal-group" @click=${() => this.#onReveal()}>
|
||||||
<input
|
<input
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
@ -60,14 +60,14 @@ export class AkPrivateTextInput extends HorizontalLightComponent<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override renderControl() {
|
public override renderControl() {
|
||||||
return this.revealed ? this.renderVisibleInput() : this.#renderPrivateInput();
|
return this.revealed ? this.renderVisibleInput() : this.#renderSecretInput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AkPrivateTextInput;
|
export default AkSecretTextInput;
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ak-private-text-input": AkPrivateTextInput;
|
"ak-secret-text-input": AkSecretTextInput;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,10 +5,10 @@ import { customElement, property } from "lit/decorators.js";
|
|||||||
import { classMap } from "lit/directives/class-map.js";
|
import { classMap } from "lit/directives/class-map.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
import { AkPrivateTextInput } from "./ak-private-text-input.js";
|
import { AkSecretTextInput } from "./ak-secret-text-input.js";
|
||||||
|
|
||||||
@customElement("ak-private-textarea-input")
|
@customElement("ak-secret-textarea-input")
|
||||||
export class AkPrivateTextAreaInput extends AkPrivateTextInput {
|
export class AkSecretTextAreaInput extends AkSecretTextInput {
|
||||||
protected override renderVisibleInput() {
|
protected override renderVisibleInput() {
|
||||||
const code = this.inputHint === "code";
|
const code = this.inputHint === "code";
|
||||||
const setValue = (ev: InputEvent) => {
|
const setValue = (ev: InputEvent) => {
|
||||||
@ -34,10 +34,10 @@ export class AkPrivateTextAreaInput extends AkPrivateTextInput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AkPrivateTextAreaInput;
|
export default AkSecretTextAreaInput;
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ak-private-textarea-input": AkPrivateTextAreaInput;
|
"ak-secret-textarea-input": AkSecretTextAreaInput;
|
||||||
}
|
}
|
||||||
}
|
}
|
89
web/src/components/ak-visibility-toggle.ts
Normal file
89
web/src/components/ak-visibility-toggle.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { AKElement } from "@goauthentik/elements/Base.js";
|
||||||
|
|
||||||
|
import { msg } from "@lit/localize";
|
||||||
|
import { html } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
|
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||||
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||||
|
|
||||||
|
export interface VisibilityToggleProps {
|
||||||
|
open: boolean;
|
||||||
|
disabled: boolean;
|
||||||
|
showMessage: string;
|
||||||
|
hideMessage: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @component ak-visibility-toggle
|
||||||
|
* @class VisibilityToggle
|
||||||
|
*
|
||||||
|
* A straightforward two-state iconic button we use in a few places as way of telling users to hide
|
||||||
|
* or show something secret, such as a password or private key. Expects the client to manage its
|
||||||
|
* state.
|
||||||
|
*
|
||||||
|
* @events
|
||||||
|
* - click: when the toggle is clicked.
|
||||||
|
*/
|
||||||
|
@customElement("ak-visibility-toggle")
|
||||||
|
export class VisibilityToggle extends AKElement implements VisibilityToggleProps {
|
||||||
|
static get styles() {
|
||||||
|
return [PFBase, PFButton];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean, reflect: true })
|
||||||
|
open = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean, reflect: true })
|
||||||
|
disabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
|
@property({ type: String, attribute: "show-message" })
|
||||||
|
showMessage = msg("Show field content");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property
|
||||||
|
* @attribute
|
||||||
|
*/
|
||||||
|
@property({ type: String, attribute: "hide-message" })
|
||||||
|
hideMessage = msg("Hide field content");
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const [label, icon] = this.open
|
||||||
|
? [this.hideMessage, "fa-eye"]
|
||||||
|
: [this.showMessage, "fa-eye-slash"];
|
||||||
|
|
||||||
|
const onClick = (ev: PointerEvent) => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.dispatchEvent(new PointerEvent(ev.type, ev));
|
||||||
|
};
|
||||||
|
|
||||||
|
return html`<button
|
||||||
|
aria-label=${label}
|
||||||
|
title=${label}
|
||||||
|
@click=${onClick}
|
||||||
|
?disabled=${this.disabled}
|
||||||
|
class="pf-c-button pf-m-control"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<i class="fas ${icon}" aria-hidden="true"></i>
|
||||||
|
</button>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ak-visibility-toggle": VisibilityToggle;
|
||||||
|
}
|
||||||
|
}
|
93
web/src/components/stories/ak-hidden-text-input.stories.ts
Normal file
93
web/src/components/stories/ak-hidden-text-input.stories.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/web-components";
|
||||||
|
|
||||||
|
import { html, nothing } from "lit";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
|
import "../ak-hidden-text-input";
|
||||||
|
import { type AkHiddenTextInput, type AkHiddenTextInputProps } from "../ak-hidden-text-input.js";
|
||||||
|
|
||||||
|
const metadata: Meta<AkHiddenTextInputProps> = {
|
||||||
|
title: "Components / <ak-hidden-text-input>",
|
||||||
|
component: "ak-hidden-text-input",
|
||||||
|
tags: ["autodocs"],
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component: `
|
||||||
|
# Hidden Text Input Component
|
||||||
|
|
||||||
|
A text-input field with a visibility control, so you can show/hide sensitive fields.
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layout: "padded",
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
label: {
|
||||||
|
control: "text",
|
||||||
|
description: "Label text for the input field",
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
control: "text",
|
||||||
|
description: "Current value of the input",
|
||||||
|
},
|
||||||
|
revealed: {
|
||||||
|
control: "boolean",
|
||||||
|
description: "Whether the text is currently visible",
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
control: "text",
|
||||||
|
description: "Placeholder text for the input",
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
control: "boolean",
|
||||||
|
description: "Whether the input is required",
|
||||||
|
},
|
||||||
|
inputHint: {
|
||||||
|
control: "select",
|
||||||
|
options: ["text", "code"],
|
||||||
|
description: "Input type hint for styling and behavior",
|
||||||
|
},
|
||||||
|
showMessage: {
|
||||||
|
control: "text",
|
||||||
|
description: "Custom message for show action",
|
||||||
|
},
|
||||||
|
hideMessage: {
|
||||||
|
control: "text",
|
||||||
|
description: "Custom message for hide action",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default metadata;
|
||||||
|
|
||||||
|
type Story = StoryObj<AkHiddenTextInput>;
|
||||||
|
|
||||||
|
const Template: Story = {
|
||||||
|
args: {
|
||||||
|
label: "Hidden Text Input",
|
||||||
|
value: "",
|
||||||
|
revealed: false,
|
||||||
|
},
|
||||||
|
render: (args) => html`
|
||||||
|
<ak-hidden-text-input
|
||||||
|
label=${ifDefined(args.label)}
|
||||||
|
value=${ifDefined(args.value)}
|
||||||
|
?revealed=${args.revealed}
|
||||||
|
placeholder=${ifDefined(args.placeholder)}
|
||||||
|
?required=${args.required}
|
||||||
|
input-hint=${ifDefined(args.inputHint)}
|
||||||
|
show-message=${ifDefined(args.showMessage)}
|
||||||
|
hide-message=${ifDefined(args.hideMessage)}
|
||||||
|
></ak-hidden-text-input>
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Password: Story = {
|
||||||
|
...Template,
|
||||||
|
args: {
|
||||||
|
label: "Password",
|
||||||
|
placeholder: "Enter your password",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
};
|
140
web/src/components/stories/ak-hidden-textarea-input.stories.ts
Normal file
140
web/src/components/stories/ak-hidden-textarea-input.stories.ts
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/web-components";
|
||||||
|
|
||||||
|
import { html, nothing } from "lit";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
|
import "../ak-hidden-textarea-input";
|
||||||
|
import {
|
||||||
|
type AkHiddenTextAreaInput,
|
||||||
|
type AkHiddenTextAreaInputProps,
|
||||||
|
} from "../ak-hidden-textarea-input.js";
|
||||||
|
|
||||||
|
const metadata: Meta<AkHiddenTextAreaInputProps> = {
|
||||||
|
title: "Components / <ak-hidden-textarea-input>",
|
||||||
|
component: "ak-hidden-textarea-input",
|
||||||
|
tags: ["autodocs"],
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component: `
|
||||||
|
# Hidden Textarea Input Component
|
||||||
|
|
||||||
|
A textarea input field with a visibility control, so you can show/hide sensitive fields.
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layout: "padded",
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
label: {
|
||||||
|
control: "text",
|
||||||
|
description: "Label text for the input field",
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
control: "text",
|
||||||
|
description: "Current value of the input",
|
||||||
|
},
|
||||||
|
revealed: {
|
||||||
|
control: "boolean",
|
||||||
|
description: "Whether the text is currently visible",
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
control: "text",
|
||||||
|
description: "Placeholder text for the input",
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
control: "boolean",
|
||||||
|
description: "Whether the input is required",
|
||||||
|
},
|
||||||
|
inputHint: {
|
||||||
|
control: "select",
|
||||||
|
options: ["text", "code"],
|
||||||
|
description: "Input type hint for styling and behavior",
|
||||||
|
},
|
||||||
|
showMessage: {
|
||||||
|
control: "text",
|
||||||
|
description: "Custom message for show action",
|
||||||
|
},
|
||||||
|
hideMessage: {
|
||||||
|
control: "text",
|
||||||
|
description: "Custom message for hide action",
|
||||||
|
},
|
||||||
|
rows: {
|
||||||
|
control: { type: "number", min: 1, max: 50 },
|
||||||
|
description: "Number of visible text lines",
|
||||||
|
},
|
||||||
|
cols: {
|
||||||
|
control: { type: "number", min: 10, max: 200 },
|
||||||
|
description: "Number of visible character width",
|
||||||
|
},
|
||||||
|
resize: {
|
||||||
|
control: "select",
|
||||||
|
options: ["none", "both", "horizontal", "vertical"],
|
||||||
|
description: "How the textarea can be resized",
|
||||||
|
},
|
||||||
|
wrap: {
|
||||||
|
control: "select",
|
||||||
|
options: ["soft", "hard", "off"],
|
||||||
|
description: "Text wrapping behavior",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default metadata;
|
||||||
|
|
||||||
|
type Story = StoryObj<AkHiddenTextAreaInput>;
|
||||||
|
|
||||||
|
const Template: Story = {
|
||||||
|
args: {
|
||||||
|
label: "Hidden Textarea Input",
|
||||||
|
value: "",
|
||||||
|
revealed: false,
|
||||||
|
rows: 4,
|
||||||
|
},
|
||||||
|
render: (args) => html`
|
||||||
|
<ak-hidden-textarea-input
|
||||||
|
label=${ifDefined(args.label)}
|
||||||
|
value=${ifDefined(args.value)}
|
||||||
|
?revealed=${args.revealed}
|
||||||
|
placeholder=${ifDefined(args.placeholder)}
|
||||||
|
rows=${ifDefined(args.rows)}
|
||||||
|
cols=${ifDefined(args.cols)}
|
||||||
|
resize=${ifDefined(args.resize)}
|
||||||
|
wrap=${ifDefined(args.wrap)}
|
||||||
|
?required=${args.required}
|
||||||
|
input-hint=${ifDefined(args.inputHint)}
|
||||||
|
show-message=${ifDefined(args.showMessage)}
|
||||||
|
hide-message=${ifDefined(args.hideMessage)}
|
||||||
|
></ak-hidden-textarea-input>
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SslCertificate: Story = {
|
||||||
|
...Template,
|
||||||
|
args: {
|
||||||
|
label: "SSL Certificate",
|
||||||
|
value: `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDXTCCAkWgAwIBAgIJAKoK/heBjcOuMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
|
||||||
|
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||||
|
aWRnaXRzIFB0eSBMdGQwHhcNMTcwNTEwMTk0MDA2WhcNMTgwNTEwMTk0MDA2WjBF
|
||||||
|
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
|
||||||
|
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTE3MDUxMDE5NDAwNloXDTE4MDUxMDE5
|
||||||
|
NDAwNlowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNV
|
||||||
|
BAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQAD
|
||||||
|
ggEPADCCAQoCggEBALdUlNS31SzxwoFShahGfjHj6GgpcVbzL1Siq0Pqnf82T6M2
|
||||||
|
EDuneMLzAgMBAAECggEBAJkPFn6jeMHyiq0Pqnf82T6M2EDuneMLzAgMBAAECggE
|
||||||
|
BAJkPFn6jeMHyiq0Pqnf82T6M2EDuneMLzAgMBAAECggEBAJkPFn6jeMHyiq0Pqn
|
||||||
|
f82T6M2EDuneMLzAgMBAAECggEBAJkPFn6jeMHyiq0Pqnf82T6M2EDuneMLzAgM
|
||||||
|
BAAECggEBAJkPFn6jeMHyiq0Pqnf82T6M2EDuneMLzAgMBAAECggEBAJkPFn6jeM
|
||||||
|
Hyiq0Pqnf82T6M2EDuneMLzAgMBAAECggEBAJkPFn6jeMHyiq0Pqnf82T6M2EDu
|
||||||
|
neMLzAgMBAAECggEBAJkPFn6jeMHyiq0Pqnf82T6M2EDuneMLzAgMBAAECggEBAJ
|
||||||
|
kPFn6jeMHyiq0Pqnf82T6M2EDuneMLzAgMBAAE=
|
||||||
|
-----END CERTIFICATE-----`,
|
||||||
|
inputHint: "code",
|
||||||
|
rows: 15,
|
||||||
|
resize: "vertical",
|
||||||
|
showMessage: "Show certificate content",
|
||||||
|
hideMessage: "Hide certificate content",
|
||||||
|
autocomplete: "off",
|
||||||
|
},
|
||||||
|
};
|
121
web/src/components/stories/ak-visibility-toggle.stories.ts
Normal file
121
web/src/components/stories/ak-visibility-toggle.stories.ts
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/web-components";
|
||||||
|
|
||||||
|
import { html, nothing } from "lit";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
|
import "../ak-visibility-toggle";
|
||||||
|
import { type VisibilityToggle, type VisibilityToggleProps } from "../ak-visibility-toggle.js";
|
||||||
|
|
||||||
|
const metadata: Meta<VisibilityToggleProps> = {
|
||||||
|
title: "Elements/<ak-visibility-toggle>",
|
||||||
|
component: "ak-visibility-toggle",
|
||||||
|
tags: ["autodocs"],
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component: `
|
||||||
|
# Visibility Toggle Component
|
||||||
|
|
||||||
|
A straightforward two-state iconic button for toggling the visibility of sensitive content such as passwords, private keys, or other secret information.
|
||||||
|
|
||||||
|
- Use for sensitive content that users might want to temporarily reveal
|
||||||
|
- There are default hide/show messages for screen readers, but they can be overridden
|
||||||
|
- Clients always handle the state
|
||||||
|
- The \`open\` state is false by default; we assume you want sensitive content hidden at start
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layout: "padded",
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
open: {
|
||||||
|
control: "boolean",
|
||||||
|
description: "Whether the toggle is in the 'show' state (true) or 'hide' state (false)",
|
||||||
|
},
|
||||||
|
showMessage: {
|
||||||
|
control: "text",
|
||||||
|
description:
|
||||||
|
'Message for screen readers when in hide state (default: "Show field content")',
|
||||||
|
},
|
||||||
|
hideMessage: {
|
||||||
|
control: "text",
|
||||||
|
description:
|
||||||
|
'Message for screen readers when in show state (default: "Hide field content")',
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
control: "boolean",
|
||||||
|
description: "Whether the button should be disabled (for demo purposes)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default metadata;
|
||||||
|
|
||||||
|
type Story = StoryObj<VisibilityToggle>;
|
||||||
|
|
||||||
|
const Template: Story = {
|
||||||
|
args: {
|
||||||
|
open: false,
|
||||||
|
showMessage: "Show field content",
|
||||||
|
hideMessage: "Hide field content",
|
||||||
|
},
|
||||||
|
render: (args) => html`
|
||||||
|
<ak-visibility-toggle
|
||||||
|
?open=${args.open}
|
||||||
|
show-message=${ifDefined(args.showMessage)}
|
||||||
|
hide-message=${ifDefined(args.hideMessage)}
|
||||||
|
@click=${(e: Event) => {
|
||||||
|
const target = e.target as VisibilityToggle;
|
||||||
|
target.open = !target.open;
|
||||||
|
// In a real application, you would also toggle the visibility
|
||||||
|
// of the associated content here
|
||||||
|
}}
|
||||||
|
></ak-visibility-toggle>
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Password field integration example
|
||||||
|
export const PasswordFieldExample: Story = {
|
||||||
|
args: {
|
||||||
|
showMessage: "Reveal password",
|
||||||
|
hideMessage: "Conceal password",
|
||||||
|
},
|
||||||
|
render: () => {
|
||||||
|
let isVisible = false;
|
||||||
|
|
||||||
|
const toggleVisibility = (e: Event) => {
|
||||||
|
isVisible = !isVisible;
|
||||||
|
const toggle = e.target as VisibilityToggle;
|
||||||
|
const passwordField = document.querySelector("#demo-password") as HTMLInputElement;
|
||||||
|
|
||||||
|
toggle.open = isVisible;
|
||||||
|
if (passwordField) {
|
||||||
|
passwordField.type = isVisible ? "text" : "password";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 1rem; max-width: 300px;">
|
||||||
|
<label for="demo-password" style="font-weight: bold;">Password:</label>
|
||||||
|
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||||
|
<input
|
||||||
|
id="demo-password"
|
||||||
|
type="password"
|
||||||
|
value="supersecretpassword123"
|
||||||
|
style="flex: 1; padding: 0.5rem; border: 1px solid #ccc; border-radius: 4px;"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
<ak-visibility-toggle
|
||||||
|
?open=${isVisible}
|
||||||
|
show-message="Show password"
|
||||||
|
hide-message="Hide password"
|
||||||
|
@click=${toggleVisibility}
|
||||||
|
></ak-visibility-toggle>
|
||||||
|
</div>
|
||||||
|
<p style="font-size: 0.875rem; color: #666;">
|
||||||
|
Click the eye icon to toggle password visibility
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
};
|
@ -16,8 +16,12 @@ import { property } from "lit/decorators.js";
|
|||||||
|
|
||||||
import { UiThemeEnum } from "@goauthentik/api";
|
import { UiThemeEnum } from "@goauthentik/api";
|
||||||
|
|
||||||
|
export interface AKElementProps {
|
||||||
|
activeTheme: ResolvedUITheme;
|
||||||
|
}
|
||||||
|
|
||||||
@localized()
|
@localized()
|
||||||
export class AKElement extends LitElement {
|
export class AKElement extends LitElement implements AKElementProps {
|
||||||
//#region Static Properties
|
//#region Static Properties
|
||||||
|
|
||||||
public static styles?: Array<CSSResult | CSSModule>;
|
public static styles?: Array<CSSResult | CSSModule>;
|
||||||
|
Reference in New Issue
Block a user