Implement database migration, notification logs, and one-click Mailcow setup
This commit is contained in:
131
app/[lang]/dashboard/logs/page.tsx
Normal file
131
app/[lang]/dashboard/logs/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user