Skip to content

Commit d562350

Browse files
authored
Merge branch 'main' into feature/configure-chat-ui
2 parents b24c8fe + a5a44b3 commit d562350

File tree

13 files changed

+330
-104
lines changed

13 files changed

+330
-104
lines changed

.dockerignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
node_modules
2+
npm-debug.log
3+
Dockerfile
4+
Dockerfile.jenkins
5+
docker-compose*.yml
6+
.git
7+
.gitignore
8+
.env
9+
.vscode
10+
dist

.github/workflows/ci-docker.yml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
name: CI & Docker
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
build-test:
11+
runs-on: unbuntu-latest
12+
13+
steps:
14+
- name: Checkout repository
15+
uses: actions/checkout@v4
16+
17+
- name: Use Node.js 20
18+
uses: actions/setup-node@v4
19+
with:
20+
node-version: 20
21+
cache: npm
22+
23+
- name: Install dependencies
24+
run: |
25+
cd server
26+
npm ci
27+
28+
- name: Run tests
29+
run: |
30+
cd server
31+
npm test --if-present
32+
33+
docker-image:
34+
needs: build-test
35+
runs-on: unbuntu-latest
36+
37+
permissions:
38+
contents: read
39+
packages: write
40+
41+
env:
42+
IMAGE_NAME: mcp-backend
43+
44+
steps:
45+
- name: Checkout repository
46+
uses: actions/checkout@v4
47+
48+
- name: Login to GitHub Container Registry
49+
uses: docker/login-action@v3
50+
with:
51+
registry: ghcr.io
52+
username: ${{ github.actor }}
53+
password: ${{ secrets.GITHUB_TOKEN }}
54+
55+
- name: Build Docker image
56+
run: |
57+
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}
58+
VERSION=${{ github.sha }}
59+
60+
echo "IMAGE_ID=$IMAGE_ID" >> $GITHUB_ENV
61+
echo "VERSION=$VERSION" >> $GITHUB_ENV
62+
63+
docker build -t $IMAGE_ID:$VERSION -t $IMAGE_ID:latest .
64+
65+
- name: Push Docker image
66+
run: |
67+
docker push $IMAGE_ID:$VERSION
68+
docker push $IMAGE_ID:latest

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,7 @@ coverage/
148148
.DS_Store
149149

150150
# SSH key for AWS
151-
jenkins.pem
151+
jenkins.pem
152+
153+
# Local change log (Chinese summary)
154+
change_log_cn.md

Dockerfile

