1648 lines
96 KiB
HTML
1648 lines
96 KiB
HTML
<!doctype html>
|
|
<html lang="en" class="h-full">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<script src="/assets/nav-prelude.js"></script>
|
|
<link rel="modulepreload" href="/assets/nav.js" />
|
|
<link rel="stylesheet" href="/vendor/inter/400.css" />
|
|
<link rel="stylesheet" href="/vendor/inter/600.css" />
|
|
<link rel="stylesheet" href="/vendor/inter/700.css" />
|
|
<title>Settings - Overlord</title>
|
|
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico" />
|
|
<link rel="stylesheet" href="/assets/tailwind.css" />
|
|
<link rel="stylesheet" href="/vendor/fontawesome/css/all.min.css" />
|
|
<link rel="stylesheet" href="/assets/main.css" />
|
|
<script defer src="/assets/toast.js"></script>
|
|
<link rel="stylesheet" href="/assets/custom.css" />
|
|
<style>
|
|
.settings-nav-link {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.625rem;
|
|
min-width: 0;
|
|
padding: 0.5rem 0.75rem;
|
|
border-radius: 0.5rem;
|
|
color: rgb(203 213 225); /* slate-300 */
|
|
cursor: pointer;
|
|
transition: background-color 120ms, color 120ms;
|
|
}
|
|
.settings-nav-link span {
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.settings-nav-link:hover {
|
|
background-color: rgb(30 41 59 / 0.7); /* slate-800/70 */
|
|
color: rgb(241 245 249); /* slate-100 */
|
|
}
|
|
/* Active state pulls its color from --accent (set inline per link to
|
|
match the link's icon). Falls back to sky-400 if not set. */
|
|
.settings-nav-link.active {
|
|
background-color: rgb(var(--accent, 56 189 248) / 0.18);
|
|
color: rgb(241 245 249);
|
|
box-shadow:
|
|
inset 2px 0 0 0 rgb(var(--accent, 56 189 248)),
|
|
0 0 14px -2px rgb(var(--accent, 56 189 248) / 0.45);
|
|
}
|
|
.settings-nav-group-label {
|
|
display: block;
|
|
padding: 0.5rem 0.75rem 0.25rem;
|
|
font-size: 0.625rem;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
color: rgb(100 116 139); /* slate-500 */
|
|
}
|
|
/* Scroll offset for #section anchors. In topbar nav mode this is set to
|
|
the topbar's actual rendered height + a small buffer at runtime; in
|
|
sidebar mode it's just a small visual margin. The settings-section
|
|
class applies scroll-margin-top, which both `scrollIntoView` and
|
|
browser hash navigation honor. */
|
|
:root { --settings-scroll-mt: 1rem; }
|
|
.settings-section { scroll-margin-top: var(--settings-scroll-mt); }
|
|
/* Also pin the sticky sidebar below the topbar so it doesn't slide
|
|
under the nav when the page is scrolled. */
|
|
#settings-sidebar {
|
|
top: var(--settings-scroll-mt);
|
|
max-height: calc(100vh - var(--settings-scroll-mt) - 1rem);
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: rgb(100 116 139 / 0.35) transparent;
|
|
}
|
|
#settings-sidebar::-webkit-scrollbar { width: 5px; }
|
|
#settings-sidebar::-webkit-scrollbar-track { background: transparent; }
|
|
#settings-sidebar::-webkit-scrollbar-thumb {
|
|
background: rgb(100 116 139 / 0.35);
|
|
border-radius: 999px;
|
|
}
|
|
@media (max-width: 1023px) {
|
|
#settings-sidebar {
|
|
max-height: none;
|
|
overflow: visible;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="min-h-screen bg-slate-950 text-slate-100">
|
|
<header id="top-nav"></header>
|
|
|
|
<main class="max-w-7xl w-full mx-auto px-4 py-8">
|
|
<div class="mb-6">
|
|
<h1 class="text-2xl font-bold text-slate-100">Settings</h1>
|
|
<p class="text-slate-400 text-sm">
|
|
Manage your account preferences, operational tools, and admin configuration.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex flex-col lg:flex-row gap-6">
|
|
|
|
<!-- Sidebar nav -->
|
|
<aside class="lg:w-56 lg:flex-shrink-0">
|
|
<nav id="settings-sidebar"
|
|
class="lg:sticky bg-slate-900/60 border border-slate-800 rounded-xl p-2 space-y-0.5 text-sm">
|
|
|
|
<span class="settings-nav-group-label">Personal</span>
|
|
<a data-target="section-account" class="settings-nav-link" style="--accent: 56 189 248"><i class="fa-solid fa-user w-4 text-sky-400"></i><span>Account</span></a>
|
|
<a data-target="section-sessions" class="settings-nav-link" style="--accent: 52 211 153"><i class="fa-solid fa-desktop w-4 text-emerald-400"></i><span>Active Sessions</span></a>
|
|
<a data-target="section-prefs" class="settings-nav-link" style="--accent: 167 139 250"><i class="fa-solid fa-sliders w-4 text-violet-400"></i><span>Dashboard Preferences</span></a>
|
|
<a data-target="input-archive-section" class="settings-nav-link" style="--accent: 250 204 21"><i class="fa-solid fa-box-archive w-4 text-yellow-400"></i><span>Input Archive</span></a>
|
|
<a data-target="telegram-settings" class="settings-nav-link" style="--accent: 52 211 153"><i class="fa-solid fa-bell w-4 text-emerald-400"></i><span>Notification Delivery</span></a>
|
|
<a data-target="section-news" class="settings-nav-link" style="--accent: 56 189 248"><i class="fa-brands fa-telegram w-4 text-sky-400"></i><span>News & Updates</span></a>
|
|
|
|
<span class="settings-nav-group-label">Operations</span>
|
|
<a data-target="section-bans" class="settings-nav-link" data-permission="network:manage-bans" style="--accent: 248 113 113"><i class="fa-solid fa-ban w-4 text-red-400"></i><span>Banned IPs</span></a>
|
|
<a data-target="section-builds" class="settings-nav-link" data-permission="clients:build" style="--accent: 251 146 60"><i class="fa-solid fa-hammer w-4 text-orange-400"></i><span>Build IDs</span></a>
|
|
<a data-target="wipe-offline-section" class="settings-nav-link" data-permission="clients:control" style="--accent: 248 113 113"><i class="fa-solid fa-circle-minus w-4 text-red-400"></i><span>Wipe Offline Clients</span></a>
|
|
|
|
<span class="settings-nav-group-label">Admin</span>
|
|
<a data-target="section-permissions-overview" class="settings-nav-link" data-permission="users:manage" style="--accent: 167 139 250"><i class="fa-solid fa-user-shield w-4 text-violet-400"></i><span>Access Overview</span></a>
|
|
<a data-target="section-security" class="settings-nav-link" data-permission="system:security" style="--accent: 129 140 248"><i class="fa-solid fa-shield-halved w-4 text-indigo-400"></i><span>Security Policy</span></a>
|
|
<a data-target="section-tls" class="settings-nav-link" data-permission="system:tls" style="--accent: 34 211 238"><i class="fa-solid fa-certificate w-4 text-cyan-400"></i><span>TLS Certificates</span></a>
|
|
<a data-target="section-oidc" class="settings-nav-link" data-permission="system:oidc" style="--accent: 20 184 166"><i class="fa-solid fa-right-to-bracket w-4 text-teal-400"></i><span>OIDC / SSO</span></a>
|
|
<a data-target="chat-settings-section" class="settings-nav-link" data-permission="system:chat" style="--accent: 56 189 248"><i class="fa-solid fa-comments w-4 text-sky-400"></i><span>Team Chat</span></a>
|
|
<a data-target="registration-section" class="settings-nav-link" data-permission="system:registration" style="--accent: 52 211 153"><i class="fa-solid fa-user-plus w-4 text-emerald-400"></i><span>Registration</span></a>
|
|
<a data-target="thumbnails-section" class="settings-nav-link" data-permission="system:thumbnails" style="--accent: 56 189 248"><i class="fa-solid fa-images w-4 text-sky-400"></i><span>Screenshot Thumbnails</span></a>
|
|
<a data-target="input-archive-admin-section" class="settings-nav-link" data-permission="system:input-archive" style="--accent: 250 204 21"><i class="fa-solid fa-database w-4 text-yellow-400"></i><span>Input Archive Admin</span></a>
|
|
<a data-target="build-rate-limit-section" class="settings-nav-link" data-permission="system:build-limits" style="--accent: 251 146 60"><i class="fa-solid fa-gauge-high w-4 text-orange-400"></i><span>Build Rate Limit</span></a>
|
|
<a data-target="section-appearance" class="settings-nav-link" data-permission="system:appearance" style="--accent: 232 121 249"><i class="fa-solid fa-palette w-4 text-fuchsia-400"></i><span>Branding</span></a>
|
|
<a data-target="export-import-section" class="settings-nav-link" data-permission="system:export-import" style="--accent: 251 191 36"><i class="fa-solid fa-file-export w-4 text-amber-400"></i><span>Export / Import</span></a>
|
|
<a data-target="section-health" class="settings-nav-link" data-permission="system:health" style="--accent: 34 211 238"><i class="fa-solid fa-heart-pulse w-4 text-cyan-400"></i><span>Server Health</span></a>
|
|
</nav>
|
|
</aside>
|
|
|
|
<!-- Sections -->
|
|
<div class="flex-1 min-w-0 space-y-6">
|
|
|
|
<!-- ========== PERSONAL ========== -->
|
|
|
|
<!-- Account -->
|
|
<section id="section-account" class="bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-sky-500/10 border border-sky-500/30">
|
|
<i class="fa-solid fa-user text-sky-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">Account</h2>
|
|
<p class="text-sm text-slate-400">View your account info and update your password.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm">
|
|
<div class="rounded-lg bg-slate-900 border border-slate-800 px-3 py-2">
|
|
<div class="text-slate-400">Username</div>
|
|
<div id="settings-username" class="font-medium">-</div>
|
|
</div>
|
|
<div class="rounded-lg bg-slate-900 border border-slate-800 px-3 py-2">
|
|
<div class="text-slate-400">Role</div>
|
|
<div id="settings-role" class="font-medium">-</div>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="password-form" class="space-y-3 mt-2 pt-4 border-t border-slate-800">
|
|
<p class="text-xs font-semibold uppercase tracking-wider text-slate-500 pb-1">Change Password</p>
|
|
<input
|
|
id="current-password"
|
|
type="password"
|
|
autocomplete="current-password"
|
|
placeholder="Current password"
|
|
class="w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
|
required
|
|
/>
|
|
<input
|
|
id="new-password"
|
|
type="password"
|
|
autocomplete="new-password"
|
|
minlength="6"
|
|
placeholder="New password (min 6 chars)"
|
|
class="w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
|
required
|
|
/>
|
|
<input
|
|
id="confirm-password"
|
|
type="password"
|
|
autocomplete="new-password"
|
|
minlength="6"
|
|
placeholder="Confirm new password"
|
|
class="w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
|
required
|
|
/>
|
|
<div class="pt-1">
|
|
<p id="password-policy-hint" class="text-xs text-slate-400 mb-3"></p>
|
|
</div>
|
|
<button
|
|
type="submit"
|
|
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-sky-600 hover:bg-sky-500 transition-colors text-white text-sm font-medium"
|
|
>
|
|
<i class="fa-solid fa-key"></i>Update Password
|
|
</button>
|
|
</form>
|
|
|
|
<div id="mfa-settings" class="space-y-3 mt-2 pt-4 border-t border-slate-800">
|
|
<div class="flex items-center justify-between gap-3 flex-wrap">
|
|
<div>
|
|
<p class="text-xs font-semibold uppercase tracking-wider text-slate-500 pb-1">Multi-Factor Authentication</p>
|
|
<p id="mfa-status-text" class="text-sm text-slate-400">Loading MFA status...</p>
|
|
</div>
|
|
<button
|
|
id="mfa-start-btn"
|
|
type="button"
|
|
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-emerald-700 hover:bg-emerald-600 transition-colors text-white text-sm font-medium"
|
|
>
|
|
<i class="fa-solid fa-shield-halved"></i>Set Up MFA
|
|
</button>
|
|
</div>
|
|
|
|
<div id="mfa-setup-panel" class="hidden rounded-lg border border-slate-700 bg-slate-900 px-4 py-4 space-y-4">
|
|
<p class="text-sm text-slate-300 pb-1">Add this manual key to your authenticator app, then enter the 6-digit code.</p>
|
|
<div class="grid grid-cols-1 md:grid-cols-[20rem_minmax(0,1fr)] gap-5 items-start">
|
|
<div class="w-80 max-w-full">
|
|
<div id="mfa-qr-code" class="aspect-square w-full rounded-lg bg-white p-1 border border-slate-700 overflow-hidden"></div>
|
|
</div>
|
|
<div class="min-w-0 space-y-3">
|
|
<p class="text-xs font-semibold uppercase tracking-wider text-slate-500">Manual Key</p>
|
|
<div class="rounded bg-slate-950 border border-slate-800 px-3 py-2 font-mono text-sm text-sky-300 break-all" id="mfa-secret-text"></div>
|
|
<input
|
|
id="mfa-enable-code"
|
|
type="text"
|
|
inputmode="numeric"
|
|
maxlength="6"
|
|
placeholder="6-digit code"
|
|
class="w-full px-3 py-2 rounded-lg bg-slate-950 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
|
/>
|
|
<div class="flex gap-2 flex-wrap">
|
|
<button id="mfa-enable-btn" type="button" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-emerald-700 hover:bg-emerald-600 transition-colors text-white text-sm font-medium">
|
|
<i class="fa-solid fa-check"></i>Enable MFA
|
|
</button>
|
|
<a id="mfa-otpauth-link" href="#" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-800 hover:bg-slate-700 border border-slate-700 text-sm transition-colors">
|
|
<i class="fa-solid fa-mobile-screen"></i>Open App Link
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="mfa-disable-panel" class="hidden rounded-lg border border-slate-700 bg-slate-900 px-4 py-4 space-y-3">
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
<input
|
|
id="mfa-disable-password"
|
|
type="password"
|
|
autocomplete="current-password"
|
|
placeholder="Current password"
|
|
class="w-full px-3 py-2 rounded-lg bg-slate-950 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-rose-500"
|
|
/>
|
|
<input
|
|
id="mfa-disable-code"
|
|
type="text"
|
|
inputmode="numeric"
|
|
maxlength="6"
|
|
placeholder="6-digit code"
|
|
class="w-full px-3 py-2 rounded-lg bg-slate-950 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-rose-500"
|
|
/>
|
|
</div>
|
|
<button id="mfa-disable-btn" type="button" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-rose-800/70 hover:bg-rose-700 transition-colors text-rose-100 border border-rose-700 text-sm font-medium">
|
|
<i class="fa-solid fa-lock-open"></i>Disable MFA
|
|
</button>
|
|
</div>
|
|
|
|
<p id="mfa-message" class="hidden text-sm rounded-lg px-3 py-2 border"></p>
|
|
</div>
|
|
|
|
<p id="settings-message" class="hidden text-sm rounded-lg px-3 py-2 border"></p>
|
|
</section>
|
|
|
|
<!-- Active Sessions: server-rendered and managed independently by Turbo. -->
|
|
<turbo-frame
|
|
id="section-sessions"
|
|
src="/ui/settings/sessions"
|
|
loading="lazy"
|
|
class="block bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section"
|
|
>
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-emerald-500/10 border border-emerald-500/30">
|
|
<i class="fa-solid fa-desktop text-emerald-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">Active Sessions</h2>
|
|
<p class="text-sm text-slate-400">Loading sessions…</p>
|
|
</div>
|
|
</div>
|
|
</turbo-frame>
|
|
|
|
<!-- Dashboard Preferences -->
|
|
<section id="section-prefs" class="bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-violet-500/10 border border-violet-500/30">
|
|
<i class="fa-solid fa-sliders text-violet-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">Dashboard Preferences</h2>
|
|
<p class="text-sm text-slate-400">Configure notifications, client refresh rate, and navigation layout.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="prefs-form" class="space-y-4">
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<div class="space-y-2">
|
|
<p class="text-xs font-semibold uppercase tracking-wider text-slate-500">Notifications</p>
|
|
<label class="flex items-center gap-2 text-sm">
|
|
<input id="pref-notifications" type="checkbox" class="h-4 w-4" />
|
|
<span>Enable notification badge updates</span>
|
|
</label>
|
|
<label class="flex items-center gap-2 text-sm">
|
|
<input id="pref-desktop-notifications" type="checkbox" class="h-4 w-4 accent-emerald-500" />
|
|
<span>Enable desktop (OS) notifications</span>
|
|
</label>
|
|
<p id="pref-desktop-notifications-hint" class="hidden text-xs text-amber-400">
|
|
<i class="fa-solid fa-triangle-exclamation mr-1"></i>
|
|
Browser permission was denied. Reset notification permissions for this site in your browser settings, then try again.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<p class="text-xs font-semibold uppercase tracking-wider text-slate-500">Refresh Interval</p>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Client refresh interval (seconds)</span>
|
|
<input
|
|
id="pref-refresh-seconds"
|
|
type="number"
|
|
min="3"
|
|
max="120"
|
|
class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
|
/>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<p class="text-xs font-semibold uppercase tracking-wider text-slate-500 mb-2">Navigation Layout</p>
|
|
<div class="flex gap-2">
|
|
<button type="button" id="pref-nav-sidebar"
|
|
class="nav-layout-btn flex-1 flex items-center justify-center gap-2 px-3 py-2 rounded-lg border text-sm font-medium transition-colors">
|
|
<i class="fa-solid fa-table-columns"></i> Sidebar
|
|
</button>
|
|
<button type="button" id="pref-nav-topbar"
|
|
class="nav-layout-btn flex-1 flex items-center justify-center gap-2 px-3 py-2 rounded-lg border text-sm font-medium transition-colors">
|
|
<i class="fa-solid fa-bars"></i> Top Bar
|
|
</button>
|
|
</div>
|
|
<p class="text-xs text-slate-500 mt-1">Changes apply immediately on save.</p>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<p class="text-xs font-semibold uppercase tracking-wider text-slate-500">Visibility</p>
|
|
<label class="flex items-center gap-2 text-sm">
|
|
<input id="pref-nav-hidden" type="checkbox" class="h-4 w-4 accent-indigo-500" />
|
|
<span>Hide navigation bar</span>
|
|
</label>
|
|
<p class="text-xs text-slate-500">Press <kbd class="px-1 py-0.5 rounded bg-slate-800 border border-slate-700 text-slate-400 text-[10px]">Ctrl+\</kbd> to toggle anytime.</p>
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-violet-700 hover:bg-violet-600 transition-colors text-white text-sm font-medium"
|
|
>
|
|
<i class="fa-solid fa-floppy-disk"></i>Save Preferences
|
|
</button>
|
|
</form>
|
|
</section>
|
|
|
|
<!-- Input Archive -->
|
|
<section id="input-archive-section" class="bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-yellow-500/10 border border-yellow-500/30">
|
|
<i class="fa-solid fa-box-archive text-yellow-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">Input Archive</h2>
|
|
<p class="text-sm text-slate-400">Opt in to server-side retrieval for clients you can access.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="input-archive-user-form" class="space-y-4">
|
|
<label class="flex items-start gap-3 p-3 rounded-lg border border-slate-700/60 bg-slate-900/40 hover:bg-slate-900/70 cursor-pointer transition-colors">
|
|
<input id="input-archive-my-enabled" type="checkbox" class="mt-1 h-4 w-4 accent-yellow-500" />
|
|
<div>
|
|
<div class="text-sm font-medium text-slate-100">Auto retrieve input logs for my accessible clients</div>
|
|
<div class="text-xs text-slate-400">When the global archive is enabled, online clients are polled and stored in SQLite so the keylogger page can search archived logs while a client is offline.</div>
|
|
</div>
|
|
</label>
|
|
|
|
<button type="submit" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-yellow-700 hover:bg-yellow-600 transition-colors text-white text-sm font-medium">
|
|
<i class="fa-solid fa-floppy-disk"></i>Save Preference
|
|
</button>
|
|
</form>
|
|
</section>
|
|
|
|
<!-- Notification Delivery -->
|
|
<section id="telegram-settings" class="bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-emerald-500/10 border border-emerald-500/30">
|
|
<i class="fa-solid fa-bell text-emerald-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">Notification Delivery</h2>
|
|
<p class="text-sm text-slate-400">Configure where alerts and notifications are sent.</p>
|
|
</div>
|
|
</div>
|
|
<div class="rounded-lg border border-slate-700 bg-slate-900 px-4 py-4">
|
|
<p class="text-sm text-slate-300">
|
|
Webhook and Telegram Bot settings have moved to the
|
|
<a href="/notifications" class="text-sky-400 hover:underline font-medium">Notifications page</a>.
|
|
Configure your personal webhook and Telegram bot there.
|
|
</p>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- News & Updates -->
|
|
<section id="section-news" class="bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-sky-500/10 border border-sky-500/30">
|
|
<i class="fa-brands fa-telegram text-sky-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">News & Updates</h2>
|
|
<p class="text-sm text-slate-400">Stay up to date with the newest features, releases, and announcements.</p>
|
|
</div>
|
|
</div>
|
|
<div class="rounded-lg border border-slate-700 bg-slate-900 px-4 py-4 flex flex-col sm:flex-row sm:items-center gap-4">
|
|
<div class="flex-1">
|
|
<p class="text-sm text-slate-300">
|
|
Follow the official Overlord Telegram channel for the latest updates, changelogs, and feature announcements.
|
|
</p>
|
|
<p class="text-xs text-slate-500 mt-1">Channel: <span class="text-slate-400 font-mono">@kunta</span></p>
|
|
</div>
|
|
<a
|
|
href="https://t.me/KTicketsBot"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-sky-600 hover:bg-sky-500 transition-colors text-white text-sm font-medium whitespace-nowrap"
|
|
>
|
|
<i class="fa-brands fa-telegram"></i>
|
|
Open on Telegram
|
|
</a>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ========== OPERATIONS ========== -->
|
|
|
|
<!-- Banned IPs -->
|
|
<section id="section-bans" data-permission="network:manage-bans" class="hidden bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
|
|
<div class="flex items-center justify-between gap-3">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-red-500/10 border border-red-500/30">
|
|
<i class="fa-solid fa-ban text-red-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">Banned IPs</h2>
|
|
<p class="text-sm text-slate-400">Unban blocked agent source IPs.</p>
|
|
</div>
|
|
</div>
|
|
<button
|
|
id="refresh-bans-btn"
|
|
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-800 hover:bg-slate-700 border border-slate-700 text-sm transition-colors"
|
|
type="button"
|
|
>
|
|
<i class="fa-solid fa-rotate"></i>Refresh
|
|
</button>
|
|
</div>
|
|
|
|
<div id="bans-permission-note" class="hidden rounded-lg border border-amber-800 bg-amber-900/20 text-amber-200 px-3 py-2 text-sm">
|
|
Your role does not allow IP unban actions.
|
|
</div>
|
|
|
|
<div class="overflow-x-auto border border-slate-800 rounded-lg">
|
|
<table class="w-full text-sm">
|
|
<thead class="bg-slate-800/60 text-slate-300">
|
|
<tr>
|
|
<th class="text-left px-3 py-2">IP</th>
|
|
<th class="text-left px-3 py-2">Reason</th>
|
|
<th class="text-left px-3 py-2">Banned At</th>
|
|
<th class="text-right px-3 py-2">Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="bans-table-body" class="divide-y divide-slate-800">
|
|
<tr>
|
|
<td colspan="4" class="px-3 py-6 text-center text-slate-400">Loading...</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Build IDs (operator/admin) -->
|
|
<section id="section-builds" data-permission="clients:build" class="hidden bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
|
|
<div class="flex items-center justify-between gap-3">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-orange-500/10 border border-orange-500/30">
|
|
<i class="fa-solid fa-hammer text-orange-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">Build IDs</h2>
|
|
<p class="text-sm text-slate-400">Review your builds, see how many agents have claimed each, and block a build to immediately disconnect any agent that connects with its tag.</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<label class="hidden lg:flex items-center gap-2 text-xs text-slate-400" id="builds-show-all-wrap">
|
|
<input id="builds-show-all" type="checkbox" class="h-3.5 w-3.5" />
|
|
<span>Show all users' builds</span>
|
|
</label>
|
|
<button
|
|
id="refresh-builds-btn"
|
|
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-800 hover:bg-slate-700 border border-slate-700 text-sm transition-colors"
|
|
type="button"
|
|
>
|
|
<i class="fa-solid fa-rotate"></i>Refresh
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="text-xs text-slate-500">
|
|
<strong>Claim count</strong> is the number of unique agent fingerprints that have ever connected using a build's tag. Each unique host produces one claim. A claim count higher than the number of machines you actually deployed to suggests the build tag may have leaked.
|
|
</p>
|
|
|
|
<div class="overflow-x-auto border border-slate-800 rounded-lg">
|
|
<table class="w-full text-sm">
|
|
<thead class="bg-slate-800/60 text-slate-300">
|
|
<tr>
|
|
<th class="text-left px-3 py-2">Build ID</th>
|
|
<th class="text-left px-3 py-2">Tag</th>
|
|
<th class="text-left px-3 py-2">Status</th>
|
|
<th class="text-left px-3 py-2">Created</th>
|
|
<th class="text-right px-3 py-2">Claims</th>
|
|
<th class="text-right px-3 py-2">Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="builds-table-body" class="divide-y divide-slate-800">
|
|
<tr>
|
|
<td colspan="6" class="px-3 py-6 text-center text-slate-400">Loading...</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<p id="builds-message" class="hidden text-sm rounded-lg px-3 py-2 border"></p>
|
|
</section>
|
|
|
|
<!-- Wipe Offline Clients -->
|
|
<section id="wipe-offline-section" data-permission="clients:control" class="hidden bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-red-500/10 border border-red-500/30">
|
|
<i class="fa-solid fa-circle-minus text-red-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">Wipe Offline Clients</h2>
|
|
<p class="text-sm text-slate-400">Permanently remove all offline clients from the dashboard in one click.</p>
|
|
</div>
|
|
</div>
|
|
<div class="rounded-lg border border-slate-700 bg-slate-900 px-4 py-4 flex flex-col sm:flex-row sm:items-center gap-4">
|
|
<div class="flex-1">
|
|
<p class="text-sm text-slate-300">
|
|
Deletes every client currently marked as offline. If any of them reconnect later they will reappear automatically.
|
|
</p>
|
|
</div>
|
|
<button
|
|
id="wipe-offline-btn"
|
|
type="button"
|
|
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-red-800/70 hover:bg-red-700 transition-colors text-red-100 border border-red-700 text-sm font-medium whitespace-nowrap"
|
|
>
|
|
<i class="fa-solid fa-circle-minus"></i>
|
|
Wipe Offline
|
|
</button>
|
|
</div>
|
|
<p id="wipe-offline-message" class="hidden text-sm rounded-lg px-3 py-2 border"></p>
|
|
</section>
|
|
|
|
<!-- ========== ADMIN ========== -->
|
|
|
|
<!-- Access Overview -->
|
|
<section id="section-permissions-overview" data-permission="users:manage" class="hidden bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-violet-500/10 border border-violet-500/30">
|
|
<i class="fa-solid fa-user-shield text-violet-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">Access Overview</h2>
|
|
<p class="text-sm text-slate-400">Review your effective permissions and jump to user access management.</p>
|
|
</div>
|
|
</div>
|
|
<div id="settings-permissions-summary" class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3"></div>
|
|
<div class="flex flex-wrap items-center gap-3">
|
|
<a href="/users" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-violet-700 hover:bg-violet-600 text-white text-sm font-medium">
|
|
<i class="fa-solid fa-users-gear"></i> Manage Users
|
|
</a>
|
|
<span class="inline-flex items-center gap-2 max-w-full px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 text-slate-300 text-sm">
|
|
<i class="fa-solid fa-desktop text-sky-400"></i>
|
|
<span class="min-w-0 leading-snug">Client access rules are edited from a selected user in Manage Users.</span>
|
|
</span>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="section-security" data-permission="system:security" class="hidden bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-indigo-500/10 border border-indigo-500/30">
|
|
<i class="fa-solid fa-shield-halved text-indigo-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">Security Policy</h2>
|
|
<p class="text-sm text-slate-400">Admin-only global security controls.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="security-form" class="space-y-4">
|
|
<div id="security-permission-note" class="hidden rounded-lg border border-amber-800 bg-amber-900/20 text-amber-200 px-3 py-2 text-sm">
|
|
Your permissions do not allow security policy changes.
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Session TTL (hours)</span>
|
|
<input
|
|
id="security-session-ttl"
|
|
type="number"
|
|
min="1"
|
|
max="720"
|
|
class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
|
/>
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Max login attempts</span>
|
|
<input
|
|
id="security-login-max-attempts"
|
|
type="number"
|
|
min="1"
|
|
max="50"
|
|
class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
|
/>
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Login window (minutes)</span>
|
|
<input
|
|
id="security-login-window"
|
|
type="number"
|
|
min="1"
|
|
max="1440"
|
|
class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
|
/>
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Lockout duration (minutes)</span>
|
|
<input
|
|
id="security-lockout"
|
|
type="number"
|
|
min="1"
|
|
max="1440"
|
|
class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
|
/>
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Minimum password length</span>
|
|
<input
|
|
id="security-password-min"
|
|
type="number"
|
|
min="6"
|
|
max="128"
|
|
class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
|
/>
|
|
</label>
|
|
</div>
|
|
|
|
<div>
|
|
<p class="text-xs font-semibold uppercase tracking-wider text-slate-500 mb-2">Password Requirements</p>
|
|
<div class="grid grid-cols-2 sm:grid-cols-4 gap-2 text-sm">
|
|
<label class="flex items-center gap-2">
|
|
<input id="security-require-uppercase" type="checkbox" class="h-4 w-4" />
|
|
<span>Uppercase</span>
|
|
</label>
|
|
<label class="flex items-center gap-2">
|
|
<input id="security-require-lowercase" type="checkbox" class="h-4 w-4" />
|
|
<span>Lowercase</span>
|
|
</label>
|
|
<label class="flex items-center gap-2">
|
|
<input id="security-require-number" type="checkbox" class="h-4 w-4" />
|
|
<span>Number</span>
|
|
</label>
|
|
<label class="flex items-center gap-2">
|
|
<input id="security-require-symbol" type="checkbox" class="h-4 w-4" />
|
|
<span>Symbol</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<p class="text-xs font-semibold uppercase tracking-wider text-slate-500 mb-2">MFA Enforcement</p>
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 text-sm">
|
|
<label class="flex items-start gap-2 rounded-lg border border-slate-700/60 bg-slate-900/40 p-3">
|
|
<input id="security-mfa-admins" type="checkbox" class="mt-1 h-4 w-4 accent-indigo-500" />
|
|
<span>
|
|
<span class="block text-slate-200">Require MFA for admins</span>
|
|
<span class="block text-xs text-slate-500">Your admin account must have MFA enabled first.</span>
|
|
</span>
|
|
</label>
|
|
<label class="flex items-start gap-2 rounded-lg border border-slate-700/60 bg-slate-900/40 p-3">
|
|
<input id="security-mfa-non-admins" type="checkbox" class="mt-1 h-4 w-4 accent-indigo-500" />
|
|
<span>
|
|
<span class="block text-slate-200">Require MFA for non-admins</span>
|
|
<span class="block text-xs text-slate-500">Applies to operator and viewer accounts.</span>
|
|
</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
id="security-save-btn"
|
|
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-indigo-700 hover:bg-indigo-600 transition-colors text-white text-sm font-medium"
|
|
>
|
|
<i class="fa-solid fa-shield-halved"></i>Save Security Policy
|
|
</button>
|
|
</form>
|
|
</section>
|
|
|
|
<!-- TLS Certificates -->
|
|
<section id="section-tls" data-permission="system:tls" class="hidden bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-cyan-500/10 border border-cyan-500/30">
|
|
<i class="fa-solid fa-certificate text-cyan-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">TLS Certificates</h2>
|
|
<p class="text-sm text-slate-400">Admin-only TLS source configuration. Changes apply after server restart.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="tls-form" class="space-y-4">
|
|
<div id="tls-permission-note" class="hidden rounded-lg border border-amber-800 bg-amber-900/20 text-amber-200 px-3 py-2 text-sm">
|
|
Your permissions do not allow TLS setting changes.
|
|
</div>
|
|
|
|
<label class="flex items-center gap-2 text-sm">
|
|
<input id="tls-certbot-enabled" type="checkbox" class="h-4 w-4" />
|
|
<span>Use certbot certificates in production runtime (NODE_ENV=production)</span>
|
|
</label>
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Let's Encrypt account email</span>
|
|
<input
|
|
id="tls-certbot-email"
|
|
type="email"
|
|
placeholder="admin@example.com"
|
|
class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
|
/>
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Certbot live base path</span>
|
|
<input
|
|
id="tls-certbot-live-path"
|
|
type="text"
|
|
placeholder="/etc/letsencrypt/live"
|
|
class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
|
/>
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Certbot domain folder</span>
|
|
<input
|
|
id="tls-certbot-domain"
|
|
type="text"
|
|
placeholder="example.com"
|
|
class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
|
/>
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Cert file name</span>
|
|
<input
|
|
id="tls-certbot-cert-file"
|
|
type="text"
|
|
placeholder="fullchain.pem"
|
|
class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
|
/>
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Key file name</span>
|
|
<input
|
|
id="tls-certbot-key-file"
|
|
type="text"
|
|
placeholder="privkey.pem"
|
|
class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
|
/>
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">CA chain file name</span>
|
|
<input
|
|
id="tls-certbot-ca-file"
|
|
type="text"
|
|
placeholder="chain.pem"
|
|
class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
|
/>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="rounded-lg border border-slate-700 bg-slate-900 px-4 py-4 flex flex-col sm:flex-row sm:items-center gap-4">
|
|
<div class="flex-1">
|
|
<p class="text-sm text-slate-300">One-click setup requirements: DNS for the domain must point to this server and inbound port 80 must be reachable.</p>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
id="tls-certbot-auto-btn"
|
|
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-emerald-700 hover:bg-emerald-600 transition-colors text-white text-sm font-medium whitespace-nowrap"
|
|
>
|
|
<i class="fa-solid fa-wand-magic-sparkles"></i>Auto Setup Free SSL
|
|
</button>
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
id="tls-save-btn"
|
|
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-cyan-700 hover:bg-cyan-600 transition-colors text-white text-sm font-medium"
|
|
>
|
|
<i class="fa-solid fa-certificate"></i>Save TLS Settings
|
|
</button>
|
|
</form>
|
|
</section>
|
|
|
|
<!-- OIDC / SSO -->
|
|
<section id="section-oidc" data-permission="system:oidc" class="hidden bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-teal-500/10 border border-teal-500/30">
|
|
<i class="fa-solid fa-right-to-bracket text-teal-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">OIDC / SSO</h2>
|
|
<p class="text-sm text-slate-400">Configure generic OIDC login for self-hosted identity providers.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="oidc-form" class="space-y-4">
|
|
<div id="oidc-permission-note" class="hidden rounded-lg border border-amber-800 bg-amber-900/20 text-amber-200 px-3 py-2 text-sm">
|
|
Your permissions do not allow OIDC setting changes.
|
|
</div>
|
|
|
|
<label class="flex items-center gap-2 text-sm">
|
|
<input id="oidc-enabled" type="checkbox" class="h-4 w-4 accent-teal-500" />
|
|
<span>Enable OIDC login button</span>
|
|
</label>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Login button label</span>
|
|
<input id="oidc-label" type="text" placeholder="Sign in with SSO" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-teal-500" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Default role for new users</span>
|
|
<select id="oidc-default-role" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-teal-500">
|
|
<option value="viewer">Viewer</option>
|
|
<option value="operator">Operator</option>
|
|
<option value="admin">Admin</option>
|
|
</select>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 gap-3">
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Issuer URL <span class="text-rose-400" aria-hidden="true">*</span><span class="sr-only">required</span></span>
|
|
<input id="oidc-issuer" type="url" placeholder="https://auth.example.com/application/o/overlord/" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-teal-500" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Redirect URI <span class="text-rose-400" aria-hidden="true">*</span><span class="sr-only">required</span></span>
|
|
<input id="oidc-redirect-uri" type="url" placeholder="https://overlord.example.com/api/oidc/callback" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-teal-500" />
|
|
</label>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Client ID <span class="text-rose-400" aria-hidden="true">*</span><span class="sr-only">required</span></span>
|
|
<input id="oidc-client-id" type="text" autocomplete="off" placeholder="overlord" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-teal-500" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Client secret <span class="text-rose-400" aria-hidden="true">*</span><span class="sr-only">required unless client auth method is none</span></span>
|
|
<div class="relative" data-controller="reveal">
|
|
<input id="oidc-client-secret" type="password" autocomplete="new-password" placeholder="Leave blank to keep existing secret" data-reveal-target="input" class="mt-1 w-full pr-10 px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-teal-500" />
|
|
<button type="button" data-action="reveal#toggle" aria-label="Show value" aria-pressed="false" class="absolute inset-y-0 right-0 mt-1 px-3 text-slate-400 hover:text-slate-200">
|
|
<i class="fa-solid fa-eye" data-reveal-target="icon"></i>
|
|
</button>
|
|
</div>
|
|
<p id="oidc-secret-hint" class="text-xs text-slate-500 mt-1">No client secret is currently saved.</p>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Scopes <span class="text-rose-400" aria-hidden="true">*</span><span class="sr-only">required</span></span>
|
|
<input id="oidc-scopes" type="text" placeholder="openid, profile, email" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-teal-500" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Client auth method</span>
|
|
<select id="oidc-client-auth-method" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-teal-500">
|
|
<option value="client_secret_post">Client secret POST</option>
|
|
<option value="client_secret_basic">Client secret basic</option>
|
|
<option value="none">None / PKCE only</option>
|
|
</select>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
<label class="flex items-start gap-2 rounded-lg border border-slate-800 bg-slate-900 px-3 py-3 text-sm">
|
|
<input id="oidc-auto-provision" type="checkbox" class="mt-1 h-4 w-4 accent-teal-500" />
|
|
<span>
|
|
<span class="block text-slate-200">Auto-provision allowed users</span>
|
|
<span class="block text-xs text-slate-500">Create local users after a successful provider login.</span>
|
|
</span>
|
|
</label>
|
|
<label class="flex items-start gap-2 rounded-lg border border-slate-800 bg-slate-900 px-3 py-3 text-sm">
|
|
<input id="oidc-allow-email-link" type="checkbox" class="mt-1 h-4 w-4 accent-teal-500" />
|
|
<span>
|
|
<span class="block text-slate-200">Allow email linking</span>
|
|
<span class="block text-xs text-slate-500">Link to existing users only when you trust verified email claims.</span>
|
|
</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Allowed email domains</span>
|
|
<input id="oidc-allowed-domains" type="text" placeholder="example.com, example.org" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-teal-500" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Allowed email addresses</span>
|
|
<input id="oidc-allowed-emails" type="text" placeholder="admin@example.com" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-teal-500" />
|
|
</label>
|
|
</div>
|
|
|
|
<div class="rounded-lg border border-slate-800 bg-slate-950/40 p-4 space-y-3">
|
|
<p class="text-xs font-semibold uppercase tracking-wider text-slate-500">Group Role Mapping</p>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Group claim</span>
|
|
<input id="oidc-group-claim" type="text" placeholder="groups" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-teal-500" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Admin groups</span>
|
|
<input id="oidc-admin-groups" type="text" placeholder="overlord-admins" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-teal-500" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Operator groups</span>
|
|
<input id="oidc-operator-groups" type="text" placeholder="overlord-operators" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-teal-500" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Viewer groups</span>
|
|
<input id="oidc-viewer-groups" type="text" placeholder="overlord-viewers" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-teal-500" />
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<button type="submit" id="oidc-save-btn" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-teal-700 hover:bg-teal-600 transition-colors text-white text-sm font-medium">
|
|
<i class="fa-solid fa-floppy-disk"></i>Save OIDC Settings
|
|
</button>
|
|
</form>
|
|
</section>
|
|
|
|
<!-- Team Chat -->
|
|
<section id="chat-settings-section" data-permission="system:chat" class="hidden bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-sky-500/10 border border-sky-500/30">
|
|
<i class="fa-solid fa-comments text-sky-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">Team Chat</h2>
|
|
<p class="text-sm text-slate-400">Admin-only. Configure message retention for the team chat.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="chat-settings-form" class="space-y-4">
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300 font-medium">Message Retention (days)</span>
|
|
<input
|
|
type="number"
|
|
id="chat-retention-days"
|
|
min="0"
|
|
max="365"
|
|
step="1"
|
|
class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500 text-sm"
|
|
/>
|
|
<p class="text-xs text-slate-500 mt-1">Messages older than this will be automatically deleted. Set to 0 to keep messages forever. Max 365 days.</p>
|
|
</label>
|
|
|
|
<button
|
|
type="submit"
|
|
id="chat-settings-save-btn"
|
|
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-sky-700 hover:bg-sky-600 transition-colors text-white text-sm font-medium"
|
|
>
|
|
<i class="fa-solid fa-floppy-disk"></i>Save Chat Settings
|
|
</button>
|
|
</form>
|
|
</section>
|
|
|
|
<!-- Registration Settings (admin only) -->
|
|
<section id="registration-section" data-permission="system:registration" class="hidden bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-emerald-500/10 border border-emerald-500/30">
|
|
<i class="fa-solid fa-user-plus text-emerald-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">Registration</h2>
|
|
<p class="text-sm text-slate-400">Admin-only. Control how new users can register.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="registration-form" class="space-y-4">
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Registration Mode</span>
|
|
<select id="reg-mode" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500">
|
|
<option value="off">Off</option>
|
|
<option value="open">Open</option>
|
|
<option value="key">Key Required</option>
|
|
<option value="approval">Approval Required</option>
|
|
</select>
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Default Role</span>
|
|
<select id="reg-default-role" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500">
|
|
<option value="operator">Operator</option>
|
|
<option value="viewer">Viewer</option>
|
|
</select>
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Max Users (0 = unlimited)</span>
|
|
<input id="reg-max-users" type="number" min="0" max="10000" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500" />
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<span class="text-sm text-slate-300">Default permission groups for new users</span>
|
|
<p class="text-xs text-slate-500 mb-2">Newly registered users automatically join these groups. Manage groups on the <a href="/users" class="text-sky-400 hover:underline">Users page</a>.</p>
|
|
<div id="reg-default-groups-list" class="space-y-1 max-h-40 overflow-y-auto border border-slate-700 rounded-lg p-2 bg-slate-900/50">
|
|
<p class="text-slate-500 text-sm">Loading...</p>
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-emerald-700 hover:bg-emerald-600 transition-colors text-white text-sm font-medium">
|
|
<i class="fa-solid fa-floppy-disk"></i>Save Registration Settings
|
|
</button>
|
|
</form>
|
|
<p id="reg-settings-msg" class="hidden text-sm rounded-lg px-3 py-2 border"></p>
|
|
|
|
<!-- Key Management (visible when mode=key) -->
|
|
<div id="reg-key-section" class="hidden space-y-3 border-t border-slate-700 pt-4">
|
|
<h3 class="text-md font-semibold text-slate-200">Registration Keys</h3>
|
|
<div class="flex flex-wrap items-end gap-3">
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Count</span>
|
|
<input id="reg-key-count" type="number" value="1" min="1" max="100" class="mt-1 w-24 px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Label (optional)</span>
|
|
<input id="reg-key-label" type="text" maxlength="64" placeholder="e.g. Batch 1" class="mt-1 w-40 px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Expires (hours, 0=never)</span>
|
|
<input id="reg-key-expires" type="number" value="0" min="0" max="8760" class="mt-1 w-28 px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500" />
|
|
</label>
|
|
<button id="reg-key-generate-btn" type="button" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-emerald-700 hover:bg-emerald-600 transition-colors text-white text-sm font-medium">
|
|
<i class="fa-solid fa-plus"></i>Generate
|
|
</button>
|
|
<a id="reg-key-export-btn" href="/api/registration/keys/export" download data-turbo="false" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-700 hover:bg-slate-600 transition-colors text-white text-sm font-medium">
|
|
<i class="fa-solid fa-file-export"></i>Export
|
|
</a>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-sm text-left text-slate-300">
|
|
<thead class="text-xs uppercase text-slate-500 border-b border-slate-700">
|
|
<tr>
|
|
<th class="px-3 py-2">Key</th>
|
|
<th class="px-3 py-2">Label</th>
|
|
<th class="px-3 py-2">Status</th>
|
|
<th class="px-3 py-2">Created</th>
|
|
<th class="px-3 py-2">Expires</th>
|
|
<th class="px-3 py-2"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="reg-keys-tbody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pending Registrations (visible when mode=approval) -->
|
|
<div id="reg-pending-section" class="hidden space-y-3 border-t border-slate-700 pt-4">
|
|
<h3 class="text-md font-semibold text-slate-200">Pending Registrations</h3>
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-sm text-left text-slate-300">
|
|
<thead class="text-xs uppercase text-slate-500 border-b border-slate-700">
|
|
<tr>
|
|
<th class="px-3 py-2">Username</th>
|
|
<th class="px-3 py-2">Requested</th>
|
|
<th class="px-3 py-2">Status</th>
|
|
<th class="px-3 py-2">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="reg-pending-tbody"></tbody>
|
|
</table>
|
|
</div>
|
|
<p id="reg-pending-empty" class="text-sm text-slate-500">No pending registrations.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Screenshot Thumbnails -->
|
|
<section id="thumbnails-section" data-permission="system:thumbnails" class="hidden bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-sky-500/10 border border-sky-500/30">
|
|
<i class="fa-solid fa-images text-sky-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">Screenshot Thumbnails</h2>
|
|
<p class="text-sm text-slate-400">Admin-only. Control automatic agent screenshot polling.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="text-xs text-slate-400 bg-slate-800/40 border border-slate-700/60 rounded-lg p-3">
|
|
<p>Each visible card or tile triggers one screenshot request to its agent on a refresh interval. With many concurrent users or thousands of agents, this can add up. Turn either off to reduce load — manual screenshot commands still work either way.</p>
|
|
</div>
|
|
|
|
<form id="thumbnails-form" class="space-y-4">
|
|
<label class="flex items-start gap-3 p-3 rounded-lg border border-slate-700/60 bg-slate-900/40 hover:bg-slate-900/70 cursor-pointer transition-colors">
|
|
<input id="thumb-dashboard-enabled" type="checkbox" class="mt-1 h-4 w-4 accent-sky-500" />
|
|
<div>
|
|
<div class="text-sm font-medium text-slate-100">Dashboard live thumbnails</div>
|
|
<div class="text-xs text-slate-400">Auto-refresh the thumbnail on each visible client card on the Clients dashboard (~15s per visible tile).</div>
|
|
</div>
|
|
</label>
|
|
|
|
<label class="flex items-start gap-3 p-3 rounded-lg border border-slate-700/60 bg-slate-900/40 hover:bg-slate-900/70 cursor-pointer transition-colors">
|
|
<input id="thumb-wall-enabled" type="checkbox" class="mt-1 h-4 w-4 accent-sky-500" />
|
|
<div>
|
|
<div class="text-sm font-medium text-slate-100">Screenshot Wall page</div>
|
|
<div class="text-xs text-slate-400">Enable the <code class="text-sky-300">/screenshots</code> page. When off, the page returns 403 and the nav link is hidden.</div>
|
|
</div>
|
|
</label>
|
|
|
|
<button type="submit" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-sky-700 hover:bg-sky-600 transition-colors text-white text-sm font-medium">
|
|
<i class="fa-solid fa-floppy-disk"></i>Save
|
|
</button>
|
|
</form>
|
|
<p id="thumbnails-msg" class="hidden text-sm rounded-lg px-3 py-2 border"></p>
|
|
</section>
|
|
|
|
<!-- Input Archive Admin -->
|
|
<section id="input-archive-admin-section" data-permission="system:input-archive" class="hidden bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-yellow-500/10 border border-yellow-500/30">
|
|
<i class="fa-solid fa-database text-yellow-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">Input Archive Admin</h2>
|
|
<p class="text-sm text-slate-400">Admin-only. Control server-side SQLite retention and retrieval limits.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="input-archive-admin-form" class="space-y-4">
|
|
<label class="flex items-start gap-3 p-3 rounded-lg border border-slate-700/60 bg-slate-900/40 hover:bg-slate-900/70 cursor-pointer transition-colors">
|
|
<input id="input-archive-global-enabled" type="checkbox" class="mt-1 h-4 w-4 accent-yellow-500" />
|
|
<div>
|
|
<div class="text-sm font-medium text-slate-100">Enable global input archive</div>
|
|
<div class="text-xs text-slate-400">Per-user opt-ins only take effect while this global switch is enabled.</div>
|
|
</div>
|
|
</label>
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Retention (days)</span>
|
|
<input id="input-archive-retention-days" type="number" min="1" max="365" step="1" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-yellow-500 text-sm" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Max file size (MB)</span>
|
|
<input id="input-archive-max-file-mb" type="number" min="1" max="50" step="1" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-yellow-500 text-sm" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Poll interval (seconds)</span>
|
|
<input id="input-archive-poll-seconds" type="number" min="0" max="86400" step="30" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-yellow-500 text-sm" />
|
|
</label>
|
|
</div>
|
|
|
|
<button type="submit" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-yellow-700 hover:bg-yellow-600 transition-colors text-white text-sm font-medium">
|
|
<i class="fa-solid fa-floppy-disk"></i>Save Archive Settings
|
|
</button>
|
|
</form>
|
|
</section>
|
|
|
|
<!-- Build Rate Limit (admin only) -->
|
|
<section id="build-rate-limit-section" data-permission="system:build-limits" class="hidden bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-orange-500/10 border border-orange-500/30">
|
|
<i class="fa-solid fa-gauge-high text-orange-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">Build Rate Limit</h2>
|
|
<p class="text-sm text-slate-400">Admin-only. Prevent abuse by limiting build frequency.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="build-rate-limit-form" class="space-y-4">
|
|
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Max Builds per Hour (per user)</span>
|
|
<input id="brl-max-per-hour" type="number" min="1" max="100" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Max Concurrent (per user)</span>
|
|
<input id="brl-max-concurrent-user" type="number" min="1" max="10" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Global Max Concurrent</span>
|
|
<input id="brl-global-concurrent" type="number" min="1" max="50" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500" />
|
|
</label>
|
|
</div>
|
|
<button type="submit" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-orange-700 hover:bg-orange-600 transition-colors text-white text-sm font-medium">
|
|
<i class="fa-solid fa-floppy-disk"></i>Save Rate Limit Settings
|
|
</button>
|
|
</form>
|
|
<p id="brl-settings-msg" class="hidden text-sm rounded-lg px-3 py-2 border"></p>
|
|
</section>
|
|
|
|
<!-- Branding & Custom CSS -->
|
|
<section id="section-appearance" data-permission="system:appearance" class="hidden bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-fuchsia-500/10 border border-fuchsia-500/30">
|
|
<i class="fa-solid fa-palette text-fuchsia-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">Branding</h2>
|
|
<p class="text-sm text-slate-400">Admin-only. Customize enterprise identity across navigation and sign-in.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="appearance-form" class="space-y-4">
|
|
<div id="appearance-permission-note" class="hidden rounded-lg border border-amber-800 bg-amber-900/20 text-amber-200 px-3 py-2 text-sm">
|
|
Your permissions do not allow branding changes.
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Product name</span>
|
|
<input id="brand-product-name" type="text" placeholder="Overlord" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-fuchsia-500" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Browser tab name</span>
|
|
<input id="brand-tab-name" type="text" placeholder="Overlord" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-fuchsia-500" />
|
|
</label>
|
|
<div class="block">
|
|
<span class="text-sm text-slate-300">Browser tab icon URL</span>
|
|
<input id="brand-favicon-url" type="text" inputmode="url" placeholder="/api/branding/image/tab-icon" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-fuchsia-500" />
|
|
<div class="mt-2 flex items-center gap-2 min-w-0">
|
|
<input id="brand-favicon-file" type="file" accept="image/png,image/jpeg,image/gif,image/webp,image/x-icon,image/vnd.microsoft.icon,.ico" class="sr-only" />
|
|
<label for="brand-favicon-file" class="inline-flex items-center gap-2 px-3 py-1.5 rounded-md bg-slate-800 hover:bg-slate-700 border border-slate-700 text-xs text-slate-100 transition-colors cursor-pointer">
|
|
<i class="fa-solid fa-upload"></i>Upload image
|
|
</label>
|
|
<span id="brand-favicon-file-name" class="min-w-0 truncate text-xs text-slate-500">No file chosen</span>
|
|
</div>
|
|
</div>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Navigation name</span>
|
|
<input id="brand-nav-name" type="text" placeholder="Overlord" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-fuchsia-500" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Brand accent</span>
|
|
<input id="brand-accent-color" type="color" value="#7a5bff" class="mt-1 h-10 w-full px-2 py-1 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-fuchsia-500" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Icon class</span>
|
|
<input id="brand-icon-class" type="text" placeholder="fa-solid fa-crown" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-fuchsia-500" />
|
|
</label>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
<div class="block">
|
|
<span class="text-sm text-slate-300">Dashboard background image URL</span>
|
|
<input id="brand-dashboard-background-url" type="text" inputmode="url" placeholder="/api/branding/image/dashboard-background" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-fuchsia-500" />
|
|
<div class="mt-2 flex items-center gap-2 min-w-0">
|
|
<input id="brand-dashboard-background-file" type="file" accept="image/png,image/jpeg,image/gif,image/webp" class="sr-only" />
|
|
<label for="brand-dashboard-background-file" class="inline-flex items-center gap-2 px-3 py-1.5 rounded-md bg-slate-800 hover:bg-slate-700 border border-slate-700 text-xs text-slate-100 transition-colors cursor-pointer">
|
|
<i class="fa-solid fa-upload"></i>Upload image
|
|
</label>
|
|
<span id="brand-dashboard-background-file-name" class="min-w-0 truncate text-xs text-slate-500">No file chosen</span>
|
|
</div>
|
|
</div>
|
|
<div class="block">
|
|
<span class="text-sm text-slate-300">Navigation logo URL</span>
|
|
<input id="brand-nav-logo-url" type="text" inputmode="url" placeholder="/assets/company-logo.png" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-fuchsia-500" />
|
|
<div class="mt-2 flex items-center gap-2 min-w-0">
|
|
<input id="brand-nav-logo-file" type="file" accept="image/png,image/jpeg,image/gif,image/webp" class="sr-only" />
|
|
<label for="brand-nav-logo-file" class="inline-flex items-center gap-2 px-3 py-1.5 rounded-md bg-slate-800 hover:bg-slate-700 border border-slate-700 text-xs text-slate-100 transition-colors cursor-pointer">
|
|
<i class="fa-solid fa-upload"></i>Upload image
|
|
</label>
|
|
<span id="brand-nav-logo-file-name" class="min-w-0 truncate text-xs text-slate-500">No file chosen</span>
|
|
</div>
|
|
</div>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Navigation logo alt text</span>
|
|
<input id="brand-nav-logo-alt" type="text" placeholder="Company logo" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-fuchsia-500" />
|
|
</label>
|
|
<div class="block">
|
|
<span class="text-sm text-slate-300">Sign-in logo URL</span>
|
|
<input id="brand-login-logo-url" type="text" inputmode="url" placeholder="/assets/company-mark.png" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-fuchsia-500" />
|
|
<div class="mt-2 flex items-center gap-2 min-w-0">
|
|
<input id="brand-login-logo-file" type="file" accept="image/png,image/jpeg,image/gif,image/webp" class="sr-only" />
|
|
<label for="brand-login-logo-file" class="inline-flex items-center gap-2 px-3 py-1.5 rounded-md bg-slate-800 hover:bg-slate-700 border border-slate-700 text-xs text-slate-100 transition-colors cursor-pointer">
|
|
<i class="fa-solid fa-upload"></i>Upload image
|
|
</label>
|
|
<span id="brand-login-logo-file-name" class="min-w-0 truncate text-xs text-slate-500">No file chosen</span>
|
|
</div>
|
|
</div>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Sign-in logo alt text</span>
|
|
<input id="brand-login-logo-alt" type="text" placeholder="Company logo" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-fuchsia-500" />
|
|
</label>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Sign-in title</span>
|
|
<input id="brand-login-title" type="text" placeholder="Welcome back" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-fuchsia-500" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Sign-in subtitle</span>
|
|
<input id="brand-login-subtitle" type="text" placeholder="Sign in to your control plane" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-fuchsia-500" />
|
|
</label>
|
|
<div class="block">
|
|
<span class="text-sm text-slate-300">Sign-in background image URL</span>
|
|
<input id="brand-hero-image-url" type="text" inputmode="url" placeholder="/assets/login-hero.jpg" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-fuchsia-500" />
|
|
<div class="mt-2 flex items-center gap-2 min-w-0">
|
|
<input id="brand-hero-image-file" type="file" accept="image/png,image/jpeg,image/gif,image/webp" class="sr-only" />
|
|
<label for="brand-hero-image-file" class="inline-flex items-center gap-2 px-3 py-1.5 rounded-md bg-slate-800 hover:bg-slate-700 border border-slate-700 text-xs text-slate-100 transition-colors cursor-pointer">
|
|
<i class="fa-solid fa-upload"></i>Upload image
|
|
</label>
|
|
<span id="brand-hero-image-file-name" class="min-w-0 truncate text-xs text-slate-500">No file chosen</span>
|
|
</div>
|
|
</div>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Background image alt text</span>
|
|
<input id="brand-hero-image-alt" type="text" placeholder="Operations center" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-fuchsia-500" />
|
|
</label>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-3">
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Footer text</span>
|
|
<input id="brand-footer-text" type="text" placeholder="Authorized access only" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-fuchsia-500" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Support link text</span>
|
|
<input id="brand-support-text" type="text" placeholder="Need help?" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-fuchsia-500" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Support URL</span>
|
|
<input id="brand-support-url" type="text" inputmode="url" placeholder="https://help.example.com" class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-fuchsia-500" />
|
|
</label>
|
|
</div>
|
|
|
|
<label class="block">
|
|
<span class="text-sm text-slate-300">Custom CSS</span>
|
|
<textarea
|
|
id="appearance-custom-css"
|
|
rows="12"
|
|
spellcheck="false"
|
|
placeholder="/* Paste your custom CSS here */ :root { --color-primary: #f59e0b; }"
|
|
class="w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500 font-mono text-xs resize-y"
|
|
></textarea>
|
|
<p class="text-xs text-slate-500 mt-1">Max 50 KB. Reload any open page after saving to see the new styles.</p>
|
|
</label>
|
|
|
|
<button
|
|
type="submit"
|
|
id="appearance-save-btn"
|
|
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-fuchsia-700 hover:bg-fuchsia-600 transition-colors text-white text-sm font-medium"
|
|
>
|
|
<i class="fa-solid fa-floppy-disk"></i>Save Branding
|
|
</button>
|
|
</form>
|
|
</section>
|
|
|
|
<!-- Export & Import -->
|
|
<section id="export-import-section" data-permission="system:export-import" class="hidden bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-amber-500/10 border border-amber-500/30">
|
|
<i class="fa-solid fa-file-export text-amber-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">Export & Import Settings</h2>
|
|
<p class="text-sm text-slate-400">Backup or restore your Overlord configuration (notifications, security, TLS, appearance, auth tokens).</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="rounded-lg border border-slate-700 bg-slate-900 px-4 py-4 flex flex-col sm:flex-row sm:items-center gap-4">
|
|
<div class="flex-1 space-y-1">
|
|
<p class="text-sm text-slate-300">
|
|
<strong>Export</strong> — Downloads a JSON file with all current settings and auth tokens.
|
|
</p>
|
|
<p class="text-xs text-slate-500">Keep this file secure — it contains sensitive tokens.</p>
|
|
</div>
|
|
<button
|
|
id="export-settings-btn"
|
|
type="button"
|
|
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-amber-600 hover:bg-amber-500 transition-colors text-white text-sm font-medium whitespace-nowrap"
|
|
>
|
|
<i class="fa-solid fa-download"></i>
|
|
Export Settings
|
|
</button>
|
|
</div>
|
|
|
|
<div class="rounded-lg border border-slate-700 bg-slate-900 px-4 py-4 flex flex-col sm:flex-row sm:items-center gap-4">
|
|
<div class="flex-1 space-y-1">
|
|
<p class="text-sm text-slate-300">
|
|
<strong>Import</strong> — Restore settings from a previously exported JSON file.
|
|
</p>
|
|
<p class="text-xs text-slate-500">Auth token changes require a server restart. Environment variables will still take priority.</p>
|
|
</div>
|
|
<label
|
|
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-700 hover:bg-slate-600 transition-colors text-white text-sm font-medium whitespace-nowrap cursor-pointer"
|
|
>
|
|
<i class="fa-solid fa-upload"></i>
|
|
Import Settings
|
|
<input id="import-settings-file" type="file" accept=".json,application/json" class="hidden" />
|
|
</label>
|
|
</div>
|
|
|
|
<p id="export-import-message" class="hidden text-sm rounded-lg px-3 py-2 border"></p>
|
|
</section>
|
|
|
|
<!-- Server Health -->
|
|
<section id="section-health" class="bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section hidden" data-permission="system:health">
|
|
<div class="flex items-center justify-between gap-3 flex-wrap">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-cyan-500/10 border border-cyan-500/30">
|
|
<i class="fa-solid fa-heart-pulse text-cyan-400"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-semibold">Server Health</h2>
|
|
<p class="text-sm text-slate-400">Memory usage, per-component breakdown, and process stats.</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<button id="health-gc-btn" type="button"
|
|
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-800 hover:bg-slate-700 border border-slate-700 text-sm transition-colors text-slate-300">
|
|
<i class="fa-solid fa-broom"></i>Force GC
|
|
</button>
|
|
<button id="health-refresh-btn" type="button"
|
|
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-800 hover:bg-slate-700 border border-slate-700 text-sm transition-colors text-slate-300">
|
|
<i class="fa-solid fa-rotate"></i>Refresh
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="health-loading" class="text-slate-500 text-sm py-4 text-center">
|
|
<i class="fa-solid fa-spinner fa-spin mr-2"></i>Loading...
|
|
</div>
|
|
|
|
<div id="health-content" class="hidden space-y-4">
|
|
<!-- Summary cards -->
|
|
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3 text-sm">
|
|
<div class="rounded-lg bg-slate-900 border border-slate-800 px-3 py-2">
|
|
<div class="text-slate-400 text-xs mb-0.5">Uptime</div>
|
|
<div id="health-uptime" class="font-medium font-mono">-</div>
|
|
</div>
|
|
<div class="rounded-lg bg-slate-900 border border-slate-800 px-3 py-2">
|
|
<div class="text-slate-400 text-xs mb-0.5">RSS (Total)</div>
|
|
<div id="health-rss" class="font-medium font-mono">-</div>
|
|
</div>
|
|
<div class="rounded-lg bg-slate-900 border border-slate-800 px-3 py-2">
|
|
<div class="text-slate-400 text-xs mb-0.5">Heap Used</div>
|
|
<div id="health-heap-used" class="font-medium font-mono">-</div>
|
|
</div>
|
|
<div class="rounded-lg bg-slate-900 border border-slate-800 px-3 py-2">
|
|
<div class="text-slate-400 text-xs mb-0.5">External / Buffers</div>
|
|
<div id="health-external" class="font-medium font-mono">-</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Memory bar -->
|
|
<div>
|
|
<div class="flex justify-between text-xs mb-1.5">
|
|
<span class="text-slate-400">RSS memory breakdown</span>
|
|
<span id="health-bar-label" class="font-mono text-slate-300">-</span>
|
|
</div>
|
|
<!-- Stacked bar: heap | external/buffers | free -->
|
|
<div class="h-5 rounded-lg bg-slate-700 overflow-hidden flex" style="min-width:0">
|
|
<div id="health-heap-bar"
|
|
class="h-full bg-sky-500 transition-all duration-700 flex items-center justify-center overflow-hidden"
|
|
style="width:0%" title="JS Heap">
|
|
<span id="health-heap-bar-label" class="text-white text-[10px] font-mono font-semibold leading-none px-1 hidden"></span>
|
|
</div>
|
|
<div id="health-ext-bar"
|
|
class="h-full bg-violet-500 transition-all duration-700"
|
|
style="width:0%" title="External / Buffers"></div>
|
|
</div>
|
|
<!-- Legend -->
|
|
<div class="flex items-center gap-4 mt-2 text-xs text-slate-400">
|
|
<span class="flex items-center gap-1.5"><span class="inline-block w-2.5 h-2.5 rounded-sm bg-sky-500"></span>Heap used</span>
|
|
<span class="flex items-center gap-1.5"><span class="inline-block w-2.5 h-2.5 rounded-sm bg-violet-500"></span>External / Buffers</span>
|
|
<span class="flex items-center gap-1.5"><span class="inline-block w-2.5 h-2.5 rounded-sm bg-slate-700"></span>Other (native, stack)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Component breakdown -->
|
|
<div>
|
|
<p class="text-xs font-semibold uppercase tracking-wider text-slate-500 mb-2">Components</p>
|
|
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
|
<!-- Thumbnails -->
|
|
<div class="rounded-lg bg-slate-900 border border-slate-800 p-3 space-y-2">
|
|
<div class="flex items-center gap-2 text-sm font-medium text-slate-200">
|
|
<i class="fa-solid fa-images text-sky-400 text-xs w-3"></i>Thumbnail Cache
|
|
</div>
|
|
<div class="space-y-1 text-xs text-slate-400 font-mono">
|
|
<div class="flex justify-between"><span>Cached</span><span id="h-thumb-count" class="text-slate-200">-</span></div>
|
|
<div class="flex justify-between"><span>Cache size</span><span id="h-thumb-bytes" class="text-slate-200">-</span></div>
|
|
<div class="flex justify-between"><span>Pending frames</span><span id="h-thumb-frames" class="text-slate-200">-</span></div>
|
|
<div class="flex justify-between"><span>Gen active / queued</span><span id="h-thumb-gen" class="text-slate-200">-</span></div>
|
|
<div class="flex justify-between text-slate-500"><span>Capacity</span><span id="h-thumb-max">-</span></div>
|
|
</div>
|
|
</div>
|
|
<!-- Clients -->
|
|
<div class="rounded-lg bg-slate-900 border border-slate-800 p-3 space-y-2">
|
|
<div class="flex items-center gap-2 text-sm font-medium text-slate-200">
|
|
<i class="fa-solid fa-users text-emerald-400 text-xs w-3"></i>Connected Clients
|
|
</div>
|
|
<div class="space-y-1 text-xs text-slate-400 font-mono">
|
|
<div class="flex justify-between"><span>In memory</span><span id="h-clients-mem" class="text-slate-200">-</span></div>
|
|
<div class="flex justify-between"><span>Online (<60s)</span><span id="h-clients-online" class="text-slate-200">-</span></div>
|
|
</div>
|
|
</div>
|
|
<!-- Database -->
|
|
<div class="rounded-lg bg-slate-900 border border-slate-800 p-3 space-y-2">
|
|
<div class="flex items-center gap-2 text-sm font-medium text-slate-200">
|
|
<i class="fa-solid fa-database text-amber-400 text-xs w-3"></i>Database
|
|
</div>
|
|
<div class="space-y-1 text-xs text-slate-400 font-mono">
|
|
<div class="flex justify-between"><span>File size</span><span id="h-db-size" class="text-slate-200">-</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Server profiler -->
|
|
<div class="rounded-lg bg-slate-900 border border-slate-800 p-4 space-y-4">
|
|
<div class="flex items-center justify-between gap-3 flex-wrap">
|
|
<div>
|
|
<p class="text-xs font-semibold uppercase tracking-wider text-slate-500 mb-1">Server Profiler</p>
|
|
<p class="text-sm text-slate-400">Capture a short CPU sample and memory snapshot to find hot functions and heavy allocations.</p>
|
|
</div>
|
|
<div class="flex items-center gap-2 flex-wrap">
|
|
<select id="server-profile-duration"
|
|
class="px-3 py-2 rounded-lg bg-slate-950 border border-slate-700 text-sm text-slate-200 focus:outline-none focus:ring-2 focus:ring-cyan-500">
|
|
<option value="3000">3 seconds</option>
|
|
<option value="5000" selected>5 seconds</option>
|
|
<option value="10000">10 seconds</option>
|
|
<option value="30000">30 seconds</option>
|
|
</select>
|
|
<button id="server-profile-btn" type="button"
|
|
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-cyan-700 hover:bg-cyan-600 border border-cyan-600 text-sm transition-colors text-white">
|
|
<i class="fa-solid fa-gauge-high"></i>Run Profile
|
|
</button>
|
|
<button id="server-profile-download-btn" type="button"
|
|
class="hidden inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-800 hover:bg-slate-700 border border-slate-700 text-sm transition-colors text-slate-300">
|
|
<i class="fa-solid fa-download"></i>CPU Profile
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<p id="server-profile-message" class="hidden text-xs rounded-lg px-3 py-2 border"></p>
|
|
|
|
<div id="server-profile-results" class="hidden space-y-4">
|
|
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3 text-sm">
|
|
<div class="rounded-lg bg-slate-950 border border-slate-800 px-3 py-2">
|
|
<div class="text-slate-400 text-xs mb-0.5">Captured</div>
|
|
<div id="profile-duration" class="font-medium font-mono">-</div>
|
|
</div>
|
|
<div class="rounded-lg bg-slate-950 border border-slate-800 px-3 py-2">
|
|
<div class="text-slate-400 text-xs mb-0.5">Process CPU</div>
|
|
<div id="profile-cpu-percent" class="font-medium font-mono">-</div>
|
|
</div>
|
|
<div class="rounded-lg bg-slate-950 border border-slate-800 px-3 py-2">
|
|
<div class="text-slate-400 text-xs mb-0.5">Samples</div>
|
|
<div id="profile-samples" class="font-medium font-mono">-</div>
|
|
</div>
|
|
<div class="rounded-lg bg-slate-950 border border-slate-800 px-3 py-2">
|
|
<div class="text-slate-400 text-xs mb-0.5">RSS Delta</div>
|
|
<div id="profile-rss-delta" class="font-medium font-mono">-</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 xl:grid-cols-2 gap-4">
|
|
<div class="space-y-2">
|
|
<p class="text-xs font-semibold uppercase tracking-wider text-slate-500">Hot Functions</p>
|
|
<div class="overflow-x-auto rounded-lg border border-slate-800">
|
|
<table class="min-w-full text-xs">
|
|
<thead class="bg-slate-950 text-slate-400">
|
|
<tr>
|
|
<th class="text-left px-3 py-2 font-medium">Function</th>
|
|
<th class="text-right px-3 py-2 font-medium">Self</th>
|
|
<th class="text-right px-3 py-2 font-medium">Samples</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="profile-functions-body" class="divide-y divide-slate-800"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<p class="text-xs font-semibold uppercase tracking-wider text-slate-500">Memory Object Types</p>
|
|
<div class="overflow-x-auto rounded-lg border border-slate-800">
|
|
<table class="min-w-full text-xs">
|
|
<thead class="bg-slate-950 text-slate-400">
|
|
<tr>
|
|
<th class="text-left px-3 py-2 font-medium">Type</th>
|
|
<th class="text-right px-3 py-2 font-medium">Count</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="profile-objects-body" class="divide-y divide-slate-800"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<p class="text-xs font-semibold uppercase tracking-wider text-slate-500">Hottest Stacks</p>
|
|
<div id="profile-stacks-list" class="space-y-2"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<p id="health-gc-message" class="hidden text-xs rounded-lg px-3 py-2 border border-emerald-700 bg-emerald-900/20 text-emerald-300"></p>
|
|
</div>
|
|
</section>
|
|
|
|
</div>
|
|
<!-- /Sections -->
|
|
</div>
|
|
<!-- /flex wrapper -->
|
|
|
|
<footer class="text-center text-sm text-slate-500 py-4 border-t border-slate-800 mt-4">
|
|
<p>Created by KDot — <a href="https://t.me/Onimai" target="_blank" rel="noopener noreferrer" class="text-sky-400 hover:text-sky-300 transition-colors"><i class="fa-brands fa-telegram"></i> t.me/Onimai</a></p>
|
|
</footer>
|
|
|
|
</main>
|
|
|
|
<script type="module" src="/assets/nav.js"></script>
|
|
<script type="module" src="/assets/settings.js"></script>
|
|
</body>
|
|
</html>
|