Implement database migration, notification logs, and one-click Mailcow setup

This commit is contained in:
AyrisAI
2026-05-14 16:49:11 +03:00
parent f328296c64
commit b024e20027
18 changed files with 1067 additions and 166 deletions

View File

@@ -0,0 +1,131 @@
"use client";
import { useState, useEffect } from "react";
import { useDictionary } from "@/components/DictionaryContext";
interface Log {
id: string;
mailbox: string;
sender: string | null;
subject: string | null;
status: string;
error: string | null;
createdAt: string;
user?: {
name: string | null;
email: string;
} | null;
}
export default function LogsPage() {
const [logs, setLogs] = useState<Log[]>([]);
const [loading, setLoading] = useState(true);
const dict = useDictionary();
const fetchLogs = async () => {
setLoading(true);
try {
const res = await fetch("/api/logs");
if (!res.ok) {
const errorData = await res.json().catch(() => ({}));
throw new Error(errorData.error || `HTTP error! status: ${res.status}`);
}
const data = await res.json();
if (Array.isArray(data)) setLogs(data);
} catch (error: any) {
console.error("Failed to fetch logs:", error);
alert(error.message);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchLogs();
}, []);
return (
<>
<div className="page-header">
<div>
<h1 className="page-title">{dict.logs?.title || "Bildirim Logları"}</h1>
<p className="page-subtitle">{dict.logs?.subtitle || "Son gönderilen bildirimlerin durumu"}</p>
</div>
<button className="btn btn-ghost" onClick={fetchLogs} disabled={loading}>
<RefreshIcon />
</button>
</div>
<div className="page-body">
<div className="table-wrap">
{loading ? (
<div className="empty-state">
<span className="spinner" style={{ width: 24, height: 24 }} />
</div>
) : logs.length === 0 ? (
<div className="empty-state">
<div style={{ fontWeight: 600 }}>{dict.logs?.noLogs || "Log kaydı bulunamadı"}</div>
</div>
) : (
<table>
<thead>
<tr>
<th>{dict.logs?.mailbox || "Alıcı"}</th>
<th>{dict.logs?.sender || "Gönderen"} / {dict.logs?.subject || "Konu"}</th>
<th>{dict.logs?.status || "Durum"}</th>
<th>{dict.logs?.date || "Tarih"}</th>
</tr>
</thead>
<tbody>
{logs.map((log) => (
<tr key={log.id}>
<td>
<div style={{ fontWeight: 500 }}>{log.mailbox}</div>
{log.user && (
<div style={{ fontSize: 11, color: "var(--text-secondary)" }}>
{log.user.name || log.user.email}
</div>
)}
</td>
<td>
<div style={{ fontSize: 13, fontWeight: 500 }}>{log.sender || "Unknown"}</div>
<div style={{ fontSize: 12, color: "var(--text-secondary)", maxWidth: 300, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{log.subject || "(No Subject)"}
</div>
</td>
<td>
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
<span className={`badge ${log.status === "SENT" ? "badge-green" : "badge-red"}`} style={{ width: "fit-content" }}>
{log.status === "SENT" ? (dict.logs?.sent || "GÖNDERİLDİ") : (dict.logs?.failed || "HATA")}
</span>
{log.error && (
<div style={{ fontSize: 10, color: "var(--text-red)", maxWidth: 200, wordBreak: "break-word" }}>
{log.error}
</div>
)}
</div>
</td>
<td style={{ fontSize: 12, color: "var(--text-secondary)" }}>
{new Date(log.createdAt).toLocaleString()}
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
</div>
</>
);
}
function RefreshIcon() {
return (
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" />
<path d="M3 21v-5h5" />
</svg>
);
}