vSphere-Backup-Manager/vsphere_backup/templates/base.html

400 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{% block title %}vSphere Backup Manager{% endblock %}</title>
<meta name="description" content="Enterprise vSphere VM backup management — schedule, monitor and manage VM backups." />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<style>
:root {
--bg-base: #080a10;
--bg-surface: #0e111a;
--bg-card: rgba(18, 22, 35, 0.6);
--bg-card-hover: rgba(24, 29, 45, 0.85);
--border: rgba(255, 255, 255, 0.05);
--border-bright: rgba(124, 107, 255, 0.25);
--accent: #6366f1; /* Indigo */
--accent-2: #06b6d4; /* Cyan */
--accent-gradient: linear-gradient(135deg, #6366f1 0%, #06b6d4 100%);
--accent-glow: rgba(99, 102, 241, 0.25);
--success: #10b981; /* Emerald */
--warning: #f59e0b; /* Amber */
--danger: #ef4444; /* Red */
--text-primary: #f8fafc;
--text-secondary:#94a3b8;
--text-muted: #64748b;
--sidebar-w: 260px;
--radius: 16px;
--radius-sm: 10px;
--shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.5), 0 1px 1px rgba(255, 255, 255, 0.05) inset;
--shadow-hover: 0 20px 40px -15px rgba(99, 102, 241, 0.3), 0 1px 2px rgba(255, 255, 255, 0.1) inset;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
background: radial-gradient(circle at 50% 0%, #151926 0%, var(--bg-base) 80%);
color: var(--text-primary);
display: flex;
min-height: 100vh;
font-size: 14px;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
/* ── Sidebar ── */
.sidebar {
width: var(--sidebar-w);
min-height: 100vh;
background: rgba(14, 17, 26, 0.85);
backdrop-filter: blur(20px);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
padding: 0;
position: fixed;
left: 0; top: 0; bottom: 0;
z-index: 100;
transition: all 0.3s ease;
}
.sidebar-logo {
padding: 28px 24px;
border-bottom: 1px solid var(--border);
}
.sidebar-logo .logo-mark {
display: flex;
align-items: center;
gap: 12px;
}
.logo-icon {
width: 40px; height: 40px;
background: var(--accent-gradient);
border-radius: var(--radius-sm);
display: flex; align-items: center; justify-content: center;
font-size: 20px;
box-shadow: 0 0 20px var(--accent-glow);
transition: transform 0.3s ease;
}
.sidebar:hover .logo-icon {
transform: rotate(5deg) scale(1.05);
}
.logo-text { font-size: 16px; font-weight: 700; color: var(--text-primary); letter-spacing: -0.02em; }
.logo-sub { font-size: 11px; color: var(--text-secondary); margin-top: 1px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; }
.sidebar-nav { flex: 1; padding: 24px 16px; }
.nav-section-label {
font-size: 11px; font-weight: 700; letter-spacing: 0.08em;
color: var(--text-muted); text-transform: uppercase;
padding: 0 12px; margin: 16px 0 8px;
}
.nav-link {
display: flex; align-items: center; gap: 12px;
padding: 10px 14px; border-radius: var(--radius-sm);
color: var(--text-secondary); text-decoration: none;
font-size: 14px; font-weight: 500;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
margin-bottom: 4px;
border: 1px solid transparent;
}
.nav-link:hover {
background: rgba(255, 255, 255, 0.02);
color: var(--text-primary);
border-color: rgba(255, 255, 255, 0.03);
transform: translateX(4px);
}
.nav-link.active {
background: rgba(99, 102, 241, 0.1);
color: #a5b4fc;
border-color: rgba(99, 102, 241, 0.2);
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.05);
font-weight: 600;
}
.nav-link .icon { font-size: 18px; width: 22px; text-align: center; }
.sidebar-footer {
padding: 20px 24px;
border-top: 1px solid var(--border);
background: rgba(8, 10, 16, 0.4);
}
.server-badge {
background: rgba(255, 255, 255, 0.02);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 10px 14px;
font-size: 12px;
margin-bottom: 12px;
}
.server-badge .server-host { font-weight: 600; color: var(--accent-2); word-break: break-all; margin-bottom: 2px; }
.server-badge .server-user { color: var(--text-muted); }
/* ── Main content ── */
.main {
margin-left: var(--sidebar-w);
flex: 1;
display: flex;
flex-direction: column;
min-height: 100vh;
transition: margin-left 0.3s ease;
}
.topbar {
padding: 24px 40px;
border-bottom: 1px solid var(--border);
background: rgba(8, 10, 16, 0.7);
backdrop-filter: blur(16px);
display: flex; align-items: center; justify-content: space-between;
position: sticky; top: 0; z-index: 50;
}
.topbar-title { font-size: 22px; font-weight: 700; letter-spacing: -0.02em; }
.topbar-subtitle { font-size: 13px; color: var(--text-secondary); margin-top: 4px; }
.topbar-actions { display: flex; align-items: center; gap: 12px; }
.content { padding: 32px 40px; flex: 1; max-width: 1400px; width: 100%; margin: 0 auto; }
/* ── Buttons ── */
.btn {
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
padding: 10px 20px; border-radius: var(--radius-sm);
font-size: 13.5px; font-weight: 600;
cursor: pointer; border: 1px solid transparent; text-decoration: none;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
line-height: 1.2;
white-space: nowrap;
}
.btn-primary {
background: var(--accent-gradient);
color: #ffffff;
box-shadow: 0 4px 14px var(--accent-glow);
}
.btn-primary:hover {
transform: translateY(-1.5px);
box-shadow: 0 6px 20px var(--accent-glow);
filter: brightness(1.05);
}
.btn-primary:active { transform: translateY(0); }
.btn-secondary {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
color: var(--text-primary);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.07);
border-color: rgba(255, 255, 255, 0.15);
transform: translateY(-1px);
}
.btn-danger {
background: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.2);
color: #fca5a5;
}
.btn-danger:hover {
background: rgba(239, 68, 68, 0.2);
border-color: rgba(239, 68, 68, 0.35);
color: #ffffff;
transform: translateY(-1px);
}
.btn-sm { padding: 8px 14px; font-size: 12.5px; }
.btn-ghost { background: transparent; border: 1px solid var(--border); color: var(--text-secondary); }
.btn-ghost:hover { border-color: var(--border-bright); color: var(--text-primary); background: rgba(255,255,255,0.01); }
/* ── Cards ── */
.card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
backdrop-filter: blur(12px);
transition: all 0.25s ease;
}
.card:hover {
border-color: var(--border-bright);
box-shadow: var(--shadow-hover);
}
.card-header {
padding: 20px 24px;
border-bottom: 1px solid var(--border);
display: flex; align-items: center; justify-content: space-between;
}
.card-title { font-size: 16px; font-weight: 600; }
.card-body { padding: 24px; }
/* ── Badges / Status chips ── */
.badge {
display: inline-flex; align-items: center; gap: 6px;
padding: 4px 12px; border-radius: 100px;
font-size: 11px; font-weight: 600; letter-spacing: 0.03em;
border: 1px solid transparent;
text-transform: uppercase;
}
.badge::before { content: ''; width: 6px; height: 6px; border-radius: 50%; display: inline-block; }
.badge-green { background: rgba(16, 185, 129, 0.08); border-color: rgba(16, 185, 129, 0.2); color: #34d399; }
.badge-green::before { background: #10b981; box-shadow: 0 0 8px #10b981; }
.badge-red { background: rgba(239, 68, 68, 0.08); border-color: rgba(239, 68, 68, 0.2); color: #f87171; }
.badge-red::before { background: #ef4444; box-shadow: 0 0 8px #ef4444; }
.badge-yellow { background: rgba(245, 158, 11, 0.08); border-color: rgba(245, 158, 11, 0.2); color: #fbbf24; }
.badge-yellow::before { background: #f59e0b; box-shadow: 0 0 8px #f59e0b; }
.badge-gray { background: rgba(148, 163, 184, 0.08); border-color: rgba(148, 163, 184, 0.2); color: #cbd5e1; }
.badge-gray::before { background: #94a3b8; }
.badge-purple { background: rgba(99, 102, 241, 0.08); border-color: rgba(99, 102, 241, 0.25); color: #c7d2fe; }
.badge-purple::before { background: #6366f1; box-shadow: 0 0 8px #6366f1; }
/* ── Form elements ── */
.form-group { margin-bottom: 20px; }
.form-label {
display: block; font-size: 13px; font-weight: 600;
color: var(--text-secondary); margin-bottom: 8px;
letter-spacing: 0.01em;
}
.form-control {
width: 100%;
background: rgba(8, 10, 16, 0.5);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text-primary);
padding: 12px 16px;
font-size: 14px;
font-family: inherit;
transition: all 0.2s ease;
outline: none;
box-shadow: 0 2px 4px rgba(0,0,0,0.1) inset;
}
.form-control:hover {
border-color: rgba(255,255,255,0.1);
}
.form-control:focus {
border-color: var(--accent);
background: rgba(8, 10, 16, 0.8);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15), 0 2px 4px rgba(0,0,0,0.15) inset;
}
.form-control::placeholder { color: var(--text-muted); }
select.form-control { cursor: pointer; }
select.form-control option { background: var(--bg-surface); color: var(--text-primary); }
.form-check { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.form-check input[type=checkbox] {
accent-color: var(--accent);
width: 18px; height: 18px;
cursor: pointer;
border-radius: 4px;
}
.form-check label { font-size: 13.5px; color: var(--text-secondary); cursor: pointer; user-select: none; }
/* ── Alert / Flash ── */
.alert {
padding: 14px 20px; border-radius: var(--radius-sm);
margin-bottom: 24px; font-size: 14px;
display: flex; align-items: center; gap: 12px;
border: 1px solid transparent;
box-shadow: 0 4px 15px rgba(0,0,0,0.15);
backdrop-filter: blur(8px);
}
.alert-danger { background: rgba(239, 68, 68, 0.08); border-color: rgba(239, 68, 68, 0.25); color: #fca5a5; }
.alert-success { background: rgba(16, 185, 129, 0.08); border-color: rgba(16, 185, 129, 0.25); color: #a7f3d0; }
.alert-info { background: rgba(6, 182, 212, 0.08); border-color: rgba(6, 182, 212, 0.2); color: #c5f2f7; }
/* ── Table ── */
table { width: 100%; border-collapse: collapse; }
th {
text-align: left; padding: 14px 20px;
font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
color: var(--text-muted); border-bottom: 1px solid var(--border);
}
td { padding: 16px 20px; border-bottom: 1px solid var(--border); vertical-align: middle; }
tr:last-child td { border-bottom: none; }
tr { transition: background 0.15s; }
tr:hover td { background: rgba(255, 255, 255, 0.015); }
/* ── Utilities ── */
.text-muted { color: var(--text-muted); }
.text-secondary { color: var(--text-secondary); }
.text-small { font-size: 12.5px; }
.mono { font-family: 'JetBrains Mono', monospace; font-size: 13px; }
.flex { display: flex; }
.flex-center { display: flex; align-items: center; }
.gap-2 { gap: 8px; }
.gap-3 { gap: 12px; }
.mt-1 { margin-top: 6px; }
.mt-2 { margin-top: 12px; }
.mt-3 { margin-top: 20px; }
.mb-1 { margin-bottom: 6px; }
.mb-2 { margin-bottom: 12px; }
/* ── Spinner ── */
@keyframes spin { to { transform: rotate(360deg); } }
.spinner {
width: 18px; height: 18px;
border: 2.5px solid rgba(255,255,255,.15);
border-top-color: var(--accent-2);
border-radius: 50%;
animation: spin .7s linear infinite;
display: inline-block;
}
/* ── Scrollbar ── */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.08); border-radius: 10px; }
::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.15); }
</style>
{% block head %}{% endblock %}
</head>
<body>
{% if session.get('host') %}
<aside class="sidebar">
<div class="sidebar-logo">
<div class="logo-mark">
<div class="logo-icon">🛡</div>
<div>
<div class="logo-text">vSphere Backup</div>
<div class="logo-sub">Manager</div>
</div>
</div>
</div>
<nav class="sidebar-nav">
<div class="nav-section-label">Navigation</div>
<a href="/vms" class="nav-link {% if active_page == 'vms' %}active{% endif %}">
<span class="icon">🖥</span> Virtual Machines
</a>
<a href="/jobs" class="nav-link {% if active_page == 'jobs' %}active{% endif %}">
<span class="icon">📋</span> Backup Jobs
</a>
<a href="/jobs/create" class="nav-link {% if active_page == 'create_job' %}active{% endif %}">
<span class="icon"></span> Create Job
</a>
<a href="/nfs" class="nav-link {% if active_page == 'nfs' %}active{% endif %}">
<span class="icon">📡</span> NFS Manager
</a>
</nav>
<div class="sidebar-footer">
<div class="server-badge">
<div class="server-host">{{ session.get('host', '—') }}</div>
<div class="server-user text-small">{{ session.get('user', '') }}</div>
</div>
<a href="/logout" class="btn btn-ghost btn-sm" style="width:100%;justify-content:center;margin-top:10px;">
⬅ Logout
</a>
</div>
</aside>
{% endif %}
<main class="main" {% if not session.get('host') %}style="margin-left:0"{% endif %}>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div style="padding:16px 32px 0">
{% for cat, msg in messages %}
<div class="alert alert-{{ cat }}">{{ msg }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</main>
{% block scripts %}{% endblock %}
</body>
</html>