This commit is contained in:
@@ -6,12 +6,68 @@
|
||||
|
||||
<section class="source-overview" aria-label="Bronoverzicht">
|
||||
<article class="stat-card signal"><span class="kpi-label">Actieve bronnen</span><strong>{{ active_source_count }}</strong><small>van {{ source_total_count }} geregistreerd</small></article>
|
||||
<article class="stat-card"><span class="kpi-label">Policyreview</span><strong>{{ review_pending_count }}</strong><small>bronnen te beoordelen</small></article>
|
||||
<article class="stat-card"><span class="kpi-label">Werkgeverswaaklijst</span><strong>{{ watchlist_count }}</strong><small>interessante regionale werkgevers</small></article>
|
||||
<article class="stat-card"><span class="kpi-label">Veilige modus</span><strong>AAN</strong><small>policy + SSRF-validatie</small></article>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<div class="section-heading"><div><span class="section-kicker">Gecontroleerde invoer</span><h2>Handmatige import</h2><p>Gebruik een publieke URL of plak vacaturetekst. URL-import passeert altijd het bronbeleid.</p></div></div>
|
||||
<section class="panel" aria-labelledby="regional-scope-title">
|
||||
<div class="section-heading"><div><span class="section-kicker">Regionale werkgeversradar</span><h2 id="regional-scope-title">2400 Mol · straal 40 km</h2><p>De werkgeverscatalogus is rond Mol gereviewd. Afstanden gebruiken lokale postcodecoördinaten en blijven afhankelijk van de concrete werklocatie in de vacature.</p></div><span class="pill {% if active_search_profile.home_latitude != None and active_search_profile.home_longitude != None %}pill-success{% else %}pill-review{% endif %}">{% if active_search_profile.home_latitude != None and active_search_profile.home_longitude != None %}Afstanden actief{% else %}Profiel controleren{% endif %}</span></div>
|
||||
{% if active_search_profile %}<p class="muted">Actief profiel: postcode {{ active_search_profile.home_postal_code|default:"niet ingevuld" }} · {{ active_search_profile.home_municipality|default:"gemeente onbekend" }} · maximaal {{ active_search_profile.max_distance_km }} km.{% if active_search_profile.home_latitude == None or active_search_profile.home_longitude == None %} Coördinaten ontbreken nog; vacatureafstanden worden niet geschat.{% else %} Postcodecentrum gecontroleerd via de lokale GeoNames-dataset.{% endif %}</p>{% endif %}
|
||||
</section>
|
||||
|
||||
{% if watchlist_sources %}<section class="panel" aria-labelledby="employer-watchlist-title">
|
||||
<div class="section-heading"><div><span class="section-kicker">Ook volgen zonder vacature</span><h2 id="employer-watchlist-title">Interessante werkgeverswaaklijst</h2><p>Deze werkgevers liggen in de ruime regio en blijven zichtbaar, ook wanneer er nu geen geschikte vacature is. Ze worden pas automatisch gescand na een stabiele, veilige parserproef.</p></div><span class="pill pill-review">{{ watchlist_count }} gevolgd</span></div>
|
||||
<div class="source-alert-grid">
|
||||
{% for source in watchlist_sources %}<article class="source-alert-card"><div><div class="title-row"><strong>{{ source.name }}</strong><span class="pill pill-review">Waaklijst</span></div><p>{{ source.metadata.municipality_scope }} · {{ source.metadata.watchlist_reason }}</p><small>{{ source.metadata.current_observation }}</small></div><a class="button button-secondary" href="{{ source.metadata.public_job_page|default:source.base_url }}" target="_blank" rel="noopener noreferrer">Open carrièrepagina</a></article>{% endfor %}
|
||||
</div>
|
||||
</section>{% endif %}
|
||||
|
||||
<section class="panel" aria-labelledby="platform-alerts-title">
|
||||
<div class="section-heading"><div><span class="section-kicker">Brede marktdekking</span><h2 id="platform-alerts-title">Platformmailboxen</h2><p>Koppel per jobboard een aparte alertmailbox. De scheduler controleert elke vijf minuten welke mailbox volgens haar eigen interval aan de beurt is.</p></div><span class="pill {% if platform_alert_ingress_ready %}pill-success{% else %}pill-review{% endif %}">{{ mailbox_connections|length }} gekoppeld</span></div>
|
||||
|
||||
{% if not mailbox_credential_key_ready %}<p class="message error">Credentialopslag is nog vergrendeld. Stel eerst <code>MAILBOX_CREDENTIAL_KEYS</code> in; app-wachtwoorden worden nooit onversleuteld opgeslagen.</p>{% endif %}
|
||||
|
||||
<div class="source-alert-grid">
|
||||
{% for connection in mailbox_connections %}
|
||||
<article class="source-alert-card">
|
||||
<div><div class="title-row"><strong>{{ connection.get_platform_display }}-mailbox</strong><span class="pill {% if connection.enabled and not connection.last_error_category %}pill-success{% elif connection.last_error_category %}pill-failed{% else %}pill-review{% endif %}">{% if connection.enabled %}{% if connection.last_error_category %}Aandacht nodig{% else %}Actief{% endif %}{% else %}Gepauzeerd{% endif %}</span></div>
|
||||
<p>{{ connection.get_provider_display }} · {{ connection.username }} · {{ connection.get_poll_interval_minutes_display|lower }}</p>
|
||||
<small>{% if connection.last_success_at %}Laatste import {{ connection.last_success_at|date:"d/m/Y H:i" }}.{% else %}Nog geen geslaagde import.{% endif %} {% if connection.enabled %}Volgende controle {{ connection.next_poll_at|date:"d/m/Y H:i" }}.{% endif %}</small>
|
||||
{% if connection.last_error_message %}<p class="message error">{{ connection.last_error_message }}</p>{% endif %}</div>
|
||||
<div class="job-actions">{% if connection.enabled %}<form method="post" action="{% url 'sources:sync_platform_alerts' connection.pk %}">{% csrf_token %}<button class="button button-primary" type="submit">Nu synchroniseren</button></form>{% endif %}<a class="button button-secondary" href="{% url 'sources:mailbox_edit' connection.pk %}#mailbox-form">Wijzigen</a><form method="post" action="{% url 'sources:mailbox_toggle' connection.pk %}">{% csrf_token %}<button class="button button-ghost" type="submit">{% if connection.enabled %}Pauzeren{% else %}Activeren{% endif %}</button></form></div>
|
||||
</article>
|
||||
{% empty %}
|
||||
<article class="source-alert-card"><div><strong>Nog geen mailbox gekoppeld</strong><p>Koppel hieronder per gewenst jobboard de mailbox die de bijbehorende vacaturealerts ontvangt.</p></div></article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<details id="mailbox-form" class="import-disclosure mailbox-disclosure" {% if editing_mailbox or mailbox_form.errors %}open{% endif %}>
|
||||
<summary><span class="source-icon"><svg class="icon"><use href="#icon-sources"></use></svg></span><span><span class="section-kicker">Versleutelde IMAP-koppeling</span><strong>{% if editing_mailbox %}{{ editing_mailbox.get_platform_display }}-mailbox wijzigen{% else %}Platformmailbox koppelen{% endif %}</strong><small>Gmail, Outlook / Microsoft 365 of aangepaste IMAP</small></span><svg class="icon disclosure-icon"><use href="#icon-chevron"></use></svg></summary>
|
||||
<div class="import-disclosure-body"><form method="post" action="{% if editing_mailbox %}{% url 'sources:mailbox_edit' editing_mailbox.pk %}{% else %}{% url 'sources:mailbox_add' %}{% endif %}">{% csrf_token %}
|
||||
<div class="form-grid mailbox-form-grid">
|
||||
<label><span class="field-label">{{ mailbox_form.platform.label }}</span>{{ mailbox_form.platform }}{% for error in mailbox_form.platform.errors %}<small class="field-error">{{ error }}</small>{% endfor %}</label>
|
||||
<label><span class="field-label">{{ mailbox_form.provider.label }}</span>{{ mailbox_form.provider }}{% for error in mailbox_form.provider.errors %}<small class="field-error">{{ error }}</small>{% endfor %}</label>
|
||||
<label><span class="field-label">{{ mailbox_form.custom_host.label }}</span>{{ mailbox_form.custom_host }}<small class="muted">Alleen invullen bij een andere IMAP-provider.</small>{% for error in mailbox_form.custom_host.errors %}<small class="field-error">{{ error }}</small>{% endfor %}</label>
|
||||
<label><span class="field-label">{{ mailbox_form.port.label }}</span>{{ mailbox_form.port }}{% for error in mailbox_form.port.errors %}<small class="field-error">{{ error }}</small>{% endfor %}</label>
|
||||
<label><span class="field-label">{{ mailbox_form.username.label }}</span>{{ mailbox_form.username }}{% for error in mailbox_form.username.errors %}<small class="field-error">{{ error }}</small>{% endfor %}</label>
|
||||
<label><span class="field-label">{{ mailbox_form.password.label }}</span>{{ mailbox_form.password }}<small class="muted">Gebruik een app-wachtwoord; dit veld wordt nooit teruggetoond.</small>{% for error in mailbox_form.password.errors %}<small class="field-error">{{ error }}</small>{% endfor %}</label>
|
||||
<label><span class="field-label">{{ mailbox_form.mailbox.label }}</span>{{ mailbox_form.mailbox }}{% for error in mailbox_form.mailbox.errors %}<small class="field-error">{{ error }}</small>{% endfor %}</label>
|
||||
<label><span class="field-label">{{ mailbox_form.poll_interval_minutes.label }}</span>{{ mailbox_form.poll_interval_minutes }}{% for error in mailbox_form.poll_interval_minutes.errors %}<small class="field-error">{{ error }}</small>{% endfor %}</label>
|
||||
</div>
|
||||
<label class="checkbox-row">{{ mailbox_form.enabled }}<span>{{ mailbox_form.enabled.label }}</span></label>
|
||||
{% for error in mailbox_form.non_field_errors %}<div class="message error">{{ error }}</div>{% endfor %}
|
||||
<div class="job-actions"><button class="button button-primary" type="submit" {% if not mailbox_credential_key_ready %}disabled{% endif %}>{% if editing_mailbox %}Wijzigingen opslaan{% else %}Mailbox koppelen en testen{% endif %}</button>{% if editing_mailbox %}<a class="button button-ghost" href="{% url 'sources:list' %}">Annuleren</a>{% endif %}</div>
|
||||
</form></div>
|
||||
</details>
|
||||
|
||||
<div class="source-alert-grid platform-help-grid">
|
||||
{% for platform in platform_alerts %}<article class="source-alert-card"><div><strong>{{ platform.label }}-alert instellen</strong><p>{{ platform.help_text }} VacatureRadar verwerkt alleen links van {{ platform.label }} en logt niet in op het platform.</p></div><a class="button button-secondary" href="{{ platform.alert_url }}" target="_blank" rel="noopener noreferrer">Open {{ platform.label }}</a></article>{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<details class="panel import-disclosure" {% if manual_import_form.errors %}open{% endif %}>
|
||||
<summary><span class="source-icon"><svg class="icon"><use href="#icon-upload"></use></svg></span><span><span class="section-kicker">Gecontroleerde invoer</span><strong>Handmatige import</strong><small>Publieke URL of vacaturetekst toevoegen</small></span><svg class="icon disclosure-icon"><use href="#icon-chevron"></use></svg></summary>
|
||||
<div class="import-disclosure-body"><p class="muted">URL-import passeert altijd het bronbeleid en de SSRF-validatie.</p>
|
||||
<form id="manual-import-form" method="post" action="{% url 'sources:manual_import' %}" data-paste-limit="{{ manual_import_paste_max_bytes }}">{% csrf_token %}
|
||||
<div class="import-layout">
|
||||
<label><span class="field-label">{{ 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">{{ error }}</small>{% endfor %}<small class="muted">Laat leeg wanneer je tekst plakt.</small></label>
|
||||
@@ -20,19 +76,17 @@
|
||||
{% for error in manual_import_form.non_field_errors %}<div class="message error">{{ error }}</div>{% endfor %}
|
||||
<div class="job-actions"><button class="button button-primary" type="submit"><svg class="icon"><use href="#icon-upload"></use></svg>Import uitvoeren</button><button id="copy-bookmarklet" class="button button-ghost" type="button">Bookmarklet kopiëren</button><a id="manual-bookmarklet" class="button button-link" href="#" data-manual-import-endpoint="{{ manual_import_bookmarklet_endpoint }}">Bookmarklet installeren</a></div>
|
||||
</form>
|
||||
<textarea id="manual-bookmarklet-code" rows="2" readonly class="sr-only" aria-label="Bookmarklet endpoint">{{ manual_import_bookmarklet_endpoint }}</textarea>
|
||||
</section>
|
||||
<textarea id="manual-bookmarklet-code" rows="2" readonly class="sr-only" aria-label="Bookmarklet endpoint">{{ manual_import_bookmarklet_endpoint }}</textarea></div>
|
||||
</details>
|
||||
|
||||
{% if manual_import_result %}<section class="panel"><span class="pill pill-success">Importresultaat</span><h2>Importresultaat</h2><p><strong>Bron:</strong> {{ manual_import_result.source_name }} ({{ manual_import_result.source_id }})</p><dl class="definition-grid"><div><dt>Modus</dt><dd>Modus: {{ manual_import_result.mode }}</dd></div><div><dt>Herkenbare items</dt><dd>Herkenbare items: {{ manual_import_result.extracted_count }}</dd></div><div><dt>Nieuwe vacatures</dt><dd>Nieuwe vacatures: {{ manual_import_result.created_count }}</dd></div><div><dt>Duplicaten</dt><dd>Duplicaten: {{ manual_import_result.duplicate_count }}</dd></div></dl>{% if manual_import_result.warnings %}<h3>Waarschuwingen</h3><ul class="compact-list caution">{% for warning in manual_import_result.warnings %}<li>{{ warning }}</li>{% endfor %}</ul>{% endif %}{% if manual_import_result.jobs %}<h3>Geïmporteerde vacatures</h3><ul class="source-list">{% for job in manual_import_result.jobs %}<li>{% if job.id %}<a href="{% url 'jobs:detail' job.id %}">{{ job.title }}</a>{% else %}{{ job.title }}{% endif %}<span>{{ job.employer }}</span></li>{% endfor %}</ul>{% endif %}</section>{% endif %}
|
||||
|
||||
<form id="bulk-action-form" method="post" action="{% url 'sources:bulk' %}" class="filter-bar">{% csrf_token %}<label><span class="field-label">Bulkactie voor selectie</span><select id="action" name="action"><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></label><button class="button button-secondary" type="submit">Uitvoeren</button></form>
|
||||
|
||||
<div class="section-heading"><div><span class="section-kicker">Endpoints</span><h2>{{ source_total_count }} geregistreerde bronnen</h2><p>Status is gebaseerd op bronbeleid en de meest recente run, indien beschikbaar.</p></div></div>
|
||||
<div class="section-heading source-list-heading"><div><span class="section-kicker">Scan-endpoints</span><h2>{{ source_total_count }} geregistreerde bronnen</h2><p>Status is gebaseerd op bronbeleid en de meest recente run.</p></div><form id="bulk-action-form" method="post" action="{% url 'sources:bulk' %}" class="inline-bulk-action">{% csrf_token %}<label><span class="sr-only">Bulkactie voor selectie</span><select id="action" name="action"><option value="approve_to_trial">Naar proefrun</option><option value="allow">Toestaan</option><option value="pause">Pauzeren</option><option value="dismiss">Afwijzen</option></select></label><button class="button button-secondary" type="submit">Uitvoeren</button></form></div>
|
||||
<div class="card-list">
|
||||
{% for source in sources %}{% with latest_run=source.runs.all|first %}
|
||||
<article class="source-card">
|
||||
<div class="source-card-head">
|
||||
<div class="source-identity"><span class="source-icon"><svg class="icon"><use href="#icon-sources"></use></svg></span><div><div class="title-row">{% if source.policy != 'deny' %}<input type="checkbox" name="source_ids" form="bulk-action-form" value="{{ source.pk }}" aria-label="Selecteer {{ source.name }}">{% endif %}<h2>{{ source.name }}</h2></div><p>{{ source.get_source_type_display }} · {{ source.domain|default:"Geen domein" }}</p><div class="job-meta"><span>{{ source.get_policy_display }}</span>{% if latest_run %}<span>laatste run {{ latest_run.started_at|date:"d/m/Y H:i" }}</span><span>{{ latest_run.extracted_count }} gevonden</span>{% else %}<span>nog niet gestart</span>{% endif %}</div></div></div>
|
||||
<div class="source-identity"><span class="source-icon"><svg class="icon"><use href="#icon-sources"></use></svg></span><div><div class="title-row">{% if source.policy != 'deny' %}<input type="checkbox" name="source_ids" form="bulk-action-form" value="{{ source.pk }}" aria-label="Selecteer {{ source.name }}">{% endif %}<h2>{{ source.name }}</h2></div><p>{{ source.get_source_type_display }} · {{ source.domain|default:"Geen domein" }}</p><div class="job-meta"><span>{{ source.get_policy_display }}</span>{% if latest_run %}<span>laatste run {{ latest_run.started_at|date:"d/m/Y H:i" }}</span><span>{{ source.job_count }} vacatures in radar</span>{% else %}<span>nog niet gestart</span>{% endif %}</div></div></div>
|
||||
<div class="source-actions"><span class="pill {% if source.policy == 'deny' or source.status == 'quarantined' or latest_run and latest_run.status == 'failed' %}pill-failed{% elif latest_run and latest_run.status == 'partial' %}pill-partial{% elif latest_run and latest_run.status == 'success' %}pill-success{% else %}pill-review{% endif %}">{% if source.policy == 'deny' %}Geblokkeerd{% elif source.status == 'quarantined' %}Quarantaine{% elif latest_run %}{{ latest_run.get_status_display }}{% else %}Geen run{% endif %}</span><button class="icon-button" type="button" data-disclosure aria-controls="source-details-{{ forloop.counter }}" aria-expanded="false" aria-label="Details van {{ source.name }} tonen"><svg class="icon disclosure-icon"><use href="#icon-chevron"></use></svg></button></div>
|
||||
</div>
|
||||
{% if source.discovery_evidence %}<div class="source-details"><span class="field-label">Ontdekkingsevidence</span><ul class="compact-list">{% for evidence in source.discovery_evidence %}<li>{{ evidence.label|default:"Ontdekte bron" }}: {{ evidence.url }}</li>{% endfor %}</ul></div>{% endif %}
|
||||
|
||||
Reference in New Issue
Block a user