ui: added indicator to show blocking status along with a modal rework

This commit is contained in:
pommee
2025-12-22 03:23:22 +01:00
parent 1471ff1d23
commit 2d23669dd9
10 changed files with 124 additions and 40 deletions

View File

@@ -141,6 +141,7 @@ func (s *Service) SendTest(ctx context.Context, alertType, name, webhook string)
})
if err != nil {
log.Error("Failed to send test alert via %s: %v", service.GetServiceName(), err)
return err
}
break
}

View File

@@ -0,0 +1,51 @@
import { GetRequest } from "@/util";
import { useEffect, useState } from "react";
import { toast } from "sonner";
export default function BlockingTimer() {
const [timeLeft, setTimeLeft] = useState(0);
useEffect(() => {
async function fetchNotifications() {
try {
const [code, response] = await GetRequest("pause");
if (code !== 200) {
toast.warning("Unable to fetch blocking status", {
id: "fetch-notifications-error"
});
return;
}
setTimeLeft(response.timeLeft || 0);
} catch {
toast.error("Error while fetching notifications");
}
}
fetchNotifications();
const intervalId = setInterval(() => {
fetchNotifications();
}, 1000);
return () => clearInterval(intervalId);
}, []);
return (
<div className="w-max">
{timeLeft === 0 ? (
<div className="text-xs font-medium text-green-500/80">
Blocking active
</div>
) : (
<div className="text-xs font-medium text-red-500/80">
Blocking paused:{" "}
{Math.floor(timeLeft / 60)
.toString()
.padStart(2, "0")}
:{(timeLeft % 60).toString().padStart(2, "0")}
</div>
)}
</div>
);
}

View File

@@ -6,7 +6,6 @@ import {
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle
} from "@/components/ui/dialog";
@@ -38,8 +37,9 @@ import {
import { compare } from "compare-versions";
import { JSX, useEffect, useState } from "react";
import { toast } from "sonner";
import { Metrics } from "./server-statistics";
import { Input } from "./ui/input";
import { Metrics } from "../server-statistics";
import { Input } from "../ui/input";
import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group";
const data = [
[
@@ -290,28 +290,74 @@ export default function PauseBlockingDialog({
}
};
const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins}:${secs.toString().padStart(2, "0")}`;
};
return (
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<ClockIcon className="text-blue-400" />
<ClockIcon className="h-5 w-5 text-primary" />
{pauseStatus?.paused ? "Blocking Paused" : "Pause Blocking"}
</DialogTitle>
<DialogDescription className="text-sm text-gray-500">
<DialogDescription>
{pauseStatus?.paused
? `Blocking is currently paused. Remaining time: ${remainingTime} seconds.`
: "This will temporarily pause domain blocking, allowing all traffic to pass through."}
? "Blocking is currently paused"
: "Temporarily allow all traffic through"}
</DialogDescription>
</DialogHeader>
{!pauseStatus?.paused ? (
<>
<div className="py-4">
<label
htmlFor="pause-time"
className="block text-sm font-medium mb-2"
{pauseStatus?.paused ? (
<div className="py-6 space-y-4">
<div className="flex flex-col items-center space-y-3">
<div className="text-4xl font-bold tabular-nums">
{formatTime(remainingTime)}
</div>
<p className="text-sm text-muted-foreground">remaining</p>
</div>
<Button
onClick={handleRemovePause}
disabled={isLoading}
className="w-full bg-primary/80 hover:bg-primary"
>
<PlayCircleIcon size={18} className="mr-2" />
{isLoading ? "Resuming..." : "Resume Now"}
</Button>
</div>
) : (
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium">Quick Select</label>
<ToggleGroup
type="single"
variant="outline"
value={String(pauseTime)}
>
Duration (seconds)
<ToggleGroupItem value="10" onClick={() => setPauseTime(10)}>
10s
</ToggleGroupItem>
<ToggleGroupItem value="30" onClick={() => setPauseTime(30)}>
30s
</ToggleGroupItem>
<ToggleGroupItem value="60" onClick={() => setPauseTime(60)}>
1m
</ToggleGroupItem>
<ToggleGroupItem value="300" onClick={() => setPauseTime(300)}>
5m
</ToggleGroupItem>
<ToggleGroupItem value="600" onClick={() => setPauseTime(600)}>
10m
</ToggleGroupItem>
</ToggleGroup>
</div>
<div className="space-y-2">
<label htmlFor="pause-time" className="text-sm font-medium">
Custom (seconds)
</label>
<Input
id="pause-time"
@@ -319,38 +365,22 @@ export default function PauseBlockingDialog({
min={1}
value={pauseTime}
onChange={(e) => setPauseTime(e.target.valueAsNumber)}
className="w-full"
/>
</div>
<DialogFooter className="flex justify-end gap-2">
<Button
variant="outline"
className="border-gray-300"
onClick={onClose}
>
<div className="flex gap-2 pt-2">
<Button variant="outline" onClick={onClose} className="flex-1">
Cancel
</Button>
<Button
onClick={handlePause}
disabled={isLoading}
className="bg-blue-500 hover:bg-blue-600 text-white"
className="flex-1 bg-primary/80 hover:bg-primary"
>
{isLoading ? "Pausing..." : "Pause Blocking"}
{isLoading ? "Pausing..." : "Pause"}
</Button>
</DialogFooter>
</>
) : (
<DialogFooter className="flex justify-center mt-4">
<Button
onClick={handleRemovePause}
disabled={isLoading}
className="bg-green-500 hover:bg-green-600 text-white flex items-center gap-2"
>
<PlayCircleIcon size={18} />
{isLoading ? "Resuming..." : "Resume Blocking Now"}
</Button>
</DialogFooter>
</div>
</div>
)}
</DialogContent>
);

View File

@@ -1,9 +1,10 @@
import { Separator } from "@/components/ui/separator";
import { SidebarTrigger } from "@/components/ui/sidebar";
import { useLocation } from "react-router-dom";
import { NavActions } from "./nav-actions";
import Notifications from "./notifications";
import { ModeToggle } from "@/app/theme/toggle-theme";
import { NavActions } from "./header/nav-actions";
import Notifications from "./header/notifications";
import { ModeToggle } from "@/components/header/theme/toggle-theme";
import BlockingTimer from "./header/BlockingTimer";
interface PageInfo {
title: string;
@@ -96,6 +97,7 @@ export function SiteHeader() {
</div>
<div className="flex items-center gap-2 px-4 lg:px-6">
<BlockingTimer />
<ModeToggle />
<Notifications />
<NavActions />

View File

@@ -15,7 +15,7 @@ import { Whitelist } from "./whitelist";
import { GenerateQuote } from "@/quotes";
import Login from "./login";
import { FileXIcon } from "@phosphor-icons/react";
import { ThemeProvider } from "@/app/theme/theme-provider";
import { ThemeProvider } from "@/components/header/theme/theme-provider";
function NotFound() {
return (