Initial commit: add compliance_checks table, per-check metadata on assets, and compliance audit trail
This commit is contained in:
155
app/templates/doc_templates/detail.html
Normal file
155
app/templates/doc_templates/detail.html
Normal 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
|
||||
<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>{{ variable_name }}</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 %}
|
||||
41
app/templates/doc_templates/edit.html
Normal file
41
app/templates/doc_templates/edit.html
Normal file
@@ -0,0 +1,41 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}Edit {{ 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"><a href="{{ url_for('doc_templates.detail', tpl_id=tpl.id) }}">{{ tpl.name }}</a></li>
|
||||
<li class="breadcrumb-item active">Edit</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header mb-4">
|
||||
<h1><i class="bi bi-pencil me-2"></i>Edit Template</h1>
|
||||
</div>
|
||||
<div class="card border-0 shadow-sm" style="max-width:600px;">
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Name <span class="text-danger">*</span></label>
|
||||
<input type="text" name="name" class="form-control" value="{{ tpl.name }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Category</label>
|
||||
<select name="category" class="form-select">
|
||||
<option value="">— no category —</option>
|
||||
{% for val, label in doc_types %}
|
||||
<option value="{{ val }}" {% if tpl.category == val %}selected{% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-semibold">Description</label>
|
||||
<textarea name="description" class="form-control" rows="3">{{ tpl.description or '' }}</textarea>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
<a href="{{ url_for('doc_templates.detail', tpl_id=tpl.id) }}" class="btn btn-outline-secondary">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
77
app/templates/doc_templates/index.html
Normal file
77
app/templates/doc_templates/index.html
Normal file
@@ -0,0 +1,77 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}Document Templates – IT Asset Management{% endblock %}
|
||||
{% block breadcrumb %}
|
||||
<li class="breadcrumb-item"><a href="{{ url_for('dashboard.index') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active">Document Templates</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>Document Templates</h1>
|
||||
<a href="{{ url_for('doc_templates.upload') }}" class="btn btn-primary btn-sm">
|
||||
<i class="bi bi-upload me-1"></i>Upload Template
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info small mb-4">
|
||||
<i class="bi bi-info-circle me-1"></i>
|
||||
Upload <strong>.docx</strong> Word files with <code>{{ variable_name }}</code> placeholders.
|
||||
When creating paperwork, the system fills them automatically from user / asset data.
|
||||
All generated documents can be regenerated with masked PII if a user leaves the company.
|
||||
</div>
|
||||
|
||||
{% if templates %}
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-3 g-3">
|
||||
{% for tpl in templates %}
|
||||
<div class="col">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-start justify-content-between mb-2">
|
||||
<h6 class="mb-0 fw-semibold">
|
||||
<i class="bi bi-file-earmark-word text-primary me-1"></i>{{ tpl.name }}
|
||||
</h6>
|
||||
{% if tpl.category %}
|
||||
<span class="badge bg-secondary ms-2">{{ dict(doc_types)[tpl.category] if tpl.category in dict(doc_types) else tpl.category }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if tpl.description %}
|
||||
<p class="text-muted small mb-2">{{ tpl.description }}</p>
|
||||
{% endif %}
|
||||
<div class="small text-muted mb-3">
|
||||
<i class="bi bi-braces me-1"></i>
|
||||
{% set vars = tpl.variables %}
|
||||
{% if vars %}
|
||||
{{ vars | length }} variable(s):
|
||||
{% for v in vars[:5] %}<code class="me-1">{{ v }}</code>{% endfor %}
|
||||
{% if vars | length > 5 %}<em>+{{ vars | length - 5 }} more</em>{% endif %}
|
||||
{% else %}
|
||||
No variables detected
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="d-flex gap-1 align-items-center">
|
||||
<a href="{{ url_for('doc_templates.detail', tpl_id=tpl.id) }}" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-eye me-1"></i>View
|
||||
</a>
|
||||
<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
|
||||
</a>
|
||||
<span class="ms-auto text-muted small">
|
||||
{{ tpl.paperwork_docs.count() }} doc(s) generated
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-white text-muted small">
|
||||
Uploaded {{ tpl.created_at.strftime('%d %b %Y') }}
|
||||
{% if tpl.created_by %} by {{ tpl.created_by.username }}{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center text-muted py-5">
|
||||
<i class="bi bi-file-earmark-word display-4 d-block mb-3"></i>
|
||||
No templates yet. <a href="{{ url_for('doc_templates.upload') }}">Upload your first template</a>.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
96
app/templates/doc_templates/upload.html
Normal file
96
app/templates/doc_templates/upload.html
Normal file
@@ -0,0 +1,96 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}Upload Template – IT Asset Management{% 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">Upload</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header mb-4">
|
||||
<h1><i class="bi bi-upload me-2"></i>Upload Document Template</h1>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-7">
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Template Name <span class="text-danger">*</span></label>
|
||||
<input type="text" name="name" class="form-control" placeholder="e.g. Equipment Handover Receipt" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Category</label>
|
||||
<select name="category" class="form-select">
|
||||
<option value="">— no category —</option>
|
||||
{% for val, label in doc_types %}
|
||||
<option value="{{ val }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="form-text">Used to pre-select this template when creating paperwork of that type.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Description</label>
|
||||
<textarea name="description" class="form-control" rows="2" placeholder="Optional notes about this template…"></textarea>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-semibold">.docx Template File <span class="text-danger">*</span></label>
|
||||
<input type="file" name="docx_file" class="form-control" accept=".docx" required>
|
||||
<div class="form-text">Word document (.docx) with <code>{{ variable_name }}</code> placeholders.</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-upload me-1"></i>Upload
|
||||
</button>
|
||||
<a href="{{ url_for('doc_templates.index') }}" class="btn btn-outline-secondary">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-5">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header fw-semibold small text-uppercase text-muted bg-white">
|
||||
Available Variables
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-sm table-hover mb-0 small">
|
||||
<thead class="table-light"><tr><th>Variable</th><th>Value</th></tr></thead>
|
||||
<tbody>
|
||||
{% set var_docs = [
|
||||
('user_name','Full name (masked if user left)'),
|
||||
('user_email','Email address'),
|
||||
('user_phone','Phone number'),
|
||||
('user_department','Department (retained after masking)'),
|
||||
('user_job_title','Job title'),
|
||||
('user_location','Office location'),
|
||||
('user_windows_id','Windows / AD ID — never masked'),
|
||||
('asset_serial','Asset serial number'),
|
||||
('asset_service_tag','Dell / vendor service tag'),
|
||||
('asset_brand','Brand (e.g. Dell)'),
|
||||
('asset_model','Model name'),
|
||||
('asset_type','Type (Laptop / Desktop / …)'),
|
||||
('asset_os','Operating system'),
|
||||
('asset_warranty_expiry','Warranty expiry date'),
|
||||
('assignment_date','Date asset was assigned'),
|
||||
('return_date','Date asset was returned'),
|
||||
('document_date','Today\'s date'),
|
||||
('document_number','Document / paperwork ID'),
|
||||
('company_name','Your company name'),
|
||||
('company_address','Your company address'),
|
||||
] %}
|
||||
{% for var, desc in var_docs %}
|
||||
<tr>
|
||||
<td><code>{{ {{ var }} }}</code></td>
|
||||
<td class="text-muted">{{ desc }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user