Projektstart
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { apiFetch, createEventSourceFor } from "./api";
|
||||
import { downloadFile } from "./export";
|
||||
import { downloadExport } from "./exportHistory";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type Tenant = {
|
||||
@@ -51,6 +52,7 @@ export default function AdminPanel({ token, onImpersonate }: Props) {
|
||||
const [exportTenantId, setExportTenantId] = useState<string | null>(null);
|
||||
const [exportStatus, setExportStatus] = useState<"idle" | "loading" | "done" | "failed">("idle");
|
||||
const [exportJobId, setExportJobId] = useState<string | null>(null);
|
||||
const [exportHistory, setExportHistory] = useState<{ id: string; status: string; expiresAt?: string | null; createdAt?: string }[]>([]);
|
||||
const [exportScope, setExportScope] = useState<"all" | "users" | "accounts" | "jobs" | "rules">("all");
|
||||
const [exportFormat, setExportFormat] = useState<"json" | "csv" | "zip">("json");
|
||||
const [tenantSort, setTenantSort] = useState<"recent" | "oldest" | "name">("recent");
|
||||
@@ -70,6 +72,8 @@ export default function AdminPanel({ token, onImpersonate }: Props) {
|
||||
|
||||
const jobsData = await apiFetch("/admin/jobs", {}, token);
|
||||
setJobs(jobsData.jobs ?? []);
|
||||
const exportsData = await apiFetch("/admin/exports", {}, token);
|
||||
setExportHistory(exportsData.exports ?? []);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -98,14 +102,15 @@ export default function AdminPanel({ token, onImpersonate }: Props) {
|
||||
} else {
|
||||
const result = await apiFetch(`/admin/tenants/${tenant.id}/export?format=zip&scope=${exportScope}`, {}, token);
|
||||
setExportJobId(result.jobId);
|
||||
setExportHistory((prev) => [{ id: result.jobId, status: "QUEUED" }, ...prev]);
|
||||
const source = createEventSourceFor(`exports/${result.jobId}`, token);
|
||||
source.onmessage = async (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
setExportHistory((prev) =>
|
||||
prev.map((item) => (item.id === data.id ? { ...item, status: data.status, expiresAt: data.expiresAt } : item))
|
||||
);
|
||||
if (data.status === "DONE") {
|
||||
const base = import.meta.env.VITE_API_URL ?? "http://localhost:8000";
|
||||
const response = await fetch(`${base}/admin/exports/${result.jobId}/download`, {
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
const response = await downloadExport(token, result.jobId);
|
||||
const blob = await response.blob();
|
||||
downloadFile(blob, `tenant-${tenant.id}.zip`);
|
||||
setExportStatus("done");
|
||||
@@ -309,6 +314,48 @@ export default function AdminPanel({ token, onImpersonate }: Props) {
|
||||
: ""}
|
||||
</p>
|
||||
)}
|
||||
{exportHistory.length > 0 && (
|
||||
<div className="export-history">
|
||||
<h4>{t("exportHistory")}</h4>
|
||||
{exportHistory.map((item) => (
|
||||
<div key={item.id} className="list-item">
|
||||
<div>
|
||||
<strong>{item.id.slice(0, 6)}</strong>
|
||||
<p>
|
||||
{item.expiresAt && new Date(item.expiresAt) < new Date()
|
||||
? t("exportStatusExpired")
|
||||
: item.status === "QUEUED"
|
||||
? t("exportStatusQueued")
|
||||
: item.status === "RUNNING"
|
||||
? t("exportStatusRunning")
|
||||
: item.status === "DONE"
|
||||
? t("exportStatusDone")
|
||||
: t("exportStatusFailed")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="inline-actions">
|
||||
<span>
|
||||
{item.createdAt ? new Date(item.createdAt).toLocaleString() : "-"} ·{" "}
|
||||
{t("exportExpires")}: {item.expiresAt ? new Date(item.expiresAt).toLocaleString() : "-"}
|
||||
</span>
|
||||
<button
|
||||
className="ghost"
|
||||
disabled={item.status !== "DONE" || (item.expiresAt ? new Date(item.expiresAt) < new Date() : false)}
|
||||
onClick={async () => {
|
||||
const response = await downloadExport(token, item.id);
|
||||
if (response.ok) {
|
||||
const blob = await response.blob();
|
||||
downloadFile(blob, `export-${item.id}.zip`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t("exportDownload")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user