242 lines
10 KiB
Django/Jinja
242 lines
10 KiB
Django/Jinja
{#- Email digest. Inline styles and table layout only: Outlook strips <style>
|
|||
|
|
blocks and ignores flex/grid. Kept deliberately plain. -#}
|
||
|
|
<!doctype html>
|
||
|
|
<html>
|
||
|
|
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width"></head>
|
||
|
|
<body style="margin:0;padding:0;background:#f4f4f2;">
|
||
|
|
<table role="presentation" width="100%" cellpadding="0" cellspacing="0"
|
||
|
|
style="background:#f4f4f2;padding:22px 10px;">
|
||
|
|
<tr><td align="center">
|
||
|
|
<table role="presentation" width="640" cellpadding="0" cellspacing="0"
|
||
|
|
style="width:640px;max-width:100%;background:#ffffff;border:1px solid #e0e0da;
|
||
|
|
border-radius:8px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',
|
||
|
|
Roboto,Helvetica,Arial,sans-serif;color:#1a1a18;">
|
||
|
|
|
||
|
|
<tr><td style="padding:22px 26px 14px;border-bottom:2px solid #1a1a18;">
|
||
|
|
<div style="font-size:22px;font-weight:700;letter-spacing:-.02em;">
|
||
|
|
AVWatch weekly briefing</div>
|
||
|
|
<div style="font-size:12.5px;color:#6b6b66;margin-top:4px;">
|
||
|
|
{{ generated_local }} · {{ run.items_checked }} items tracked
|
||
|
|
· verdicts by {{ engine_label }}</div>
|
||
|
|
</td></tr>
|
||
|
|
|
||
|
|
{# ---- headline numbers ---- #}
|
||
|
|
<tr><td style="padding:16px 26px 4px;">
|
||
|
|
<table role="presentation" width="100%" cellpadding="0" cellspacing="0">
|
||
|
|
<tr>
|
||
|
|
{% for label, value, colour in tiles %}
|
||
|
|
<td align="center" style="padding:8px 4px;">
|
||
|
|
<div style="font-size:24px;font-weight:700;color:{{ colour }};line-height:1.1;">{{ value }}</div>
|
||
|
|
<div style="font-size:10px;text-transform:uppercase;letter-spacing:.06em;
|
||
|
|
color:#6b6b66;margin-top:2px;">{{ label }}</div>
|
||
|
|
</td>
|
||
|
|
{% endfor %}
|
||
|
|
</tr>
|
||
|
|
</table>
|
||
|
|
</td></tr>
|
||
|
|
|
||
|
|
{% if summary_line %}
|
||
|
|
<tr><td style="padding:8px 26px 0;">
|
||
|
|
<div style="font-size:14px;line-height:1.55;background:#f7f7f5;
|
||
|
|
border-left:3px solid #2b5c8a;padding:11px 14px;border-radius:4px;">
|
||
|
|
{{ summary_line }}</div>
|
||
|
|
</td></tr>
|
||
|
|
{% endif %}
|
||
|
|
|
||
|
|
{% macro section(title, findings, colour) %}
|
||
|
|
{% if findings %}
|
||
|
|
<tr><td style="padding:22px 26px 0;">
|
||
|
|
<div style="font-size:11.5px;text-transform:uppercase;letter-spacing:.09em;
|
||
|
|
color:#6b6b66;border-bottom:1px solid #e0e0da;padding-bottom:6px;
|
||
|
|
margin-bottom:12px;">{{ title }} ({{ findings|length }})</div>
|
||
|
|
|
||
|
|
{% for f in findings %}
|
||
|
|
{% set v = f.verdict %}
|
||
|
|
<table role="presentation" width="100%" cellpadding="0" cellspacing="0"
|
||
|
|
style="margin-bottom:12px;border-left:3px solid {{ colour }};
|
||
|
|
background:#fbfbf9;border-radius:0 4px 4px 0;">
|
||
|
|
<tr><td style="padding:11px 14px;">
|
||
|
|
<div style="font-size:15px;font-weight:650;">
|
||
|
|
{{ f.item.name }}
|
||
|
|
<span style="font-weight:400;color:#6b6b66;font-size:12.5px;">
|
||
|
|
— {{ f.item.vendor }}</span>
|
||
|
|
{% if f.is_new_since_last_run %}
|
||
|
|
<span style="font-size:9.5px;font-weight:700;color:#2b5c8a;
|
||
|
|
border:1px solid #2b5c8a;border-radius:9px;padding:1px 6px;
|
||
|
|
text-transform:uppercase;letter-spacing:.05em;">new</span>
|
||
|
|
{% endif %}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{% if f.check.latest %}
|
||
|
|
<div style="font-family:Consolas,Menlo,monospace;font-size:12.5px;
|
||
|
|
color:#6b6b66;margin:5px 0 3px;">
|
||
|
|
{{ f.item.installed or 'unknown' }} →
|
||
|
|
<span style="color:#1a1a18;font-weight:700;">{{ f.check.latest }}</span>
|
||
|
|
{% if f.check.age_days is not none %}
|
||
|
|
({{ f.bump }}, {{ f.check.age_days|round(0)|int }} days old)
|
||
|
|
{% endif %}
|
||
|
|
</div>
|
||
|
|
{% endif %}
|
||
|
|
|
||
|
|
{% if v %}
|
||
|
|
{% if v.headline %}
|
||
|
|
<div style="font-size:13.5px;font-weight:600;margin:6px 0 3px;">
|
||
|
|
{{ v.headline }}</div>
|
||
|
|
{% endif %}
|
||
|
|
{% if v.reasons %}
|
||
|
|
<ul style="margin:4px 0 0;padding-left:18px;font-size:13px;line-height:1.5;">
|
||
|
|
{% for r in v.reasons[:4] %}<li style="margin:2px 0;">{{ r }}</li>{% endfor %}
|
||
|
|
{% if v.suggested_wait_days %}
|
||
|
|
<li style="margin:2px 0;"><strong>Revisit in about
|
||
|
|
{{ v.suggested_wait_days }} days.</strong></li>
|
||
|
|
{% endif %}
|
||
|
|
</ul>
|
||
|
|
{% endif %}
|
||
|
|
<div style="font-size:11px;color:#6b6b66;margin-top:7px;">
|
||
|
|
risk {{ v.risk_score|round(0)|int }}/100 ·
|
||
|
|
rule-based {{ v.heuristic_score|round(0)|int }}/100
|
||
|
|
({{ v.heuristic_recommendation|replace('-',' ') }}) ·
|
||
|
|
confidence {{ v.confidence }}
|
||
|
|
{% if v.heuristic_recommendation != v.recommendation
|
||
|
|
and v.heuristic_recommendation != 'unknown' %}
|
||
|
|
· <strong style="color:#8a6100;">engines disagree</strong>
|
||
|
|
{% endif %}
|
||
|
|
</div>
|
||
|
|
{% endif %}
|
||
|
|
|
||
|
|
{% if f.evidence %}
|
||
|
|
<div style="font-size:11.5px;color:#6b6b66;margin-top:8px;
|
||
|
|
border-top:1px solid #ececE6;padding-top:6px;">
|
||
|
|
Evidence:
|
||
|
|
{% for e in f.evidence[:4] %}
|
||
|
|
<a href="{{ e.url }}" style="color:#2b5c8a;text-decoration:none;">
|
||
|
|
[{{ e.severity }}] {{ e.title[:70] }}</a>{% if not loop.last %} · {% endif %}
|
||
|
|
{% endfor %}
|
||
|
|
</div>
|
||
|
|
{% endif %}
|
||
|
|
</td></tr>
|
||
|
|
</table>
|
||
|
|
{% endfor %}
|
||
|
|
</td></tr>
|
||
|
|
{% endif %}
|
||
|
|
{% endmacro %}
|
||
|
|
|
||
|
|
{% if groups.mismatched %}
|
||
|
|
<tr><td style="padding:20px 26px 0;">
|
||
|
|
<div style="font-size:11.5px;text-transform:uppercase;letter-spacing:.09em;
|
||
|
|
color:#a3231d;border-bottom:2px solid #a3231d;padding-bottom:6px;
|
||
|
|
margin-bottom:12px;">Version mismatch — broken now
|
||
|
|
({{ groups.mismatched|length }})</div>
|
||
|
|
{% for f in groups.mismatched %}
|
||
|
|
<table role="presentation" width="100%" cellpadding="0" cellspacing="0"
|
||
|
|
style="margin-bottom:10px;border-left:3px solid #a3231d;
|
||
|
|
background:#fbe9e7;border-radius:0 4px 4px 0;">
|
||
|
|
<tr><td style="padding:10px 14px;">
|
||
|
|
<div style="font-size:15px;font-weight:650;">{{ f.item.name }}</div>
|
||
|
|
{% for c in f.coupling %}
|
||
|
|
{% if c.severity == 'blocking' %}
|
||
|
|
<div style="font-size:13px;margin-top:4px;">{{ c.message }}</div>
|
||
|
|
{% endif %}
|
||
|
|
{% endfor %}
|
||
|
|
</td></tr>
|
||
|
|
</table>
|
||
|
|
{% endfor %}
|
||
|
|
</td></tr>
|
||
|
|
{% endif %}
|
||
|
|
|
||
|
|
{{ section('Do not install', groups.avoid, '#a3231d') }}
|
||
|
|
{{ section('Hold off for now', groups.wait, '#8a6100') }}
|
||
|
|
{{ section('Cleared to update', groups.go_behind, '#1f7a3d') }}
|
||
|
|
|
||
|
|
{% if groups.no_baseline %}
|
||
|
|
<tr><td style="padding:22px 26px 0;">
|
||
|
|
<div style="font-size:11.5px;text-transform:uppercase;letter-spacing:.09em;
|
||
|
|
color:#6b6b66;border-bottom:1px solid #e0e0da;padding-bottom:6px;
|
||
|
|
margin-bottom:10px;">No installed version on record
|
||
|
|
({{ groups.no_baseline|length }})</div>
|
||
|
|
<div style="font-size:12.5px;color:#6b6b66;line-height:1.6;margin-bottom:8px;">
|
||
|
|
Current versions were found, but AVWatch does not know what you run, so
|
||
|
|
it cannot say whether you are behind:
|
||
|
|
</div>
|
||
|
|
<table role="presentation" width="100%" cellpadding="0" cellspacing="0"
|
||
|
|
style="font-size:12.5px;">
|
||
|
|
{% for f in groups.no_baseline[:15] %}
|
||
|
|
<tr>
|
||
|
|
<td style="padding:3px 0;">{{ f.item.name }}</td>
|
||
|
|
<td style="padding:3px 0;text-align:right;font-family:Consolas,Menlo,monospace;">
|
||
|
|
<strong>{{ f.check.latest }}</strong></td>
|
||
|
|
</tr>
|
||
|
|
{% endfor %}
|
||
|
|
{% if groups.no_baseline|length > 15 %}
|
||
|
|
<tr><td colspan="2" style="padding:3px 0;color:#6b6b66;">
|
||
|
|
… and {{ groups.no_baseline|length - 15 }} more in the attached report.</td></tr>
|
||
|
|
{% endif %}
|
||
|
|
</table>
|
||
|
|
</td></tr>
|
||
|
|
{% endif %}
|
||
|
|
|
||
|
|
{% if groups.needs_setup %}
|
||
|
|
<tr><td style="padding:22px 26px 0;">
|
||
|
|
<div style="font-size:11.5px;text-transform:uppercase;letter-spacing:.09em;
|
||
|
|
color:#6b6b66;border-bottom:1px solid #e0e0da;padding-bottom:6px;
|
||
|
|
margin-bottom:10px;">Could not be checked
|
||
|
|
({{ groups.needs_setup|length }})</div>
|
||
|
|
<div style="font-size:13px;line-height:1.6;background:#eef3f8;
|
||
|
|
border-left:3px solid #2b5c8a;padding:11px 14px;
|
||
|
|
border-radius:4px;">
|
||
|
|
<strong>These {{ groups.needs_setup|length }} items are tracked</strong>
|
||
|
|
— AVWatch could not read their versions this run. This is a
|
||
|
|
configuration gap, not a gap in the inventory.
|
||
|
|
{% set by_reason = {} %}
|
||
|
|
{% for f in groups.needs_setup %}
|
||
|
|
{% set r = f.check.detail or 'unknown reason' %}
|
||
|
|
{% if by_reason.update({r: (by_reason.get(r, 0) + 1)}) %}{% endif %}
|
||
|
|
{% endfor %}
|
||
|
|
<ul style="margin:8px 0 0;padding-left:18px;">
|
||
|
|
{% for reason, n in by_reason.items() %}
|
||
|
|
<li style="margin:2px 0;">{{ reason }} — {{ n }} item(s)</li>
|
||
|
|
{% endfor %}
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
</td></tr>
|
||
|
|
{% endif %}
|
||
|
|
|
||
|
|
{% if groups.manual %}
|
||
|
|
<tr><td style="padding:18px 26px 0;">
|
||
|
|
<div style="font-size:12.5px;color:#6b6b66;line-height:1.6;">
|
||
|
|
<strong>Nothing to track ({{ groups.manual|length }}):</strong>
|
||
|
|
{% for f in groups.manual[:8] %}{{ f.item.name }}{% if not loop.last %}, {% endif %}{% endfor %}
|
||
|
|
{%- if groups.manual|length > 8 %} and {{ groups.manual|length - 8 }} others{% endif %}
|
||
|
|
— no field-updatable firmware, so these will never produce an update.
|
||
|
|
</div>
|
||
|
|
</td></tr>
|
||
|
|
{% endif %}
|
||
|
|
|
||
|
|
{% if groups.errors %}
|
||
|
|
<tr><td style="padding:18px 26px 0;">
|
||
|
|
<div style="font-size:12.5px;color:#a3231d;line-height:1.6;">
|
||
|
|
<strong>{{ groups.errors|length }} source(s) failed this run</strong> —
|
||
|
|
likely a vendor page change. See the attached report for details.
|
||
|
|
</div>
|
||
|
|
</td></tr>
|
||
|
|
{% endif %}
|
||
|
|
|
||
|
|
<tr><td style="padding:24px 26px 26px;">
|
||
|
|
<div style="border-top:1px solid #e0e0da;padding-top:12px;font-size:11.5px;
|
||
|
|
color:#6b6b66;line-height:1.65;">
|
||
|
|
Full dashboard, including the up-to-date list and every evidence link, is
|
||
|
|
attached as an HTML file{% if report_path %} and saved at
|
||
|
|
<span style="font-family:Consolas,Menlo,monospace;">{{ report_path }}</span>{% endif %}.<br>
|
||
|
|
Community posts are unverified writing by strangers; AVWatch ranks and
|
||
|
|
summarises them, it does not vouch for them. Always have a rollback plan
|
||
|
|
before touching show-critical gear.<br>
|
||
|
|
Sent by AVWatch on {{ hostname }}.
|
||
|
|
</div>
|
||
|
|
</td></tr>
|
||
|
|
|
||
|
|
</table>
|
||
|
|
</td></tr>
|
||
|
|
</table>
|
||
|
|
</body>
|
||
|
|
</html>
|