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:
@@ -0,0 +1,7 @@
|
||||
<div class="attach">
|
||||
<label class="attach-label">Anhänge
|
||||
<span class="attach-hint">Screenshot mit Strg+V einfügen, Dateien hierher ziehen oder auswählen</span>
|
||||
</label>
|
||||
<input class="attach-input" type="file" name="files" multiple>
|
||||
<div class="attach-list"></div>
|
||||
</div>
|
||||
@@ -32,5 +32,6 @@
|
||||
<footer>
|
||||
EPI-Support-Monitor · liest <code>{{ zammad_url }}</code> · Seite aktualisiert sich automatisch
|
||||
</footer>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -8,6 +8,13 @@
|
||||
<p class="warn">⚠ {{ intro }}</p>
|
||||
<div class="preview-box">{{ preview }}</div>
|
||||
|
||||
{% if attachments %}
|
||||
<div class="attach-summary">
|
||||
<strong>Anhänge ({{ attachments|length }}):</strong>
|
||||
{% for name in attachments %}<span class="attach-chip">📎 {{ name }}</span>{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="{{ action_url }}" class="confirm-actions"
|
||||
onsubmit="this.querySelector('button').disabled=true;">
|
||||
{% for name, value in fields.items() %}
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-head"><h2>Neues Ticket beim EPI-Support anlegen</h2></div>
|
||||
<form method="post" action="{{ url_for('new_ticket_preview') }}" class="form">
|
||||
<form method="post" action="{{ url_for('new_ticket_preview') }}"
|
||||
class="form attach-form" enctype="multipart/form-data">
|
||||
<label>Titel / Betreff
|
||||
<input type="text" name="title" required maxlength="200"
|
||||
placeholder="Kurze Zusammenfassung des Anliegens">
|
||||
@@ -20,11 +21,13 @@
|
||||
</label>
|
||||
<label>Nachricht
|
||||
<textarea name="body" rows="10" required
|
||||
placeholder="Beschreibe dein Anliegen…"></textarea>
|
||||
placeholder="Beschreibe dein Anliegen… (Screenshot mit Strg+V einfügen)"></textarea>
|
||||
</label>
|
||||
{% include "_attach.html" %}
|
||||
<div class="form-actions">
|
||||
<button class="btn-sm" type="submit">Weiter zur Bestätigung →</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
{% endblock %}
|
||||
{% block scripts %}<script src="{{ url_for('static', filename='upload.js') }}"></script>{% endblock %}
|
||||
|
||||
+27
-3
@@ -25,6 +25,9 @@
|
||||
<section class="panel">
|
||||
<div class="panel-head">
|
||||
<h2>Konversation{% if conversation %} ({{ conversation|length }}){% endif %}</h2>
|
||||
<form method="post" action="{{ url_for('refresh_conversation', ticket_id=ticket.id) }}">
|
||||
<button class="btn-sm" type="submit">↻ Aktualisieren</button>
|
||||
</form>
|
||||
</div>
|
||||
{% if conv_error %}
|
||||
<p class="muted">Konversation konnte nicht geladen werden: {{ conv_error }}</p>
|
||||
@@ -42,7 +45,25 @@
|
||||
{% if a.subject %}<div class="msg-subj">{{ a.subject }}</div>{% endif %}
|
||||
<div class="msg-body">{{ a.body }}</div>
|
||||
{% if a.attachments %}
|
||||
<div class="msg-att">📎 {{ a.attachments|join(', ') }}</div>
|
||||
{% set imgs = a.attachments|selectattr('is_image')|list %}
|
||||
{% set files = a.attachments|rejectattr('is_image')|list %}
|
||||
{% if imgs %}
|
||||
<div class="msg-imgs">
|
||||
{% for att in imgs %}
|
||||
<a href="{{ url_for('attachment', ticket_id=ticket.id, article_id=a.id, att_id=att.id) }}" target="_blank" rel="noopener">
|
||||
<img class="msg-img" loading="lazy" alt="{{ att.filename }}"
|
||||
src="{{ url_for('attachment', ticket_id=ticket.id, article_id=a.id, att_id=att.id) }}">
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if files %}
|
||||
<div class="msg-att">
|
||||
{% for att in files %}
|
||||
📎 {% if att.id %}<a href="{{ url_for('attachment', ticket_id=ticket.id, article_id=a.id, att_id=att.id) }}" target="_blank" rel="noopener">{{ att.filename }}</a>{% else %}{{ att.filename }}{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</article>
|
||||
{% endfor %}
|
||||
@@ -52,9 +73,11 @@
|
||||
{% endif %}
|
||||
|
||||
{% if ticket.state != 'closed' %}
|
||||
<form method="post" action="{{ url_for('reply_preview', ticket_id=ticket.id) }}" class="reply-form">
|
||||
<form method="post" action="{{ url_for('reply_preview', ticket_id=ticket.id) }}"
|
||||
class="reply-form attach-form" enctype="multipart/form-data">
|
||||
<textarea name="body" rows="4" required
|
||||
placeholder="Antwort an den EPI-Support verfassen…"></textarea>
|
||||
placeholder="Antwort an den EPI-Support verfassen… (Screenshot mit Strg+V einfügen)"></textarea>
|
||||
{% include "_attach.html" %}
|
||||
<div class="form-actions">
|
||||
<span class="hint-inline">Geht nach Bestätigung live an EPI.</span>
|
||||
<button class="btn-sm" type="submit">Antworten →</button>
|
||||
@@ -84,3 +107,4 @@
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endblock %}
|
||||
{% block scripts %}<script src="{{ url_for('static', filename='upload.js') }}"></script>{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user