fix(platform): isolate authentik admin styling
This commit is contained in:
@@ -36,10 +36,16 @@ Later phases should add reproducible configuration for:
|
||||
|
||||
## NODE.DC branded login
|
||||
|
||||
`custom-templates/branding/nodedc-login.css` is mounted into Authentik at `/templates/branding/nodedc-login.css` and applied by `bootstrap-dev.py` through the native Authentik Brand `branding_custom_css` field.
|
||||
`custom-templates/branding/nodedc-login.css` is mounted into Authentik at `/templates/branding/nodedc-login.css`, but it must not be stored in the native Authentik Brand `branding_custom_css` field.
|
||||
|
||||
`custom-templates/base/header_js.html` keeps Authentik's native config script and adds a minimal NODE.DC field enhancement for the email clear control and password placeholder only.
|
||||
Authentik Brand custom CSS is global. Authentik also passes it into the Admin/User web component runtime, so broad login selectors can break list pages, toolbars, tables, and other Admin interface content. Keep `Brand.branding_custom_css` empty in local bootstrap and Synology deploy scripts.
|
||||
|
||||
`custom-templates/base/header_js.html` keeps Authentik's native config script and applies the NODE.DC favicon globally. It renders NODE.DC auth-screen CSS/DOM helpers only on `/if/flow/`, `/flows/`, and `/login/` paths, and excludes technical admin hosts such as `auth-admin.nas.nodedc` / `auth-admin.local.nodedc`. On public auth paths only, it also passes `nodedc-login.css` into `window.authentik.brand` so Authentik's Lit web components can style their shadow DOM. Admin and User paths receive an empty runtime brand CSS value.
|
||||
|
||||
`custom-templates/base/skeleton.html` leaves `<style data-id="brand-css">` empty outside flow/login paths as a second guard. `custom-templates/if/admin.html` tracks Authentik's upstream Admin shell while still including `base/header_js.html`, so module execution sees the sanitized brand config without rewriting Admin markup.
|
||||
|
||||
OAuth2 providers are assigned Authentik's `default-invalidation-flow` so application logout completes the IdP session and returns through the NODE.DC launcher route instead of showing the default Authentik application logout screen.
|
||||
|
||||
The bootstrap admin must belong to the `authentik Admins` group, and that group must have `is_superuser=True`. Without this, `/if/admin/` can render the admin shell while model list pages appear empty because their API calls are denied.
|
||||
|
||||
This is intentionally not an HTML-rewriting proxy. Passwords, MFA, recovery, sessions and audit remain inside Authentik; Launcher and Task Manager stay OIDC clients.
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from os import environ
|
||||
from pathlib import Path
|
||||
|
||||
from django.db import transaction
|
||||
|
||||
@@ -21,8 +20,6 @@ from authentik.providers.oauth2.models import (
|
||||
from authentik.stages.identification.models import IdentificationStage
|
||||
from authentik.stages.password.models import PasswordStage
|
||||
|
||||
BRANDING_CSS_PATH = Path("/templates/branding/nodedc-login.css")
|
||||
|
||||
GROUP_SPECS = [
|
||||
("nodedc:superadmin", False),
|
||||
("nodedc:launcher:admin", False),
|
||||
@@ -91,7 +88,10 @@ def ensure_groups():
|
||||
|
||||
|
||||
def ensure_user_groups(groups):
|
||||
admin_email = environ.get("NODEDC_BOOTSTRAP_ADMIN_EMAIL", "").strip()
|
||||
admin_email = (
|
||||
environ.get("NODEDC_BOOTSTRAP_ADMIN_EMAIL", "").strip()
|
||||
or environ.get("AUTHENTIK_BOOTSTRAP_EMAIL", "").strip()
|
||||
)
|
||||
if not admin_email:
|
||||
return None
|
||||
|
||||
@@ -105,13 +105,19 @@ def ensure_user_groups(groups):
|
||||
user.email = admin_email
|
||||
user.is_active = True
|
||||
user.type = "internal"
|
||||
if environ.get("NODEDC_BOOTSTRAP_ADMIN_PASSWORD"):
|
||||
user.set_password(environ["NODEDC_BOOTSTRAP_ADMIN_PASSWORD"])
|
||||
admin_password = (
|
||||
environ.get("NODEDC_BOOTSTRAP_ADMIN_PASSWORD")
|
||||
or environ.get("AUTHENTIK_BOOTSTRAP_PASSWORD")
|
||||
)
|
||||
if admin_password:
|
||||
user.set_password(admin_password)
|
||||
user.save()
|
||||
|
||||
authentik_admins = Group.objects.filter(name="authentik Admins").first()
|
||||
if authentik_admins:
|
||||
user.groups.add(authentik_admins)
|
||||
authentik_admins, _ = Group.objects.get_or_create(name="authentik Admins")
|
||||
if not authentik_admins.is_superuser:
|
||||
authentik_admins.is_superuser = True
|
||||
authentik_admins.save(update_fields=["is_superuser"])
|
||||
user.groups.add(authentik_admins)
|
||||
|
||||
for name in groups:
|
||||
user.groups.add(groups[name])
|
||||
@@ -174,12 +180,6 @@ def default_scope_mappings():
|
||||
return mappings
|
||||
|
||||
|
||||
def read_branding_css():
|
||||
if BRANDING_CSS_PATH.exists():
|
||||
return BRANDING_CSS_PATH.read_text(encoding="utf-8")
|
||||
return ""
|
||||
|
||||
|
||||
def ensure_nodedc_brand():
|
||||
auth_domain = environ.get("AUTH_DOMAIN", "auth.local.nodedc").strip() or "auth.local.nodedc"
|
||||
authentication_flow = Flow.objects.get(slug="default-authentication-flow")
|
||||
@@ -214,7 +214,9 @@ def ensure_nodedc_brand():
|
||||
brand.branding_title = "NODE.DC"
|
||||
brand.branding_logo = ""
|
||||
brand.branding_favicon = ""
|
||||
brand.branding_custom_css = read_branding_css()
|
||||
# Login styling is injected by flow templates only. Brand custom CSS is global
|
||||
# and Authentik applies it to Admin/User web components as well.
|
||||
brand.branding_custom_css = ""
|
||||
brand.flow_authentication = authentication_flow
|
||||
brand.flow_invalidation = invalidation_flow
|
||||
brand.attributes = {
|
||||
|
||||
@@ -21,8 +21,38 @@
|
||||
})();
|
||||
</script>
|
||||
|
||||
{% with request_host=request.get_host %}
|
||||
{% if request_host|slice:":11" != "auth-admin." and request_host|slice:":9" != "id-admin." and request_host|slice:":12" != "172.22.0.222" %}
|
||||
{% if request.path|slice:":9" == "/if/flow/" or request.path|slice:":7" == "/flows/" or request.path|slice:":7" == "/login/" %}
|
||||
<script data-id="nodedc-auth-flow-scope">
|
||||
"use strict";
|
||||
|
||||
(function () {
|
||||
const isAuthentikAdminHost =
|
||||
window.location.hostname.startsWith("auth-admin.") ||
|
||||
window.location.hostname.startsWith("id-admin.") ||
|
||||
window.location.hostname === "172.22.0.222";
|
||||
const isAuthExperiencePage =
|
||||
!isAuthentikAdminHost &&
|
||||
(
|
||||
window.location.pathname.includes("/if/flow/") ||
|
||||
window.location.pathname.includes("/flows/") ||
|
||||
window.location.pathname.includes("/login/")
|
||||
);
|
||||
document.documentElement.classList.toggle("nodedc-auth-flow-page", isAuthExperiencePage);
|
||||
|
||||
window.addEventListener("DOMContentLoaded", function () {
|
||||
document.body?.classList.toggle("nodedc-auth-flow-page", isAuthExperiencePage);
|
||||
}, { once: true });
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style data-id="nodedc-auth-login-css">
|
||||
{% include "branding/nodedc-login.css" %}
|
||||
</style>
|
||||
|
||||
<style data-id="nodedc-auth-critical-loader">
|
||||
:root {
|
||||
html.nodedc-auth-flow-page {
|
||||
--ak-global--primary: rgb(195, 255, 102) !important;
|
||||
--ak-global--background: #0e0f10 !important;
|
||||
--ak-global--background-image: none !important;
|
||||
@@ -37,22 +67,22 @@
|
||||
--nodedc-auth-bg: #0e0f10 !important;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
html body .pf-c-login {
|
||||
html.nodedc-auth-flow-page,
|
||||
html.nodedc-auth-flow-page body,
|
||||
html.nodedc-auth-flow-page body .pf-c-login {
|
||||
background: #0e0f10 !important;
|
||||
color-scheme: dark !important;
|
||||
}
|
||||
|
||||
html body ak-flow-executor::part(main),
|
||||
html body .pf-c-login__main,
|
||||
html body .pf-c-empty-state,
|
||||
html body .pf-v5-c-empty-state,
|
||||
html body .pf-v6-c-empty-state,
|
||||
html body .pf-c-card,
|
||||
html body .pf-v5-c-card,
|
||||
html body .pf-v6-c-card,
|
||||
html body [class*="empty-state"] {
|
||||
html.nodedc-auth-flow-page body ak-flow-executor::part(main),
|
||||
html.nodedc-auth-flow-page body .pf-c-login__main,
|
||||
html.nodedc-auth-flow-page body .pf-c-empty-state,
|
||||
html.nodedc-auth-flow-page body .pf-v5-c-empty-state,
|
||||
html.nodedc-auth-flow-page body .pf-v6-c-empty-state,
|
||||
html.nodedc-auth-flow-page body .pf-c-card,
|
||||
html.nodedc-auth-flow-page body .pf-v5-c-card,
|
||||
html.nodedc-auth-flow-page body .pf-v6-c-card,
|
||||
html.nodedc-auth-flow-page body [class*="empty-state"] {
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
border: 0 !important;
|
||||
@@ -60,11 +90,11 @@
|
||||
backdrop-filter: none !important;
|
||||
}
|
||||
|
||||
html body .pf-c-spinner,
|
||||
html body .pf-v5-c-spinner,
|
||||
html body .pf-v6-c-spinner,
|
||||
html body [role="progressbar"],
|
||||
html body ak-spinner {
|
||||
html.nodedc-auth-flow-page body .pf-c-spinner,
|
||||
html.nodedc-auth-flow-page body .pf-v5-c-spinner,
|
||||
html.nodedc-auth-flow-page body .pf-v6-c-spinner,
|
||||
html.nodedc-auth-flow-page body [role="progressbar"],
|
||||
html.nodedc-auth-flow-page body ak-spinner {
|
||||
--pf-c-spinner--Color: rgb(195, 255, 102) !important;
|
||||
--pf-v5-c-spinner--Color: rgb(195, 255, 102) !important;
|
||||
--pf-v6-c-spinner--Color: rgb(195, 255, 102) !important;
|
||||
@@ -74,8 +104,8 @@
|
||||
color: rgb(195, 255, 102) !important;
|
||||
}
|
||||
|
||||
html body #ak-placeholder.ak-c-placeholder,
|
||||
html body .ak-c-placeholder[slot="placeholder"] {
|
||||
html.nodedc-auth-flow-page body #ak-placeholder.ak-c-placeholder,
|
||||
html.nodedc-auth-flow-page body .ak-c-placeholder[slot="placeholder"] {
|
||||
position: fixed !important;
|
||||
top: 50% !important;
|
||||
left: 50% !important;
|
||||
@@ -98,16 +128,16 @@
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
html body #ak-placeholder.ak-c-placeholder .pf-c-spinner,
|
||||
html body .ak-c-placeholder[slot="placeholder"] .pf-c-spinner,
|
||||
html body #ak-placeholder.ak-c-placeholder [role="progressbar"],
|
||||
html body .ak-c-placeholder[slot="placeholder"] [role="progressbar"] {
|
||||
html.nodedc-auth-flow-page body #ak-placeholder.ak-c-placeholder .pf-c-spinner,
|
||||
html.nodedc-auth-flow-page body .ak-c-placeholder[slot="placeholder"] .pf-c-spinner,
|
||||
html.nodedc-auth-flow-page body #ak-placeholder.ak-c-placeholder [role="progressbar"],
|
||||
html.nodedc-auth-flow-page body .ak-c-placeholder[slot="placeholder"] [role="progressbar"] {
|
||||
opacity: 0 !important;
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
html body #ak-placeholder.ak-c-placeholder::before,
|
||||
html body .ak-c-placeholder[slot="placeholder"]::before {
|
||||
html.nodedc-auth-flow-page body #ak-placeholder.ak-c-placeholder::before,
|
||||
html.nodedc-auth-flow-page body .ak-c-placeholder[slot="placeholder"]::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -118,14 +148,14 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
html body.nodedc-auth-loading ak-flow-executor::part(main),
|
||||
html body.nodedc-auth-loading .pf-c-login__main {
|
||||
html.nodedc-auth-flow-page body.nodedc-auth-loading ak-flow-executor::part(main),
|
||||
html.nodedc-auth-flow-page body.nodedc-auth-loading .pf-c-login__main {
|
||||
opacity: 0 !important;
|
||||
visibility: hidden !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
html body.nodedc-auth-loading::before {
|
||||
html.nodedc-auth-flow-page body.nodedc-auth-loading::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
@@ -141,7 +171,7 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
html body.nodedc-auth-card-ready::before {
|
||||
html.nodedc-auth-flow-page body.nodedc-auth-card-ready::before {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@@ -165,14 +195,38 @@
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<script data-id="authentik-config">
|
||||
"use strict";
|
||||
|
||||
const isAuthentikAdminHost =
|
||||
window.location.hostname.startsWith("auth-admin.") ||
|
||||
window.location.hostname.startsWith("id-admin.") ||
|
||||
window.location.hostname === "172.22.0.222";
|
||||
const isAuthExperiencePage =
|
||||
!isAuthentikAdminHost &&
|
||||
(
|
||||
window.location.pathname.includes("/if/flow/") ||
|
||||
window.location.pathname.includes("/flows/") ||
|
||||
window.location.pathname.includes("/login/")
|
||||
);
|
||||
const authentikBrand = JSON.parse('{{ brand_json|escapejs }}' || "{}");
|
||||
if (isAuthExperiencePage) {
|
||||
const nodedcAuthLoginCss = document.querySelector('style[data-id="nodedc-auth-login-css"]')?.textContent || "";
|
||||
authentikBrand.branding_custom_css = nodedcAuthLoginCss;
|
||||
authentikBrand.brandingCustomCss = nodedcAuthLoginCss;
|
||||
} else {
|
||||
authentikBrand.branding_custom_css = "";
|
||||
authentikBrand.brandingCustomCss = "";
|
||||
}
|
||||
|
||||
window.authentik = {
|
||||
locale: "ru",
|
||||
config: JSON.parse('{{ config_json|escapejs }}' || "{}"),
|
||||
brand: JSON.parse('{{ brand_json|escapejs }}' || "{}"),
|
||||
brand: authentikBrand,
|
||||
versionFamily: "{{ version_family }}",
|
||||
versionSubdomain: "{{ version_subdomain }}",
|
||||
build: "{{ build }}",
|
||||
@@ -197,10 +251,31 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
{% with request_host=request.get_host %}
|
||||
{% if request_host|slice:":11" != "auth-admin." and request_host|slice:":9" != "id-admin." and request_host|slice:":12" != "172.22.0.222" %}
|
||||
{% if request.path|slice:":9" == "/if/flow/" or request.path|slice:":7" == "/flows/" or request.path|slice:":7" == "/login/" %}
|
||||
<script data-id="nodedc-auth-field-enhancements">
|
||||
"use strict";
|
||||
|
||||
(function () {
|
||||
const isAuthentikAdminHost =
|
||||
window.location.hostname.startsWith("auth-admin.") ||
|
||||
window.location.hostname.startsWith("id-admin.") ||
|
||||
window.location.hostname === "172.22.0.222";
|
||||
const isAuthExperiencePage =
|
||||
!isAuthentikAdminHost &&
|
||||
(
|
||||
window.location.pathname.includes("/if/flow/") ||
|
||||
window.location.pathname.includes("/flows/") ||
|
||||
window.location.pathname.includes("/login/")
|
||||
);
|
||||
if (!isAuthExperiencePage) return;
|
||||
|
||||
document.documentElement.classList.add("nodedc-auth-flow-page");
|
||||
window.addEventListener("DOMContentLoaded", function () {
|
||||
document.body?.classList.add("nodedc-auth-flow-page");
|
||||
}, { once: true });
|
||||
|
||||
const logoSvg = `
|
||||
<svg id="nodedc-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220.82 54.55" aria-hidden="true">
|
||||
<defs>
|
||||
@@ -254,9 +329,11 @@
|
||||
["Continue", "Продолжить"],
|
||||
["Show password", "Показать пароль"],
|
||||
["Hide password", "Скрыть пароль"],
|
||||
["Permission denied", "Доступ ограничен"],
|
||||
["Permission denied", "Доступ запрещён"],
|
||||
["Not you?", "Сменить пользователя"],
|
||||
["Request has been denied.", "Доступ к модулю ограничен."],
|
||||
["Request has been denied.", "Доступ запрещён."],
|
||||
["The request has been denied.", "Доступ запрещён."],
|
||||
["В произведении запроса было отказано.", "Доступ запрещён."],
|
||||
["Go home", "Вернуться назад"],
|
||||
["Response returned an error code", "Не удалось завершить операцию."],
|
||||
]);
|
||||
@@ -366,7 +443,11 @@
|
||||
denied = [
|
||||
"Permission denied",
|
||||
"Доступ ограничен",
|
||||
"Доступ запрещен",
|
||||
"Доступ запрещён",
|
||||
"Request has been denied.",
|
||||
"The request has been denied.",
|
||||
"В произведении запроса было отказано.",
|
||||
"Доступ к модулю ограничен.",
|
||||
].some((message) => text.includes(message));
|
||||
});
|
||||
@@ -387,8 +468,8 @@
|
||||
}
|
||||
|
||||
function applyPermissionDeniedLayout(root) {
|
||||
const deniedTitleMessages = ["Permission denied", "Доступ ограничен"];
|
||||
const deniedSubtitle = "Доступ к модулю NODE.DC ограничен.";
|
||||
const deniedTitleMessages = ["Permission denied", "Доступ ограничен", "Доступ запрещен", "Доступ запрещён"];
|
||||
const deniedSubtitle = "Доступ запрещён.";
|
||||
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
||||
acceptNode(node) {
|
||||
const text = node.nodeValue?.trim() || "";
|
||||
@@ -426,7 +507,19 @@
|
||||
}
|
||||
|
||||
function hidePermissionDeniedReason(root) {
|
||||
const deniedMessages = ["Request has been denied.", "Доступ к модулю ограничен."];
|
||||
const deniedMessages = [
|
||||
"Request has been denied.",
|
||||
"The request has been denied.",
|
||||
"В произведении запроса было отказано.",
|
||||
"Доступ к модулю ограничен.",
|
||||
"Объяснение:",
|
||||
"Reason:",
|
||||
"Привязка политики",
|
||||
"Policy binding",
|
||||
"returned result",
|
||||
"returned a result",
|
||||
"'None' вернула результат",
|
||||
];
|
||||
|
||||
root.querySelectorAll(".pf-c-alert, .pf-v5-c-alert, .pf-c-empty-state__body, [class*='alert']").forEach((element) => {
|
||||
const text = element.textContent || "";
|
||||
@@ -547,12 +640,35 @@
|
||||
enhancePasswordFields(root);
|
||||
}
|
||||
|
||||
function dispatchSyntheticInput(input) {
|
||||
if (!input) return;
|
||||
|
||||
try {
|
||||
input.dispatchEvent(new InputEvent("input", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
inputType: "insertReplacementText",
|
||||
data: input.value || "",
|
||||
}));
|
||||
} catch {
|
||||
input.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
|
||||
}
|
||||
input.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
|
||||
}
|
||||
|
||||
function syncFormFieldsBeforeSubmit(form) {
|
||||
form.querySelectorAll("#ak-identifier-input, #ak-stage-identification-password, #ak-stage-password-input").forEach((input) => {
|
||||
dispatchSyntheticInput(input);
|
||||
});
|
||||
}
|
||||
|
||||
function enhanceSubmitHandoff(root) {
|
||||
root.querySelectorAll("form").forEach((form) => {
|
||||
if (form.dataset.nodedcSubmitBound === "true") return;
|
||||
|
||||
form.dataset.nodedcSubmitBound = "true";
|
||||
form.addEventListener("submit", () => {
|
||||
syncFormFieldsBeforeSubmit(form);
|
||||
document.body?.classList.add("nodedc-auth-submitting");
|
||||
}, true);
|
||||
});
|
||||
@@ -823,3 +939,6 @@
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load authentik_core %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html
|
||||
lang="{{ LANGUAGE_CODE }}"
|
||||
data-theme="{% if ui_theme == "dark" %}dark{% else %}light{% endif %}"
|
||||
data-theme-choice="{% if ui_theme == "dark" %}dark{% elif ui_theme == "light" %}light{% else %}auto{% endif %}"
|
||||
>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
{# Darkreader breaks the site regardless of theme as its not compatible with webcomponents, and we default to a dark theme based on preferred colour-scheme #}
|
||||
<meta name="darkreader-lock">
|
||||
<title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title>
|
||||
<link rel="icon" href="{{ brand.branding_favicon_url }}">
|
||||
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}">
|
||||
|
||||
{% block head_before %}
|
||||
{% endblock %}
|
||||
|
||||
{% include "base/theme.html" %}
|
||||
|
||||
{% with request_host=request.get_host %}
|
||||
{% if request_host|slice:":11" != "auth-admin." and request_host|slice:":9" != "id-admin." and request_host|slice:":12" != "172.22.0.222" %}
|
||||
{% if request.path|slice:":9" == "/if/flow/" or request.path|slice:":7" == "/flows/" or request.path|slice:":7" == "/login/" %}
|
||||
<style data-id="brand-css">{{ brand_css }}</style>
|
||||
{% else %}
|
||||
<style data-id="brand-css"></style>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<style data-id="brand-css"></style>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<script src="{% versioned_script 'dist/poly-%v.js' %}" type="module"></script>
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
{% for key, value in html_meta.items %}
|
||||
<meta name="{{key}}" content="{{ value }}" />
|
||||
{% endfor %}
|
||||
</head>
|
||||
<body>
|
||||
{% block body %}
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -280,7 +280,7 @@ body.nodedc-auth-permission-denied .pf-c-login__main-header::after {
|
||||
}
|
||||
|
||||
body.nodedc-auth-permission-denied .pf-c-login__main-header::after {
|
||||
content: "Доступ к модулю NODE.DC ограничен." !important;
|
||||
content: "Доступ запрещён." !important;
|
||||
}
|
||||
|
||||
.pf-c-login__main-header[data-nodedc-permission-denied="true"] .pf-c-title,
|
||||
@@ -292,6 +292,16 @@ body.nodedc-auth-permission-denied h1.pf-c-title {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body.nodedc-auth-permission-denied ak-flow-executor::part(main),
|
||||
body.nodedc-auth-permission-denied .pf-c-login__main {
|
||||
background: transparent !important;
|
||||
border: 0 !important;
|
||||
outline: 0 !important;
|
||||
box-shadow: none !important;
|
||||
-webkit-backdrop-filter: none !important;
|
||||
backdrop-filter: none !important;
|
||||
}
|
||||
|
||||
.pf-c-login__main-body {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{% extends "base/skeleton.html" %}
|
||||
|
||||
{% load authentik_core %}
|
||||
|
||||
{% block head %}
|
||||
<script src="{% versioned_script 'dist/admin/AdminInterface-%v.js' %}" type="module"></script>
|
||||
{% include "base/header_js.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<ak-skip-to-content></ak-skip-to-content>
|
||||
<ak-message-container alignment="bottom"></ak-message-container>
|
||||
<ak-interface-admin>
|
||||
{% include "base/placeholder.html" %}
|
||||
</ak-interface-admin>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user