Initial commit: add compliance_checks table, per-check metadata on assets, and compliance audit trail

This commit is contained in:
2026-04-24 07:14:27 +03:00
commit e63b486ec2
58 changed files with 6468 additions and 0 deletions

View File

@@ -0,0 +1,155 @@
{% extends 'base.html' %}
{% block title %}{{ tpl.name }} Templates{% endblock %}
{% block breadcrumb %}
<li class="breadcrumb-item"><a href="{{ url_for('dashboard.index') }}">Home</a></li>
<li class="breadcrumb-item"><a href="{{ url_for('doc_templates.index') }}">Templates</a></li>
<li class="breadcrumb-item active">{{ tpl.name }}</li>
{% endblock %}
{% block content %}
<div class="page-header d-flex align-items-center justify-content-between mb-4">
<h1><i class="bi bi-file-earmark-word me-2"></i>{{ tpl.name }}</h1>
<div class="d-flex gap-2">
<a href="{{ url_for('doc_templates.download', tpl_id=tpl.id) }}" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-download me-1"></i>Download .docx
</a>
<a href="{{ url_for('doc_templates.edit', tpl_id=tpl.id) }}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-pencil me-1"></i>Edit details
</a>
<form method="POST" action="{{ url_for('doc_templates.rescan', tpl_id=tpl.id) }}" class="d-inline">
<button type="submit" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-arrow-clockwise me-1"></i>Re-scan variables
</button>
</form>
<button class="btn btn-sm btn-outline-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">
<i class="bi bi-trash me-1"></i>Delete
</button>
</div>
</div>
<div class="row g-4">
<!-- Metadata -->
<div class="col-md-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white fw-semibold small text-uppercase text-muted">Details</div>
<div class="card-body">
<dl class="row small mb-0">
<dt class="col-5">Category</dt>
<dd class="col-7">
{% if tpl.category %}
<span class="badge bg-secondary">{{ dict(doc_types)[tpl.category] if tpl.category in dict(doc_types) else tpl.category }}</span>
{% else %}<span class="text-muted"></span>{% endif %}
</dd>
<dt class="col-5">File</dt>
<dd class="col-7"><code>{{ tpl.filename }}</code></dd>
<dt class="col-5">Uploaded</dt>
<dd class="col-7">{{ tpl.created_at.strftime('%d %b %Y %H:%M') }}</dd>
<dt class="col-5">By</dt>
<dd class="col-7">{{ tpl.created_by.username if tpl.created_by else '—' }}</dd>
<dt class="col-5">Docs generated</dt>
<dd class="col-7">{{ tpl.paperwork_docs.count() }}</dd>
</dl>
{% if tpl.description %}
<hr class="my-2">
<p class="small text-muted mb-0">{{ tpl.description }}</p>
{% endif %}
</div>
</div>
</div>
<!-- Variables -->
<div class="col-md-8">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white fw-semibold small text-uppercase text-muted d-flex justify-content-between align-items-center">
Detected Variables
<span class="badge bg-primary rounded-pill">{{ tpl.variables | length }}</span>
</div>
{% set vars = tpl.variables %}
{% if vars %}
<div class="card-body">
<p class="small text-muted mb-3">
These placeholders were detected in the template file.
They will be filled automatically when generating a document.
<strong class="text-danger">PII variables</strong> (name, email, phone)
are replaced with <code>[MASKED]</code> when a user's record is erased.
</p>
{% set pii = ['user_name','user_email','user_phone'] %}
<div class="row row-cols-2 row-cols-md-3 g-2">
{% for v in vars %}
<div class="col">
<span class="badge {% if v in pii %}bg-danger{% else %}bg-light text-dark border{% endif %} w-100 text-start p-2">
{% if v in pii %}<i class="bi bi-shield-x me-1"></i>{% else %}<i class="bi bi-braces me-1"></i>{% endif %}
{{ v }}
</span>
</div>
{% endfor %}
</div>
<div class="mt-3 small">
<span class="badge bg-danger me-1">PII</span> masked on departure &nbsp;
<span class="badge bg-light text-dark border me-1">other</span> retained
</div>
</div>
{% else %}
<div class="card-body text-muted small">
No variables detected. Make sure your template uses <code>&#123;&#123; variable_name &#125;&#125;</code> syntax
and click <strong>Re-scan</strong>.
</div>
{% endif %}
</div>
</div>
</div>
<!-- Documents generated from this template -->
{% set recent_docs = tpl.paperwork_docs.order_by('created_at desc').limit(10).all() %}
{% if recent_docs %}
<div class="card border-0 shadow-sm mt-4">
<div class="card-header bg-white fw-semibold small text-uppercase text-muted">Recently Generated Documents</div>
<div class="table-responsive">
<table class="table table-sm table-hover mb-0 small">
<thead class="table-light">
<tr><th>Title</th><th>User</th><th>Created</th><th>Signed</th><th></th></tr>
</thead>
<tbody>
{% for doc in recent_docs %}
<tr>
<td>{{ doc.title }}</td>
<td>{{ doc.user.display_name if doc.user else '—' }}</td>
<td>{{ doc.created_at.strftime('%d/%m/%Y') }}</td>
<td>{% if doc.is_signed %}<i class="bi bi-check-circle text-success"></i>{% else %}<span class="text-muted"></span>{% endif %}</td>
<td><a href="{{ url_for('paperwork.detail', doc_id=doc.id) }}" class="btn btn-sm btn-outline-secondary py-0 px-2"><i class="bi bi-eye"></i></a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<!-- Delete modal -->
<div class="modal fade" id="deleteModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Delete Template</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
Delete <strong>{{ tpl.name }}</strong>?
{% if tpl.paperwork_docs.count() > 0 %}
<div class="alert alert-danger mt-2 small">
Cannot delete — {{ tpl.paperwork_docs.count() }} document(s) were generated from this template.
</div>
{% endif %}
</div>
<div class="modal-footer">
<button class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
{% if tpl.paperwork_docs.count() == 0 %}
<form method="POST" action="{{ url_for('doc_templates.delete', tpl_id=tpl.id) }}">
<button class="btn btn-danger btn-sm" type="submit">Delete</button>
</form>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}