@@ -0,0 +1,489 @@
|
||||
{% load static %}
|
||||
<!doctype html>
|
||||
<html class="dark" lang="nl">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||
<title>VacatureRadar - Bronnen & Source Health</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Geist:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet" />
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
"on-secondary-fixed-variant": "#005236",
|
||||
"secondary-fixed": "#6ffbbe",
|
||||
"on-error-container": "#ffdad6",
|
||||
"surface": "#0b1326",
|
||||
"surface-dim": "#0b1326",
|
||||
"on-primary": "#003739",
|
||||
"on-surface": "#dae2fd",
|
||||
"inverse-surface": "#dae2fd",
|
||||
"background": "#0b1326",
|
||||
"tertiary-fixed": "#e1e0ff",
|
||||
"surface-container": "#171f33",
|
||||
"primary-container": "#00f5ff",
|
||||
"surface-container-lowest": "#060e20",
|
||||
"on-tertiary-fixed-variant": "#2f2ebe",
|
||||
"on-tertiary": "#1000a9",
|
||||
"on-secondary": "#003824",
|
||||
"surface-container-highest": "#2d3449",
|
||||
"on-primary-fixed-variant": "#004f53",
|
||||
"on-secondary-container": "#00311f",
|
||||
"on-error": "#690005",
|
||||
"tertiary": "#fcf8ff",
|
||||
"on-secondary-fixed": "#002113",
|
||||
"on-primary-fixed": "#002021",
|
||||
"surface-variant": "#2d3449",
|
||||
"inverse-primary": "#00696e",
|
||||
"surface-bright": "#31394d",
|
||||
"secondary": "#4edea3",
|
||||
"primary-fixed": "#63f7ff",
|
||||
"outline": "#849495",
|
||||
"surface-container-high": "#222a3d",
|
||||
"primary": "#e9feff",
|
||||
"secondary-fixed-dim": "#4edea3",
|
||||
"on-background": "#dae2fd",
|
||||
"on-surface-variant": "#b9caca",
|
||||
"error": "#ffb4ab",
|
||||
"secondary-container": "#00a572",
|
||||
"tertiary-fixed-dim": "#c0c1ff",
|
||||
"on-tertiary-container": "#4c4ed9",
|
||||
"surface-container-low": "#131b2e",
|
||||
"outline-variant": "#3a494a",
|
||||
"inverse-on-surface": "#283044",
|
||||
"on-primary-container": "#006c71",
|
||||
"primary-fixed-dim": "#00dce5",
|
||||
"error-container": "#93000a",
|
||||
"tertiary-container": "#dbdaff",
|
||||
"surface-tint": "#00dce5",
|
||||
"on-tertiary-fixed": "#07006c"
|
||||
},
|
||||
borderRadius: {
|
||||
DEFAULT: "0.125rem",
|
||||
lg: "0.25rem",
|
||||
xl: "0.5rem",
|
||||
full: "0.75rem"
|
||||
},
|
||||
spacing: {
|
||||
"stack-md": "16px",
|
||||
"container-max": "1280px",
|
||||
"gutter": "24px",
|
||||
"stack-sm": "8px",
|
||||
"margin-mobile": "16px",
|
||||
"stack-lg": "32px"
|
||||
},
|
||||
fontFamily: {
|
||||
"headline-lg": ["Geist"],
|
||||
"headline-md": ["Geist"],
|
||||
"body-md": ["Inter"],
|
||||
"body-sm": ["Inter"],
|
||||
"label-mono": ["JetBrains Mono"],
|
||||
"metadata-caps": ["JetBrains Mono"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
background-color: #0b1326;
|
||||
color: #dae2fd;
|
||||
}
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
}
|
||||
.signal-glow-primary {
|
||||
box-shadow: 0 0 15px rgba(0, 245, 255, 0.1);
|
||||
border: 1px solid rgba(0, 245, 255, 0.2);
|
||||
}
|
||||
.glass-card {
|
||||
background: rgba(23, 31, 51, 0.6);
|
||||
backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(58, 73, 74, 0.5);
|
||||
}
|
||||
.technical-log {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #3a494a #171f33;
|
||||
}
|
||||
@keyframes pulse-cyan {
|
||||
0% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: 0.5; transform: scale(1.2); }
|
||||
100% { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
.status-pulse {
|
||||
animation: pulse-cyan 2s infinite ease-in-out;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="font-body-md text-body-md overflow-x-hidden">
|
||||
<header class="fixed top-0 w-full z-50 bg-surface/80 backdrop-blur-xl border-b border-outline-variant/30 shadow-sm hidden md:block">
|
||||
<div class="flex justify-between items-center px-gutter py-stack-sm w-full max-w-container-max mx-auto">
|
||||
<div class="flex items-center gap-8">
|
||||
<span class="font-headline-md text-headline-md font-bold tracking-tight text-primary">VacatureRadar</span>
|
||||
<nav class="flex gap-6">
|
||||
<a class="text-on-surface-variant hover:bg-surface-container-highest/50 px-3 py-1 rounded transition-colors font-medium" href="#">Overzicht</a>
|
||||
<a class="text-primary font-bold px-3 py-1 rounded" href="#">Bronnen</a>
|
||||
<a class="text-on-surface-variant hover:bg-surface-container-highest/50 px-3 py-1 rounded transition-colors font-medium" href="#">Instellingen</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="material-symbols-outlined text-on-surface-variant cursor-pointer hover:text-primary transition-colors">notifications</span>
|
||||
<span class="material-symbols-outlined text-on-surface-variant cursor-pointer hover:text-primary transition-colors">account_circle</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<aside class="h-screen w-64 fixed left-0 top-0 bg-surface-container border-r border-outline-variant/20 hidden md:flex flex-col py-stack-lg gap-stack-md z-40">
|
||||
<div class="px-6 pb-6">
|
||||
<h1 class="font-headline-md text-headline-md text-primary font-bold">VacatureRadar</h1>
|
||||
<p class="font-label-mono text-label-mono text-on-surface-variant opacity-60">Intelligent Discovery</p>
|
||||
</div>
|
||||
<nav class="flex flex-col px-3">
|
||||
<a class="flex items-center gap-3 px-4 py-3 rounded text-on-surface-variant hover:text-primary hover:bg-surface-container-high transition-all" href="#">
|
||||
<span class="material-symbols-outlined">dashboard</span>
|
||||
<span class="font-label-mono text-label-mono">Overview</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-4 py-3 rounded text-on-surface-variant hover:text-primary hover:bg-surface-container-high transition-all" href="#">
|
||||
<span class="material-symbols-outlined">radar</span>
|
||||
<span class="font-label-mono text-label-mono">Nieuwe matches</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-4 py-3 rounded text-primary bg-primary-container/10 border-r-2 border-primary-container" href="#">
|
||||
<span class="material-symbols-outlined">hub</span>
|
||||
<span class="font-label-mono text-label-mono">Bronnen</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-4 py-3 rounded text-on-surface-variant hover:text-primary hover:bg-surface-container-high transition-all" href="#">
|
||||
<span class="material-symbols-outlined">precision_manufacturing</span>
|
||||
<span class="font-label-mono text-label-mono">Automatisering</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-4 py-3 rounded text-on-surface-variant hover:text-primary hover:bg-surface-container-high transition-all" href="#">
|
||||
<span class="material-symbols-outlined">settings</span>
|
||||
<span class="font-label-mono text-label-mono">Instellingen</span>
|
||||
</a>
|
||||
</nav>
|
||||
<div class="px-6 py-4 mt-auto border-t border-outline-variant/30">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="material-symbols-outlined">group</span>
|
||||
<div>
|
||||
<p class="font-body-sm font-medium">{{ user.get_short_name|default:user.username|default:"Gast" }}</p>
|
||||
<p class="text-[10px] font-label-mono text-on-surface-variant">gebruiker</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="md:ml-64 pt-6 md:pt-24 min-h-screen px-4 md:px-gutter max-w-container-max mx-auto mb-24 md:mb-12">
|
||||
{% if messages %}
|
||||
<div class="mb-stack-md" aria-live="polite">
|
||||
{% for message in messages %}
|
||||
<div class="px-4 py-2 rounded mb-2 border border-outline-variant/30 bg-surface-container-highest">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="flex flex-col md:flex-row md:items-end justify-between gap-6 mb-stack-lg">
|
||||
<div>
|
||||
<h2 class="font-headline-lg text-headline-lg text-primary tracking-tight">Bronbeheer</h2>
|
||||
<p class="text-on-surface-variant font-body-md mt-1">Beheer en monitor de actieve scan-endpoints van jouw radarprofiel.</p>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<div class="glass-card px-4 py-2 rounded-xl flex flex-col">
|
||||
<span class="text-label-mono text-[10px] text-on-surface-variant uppercase tracking-tighter">Actieve Bronnen</span>
|
||||
<span class="font-headline-md text-secondary">{{ active_source_count }}</span>
|
||||
</div>
|
||||
<div class="glass-card px-4 py-2 rounded-xl flex flex-col">
|
||||
<span class="text-label-mono text-[10px] text-on-surface-variant uppercase tracking-tighter">Systeemstatus</span>
|
||||
<span class="font-headline-md text-primary flex items-center gap-2">
|
||||
Gezond
|
||||
<span class="w-2 h-2 rounded-full bg-secondary status-pulse"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="glass-card rounded-xl p-6 mb-stack-lg">
|
||||
<h3 class="font-headline-md text-primary mb-4">Handmatige import</h3>
|
||||
<p class="mb-4">Importeer een URL of plak vacaturetekst met dezelfde policy- en SSRF-controle als de backend.</p>
|
||||
|
||||
<form id="manual-import-form" method="post" action="{% url 'sources:manual_import' %}" class="grid grid-cols-1 md:grid-cols-2 gap-4" data-paste-limit="{{ manual_import_paste_max_bytes }}">
|
||||
{% csrf_token %}
|
||||
<label>
|
||||
<span class="text-label-mono text-label-mono text-on-surface-variant mb-1 block">{{ manual_import_form.source_url.label }}</span>
|
||||
{{ manual_import_form.source_url }}
|
||||
{% for error in manual_import_form.source_url.errors %}
|
||||
<small class="field-error block text-error">{{ error }}</small>
|
||||
{% endfor %}
|
||||
<small class="text-on-surface-variant">Vooraf ingevuld via bookmarklet; laat dit veld leeg wanneer je tekst plakt.</small>
|
||||
</label>
|
||||
<label>
|
||||
<span class="text-label-mono text-label-mono text-on-surface-variant mb-1 block">{{ manual_import_form.pasted_text.label }}</span>
|
||||
{{ manual_import_form.pasted_text }}
|
||||
<small class="text-on-surface-variant">Maximaal <span id="manual-import-byte-count">{{ manual_import_paste_max_bytes }}</span> bytes.</small>
|
||||
{% for error in manual_import_form.pasted_text.errors %}
|
||||
<small class="field-error block text-error">{{ error }}</small>
|
||||
{% endfor %}
|
||||
</label>
|
||||
<div class="md:col-span-2 flex flex-wrap gap-2">
|
||||
<button class="bg-primary text-on-primary px-4 py-2 rounded" type="submit">Import uitvoeren</button>
|
||||
<button id="copy-bookmarklet" class="border border-outline-variant px-4 py-2 rounded" type="button">Bookmarklet kopieren</button>
|
||||
</div>
|
||||
<a id="manual-bookmarklet" href="#" class="text-sm text-primary" data-manual-import-endpoint="{{ manual_import_bookmarklet_endpoint }}">
|
||||
Bookmarklet: sleep naar bladwijzers
|
||||
</a>
|
||||
{% for error in manual_import_form.non_field_errors %}
|
||||
<small class="field-error block text-error">{{ error }}</small>
|
||||
{% endfor %}
|
||||
</form>
|
||||
<textarea id="manual-bookmarklet-code" rows="2" readonly class="sr-only">{{ manual_import_bookmarklet_endpoint }}</textarea>
|
||||
</section>
|
||||
|
||||
{% if manual_import_result %}
|
||||
<section class="glass-card rounded-xl p-6 mb-stack-lg">
|
||||
<h3 class="font-headline-md text-primary mb-4">Importresultaat</h3>
|
||||
<p class="mb-2"><strong>Bron:</strong> {{ manual_import_result.source_name }} ({{ manual_import_result.source_id }})</p>
|
||||
<ul class="space-y-1 font-label-mono text-on-surface-variant text-xs mb-4">
|
||||
<li>Modus: {{ manual_import_result.mode }}</li>
|
||||
<li>URL: {{ manual_import_result.source_url }}</li>
|
||||
<li>Herkenbare items: {{ manual_import_result.extracted_count }}</li>
|
||||
<li>Nieuwe vacatures: {{ manual_import_result.created_count }}</li>
|
||||
<li>Reeds bestaande vacatures: {{ manual_import_result.duplicate_count }}</li>
|
||||
</ul>
|
||||
{% if manual_import_result.warnings %}
|
||||
<h4 class="font-label-mono text-label-mono uppercase text-xs mb-2">Waarschuwingen</h4>
|
||||
<ul class="list-disc list-inside text-on-surface-variant text-xs mb-4">
|
||||
{% for warning in manual_import_result.warnings %}
|
||||
<li>{{ warning }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if manual_import_result.jobs %}
|
||||
<h4 class="font-label-mono text-label-mono uppercase text-xs mb-2">Geïmporteerde vacatures</h4>
|
||||
<ul class="space-y-1">
|
||||
{% for job in manual_import_result.jobs %}
|
||||
<li>
|
||||
{% if job.id %}
|
||||
<a class="text-primary" href="{% url 'jobs:detail' job.id %}">{{ job.title }}</a> — {{ job.employer }}
|
||||
{% else %}
|
||||
{{ job.title }} — {{ job.employer }}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<form
|
||||
id="bulk-action-form"
|
||||
method="post"
|
||||
action="{% url 'sources:bulk' %}"
|
||||
class="glass-card rounded-xl p-4 mb-stack-lg flex flex-wrap gap-3 items-center"
|
||||
>
|
||||
{% csrf_token %}
|
||||
<label for="action" class="text-label-mono text-[11px] uppercase tracking-[0.04em] text-on-surface-variant">Bulkactie</label>
|
||||
<select
|
||||
id="action"
|
||||
name="action"
|
||||
class="bg-surface-container-low border border-outline-variant rounded-lg px-3 py-2 text-sm text-on-surface"
|
||||
>
|
||||
<option value="approve_to_trial">Kandidaten naar proefrun</option>
|
||||
<option value="allow">Toestaan</option>
|
||||
<option value="pause">Pauzeren</option>
|
||||
<option value="dismiss">Afwijzen</option>
|
||||
</select>
|
||||
<button
|
||||
class="bg-primary text-on-primary px-4 py-2 rounded-lg font-semibold border border-primary/30 shadow-sm"
|
||||
type="submit"
|
||||
>
|
||||
Uitvoeren
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 mb-stack-lg">
|
||||
{% for source in sources %}
|
||||
{% with latest_run=source.runs.all|first %}
|
||||
<section class="glass-card rounded-xl overflow-hidden
|
||||
{% if source.status == 'active' and source.policy != 'deny' and latest_run and latest_run.status == 'success' %}signal-glow-primary{% elif source.status == 'quarantined' or source.policy == 'deny' %}border-error/30{% elif latest_run and latest_run.status == 'failed' %}border-error/30{% elif latest_run and latest_run.status == 'partial' %}border-orange-500/20{% else %}border-surface-container-high{% endif %}
|
||||
">
|
||||
<div class="p-6 flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-12 h-12 rounded-lg bg-surface-container-highest flex items-center justify-center border border-outline-variant/30">
|
||||
<span class="material-symbols-outlined text-primary">
|
||||
{% if source.source_type == 'email' %}mail{% elif source.source_type == 'rss' or source.source_type == 'sitemap' %}rss_feed{% elif source.source_type == 'ats' %}hub{% else %}web{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
{% if source.policy != "deny" %}
|
||||
<input type="checkbox" name="source_ids" form="bulk-action-form" value="{{ source.pk }}" aria-label="Selecteer {{ source.name }}">
|
||||
{% endif %}
|
||||
<h3 class="font-headline-md text-primary">{{ source.name }}</h3>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-3 mt-1">
|
||||
<span class="font-label-mono text-label-mono text-on-surface-variant">{{ source.get_source_type_display }}</span>
|
||||
<span class="w-1 h-1 rounded-full bg-outline-variant"></span>
|
||||
<span class="font-label-mono text-label-mono text-on-surface-variant">{{ source.domain|default:'Geen domein' }}</span>
|
||||
{% if latest_run %}
|
||||
<span class="font-label-mono text-label-mono text-on-surface-variant">Laatste scan: {{ latest_run.started_at|date:'d/m/Y H:i' }}</span>
|
||||
{% else %}
|
||||
<span class="font-label-mono text-label-mono text-on-surface-variant">Nog niet gestart</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-6">
|
||||
<div class="text-right">
|
||||
<span class="block text-[10px] font-label-mono text-on-surface-variant uppercase">Vacatures gevonden</span>
|
||||
<span class="text-headline-md text-primary">{% if latest_run %}{{ latest_run.extracted_count }}{% else %}0{% endif %}</span>
|
||||
</div>
|
||||
<div class="px-3 py-1 rounded-full flex items-center gap-2
|
||||
{% if source.policy == 'deny' or (latest_run and latest_run.status == 'failed') %}bg-error/10 border border-error/20{% elif source.status == 'quarantined' %}bg-error/10 border border-error/20{% elif latest_run and latest_run.status == 'partial' %}bg-orange-500/10 border border-orange-500/20{% else %}bg-secondary/10 border border-secondary/20{% endif %}">
|
||||
<span class="w-2 h-2 rounded-full
|
||||
{% if source.policy == 'deny' or (latest_run and latest_run.status == 'failed') %}bg-error{% elif latest_run and latest_run.status == 'partial' %}bg-orange-500{% else %}bg-secondary{% endif %}
|
||||
"></span>
|
||||
<span class="text-label-mono font-medium
|
||||
{% if source.policy == 'deny' or (latest_run and latest_run.status == 'failed') %}text-error{% elif latest_run and latest_run.status == 'partial' %}text-orange-400{% else %}text-secondary{% endif %}">
|
||||
{% if source.policy == "deny" %}
|
||||
Geblokkeerd
|
||||
{% elif latest_run and latest_run.status == 'failed' %}
|
||||
Mislukt
|
||||
{% elif latest_run and latest_run.status == 'partial' %}
|
||||
Beperkt
|
||||
{% elif source.status == 'quarantined' %}
|
||||
Quarantaine
|
||||
{% else %}
|
||||
Gezond
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<button class="text-on-surface-variant hover:text-primary transition-colors" type="button" onclick="toggleDetails('details-{{ forloop.counter }}')">
|
||||
<span class="material-symbols-outlined" id="icon-details-{{ forloop.counter }}">expand_more</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden border-t border-outline-variant/20 bg-surface-container-low/50" id="details-{{ forloop.counter }}">
|
||||
<div class="p-6 grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<h4 class="text-label-mono text-[11px] text-on-surface-variant uppercase mb-3">Technische log</h4>
|
||||
<div class="bg-surface-container-lowest p-4 rounded border border-outline-variant/30 font-label-mono text-xs leading-relaxed text-on-surface-variant/80 technical-log max-h-40 overflow-y-auto">
|
||||
{% if latest_run %}
|
||||
{% if latest_run.status == 'failed' and latest_run.error_message %}
|
||||
[{% if latest_run.finished_at %}{{ latest_run.finished_at|date:"Y-m-d H:i:s" }}{% else %}--{% endif %}] ERROR: {{ latest_run.error_message }}
|
||||
{% else %}
|
||||
[{% if latest_run.finished_at %}{{ latest_run.finished_at|date:"Y-m-d H:i:s" }}{% else %}--{% endif %}] RUN {{ latest_run.get_status_display }}<br />
|
||||
Extracted: {{ latest_run.extracted_count }} Created: {{ latest_run.created_count }} Updated: {{ latest_run.updated_count }} Duplicate: {{ latest_run.duplicate_count }}
|
||||
{% if latest_run.http_status %}<br />HTTP {{ latest_run.http_status }}{% endif %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
Nog geen bronrun beschikbaar.
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h4 class="text-label-mono text-[11px] text-on-surface-variant uppercase mb-2">Bronstatus</h4>
|
||||
<p class="font-label-mono text-xs text-on-surface-variant/80">Policy: {{ source.get_policy_display }}</p>
|
||||
<p class="font-label-mono text-xs text-on-surface-variant/80">Review: {{ source.policy_review_summary }}</p>
|
||||
<p class="font-label-mono text-xs text-on-surface-variant/80">Volgende run: {{ source.next_run_at|date:'d/m/Y H:i'|default:'-' }}</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
{% if source.policy != 'deny' %}
|
||||
<form method="post" action="{% url 'sources:retry' source.pk %}">
|
||||
{% csrf_token %}
|
||||
<button class="bg-primary-container text-on-primary-container px-4 py-2 rounded font-bold text-sm" type="submit">Herstart scan</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<button class="bg-error text-surface px-4 py-2 rounded font-bold text-sm" type="button">Geblokkeerd</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endwith %}
|
||||
{% empty %}
|
||||
<section class="glass-card rounded-xl p-6">
|
||||
<p>Nog geen bronnen geregistreerd.</p>
|
||||
</section>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<section class="mt-12 mb-24 md:mb-12">
|
||||
<h3 class="font-headline-md text-primary mb-6">Bronstatus op overzicht</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div class="glass-card p-6 rounded-xl relative overflow-hidden h-64">
|
||||
<h4 class="text-label-mono text-[11px] text-on-surface-variant uppercase mb-4">Bronnen geregistreerd</h4>
|
||||
<div class="relative z-10 flex flex-col justify-end h-full">
|
||||
<span class="text-headline-md text-primary">{{ source_total_count }}</span>
|
||||
<span class="text-xs text-on-surface-variant font-label-mono">Totaal in beheer</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="glass-card p-6 rounded-xl relative overflow-hidden h-64">
|
||||
<h4 class="text-label-mono text-[11px] text-on-surface-variant uppercase mb-4">Te reviewen</h4>
|
||||
<div class="relative z-10 flex flex-col justify-end h-full">
|
||||
<span class="text-headline-md text-primary">{{ review_pending_count }}</span>
|
||||
<span class="text-xs text-on-surface-variant font-label-mono">Policy review</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="glass-card p-6 rounded-xl relative overflow-hidden h-64">
|
||||
<h4 class="text-label-mono text-[11px] text-on-surface-variant uppercase mb-4">Systeemlogboek</h4>
|
||||
<p class="text-xs text-on-surface-variant">Brongezondheid en technische events zijn zichtbaar op source-level kaarten.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<nav class="md:hidden fixed bottom-0 left-0 w-full bg-surface-container border-t border-outline-variant/10 py-3 px-6 flex justify-between items-center z-50">
|
||||
<button class="flex flex-col items-center gap-1 text-on-surface-variant">
|
||||
<span class="material-symbols-outlined">dashboard</span>
|
||||
<span class="text-[10px] font-label-mono">Overzicht</span>
|
||||
</button>
|
||||
<button class="flex flex-col items-center gap-1 text-primary">
|
||||
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 1;">hub</span>
|
||||
<span class="text-[10px] font-label-mono">Bronnen</span>
|
||||
</button>
|
||||
<button class="flex flex-col items-center gap-1 text-on-surface-variant">
|
||||
<span class="material-symbols-outlined">radar</span>
|
||||
<span class="text-[10px] font-label-mono">Radar</span>
|
||||
</button>
|
||||
<button class="flex flex-col items-center gap-1 text-on-surface-variant">
|
||||
<span class="material-symbols-outlined">settings</span>
|
||||
<span class="text-[10px] font-label-mono">Instellingen</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
function toggleDetails(id) {
|
||||
const el = document.getElementById(id);
|
||||
const icon = document.getElementById('icon-' + id);
|
||||
if (!el || !icon) {
|
||||
return;
|
||||
}
|
||||
const isHidden = el.classList.contains('hidden');
|
||||
|
||||
document.querySelectorAll('[id^=\"details-\"]').forEach(item => {
|
||||
if (item.id !== id) {
|
||||
item.classList.add('hidden');
|
||||
const otherIcon = document.getElementById('icon-' + item.id);
|
||||
if (otherIcon) {
|
||||
otherIcon.style.transform = 'rotate(0deg)';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (isHidden) {
|
||||
el.classList.remove('hidden');
|
||||
icon.style.transform = 'rotate(180deg)';
|
||||
icon.style.transition = 'transform 0.3s ease';
|
||||
} else {
|
||||
el.classList.add('hidden');
|
||||
icon.style.transform = 'rotate(0deg)';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="{% static 'js/manual_import.js' %}" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user