dark mode, logging fixes to avoid agent traffic bloat

This commit is contained in:
boris committed 2026-04-16 20:10:23 +01:00
1 parent d97b1661c1
commit f7cf22f970
10 files changed
+488 -36

No files matched your search

+14 -2
View File
@@ -1,10 +1,15 @@
from django.contrib import admin
from django.utils.html import format_html
try:
from unfold.admin import ModelAdmin as UnfoldModelAdmin
except ImportError:
UnfoldModelAdmin = admin.ModelAdmin
try:
from unfold.contrib.guardian.admin import GuardedModelAdmin
except ImportError: # Fallback for older Unfold builds without guardian admin shim.
from guardian.admin import GuardedModelAdmin as GuardianGuardedModelAdmin
from unfold.admin import ModelAdmin as UnfoldModelAdmin
class GuardedModelAdmin(GuardianGuardedModelAdmin, UnfoldModelAdmin):
pass
@@ -65,13 +70,20 @@ class ServerAdmin(GuardedModelAdmin):
@admin.register(EnrollmentToken)
class EnrollmentTokenAdmin(admin.ModelAdmin):
class EnrollmentTokenAdmin(UnfoldModelAdmin):
list_display = ("token", "created_at", "expires_at", "used_at", "server")
list_filter = ("created_at", "used_at")
search_fields = ("token", "server__display_name", "server__hostname")
readonly_fields = ("token", "created_at", "used_at", "server", "created_by")
fields = ("token", "expires_at", "created_by", "created_at", "used_at", "server")
def get_fields(self, request, obj=None):
# Keep the add form minimal and deterministic in Unfold: token and
# metadata are generated server-side and shown on the change view.
if obj is None:
return ("expires_at",)
return self.fields
def save_model(self, request, obj, form, change) -> None:
if not obj.pk:
obj.ensure_token()
+85 -23
View File
@@ -6,37 +6,37 @@
{% block extra_head %}
<style>
.ak-chart-card {
border-color: #1f2937;
background: radial-gradient(120% 140% at 0% 0%, #1e293b 0%, #111827 50%, #0b1220 100%);
color: #e5e7eb;
border-color: var(--kw-chart-card-border);
background: var(--kw-chart-card-bg);
color: var(--kw-chart-text);
box-shadow: 0 10px 30px rgba(2, 6, 23, 0.35);
}
.ak-chart-title {
color: #f3f4f6;
color: var(--kw-chart-text);
font-weight: 700;
letter-spacing: 0.01em;
}
.ak-chart-subtitle {
color: #94a3b8;
color: var(--kw-chart-subtext);
}
.ak-chart-badge {
border: 1px solid rgba(100, 116, 139, 0.45);
background: rgba(15, 23, 42, 0.65);
color: #e2e8f0;
border: 1px solid var(--kw-chart-badge-border);
background: var(--kw-chart-badge-bg);
color: var(--kw-chart-badge-text);
}
.ak-chart-canvas-wrap {
border: 1px solid rgba(71, 85, 105, 0.35);
background: rgba(2, 6, 23, 0.32);
border: 1px solid var(--kw-chart-canvas-border);
background: var(--kw-chart-canvas-bg);
border-radius: 14px;
padding: 0.6rem 0.75rem;
}
.ak-chart-empty {
color: #94a3b8;
color: var(--kw-chart-empty-text);
}
</style>
{% endblock %}
@@ -230,6 +230,7 @@
var panelUrl = "{{ audit_panel_url }}";
var lineLimitSelect = document.getElementById("js-audit-line-limit");
var chartsInitialized = false;
var chartInstances = [];
var auditPath = "{{ request.path }}";
function parseChartData(scriptId) {
@@ -244,13 +245,49 @@
}
}
function getResolvedTheme() {
return document.documentElement.getAttribute("data-theme") === "dark" ? "dark" : "light";
}
function getChartPalette() {
if (getResolvedTheme() === "dark") {
return {
legend: "#e2e8f0",
tickPrimary: "#94a3b8",
tickSecondary: "#cbd5e1",
gridStrong: "rgba(148, 163, 184, 0.12)",
gridSoft: "rgba(148, 163, 184, 0.08)",
tooltipBg: "rgba(15, 23, 42, 0.94)",
tooltipBorder: "rgba(148, 163, 184, 0.35)",
tooltipTitle: "#e2e8f0",
tooltipBody: "#cbd5e1"
};
}
return {
legend: "#0f172a",
tickPrimary: "#64748b",
tickSecondary: "#334155",
gridStrong: "rgba(148, 163, 184, 0.24)",
gridSoft: "rgba(148, 163, 184, 0.16)",
tooltipBg: "rgba(248, 250, 252, 0.98)",
tooltipBorder: "rgba(100, 116, 139, 0.35)",
tooltipTitle: "#0f172a",
tooltipBody: "#334155"
};
}
function renderBarChart(canvasId, emptyId, data, options) {
var canvas = document.getElementById(canvasId);
var emptyNode = document.getElementById(emptyId);
var chartOpts = options || {};
var palette = getChartPalette();
if (!canvas) {
return;
}
if (canvas._kwChart) {
canvas._kwChart.destroy();
canvas._kwChart = null;
}
if (!window.Chart || !data || !Array.isArray(data.labels) || data.labels.length === 0) {
canvas.classList.add("hidden");
if (emptyNode) {
@@ -275,7 +312,7 @@
}
]
};
new window.Chart(canvas.getContext("2d"), {
var chart = new window.Chart(canvas.getContext("2d"), {
type: "bar",
data: chartData,
options: {
@@ -288,7 +325,7 @@
position: "top",
align: "center",
labels: {
color: "#e2e8f0",
color: palette.legend,
boxWidth: 18,
boxHeight: 8,
font: {
@@ -298,11 +335,11 @@
}
},
tooltip: {
backgroundColor: "rgba(15, 23, 42, 0.94)",
borderColor: "rgba(148, 163, 184, 0.35)",
backgroundColor: palette.tooltipBg,
borderColor: palette.tooltipBorder,
borderWidth: 1,
titleColor: "#e2e8f0",
bodyColor: "#cbd5e1",
titleColor: palette.tooltipTitle,
bodyColor: palette.tooltipBody,
callbacks: {
label: function (ctx) {
var isHorizontal = chartOpts.indexAxis === "y";
@@ -317,19 +354,19 @@
beginAtZero: true,
ticks: {
precision: 0,
color: "#94a3b8"
color: palette.tickPrimary
},
grid: {
color: "rgba(148, 163, 184, 0.12)",
color: palette.gridStrong,
drawBorder: false
}
},
y: {
ticks: {
color: "#cbd5e1"
color: palette.tickSecondary
},
grid: {
color: "rgba(148, 163, 184, 0.08)",
color: palette.gridSoft,
drawBorder: false
}
}
@@ -354,12 +391,28 @@
}
}
});
canvas._kwChart = chart;
chartInstances.push(chart);
}
function initializeCharts() {
if (chartsInitialized) {
function destroyCharts() {
for (var i = 0; i < chartInstances.length; i += 1) {
try {
chartInstances[i].destroy();
} catch (err) {
// noop
}
}
chartInstances = [];
}
function initializeCharts(force) {
if (chartsInitialized && !force) {
return;
}
if (force) {
destroyCharts();
}
chartsInitialized = true;
renderBarChart("js-login-chart", "js-login-chart-empty", parseChartData("js-login-chart-data"), {
datasetLabel: "Logins"
@@ -512,6 +565,15 @@
}
setSubpage("{{ audit_subpage }}", false);
window.addEventListener("keywarden:theme-mode", function () {
if (chartsSection && !chartsSection.classList.contains("hidden")) {
initializeCharts(true);
return;
}
destroyCharts();
chartsInitialized = false;
});
if (addButton) {
addButton.addEventListener("click", function () {
createPanel("");
@@ -100,14 +100,28 @@
</dd>
</div>
</dl>
<div class="mt-5 flex items-center justify-between border-t border-gray-100 pt-4 text-xs text-gray-500">
<span>Certificates and access</span>
<div class="mt-5 border-t border-gray-100 pt-4">
{% if item.access.can_users %}
<a href="{% url 'servers:detail' item.server.id %}" class="font-semibold text-blue-700 hover:underline">View details</a>
<a
href="{% url 'servers:detail' item.server.id %}"
class="inline-flex w-full items-center justify-center rounded-full bg-blue-700 px-4 py-2 text-xs font-semibold text-white transition hover:bg-blue-800"
>
Open details
</a>
{% elif item.access.can_logs %}
<a href="{% url 'servers:audit' item.server.id %}" class="font-semibold text-blue-700 hover:underline">Open audit</a>
<a
href="{% url 'servers:audit' item.server.id %}"
class="inline-flex w-full items-center justify-center rounded-full bg-blue-700 px-4 py-2 text-xs font-semibold text-white transition hover:bg-blue-800"
>
Open audit
</a>
{% elif item.access.can_shell %}
<a href="{% url 'servers:shell' item.server.id %}" class="font-semibold text-blue-700 hover:underline">Open shell</a>
<a
href="{% url 'servers:shell' item.server.id %}"
class="inline-flex w-full items-center justify-center rounded-full bg-blue-700 px-4 py-2 text-xs font-semibold text-white transition hover:bg-blue-800"
>
Open shell
</a>
{% endif %}
</div>
{% else %}