Reorganize admin Infra page: group PostgreSQL+PgBouncer and Redis+DBs
PostgreSQL & PgBouncer on same row, Redis Global + 3 DB cards on same row. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,49 +5,11 @@
|
||||
{% block body %}
|
||||
<h1 class="text-3xl font-black uppercase tracking-tighter italic heading-page mb-8">Infrastructure</h1>
|
||||
|
||||
{# Redis Global #}
|
||||
{# PostgreSQL & PgBouncer #}
|
||||
<h2 class="text-xl font-black uppercase tracking-tighter italic heading-page mb-4">PostgreSQL & PgBouncer</h2>
|
||||
<div class="flex flex-wrap gap-6 mb-8">
|
||||
<div class="flex-1 min-w-[400px]">
|
||||
<div class="admin-card">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-sm font-black uppercase tracking-widest">Redis — Global</h2>
|
||||
{% if redis_global.connected %}
|
||||
<span class="admin-badge-green text-xs font-black uppercase">Connecte</span>
|
||||
{% else %}
|
||||
<span class="admin-badge-red text-xs font-black uppercase">Deconnecte</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if redis_global.connected %}
|
||||
<div class="flex flex-col gap-0">
|
||||
{{ _self.row('Version', redis_global.version) }}
|
||||
{{ _self.row('Uptime', redis_global.uptime_days ~ ' jours') }}
|
||||
{{ _self.row('Role', redis_global.role) }}
|
||||
{{ _self.row('Clients connectes', redis_global.connected_clients) }}
|
||||
{{ _self.row('Ops/sec', redis_global.instantaneous_ops_per_sec) }}
|
||||
|
||||
<div class="border-t border-gray-200 my-2"></div>
|
||||
<p class="text-[10px] font-black uppercase tracking-widest text-gray-400 mb-1">Memoire</p>
|
||||
{{ _self.row('Utilisee', redis_global.used_memory_human) }}
|
||||
{{ _self.row('Pic', redis_global.used_memory_peak_human) }}
|
||||
|
||||
<div class="border-t border-gray-200 my-2"></div>
|
||||
<p class="text-[10px] font-black uppercase tracking-widest text-gray-400 mb-1">Performance</p>
|
||||
{{ _self.row('Commandes traitees', redis_global.total_commands_processed|number_format(0, '.', ' ')) }}
|
||||
{{ _self.row('Hits', redis_global.keyspace_hits|number_format(0, '.', ' ')) }}
|
||||
{{ _self.row('Misses', redis_global.keyspace_misses|number_format(0, '.', ' ')) }}
|
||||
{{ _self.row_color('Hit Rate', redis_global.hit_rate, redis_global.hit_rate != 'N/A' and redis_global.hit_rate|replace({'%': ''})|number_format > 80 ? 'green' : (redis_global.hit_rate == 'N/A' ? 'gray' : 'red')) }}
|
||||
{{ _self.row('Cles expirees', redis_global.expired_keys|number_format(0, '.', ' ')) }}
|
||||
{{ _self.row_color('Cles evictees', redis_global.evicted_keys, redis_global.evicted_keys == '0' ? 'green' : 'red') }}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-sm text-red-500 font-bold">{{ redis_global.error }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# PostgreSQL #}
|
||||
<div class="flex-1 min-w-[400px]">
|
||||
<div class="flex-1 min-w-[350px]">
|
||||
<div class="admin-card">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-sm font-black uppercase tracking-widest">PostgreSQL</h2>
|
||||
@@ -80,13 +42,136 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# PgBouncer Pools #}
|
||||
{% if pgbouncer.connected %}
|
||||
<div class="flex-1 min-w-[350px]">
|
||||
<div class="admin-card">
|
||||
<h2 class="text-sm font-black uppercase tracking-widest mb-4">PgBouncer — Pools</h2>
|
||||
{% if pgbouncer.pools|length > 0 %}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="admin-table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300">Database</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-center">Mode</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-center">Cl. actifs</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-center">Cl. attente</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-center">Sv. actifs</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-center">Sv. idle</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for pool in pgbouncer.pools %}
|
||||
<tr>
|
||||
<td class="font-black text-sm">{{ pool.database }}</td>
|
||||
<td class="text-center"><span class="admin-badge-green text-xs font-black uppercase">{{ pool.pool_mode }}</span></td>
|
||||
<td class="text-center font-black">{{ pool.cl_active }}</td>
|
||||
<td class="text-center font-black {% if pool.cl_waiting > 0 %}text-yellow-600{% endif %}">{{ pool.cl_waiting }}</td>
|
||||
<td class="text-center font-black">{{ pool.sv_active }}</td>
|
||||
<td class="text-center font-black text-gray-400">{{ pool.sv_idle }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-sm text-gray-400">Aucun pool actif.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# PgBouncer Stats #}
|
||||
<div class="flex-1 min-w-[350px]">
|
||||
<div class="admin-card">
|
||||
<h2 class="text-sm font-black uppercase tracking-widest mb-4">PgBouncer — Stats</h2>
|
||||
{% if pgbouncer.stats|length > 0 %}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="admin-table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300">Database</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-right">Xacts</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-right">Queries</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-right">Avg xact</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-right">Avg query</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for stat in pgbouncer.stats %}
|
||||
<tr>
|
||||
<td class="font-black text-sm">{{ stat.database }}</td>
|
||||
<td class="text-right text-sm font-bold">{{ stat.total_xact_count|number_format(0, '.', ' ') }}</td>
|
||||
<td class="text-right text-sm font-bold">{{ stat.total_query_count|number_format(0, '.', ' ') }}</td>
|
||||
<td class="text-right text-sm {% if stat.avg_xact_time > 100000 %}text-red-600{% elseif stat.avg_xact_time > 10000 %}text-yellow-600{% else %}text-green-600{% endif %} font-bold">{{ stat.avg_xact_time|number_format(0, '.', ' ') }}us</td>
|
||||
<td class="text-right text-sm {% if stat.avg_query_time > 50000 %}text-red-600{% elseif stat.avg_query_time > 5000 %}text-yellow-600{% else %}text-green-600{% endif %} font-bold">{{ stat.avg_query_time|number_format(0, '.', ' ') }}us</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-sm text-gray-400">Aucune stat disponible.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="flex-1 min-w-[350px]">
|
||||
<div class="admin-card">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-sm font-black uppercase tracking-widest">PgBouncer</h2>
|
||||
<span class="admin-badge-red text-xs font-black uppercase">Deconnecte</span>
|
||||
</div>
|
||||
<p class="text-sm text-red-500 font-bold">{{ pgbouncer.error }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Redis DBs #}
|
||||
<h2 class="text-xl font-black uppercase tracking-tighter italic heading-page mb-4">Redis — Databases</h2>
|
||||
{# Redis #}
|
||||
<h2 class="text-xl font-black uppercase tracking-tighter italic heading-page mb-4">Redis</h2>
|
||||
<div class="flex flex-wrap gap-6 mb-8">
|
||||
{# Global #}
|
||||
<div class="flex-1 min-w-[350px]">
|
||||
<div class="admin-card">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-sm font-black uppercase tracking-widest">Global</h2>
|
||||
{% if redis_global.connected %}
|
||||
<span class="admin-badge-green text-xs font-black uppercase">Connecte</span>
|
||||
{% else %}
|
||||
<span class="admin-badge-red text-xs font-black uppercase">Deconnecte</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if redis_global.connected %}
|
||||
<div class="flex flex-col gap-0">
|
||||
{{ _self.row('Version', redis_global.version) }}
|
||||
{{ _self.row('Uptime', redis_global.uptime_days ~ ' jours') }}
|
||||
{{ _self.row('Role', redis_global.role) }}
|
||||
{{ _self.row('Clients connectes', redis_global.connected_clients) }}
|
||||
{{ _self.row('Ops/sec', redis_global.instantaneous_ops_per_sec) }}
|
||||
|
||||
<div class="border-t border-gray-200 my-2"></div>
|
||||
<p class="text-[10px] font-black uppercase tracking-widest text-gray-400 mb-1">Memoire</p>
|
||||
{{ _self.row('Utilisee', redis_global.used_memory_human) }}
|
||||
{{ _self.row('Pic', redis_global.used_memory_peak_human) }}
|
||||
|
||||
<div class="border-t border-gray-200 my-2"></div>
|
||||
<p class="text-[10px] font-black uppercase tracking-widest text-gray-400 mb-1">Performance</p>
|
||||
{{ _self.row('Commandes', redis_global.total_commands_processed|number_format(0, '.', ' ')) }}
|
||||
{{ _self.row('Hits / Misses', redis_global.keyspace_hits|number_format(0, '.', ' ') ~ ' / ' ~ redis_global.keyspace_misses|number_format(0, '.', ' ')) }}
|
||||
{{ _self.row_color('Hit Rate', redis_global.hit_rate, redis_global.hit_rate != 'N/A' and redis_global.hit_rate|replace({'%': ''})|number_format > 80 ? 'green' : (redis_global.hit_rate == 'N/A' ? 'gray' : 'red')) }}
|
||||
{{ _self.row_color('Cles evictees', redis_global.evicted_keys, redis_global.evicted_keys == '0' ? 'green' : 'red') }}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-sm text-red-500 font-bold">{{ redis_global.error }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Per-DB cards #}
|
||||
{% for db in redis_dbs %}
|
||||
<div class="flex-1 min-w-[250px]">
|
||||
<div class="flex-1 min-w-[220px]">
|
||||
<div class="admin-card">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-sm font-black uppercase tracking-widest">{{ db.label }}</h2>
|
||||
@@ -109,100 +194,6 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{# PgBouncer #}
|
||||
<h2 class="text-xl font-black uppercase tracking-tighter italic heading-page mb-4">PgBouncer</h2>
|
||||
<div class="flex flex-wrap gap-6 mb-8">
|
||||
{% if pgbouncer.connected %}
|
||||
{# Pools #}
|
||||
<div class="flex-1 min-w-[500px]">
|
||||
<div class="admin-card">
|
||||
<h2 class="text-sm font-black uppercase tracking-widest mb-4">Pools</h2>
|
||||
{% if pgbouncer.pools|length > 0 %}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="admin-table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300">Database</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300">User</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-center">Mode</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-center">Clients actifs</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-center">Clients en attente</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-center">Serveurs actifs</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-center">Serveurs idle</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for pool in pgbouncer.pools %}
|
||||
<tr>
|
||||
<td class="font-black text-sm">{{ pool.database }}</td>
|
||||
<td class="text-sm">{{ pool.user }}</td>
|
||||
<td class="text-center"><span class="admin-badge-green text-xs font-black uppercase">{{ pool.pool_mode }}</span></td>
|
||||
<td class="text-center font-black">{{ pool.cl_active }}</td>
|
||||
<td class="text-center font-black {% if pool.cl_waiting > 0 %}text-yellow-600{% endif %}">{{ pool.cl_waiting }}</td>
|
||||
<td class="text-center font-black">{{ pool.sv_active }}</td>
|
||||
<td class="text-center font-black text-gray-400">{{ pool.sv_idle }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-sm text-gray-400">Aucun pool actif.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Stats #}
|
||||
<div class="flex-1 min-w-[500px]">
|
||||
<div class="admin-card">
|
||||
<h2 class="text-sm font-black uppercase tracking-widest mb-4">Stats</h2>
|
||||
{% if pgbouncer.stats|length > 0 %}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="admin-table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300">Database</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-right">Transactions</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-right">Requetes</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-right">Avg xact (us)</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-right">Avg query (us)</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-right">Recu</th>
|
||||
<th class="text-[10px] font-black uppercase tracking-widest text-gray-300 text-right">Envoye</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for stat in pgbouncer.stats %}
|
||||
<tr>
|
||||
<td class="font-black text-sm">{{ stat.database }}</td>
|
||||
<td class="text-right text-sm font-bold">{{ stat.total_xact_count|number_format(0, '.', ' ') }}</td>
|
||||
<td class="text-right text-sm font-bold">{{ stat.total_query_count|number_format(0, '.', ' ') }}</td>
|
||||
<td class="text-right text-sm {% if stat.avg_xact_time > 100000 %}text-red-600{% elseif stat.avg_xact_time > 10000 %}text-yellow-600{% else %}text-green-600{% endif %} font-bold">{{ stat.avg_xact_time|number_format(0, '.', ' ') }}</td>
|
||||
<td class="text-right text-sm {% if stat.avg_query_time > 50000 %}text-red-600{% elseif stat.avg_query_time > 5000 %}text-yellow-600{% else %}text-green-600{% endif %} font-bold">{{ stat.avg_query_time|number_format(0, '.', ' ') }}</td>
|
||||
<td class="text-right text-sm text-gray-500">{{ (stat.total_received / 1048576)|number_format(1) }} MB</td>
|
||||
<td class="text-right text-sm text-gray-500">{{ (stat.total_sent / 1048576)|number_format(1) }} MB</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-sm text-gray-400">Aucune stat disponible.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="flex-1 min-w-[400px]">
|
||||
<div class="admin-card">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-sm font-black uppercase tracking-widest">PgBouncer</h2>
|
||||
<span class="admin-badge-red text-xs font-black uppercase">Deconnecte</span>
|
||||
</div>
|
||||
<p class="text-sm text-red-500 font-bold">{{ pgbouncer.error }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% macro row(label, value) %}
|
||||
|
||||
Reference in New Issue
Block a user