patchcenter/app/templates/servers.html
Khalid MOUTAOUAKIL 8277653c43 PatchCenter v2.0 — Initial commit
Modules: Dashboard, Serveurs, Campagnes, Planning, Specifiques, Settings, Users
Stack: FastAPI + Jinja2 + HTMX + Alpine.js + TailwindCSS + PostgreSQL
Features: Qualys sync, prereqs auto, planning annuel, server specifics, role-based access

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 03:00:12 +02:00

110 lines
7.4 KiB
HTML

{% extends 'base.html' %}
{% block title %}Serveurs{% endblock %}
{% macro sort_url(col) -%}
?sort={{ col }}&sort_dir={% if sort == col and sort_dir == 'asc' %}desc{% else %}asc{% endif %}&search={{ filters.search or '' }}&domain={{ filters.domain or '' }}&env={{ filters.env or '' }}&tier={{ filters.tier or '' }}&etat={{ filters.etat or '' }}&page=1
{%- endmacro %}
{% macro sort_icon(col) -%}
{% if sort == col %}{% if sort_dir == 'asc' %}&#9650;{% else %}&#9660;{% endif %}{% endif %}
{%- endmacro %}
{% macro qs(p) -%}
?page={{ p }}&search={{ filters.search or '' }}&domain={{ filters.domain or '' }}&env={{ filters.env or '' }}&tier={{ filters.tier or '' }}&etat={{ filters.etat or '' }}&sort={{ sort }}&sort_dir={{ sort_dir }}
{%- endmacro %}
{% block content %}
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-cyber-accent">Serveurs <span class="text-sm text-gray-500">({{ total }})</span></h2>
<div class="flex gap-2">
<button class="btn-sm bg-cyber-green text-black" onclick="alert('Export bientot')">Export CSV</button>
</div>
</div>
<!-- Filtres -->
<form method="GET" class="card p-3 mb-4 flex gap-3 items-center flex-wrap">
<input type="hidden" name="sort" value="{{ sort }}">
<input type="hidden" name="sort_dir" value="{{ sort_dir }}">
<input type="text" name="search" value="{{ filters.search or '' }}" placeholder="Rechercher..." class="w-44" hx-get="/servers" hx-trigger="keyup changed delay:300ms" hx-target="#server-table" hx-select="#server-table" hx-push-url="true">
<select name="domain" onchange="this.form.submit()"><option value="">Domaine</option>
{% for d in domains_list %}<option value="{{ d.code }}" {% if filters.domain == d.code %}selected{% endif %}>{{ d.name }}</option>{% endfor %}
</select>
<select name="env" onchange="this.form.submit()"><option value="">Env</option>
{% for e in envs_list %}<option value="{{ e.code }}" {% if filters.env == e.code %}selected{% endif %}>{{ e.name }}</option>{% endfor %}
</select>
<select name="tier" onchange="this.form.submit()"><option value="">Tier</option>
{% for t in ['tier0','tier1','tier2','tier3'] %}<option value="{{ t }}" {% if filters.tier == t %}selected{% endif %}>{{ t }}</option>{% endfor %}
</select>
<select name="etat" onchange="this.form.submit()"><option value="">Etat</option>
{% for e in ['en_production','en_implementation','decommissionne'] %}<option value="{{ e }}" {% if filters.etat == e %}selected{% endif %}>{{ e }}</option>{% endfor %}
</select>
<button type="submit" class="btn-primary px-3 py-1 text-sm">Filtrer</button>
<a href="/servers" class="text-xs text-gray-500 hover:text-gray-300">Reset</a>
</form>
<!-- Table -->
<div id="server-table" class="card overflow-x-auto">
<table class="w-full table-cyber">
<thead><tr>
<th class="p-2 w-8"><input type="checkbox" id="check-all"></th>
<th class="text-left p-2"><a href="{{ sort_url('hostname') }}" class="hover:text-cyber-accent">Hostname {{ sort_icon('hostname') }}</a></th>
<th class="p-2"><a href="{{ sort_url('domaine') }}" class="hover:text-cyber-accent">Domaine {{ sort_icon('domaine') }}</a></th>
<th class="p-2"><a href="{{ sort_url('env') }}" class="hover:text-cyber-accent">Env {{ sort_icon('env') }}</a></th>
<th class="p-2"><a href="{{ sort_url('zone') }}" class="hover:text-cyber-accent">Zone {{ sort_icon('zone') }}</a></th>
<th class="p-2"><a href="{{ sort_url('os') }}" class="hover:text-cyber-accent">OS {{ sort_icon('os') }}</a></th>
<th class="p-2">Version</th>
<th class="p-2">Licence</th>
<th class="p-2"><a href="{{ sort_url('tier') }}" class="hover:text-cyber-accent">Tier {{ sort_icon('tier') }}</a></th>
<th class="p-2"><a href="{{ sort_url('etat') }}" class="hover:text-cyber-accent">Etat {{ sort_icon('etat') }}</a></th>
<th class="p-2"><a href="{{ sort_url('owner') }}" class="hover:text-cyber-accent">Owner {{ sort_icon('owner') }}</a></th>
<th class="p-2">Actions</th>
</tr></thead>
<tbody>
{% for s in servers %}
<tr id="row-{{ s.id }}" class="group" hx-get="/servers/{{ s.id }}/detail" hx-target="#detail-panel" hx-swap="innerHTML" onclick="openPanel()">
<td class="p-2" onclick="event.stopPropagation()"><input type="checkbox" name="srv" value="{{ s.id }}"></td>
<td class="p-2 font-mono text-sm text-cyber-accent">{{ s.hostname }}</td>
<td class="p-2 text-center text-xs">{{ s.domaine or '-' }}</td>
<td class="p-2 text-center"><span class="badge {% if s.environnement == 'Production' %}badge-green{% elif s.environnement == 'Recette' %}badge-yellow{% else %}badge-gray{% endif %}">{{ (s.environnement or '-')[:6] }}</span></td>
<td class="p-2 text-center"><span class="badge {% if s.zone == 'DMZ' %}badge-red{% elif s.zone == 'EMV' %}badge-yellow{% else %}badge-blue{% endif %}">{{ s.zone or 'LAN' }}</span></td>
<td class="p-2 text-center text-xs">{{ s.os_family or '-' }}</td>
<td class="p-2 text-center text-xs text-gray-400" title="{{ s.os_version or '' }}">{{ s.os_short or '-' }}</td>
<td class="p-2 text-center"><span class="badge {% if s.licence_support == 'active' %}badge-green{% elif s.licence_support == 'eol' %}badge-red{% elif s.licence_support == 'els' %}badge-yellow{% else %}badge-gray{% endif %}">{{ s.licence_support }}</span></td>
<td class="p-2 text-center"><span class="badge {% if s.tier == 'tier0' %}badge-red{% elif s.tier == 'tier1' %}badge-yellow{% elif s.tier == 'tier2' %}badge-blue{% else %}badge-green{% endif %}">{{ s.tier }}</span></td>
<td class="p-2 text-center"><span class="badge {% if s.etat == 'en_production' %}badge-green{% elif s.etat == 'decommissionne' %}badge-red{% else %}badge-yellow{% endif %}">{{ (s.etat or '')[:8] }}</span></td>
<td class="p-2 text-center text-xs">{{ s.patch_os_owner or '-' }}</td>
<td class="p-2 text-center" onclick="event.stopPropagation()">
<button class="btn-sm bg-cyber-border text-cyber-accent" hx-get="/servers/{{ s.id }}/edit" hx-target="#detail-panel" hx-swap="innerHTML" onclick="openPanel()">Edit</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="flex justify-between items-center mt-4 text-sm text-gray-500">
<span>Page {{ page }} / {{ ((total - 1) // per_page) + 1 }} — {{ total }} serveurs</span>
<div class="flex gap-2">
{% if page > 1 %}<a href="{{ qs(page - 1) }}" class="btn-sm bg-cyber-border text-gray-300">Precedent</a>{% endif %}
{% if page * per_page < total %}<a href="{{ qs(page + 1) }}" class="btn-sm bg-cyber-border text-gray-300">Suivant</a>{% endif %}
</div>
</div>
<script>
function openPanel() {
document.getElementById("detail-panel").scrollTop = 0;
document.getElementById('detail-panel').style.width = '400px';
document.getElementById('detail-panel').style.overflow = 'auto';
window.scrollTo({top: 0, behavior: 'smooth'});
}
function closePanel() {
document.getElementById('detail-panel').style.width = '0';
document.getElementById('detail-panel').style.overflow = 'hidden';
}
document.getElementById('check-all').addEventListener('change', function(e) {
document.querySelectorAll('input[name=srv]').forEach(cb => cb.checked = e.target.checked);
});
</script>
{% endblock %}