feat(ops): health-fail alerts, optional DB backup, admin UX polish

Wire jobs-worker health probes to Discord/email alerts with cooldown, optional mysqldump, rate-limit /api/health, mark-all-read alerts, ConfirmDialog on destructive admin actions, and raise coverage floors.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-08-01 15:25:47 +02:00
1 parent 3bd712e744
commit 725e1cb338
16 files changed
+327 -32

No files matched your search

+1
View File
@@ -0,0 +1 @@
export { default } from "../loading";
+11 -2
View File
@@ -1,7 +1,7 @@
import { desc } from "drizzle-orm";
import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server";
import { sendHotelAlert } from "@/actions/admin-alerts";
import { markAllAlertsRead, sendHotelAlert } from "@/actions/admin-alerts";
import { StatusCard } from "@/components/admin/dashboard";
import { Button } from "@/components/ui/button";
import { AlertLogs, db } from "@/lib/db";
@@ -125,7 +125,16 @@ export default async function AdminAlerts() {
</form>
<section className="admin-card">
<h3 className="mt-0">{t("recentAlerts", { count: alerts.length })}</h3>
<div className="mb-3 flex flex-wrap items-center justify-between gap-2">
<h3 className="m-0">{t("recentAlerts", { count: alerts.length })}</h3>
{unread > 0 ? (
<form action={markAllAlertsRead}>
<Button type="submit" variant="outline" size="sm">
{t("markAllRead")}
</Button>
</form>
) : null}
</div>
{alerts.length === 0 ? (
<div className="admin-empty">{t("noAlerts")}</div>
) : (
@@ -0,0 +1 @@
export { default } from "../loading";
+1
View File
@@ -0,0 +1 @@
export { default } from "../loading";
@@ -19,6 +19,7 @@ import {
sendCredits,
unmuteUser,
} from "@/actions/users";
import { useConfirmDialog } from "@/components/admin/confirm-dialog";
import { CurrencyIcon } from "@/components/shared/currency-icon";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -46,6 +47,7 @@ export function UserQuickActions({
isOnline,
}: UserQuickActionsProps) {
const { run, isPending } = useServerAction();
const { confirm, dialog: confirmDialog } = useConfirmDialog();
// Reset password state
const [newPassword, setNewPassword] = useState<string | null>(null);
@@ -60,13 +62,14 @@ export function UserQuickActions({
// Credits state
const [creditAmount, setCreditAmount] = useState("1000");
function handleResetPassword() {
if (
!confirm(
`Reset password for ${username}? A new random password will be generated.`,
)
)
return;
async function handleResetPassword() {
const ok = await confirm({
title: "Reset password",
description: `Reset password for ${username}? A new random password will be generated.`,
confirmLabel: "Reset",
variant: "danger",
});
if (!ok) return;
run(() => resetPassword({ userId }), {
successMessage: "Password reset successfully.",
onSuccess: (data) => {
@@ -121,6 +124,7 @@ export function UserQuickActions({
return (
<>
{confirmDialog}
<Card>
<CardHeader>
<CardTitle>Quick Actions</CardTitle>