Files
enterprise_digital-platform/IT_asset_management/app/templates/base.html
T
ske087 c4e718ac4b IT Assets: add dark mode matching portal color scheme
- Inline <script> in <head> applies edp_theme from localStorage before first
  paint — no flash of unstyled content
- Bootstrap 5.3 data-bs-theme + custom data-edp-theme attribute for overrides
- Dark palette mirrors portal (bg #0d1117, surface #161b22, card #1c2128,
  accent-blue #388bfd, text #e6edf3, borders #30363d)
- CSS tokens for both themes via :root and [data-edp-theme='dark']
- All Bootstrap components overridden in dark mode: cards, tables, forms,
  modals, alerts, breadcrumbs, code, badges, inputs
- Moon/sun toggle button in topbar; sun shown in dark mode, moon in light
- Shared localStorage key 'edp_theme' — setting persists across all EDP apps
2026-07-09 00:20:06 +03:00

356 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}IT Asset Management{% endblock %}</title>
<!-- Apply theme BEFORE paint to avoid flash -->
<script>
(function(){
var t = localStorage.getItem('edp_theme') || 'light';
document.documentElement.setAttribute('data-bs-theme', t);
document.documentElement.setAttribute('data-edp-theme', t);
})();
</script>
<!-- Bootstrap 5 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<style>
/* ── Light-mode tokens ──────────────────────────────────────────────── */
:root {
--sidebar-bg: #1a3a5c;
--sidebar-text: #cce0f5;
--sidebar-active: #2e6da4;
--accent: #2e86de;
--page-bg: #f0f4f8;
--topbar-bg: #ffffff;
--topbar-border: #dde3ea;
--card-bg: #ffffff;
--card-border: #e2e8f0;
--text-primary: #1a202c;
--text-muted: #718096;
--hover-row: #f5f8ff;
--header-color: #1a3a5c;
}
/* ── Dark-mode tokens (portal palette) ─────────────────────────────── */
[data-edp-theme="dark"] {
--sidebar-bg: #0d1117;
--sidebar-text: #8b949e;
--sidebar-active: #1c2128;
--accent: #388bfd;
--page-bg: #0d1117;
--topbar-bg: #161b22;
--topbar-border: #30363d;
--card-bg: #1c2128;
--card-border: #30363d;
--text-primary: #e6edf3;
--text-muted: #8b949e;
--hover-row: #21262d;
--header-color: #e6edf3;
}
body { background: var(--page-bg); font-size: .92rem; color: var(--text-primary); transition: background .2s, color .2s; }
/* Sidebar */
#sidebar {
min-height: 100vh;
width: 240px;
background: var(--sidebar-bg);
position: fixed;
top: 0; left: 0;
display: flex; flex-direction: column;
z-index: 1000;
transition: background .2s;
}
#sidebar .brand {
padding: 1.1rem 1.2rem;
font-size: 1.05rem;
font-weight: 700;
color: #fff;
border-bottom: 1px solid rgba(255,255,255,.12);
letter-spacing: .02em;
}
#sidebar .brand small { font-weight: 400; font-size: .7rem; color: var(--sidebar-text); display:block; }
#sidebar .nav-section {
font-size: .68rem;
text-transform: uppercase;
letter-spacing: .08em;
color: rgba(255,255,255,.35);
padding: .75rem 1.2rem .25rem;
}
#sidebar .nav-link {
color: var(--sidebar-text);
padding: .45rem 1.2rem;
border-radius: 0;
display: flex; align-items: center; gap: .6rem;
font-size: .88rem;
}
#sidebar .nav-link:hover,
#sidebar .nav-link.active {
background: var(--sidebar-active);
color: #fff;
}
#sidebar .nav-link i { font-size: 1rem; width: 1.2rem; text-align: center; }
#sidebar .sidebar-footer {
margin-top: auto;
padding: .8rem 1.2rem;
border-top: 1px solid rgba(255,255,255,.12);
font-size: .8rem;
color: var(--sidebar-text);
}
/* Main content */
#main-wrapper { margin-left: 240px; min-height: 100vh; display: flex; flex-direction: column; }
#topbar {
background: var(--topbar-bg);
border-bottom: 1px solid var(--topbar-border);
padding: .55rem 1.5rem;
display: flex; align-items: center; justify-content: space-between;
position: sticky; top: 0; z-index: 900;
box-shadow: 0 1px 4px rgba(0,0,0,.06);
transition: background .2s, border-color .2s;
}
#topbar .breadcrumb { margin: 0; background: none; padding: 0; font-size: .85rem; }
#page-content { flex: 1; padding: 1.5rem; }
/* Theme toggle button */
#themeToggle {
background: none;
border: none;
cursor: pointer;
font-size: 1rem;
color: var(--text-muted);
padding: .25rem .4rem;
border-radius: 4px;
transition: color .15s;
}
#themeToggle:hover { color: var(--accent); }
/* Cards */
.stat-card { border: none; border-radius: .6rem; box-shadow: 0 2px 8px rgba(0,0,0,.07); }
.stat-card .card-body { padding: 1.1rem 1.3rem; }
.stat-card .stat-icon { font-size: 2rem; opacity: .85; }
.stat-card .stat-value { font-size: 2rem; font-weight: 700; line-height: 1; }
.stat-card .stat-label { font-size: .8rem; text-transform: uppercase; letter-spacing: .05em; opacity: .8; }
/* Badges */
.badge-available { background:#198754 !important; }
.badge-assigned { background:#0d6efd !important; }
.badge-maintenance{ background:#ffc107 !important; color:#000 !important; }
.badge-retired { background:#6c757d !important; }
.badge-lost { background:#dc3545 !important; }
.badge-masked { background:#6f42c1 !important; }
/* Tables */
.table-hover tbody tr:hover { background: var(--hover-row); }
/* Masked row */
tr.masked-row { opacity: .65; font-style: italic; }
/* Search bar */
.search-bar { max-width: 360px; }
/* Page header */
.page-header h1 { font-size: 1.35rem; font-weight: 700; color: var(--header-color); margin: 0; }
/* ── Dark-mode Bootstrap overrides ──────────────────────────────────── */
[data-edp-theme="dark"] .card { background: var(--card-bg); border-color: var(--card-border); }
[data-edp-theme="dark"] .card-header,
[data-edp-theme="dark"] .card-footer { background: var(--card-bg) !important; border-color: var(--card-border) !important; }
[data-edp-theme="dark"] #topbar { box-shadow: 0 1px 4px rgba(0,0,0,.4); }
[data-edp-theme="dark"] .table { --bs-table-hover-bg: var(--hover-row); color: var(--text-primary); }
[data-edp-theme="dark"] .table-light { --bs-table-bg: #161b22; color: var(--text-muted); }
[data-edp-theme="dark"] .table { border-color: var(--card-border); }
[data-edp-theme="dark"] .form-control,
[data-edp-theme="dark"] .form-select { background-color: #0d1117; color: var(--text-primary); border-color: var(--card-border); }
[data-edp-theme="dark"] .form-control:focus,
[data-edp-theme="dark"] .form-select:focus { background-color: #0d1117; color: var(--text-primary); border-color: var(--accent); box-shadow: 0 0 0 .25rem rgba(56,139,253,.2); }
[data-edp-theme="dark"] .form-control::placeholder { color: var(--text-muted); }
[data-edp-theme="dark"] .input-group-text { background-color: #161b22; border-color: var(--card-border); color: var(--text-muted); }
[data-edp-theme="dark"] .breadcrumb-item,
[data-edp-theme="dark"] .breadcrumb-item.active { color: var(--text-secondary, #8b949e); }
[data-edp-theme="dark"] .breadcrumb-item a { color: var(--accent); }
[data-edp-theme="dark"] .text-muted { color: var(--text-muted) !important; }
[data-edp-theme="dark"] .bg-white { background-color: var(--card-bg) !important; }
[data-edp-theme="dark"] .bg-light { background-color: #161b22 !important; }
[data-edp-theme="dark"] .border,
[data-edp-theme="dark"] .border-bottom { border-color: var(--card-border) !important; }
[data-edp-theme="dark"] .modal-content { background: var(--card-bg); border-color: var(--card-border); }
[data-edp-theme="dark"] .modal-header,
[data-edp-theme="dark"] .modal-footer { border-color: var(--card-border); }
[data-edp-theme="dark"] .alert-success { background: rgba(63,185,80,.12); border-color: rgba(63,185,80,.3); color: #3fb950; }
[data-edp-theme="dark"] .alert-info { background: rgba(56,139,253,.12); border-color: rgba(56,139,253,.3); color: #79c0ff; }
[data-edp-theme="dark"] .alert-warning { background: rgba(210,153,34,.12); border-color: rgba(210,153,34,.3); color: #e3b341; }
[data-edp-theme="dark"] .alert-danger { background: rgba(248,81,73,.12); border-color: rgba(248,81,73,.3); color: #f85149; }
[data-edp-theme="dark"] .btn-close { filter: invert(1); }
[data-edp-theme="dark"] code { background: rgba(56,139,253,.12); color: #79c0ff; padding: 1px 5px; border-radius: 3px; }
[data-edp-theme="dark"] .page-header h1 { color: var(--text-primary); }
[data-edp-theme="dark"] .badge.bg-secondary { background-color: #30363d !important; }
</style>
{% block extra_head %}{% endblock %}
</head>
<body>
<!-- ===== SIDEBAR ===== -->
<nav id="sidebar">
<div class="brand">
<i class="bi bi-hdd-rack-fill me-2"></i>IT Assets
<small>Hardware Management</small>
</div>
<div class="nav-section">Main</div>
<a href="{{ url_for('dashboard.index') }}"
class="nav-link {% if request.endpoint == 'dashboard.index' %}active{% endif %}">
<i class="bi bi-speedometer2"></i> Dashboard
</a>
<div class="nav-section">People</div>
<a href="{{ url_for('users.index') }}"
class="nav-link {% if request.blueprint == 'users' %}active{% endif %}">
<i class="bi bi-people-fill"></i> Users
</a>
{% if current_user.is_editor %}
<a href="{{ url_for('users.import_page') }}"
class="nav-link {% if request.endpoint == 'users.import_page' %}active{% endif %}">
<i class="bi bi-cloud-download"></i> Import Users
</a>
{% endif %}
<div class="nav-section">Hardware</div>
<a href="{{ url_for('assets.index') }}"
class="nav-link {% if request.blueprint == 'assets' %}active{% endif %}">
<i class="bi bi-laptop"></i> Assets
</a>
<a href="{{ url_for('maintenance.index') }}"
class="nav-link {% if request.blueprint == 'maintenance' %}active{% endif %}">
<i class="bi bi-wrench-adjustable"></i> Maintenance
</a>
{% if current_user.is_editor %}
<a href="{{ url_for('assets.create') }}"
class="nav-link {% if request.endpoint == 'assets.create' %}active{% endif %}">
<i class="bi bi-plus-circle"></i> Add Asset
</a>
{% endif %}
<div class="nav-section">Assignments</div>
<a href="{{ url_for('assignments.index') }}"
class="nav-link {% if request.blueprint == 'assignments' %}active{% endif %}">
<i class="bi bi-arrow-left-right"></i> Assignments
</a>
{% if current_user.is_editor %}
<a href="{{ url_for('assignments.create') }}"
class="nav-link {% if request.endpoint == 'assignments.create' %}active{% endif %}">
<i class="bi bi-plus-circle"></i> Assign Asset
</a>
{% endif %}
<div class="nav-section">Documents</div>
<a href="{{ url_for('paperwork.index') }}"
class="nav-link {% if request.blueprint == 'paperwork' %}active{% endif %}">
<i class="bi bi-file-earmark-text"></i> Paperwork
</a>
{% if current_user.is_editor %}
<a href="{{ url_for('paperwork.create') }}"
class="nav-link {% if request.endpoint == 'paperwork.create' %}active{% endif %}">
<i class="bi bi-file-earmark-plus"></i> New Document
</a>
{% endif %}
<a href="{{ url_for('doc_templates.index') }}"
class="nav-link {% if request.blueprint == 'doc_templates' %}active{% endif %}">
<i class="bi bi-file-earmark-word"></i> Templates
</a>
<div class="nav-section">System</div>
<a href="{{ url_for('audit.index') }}"
class="nav-link {% if request.blueprint == 'audit' %}active{% endif %}">
<i class="bi bi-shield-check"></i> Audit Log
</a>
<a href="{{ url_for('settings.index') }}"
class="nav-link {% if request.blueprint == 'settings' %}active{% endif %}">
<i class="bi bi-gear"></i> Settings
</a>
<div class="sidebar-footer">
<i class="bi bi-person-circle me-1"></i>
<strong>{{ current_user.username }}</strong>
<span class="badge ms-1 {% if current_user.role == 'admin' %}bg-danger{% elif current_user.role == 'editor' %}bg-primary{% else %}bg-secondary{% endif %}" style="font-size:.65rem;">
{{ current_user.role }}
</span>
<a href="/" class="ms-2 text-info text-decoration-none" title="Back to Portal">
<i class="bi bi-house-door"></i>
</a>
<a href="{{ url_for('auth.logout') }}" class="ms-1 text-warning text-decoration-none" title="Sign out">
<i class="bi bi-box-arrow-right"></i>
</a>
</div>
</nav>
<!-- ===== MAIN WRAPPER ===== -->
<div id="main-wrapper">
<!-- Topbar -->
<div id="topbar">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">{% block breadcrumb %}<li class="breadcrumb-item active">Home</li>{% endblock %}</ol>
</nav>
<div class="d-flex align-items-center gap-3">
<span class="text-muted" style="font-size:.8rem;">
<i class="bi bi-calendar3"></i>
{{ now.strftime('%d %b %Y') if now else '' }}
</span>
<button id="themeToggle" title="Toggle dark / light mode">
<i class="bi bi-moon-stars-fill" id="themeIcon"></i>
</button>
</div>
</div>
<!-- Flash messages -->
<div id="page-content">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for cat, msg in messages %}
<div class="alert alert-{{ 'danger' if cat == 'error' else cat }} alert-dismissible fade show mb-3" role="alert">
{{ msg }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
/* ── Theme toggle (shared edp_theme key with portal) ── */
(function () {
var icon = document.getElementById('themeIcon');
var btn = document.getElementById('themeToggle');
function applyTheme(t) {
document.documentElement.setAttribute('data-bs-theme', t);
document.documentElement.setAttribute('data-edp-theme', t);
if (icon) {
icon.className = t === 'dark' ? 'bi bi-sun-fill' : 'bi bi-moon-stars-fill';
}
localStorage.setItem('edp_theme', t);
}
// Init icon from current theme
var current = localStorage.getItem('edp_theme') || 'light';
applyTheme(current);
if (btn) {
btn.addEventListener('click', function () {
var next = document.documentElement.getAttribute('data-edp-theme') === 'dark' ? 'light' : 'dark';
applyTheme(next);
});
}
})();
</script>
{% block extra_js %}{% endblock %}
</body>
</html>