Commits vergleichen

..

3 Commits

Autor SHA1 Nachricht Datum
38ce26f0be Promote develop → main (2026-05-22 19:10 UTC) 2026-05-22 21:10:42 +02:00
7f7b30c1d6 Release-Notes: Exportdialog: Ersteller manuell eintragbar 2026-05-22 21:10:28 +02:00
Claude Code
d986d611cf feat(export): Ersteller im Export-Dialog manuell eingebbar
Der Export-Dialog hat ein neues optionales Feld "Ersteller". Ist es
gefuellt, wird dieser Name im Bericht als Ersteller verwendet; bleibt es
leer, gilt wie bisher die E-Mail des Lage-Erstellers.

- export_incident: optionaler Query-Parameter creator, hat Vorrang vor
  der E-Mail-Ableitung
- exportReport (api.js) haengt creator an die Export-URL
- submitExport (app.js) liest das neue Feld aus
- Eingabefeld im Export-Modal (dashboard.html)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 19:08:26 +00:00
5 geänderte Dateien mit 29 neuen und 8 gelöschten Zeilen

Datei anzeigen

@@ -1,4 +1,12 @@
[ [
{
"version": "2026-05-22T19:10Z",
"date": "2026-05-22",
"title": "Exportdialog: Ersteller manuell eintragbar",
"items": [
"Im Export-Dialog kann der Ersteller jetzt manuell eingegeben werden."
]
},
{ {
"version": "2026-05-22T07:41Z", "version": "2026-05-22T07:41Z",
"date": "2026-05-22", "date": "2026-05-22",

Datei anzeigen

@@ -1153,6 +1153,7 @@ async def export_incident(
scope: str = Query("report", pattern="^(summary|report|full)$"), scope: str = Query("report", pattern="^(summary|report|full)$"),
sections: str = Query(None), sections: str = Query(None),
branding: str = Query("on", pattern="^(on|off)$"), branding: str = Query("on", pattern="^(on|off)$"),
creator: str = Query(None, max_length=120),
current_user: dict = Depends(get_current_user), current_user: dict = Depends(get_current_user),
db: aiosqlite.Connection = Depends(db_dependency), db: aiosqlite.Connection = Depends(db_dependency),
): ):
@@ -1171,10 +1172,13 @@ async def export_incident(
row = await _check_incident_access(db, incident_id, current_user["id"], tenant_id) row = await _check_incident_access(db, incident_id, current_user["id"], tenant_id)
incident = dict(row) incident = dict(row)
# Ersteller-Name # Ersteller-Name: manuell uebergebener Wert hat Vorrang, sonst E-Mail des Lage-Erstellers
cursor = await db.execute("SELECT email FROM users WHERE id = ?", (incident["created_by"],)) if creator and creator.strip():
user_row = await cursor.fetchone() creator = creator.strip()
creator = user_row["email"] if user_row else "Unbekannt" else:
cursor = await db.execute("SELECT email FROM users WHERE id = ?", (incident["created_by"],))
user_row = await cursor.fetchone()
creator = user_row["email"] if user_row else "Unbekannt"
# Organisation (fuer Dateimetadaten) # Organisation (fuer Dateimetadaten)
organization_name = None organization_name = None

Datei anzeigen

@@ -805,12 +805,12 @@
<script src="/static/vendor/leaflet.js"></script> <script src="/static/vendor/leaflet.js"></script>
<script src="/static/vendor/leaflet.markercluster.js"></script> <script src="/static/vendor/leaflet.markercluster.js"></script>
<script src="/static/js/i18n.js?v=20260513a"></script> <script src="/static/js/i18n.js?v=20260513a"></script>
<script src="/static/js/api.js?v=20260522e"></script> <script src="/static/js/api.js?v=20260522f"></script>
<script src="/static/js/ws.js?v=20260316b"></script> <script src="/static/js/ws.js?v=20260316b"></script>
<script src="/static/js/components.js?v=20260522d"></script> <script src="/static/js/components.js?v=20260522d"></script>
<script src="/static/js/layout.js?v=20260513f"></script> <script src="/static/js/layout.js?v=20260513f"></script>
<script src="/static/js/pipeline.js?v=20260513d"></script> <script src="/static/js/pipeline.js?v=20260513d"></script>
<script src="/static/js/app.js?v=20260522e"></script> <script src="/static/js/app.js?v=20260522f"></script>
<script src="/static/js/cluster-data.js?v=20260322f"></script> <script src="/static/js/cluster-data.js?v=20260322f"></script>
<script src="/static/js/tutorial.js?v=20260316z"></script> <script src="/static/js/tutorial.js?v=20260316z"></script>
<script src="/static/js/chat.js?v=20260514e"></script> <script src="/static/js/chat.js?v=20260514e"></script>
@@ -855,6 +855,11 @@
<label class="export-radio"><input type="radio" name="export-branding" value="on" checked><span data-i18n="export.branding.on">Mit AegisSight-Branding</span></label> <label class="export-radio"><input type="radio" name="export-branding" value="on" checked><span data-i18n="export.branding.on">Mit AegisSight-Branding</span></label>
<label class="export-radio"><input type="radio" name="export-branding" value="off"><span data-i18n="export.branding.off">Ohne Firmen-Branding</span></label> <label class="export-radio"><input type="radio" name="export-branding" value="off"><span data-i18n="export.branding.off">Ohne Firmen-Branding</span></label>
</div> </div>
<div style="margin-bottom:0;">
<label for="export-ersteller" style="font-size:11px;text-transform:uppercase;letter-spacing:1px;color:var(--text-secondary);display:block;margin-bottom:8px;">Ersteller</label>
<input type="text" id="export-ersteller" maxlength="120" placeholder="Name des Erstellers (optional)" style="width:100%;box-sizing:border-box;">
<div style="font-size:11px;color:var(--text-secondary);margin-top:6px;">Leer lassen, dann wird automatisch der Lage-Ersteller verwendet.</div>
</div>
</div> </div>
<div class="modal-footer" style="padding:12px 20px;display:flex;justify-content:flex-end;gap:8px;border-top:1px solid var(--border);"> <div class="modal-footer" style="padding:12px 20px;display:flex;justify-content:flex-end;gap:8px;border-top:1px solid var(--border);">
<button class="btn btn-secondary" onclick="closeModal('modal-export')" data-i18n="common.cancel">Abbrechen</button> <button class="btn btn-secondary" onclick="closeModal('modal-export')" data-i18n="common.cancel">Abbrechen</button>

Datei anzeigen

@@ -330,7 +330,7 @@ const API = {
resetTutorialState() { resetTutorialState() {
return this._request('DELETE', '/tutorial/state'); return this._request('DELETE', '/tutorial/state');
}, },
exportReport(id, format, scope, sections, includeBranding) { exportReport(id, format, scope, sections, includeBranding, creator) {
const token = localStorage.getItem('osint_token'); const token = localStorage.getItem('osint_token');
let url = `${this.baseUrl}/incidents/${id}/export?format=${format}`; let url = `${this.baseUrl}/incidents/${id}/export?format=${format}`;
if (sections && sections.length > 0) { if (sections && sections.length > 0) {
@@ -341,6 +341,9 @@ const API = {
if (includeBranding === false) { if (includeBranding === false) {
url += `&branding=off`; url += `&branding=off`;
} }
if (creator) {
url += `&creator=${encodeURIComponent(creator)}`;
}
return fetch(url, { return fetch(url, {
headers: { 'Authorization': `Bearer ${token}` }, headers: { 'Authorization': `Bearer ${token}` },
}); });

Datei anzeigen

@@ -2639,6 +2639,7 @@ async handleRefresh() {
const format = document.querySelector('input[name="export-format"]:checked').value; const format = document.querySelector('input[name="export-format"]:checked').value;
const brandingEl = document.querySelector('input[name="export-branding"]:checked'); const brandingEl = document.querySelector('input[name="export-branding"]:checked');
const includeBranding = !brandingEl || brandingEl.value === 'on'; const includeBranding = !brandingEl || brandingEl.value === 'on';
const ersteller = (document.getElementById('export-ersteller')?.value || '').trim();
const btn = document.getElementById('export-submit-btn'); const btn = document.getElementById('export-submit-btn');
const origText = btn.textContent; const origText = btn.textContent;
@@ -2646,7 +2647,7 @@ async handleRefresh() {
btn.textContent = (typeof T === 'function' ? T('action.creating', 'Wird erstellt...') : 'Wird erstellt...'); btn.textContent = (typeof T === 'function' ? T('action.creating', 'Wird erstellt...') : 'Wird erstellt...');
try { try {
const response = await API.exportReport(this.currentIncidentId, format, null, sections, includeBranding); const response = await API.exportReport(this.currentIncidentId, format, null, sections, includeBranding, ersteller);
if (!response.ok) { if (!response.ok) {
const err = await response.json().catch(() => ({})); const err = await response.json().catch(() => ({}));
throw new Error(err.detail || 'Fehler ' + response.status); throw new Error(err.detail || 'Fehler ' + response.status);