| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- "use client";
- import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
- import { useEffect } from "react";
- import toast from "react-hot-toast";
- import { get, post } from "../../lib/api";
- import LoadingCard from "./Loading";
- import { useNotifications } from "./NotificationContext";
- export default function WatcherStatus() {
- const queryClient = useQueryClient();
- const { addNotification } = useNotifications();
- const { data, isLoading, error } = useQuery({
- queryKey: ["watcher", "status"],
- queryFn: () => get("/watcher/status"),
- });
- const { data: _datasets, isLoading: _datasetsLoading } = useQuery({
- queryKey: ["datasets"],
- queryFn: () => get("/files"),
- });
- const { data: settings } = useQuery({
- queryKey: ["settings", "datasets"],
- queryFn: () => get("/config/settings/datasets"),
- });
- const startMutation = useMutation({
- mutationFn: () => post("/watcher/start"),
- onSuccess: () => {
- toast.success("Watcher started successfully");
- addNotification({
- type: "success",
- title: "Watcher Started",
- message: "The file watcher has been started successfully.",
- });
- // Invalidate and refetch to ensure status updates immediately
- queryClient.invalidateQueries({ queryKey: ["watcher", "status"] });
- setTimeout(() => {
- queryClient.refetchQueries({ queryKey: ["watcher", "status"] });
- }, 100);
- },
- });
- const stopMutation = useMutation({
- mutationFn: () => post("/watcher/stop"),
- onSuccess: () => {
- toast.success("Watcher stopped successfully");
- addNotification({
- type: "success",
- title: "Watcher Stopped",
- message: "The file watcher has been stopped successfully.",
- });
- // Invalidate and refetch to ensure status updates immediately
- queryClient.invalidateQueries({ queryKey: ["watcher", "status"] });
- setTimeout(() => {
- queryClient.refetchQueries({ queryKey: ["watcher", "status"] });
- }, 100);
- },
- });
- // Listen for WebSocket events
- useEffect(() => {
- const handleWatcherUpdate = (event: CustomEvent) => {
- const updateData = event.detail;
- if (updateData.type === "started" || updateData.type === "stopped") {
- // Invalidate and refetch the watcher status
- queryClient.invalidateQueries({ queryKey: ["watcher", "status"] });
- }
- };
- window.addEventListener(
- "watcherUpdate",
- handleWatcherUpdate as EventListener
- );
- return () => {
- window.removeEventListener(
- "watcherUpdate",
- handleWatcherUpdate as EventListener
- );
- };
- }, [queryClient]);
- if (isLoading) return <LoadingCard message="Loading watcher status..." />;
- if (error) {
- return (
- <div className="mb-6 p-4 border rounded bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800">
- <div className="text-center">
- <div className="mb-4">
- <svg
- className="mx-auto h-12 w-12 text-red-400"
- fill="none"
- viewBox="0 0 24 24"
- stroke="currentColor"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"
- />
- </svg>
- </div>
- <h3 className="font-semibold text-red-800 dark:text-red-200 mb-2">
- Failed to load watcher status
- </h3>
- <p className="text-sm text-red-600 dark:text-red-400 mb-4">
- Unable to connect to the file watcher service.
- </p>
- <button
- onClick={() =>
- queryClient.invalidateQueries({ queryKey: ["watcher", "status"] })
- }
- className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-red-700 dark:text-red-300 bg-red-100 dark:bg-red-900 hover:bg-red-200 dark:hover:bg-red-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
- >
- Retry
- </button>
- </div>
- </div>
- );
- }
- return (
- <div className="mb-6 p-4 border rounded bg-gray-50 dark:bg-gray-800">
- <div className="flex items-center justify-between mb-4">
- <h3 className="font-semibold">File Watcher</h3>
- <div className="flex gap-2">
- <button
- className="px-3 py-1 rounded bg-green-600 text-white disabled:opacity-50 text-sm"
- onClick={() => startMutation.mutate()}
- disabled={data.isWatching || startMutation.isPending}
- >
- {startMutation.isPending ? "Starting..." : "Start"}
- </button>
- <button
- className="px-3 py-1 rounded bg-red-600 text-white disabled:opacity-50 text-sm"
- onClick={() => stopMutation.mutate()}
- disabled={!data.isWatching || stopMutation.isPending}
- >
- {stopMutation.isPending ? "Stopping..." : "Stop"}
- </button>
- </div>
- </div>
- <div className="grid grid-cols-2 gap-4 mb-4 text-sm">
- <div>
- <span className="font-medium text-gray-700 dark:text-gray-300">
- Status:
- </span>
- <span
- className={`ml-2 ${data.isWatching ? "text-green-600" : "text-red-600"}`}
- >
- {data.isWatching ? "Watching" : "Idle"}
- </span>
- </div>
- <div>
- <span className="font-medium text-gray-700 dark:text-gray-300">
- Active Watches:
- </span>
- <span className="ml-2 text-gray-900 dark:text-gray-100">
- {settings
- ? Object.values(settings).filter(
- (dataset: any) => dataset.enabled !== false
- ).length
- : 0}
- </span>
- </div>
- </div>
- {settings && (
- <div className="mb-4">
- <span className="font-medium text-gray-700 dark:text-gray-300 text-sm">
- Configured Datasets ({Object.keys(settings).length}):
- </span>
- <div className="mt-1 flex flex-wrap gap-1">
- {Object.keys(settings).map((datasetName: string) => {
- const datasetConfig = settings[datasetName];
- const isEnabled = datasetConfig?.enabled !== false;
- return (
- <span
- key={datasetName}
- className={`text-xs px-2 py-1 rounded ${
- isEnabled
- ? "text-green-700 dark:text-green-300 bg-green-100 dark:bg-green-900"
- : "text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-700"
- }`}
- >
- {datasetName}
- {!isEnabled && " (disabled)"}
- </span>
- );
- })}
- </div>
- </div>
- )}
- {data.watches && data.watches.length > 0 && (
- <div className="mb-4">
- <span className="font-medium text-gray-700 dark:text-gray-300 text-sm">
- Watched Paths:
- </span>
- <div className="mt-1 max-h-20 overflow-y-auto">
- {data.watches.map((watch: any, index: number) => (
- <div
- key={index}
- className="text-xs text-gray-600 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded mt-1"
- >
- {watch.path || watch}
- </div>
- ))}
- </div>
- </div>
- )}
- </div>
- );
- }
|