dark mode, logging fixes to avoid agent traffic bloat
This commit is contained in:
1 parent
d97b1661c1
commit
f7cf22f970
10 files changed
+488
-36
No files matched your search
@@ -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()
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
Reference in new issue
Block a user