feat: enrichir le rapport DNS avec colonnes attendu/dig/cloudflare + envoi a monitor@siteconseil.fr

src/Service/DnsCheckService.php:
- Methode check() enrichie avec 4 nouveaux champs: expected (valeur attendue),
  dig (valeur actuelle trouvee par dig), cloudflare (valeur dans la zone CF),
  cf_status (statut de la colonne CF: ok/error/vide)
- checkSpf(): expected = "include:X dans le SPF", dig = contenu SPF complet
- checkDmarc(): expected = "p=reject ou p=quarantine", dig = contenu DMARC
- checkDkim(): expected = "FQDN CNAME/TXT", dig = cible CNAME ou debut TXT
- checkMx(): expected = MX attendu, dig = liste des MX trouves avec priorite
- checkBounce(): expected = "feedback-smtp.*.amazonses.com", dig = valeur trouvee

src/Command/CheckDnsCommand.php:
- Nouveau champ MONITOR_EMAIL = 'monitor@siteconseil.fr' pour l'envoi du rapport
- loadCloudflareRecords(): charge les records CF une seule fois par domaine
  au debut de l'execution, retourne un array indexe par domaine
- enrichWithCloudflare(): apres chaque check DNS, parcourt les records CF
  pour trouver l'enregistrement correspondant et remplir les colonnes
  cloudflare et cf_status dans chaque check
- checkAwsSes(): utilise DnsCheckService::check() avec expected/dig
  (ex: expected="Success", dig="Absent" pour la verification domaine)
- checkMailcow(): utilise DnsCheckService::check() avec expected/dig
  (ex: expected="Cle Mailcow: abc...", dig="Cle DNS: xyz..." pour DKIM)
- sendReport(): envoie a MONITOR_EMAIL au lieu de l'admin email

templates/emails/dns_report.html.twig:
- Tableau par domaine avec 6 colonnes: Type, Check, Attendu, Dig (actuel),
  Cloudflare, Statut (OK/erreur/warning)
- Colonne Dig coloree en vert/rouge/jaune selon le statut du check
- Colonne Cloudflare coloree selon cf_status
- Colonnes avec word-break: break-all pour les longues valeurs DNS
- Bandeau resume en haut avec compteurs succes/erreurs/warnings
  avec bordures laterales colorees
- Pied de mail: "Rapport par Esy-Infra - Service de monitoring d'infra"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-04-02 21:35:46 +02:00
parent 484c48e331
commit c666e0db65
3 changed files with 258 additions and 231 deletions

View File

