diff --git a/.gitignore b/.gitignore index 0653358..96c6419 100644 --- a/.gitignore +++ b/.gitignore @@ -148,4 +148,7 @@ coverage/ .DS_Store # SSH key for AWS -jenkins.pem \ No newline at end of file +jenkins.pem + +# Local change log (Chinese summary) +change_log_cn.md diff --git a/client/src/routes/Jenkins.tsx b/client/src/routes/Jenkins.tsx index a40c3ea..1ffa13e 100644 --- a/client/src/routes/Jenkins.tsx +++ b/client/src/routes/Jenkins.tsx @@ -1,16 +1,54 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { BASE } from "../lib/api"; const SERVER_BASE = BASE.replace(/\/api$/, ""); +const MCP_URL_FALLBACK = "http://192.168.1.35/mcp-server/mcp"; +const MCP_URL_GENERAL_HINT = "Enter the MCP server URL (e.g. https:///mcp-server/mcp)"; export default function Jenkins() { const [question, setQuestion] = useState(""); const [answer, setAnswer] = useState(""); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); + const [mcpUrl, setMcpUrl] = useState(MCP_URL_FALLBACK); + const [jenkinsToken, setJenkinsToken] = useState(""); + const [mcpUrlHint, setMcpUrlHint] = useState(MCP_URL_FALLBACK); + const [tokenHint, setTokenHint] = useState(""); + const [configError, setConfigError] = useState(null); + + useEffect(() => { + let isMounted = true; + async function loadHints() { + try { + const res = await fetch(`${SERVER_BASE}/jenkins/config`, { + credentials: "include", + }); + if (!res.ok) { + throw new Error(res.statusText); + } + const data = await res.json().catch(() => ({})); + if (!isMounted) return; + const hintUrl = data?.mcpUrlHint || MCP_URL_FALLBACK; + const hintToken = data?.tokenHint || ""; + setMcpUrlHint(hintUrl); + setTokenHint(hintToken); + setMcpUrl(hintUrl); + setJenkinsToken(hintToken); + setConfigError(null); + } catch (err: any) { + if (!isMounted) return; + setConfigError(err?.message || "Unable to load Jenkins defaults"); + } + } + + loadHints(); + return () => { + isMounted = false; + }; + }, []); async function handleAsk() { - if (!question.trim()) return; + if (!question.trim() || !mcpUrl.trim() || !jenkinsToken.trim()) return; setLoading(true); setError(null); setAnswer(""); @@ -19,7 +57,11 @@ export default function Jenkins() { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", - body: JSON.stringify({ question: question.trim() }), + body: JSON.stringify({ + question: question.trim(), + mcpUrl: mcpUrl.trim() || undefined, + token: jenkinsToken.trim() || undefined, + }), }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error((data as any)?.error || res.statusText); @@ -34,6 +76,32 @@ export default function Jenkins() { return (

Jenkins

+ +