RBACMiddleware only guarded non-staff users, so any staff account stepped past the role routing for /admin/ — an ops or buyer account with the flag set could reach the admin index, which embeds the model registry, and a later permission grant would have been inherited. Restrict /admin/ to the admin role (superuser excepted) and hide the ops Admin link from non-admins. Also close the second finding: the seed set is_staff only on creation, so a buyer whose flag had drifted stayed staff on every re-run. Derive it from the role on the update path too, and align existing rows with a one-off migration so the flag never disagrees with the role.
80 lines
6.4 KiB
HTML
80 lines
6.4 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{% block title %}IXG Operations{% endblock %}</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
<style>
|
|
body { font-size: .875rem; background: #f5f6fa; }
|
|
.sidebar { position: fixed; top: 0; bottom: 0; left: 0; z-index: 100; padding: 48px 0 0; width: 240px; background: #1e2a3a; }
|
|
.sidebar .nav-link { color: #a0aec0; padding: .65rem 1.25rem; font-size: .875rem; }
|
|
.sidebar .nav-link:hover, .sidebar .nav-link.active { color: #fff; background: rgba(255,255,255,.08); }
|
|
.sidebar .nav-link i { margin-right: .5rem; }
|
|
.sidebar .brand { padding: 1rem 1.25rem; font-size: 1.1rem; font-weight: 700; color: #fff; border-bottom: 1px solid rgba(255,255,255,.08); margin-bottom: .5rem; }
|
|
.main { margin-left: 240px; }
|
|
.topbar { background: #fff; border-bottom: 1px solid #e2e8f0; padding: .75rem 1.5rem; display: flex; justify-content: space-between; align-items: center; }
|
|
.content { padding: 1.5rem; }
|
|
.stat-card { background: #fff; border-radius: .5rem; padding: 1.25rem; border: 1px solid #e2e8f0; }
|
|
.stat-card .stat-value { font-size: 1.75rem; font-weight: 700; }
|
|
.stat-card .stat-label { font-size: .8rem; color: #718096; text-transform: uppercase; letter-spacing: .05em; }
|
|
.badge-status { font-size: .75rem; }
|
|
.page-header { margin-bottom: 1.5rem; }
|
|
.page-header h2 { font-weight: 600; }
|
|
.filter-bar { background: #fff; border-radius: .5rem; padding: 1rem; border: 1px solid #e2e8f0; margin-bottom: 1.5rem; }
|
|
.guide-content img { max-width: 100%; height: auto; border-radius: .375rem; }
|
|
.guide-content table { width: 100%; border-collapse: collapse; margin-bottom: 1rem; }
|
|
.guide-content th, .guide-content td { border: 1px solid #e2e8f0; padding: .5rem .75rem; }
|
|
.guide-content pre { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: .375rem; padding: .75rem; }
|
|
.guide-content blockquote { border-left: 4px solid #a7f3d0; padding-left: 1rem; color: #475569; }
|
|
</style>
|
|
{% block extra_head %}{% endblock %}
|
|
</head>
|
|
<body>
|
|
|
|
<div class="sidebar">
|
|
<div class="brand">IXG Platform</div>
|
|
<ul class="nav flex-column">
|
|
<li class="nav-item"><a class="nav-link {% if request.resolver_match.url_name == 'dashboard' %}active{% endif %}" href="{% url 'ops:dashboard' %}"><i class="bi bi-speedometer2"></i> Dashboard</a></li>
|
|
<li class="nav-item"><a class="nav-link {% if 'intake' in request.resolver_match.url_name %}active{% endif %}" href="{% url 'ops:intake_list' %}"><i class="bi bi-basket"></i> Harvest Intakes</a></li>
|
|
<li class="nav-item"><a class="nav-link {% if 'facility' in request.resolver_match.url_name %}active{% endif %}" href="{% url 'ops:facility_list' %}"><i class="bi bi-building"></i> Facilities</a></li>
|
|
<li class="nav-item"><a class="nav-link {% if 'origin_zone' in request.resolver_match.url_name %}active{% endif %}" href="{% url 'ops:origin_zone_list' %}"><i class="bi bi-geo-alt"></i> Origin Zones</a></li>
|
|
<li class="nav-item"><a class="nav-link {% if 'collector' in request.resolver_match.url_name %}active{% endif %}" href="{% url 'ops:collector_group_list' %}"><i class="bi bi-people"></i> Collector Groups</a></li>
|
|
<li class="nav-item"><a class="nav-link {% if 'specification' in request.resolver_match.url_name %}active{% endif %}" href="{% url 'ops:specification_list' %}"><i class="bi bi-clipboard-data"></i> Specifications</a></li>
|
|
<li class="nav-item"><a class="nav-link {% if 'batch' in request.resolver_match.url_name %}active{% endif %}" href="{% url 'ops:batch_list' %}"><i class="bi bi-box-seam"></i> Batches</a></li>
|
|
<li class="nav-item"><a class="nav-link {% if 'qc' in request.resolver_match.url_name %}active{% endif %}" href="{% url 'ops:qc_list' %}"><i class="bi bi-clipboard-check"></i> QC Results</a></li>
|
|
<li class="nav-item"><a class="nav-link {% if 'verify' in request.resolver_match.url_name %}active{% endif %}" href="{% url 'ops:qc_verify_list' %}"><i class="bi bi-person-check"></i> QC Approvals</a></li>
|
|
<li class="nav-item"><a class="nav-link {% if 'coa' in request.resolver_match.url_name %}active{% endif %}" href="{% url 'ops:coa_review_list' %}"><i class="bi bi-file-earmark-medical"></i> CoA Reviews</a></li>
|
|
<li class="nav-item"><a class="nav-link {% if 'eudr' in request.resolver_match.url_name %}active{% endif %}" href="{% url 'ops:eudr_list' %}"><i class="bi bi-shield-check"></i> EUDR Compliance</a></li>
|
|
<li class="nav-item"><a class="nav-link {% if 'shipment' in request.resolver_match.url_name %}active{% endif %}" href="{% url 'ops:shipment_list' %}"><i class="bi bi-truck"></i> Shipments</a></li>
|
|
<li class="nav-item"><a class="nav-link {% if 'capa' in request.resolver_match.url_name %}active{% endif %}" href="{% url 'ops:capa_list' %}"><i class="bi bi-exclamation-triangle"></i> CAPA Records</a></li>
|
|
<li class="nav-item"><a class="nav-link {% if 'document' in request.resolver_match.url_name %}active{% endif %}" href="{% url 'ops:document_list' %}"><i class="bi bi-file-earmark-text"></i> Documents</a></li>
|
|
<li class="nav-item"><a class="nav-link {% if 'esg' in request.resolver_match.url_name %}active{% endif %}" href="{% url 'ops:esg_list' %}"><i class="bi bi-tree"></i> ESG Reports</a></li>
|
|
<li class="nav-item"><a class="nav-link {% if 'guide' in request.resolver_match.url_name %}active{% endif %}" href="{% url 'ops:guide_index' %}"><i class="bi bi-book"></i> Help</a></li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="main">
|
|
<div class="topbar">
|
|
<span></span>
|
|
<div class="d-flex align-items-center gap-3">
|
|
{% if request.user.role == 'admin' or request.user.is_superuser %}
|
|
<a href="/admin/" class="text-decoration-none text-secondary"><i class="bi bi-shield-lock"></i> Admin</a>
|
|
{% endif %}
|
|
<span class="text-secondary">{{ request.user.full_name|default:request.user.email }}</span>
|
|
<form method="post" action="{% url 'oidc_logout' %}" class="m-0">
|
|
{% csrf_token %}
|
|
<button type="submit" class="btn btn-sm btn-outline-secondary">Logout</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="content">
|
|
{% block content %}{% endblock %}
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
{% block extra_js %}{% endblock %}
|
|
</body>
|
|
</html>
|