patchcenter/app/templates/admin_applications_multi.html
Admin MPCZ 677f621c81 Admin applications + correspondance cleanup + tools presentation DSI
- Admin applications: CRUD module (list/add/edit/delete/assign/multi-app)
  avec push iTop bidirectionnel (applications.py + 3 templates)
- Correspondance prod<->hors-prod: migration vers server_correspondance
  globale, suppression ancien code quickwin, ajout filtre environnement
  et solution applicative, colonne environnement dans builder
- Servers page: colonne application_name + equivalent(s) via get_links_bulk,
  filtre application_id, push iTop sur changement application
- Patching: bulk_update_application, bulk_update_excludes, validations
- Fix paramiko sftp.put (remote_path -> positional arg)
- Tools: wiki_to_pdf.py (DokuWiki -> PDF) + generate_ppt.py (PPTX 19 slides
  DSI patching) + contenu source (processus_patching.txt, script_presentation.txt)

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

80 lines
3.5 KiB
HTML

{% extends 'base.html' %}
{% block title %}Serveurs multi-applications{% endblock %}
{% block content %}
<div class="flex justify-between items-center mb-4">
<div>
<a href="/admin/applications" class="text-xs text-gray-500 hover:text-gray-300">&larr; Applications</a>
<h2 class="text-xl font-bold text-cyber-accent">Serveurs liés à plusieurs applications</h2>
<p class="text-xs text-gray-500 mt-1">Source : iTop (<code>applicationsolution_list</code> avec 2+ entrées). Cliquer sur une app pour la garder comme unique.</p>
</div>
</div>
<div class="card p-3 mb-4 text-xs text-gray-400" style="background:#111827">
<b class="text-cyber-accent">Règle :</b> si un serveur apparaît sous plusieurs apps, souvent c'est une duplication (même app avec noms différents) ou une erreur de saisie. Sélectionner l'app à conserver → les autres seront retirées du serveur (côté iTop également).
</div>
{% if not multi_servers %}
<div class="card p-6 text-center text-gray-500">
<p>Aucun serveur avec plusieurs applications dans iTop.</p>
</div>
{% else %}
<div class="card overflow-x-auto">
<table class="w-full table-cyber text-xs">
<thead><tr>
<th class="p-2 text-left">Hostname</th>
<th class="p-2 text-left">App actuelle (PatchCenter)</th>
<th class="p-2 text-left">Apps iTop</th>
<th class="p-2">Action</th>
</tr></thead>
<tbody>
{% for m in multi_servers %}
<tr class="border-t border-cyber-border/30" id="row-{{ loop.index }}">
<td class="p-2 font-mono text-cyber-accent">{{ m.hostname }}</td>
<td class="p-2 text-xs text-gray-300">{{ m.current_app_name or '—' }}</td>
<td class="p-2">
{% for a in m.apps %}
<button onclick="keepApp('{{ m.hostname }}', {{ a.itop_id }}, '{{ a.name|e }}', {{ loop.index0 }}, this)"
class="btn-sm bg-cyber-border text-cyber-accent" style="margin:2px;padding:4px 10px">
<span class="font-mono">{{ a.name }}</span>
</button>
{% endfor %}
</td>
<td class="p-2 text-center">
<span id="result-{{ loop.index }}" class="text-xs"></span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<script>
function keepApp(hostname, keepItopId, appName, btnIdx, btn) {
if (!confirm('Garder UNIQUEMENT "' + appName + '" pour ' + hostname + ' ?\n(Les autres apps seront retirées, iTop mis à jour)')) return;
const row = btn.closest('tr');
const resultEl = row.querySelector('[id^=result-]');
resultEl.textContent = '…';
resultEl.className = 'text-xs text-gray-400';
fetch('/admin/applications/keep-single-app', {
method: 'POST', credentials: 'same-origin',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({hostname: hostname, keep_itop_id: keepItopId})
})
.then(r => r.json())
.then(d => {
if (d.ok) {
resultEl.textContent = '✓ ' + d.app_name;
resultEl.className = 'text-xs text-cyber-green';
// Griser la ligne
row.style.opacity = '0.5';
row.querySelectorAll('button').forEach(b => b.disabled = true);
} else {
resultEl.textContent = '✗ ' + (d.msg || 'Erreur');
resultEl.className = 'text-xs text-cyber-red';
}
});
}
</script>
{% endblock %}