Lines changed: 17 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,17 @@
1-
# Jenkins LTS (with JDK 17). The current LTS line (>= 2.492.3) meets the MCP plugin minimum requirement.
2-
FROM jenkins/jenkins:lts-jdk17
3-
4-
USER root
5-
6-
# Install base tools (git, curl, certificates).
7-
# If you use dedicated Jenkins agents, also install git inside your agent images.
8-
RUN apt-get update && apt-get install -y --no-install-recommends \
9-
git curl ca-certificates && \
10-
rm -rf /var/lib/apt/lists/*
11-
12-
# Switch back to jenkins user
13-
USER jenkins
14-
15-
# Preinstall plugins:
16-
# MCP Server, Git, Git Client, GitHub integration, Pipeline, and Credentials
17-
# Note: jenkins-plugin-cli is included in the official Jenkins image.
18-
RUN jenkins-plugin-cli --plugins \
19-
mcp-server \
20-
git \
21-
git-client \
22-
github \
23-
github-branch-source \
24-
workflow-aggregator \
25-
credentials \
26-
ssh-credentials \
27-
configuration-as-code
28-
29-
# Expose ports
30-
EXPOSE 8080 50000
31-
32-
# (Optional) Jenkins startup parameters
33-
# Disable the setup wizard on first startup:
34-
# ENV JAVA_OPTS="-Djenkins.install.runSetupWizard=false"
35-
36-
# (Optional) Mount JCasC configuration file
37-
# ENV CASC_JENKINS_CONFIG=/var/jenkins_home/casc.yaml
1+
FROM node:20-alpine
2+
3+
WORKDIR /app
4+
5+
ENV NODE_ENV=production
6+
7+
ENV PORT=3000
8+
9+
COPY package*.json ./
10+
11+
RUN npm ci --omit=dev
12+
13+
COPY . .
14+
15+
EXPOSE 3000
16+
17+
CMD ["node", "server/server.js"]

Dockerfile.jenkins

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Jenkins LTS (with JDK 17). The current LTS line (>= 2.492.3) meets the MCP plugin minimum requirement.
2+
FROM jenkins/jenkins:lts-jdk17
3+
4+
USER root
5+
6+
# Install base tools (git, curl, certificates).
7+
# If you use dedicated Jenkins agents, also install git inside your agent images.
8+
RUN apt-get update && apt-get install -y --no-install-recommends \
9+
git curl ca-certificates && \
10+
rm -rf /var/lib/apt/lists/*
11+
12+
# Switch back to jenkins user
13+
USER jenkins
14+
15+
# Preinstall plugins:
16+
# MCP Server, Git, Git Client, GitHub integration, Pipeline, and Credentials
17+
# Note: jenkins-plugin-cli is included in the official Jenkins image.
18+
RUN jenkins-plugin-cli --plugins \
19+
mcp-server \
20+
git \
21+
git-client \
22+
github \
23+
github-branch-source \
24+
workflow-aggregator \
25+
credentials \
26+
ssh-credentials \
27+
configuration-as-code
28+
29+
# Expose ports
30+
EXPOSE 8080 50000
31+
32+
# (Optional) Jenkins startup parameters
33+
# Disable the setup wizard on first startup:
34+
# ENV JAVA_OPTS="-Djenkins.install.runSetupWizard=false"
35+
36+
# (Optional) Mount JCasC configuration file
37+
# ENV CASC_JENKINS_CONFIG=/var/jenkins_home/casc.yaml

client/src/index.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,4 @@ body {
5454
@tailwind utilities;
5555
5656
html, body, #root { height: 100%; }
57-
body { background: transparent; } */
57+
body { background: transparent; } */

client/src/routes/Jenkins.tsx

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,54 @@
1-
import { useState } from "react";
1+
import { useEffect, useState } from "react";
22
import { BASE } from "../lib/api";
33

44
const SERVER_BASE = BASE.replace(/\/api$/, "");
5+
const MCP_URL_FALLBACK = "http://192.168.1.35/mcp-server/mcp";
6+
const MCP_URL_GENERAL_HINT = "Enter the MCP server URL (e.g. https://<host>/mcp-server/mcp)";
57

