|
@@ -1,10 +1,14 @@
|
|
|
"use client";
|
|
"use client";
|
|
|
-import { useQuery } from "@tanstack/react-query";
|
|
|
|
|
|
|
+import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
|
import { useState } from "react";
|
|
import { useState } from "react";
|
|
|
-import { get } from "../../lib/api";
|
|
|
|
|
|
|
+import { get, post } from "../../lib/api";
|
|
|
|
|
+import { ArrowPathIcon } from "@heroicons/react/24/outline";
|
|
|
|
|
+import toast from "react-hot-toast";
|
|
|
|
|
|
|
|
export default function ApiHealth() {
|
|
export default function ApiHealth() {
|
|
|
const [responseTime, setResponseTime] = useState<number | null>(null);
|
|
const [responseTime, setResponseTime] = useState<number | null>(null);
|
|
|
|
|
+ const [showRestartConfirm, setShowRestartConfirm] = useState(false);
|
|
|
|
|
+ const queryClient = useQueryClient();
|
|
|
|
|
|
|
|
const { data, isLoading, error } = useQuery({
|
|
const { data, isLoading, error } = useQuery({
|
|
|
queryKey: ["api", "health"],
|
|
queryKey: ["api", "health"],
|
|
@@ -18,6 +22,21 @@ export default function ApiHealth() {
|
|
|
refetchInterval: 30000 // Check every 30 seconds
|
|
refetchInterval: 30000 // Check every 30 seconds
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+ const restartMutation = useMutation({
|
|
|
|
|
+ mutationFn: () => post("/restart", {}),
|
|
|
|
|
+ onSuccess: () => {
|
|
|
|
|
+ toast.success("API service restarting...");
|
|
|
|
|
+ setShowRestartConfirm(false);
|
|
|
|
|
+ // Retry health check after a delay to see if service is back
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ queryClient.invalidateQueries({ queryKey: ["api", "health"] });
|
|
|
|
|
+ }, 3000);
|
|
|
|
|
+ },
|
|
|
|
|
+ onError: (error: any) => {
|
|
|
|
|
+ toast.error("Failed to restart API service");
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
const isHealthy = data?.status === "healthy";
|
|
const isHealthy = data?.status === "healthy";
|
|
|
const lastChecked = data?.datetime
|
|
const lastChecked = data?.datetime
|
|
|
? new Date(data.datetime).toLocaleTimeString()
|
|
? new Date(data.datetime).toLocaleTimeString()
|
|
@@ -41,6 +60,18 @@ export default function ApiHealth() {
|
|
|
{isHealthy && !error ? "Healthy" : "Unhealthy"}
|
|
{isHealthy && !error ? "Healthy" : "Unhealthy"}
|
|
|
</span>
|
|
</span>
|
|
|
)}
|
|
)}
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={() => setShowRestartConfirm(true)}
|
|
|
|
|
+ disabled={restartMutation.isPending}
|
|
|
|
|
+ className="ml-2 p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
|
|
|
+ title="Restart API service"
|
|
|
|
|
+ >
|
|
|
|
|
+ <ArrowPathIcon
|
|
|
|
|
+ className={`w-4 h-4 ${
|
|
|
|
|
+ restartMutation.isPending ? "animate-spin" : ""
|
|
|
|
|
+ }`}
|
|
|
|
|
+ />
|
|
|
|
|
+ </button>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -90,6 +121,37 @@ export default function ApiHealth() {
|
|
|
</div>
|
|
</div>
|
|
|
)}
|
|
)}
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Restart Confirmation Dialog */}
|
|
|
|
|
+ {showRestartConfirm && (
|
|
|
|
|
+ <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
|
|
|
|
+ <div className="bg-white dark:bg-gray-900 rounded-lg shadow-lg p-6 max-w-sm mx-4">
|
|
|
|
|
+ <h2 className="text-lg font-semibold mb-2">Restart API Service?</h2>
|
|
|
|
|
+ <p className="text-gray-600 dark:text-gray-400 text-sm mb-6">
|
|
|
|
|
+ This will gracefully restart the API service. The service will be temporarily unavailable.
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <div className="flex justify-end gap-3">
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={() => setShowRestartConfirm(false)}
|
|
|
|
|
+ disabled={restartMutation.isPending}
|
|
|
|
|
+ className="px-4 py-2 rounded border border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-800 disabled:opacity-50 transition-colors"
|
|
|
|
|
+ >
|
|
|
|
|
+ Cancel
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={() => restartMutation.mutate()}
|
|
|
|
|
+ disabled={restartMutation.isPending}
|
|
|
|
|
+ className="px-4 py-2 rounded bg-red-600 hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-800 text-white disabled:opacity-50 transition-colors flex items-center gap-2"
|
|
|
|
|
+ >
|
|
|
|
|
+ {restartMutation.isPending && (
|
|
|
|
|
+ <ArrowPathIcon className="w-4 h-4 animate-spin" />
|
|
|
|
|
+ )}
|
|
|
|
|
+ Restart
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
</div>
|
|
</div>
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|