TaskProcessingControls.tsx 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. "use client";
  2. import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
  3. import { useEffect } from "react";
  4. import toast from "react-hot-toast";
  5. import { get, post } from "../../lib/api";
  6. import { useNotifications } from "./NotificationContext";
  7. export default function TaskProcessingControls() {
  8. const queryClient = useQueryClient();
  9. const { addNotification } = useNotifications();
  10. const { data } = useQuery({
  11. queryKey: ["tasks", "processing-status"],
  12. queryFn: () => get("/tasks/processing-status"),
  13. });
  14. const startMutation = useMutation({
  15. mutationFn: () => post("/tasks/start-processing"),
  16. onSuccess: () => {
  17. queryClient.invalidateQueries({
  18. queryKey: ["tasks", "processing-status"],
  19. });
  20. queryClient.invalidateQueries({ queryKey: ["tasks", "queue", "status"] });
  21. toast.success("Task processing started successfully");
  22. addNotification({
  23. type: "success",
  24. title: "Task Processing Started",
  25. message: "The task processing queue has been started successfully.",
  26. });
  27. },
  28. });
  29. const stopMutation = useMutation({
  30. mutationFn: () => post("/tasks/stop-processing", { graceful: true }),
  31. onSuccess: () => {
  32. queryClient.invalidateQueries({
  33. queryKey: ["tasks", "processing-status"],
  34. });
  35. queryClient.invalidateQueries({ queryKey: ["tasks", "queue", "status"] });
  36. toast.success("Graceful stop initiated – finishing active tasks");
  37. addNotification({
  38. type: "info",
  39. title: "Graceful Stop Requested",
  40. message: "The queue will finish active tasks before stopping.",
  41. });
  42. },
  43. });
  44. const hardStopMutation = useMutation({
  45. mutationFn: () => post("/tasks/stop-processing", { graceful: false }),
  46. onSuccess: () => {
  47. queryClient.invalidateQueries({
  48. queryKey: ["tasks", "processing-status"],
  49. });
  50. queryClient.invalidateQueries({ queryKey: ["tasks", "queue", "status"] });
  51. toast.success("Task processing stopped immediately");
  52. addNotification({
  53. type: "warning",
  54. title: "Immediate Stop",
  55. message: "Queue scheduling and active work halted immediately.",
  56. });
  57. },
  58. });
  59. // Listen for WebSocket events
  60. useEffect(() => {
  61. const handleTaskUpdate = (event: CustomEvent) => {
  62. const updateData = event.detail;
  63. if (
  64. updateData.type === "started" ||
  65. updateData.type === "stopped" ||
  66. updateData.type === "progress"
  67. ) {
  68. // Invalidate and refetch the task processing status
  69. queryClient.invalidateQueries({
  70. queryKey: ["tasks", "processing-status"],
  71. });
  72. queryClient.invalidateQueries({
  73. queryKey: ["tasks", "queue", "status"],
  74. });
  75. }
  76. };
  77. window.addEventListener("taskUpdate", handleTaskUpdate as EventListener);
  78. return () => {
  79. window.removeEventListener(
  80. "taskUpdate",
  81. handleTaskUpdate as EventListener
  82. );
  83. };
  84. }, [queryClient]);
  85. return (
  86. <div className="flex items-center gap-2">
  87. <div className="relative group">
  88. <button
  89. className="p-2 rounded-md text-gray-600 dark:text-gray-400 hover:bg-green-100 dark:hover:bg-green-900/30 hover:text-green-600 dark:hover:text-green-400 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
  90. onClick={() => startMutation.mutate()}
  91. disabled={data?.isProcessing || startMutation.isPending}
  92. title="Start task processing"
  93. >
  94. <svg
  95. className="h-5 w-5"
  96. fill="none"
  97. viewBox="0 0 24 24"
  98. stroke="currentColor"
  99. >
  100. <path
  101. strokeLinecap="round"
  102. strokeLinejoin="round"
  103. strokeWidth={2}
  104. d="M14.828 14.828a4 4 0 01-5.656 0M9 10h1.586a1 1 0 01.707.293l.707.707A1 1 0 0012.414 11H15m-3-3v3m0 0v3m0-3h3m-3 0H9"
  105. />
  106. </svg>
  107. </button>
  108. <div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none">
  109. {startMutation.isPending ? "Starting..." : "Start"}
  110. </div>
  111. </div>
  112. <div className="relative group">
  113. <button
  114. className="p-2 rounded-md text-gray-600 dark:text-gray-400 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
  115. onClick={() => stopMutation.mutate()}
  116. disabled={!data?.isProcessing || stopMutation.isPending}
  117. title="Graceful stop (finish active tasks)"
  118. >
  119. <svg
  120. className="h-5 w-5"
  121. fill="none"
  122. viewBox="0 0 24 24"
  123. stroke="currentColor"
  124. >
  125. <path
  126. strokeLinecap="round"
  127. strokeLinejoin="round"
  128. strokeWidth={2}
  129. d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
  130. />
  131. <path
  132. strokeLinecap="round"
  133. strokeLinejoin="round"
  134. strokeWidth={2}
  135. d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z"
  136. />
  137. </svg>
  138. </button>
  139. <div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none">
  140. {stopMutation.isPending ? "Stopping..." : "Graceful Stop"}
  141. </div>
  142. </div>
  143. <div className="relative group">
  144. <button
  145. className="p-2 rounded-md text-gray-600 dark:text-gray-400 hover:bg-orange-100 dark:hover:bg-orange-900/30 hover:text-orange-600 dark:hover:text-orange-400 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
  146. onClick={() => hardStopMutation.mutate()}
  147. disabled={!data?.isProcessing || hardStopMutation.isPending}
  148. title="Immediate stop (cancel scheduling)"
  149. >
  150. <svg
  151. className="h-5 w-5"
  152. fill="none"
  153. viewBox="0 0 24 24"
  154. stroke="currentColor"
  155. >
  156. <path
  157. strokeLinecap="round"
  158. strokeLinejoin="round"
  159. strokeWidth={2}
  160. d="M6 18L18 6M6 6l12 12"
  161. />
  162. </svg>
  163. </button>
  164. <div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none">
  165. {hardStopMutation.isPending ? "Stopping..." : "Hard Stop"}
  166. </div>
  167. </div>
  168. </div>
  169. );
  170. }