Dateien
AegisSight-Monitor/src/static/js/api.js
claude-dev c3680c3673 Add image attachments to feedback form (JPEG/PNG)
- File input in feedback modal (max 3 images, 5 MB each)
- Frontend validation for file count and size
- Backend: multipart/form-data with UploadFile, MIME attachments
- Images attached to feedback email as base64-encoded attachments
- Only JPEG and PNG allowed (validated server-side)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 13:35:47 +01:00

225 Zeilen
6.5 KiB
JavaScript

/**
* API-Client für den OSINT Lagemonitor.
*/
const API = {
baseUrl: '/api',
_getHeaders() {
const token = localStorage.getItem('osint_token');
return {
'Content-Type': 'application/json',
'Authorization': token ? `Bearer ${token}` : '',
};
},
async _request(method, path, body = null) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30000);
const options = {
method,
headers: this._getHeaders(),
signal: controller.signal,
};
if (body) {
options.body = JSON.stringify(body);
}
let response;
try {
response = await fetch(`${this.baseUrl}${path}`, options);
} catch (err) {
clearTimeout(timeout);
if (err.name === 'AbortError') {
throw new Error('Zeitüberschreitung bei der Anfrage');
}
throw err;
}
clearTimeout(timeout);
if (response.status === 401) {
localStorage.removeItem('osint_token');
localStorage.removeItem('osint_username');
window.location.href = '/';
return;
}
if (!response.ok) {
const data = await response.json().catch(() => ({}));
let detail = data.detail;
if (Array.isArray(detail)) {
detail = detail.map(e => e.msg || JSON.stringify(e)).join('; ');
} else if (typeof detail === 'object' && detail !== null) {
detail = JSON.stringify(detail);
}
throw new Error(detail || `Fehler ${response.status}`);
}
if (response.status === 204) return null;
return response.json();
},
// Auth
getMe() {
return this._request('GET', '/auth/me');
},
// Incidents
listIncidents(statusFilter = null) {
const query = statusFilter ? `?status_filter=${statusFilter}` : '';
return this._request('GET', `/incidents${query}`);
},
createIncident(data) {
return this._request('POST', '/incidents', data);
},
getRefreshingIncidents() {
return this._request('GET', '/incidents/refreshing');
},
getIncident(id) {
return this._request('GET', `/incidents/${id}`);
},
updateIncident(id, data) {
return this._request('PUT', `/incidents/${id}`, data);
},
deleteIncident(id) {
return this._request('DELETE', `/incidents/${id}`);
},
getArticles(incidentId) {
return this._request('GET', `/incidents/${incidentId}/articles`);
},
getFactChecks(incidentId) {
return this._request('GET', `/incidents/${incidentId}/factchecks`);
},
getSnapshots(incidentId) {
return this._request('GET', `/incidents/${incidentId}/snapshots`);
},
getLocations(incidentId) {
return this._request('GET', `/incidents/${incidentId}/locations`);
},
triggerGeoparse(incidentId) {
return this._request('POST', `/incidents/${incidentId}/geoparse`);
},
getGeoparseStatus(incidentId) {
return this._request('GET', `/incidents/${incidentId}/geoparse-status`);
},
refreshIncident(id) {
return this._request('POST', `/incidents/${id}/refresh`);
},
getRefreshLog(incidentId, limit = 20) {
return this._request('GET', `/incidents/${incidentId}/refresh-log?limit=${limit}`);
},
// Sources (Quellenverwaltung)
listSources(params = {}) {
const query = new URLSearchParams();
if (params.source_type) query.set('source_type', params.source_type);
if (params.category) query.set('category', params.category);
if (params.source_status) query.set('source_status', params.source_status);
const qs = query.toString();
return this._request('GET', `/sources${qs ? '?' + qs : ''}`);
},
createSource(data) {
return this._request('POST', '/sources', data);
},
updateSource(id, data) {
return this._request('PUT', `/sources/${id}`, data);
},
deleteSource(id) {
return this._request('DELETE', `/sources/${id}`);
},
getSourceStats() {
return this._request('GET', '/sources/stats');
},
discoverMulti(url) {
return this._request('POST', '/sources/discover-multi', { url });
},
getMyExclusions() {
return this._request('GET', '/sources/my-exclusions');
},
blockDomain(domain, notes) {
return this._request('POST', '/sources/block-domain', { domain, notes });
},
unblockDomain(domain) {
return this._request('POST', '/sources/unblock-domain', { domain });
},
deleteDomain(domain) {
return this._request('DELETE', `/sources/domain/${encodeURIComponent(domain)}`);
},
cancelRefresh(id) {
return this._request('POST', `/incidents/${id}/cancel-refresh`);
},
// Notifications
listNotifications(limit = 50) {
return this._request('GET', `/notifications?limit=${limit}`);
},
markNotificationsRead(ids = null) {
return this._request('PUT', '/notifications/mark-read', { notification_ids: ids });
},
// Subscriptions (E-Mail-Benachrichtigungen)
getSubscription(incidentId) {
return this._request('GET', '/incidents/' + incidentId + '/subscription');
},
updateSubscription(incidentId, data) {
return this._request('PUT', '/incidents/' + incidentId + '/subscription', data);
},
// Feedback
sendFeedback(data) {
return this._request('POST', '/feedback', data);
},
async sendFeedbackForm(formData) {
const token = localStorage.getItem('osint_token');
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 60000);
const resp = await fetch(this.baseUrl + '/feedback', {
method: 'POST',
headers: { 'Authorization': token ? 'Bearer ' + token : '' },
body: formData,
signal: controller.signal,
});
clearTimeout(timeout);
if (!resp.ok) {
const err = await resp.json().catch(() => ({}));
throw new Error(err.detail || 'Fehler ' + resp.status);
}
},
// Export
exportIncident(id, format, scope) {
const token = localStorage.getItem('osint_token');
return fetch(`${this.baseUrl}/incidents/${id}/export?format=${format}&scope=${scope}`, {
headers: { 'Authorization': `Bearer ${token}` },
});
},
};