"use client";
import { useState, useEffect, useCallback } from "react";
import { Card, Button, Badge, Input, ModelSelectModal } from "@/shared/components";
import { TOOL_HOSTS } from "@/shared/constants/mitmToolHosts";
import Image from "next/image";
/**
* Per-tool MITM card — shows DNS status + model mappings.
* - Auto-saves model mapping on blur or modal select
* - Skips sudo modal if password is already cached
* - Model mappings can only be edited when DNS is active
*/
export default function MitmToolCard({
tool,
isExpanded,
onToggle,
serverRunning,
dnsActive,
hasCachedPassword,
needsSudoPassword,
isWin,
apiKeys,
activeProviders,
hasActiveProviders,
modelAliases = {},
cloudEnabled,
onDnsChange,
}) {
const [loading, setLoading] = useState(false);
const [warning, setWarning] = useState(null);
const [showPasswordModal, setShowPasswordModal] = useState(false);
const [sudoPassword, setSudoPassword] = useState("");
const [pendingDnsAction, setPendingDnsAction] = useState(null);
const [modalError, setModalError] = useState(null);
const [modelMappings, setModelMappings] = useState({});
const [modalOpen, setModalOpen] = useState(false);
const [currentEditingAlias, setCurrentEditingAlias] = useState(null);
const mitmHosts = TOOL_HOSTS[tool.id] ?? [];
const canRunWithoutPassword = isWin || hasCachedPassword || needsSudoPassword === false;
useEffect(() => {
if (isExpanded) loadSavedMappings();
}, [isExpanded]);
const loadSavedMappings = async () => {
try {
const res = await fetch(`/api/cli-tools/antigravity-mitm/alias?tool=${tool.id}`);
if (res.ok) {
const data = await res.json();
if (Object.keys(data.aliases || {}).length > 0) setModelMappings(data.aliases);
}
} catch { /* ignore */ }
};
const saveMappings = useCallback(async (mappings) => {
try {
await fetch("/api/cli-tools/antigravity-mitm/alias", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ tool: tool.id, mappings }),
});
} catch { /* ignore */ }
}, [tool.id]);
const handleMappingBlur = (alias, value) => {
saveMappings({ ...modelMappings, [alias]: value });
};
const handleModelMappingChange = (alias, value) => {
setModelMappings(prev => ({ ...prev, [alias]: value }));
};
const openModelSelector = (alias) => {
setCurrentEditingAlias(alias);
setModalOpen(true);
};
const handleModelSelect = (model) => {
if (!currentEditingAlias || model.isPlaceholder) return;
const updated = { ...modelMappings, [currentEditingAlias]: model.value };
setModelMappings(updated);
saveMappings(updated);
};
const handleDnsToggle = () => {
if (!serverRunning) return;
const action = dnsActive ? "disable" : "enable";
if (canRunWithoutPassword) {
doDnsAction(action, "");
} else {
setPendingDnsAction(action);
setShowPasswordModal(true);
setModalError(null);
}
};
const doDnsAction = async (action, password) => {
setLoading(true);
setWarning(null);
try {
const res = await fetch("/api/cli-tools/antigravity-mitm", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ tool: tool.id, action, sudoPassword: password }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || "Failed to toggle DNS");
if (action === "enable") {
setWarning(`Restart ${tool.name} to apply changes`);
}
setShowPasswordModal(false);
setSudoPassword("");
onDnsChange?.(data);
} catch { /* ignore */ } finally {
setLoading(false);
setPendingDnsAction(null);
}
};
const handleConfirmPassword = () => {
if (!sudoPassword.trim()) {
setModalError("Sudo password is required");
return;
}
doDnsAction(pendingDnsAction, sudoPassword);
};
return (
<>
Intercept {tool.name} requests via MITM proxy
Edit hosts file manually to add the following entries:
Toggle DNS to redirect {tool.name} traffic through 9Router via MITM.
⚠️ Enable DNS to edit model mappings
Model mappings will be available soon.{tool.name}
{!serverRunning ? (
{mitmHosts.map((h) => (
Required to modify /etc/hosts and flush DNS cache