Aug 19 - Update code to catch up with ST
This commit is contained in:
@@ -115,10 +115,15 @@
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
{% if issues.items %}
|
||||
{% include 'partials/bulk_issues_toolbar.html' %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:34px;">
|
||||
<input type="checkbox" class="form-check-input bulk-check-all"
|
||||
title="Select all on this page" aria-label="Select all">
|
||||
</th>
|
||||
<th>#</th>
|
||||
<th>Reported</th>
|
||||
<th>Severity</th>
|
||||
@@ -137,6 +142,11 @@
|
||||
{% set is_following = issue.id in followed_ids %}
|
||||
{% set sla = sla_status(issue) %}
|
||||
<tr class="{{ 'table-danger' if sla == 'breached' else 'table-warning' if sla == 'at_risk' else '' }}">
|
||||
<td>
|
||||
<input type="checkbox" class="form-check-input bulk-check"
|
||||
form="issuesBulkForm" name="issue_ids" value="{{ issue.id }}"
|
||||
aria-label="Select issue #{{ issue.id }}">
|
||||
</td>
|
||||
<td><small class="text-muted">#{{ issue.id }}</small></td>
|
||||
<td><small>{{ issue.reported_at.strftime('%Y-%m-%d %H:%M') }}</small></td>
|
||||
<td>
|
||||
@@ -181,7 +191,7 @@
|
||||
<select class="form-select form-select-sm quick-assign-select" style="min-width:110px;font-size:.78rem;">
|
||||
<option value="">— Unassigned —</option>
|
||||
{% for u in staff %}
|
||||
<option value="{{ u.id }}" {{ 'selected' if issue.assigned_to == u.id }}>{{ u.display_name }}{{ ' (External)' if u.is_external_inspector }}</option>
|
||||
<option value="{{ u.id }}" {{ 'selected' if issue.assigned_to == u.id }}>{{ u.display_name }}{{ ' (Customer)' if u.is_external_inspector }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<span class="quick-assign-spinner spinner-border spinner-border-sm text-secondary d-none" role="status"></span>
|
||||
@@ -202,7 +212,7 @@
|
||||
class="d-inline"
|
||||
title="Unfollow this issue">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="next" value="{{ url_for('issues.index', page=issues.page, issue_id=issue_id_filter, severity=severity_filter, status=status_filter, contract_id=contract_filter, facility_id=facility_filter, date_from=date_from_filter, date_to=date_to_filter, reporter_id=reporter_filter) }}">
|
||||
<input type="hidden" name="next" value="{{ current_url() }}">
|
||||
<button type="submit" class="btn btn-sm btn-outline-primary p-0 px-1 me-1"
|
||||
title="Unfollow">
|
||||
<i class="bi bi-bell-slash" style="font-size:.75rem;"></i>
|
||||
@@ -210,7 +220,7 @@
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<a href="{{ url_for('issues.view', issue_id=issue.id) }}"
|
||||
<a href="{{ url_for('issues.view', issue_id=issue.id, next=current_url()) }}"
|
||||
class="btn btn-sm btn-outline-secondary">
|
||||
{% if current_user.role in ['admin','director','auditor'] or issue.assigned_to == current_user.id %}
|
||||
<i class="bi bi-pencil"></i> Edit
|
||||
@@ -223,6 +233,7 @@
|
||||
class="d-inline"
|
||||
onsubmit="return confirm('Permanently delete Issue #{{ issue.id }}? This cannot be undone.');">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="next" value="{{ current_url() }}">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger"
|
||||
title="Delete Issue #{{ issue.id }}">
|
||||
<i class="bi bi-trash"></i>
|
||||
@@ -259,6 +270,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{% include 'partials/bulk_select_js.html' %}
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
@@ -19,6 +19,30 @@
|
||||
|
||||
{% block content %}
|
||||
{% set can_edit = current_user.role in ['admin','director','auditor'] or issue.assigned_to == current_user.id %}
|
||||
{# The filtered list URL this page was opened from (phase: filter
|
||||
preservation). Threaded into every action so an update or delete
|
||||
returns to the same filtered page, and used by the Back button. #}
|
||||
{% set back_url = request.args.get('next') or url_for('issues.index') %}
|
||||
{# Is the viewer OUR staff? Drives the internal-only chrome on this page: the
|
||||
"comments are visible to everyone" warning and the per-comment
|
||||
"Customer visible" / "Staff only" badges. Both are instructions about how WE
|
||||
work and must never reach a customer account.
|
||||
|
||||
Written as an explicit ALLOWLIST of our own roles, deliberately:
|
||||
|
||||
* It FAILS CLOSED. The obvious form, `not current_user.is_customer_account`,
|
||||
fails OPEN — if the attribute is missing for any reason (a process still
|
||||
running an older models/user.py after a template-only reload, say) Jinja
|
||||
yields Undefined, `not Undefined` is true, and the internal text is shown
|
||||
to exactly the people it must be hidden from. An allowlist of literal role
|
||||
strings can only ever be true for a role we listed.
|
||||
* `external_inspector` is absent ON PURPOSE. This is NOT the rule-87 case:
|
||||
rule 87 is about capability/scoping, where a Customer Inspector must
|
||||
behave exactly like our own inspector. Here the question is "does this
|
||||
person work for us?", which is the one place the two genuinely differ.
|
||||
Do not "fix" this by adding external_inspector to the list. #}
|
||||
{% set viewer_is_our_staff = current_user.role in
|
||||
['admin', 'director', 'project_manager', 'auditor', 'inspector'] %}
|
||||
|
||||
<div class="row">
|
||||
{# ══════════════════════════════════ LEFT COLUMN ══════════════════════════════════ #}
|
||||
@@ -187,6 +211,7 @@
|
||||
<strong>Awaiting director verification.</strong>
|
||||
{% if current_user.role in ['admin','director','auditor'] %}
|
||||
<form method="POST" action="{{ url_for('issues.verify', issue_id=issue.id) }}" class="mt-2">
|
||||
<input type="hidden" name="next" value="{{ back_url }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="mb-2">
|
||||
<input type="text" name="verification_note" class="form-control form-control-sm"
|
||||
@@ -230,8 +255,12 @@
|
||||
{% else %}
|
||||
<span class="badge bg-secondary" style="font-size:.65rem;">{{ c.author.role|replace('_',' ')|title }}</span>
|
||||
{% endif %}
|
||||
{# Visibility indicator — staff only #}
|
||||
{% if current_user.role != 'customer' %}
|
||||
{# Visibility indicator — OUR staff only, and only while the
|
||||
per-comment flag still decides anything. While comments_open
|
||||
is set EVERY comment reaches the customer, so a "Staff only"
|
||||
badge would be a lie; it is suppressed rather than shown
|
||||
incorrectly. #}
|
||||
{% if viewer_is_our_staff and not comments_open %}
|
||||
{% if c.is_customer_visible %}
|
||||
<span class="badge bg-success bg-opacity-10 text-success border border-success"
|
||||
style="font-size:.6rem;" title="Customer can see this comment">
|
||||
@@ -274,6 +303,7 @@
|
||||
<div class="card-body">
|
||||
<p class="fw-semibold small mb-2">Add Comment</p>
|
||||
<form method="post">
|
||||
<input type="hidden" name="next" value="{{ back_url }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="status" value="{{ issue.status }}">
|
||||
<input type="hidden" name="assigned_to" value="{{ issue.assigned_to or 0 }}">
|
||||
@@ -281,6 +311,25 @@
|
||||
<textarea name="update_notes" class="form-control" rows="3"
|
||||
placeholder="Write a comment…" required></textarea>
|
||||
</div>
|
||||
{# While comments_open is set, every comment reaches the customer, so
|
||||
the "Share with customer" tick decides nothing. Saying so plainly
|
||||
matters: a staff member must not write something they believe is
|
||||
private. The checkbox is still posted and recorded, so turning the
|
||||
setting off restores its meaning immediately.
|
||||
|
||||
OUR STAFF ONLY. `can_edit` is also true for a Customer Inspector
|
||||
assigned to the issue, and this banner is an internal-process
|
||||
warning ("do not post internal-only notes") — showing it to a
|
||||
customer account exposes how we work and reads as nonsense to
|
||||
them, since nothing they write was ever private. #}
|
||||
{% if comments_open and viewer_is_our_staff %}
|
||||
<div class="alert alert-warning py-2 px-3 small mb-2">
|
||||
<i class="bi bi-eye me-1"></i>
|
||||
<strong>Comments are currently visible to everyone,</strong> including
|
||||
the customer, regardless of the tick below. Do not post internal-only
|
||||
notes here.
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
|
||||
<div class="form-check form-check-inline mb-0">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
@@ -301,6 +350,7 @@
|
||||
<div class="card-body">
|
||||
<p class="fw-semibold small mb-2">Add Comment</p>
|
||||
<form method="post">
|
||||
<input type="hidden" name="next" value="{{ back_url }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="mb-2">
|
||||
<textarea name="update_notes" class="form-control" rows="3"
|
||||
@@ -369,6 +419,7 @@
|
||||
<div class="card-header bg-light"><h6 class="mb-0">Update Issue</h6></div>
|
||||
<div class="card-body">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="next" value="{{ back_url }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="mb-3">
|
||||
{{ form.status.label(class="form-label fw-semibold") }}
|
||||
@@ -545,7 +596,7 @@
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center gap-2 mt-2">
|
||||
<a href="{{ url_for('issues.index') }}" class="btn btn-outline-secondary btn-sm">
|
||||
<a href="{{ back_url }}" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="bi bi-arrow-left"></i> Back to Issues
|
||||
</a>
|
||||
<a href="{{ url_for('issues.export_pdf', issue_id=issue.id) }}" class="btn btn-outline-primary btn-sm">
|
||||
@@ -583,6 +634,7 @@
|
||||
<i class="bi bi-x-circle"></i> Cancel
|
||||
</button>
|
||||
<form method="POST" action="{{ url_for('issues.delete', issue_id=issue.id) }}" class="d-inline">
|
||||
<input type="hidden" name="next" value="{{ back_url }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="bi bi-trash-fill"></i> Delete Permanently
|
||||
|
||||
Reference in New Issue
Block a user