@@ -18,73 +18,52 @@
{{ date|date('d/m/Y H:i:s') }} &#8226; Domaines : <strong>{{ domains|join(', ') }}</strong>
</p>
{# ─── Succes ─── #}
{% if successes|length > 0 %}
<h2 style="font-size: 14px; font-weight: 700; color: #16a34a; margin-top: 16px; margin-right: 0; margin-bottom: 8px; margin-left: 0;">
Verifications reussies ({{ successes|length }})
</h2>
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border: 1px solid #e5e5e5; margin-top: 0; margin-right: 0; margin-bottom: 16px; margin-left: 0;">
{% for success in successes %}
<tr>
<td style="padding-top: 6px; padding-bottom: 6px; padding-left: 12px; padding-right: 12px; font-size: 12px; mso-line-height-rule: exactly; line-height: 18px; background-color: {{ loop.index is odd ? '#ffffff' : '#f9fafb' }}; border-bottom: 1px solid #eeeeee; color: #16a34a;">
&#10003; {{ success }}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{# ─── Resume ─── #}
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="margin-top: 0; margin-right: 0; margin-bottom: 16px; margin-left: 0;">
<tr>
<td style="padding-top: 8px; padding-bottom: 8px; padding-left: 12px; padding-right: 12px; background-color: #f0fdf4; border-left: 3px solid #16a34a; font-size: 12px; font-weight: 700; color: #16a34a;">
&#10003; {{ successes|length }} verification(s) OK
</td>
</tr>
{% if errors|length > 0 %}
<tr>
<td style="padding-top: 8px; padding-bottom: 8px; padding-left: 12px; padding-right: 12px; background-color: #fef2f2; border-left: 3px solid #dc2626; font-size: 12px; font-weight: 700; color: #dc2626;">
&#10007; {{ errors|length }} erreur(s)
</td>
</tr>
{% endif %}
{% if warnings|length > 0 %}
<tr>
<td style="padding-top: 8px; padding-bottom: 8px; padding-left: 12px; padding-right: 12px; background-color: #fffbeb; border-left: 3px solid #f59e0b; font-size: 12px; font-weight: 700; color: #f59e0b;">
&#9888; {{ warnings|length }} avertissement(s)
</td>
</tr>
{% endif %}
</table>
{# ─── Erreurs ─── #}
{% if errors|length > 0 %}
<h2 style="font-size: 14px; font-weight: 700; color: #dc2626; margin-top: 16px; margin-right: 0; margin-bottom: 8px; margin-left: 0;">
Erreurs ({{ errors|length }})
</h2>
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border: 1px solid #fca5a5; margin-top: 0; margin-right: 0; margin-bottom: 16px; margin-left: 0;">
{% for error in errors %}
<tr>
<td style="padding-top: 6px; padding-bottom: 6px; padding-left: 12px; padding-right: 12px; font-size: 12px; mso-line-height-rule: exactly; line-height: 18px; background-color: #fef2f2; color: #991b1b; border-bottom: 1px solid #fca5a5;">
&#10007; {{ error }}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{# ─── Avertissements ─── #}
{% if warnings|length > 0 %}
<h2 style="font-size: 14px; font-weight: 700; color: #f59e0b; margin-top: 16px; margin-right: 0; margin-bottom: 8px; margin-left: 0;">
Avertissements ({{ warnings|length }})
</h2>
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border: 1px solid #fde68a; margin-top: 0; margin-right: 0; margin-bottom: 16px; margin-left: 0;">
{% for warning in warnings %}
<tr>
<td style="padding-top: 6px; padding-bottom: 6px; padding-left: 12px; padding-right: 12px; font-size: 12px; mso-line-height-rule: exactly; line-height: 18px; background-color: #fffbeb; color: #92400e; border-bottom: 1px solid #fde68a;">
&#9888; {{ warning }}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{# ─── Detail par domaine ─── #}
{# ─── Detail par domaine avec valeurs attendue / dig / cloudflare ─── #}
{% for domainData in domainResults %}
<h2 style="font-size: 14px; font-weight: 700; margin-top: 20px; margin-right: 0; margin-bottom: 8px; margin-left: 0;">
<h2 style="font-size: 16px; font-weight: 700; margin-top: 24px; margin-right: 0; margin-bottom: 8px; margin-left: 0; border-bottom: 2px solid #fabf04; padding-bottom: 4px;">
{{ domainData.domain }}
</h2>
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border: 1px solid #e5e5e5; margin-top: 0; margin-right: 0; margin-bottom: 16px; margin-left: 0;">
<tr>
<td style="background-color: #111827; color: #ffffff; padding-top: 8px; padding-bottom: 8px; padding-left: 12px; padding-right: 12px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; width: 80px;">Type</td>
<td style="background-color: #111827; color: #ffffff; padding-top: 8px; padding-bottom: 8px; padding-left: 12px; padding-right: 12px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px;">Verification</td>
<td style="background-color: #111827; color: #ffffff; padding-top: 8px; padding-bottom: 8px; padding-left: 12px; padding-right: 12px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; text-align: center; width: 60px;">Statut</td>
<td style="background-color: #111827; color: #ffffff; padding-top: 8px; padding-bottom: 8px; padding-left: 8px; padding-right: 8px; font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; width: 60px;">Type</td>
<td style="background-color: #111827; color: #ffffff; padding-top: 8px; padding-bottom: 8px; padding-left: 8px; padding-right: 8px; font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px;">Check</td>
<td style="background-color: #111827; color: #ffffff; padding-top: 8px; padding-bottom: 8px; padding-left: 8px; padding-right: 8px; font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px;">Attendu</td>
<td style="background-color: #111827; color: #ffffff; padding-top: 8px; padding-bottom: 8px; padding-left: 8px; padding-right: 8px; font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px;">Dig (actuel)</td>
<td style="background-color: #111827; color: #ffffff; padding-top: 8px; padding-bottom: 8px; padding-left: 8px; padding-right: 8px; font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px;">Cloudflare</td>
<td style="background-color: #111827; color: #ffffff; padding-top: 8px; padding-bottom: 8px; padding-left: 8px; padding-right: 8px; font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; text-align: center; width: 30px;">OK</td>
</tr>
{% for check in domainData.checks %}
<tr>
<td style="padding-top: 6px; padding-bottom: 6px; padding-left: 12px; padding-right: 12px; font-size: 11px; font-weight: 700; border-bottom: 1px solid #eeeeee; color: #666666;">{{ check.type }}</td>
<td style="padding-top: 6px; padding-bottom: 6px; padding-left: 12px; padding-right: 12px; font-size: 11px; border-bottom: 1px solid #eeeeee;">
<strong>{{ check.label }}</strong>
{% if check.detail %}<br><span style="color: #888888; font-size: 10px;">{{ check.detail }}</span>{% endif %}
</td>
<td style="padding-top: 6px; padding-bottom: 6px; padding-left: 12px; padding-right: 12px; font-size: 12px; text-align: center; border-bottom: 1px solid #eeeeee; font-weight: 700; color: {{ check.status == 'ok' ? '#16a34a' : (check.status == 'error' ? '#dc2626' : '#f59e0b') }};">
<td style="padding-top: 5px; padding-bottom: 5px; padding-left: 8px; padding-right: 8px; font-size: 9px; font-weight: 700; border-bottom: 1px solid #eeeeee; color: #666666; vertical-align: top;">{{ check.type }}</td>
<td style="padding-top: 5px; padding-bottom: 5px; padding-left: 8px; padding-right: 8px; font-size: 9px; font-weight: 700; border-bottom: 1px solid #eeeeee; vertical-align: top;">{{ check.label }}</td>
<td style="padding-top: 5px; padding-bottom: 5px; padding-left: 8px; padding-right: 8px; font-size: 9px; border-bottom: 1px solid #eeeeee; color: #444444; vertical-align: top; word-break: break-all;">{{ check.expected|default('—') }}</td>
<td style="padding-top: 5px; padding-bottom: 5px; padding-left: 8px; padding-right: 8px; font-size: 9px; border-bottom: 1px solid #eeeeee; color: {{ check.status == 'ok' ? '#16a34a' : (check.status == 'error' ? '#dc2626' : '#92400e') }}; vertical-align: top; word-break: break-all;">{{ check.dig|default('—') }}</td>
<td style="padding-top: 5px; padding-bottom: 5px; padding-left: 8px; padding-right: 8px; font-size: 9px; border-bottom: 1px solid #eeeeee; color: {{ check.cf_status|default('') == 'ok' ? '#16a34a' : (check.cf_status|default('') == 'error' ? '#dc2626' : '#888888') }}; vertical-align: top; word-break: break-all;">{{ check.cloudflare|default('—') }}</td>
<td style="padding-top: 5px; padding-bottom: 5px; padding-left: 8px; padding-right: 8px; font-size: 12px; text-align: center; border-bottom: 1px solid #eeeeee; font-weight: 700; color: {{ check.status == 'ok' ? '#16a34a' : (check.status == 'error' ? '#dc2626' : '#f59e0b') }}; vertical-align: top;">
{{ check.status == 'ok' ? '&#10003;' : (check.status == 'error' ? '&#10007;' : '&#9888;') }}
</td>
</tr>
@@ -92,8 +71,39 @@
</table>
{% endfor %}
{# ─── Erreurs detaillees ─── #}
{% if errors|length > 0 %}
<h2 style="font-size: 14px; font-weight: 700; color: #dc2626; margin-top: 16px; margin-right: 0; margin-bottom: 8px; margin-left: 0;">
Erreurs a corriger ({{ errors|length }})
</h2>
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border: 1px solid #fca5a5; margin-top: 0; margin-right: 0; margin-bottom: 16px; margin-left: 0;">
{% for error in errors %}
<tr>
<td style="padding-top: 6px; padding-bottom: 6px; padding-left: 12px; padding-right: 12px; font-size: 11px; mso-line-height-rule: exactly; line-height: 16px; background-color: #fef2f2; color: #991b1b; border-bottom: 1px solid #fca5a5;">
&#10007; {{ error }}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% if warnings|length > 0 %}
<h2 style="font-size: 14px; font-weight: 700; color: #f59e0b; margin-top: 16px; margin-right: 0; margin-bottom: 8px; margin-left: 0;">
Avertissements ({{ warnings|length }})
</h2>
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border: 1px solid #fde68a; margin-top: 0; margin-right: 0; margin-bottom: 16px; margin-left: 0;">
{% for warning in warnings %}
<tr>
<td style="padding-top: 6px; padding-bottom: 6px; padding-left: 12px; padding-right: 12px; font-size: 11px; mso-line-height-rule: exactly; line-height: 16px; background-color: #fffbeb; color: #92400e; border-bottom: 1px solid #fde68a;">
&#9888; {{ warning }}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
<p style="font-size: 11px; color: #888888; mso-line-height-rule: exactly; line-height: 16px; margin-top: 16px; margin-right: 0; margin-bottom: 0; margin-left: 0;">
Rapport genere par la commande <strong>app:dns:check</strong>. Verifiez la configuration dans Cloudflare et AWS SES si necessaire.
Rapport par <strong>Esy-Infra</strong> - Service de monitoring d'infra
</p>
{% endblock %}