68
export default function Jenkins() {
79
const [question, setQuestion] = useState("");
810
const [answer, setAnswer] = useState("");
911
const [error, setError] = useState<string | null>(null);
1012
const [loading, setLoading] = useState(false);
13+
const [mcpUrl, setMcpUrl] = useState(MCP_URL_FALLBACK);
14+
const [jenkinsToken, setJenkinsToken] = useState("");
15+
const [mcpUrlHint, setMcpUrlHint] = useState(MCP_URL_FALLBACK);
16+
const [tokenHint, setTokenHint] = useState("");
17+
const [configError, setConfigError] = useState<string | null>(null);
18+
19+
useEffect(() => {
20+
let isMounted = true;
21+
async function loadHints() {
22+
try {
23+
const res = await fetch(`${SERVER_BASE}/jenkins/config`, {
24+
credentials: "include",
25+
});
26+
if (!res.ok) {
27+
throw new Error(res.statusText);
28+
}
29+
const data = await res.json().catch(() => ({}));
30+
if (!isMounted) return;
31+
const hintUrl = data?.mcpUrlHint || MCP_URL_FALLBACK;
32+
const hintToken = data?.tokenHint || "";
33+
setMcpUrlHint(hintUrl);
34+
setTokenHint(hintToken);
35+
setMcpUrl(hintUrl);
36+
setJenkinsToken(hintToken);
37+
setConfigError(null);
38+
} catch (err: any) {
39+
if (!isMounted) return;
40+
setConfigError(err?.message || "Unable to load Jenkins defaults");
41+
}
42+
}
43+
44+
loadHints();
45+
return () => {
46+
isMounted = false;
47+
};
48+
}, []);
1149

1250
async function handleAsk() {
13-
if (!question.trim()) return;
51+
if (!question.trim() || !mcpUrl.trim() || !jenkinsToken.trim()) return;
1452
setLoading(true);
1553
setError(null);
1654
setAnswer("");
@@ -19,7 +57,11 @@ export default function Jenkins() {
1957
method: "POST",
2058
headers: { "Content-Type": "application/json" },
2159
credentials: "include",
22-
body: JSON.stringify({ question: question.trim() }),
60+
body: JSON.stringify({
61+
question: question.trim(),
62+
mcpUrl: mcpUrl.trim() || undefined,
63+
token: jenkinsToken.trim() || undefined,
64+
}),
2365
});
2466
const data = await res.json().catch(() => ({}));
2567
if (!res.ok) throw new Error((data as any)?.error || res.statusText);
@@ -34,6 +76,32 @@ export default function Jenkins() {
3476
return (
3577
<section style={{ display: "grid", gap: 12 }}>
3678
<h1>Jenkins</h1>
79+
<label style={{ display: "grid", gap: 4 }}>
80+
<span style={{ fontWeight: 500 }}>MCP URL</span>
81+
<input
82+
type="text"
83+
value={mcpUrl}
84+
onChange={(e) => setMcpUrl(e.target.value)}
85+
placeholder={MCP_URL_GENERAL_HINT}
86+
style={{ width: "100%", padding: 8, fontSize: 14 }}
87+
/>
88+
<span style={{ fontSize: 12, color: "#555" }}>
89+
Hint: {MCP_URL_GENERAL_HINT}
90+
</span>
91+
</label>
92+
<label style={{ display: "grid", gap: 4 }}>
93+
<span style={{ fontWeight: 500 }}>Jenkins Token</span>
94+
<input
95+
type="password"
96+
value={jenkinsToken}
97+
onChange={(e) => setJenkinsToken(e.target.value)}
98+
placeholder={tokenHint || "Enter Jenkins token"}
99+
style={{ width: "100%", padding: 8, fontSize: 14 }}
100+
/>
101+
<span style={{ fontSize: 12, color: "#555" }}>
102+
Hint: {tokenHint ? tokenHint : "Set JENKINS_TOKEN to prefill"}
103+
</span>
104+
</label>
37105
<textarea
38106
rows={4}
39107
value={question}
@@ -42,7 +110,12 @@ export default function Jenkins() {
42110
style={{ width: "100%", padding: 8, fontSize: 14 }}
43111
/>
44112
<div style={{ display: "flex", gap: 8 }}>
45-
<button onClick={handleAsk} disabled={loading || !question.trim()}>
113+
<button
114+
onClick={handleAsk}
115+
disabled={
116+
loading || !question.trim() || !mcpUrl.trim() || !jenkinsToken.trim()
117+
}
118+
>
46119
{loading ? "Sending..." : "Ask"}
47120
</button>
48121
<button
@@ -51,11 +124,18 @@ export default function Jenkins() {
51124
setQuestion("");
52125
setAnswer("");
53126
setError(null);
127+
setMcpUrl(mcpUrlHint);
128+
setJenkinsToken(tokenHint);
54129
}}
55130
>
56131
Clear
57132
</button>
58133
</div>
134+
{configError && (
135+
<div style={{ color: "#a67c00", fontSize: 13 }}>
136+
Using fallback MCP defaults: {configError}
137+
</div>
138+
)}
59139
{error && <div style={{ color: "red", fontSize: 13 }}>{error}</div>}
60140
<textarea
61141
readOnly

client/tailwind.config.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/** @type {import('tailwindcss').Config} */
2+
export default {
3+
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
4+
theme: { extend: {} },
5+
plugins: [],
6+
};

0 commit comments

Comments
 (0)