feat: add cron job management functionality with status retrieval and action handling

This commit is contained in:
2026-01-12 12:22:00 +01:00
parent a8db92731f
commit e35f9b3e7b
3 changed files with 395 additions and 0 deletions

View File

@@ -12,6 +12,9 @@ export default function AdminSettingsPage() {
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [users, setUsers] = useState([]);
const [cronStatus, setCronStatus] = useState(null);
const [cronLoading, setCronLoading] = useState(false);
const [cronActionLoading, setCronActionLoading] = useState(null);
// Redirect if not admin
useEffect(() => {
@@ -22,6 +25,7 @@ export default function AdminSettingsPage() {
}
fetchSettings();
fetchUsers();
fetchCronStatus();
}, [session, status, router]);
const fetchSettings = async () => {
@@ -50,6 +54,44 @@ export default function AdminSettingsPage() {
}
};
const fetchCronStatus = async () => {
setCronLoading(true);
try {
const response = await fetch("/api/admin/cron");
if (response.ok) {
const data = await response.json();
setCronStatus(data);
}
} catch (error) {
console.error("Error fetching cron status:", error);
} finally {
setCronLoading(false);
}
};
const handleCronAction = async (action) => {
setCronActionLoading(action);
try {
const response = await fetch("/api/admin/cron", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action }),
});
const data = await response.json();
if (data.success) {
alert(data.message);
fetchCronStatus();
} else {
alert("Error: " + data.message);
}
} catch (error) {
console.error("Error performing cron action:", error);
alert("Error performing action");
} finally {
setCronActionLoading(null);
}
};
const updateSetting = async (key, value) => {
setSaving(true);
try {
@@ -166,6 +208,116 @@ export default function AdminSettingsPage() {
</div>
{/* Future settings can be added here */}
<div className="border rounded-lg p-4">
<div className="flex items-center justify-between mb-2">
<h3 className="text-lg font-medium text-gray-900">
Cron Jobs Status
</h3>
<button
onClick={fetchCronStatus}
disabled={cronLoading}
className="text-sm text-blue-600 hover:text-blue-800"
>
{cronLoading ? "Refreshing..." : "↻ Refresh"}
</button>
</div>
{cronLoading && !cronStatus ? (
<p className="text-sm text-gray-500">Loading cron status...</p>
) : cronStatus ? (
<div className="space-y-4">
{/* Status indicators */}
<div className="flex flex-wrap gap-3">
<div className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-medium ${
cronStatus.available
? "bg-green-100 text-green-800"
: "bg-yellow-100 text-yellow-800"
}`}>
{cronStatus.available ? "✓ Cron Available" : "⚠ Cron Unavailable"}
</div>
{cronStatus.available && (
<div className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-medium ${
cronStatus.running
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`}>
{cronStatus.running ? "✓ Daemon Running" : "✗ Daemon Not Running"}
</div>
)}
{cronStatus.available && (
<div className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
{cronStatus.jobCount || 0} Job(s) Scheduled
</div>
)}
</div>
{/* Scheduled jobs */}
{cronStatus.jobs && cronStatus.jobs.length > 0 && (
<div>
<p className="text-sm font-medium text-gray-700 mb-1">Scheduled Jobs:</p>
<div className="bg-gray-50 rounded p-2 font-mono text-xs">
{cronStatus.jobs.map((job, idx) => (
<div key={idx} className="py-0.5">{job}</div>
))}
</div>
</div>
)}
{/* Last backup info */}
{cronStatus.lastBackup && (
<div className="text-sm">
<span className="font-medium text-gray-700">Last Backup: </span>
{cronStatus.lastBackup.exists ? (
<span className="text-green-600">
{cronStatus.lastBackup.filename} ({new Date(cronStatus.lastBackup.date).toLocaleString()})
<span className="text-gray-500 ml-2">
({cronStatus.lastBackup.count} total backups)
</span>
</span>
) : (
<span className="text-gray-500">{cronStatus.lastBackup.message || "No backups"}</span>
)}
</div>
)}
{/* Message for non-Linux environments */}
{cronStatus.message && (
<p className="text-sm text-yellow-600">{cronStatus.message}</p>
)}
{/* Action buttons */}
<div className="flex flex-wrap gap-2 pt-2">
{cronStatus.available && (
<button
onClick={() => handleCronAction("restart")}
disabled={cronActionLoading === "restart"}
className="px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded hover:bg-blue-700 disabled:opacity-50"
>
{cronActionLoading === "restart" ? "Restarting..." : "🔄 Restart Cron Jobs"}
</button>
)}
<button
onClick={() => handleCronAction("run-backup")}
disabled={cronActionLoading === "run-backup"}
className="px-4 py-2 bg-green-600 text-white text-sm font-medium rounded hover:bg-green-700 disabled:opacity-50"
>
{cronActionLoading === "run-backup" ? "Running..." : "💾 Run Backup Now"}
</button>
<button
onClick={() => handleCronAction("run-reminders")}
disabled={cronActionLoading === "run-reminders"}
className="px-4 py-2 bg-purple-600 text-white text-sm font-medium rounded hover:bg-purple-700 disabled:opacity-50"
>
{cronActionLoading === "run-reminders" ? "Running..." : "📧 Send Reminders Now"}
</button>
</div>
</div>
) : (
<p className="text-sm text-red-500">Failed to load cron status</p>
)}
</div>
{/* System Information */}
<div className="border rounded-lg p-4">
<h3 className="text-lg font-medium text-gray-900 mb-2">
System Information