' % (html.escape(label), value)
def _tickets_body(filter_status=None, filter_kategorie=None):
tickets = sorted(_load("tickets.json", []), key=lambda t: t["aktualisiert"], reverse=True)
kpis = _kpis(tickets)
kpi_html = "".join(_kpi_tile(k, v) for k, v in kpis.items())
shown = tickets
if filter_kategorie:
shown = [t for t in shown if t["kategorie"] == filter_kategorie]
tabs = [("", "Alle"), ("Offen", "Offen"), ("In Bearbeitung", "In Bearbeitung"),
("Warten", "Warten"), ("Geloest", "Geloest"), ("Ueberfaellig", "Ueberfaellig")]
base = "/probleme" if filter_kategorie == "Problem" else (
"/aenderungen" if filter_kategorie == "Change" else (
"/releases" if filter_kategorie == "Release" else "/tickets"))
tabs_html = "".join(
'%s' % (
base, ("?status=" + s) if s else "", "active" if (s or "") == (filter_status or "") else "", label)
for s, label in tabs)
if filter_status == "Ueberfaellig":
shown = [t for t in shown if _is_overdue(t)]
elif filter_status:
shown = [t for t in shown if t["status"] == filter_status]
rows = "".join(_ticket_row(t) for t in shown) or '
Keine Tickets.
'
new_form = """
Neues Ticket
""" % (
"".join('' % (c, c) for c in CATEGORIES),
"".join('' % (p, p) for p in PRIORITIES))
return """
Tickets
%s
%s
%s
Ticket-Nr
Titel (Service)
Status
Prioritaet
Kategorie
Zugewiesen an
Aktualisiert
Fortschritt
%s
""" % (kpi_html, new_form, tabs_html, rows)
@app.route("/tickets")
def tickets_route():
r = _require_login()
if r:
return r
return _shell("/tickets", "Tickets", _tickets_body(filter_status=request.args.get("status")))
@app.route("/probleme")
def probleme_route():
r = _require_login()
if r:
return r
return _shell("/probleme", "Probleme", _tickets_body(request.args.get("status"), "Problem"))
@app.route("/aenderungen")
def aenderungen_route():
r = _require_login()
if r:
return r
return _shell("/aenderungen", "Aenderungen", _tickets_body(request.args.get("status"), "Change"))
@app.route("/releases")
def releases_route():
r = _require_login()
if r:
return r
return _shell("/releases", "Releases", _tickets_body(request.args.get("status"), "Release"))
@app.route("/tickets/new", methods=["POST"])
def tickets_new_route():
r = _require_login()
if r:
return r
with _LOCK:
tickets = _load("tickets.json", [])
tid = _next_ticket_id(tickets)
t = {
"id": tid,
"titel": request.form.get("titel", "").strip() or "(ohne Titel)",
"beschreibung": request.form.get("beschreibung", "").strip(),
"service": request.form.get("service", "").strip(),
"status": "Offen",
"prioritaet": request.form.get("prioritaet", "Mittel"),
"kategorie": request.form.get("kategorie", "Task"),
"zugewiesen_an": session.get("user", ITSM_USER),
"erstellt": _now(),
"aktualisiert": _now(),
"fortschritt": 0,
"betroffene_services": [],
"ci_elemente": [],
"sla_antwort_minuten": 480,
"sla_loesung_minuten": 2880,
"timeline": [{"zeit": _now(), "akteur": session.get("user", ITSM_USER), "text": "Ticket angelegt."}],
}
tickets.append(t)
_save("tickets.json", tickets)
return redirect(url_for("tickets_route"))
@app.route("/api/tickets/")
def api_ticket(tid):
r = _require_login()
if r:
return r
tickets = _load("tickets.json", [])
for t in tickets:
if t["id"] == tid:
return jsonify(t)
abort(404)
# ── Service-Katalog ────────────────────────────────────────────────────────────
def _aes_live_status():
"""Fragt den Status des AES-Service live ueber dessen Dashboard-API ab
(kein Code-Import -- ITSM und AES sind getrennte Systeme, verbunden nur
per HTTP, wie es die Rolle 'AES als buchbarer Service' verlangt)."""
if not AES_DASHBOARD_URL:
return None
try:
req = urllib.request.Request(AES_DASHBOARD_URL + "/health", method="GET")
with urllib.request.urlopen(req, timeout=4) as resp:
return resp.status == 200
except Exception:
return None
def _services_body():
services = _load("services.json", [])
cards = []
for s in services:
badge = 'gebucht' if s.get("gebucht") else 'nicht gebucht'
link = ('
' + "".join(cards) + add_form
@app.route("/services")
def services_route():
r = _require_login()
if r:
return r
return _shell("/services", "Service-Katalog", _services_body())
@app.route("/services/new", methods=["POST"])
def services_new_route():
r = _require_login()
if r:
return r
with _LOCK:
services = _load("services.json", [])
services.append({
"id": uuid.uuid4().hex[:8],
"name": request.form.get("name", "").strip(),
"beschreibung": request.form.get("beschreibung", "").strip(),
"kategorie": "Sonstiges",
"gebucht": True,
"endpoint": request.form.get("endpoint", "").strip(),
"hinzugefuegt": _now(),
})
_save("services.json", services)
return redirect(url_for("services_route"))
# ── Platzhalterseiten (Wissen/Assets/Administration) ──────────────────────────
def _placeholder(title, note):
return '
%s
%s
' % (
html.escape(title), html.escape(note))
@app.route("/wissen")
def wissen_route():
r = _require_login()
if r:
return r
return _shell("/wissen", "Wissensdatenbank",
_placeholder("Wissensdatenbank", "Noch nicht aufgebaut -- folgt in einer naechsten Ausbaustufe."))
@app.route("/assets")
def assets_route():
r = _require_login()
if r:
return r
return _shell("/assets", "Asset-Liste",
_placeholder("Asset-Liste", "Noch nicht aufgebaut -- folgt in einer naechsten Ausbaustufe."))
@app.route("/admin")
def admin_route():
r = _require_login()
if r:
return r
return _shell("/admin", "Einstellungen",
_placeholder("Einstellungen", "Benutzer/Rollen/Workflows folgen in einer naechsten Ausbaustufe."))
@app.route("/")
def root_route():
return redirect(url_for("tickets_route"))
@app.route("/health")
def health_route():
return jsonify({"status": "ok"})
_seed_services_if_empty()
_seed_if_empty()
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)