chore: remove unused tool-types and tool-configs pages
These pages are superseded by the Tool Workshop page. No functional changes.
This commit is contained in:
@@ -1,354 +0,0 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
import { Icon } from "../components/icon";
|
|
||||||
import { listToolTypes, type ToolType } from "../api/tool_types";
|
|
||||||
import {
|
|
||||||
createToolConfig,
|
|
||||||
deleteToolConfig,
|
|
||||||
listToolConfigs,
|
|
||||||
updateToolConfig,
|
|
||||||
type ToolConfig,
|
|
||||||
} from "../api/tool_configs";
|
|
||||||
|
|
||||||
type ConfigStatus = "loading" | "ready" | "error";
|
|
||||||
|
|
||||||
export const ToolConfigsPage = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [status, setStatus] = useState<ConfigStatus>("loading");
|
|
||||||
const [toolTypes, setToolTypes] = useState<ToolType[]>([]);
|
|
||||||
const [configs, setConfigs] = useState<ToolConfig[]>([]);
|
|
||||||
const [selectedToolType, setSelectedToolType] = useState<string>("");
|
|
||||||
const [showForm, setShowForm] = useState(false);
|
|
||||||
const [editingConfig, setEditingConfig] = useState<ToolConfig | null>(null);
|
|
||||||
const [formData, setFormData] = useState({
|
|
||||||
key: "",
|
|
||||||
value: "",
|
|
||||||
config_type: "env",
|
|
||||||
file_path: "",
|
|
||||||
});
|
|
||||||
const [saveStatus, setSaveStatus] = useState<"idle" | "saving" | "saved" | "error">("idle");
|
|
||||||
|
|
||||||
const loadData = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const [typesData, configsData] = await Promise.all([
|
|
||||||
listToolTypes(),
|
|
||||||
listToolConfigs(),
|
|
||||||
]);
|
|
||||||
setToolTypes(typesData);
|
|
||||||
setConfigs(configsData);
|
|
||||||
if (typesData.length > 0 && !selectedToolType) {
|
|
||||||
setSelectedToolType(typesData[0].id);
|
|
||||||
}
|
|
||||||
setStatus("ready");
|
|
||||||
} catch {
|
|
||||||
setStatus("error");
|
|
||||||
}
|
|
||||||
}, [selectedToolType]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
void loadData();
|
|
||||||
}, [loadData]);
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setSaveStatus("saving");
|
|
||||||
try {
|
|
||||||
const data = {
|
|
||||||
tool_type_id: selectedToolType,
|
|
||||||
key: formData.key,
|
|
||||||
value: formData.value,
|
|
||||||
config_type: formData.config_type,
|
|
||||||
file_path: formData.config_type === "file" ? formData.file_path : undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (editingConfig) {
|
|
||||||
await updateToolConfig(editingConfig.id, data);
|
|
||||||
} else {
|
|
||||||
await createToolConfig(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
setSaveStatus("saved");
|
|
||||||
setShowForm(false);
|
|
||||||
setEditingConfig(null);
|
|
||||||
setFormData({ key: "", value: "", config_type: "env", file_path: "" });
|
|
||||||
await loadData();
|
|
||||||
} catch {
|
|
||||||
setSaveStatus("error");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEdit = (config: ToolConfig) => {
|
|
||||||
setEditingConfig(config);
|
|
||||||
setFormData({
|
|
||||||
key: config.key,
|
|
||||||
value: config.value,
|
|
||||||
config_type: config.config_type,
|
|
||||||
file_path: config.file_path || "",
|
|
||||||
});
|
|
||||||
setSelectedToolType(config.tool_type_id);
|
|
||||||
setShowForm(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelete = async (id: string) => {
|
|
||||||
if (!window.confirm("Delete this config?")) return;
|
|
||||||
try {
|
|
||||||
await deleteToolConfig(id);
|
|
||||||
await loadData();
|
|
||||||
} catch {
|
|
||||||
// Error handled by UI state
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const filteredConfigs = configs.filter(
|
|
||||||
(c) => c.tool_type_id === selectedToolType
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectedTool = toolTypes.find((t) => t.id === selectedToolType);
|
|
||||||
|
|
||||||
if (status === "loading") {
|
|
||||||
return (
|
|
||||||
<section className="stack">
|
|
||||||
<div className="page-header">
|
|
||||||
<h1>Tool Configurations</h1>
|
|
||||||
</div>
|
|
||||||
<p className="muted">Loading...</p>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status === "error") {
|
|
||||||
return (
|
|
||||||
<section className="stack">
|
|
||||||
<div className="page-header">
|
|
||||||
<h1>Tool Configurations</h1>
|
|
||||||
</div>
|
|
||||||
<div className="card stack">
|
|
||||||
<p>Failed to load configurations</p>
|
|
||||||
<button className="secondary-button" onClick={() => void loadData()} type="button">
|
|
||||||
<Icon name="refresh" size="sm" />
|
|
||||||
Retry
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="stack">
|
|
||||||
<div className="page-header">
|
|
||||||
<div>
|
|
||||||
<p className="eyebrow">Settings</p>
|
|
||||||
<h1>Tool Configurations</h1>
|
|
||||||
</div>
|
|
||||||
<button className="secondary-button" type="button" onClick={() => navigate("/settings")}>
|
|
||||||
Back to settings
|
|
||||||
</button>
|
|
||||||
<p className="muted">
|
|
||||||
Manage environment variables and configuration files for your tools
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Tool Type Selector */}
|
|
||||||
<div className="card">
|
|
||||||
<label htmlFor="tool-type-select">Select Tool</label>
|
|
||||||
<select
|
|
||||||
id="tool-type-select"
|
|
||||||
value={selectedToolType}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSelectedToolType(e.target.value);
|
|
||||||
setShowForm(false);
|
|
||||||
setEditingConfig(null);
|
|
||||||
}}
|
|
||||||
className="form-input"
|
|
||||||
>
|
|
||||||
{toolTypes.map((tool) => (
|
|
||||||
<option key={tool.id} value={tool.id}>
|
|
||||||
{tool.display_name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{selectedTool && (
|
|
||||||
<p className="muted" style={{ marginTop: "0.5rem" }}>
|
|
||||||
Category: {selectedTool.category} · Interface: {selectedTool.interface_type}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Config List */}
|
|
||||||
<div className="card stack">
|
|
||||||
<div className="row" style={{ justifyContent: "space-between", alignItems: "center" }}>
|
|
||||||
<h2>Configuration Variables</h2>
|
|
||||||
<button
|
|
||||||
className="primary-button small"
|
|
||||||
onClick={() => {
|
|
||||||
setShowForm(true);
|
|
||||||
setEditingConfig(null);
|
|
||||||
setFormData({ key: "", value: "", config_type: "env", file_path: "" });
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<Icon name="add" size="sm" />
|
|
||||||
Add Config
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{filteredConfigs.length === 0 ? (
|
|
||||||
<p className="muted">No configurations for this tool yet.</p>
|
|
||||||
) : (
|
|
||||||
<div className="stack" style={{ gap: "0.5rem" }}>
|
|
||||||
{filteredConfigs.map((config) => (
|
|
||||||
<div
|
|
||||||
key={config.id}
|
|
||||||
className="card"
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
padding: "0.75rem 1rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div className="row" style={{ gap: "0.5rem", alignItems: "center" }}>
|
|
||||||
<code style={{ fontWeight: 600 }}>{config.key}</code>
|
|
||||||
<span
|
|
||||||
className="badge"
|
|
||||||
style={{
|
|
||||||
fontSize: "0.7rem",
|
|
||||||
textTransform: "uppercase",
|
|
||||||
background: config.config_type === "env" ? "var(--color-info)" : "var(--color-warning)",
|
|
||||||
color: "white",
|
|
||||||
padding: "0.125rem 0.5rem",
|
|
||||||
borderRadius: "9999px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{config.config_type}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p className="muted" style={{ marginTop: "0.25rem", fontSize: "0.875rem" }}>
|
|
||||||
{config.config_type === "file" && config.file_path
|
|
||||||
? `File: ${config.file_path}`
|
|
||||||
: "Environment variable"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="row" style={{ gap: "0.5rem" }}>
|
|
||||||
<button
|
|
||||||
className="ghost-button small"
|
|
||||||
onClick={() => handleEdit(config)}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<Icon name="edit" size="sm" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="ghost-button small"
|
|
||||||
onClick={() => void handleDelete(config.id)}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<Icon name="delete" size="sm" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Add/Edit Form */}
|
|
||||||
{showForm && (
|
|
||||||
<div className="card stack">
|
|
||||||
<h3>{editingConfig ? "Edit Config" : "Add Config"}</h3>
|
|
||||||
<form onSubmit={handleSubmit} className="stack">
|
|
||||||
<div>
|
|
||||||
<label htmlFor="config-key">Key</label>
|
|
||||||
<input
|
|
||||||
id="config-key"
|
|
||||||
type="text"
|
|
||||||
value={formData.key}
|
|
||||||
onChange={(e) => setFormData({ ...formData, key: e.target.value })}
|
|
||||||
placeholder="e.g., OPENAI_API_KEY"
|
|
||||||
className="form-input"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label htmlFor="config-type">Type</label>
|
|
||||||
<select
|
|
||||||
id="config-type"
|
|
||||||
value={formData.config_type}
|
|
||||||
onChange={(e) =>
|
|
||||||
setFormData({ ...formData, config_type: e.target.value })
|
|
||||||
}
|
|
||||||
className="form-input"
|
|
||||||
>
|
|
||||||
<option value="env">Environment Variable</option>
|
|
||||||
<option value="file">Configuration File</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{formData.config_type === "file" && (
|
|
||||||
<div>
|
|
||||||
<label htmlFor="config-file-path">File Path</label>
|
|
||||||
<input
|
|
||||||
id="config-file-path"
|
|
||||||
type="text"
|
|
||||||
value={formData.file_path}
|
|
||||||
onChange={(e) =>
|
|
||||||
setFormData({ ...formData, file_path: e.target.value })
|
|
||||||
}
|
|
||||||
placeholder="e.g., /app/config.json"
|
|
||||||
className="form-input"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label htmlFor="config-value">Value</label>
|
|
||||||
<textarea
|
|
||||||
id="config-value"
|
|
||||||
value={formData.value}
|
|
||||||
onChange={(e) => setFormData({ ...formData, value: e.target.value })}
|
|
||||||
placeholder={
|
|
||||||
formData.config_type === "env"
|
|
||||||
? "Enter value..."
|
|
||||||
: "Enter file contents..."
|
|
||||||
}
|
|
||||||
className="form-input"
|
|
||||||
rows={formData.config_type === "file" ? 8 : 2}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="row" style={{ gap: "0.5rem", justifyContent: "flex-end" }}>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="secondary-button"
|
|
||||||
onClick={() => {
|
|
||||||
setShowForm(false);
|
|
||||||
setEditingConfig(null);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<button type="submit" className="primary-button">
|
|
||||||
{editingConfig ? "Update" : "Add"} Config
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{saveStatus === "saved" && (
|
|
||||||
<p className="text-success" style={{ textAlign: "right" }}>
|
|
||||||
Saved successfully!
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{saveStatus === "error" && (
|
|
||||||
<p className="text-error" style={{ textAlign: "right" }}>
|
|
||||||
Failed to save. Please try again.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,383 +0,0 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
import {
|
|
||||||
createToolType,
|
|
||||||
deleteToolType,
|
|
||||||
listToolTypes,
|
|
||||||
updateToolType,
|
|
||||||
type CreateToolTypeRequest,
|
|
||||||
type UpdateToolTypeRequest,
|
|
||||||
} from "../api/tool_types";
|
|
||||||
import { Icon } from "../components/icon";
|
|
||||||
import type { ToolType } from "../api/tool_types";
|
|
||||||
|
|
||||||
type ToolTypesStatus = "loading" | "ready" | "error";
|
|
||||||
type DialogMode = "none" | "create" | "edit";
|
|
||||||
|
|
||||||
export const ToolTypesPage = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [status, setStatus] = useState<ToolTypesStatus>("loading");
|
|
||||||
const [toolTypes, setToolTypes] = useState<ToolType[]>([]);
|
|
||||||
const [dialogMode, setDialogMode] = useState<DialogMode>("none");
|
|
||||||
const [editingToolType, setEditingToolType] = useState<ToolType | null>(null);
|
|
||||||
const [formName, setFormName] = useState("");
|
|
||||||
const [formDisplayName, setFormDisplayName] = useState("");
|
|
||||||
const [formDescription, setFormDescription] = useState("");
|
|
||||||
const [formCategory, setFormCategory] = useState("");
|
|
||||||
const [formInterfaces, setFormInterfaces] = useState<string[]>([]);
|
|
||||||
const [formPort, setFormPort] = useState("");
|
|
||||||
const [formTemplate, setFormTemplate] = useState("");
|
|
||||||
const [formVariables, setFormVariables] = useState("");
|
|
||||||
const [formError, setFormError] = useState<string | null>(null);
|
|
||||||
const [deleteConfirmId, setDeleteConfirmId] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const loadToolTypes = useCallback(async () => {
|
|
||||||
setStatus("loading");
|
|
||||||
try {
|
|
||||||
const data = await listToolTypes();
|
|
||||||
setToolTypes(data);
|
|
||||||
setStatus("ready");
|
|
||||||
} catch {
|
|
||||||
setToolTypes([]);
|
|
||||||
setStatus("error");
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
void loadToolTypes();
|
|
||||||
}, [loadToolTypes]);
|
|
||||||
|
|
||||||
const openCreate = () => {
|
|
||||||
setFormName("");
|
|
||||||
setFormDisplayName("");
|
|
||||||
setFormDescription("");
|
|
||||||
setFormCategory("");
|
|
||||||
setFormInterfaces([]);
|
|
||||||
setFormPort("");
|
|
||||||
setFormTemplate("");
|
|
||||||
setFormVariables("");
|
|
||||||
setFormError(null);
|
|
||||||
setEditingToolType(null);
|
|
||||||
setDialogMode("create");
|
|
||||||
};
|
|
||||||
|
|
||||||
const openEdit = (toolType: ToolType) => {
|
|
||||||
setFormName(toolType.name);
|
|
||||||
setFormDisplayName(toolType.display_name);
|
|
||||||
setFormDescription(toolType.description ?? "");
|
|
||||||
setFormCategory(toolType.category ?? "");
|
|
||||||
setFormInterfaces(toolType.interface_type ? [toolType.interface_type] : []);
|
|
||||||
setFormPort(toolType.default_port?.toString() ?? "");
|
|
||||||
setFormTemplate(toolType.compose_template ?? "");
|
|
||||||
setFormVariables(toolType.required_variables.join(", "));
|
|
||||||
setFormError(null);
|
|
||||||
setEditingToolType(toolType);
|
|
||||||
setDialogMode("edit");
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeDialog = () => {
|
|
||||||
setDialogMode("none");
|
|
||||||
setEditingToolType(null);
|
|
||||||
setFormError(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setFormError(null);
|
|
||||||
|
|
||||||
if (!formName.trim() || !formDisplayName.trim() || !formTemplate.trim()) {
|
|
||||||
setFormError("Name, display name, and compose template are required");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!formPort.trim() || isNaN(Number(formPort))) {
|
|
||||||
setFormError("Default port is required and must be a number");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const variables = formVariables
|
|
||||||
.split(",")
|
|
||||||
.map((v) => v.trim())
|
|
||||||
.filter((v) => v.length > 0);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (dialogMode === "create") {
|
|
||||||
const input: CreateToolTypeRequest = {
|
|
||||||
name: formName.trim(),
|
|
||||||
display_name: formDisplayName.trim(),
|
|
||||||
description: formDescription.trim() || undefined,
|
|
||||||
category: formCategory.trim() || undefined,
|
|
||||||
interface_type: formInterfaces.length > 0 ? formInterfaces[0] : "web",
|
|
||||||
requires_port: formInterfaces.includes("web"),
|
|
||||||
default_port: Number(formPort),
|
|
||||||
definition_type: "compose",
|
|
||||||
compose_template: formTemplate.trim(),
|
|
||||||
required_variables: variables,
|
|
||||||
};
|
|
||||||
await createToolType(input);
|
|
||||||
} else if (dialogMode === "edit" && editingToolType) {
|
|
||||||
const input: UpdateToolTypeRequest = {
|
|
||||||
display_name: formDisplayName.trim(),
|
|
||||||
description: formDescription.trim() || undefined,
|
|
||||||
category: formCategory.trim() || undefined,
|
|
||||||
interface_type: formInterfaces.length > 0 ? formInterfaces[0] : "web",
|
|
||||||
requires_port: formInterfaces.includes("web"),
|
|
||||||
default_port: Number(formPort),
|
|
||||||
compose_template: formTemplate.trim(),
|
|
||||||
required_variables: variables,
|
|
||||||
};
|
|
||||||
await updateToolType(editingToolType.id, input);
|
|
||||||
}
|
|
||||||
closeDialog();
|
|
||||||
await loadToolTypes();
|
|
||||||
} catch (err) {
|
|
||||||
const axiosError = err as { response?: { data?: { detail?: string } } };
|
|
||||||
const detail = axiosError?.response?.data?.detail || "Failed to save tool type";
|
|
||||||
setFormError(detail);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelete = async (id: string) => {
|
|
||||||
try {
|
|
||||||
await deleteToolType(id);
|
|
||||||
setDeleteConfirmId(null);
|
|
||||||
await loadToolTypes();
|
|
||||||
} catch {
|
|
||||||
alert("Failed to delete tool type");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (status === "loading") {
|
|
||||||
return (
|
|
||||||
<div className="container">
|
|
||||||
<p>Loading tool types...</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status === "error") {
|
|
||||||
return (
|
|
||||||
<div className="container">
|
|
||||||
<p className="text-error">Failed to load tool types.</p>
|
|
||||||
<button onClick={loadToolTypes}>
|
|
||||||
<Icon name="refresh" size="sm" />
|
|
||||||
Retry
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="container">
|
|
||||||
<div className="page-header" style={{ marginBottom: "1rem" }}>
|
|
||||||
<div>
|
|
||||||
<p className="eyebrow">Settings</p>
|
|
||||||
<h1>Tool Types</h1>
|
|
||||||
</div>
|
|
||||||
<div className="row">
|
|
||||||
<button className="secondary-button" type="button" onClick={() => navigate("/settings")}>Back to settings</button>
|
|
||||||
<button onClick={openCreate}>
|
|
||||||
<Icon name="add" size="sm" />
|
|
||||||
Create Tool Type
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{toolTypes.length === 0 ? (
|
|
||||||
<p>No tool types found.</p>
|
|
||||||
) : (
|
|
||||||
<div className="card-grid">
|
|
||||||
{toolTypes.map((toolType) => (
|
|
||||||
<div key={toolType.id} className="card">
|
|
||||||
<div className="card-header">
|
|
||||||
<h3>{toolType.display_name}</h3>
|
|
||||||
{toolType.is_builtin && <span className="badge">Built-in</span>}
|
|
||||||
</div>
|
|
||||||
<p className="text-secondary">{toolType.description || "No description"}</p>
|
|
||||||
<div className="tool-type-meta">
|
|
||||||
<span>Port: {toolType.default_port || "N/A"}</span>
|
|
||||||
{toolType.interface_type && (
|
|
||||||
<span>Interface: {toolType.interface_type}</span>
|
|
||||||
)}
|
|
||||||
{toolType.category && <span>Category: {toolType.category}</span>}
|
|
||||||
</div>
|
|
||||||
<div className="card-actions">
|
|
||||||
{!toolType.is_builtin && (
|
|
||||||
<>
|
|
||||||
<button onClick={() => openEdit(toolType)} className="button-secondary">
|
|
||||||
<Icon name="edit" size="sm" />
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setDeleteConfirmId(toolType.id)}
|
|
||||||
className="button-danger"
|
|
||||||
>
|
|
||||||
<Icon name="delete" size="sm" />
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{deleteConfirmId === toolType.id && (
|
|
||||||
<div className="dialog-overlay">
|
|
||||||
<div className="dialog">
|
|
||||||
<p>Delete tool type "{toolType.display_name}"?</p>
|
|
||||||
<div className="dialog-actions">
|
|
||||||
<button onClick={() => handleDelete(toolType.id)} className="button-danger">
|
|
||||||
<Icon name="delete" size="sm" />
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
<button onClick={() => setDeleteConfirmId(null)}>
|
|
||||||
<Icon name="cancel" size="sm" />
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{dialogMode !== "none" && (
|
|
||||||
<div className="dialog-overlay">
|
|
||||||
<div className="dialog">
|
|
||||||
<h2>{dialogMode === "create" ? "Create Tool Type" : "Edit Tool Type"}</h2>
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<div className="form-group">
|
|
||||||
<label>Name (unique identifier)</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={formName}
|
|
||||||
onChange={(e) => setFormName(e.target.value)}
|
|
||||||
disabled={dialogMode === "edit"}
|
|
||||||
placeholder="e.g., code-server"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label>Display Name</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={formDisplayName}
|
|
||||||
onChange={(e) => setFormDisplayName(e.target.value)}
|
|
||||||
placeholder="e.g., VS Code Server"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label>Description</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={formDescription}
|
|
||||||
onChange={(e) => setFormDescription(e.target.value)}
|
|
||||||
placeholder="Optional description"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label>Category</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={formCategory}
|
|
||||||
onChange={(e) => setFormCategory(e.target.value)}
|
|
||||||
placeholder="e.g., editor, notebook, ai-assistant"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label>Interfaces</label>
|
|
||||||
<div className="checkbox-group">
|
|
||||||
<label className="checkbox-label">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={formInterfaces.includes("web")}
|
|
||||||
onChange={(e) => {
|
|
||||||
if (e.target.checked) {
|
|
||||||
setFormInterfaces([...formInterfaces, "web"]);
|
|
||||||
} else {
|
|
||||||
setFormInterfaces(formInterfaces.filter((i) => i !== "web"));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
Web
|
|
||||||
</label>
|
|
||||||
<label className="checkbox-label">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={formInterfaces.includes("terminal")}
|
|
||||||
onChange={(e) => {
|
|
||||||
if (e.target.checked) {
|
|
||||||
setFormInterfaces([...formInterfaces, "terminal"]);
|
|
||||||
} else {
|
|
||||||
setFormInterfaces(formInterfaces.filter((i) => i !== "terminal"));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
Terminal
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label>Default Port *</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
value={formPort}
|
|
||||||
onChange={(e) => setFormPort(e.target.value)}
|
|
||||||
placeholder="e.g., 8443"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label>Compose Template (YAML)</label>
|
|
||||||
<textarea
|
|
||||||
value={formTemplate}
|
|
||||||
onChange={(e) => setFormTemplate(e.target.value)}
|
|
||||||
rows={10}
|
|
||||||
placeholder="version: '3.8' services: app: image: ..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label>Required Variables (comma-separated)</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={formVariables}
|
|
||||||
onChange={(e) => setFormVariables(e.target.value)}
|
|
||||||
placeholder="REPO_PATH, TOOL_NAME"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{formError && <p className="text-error">{formError}</p>}
|
|
||||||
|
|
||||||
<div className="dialog-actions">
|
|
||||||
<button type="submit">
|
|
||||||
{dialogMode === "create" ? (
|
|
||||||
<>
|
|
||||||
<Icon name="add" size="sm" />
|
|
||||||
Create
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Icon name="save" size="sm" />
|
|
||||||
Update
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
<button type="button" onClick={closeDialog} className="button-secondary">
|
|
||||||
<Icon name="cancel" size="sm" />
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user