Add EPI-Support monitoring & analytics platform

Internal Flask app that monitors the epi-helpdesk.zammad.com tickets via
the Zammad API, deployable as a systemd service behind Apache under
/episupport.

Features:
- Poller: session-login API sync, snapshot diff to detect agent changes
  (status, owner, priority, closures, new replies); email notifications
- Dashboard: ticket overview with sorting, "not closed" filter and
  highlighting of tickets with unconfirmed changes; per-item and bulk
  "mark as read"
- Analytics: KPIs, response/resolution times, monthly SVG trend chart,
  distributions (status/priority/group/agent), Excel export
- Conversation view: full per-ticket thread cached in the DB (avoids
  proxy timeouts), inline image attachments, safe HTML-to-text rendering
- Write actions: reply to tickets and create new tickets with a
  confirmation step and attachment upload (incl. clipboard paste);
  self-triggered actions are synced back so they aren't flagged as changes
- Local timezone display (Europe/Berlin) for all timestamps

Includes deploy tooling: setup.sh, systemd units and Apache config.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 09:19:53 +02:00
co-authored by Claude Opus 4.8
parent da2f7c3c86
commit 1041f283a4
17 changed files with 600 additions and 57 deletions
+23
View File
@@ -186,6 +186,11 @@ table.kv td:first-child { font-weight: 600; }
.msg-subj { font-weight: 600; margin: .35rem 0 .15rem; font-size: .9rem; }
.msg-body { white-space: pre-wrap; word-wrap: break-word; font-size: .9rem; line-height: 1.45; }
.msg-att { margin-top: .5rem; font-size: .8rem; color: var(--muted); }
.msg-imgs { margin-top: .6rem; display: flex; flex-wrap: wrap; gap: .5rem; }
.msg-img {
max-width: 260px; max-height: 260px; border: 1px solid var(--line);
border-radius: 8px; object-fit: contain; background: #fff; cursor: zoom-in;
}
/* --- Formulare / Antworten --- */
.reply-form { padding: .8rem 1.1rem 1.1rem; border-top: 1px solid var(--line); }
@@ -208,3 +213,21 @@ textarea { resize: vertical; }
.btn-danger:hover { background: #a13325; }
.btn-danger:disabled { opacity: .6; cursor: default; }
.btn-ghost { padding: .55rem 1rem; border-radius: 7px; border: 1px solid var(--line); color: #44525e; font-size: .9rem; }
/* --- Anhänge --- */
.attach { display: flex; flex-direction: column; gap: .4rem; }
.attach-label { font-size: .85rem; font-weight: 600; color: #44525e; display: flex; flex-direction: column; gap: .15rem; }
.attach-hint { font-weight: 400; color: var(--muted); font-size: .78rem; }
.attach-input { font: inherit; font-size: .82rem; }
.attach-list { display: flex; flex-wrap: wrap; gap: .4rem; }
.attach-chip {
display: inline-flex; align-items: center; gap: .3rem; background: #eef2f5;
border: 1px solid var(--line); border-radius: 999px; padding: .15rem .6rem;
font-size: .8rem; color: #44525e;
}
.attach-chip button {
border: 0; background: transparent; color: var(--accent); cursor: pointer;
font-size: 1rem; line-height: 1; padding: 0;
}
.attach-form.dragging { outline: 2px dashed var(--brand); outline-offset: 4px; border-radius: 8px; }
.attach-summary { margin: .4rem 0 .2rem; display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; font-size: .85rem; }