feat(06.2-04): frontend — user_handle filter, fetch+Blob export, daily-export section

- adminListAuditLog: rename user_id param to user_handle (backend API change)
- adminExportAuditLogCsv(): fetch+Blob pattern — sends Bearer header (D-13, T-06.2-04-03)
- adminListDailyExports(): raw fetch returning JSON for daily export listing (D-17)
- adminDownloadDailyExport(date): fetch+Blob download with audit-{date}.csv filename (D-17)
- AuditLogTab: rename filters.user_id to filters.user_handle + label 'User handle' (D-12, C-5)
- AuditLogTab: exportCsv() replaced with async fetch+Blob call, exportingCsv loading state
- AuditLogTab: daily exports section below pagination — date dropdown + Download button (D-17, C-4)
- window.location.href removed from AuditLogTab (broken auth bypass closed)
- Build exits 0, full backend suite: 337 passed, 1 pre-existing failure
This commit is contained in:
curo1305
2026-05-31 15:21:23 +02:00
parent 839bfe0ffe
commit 0647e6e9bf
2 changed files with 197 additions and 15 deletions
+96 -2
View File
@@ -377,17 +377,111 @@ export function updateMyPreferences(payload) {
// ── Audit Log ─────────────────────────────────────────────────────────────────
export function adminListAuditLog({ start, end, user_id, event_type, page = 1, per_page = 50 } = {}) {
export function adminListAuditLog({ start, end, user_handle, event_type, page = 1, per_page = 50 } = {}) {
const params = new URLSearchParams()
if (start) params.set('start', start)
if (end) params.set('end', end)
if (user_id) params.set('user_id', user_id)
if (user_handle) params.set('user_handle', user_handle)
if (event_type) params.set('event_type', event_type)
params.set('page', page)
params.set('per_page', per_page)
return request(`/api/admin/audit-log?${params}`)
}
/**
* Export the audit log as a CSV file using fetch + Blob URL.
*
* Unlike window.location.href, this sends the Authorization Bearer header so
* the endpoint can authenticate the request (D-13, T-06.2-04-03).
* Must NOT call res.json() — CSV is text/csv (Pitfall 5).
*/
export async function adminExportAuditLogCsv(params = {}) {
const { useAuthStore } = await import('../stores/auth.js')
const authStore = useAuthStore()
const searchParams = new URLSearchParams({ format: 'csv' })
if (params.start) searchParams.set('start', params.start)
if (params.end) searchParams.set('end', params.end)
if (params.user_handle) searchParams.set('user_handle', params.user_handle)
if (params.event_type) searchParams.set('event_type', params.event_type)
const headers = {}
if (authStore.accessToken) {
headers['Authorization'] = `Bearer ${authStore.accessToken}`
}
const res = await fetch(`/api/admin/audit-log/export?${searchParams}`, {
headers,
credentials: 'include',
})
if (!res.ok) throw new Error(`Export failed: ${res.status}`)
const text = await res.text()
const blob = new Blob([text], { type: 'text/csv' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'audit-export.csv'
a.click()
URL.revokeObjectURL(url)
}
/**
* List available Celery daily audit export files from the MinIO audit-logs bucket.
*
* Returns: { items: [{ date: "YYYY-MM-DD", key: "audit-logs/YYYY-MM-DD.csv" }] }
* Items are sorted descending by date.
*/
export async function adminListDailyExports() {
const { useAuthStore } = await import('../stores/auth.js')
const authStore = useAuthStore()
const headers = {}
if (authStore.accessToken) {
headers['Authorization'] = `Bearer ${authStore.accessToken}`
}
const res = await fetch('/api/admin/audit-log/daily-exports', {
headers,
credentials: 'include',
})
if (!res.ok) throw new Error(`Failed to list daily exports: ${res.status}`)
return res.json()
}
/**
* Download a specific Celery daily audit export file from MinIO using fetch + Blob URL.
*
* Uses the same fetch+Blob pattern as adminExportAuditLogCsv to send the
* Authorization Bearer header (D-17, T-06.2-04-03).
*
* @param {string} date — YYYY-MM-DD format date string
*/
export async function adminDownloadDailyExport(date) {
const { useAuthStore } = await import('../stores/auth.js')
const authStore = useAuthStore()
const headers = {}
if (authStore.accessToken) {
headers['Authorization'] = `Bearer ${authStore.accessToken}`
}
const res = await fetch(`/api/admin/audit-log/daily-exports/${date}`, {
headers,
credentials: 'include',
})
if (!res.ok) throw new Error(`Download failed: ${res.status}`)
const text = await res.text()
const blob = new Blob([text], { type: 'text/csv' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `audit-${date}.csv`
a.click()
URL.revokeObjectURL(url)
}
// ── Document content proxy URL ────────────────────────────────────────────────
export function getDocumentContentUrl(docId) {