502 lines
20 KiB
HTML
502 lines
20 KiB
HTML
{% load static tailwind_tags %}
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
<title>{% block title %}Keywarden{% endblock %}</title>
|
|
<link rel="icon" type="image/svg+xml" href="{% static 'branding/keywarden-favicon.svg' %}">
|
|
<link rel="icon" type="image/png" href="{% static 'ninja/favicon.png' %}">
|
|
<meta id="kw-theme-color" name="theme-color" content="#0f172a">
|
|
<meta property="og:title" content="Keywarden">
|
|
<meta name="twitter:card" content="summary">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
{% tailwind_css %}
|
|
<style>
|
|
:root {
|
|
color-scheme: light;
|
|
--kw-chart-card-border: #d6e0ee;
|
|
--kw-chart-card-bg: radial-gradient(120% 140% at 0% 0%, #f8fbff 0%, #eef4ff 50%, #e6eefb 100%);
|
|
--kw-chart-text: #0f172a;
|
|
--kw-chart-subtext: #475569;
|
|
--kw-chart-badge-border: rgba(148, 163, 184, 0.45);
|
|
--kw-chart-badge-bg: rgba(255, 255, 255, 0.85);
|
|
--kw-chart-badge-text: #1e293b;
|
|
--kw-chart-canvas-border: rgba(148, 163, 184, 0.35);
|
|
--kw-chart-canvas-bg: rgba(248, 250, 252, 0.75);
|
|
--kw-chart-empty-text: #64748b;
|
|
}
|
|
html[data-theme="dark"] {
|
|
color-scheme: dark;
|
|
--kw-chart-card-border: #1f2937;
|
|
--kw-chart-card-bg: radial-gradient(120% 140% at 0% 0%, #1e293b 0%, #111827 50%, #0b1220 100%);
|
|
--kw-chart-text: #f3f4f6;
|
|
--kw-chart-subtext: #94a3b8;
|
|
--kw-chart-badge-border: rgba(100, 116, 139, 0.45);
|
|
--kw-chart-badge-bg: rgba(15, 23, 42, 0.65);
|
|
--kw-chart-badge-text: #e2e8f0;
|
|
--kw-chart-canvas-border: rgba(71, 85, 105, 0.35);
|
|
--kw-chart-canvas-bg: rgba(2, 6, 23, 0.32);
|
|
--kw-chart-empty-text: #94a3b8;
|
|
}
|
|
.kw-shell-width {
|
|
max-width: 80rem;
|
|
}
|
|
.kw-topnav-link {
|
|
transition: color 150ms ease, background-color 150ms ease;
|
|
}
|
|
html[data-display-mode="full"] .kw-shell-width {
|
|
max-width: min(1800px, calc(100vw - 1.5rem));
|
|
}
|
|
@media (min-width: 640px) {
|
|
html[data-display-mode="full"] .kw-shell-width {
|
|
max-width: min(1880px, calc(100vw - 3rem));
|
|
}
|
|
}
|
|
html[data-theme="dark"] body {
|
|
background: linear-gradient(135deg, #020617, #0f172a 45%, #111827);
|
|
color: #e2e8f0;
|
|
}
|
|
html[data-theme="dark"] header,
|
|
html[data-theme="dark"] footer {
|
|
border-color: #334155 !important;
|
|
background-color: rgba(15, 23, 42, 0.92) !important;
|
|
}
|
|
html[data-theme="dark"] .bg-white {
|
|
background-color: #0f172a !important;
|
|
}
|
|
html[data-theme="dark"] .bg-gray-50 {
|
|
background-color: #111827 !important;
|
|
}
|
|
html[data-theme="dark"] .bg-gray-100 {
|
|
background-color: #1f2937 !important;
|
|
}
|
|
html[data-theme="dark"] .border-gray-100 {
|
|
border-color: #1e293b !important;
|
|
}
|
|
html[data-theme="dark"] .border-gray-200 {
|
|
border-color: #334155 !important;
|
|
}
|
|
html[data-theme="dark"] .border-gray-300 {
|
|
border-color: #475569 !important;
|
|
}
|
|
html[data-theme="dark"] .divide-gray-100 > :not([hidden]) ~ :not([hidden]) {
|
|
border-color: #1e293b !important;
|
|
}
|
|
html[data-theme="dark"] .divide-gray-200 > :not([hidden]) ~ :not([hidden]) {
|
|
border-color: #334155 !important;
|
|
}
|
|
html[data-theme="dark"] .text-gray-900 {
|
|
color: #f1f5f9 !important;
|
|
}
|
|
html[data-theme="dark"] .text-gray-800 {
|
|
color: #e2e8f0 !important;
|
|
}
|
|
html[data-theme="dark"] .text-gray-700 {
|
|
color: #e2e8f0 !important;
|
|
}
|
|
html[data-theme="dark"] .text-gray-600 {
|
|
color: #cbd5e1 !important;
|
|
}
|
|
html[data-theme="dark"] .text-gray-500 {
|
|
color: #94a3b8 !important;
|
|
}
|
|
html[data-theme="dark"] .text-gray-400 {
|
|
color: #64748b !important;
|
|
}
|
|
html[data-theme="dark"] .text-blue-700 {
|
|
color: #60a5fa !important;
|
|
}
|
|
html[data-theme="dark"] .text-emerald-700 {
|
|
color: #34d399 !important;
|
|
}
|
|
html[data-theme="dark"] .text-emerald-800 {
|
|
color: #6ee7b7 !important;
|
|
}
|
|
html[data-theme="dark"] .text-indigo-700 {
|
|
color: #818cf8 !important;
|
|
}
|
|
html[data-theme="dark"] .text-rose-700 {
|
|
color: #fb7185 !important;
|
|
}
|
|
html[data-theme="dark"] .text-red-600 {
|
|
color: #f87171 !important;
|
|
}
|
|
html[data-theme="dark"] .text-red-800 {
|
|
color: #fca5a5 !important;
|
|
}
|
|
html[data-theme="dark"] .text-amber-700 {
|
|
color: #fbbf24 !important;
|
|
}
|
|
html[data-theme="dark"] .text-amber-800 {
|
|
color: #fcd34d !important;
|
|
}
|
|
html[data-theme="dark"] .ring-blue-100 {
|
|
--tw-ring-color: rgba(59, 130, 246, 0.35) !important;
|
|
}
|
|
html[data-theme="dark"] .hover\:bg-gray-50:hover {
|
|
background-color: #1f2937 !important;
|
|
}
|
|
html[data-theme="dark"] .hover\:bg-gray-100:hover {
|
|
background-color: #334155 !important;
|
|
}
|
|
html[data-theme="dark"] .hover\:bg-white:hover {
|
|
background-color: #1e293b !important;
|
|
}
|
|
html[data-theme="dark"] .hover\:text-gray-900:hover {
|
|
color: #f1f5f9 !important;
|
|
}
|
|
html[data-theme="dark"] .bg-blue-50 {
|
|
background-color: rgba(37, 99, 235, 0.2) !important;
|
|
}
|
|
html[data-theme="dark"] .bg-blue-100 {
|
|
background-color: rgba(37, 99, 235, 0.3) !important;
|
|
}
|
|
html[data-theme="dark"] .bg-emerald-50 {
|
|
background-color: rgba(5, 150, 105, 0.2) !important;
|
|
}
|
|
html[data-theme="dark"] .bg-emerald-100 {
|
|
background-color: rgba(5, 150, 105, 0.28) !important;
|
|
}
|
|
html[data-theme="dark"] .bg-indigo-50 {
|
|
background-color: rgba(79, 70, 229, 0.22) !important;
|
|
}
|
|
html[data-theme="dark"] .bg-rose-50 {
|
|
background-color: rgba(225, 29, 72, 0.2) !important;
|
|
}
|
|
html[data-theme="dark"] .bg-rose-100 {
|
|
background-color: rgba(225, 29, 72, 0.28) !important;
|
|
}
|
|
html[data-theme="dark"] .bg-amber-100 {
|
|
background-color: rgba(180, 83, 9, 0.35) !important;
|
|
}
|
|
html[data-theme="dark"] .bg-red-50 {
|
|
background-color: rgba(127, 29, 29, 0.32) !important;
|
|
}
|
|
html[data-theme="dark"] .bg-gray-200 {
|
|
background-color: #334155 !important;
|
|
}
|
|
html[data-theme="dark"] .hover\:bg-blue-50:hover {
|
|
background-color: rgba(37, 99, 235, 0.28) !important;
|
|
}
|
|
html[data-theme="dark"] .hover\:bg-blue-100:hover {
|
|
background-color: rgba(37, 99, 235, 0.35) !important;
|
|
}
|
|
html[data-theme="dark"] tr.border-t {
|
|
border-color: #334155 !important;
|
|
}
|
|
html[data-theme="dark"] hr {
|
|
border-color: #334155 !important;
|
|
}
|
|
html[data-theme="dark"] input[type="text"],
|
|
html[data-theme="dark"] input[type="password"],
|
|
html[data-theme="dark"] input[type="email"],
|
|
html[data-theme="dark"] input[type="search"],
|
|
html[data-theme="dark"] input[type="datetime-local"],
|
|
html[data-theme="dark"] input[type="number"],
|
|
html[data-theme="dark"] input[type="url"],
|
|
html[data-theme="dark"] input[type="tel"],
|
|
html[data-theme="dark"] textarea,
|
|
html[data-theme="dark"] select {
|
|
background-color: #0b1220 !important;
|
|
border-color: #475569 !important;
|
|
color: #e2e8f0 !important;
|
|
}
|
|
html[data-theme="dark"] input::placeholder,
|
|
html[data-theme="dark"] textarea::placeholder {
|
|
color: #94a3b8 !important;
|
|
}
|
|
html[data-theme="dark"] .kw-topnav-link {
|
|
color: #cbd5e1 !important;
|
|
}
|
|
html[data-theme="dark"] .kw-topnav-link:hover {
|
|
background-color: #1f2937 !important;
|
|
color: #bfdbfe !important;
|
|
}
|
|
@media (min-width: 768px) {
|
|
html[data-theme="dark"] .kw-topnav-link:hover {
|
|
background-color: transparent !important;
|
|
color: #93c5fd !important;
|
|
}
|
|
}
|
|
</style>
|
|
<script>
|
|
(function () {
|
|
var key = "keywardenThemeMode";
|
|
function normalize(mode) {
|
|
return mode === "light" || mode === "dark" || mode === "system" ? mode : "system";
|
|
}
|
|
function resolve(mode) {
|
|
if (mode === "system") {
|
|
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
}
|
|
return mode;
|
|
}
|
|
var mode = "system";
|
|
try {
|
|
mode = normalize(window.localStorage.getItem(key));
|
|
} catch (err) {
|
|
mode = "system";
|
|
}
|
|
var resolved = resolve(mode);
|
|
document.documentElement.setAttribute("data-theme-mode", mode);
|
|
document.documentElement.setAttribute("data-theme", resolved);
|
|
})();
|
|
</script>
|
|
<script>
|
|
(function () {
|
|
var key = "keywardenDisplayMode";
|
|
function normalize(mode) {
|
|
return mode === "full" ? "full" : "compact";
|
|
}
|
|
var mode = "compact";
|
|
try {
|
|
mode = normalize(window.localStorage.getItem(key));
|
|
} catch (err) {
|
|
mode = "compact";
|
|
}
|
|
document.documentElement.setAttribute("data-display-mode", mode);
|
|
})();
|
|
</script>
|
|
{% block extra_head %}{% endblock %}
|
|
</head>
|
|
<body class="min-h-screen bg-gradient-to-br from-slate-50 via-white to-slate-100 text-slate-900 antialiased font-['Space_Grotesk']">
|
|
<div class="min-h-screen flex flex-col">
|
|
{% if not is_popout %}
|
|
<header class="border-b border-gray-200 bg-white/90 backdrop-blur supports-[backdrop-filter]:bg-white/75">
|
|
<nav class="kw-shell-width mx-auto w-full px-4 py-3 lg:px-6">
|
|
<div class="flex flex-wrap items-center justify-between">
|
|
<a href="/" class="flex items-center gap-3">
|
|
<span class="flex h-11 w-11 items-center justify-center rounded-2xl bg-white p-1 shadow-sm ring-1 ring-blue-100">
|
|
<img src="{% static 'branding/keywarden-favicon.svg' %}" alt="Keywarden logo" class="h-8 w-8">
|
|
</span>
|
|
<span class="flex flex-col">
|
|
<span class="text-xl font-semibold leading-tight tracking-tight">Keywarden</span>
|
|
<span class="text-xs text-gray-500">Access control vault</span>
|
|
</span>
|
|
</a>
|
|
<div class="flex items-center gap-3 md:order-2">
|
|
<button
|
|
type="button"
|
|
data-theme-toggle
|
|
class="inline-flex items-center gap-1 rounded-lg border border-gray-200 bg-white px-3 py-2 text-xs font-semibold text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-300"
|
|
aria-label="Toggle theme"
|
|
>
|
|
<span data-theme-icon aria-hidden="true">◐</span>
|
|
<span data-theme-label>Theme</span>
|
|
</button>
|
|
{% if request.user.is_authenticated %}
|
|
<a
|
|
href="{% url 'accounts:logout' %}"
|
|
class="inline-flex items-center rounded-lg bg-blue-700 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-800 focus:outline-none focus:ring-4 focus:ring-blue-300"
|
|
>
|
|
Logout
|
|
</a>
|
|
{% else %}
|
|
<a
|
|
href="{% url 'accounts:login' %}"
|
|
class="inline-flex items-center rounded-lg bg-blue-700 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-800 focus:outline-none focus:ring-4 focus:ring-blue-300"
|
|
>
|
|
Login
|
|
</a>
|
|
{% endif %}
|
|
<button
|
|
data-collapse-toggle="navbar-default"
|
|
type="button"
|
|
class="inline-flex items-center rounded-lg p-2 text-sm text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 md:hidden"
|
|
aria-controls="navbar-default"
|
|
aria-expanded="false"
|
|
>
|
|
<span class="sr-only">Open main menu</span>
|
|
<svg class="h-6 w-6" aria-hidden="true" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div class="hidden w-full md:order-1 md:block md:w-auto" id="navbar-default">
|
|
<ul class="mt-4 flex flex-col rounded-lg border border-gray-100 bg-gray-50 p-4 text-sm font-medium text-gray-700 md:mt-0 md:flex-row md:space-x-6 md:border-0 md:bg-transparent md:p-0">
|
|
{% if request.user.is_authenticated %}
|
|
<li>
|
|
<a
|
|
href="{% url 'servers:dashboard' %}"
|
|
class="kw-topnav-link block rounded px-3 py-2 hover:bg-gray-100 md:px-0 md:py-0 md:hover:bg-transparent md:hover:text-blue-700"
|
|
>
|
|
Servers
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a
|
|
href="{% url 'accounts:profile' %}"
|
|
class="kw-topnav-link block rounded px-3 py-2 hover:bg-gray-100 md:px-0 md:py-0 md:hover:bg-transparent md:hover:text-blue-700"
|
|
>
|
|
Profile
|
|
</a>
|
|
</li>
|
|
{% if request.user.is_superuser or request.user.is_staff or perms.servers.change_server or perms.access.change_accessrequest %}
|
|
<li>
|
|
<a
|
|
href="{% url 'servers:admin_dashboard' %}"
|
|
class="kw-topnav-link block rounded px-3 py-2 hover:bg-gray-100 md:px-0 md:py-0 md:hover:bg-transparent md:hover:text-blue-700"
|
|
>
|
|
Admin
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
{% endif %}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
</header>
|
|
{% endif %}
|
|
|
|
<main class="kw-shell-width mx-auto w-full flex-1 px-4 py-8 sm:px-6 lg:px-8">
|
|
{% block content %}{% endblock %}
|
|
</main>
|
|
|
|
{% if not is_popout %}
|
|
<footer class="border-t border-gray-200 bg-white">
|
|
<div class="kw-shell-width mx-auto flex w-full flex-col gap-2 px-4 py-6 text-sm text-gray-500 sm:flex-row sm:items-center sm:justify-between sm:px-6 lg:px-8">
|
|
<div class="flex items-center gap-2">
|
|
<span class="font-medium text-gray-700">Keywarden</span>
|
|
<span class="text-gray-300">•</span>
|
|
<span>Secure access manager</span>
|
|
</div>
|
|
<div class="flex items-center gap-3">
|
|
<a class="font-medium text-blue-700 hover:underline" href="https://git.ntbx.io/boris/keywarden">Repository</a>
|
|
<span class="text-gray-300">•</span>
|
|
<a class="font-medium text-blue-700 hover:underline" href="https://ntbx.io">George Wilkinson</a>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
{% endif %}
|
|
</div>
|
|
<script>
|
|
(function () {
|
|
var key = "keywardenThemeMode";
|
|
var root = document.documentElement;
|
|
var toggles = document.querySelectorAll("[data-theme-toggle]");
|
|
var labels = document.querySelectorAll("[data-theme-label]");
|
|
var icons = document.querySelectorAll("[data-theme-icon]");
|
|
var themeMeta = document.getElementById("kw-theme-color");
|
|
|
|
function normalize(mode) {
|
|
return mode === "light" || mode === "dark" || mode === "system" ? mode : "system";
|
|
}
|
|
function resolve(mode) {
|
|
if (mode === "system") {
|
|
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
}
|
|
return mode;
|
|
}
|
|
function readMode() {
|
|
try {
|
|
return normalize(window.localStorage.getItem(key));
|
|
} catch (err) {
|
|
return "system";
|
|
}
|
|
}
|
|
function writeMode(mode) {
|
|
try {
|
|
window.localStorage.setItem(key, mode);
|
|
} catch (err) {
|
|
// Ignore storage failures.
|
|
}
|
|
}
|
|
function apply(mode) {
|
|
var resolved = resolve(mode);
|
|
root.setAttribute("data-theme-mode", mode);
|
|
root.setAttribute("data-theme", resolved);
|
|
var labelText = mode === "system" ? "Theme: System" : (mode === "dark" ? "Theme: Dark" : "Theme: Light");
|
|
var iconText = resolved === "dark" ? "☾" : "☀";
|
|
if (themeMeta) {
|
|
themeMeta.setAttribute("content", resolved === "dark" ? "#020617" : "#f8fafc");
|
|
}
|
|
for (var i = 0; i < labels.length; i += 1) {
|
|
labels[i].textContent = labelText;
|
|
}
|
|
for (var j = 0; j < icons.length; j += 1) {
|
|
icons[j].textContent = iconText;
|
|
}
|
|
if (typeof window.CustomEvent === "function") {
|
|
window.dispatchEvent(new CustomEvent("keywarden:theme-mode", { detail: { mode: mode, resolved: resolved } }));
|
|
}
|
|
return mode;
|
|
}
|
|
function cycle(mode) {
|
|
if (mode === "light") {
|
|
return "dark";
|
|
}
|
|
if (mode === "dark") {
|
|
return "system";
|
|
}
|
|
return "light";
|
|
}
|
|
|
|
var currentMode = apply(readMode());
|
|
for (var k = 0; k < toggles.length; k += 1) {
|
|
toggles[k].addEventListener("click", function () {
|
|
currentMode = apply(cycle(currentMode));
|
|
writeMode(currentMode);
|
|
});
|
|
}
|
|
|
|
var media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
var mediaListener = function () {
|
|
if (currentMode === "system") {
|
|
apply(currentMode);
|
|
}
|
|
};
|
|
if (typeof media.addEventListener === "function") {
|
|
media.addEventListener("change", mediaListener);
|
|
} else if (typeof media.addListener === "function") {
|
|
media.addListener(mediaListener);
|
|
}
|
|
|
|
window.keywardenGetThemeMode = function () {
|
|
return currentMode;
|
|
};
|
|
window.keywardenSetThemeMode = function (mode) {
|
|
currentMode = apply(normalize(mode));
|
|
writeMode(currentMode);
|
|
return currentMode;
|
|
};
|
|
})();
|
|
</script>
|
|
<script>
|
|
(function () {
|
|
var key = "keywardenDisplayMode";
|
|
function normalize(mode) {
|
|
return mode === "full" ? "full" : "compact";
|
|
}
|
|
function getMode() {
|
|
try {
|
|
return normalize(window.localStorage.getItem(key));
|
|
} catch (err) {
|
|
return "compact";
|
|
}
|
|
}
|
|
function setMode(mode) {
|
|
var normalized = normalize(mode);
|
|
document.documentElement.setAttribute("data-display-mode", normalized);
|
|
try {
|
|
window.localStorage.setItem(key, normalized);
|
|
} catch (err) {
|
|
// Ignore storage quota or privacy-mode failures.
|
|
}
|
|
if (typeof window.CustomEvent === "function") {
|
|
window.dispatchEvent(new CustomEvent("keywarden:display-mode", { detail: { mode: normalized } }));
|
|
}
|
|
return normalized;
|
|
}
|
|
window.keywardenGetDisplayMode = getMode;
|
|
window.keywardenSetDisplayMode = setMode;
|
|
})();
|
|
</script>
|
|
<script src="https://cdn.jsdelivr.net/npm/flowbite@4.0.1/dist/flowbite.min.js"></script>
|
|
</body>
|
|
</html>
|