Skip to content

Commit 6f064fe

Browse files
committed
backend converted to typescript
Signed-off-by: Sayan Banerjee <[email protected]>
1 parent a4742ce commit 6f064fe

28 files changed

+3711
-3212
lines changed

.github/workflows/build.yml

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Build Check
2+
3+
on:
4+
pull_request:
5+
branches: [main]
6+
7+
jobs:
8+
build-check:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: Checkout code
12+
uses: actions/checkout@v4
13+
14+
- name: Install Node.js
15+
uses: actions/setup-node@v4
16+
with:
17+
node-version: 20
18+
cache: "npm"
19+
20+
- name: Install dependencies
21+
run: npm ci
22+
23+
- name: Run Frontend Build
24+
run: npm run build -w frontend || (echo "Frontend build failed!" && exit 1)
25+
26+
- name: Run Backend Build
27+
run: npm run build -w backend || (echo "Backend build failed!" && exit 1)

backend/.babelrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"presets": ["@babel/preset-env"],
2+
"presets": ["@babel/preset-env", "@babel/preset-typescript"],
33
"plugins": [
44
"@babel/plugin-transform-nullish-coalescing-operator",
55
"@babel/plugin-transform-logical-assignment-operators",

backend/nodemon.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"watch": ["src"],
3+
"ext": "ts,js",
4+
"ignore": ["node_modules"],
5+
"exec": "tsx ./src/server.ts"
6+
}

backend/package.json

