servercontrol/templates/base.html.twig

423 lines
16 KiB
Twig

<!DOCTYPE html>
<html lang="fr" data-bs-theme="dark">
<head>
<link rel="icon" type="image/svg+xml" data-favicon="yb" href="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 64 64%22%3E%3Ctitle%3EServerControl%3C/title%3E%3Crect width=%2264%22 height=%2264%22 rx=%2214%22 fill=%22#0d6efd%22/%3E%3Cg fill=%22#fff%22%3E%3Crect x=%2213%22 y=%2212%22 width=%2238%22 height=%2211%22 rx=%223.5%22/%3E%3Crect x=%2213%22 y=%2226.5%22 width=%2238%22 height=%2211%22 rx=%223.5%22/%3E%3Crect x=%2213%22 y=%2241%22 width=%2238%22 height=%2211%22 rx=%223.5%22/%3E%3C/g%3E%3Cg fill=%22#0d6efd%22%3E%3Ccircle cx=%2219.5%22 cy=%2217.5%22 r=%222%22/%3E%3Ccircle cx=%2219.5%22 cy=%2232%22 r=%222%22/%3E%3Ccircle cx=%2219.5%22 cy=%2246.5%22 r=%222%22/%3E%3Crect x=%2234%22 y=%2216%22 width=%2212%22 height=%223%22 rx=%221.5%22/%3E%3Crect x=%2234%22 y=%2230.5%22 width=%2212%22 height=%223%22 rx=%221.5%22/%3E%3C/g%3E%3Ccircle cx=%2240%22 cy=%2246.5%22 r=%222.4%22 fill=%22#22c55e%22/%3E%3Ccircle cx=%2246%22 cy=%2246.5%22 r=%222.4%22 fill=%22#0d6efd%22/%3E%3C/svg%3E">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}ServerControl{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
<style>
:root {
--text: #e8ede8;
--background: #0b0f0c;
--primary: #b3c7b4;
--secondary: #4b435b;
--accent: #997595;
--sidebar-bg: #0d1210;
--card-bg: #111711;
--border: #1e2820;
--muted: #56695a;
--input-bg: #0d1210;
}
@media (prefers-color-scheme: light) {
:root {
--text: #121712;
--background: #f0f4f1;
--primary: #384c39;
--secondary: #aca4bc;
--accent: #8a6686;
--sidebar-bg: #e7ece8;
--card-bg: #ffffff;
--border: #d2dad3;
--muted: #7a8c7b;
--input-bg: #ffffff;
}
}
[data-bs-theme="dark"] {
--text: #e8ede8;
--background: #0b0f0c;
--primary: #b3c7b4;
--secondary: #4b435b;
--accent: #997595;
--sidebar-bg: #0d1210;
--card-bg: #111711;
--border: #1e2820;
--muted: #56695a;
--input-bg: #0d1210;
}
[data-bs-theme="light"] {
--text: #121712;
--background: #f0f4f1;
--primary: #384c39;
--secondary: #aca4bc;
--accent: #8a6686;
--sidebar-bg: #e7ece8;
--card-bg: #ffffff;
--border: #d2dad3;
--muted: #7a8c7b;
--input-bg: #ffffff;
}
body { background: var(--background); color: var(--text); }
.app-wrapper { display: flex; min-height: 100vh; }
.main-column { flex: 1; display: flex; flex-direction: column; min-height: 100vh; overflow-x: hidden; }
.sidebar {
width: 220px;
flex-shrink: 0;
min-height: 100vh;
padding: 1rem;
display: flex;
flex-direction: column;
background: var(--sidebar-bg);
border-right: 1px solid var(--border);
}
.sidebar .brand {
font-size: 1rem;
font-weight: 700;
text-decoration: none;
display: block;
margin-bottom: .75rem;
padding: .35rem .65rem;
border-radius: 6px;
color: var(--primary);
letter-spacing: -.01em;
}
.sidebar-top {
display: flex;
flex-direction: column;
padding: .1rem .5rem;
margin-bottom: 1rem;
gap: .35rem;
}
.user-name {
font-size: .82rem;
font-weight: 500;
color: var(--muted);
}
.sidebar-actions {
display: flex;
align-items: center;
gap: .3rem;
}
.theme-switch-wrap {
display: flex;
align-items: center;
gap: .25rem;
font-size: .68rem;
}
.theme-switch {
position: relative;
display: inline-block;
cursor: pointer;
margin: 0;
line-height: 0;
}
#theme-checkbox {
opacity: 0;
position: absolute;
z-index: -1;
width: 0;
height: 0;
margin: 0;
}
.theme-track {
display: inline-block;
width: 34px;
height: 20px;
border-radius: 20px;
background: var(--muted);
position: relative;
transition: background .22s ease-in-out;
vertical-align: middle;
}
.theme-track::after {
content: '';
position: absolute;
top: 3px;
left: 3px;
width: 14px;
height: 14px;
border-radius: 50%;
background: #fff;
box-shadow: 0 1px 4px rgba(0,0,0,.35);
transition: left .22s ease-in-out, transform .22s ease-in-out;
}
#theme-checkbox:checked + .theme-track { background: var(--accent); }
#theme-checkbox:checked + .theme-track::after {
left: calc(100% - 3px);
transform: translateX(-100%);
}
.theme-icon-sun { color: #c8a83a; }
.theme-icon-moon { color: var(--muted); }
.btn-logout {
width: 26px;
height: 26px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
text-decoration: none;
font-size: .82rem;
color: var(--muted);
transition: background .12s, color .12s;
flex-shrink: 0;
}
.btn-logout:hover { background: rgba(220,60,60,.1); color: #e05a5a; }
.sidebar-divider {
height: 1px;
background: var(--border);
margin-bottom: 1rem;
}
.sidebar .nav-link {
border-radius: 6px;
padding: .45rem .75rem;
font-size: .87rem;
color: var(--muted);
transition: background .12s, color .12s;
text-decoration: none;
display: flex;
align-items: center;
}
.sidebar .nav-link:hover { color: var(--primary); background: rgba(179,199,180,.07); }
.sidebar .nav-link.active {
color: var(--accent);
background: rgba(153,117,149,.13);
box-shadow: inset 3px 0 0 var(--accent);
font-weight: 500;
}
.sidebar-section-label {
font-size: .62rem;
font-weight: 700;
letter-spacing: .1em;
text-transform: uppercase;
padding: .3rem .75rem .1rem;
display: block;
color: var(--muted);
}
.card { background: var(--card-bg) !important; border-color: var(--border) !important; box-shadow: 0 2px 8px rgba(0,0,0,.2); }
.card-header { background: rgba(0,0,0,.15) !important; border-bottom-color: var(--border) !important; color: var(--primary); }
.card-footer { background: rgba(0,0,0,.1) !important; border-top-color: var(--border) !important; }
[data-bs-theme="light"] .card-header { background: rgba(0,0,0,.03) !important; }
[data-bs-theme="light"] .card-footer { background: rgba(0,0,0,.02) !important; }
thead th { border-bottom: 2px solid var(--border); font-size: .82rem; color: var(--muted); }
.table { color: var(--text); }
.table td, .table th { border-color: var(--border); }
[data-bs-theme="dark"] .table-light,
[data-bs-theme="dark"] .table-light th,
[data-bs-theme="dark"] .table-light td { color: #212529 !important; }
[data-bs-theme="light"] .text-white-50 { color: rgba(0,0,0,.45) !important; }
.form-control, .form-select { background-color: var(--input-bg); border-color: var(--border); color: var(--text); }
.form-control:focus, .form-select:focus {
background-color: var(--input-bg);
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(153,117,149,.2);
color: var(--text);
}
.input-group-text { background: var(--input-bg); border-color: var(--border); color: var(--muted); }
.form-section-title {
font-size: .7rem;
font-weight: 700;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: .75rem;
padding-bottom: .4rem;
border-bottom: 1px solid var(--border);
}
.field-icon { color: var(--muted); }
.modal-content { background: var(--card-bg); border-color: var(--border); color: var(--text); }
.modal-header, .modal-footer { border-color: var(--border); }
.status-online { color: #4caf78; }
.status-offline { color: #e05a5a; }
.status-unknown { color: var(--muted); }
[data-bs-theme="dark"] .btn-close { filter: invert(1); }
.terminal-titlebar {
background: #161b1a;
padding: 7px 14px;
border-radius: 8px 8px 0 0;
display: flex;
align-items: center;
user-select: none;
border-bottom: 1px solid #21312a;
}
.terminal-host { color: var(--primary); font-size: .78rem; font-family: 'Courier New', monospace; }
.terminal-body {
padding: 12px 14px;
height: 380px;
overflow-y: auto;
color: #c9d9c9;
background: #080e09;
font-size: .82rem;
line-height: 1.65;
}
.terminal-body::-webkit-scrollbar { width: 5px; }
.terminal-body::-webkit-scrollbar-thumb { background: #2a3a2d; border-radius: 3px; }
.term-line { white-space: pre-wrap; word-break: break-all; }
.term-prompt { color: #7ab87a; }
.term-success { color: #56d364; }
.term-error { color: #e06060; }
.term-info { color: #7a9e8a; font-style: italic; }
.term-warn { color: #c8a83a; }
.terminal-input-row {
display: flex;
align-items: center;
background: #080e09;
border-top: 1px solid #1a2a1d;
padding: 8px 14px;
border-radius: 0 0 8px 8px;
}
.terminal-ps1 { color: #7ab87a; font-family: 'Courier New', monospace; font-size: .82rem; white-space: nowrap; margin-right: 6px; }
.terminal-cmd {
flex: 1;
background: transparent;
border: none;
outline: none;
color: #c9d9c9;
font-family: 'Courier New', Courier, monospace;
font-size: .82rem;
caret-color: #7ab87a;
}
.terminal-cmd::placeholder { color: #2e4030; }
#terminal-card { display: flex; flex-direction: column; }
#terminal-card .card-body { display: flex; flex-direction: column; background: #080e09 !important; padding: 0 !important; }
.service-card { border: 1px solid var(--border) !important; box-shadow: 0 1px 3px rgba(0,0,0,.15) !important; }
.service-card .card-footer { background: transparent !important; border-top: 1px solid var(--border) !important; }
.role-badge { font-size: .75rem; padding: .3em .6em; }
.server-search-wrap { position: relative; }
.server-search-wrap .bi-search { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--muted); pointer-events: none; }
#server-search { padding-left: 2rem; }
#server-list { max-height: 340px; overflow-y: auto; }
#server-list::-webkit-scrollbar { width: 5px; }
#server-list::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
.server-item { transition: background .12s; }
.server-item.hidden { display: none !important; }
.grant-form {
display: none;
background: rgba(153,117,149,.05);
border-top: 1px solid rgba(153,117,149,.15);
padding: .75rem 1rem;
gap: .5rem;
}
.server-item.open .grant-form { display: flex; flex-wrap: wrap; align-items: flex-end; }
.grant-form .form-label { font-size: .78rem; font-weight: 600; margin-bottom: .2rem; }
.grant-form .form-control { font-size: .82rem; }
</style>
{% block stylesheets %}{% endblock %}
</head>
<body>
<div class="app-wrapper">
{% if app.user %}
<nav class="sidebar">
<a class="brand" href="{{ path('app_server_index') }}">
<i class="bi bi-server me-2"></i>ServerControl
</a>
<div class="sidebar-top">
<span class="user-name">{{ app.user.pseudo }}</span>
<div class="sidebar-actions">
<div class="theme-switch-wrap" title="Changer de thème">
<i class="bi bi-sun-fill theme-icon-sun"></i>
<label class="theme-switch">
<input type="checkbox" id="theme-checkbox" onchange="toggleTheme()">
<span class="theme-track"></span>
</label>
<i class="bi bi-moon-stars-fill theme-icon-moon"></i>
</div>
<a href="{{ path('app_logout') }}" class="btn-logout" title="Déconnexion">
<i class="bi bi-box-arrow-right"></i>
</a>
</div>
</div>
<div class="sidebar-divider"></div>
<ul class="nav flex-column gap-1">
<li class="nav-item">
<a class="nav-link {% if app.request.attributes.get('_route') starts with 'app_server' %}active{% endif %}"
href="{{ path('app_server_index') }}">
<i class="bi bi-hdd-stack me-2"></i>Servers
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if app.request.attributes.get('_route') starts with 'app_log' %}active{% endif %}"
href="{{ path('app_log_index') }}">
<i class="bi bi-journal-text me-2"></i>Logs
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if app.request.attributes.get('_route') == 'app_profile' %}active{% endif %}"
href="{{ path('app_profile') }}">
<i class="bi bi-person-gear me-2"></i>Mon profil
</a>
</li>
{% if is_granted('ROLE_ADMIN') %}
<li class="nav-item mt-2">
<span class="sidebar-section-label">Admin</span>
</li>
<li class="nav-item">
<a class="nav-link {% if app.request.attributes.get('_route') starts with 'app_admin' %}active{% endif %}"
href="{{ path('app_admin_users') }}">
<i class="bi bi-people me-2"></i>Users
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ path('app_server_new') }}">
<i class="bi bi-plus-circle me-2"></i>Add Server
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
<div class="main-column">
<main class="p-4 flex-grow-1">
{% for label, messages in app.flashes %}
{% for message in messages %}
<div class="alert alert-{{ label == 'error' ? 'danger' : label }} alert-dismissible fade show">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endfor %}
{% block body %}{% endblock %}
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
(function () {
const saved = localStorage.getItem('sc-theme') || 'dark';
applyTheme(saved);
})();
function applyTheme(theme) {
document.documentElement.setAttribute('data-bs-theme', theme);
localStorage.setItem('sc-theme', theme);
const cb = document.getElementById('theme-checkbox');
if (cb) cb.checked = (theme === 'dark');
}
function toggleTheme() {
const current = document.documentElement.getAttribute('data-bs-theme');
applyTheme(current === 'dark' ? 'light' : 'dark');
}
</script>
{% block javascripts %}{% endblock %}
</body>
</html>