+16-7
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@
33
"version": "v1.30.0",
44
"description": "JS API client generated by OpenAPI Generator",
55
"license": "Unlicense",
6-
"main": "dist/index.js",
6+
"main": "dist/server.js",
77
"scripts": {
8-
"dev": "nodemon src/server.js",
9-
"build": "babel src -d dist",
10-
"prepare": "npm run build",
8+
"dev": "nodemon",
9+
"build": "tsc --noEmit && babel src --extensions \".ts,.js\" -d dist",
1110
"test": "vitest",
12-
"test:ui": "vitest --ui"
11+
"test:ui": "vitest --ui",
12+
"generate:types": "bash ./src/types/generate.sh"
1313
},
1414
"browser": {
1515
"fs": false
1616
},
17-
"type": "module",
17+
"type": "commonjs",
1818
"dependencies": {
1919
"@babel/cli": "^7.0.0",
2020
"@kubernetes/client-node": "^1.0.0",
@@ -43,12 +43,21 @@
4343
"@babel/plugin-transform-numeric-separator": "^7.25.9",
4444
"@babel/plugin-transform-optional-chaining": "^7.25.9",
4545
"@babel/preset-env": "^7.0.0",
46+
"@babel/preset-typescript": "^7.26.0",
4647
"@babel/register": "^7.0.0",
48+
"@types/cors": "^2.8.17",
49+
"@types/express": "^5.0.0",
50+
"@types/jest": "^29.5.14",
51+
"@types/sinon": "^17.0.4",
52+
"@types/supertest": "^6.0.3",
4753
"expect.js": "^0.3.1",
4854
"mocha": "^11.1.0",
4955
"nodemon": "^3.1.9",
56+
"openapi-typescript": "^7.6.1",
5057
"sinon": "^7.2.0",
51-
"supertest": "^7.0.0"
58+
"supertest": "^7.0.0",
59+
"tsx": "^4.19.2",
60+
"typescript": "^5.7.3"
5261
},
5362
"files": [
5463
"dist"

backend/src/app.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import express from "express";
2+
import cors from "cors";
3+
import {
4+
jobRouter,
5+
namespaceRouter,
6+
podRouter,
7+
queueRouter,
8+
} from "./routes/index";
9+
10+
const app = express();
11+
12+
app.use(cors({ origin: "*" }));
13+
14+
app.use("/api", jobRouter);
15+
app.use("/api", namespaceRouter);
16+
app.use("/api", podRouter);
17+
app.use("/api", queueRouter);
18+
19+
export default app;

backend/src/config/kubernetes.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {
2+
CoreV1Api,
3+
CustomObjectsApi,
4+
KubeConfig,
5+
} from "@kubernetes/client-node";
6+
7+
const kc = new KubeConfig();
8+
kc.loadFromDefault();
9+
10+
export const k8sApi = kc.makeApiClient(CustomObjectsApi);
11+
export const k8sCoreApi = kc.makeApiClient(CoreV1Api);

backend/src/controllers/job.ts

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import { Request, Response } from "express";
2+
import { k8sApi } from "../config/kubernetes";
3+
import http from "http";
4+
import yaml from "js-yaml";
5+
import { IJob } from "../types/index";
6+
7+
interface IResponse {
8+
items: IJob[];
9+
}
10+
11+
// Auxiliary function: determine the status based on the job status
12+
function getJobState(job: IJob) {
13+
if (job.status === "Running") return "Running";
14+
if (job.status === "Completed") return "Completed";
15+
if (job.status === "Failed") return "Failed";
16+
if (job.status === "Pending") return "Running";
17+
return job.status || "Unknown";
18+
}
19+
20+
// @desc Get all Jobs (no pagination)
21+
// @route GET /all-jobs
22+
export const getAllJobs = async (req: Request, res: Response) => {
23+
try {
24+
const response: IResponse = await k8sApi.listClusterCustomObject({
25+
group: "batch.volcano.sh",
26+
version: "v1alpha1",
27+
plural: "jobs", // 修改这里:从 "jobs" 改为 "vcjobs"
28+
pretty: "true",
29+
});
30+
31+
const jobs = response.items.map((job) => ({
32+
...job,
33+
status: {
34+
state: job.status?.state || getJobState(job),
35+
phase:
36+
job.status?.state?.phase || job.spec?.minAvailable
37+
? "Running"
38+
: "Unknown",
39+
},
40+
}));
41+
42+
res.json({
43+
items: jobs,
44+
totalCount: jobs.length,
45+
});
46+
} catch (err) {
47+
console.error("Error fetching all jobs:", err);
48+
res.status(500).json({ error: "Failed to fetch all jobs" });
49+
}
50+
};
51+
52+
interface JobQueryParams {
53+
namespace?: string;
54+
search?: string;
55+
queue?: string;
56+
status?: string;
57+
}
58+
59+
// @desc Get Jobs with pagination
60+
// @route GET /jobs
61+
export const getJobs = async (
62+
req: Request<{}, {}, {}, JobQueryParams>,
63+
res: Response,
64+
) => {
65+
try {
66+
const namespace = req.query.namespace || "";
67+
const searchTerm = req.query.search || "";
68+
const queueFilter = req.query.queue || "";
69+
const statusFilter = req.query.status || "";
70+
71+
console.log("Fetching jobs with params:", {
72+
namespace,
73+
searchTerm,
74+
queueFilter,
75+
statusFilter,
76+
});
77+
78+
let response: IResponse;
79+
if (namespace === "" || namespace === "All") {
80+
response = await k8sApi.listClusterCustomObject({
81+
group: "batch.volcano.sh",
82+
version: "v1alpha1",
83+
plural: "jobs",
84+
pretty: "true",
85+
});
86+
} else {
87+
response = await k8sApi.listNamespacedCustomObject({
88+
group: "batch.volcano.sh",
89+
version: "v1alpha1",
90+
namespace,
91+
plural: "jobs",
92+
pretty: "true",
93+
});
94+
}
95+
96+
let filteredJobs = response.items || [];
97+
98+
if (searchTerm) {
99+
filteredJobs = filteredJobs.filter((job) =>
100+
job.metadata?.name
101+
.toLowerCase()
102+
.includes(searchTerm.toLowerCase()),
103+
);
104+
}
105+
106+
// Apply queueFilter filtering
107+
if (queueFilter && queueFilter !== "All") {
108+
filteredJobs = filteredJobs.filter(
109+
(job) => job.spec?.queue === queueFilter,
110+
);
111+
}
112+
113+
if (statusFilter && statusFilter !== "All") {
114+
filteredJobs = filteredJobs.filter(
115+
(job) => job.status?.state?.phase === statusFilter,
116+
);
117+
}
118+
119+
res.json({
120+
items: filteredJobs,
121+
totalCount: filteredJobs.length,
122+
});
123+
} catch (err) {
124+
console.error("Error fetching jobs:", err);
125+
res.status(500).json({
126+
error: "Failed to fetch jobs",
127+
details: (err as Error).message,
128+
});
129+
}
130+
};
131+
132+
// @desc Add an interface to obtain a single job
133+
// @route GET /jobs/:namespace/:name
134+
export const getJobByName = async (req: Request, res: Response) => {
135+
try {
136+
const { namespace, name } = req.params;
137+
const response: IResponse = await k8sApi.getNamespacedCustomObject({
138+
group: "batch.volcano.sh",
139+
version: "v1alpha1",
140+
namespace,
141+
plural: "jobs",
142+
name,
143+
});
144+
res.json(response);
145+
} catch (err) {
146+
console.error("Error fetching job:", err);
147+
res.status(500).json({
148+
error: "Failed to fetch job",
149+
details: (err as Error).message,
150+
});
151+
}
152+
};
153+
154+
interface JobParams {
155+
namespace: string;
156+
name: string;
157+
}
158+
//@desc Add a route to obtain YAML for a single job
159+
//@route GET /jobs/:namespace/:name/yaml
160+
export const getJobYamlByName = async (
161+
req: Request<JobParams>,
162+
res: Response,
163+
) => {
164+
try {
165+
const { namespace, name } = req.params;
166+
const response: IResponse = await k8sApi.getNamespacedCustomObject({
167+
group: "batch.volcano.sh",
168+
version: "v1alpha1",
169+
namespace,
170+
plural: "jobs",
171+
name,
172+
});
173+
174+
const formattedYaml = yaml.dump(response, {
175+
indent: 2,
176+
lineWidth: -1,
177+
noRefs: true,
178+
sortKeys: false,
179+
});
180+
181+
res.setHeader("Content-Type", "text/yaml");
182+
res.send(formattedYaml);
183+
} catch (err) {
184+
console.error("Error fetching job YAML:", err);
185+
res.status(500).json({
186+
error: "Failed to fetch job YAML",
187+
details: (err as Error).message,
188+
});
189+
}
190+
};

backend/src/controllers/namespace.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Request, Response } from "express";
2+
import { k8sCoreApi } from "../config/kubernetes.js";
3+
4+
// @desc Get all namespaces
5+
// @route GET /namespaces
6+
export const getNamespaces = async (req: Request, res: Response) => {
7+
try {
8+
const response = await k8sCoreApi.listNamespace();
9+
10+
res.json({
11+
items: response.items,
12+
});
13+
} catch (error) {
14+
console.error("Error fetching namespaces:", error);
15+
res.status(500).json({
16+
error: "Failed to fetch namespaces",
17+
details: (error as Error).message,
18+
});
19+
}
20+
};

0 commit comments

Comments
 (0)