Skip to content

Commit c68ae3a

Browse files
committed
feat(webui): use nextjs middleware to dynamically redirect API calls
1 parent 751cf95 commit c68ae3a

File tree

9 files changed

+125
-99
lines changed

9 files changed

+125
-99
lines changed

webui/next.config.mjs

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,10 @@
1717
* under the License.
1818
*/
1919

20-
import { PHASE_DEVELOPMENT_SERVER } from "next/constants.js";
21-
22-
const apiPrefix = "/api/v1";
23-
const devHost = "127.0.0.1:9379";
24-
const prodHost = "production-api.yourdomain.com";
25-
2620
const nextConfig = (phase, { defaultConfig }) => {
27-
const isDev = phase === PHASE_DEVELOPMENT_SERVER;
28-
const host = isDev ? devHost : prodHost;
29-
3021
return {
31-
async rewrites() {
32-
return [
33-
{
34-
source: `${apiPrefix}/:slug*`,
35-
destination: `http://${host}${apiPrefix}/:slug*`,
36-
},
37-
];
38-
},
22+
reactStrictMode: true,
23+
output: 'standalone',
3924
};
4025
};
4126

webui/package.json

Lines changed: 44 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,46 @@
11
{
2-
"name": "kvrocks-controller-ui",
3-
"version": "0.1.0",
4-
"private": true,
5-
"scripts": {
6-
"dev": "next dev",
7-
"build": "next build",
8-
"start": "next start",
9-
"lint": "next lint"
10-
},
11-
"dependencies": {
12-
"@emotion/react": "^11.11.3",
13-
"@emotion/styled": "^11.11.0",
14-
"@fortawesome/fontawesome-svg-core": "^6.6.0",
15-
"@fortawesome/free-brands-svg-icons": "^6.6.0",
16-
"@fortawesome/free-solid-svg-icons": "^6.6.0",
17-
"@fortawesome/react-fontawesome": "^0.2.2",
18-
"@mui/icons-material": "^5.15.7",
19-
"@mui/material": "^5.15.5",
20-
"@types/js-yaml": "^4.0.9",
21-
"axios": "^1.6.7",
22-
"js-yaml": "^4.1.0",
23-
"next": "^14.2.29",
24-
"react": "^18",
25-
"react-dom": "^18"
26-
},
27-
"devDependencies": {
28-
"@types/node": "^20",
29-
"@types/react": "^18",
30-
"@types/react-dom": "^18",
31-
"autoprefixer": "^10.0.1",
32-
"eslint": "^8",
33-
"eslint-config-next": "14.1.0",
34-
"eslint-config-prettier": "^10.1.1",
35-
"postcss": "^8",
36-
"prettier": "^3.5.3",
37-
"prettier-plugin-tailwindcss": "^0.6.11",
38-
"tailwindcss": "^3.3.0",
39-
"typescript": "^5"
40-
}
2+
"name": "kvrocks-controller-ui",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"dev": "next dev --turbopack",
7+
"build": "next build",
8+
"start": "next start",
9+
"lint": "next lint",
10+
"deploy": "next build && cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/"
11+
},
12+
"dependencies": {
13+
"@emotion/react": "^11.11.3",
14+
"@emotion/styled": "^11.11.0",
15+
"@fortawesome/fontawesome-svg-core": "^6.6.0",
16+
"@fortawesome/free-brands-svg-icons": "^6.6.0",
17+
"@fortawesome/free-solid-svg-icons": "^6.6.0",
18+
"@fortawesome/react-fontawesome": "^0.2.2",
19+
"@mui/icons-material": "^5.15.7",
20+
"@mui/material": "^5.15.5",
21+
"@types/js-yaml": "^4.0.9",
22+
"axios": "^1.6.7",
23+
"js-yaml": "^4.1.0",
24+
"next": "15.5.4",
25+
"react": "19.2.0",
26+
"react-dom": "19.2.0"
27+
},
28+
"devDependencies": {
29+
"@types/node": "^20",
30+
"@types/react": "19.2.2",
31+
"@types/react-dom": "19.2.1",
32+
"autoprefixer": "^10.0.1",
33+
"eslint": "^8",
34+
"eslint-config-next": "15.5.4",
35+
"eslint-config-prettier": "^10.1.1",
36+
"postcss": "^8",
37+
"prettier": "^3.5.3",
38+
"prettier-plugin-tailwindcss": "^0.6.11",
39+
"tailwindcss": "^3.3.0",
40+
"typescript": "^5"
41+
},
42+
"overrides": {
43+
"@types/react": "19.2.2",
44+
"@types/react-dom": "19.2.1"
45+
}
4146
}

webui/src/app/namespaces/[namespace]/clusters/[cluster]/page.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import {
3838
Divider,
3939
} from "@mui/material";
4040
import { ClusterSidebar } from "../../../../ui/sidebar";
41-
import { useState, useEffect } from "react";
41+
import { useState, useEffect, use } from "react";
4242
import { listShards, listNodes, fetchCluster, deleteShard } from "@/app/lib/api";
4343
import { AddShardCard, ResourceCard } from "@/app/ui/createCard";
4444
import Link from "next/link";
@@ -95,7 +95,8 @@ type FilterOption =
9595
| "with-importing";
9696
type SortOption = "index-asc" | "index-desc" | "nodes-desc" | "nodes-asc";
9797

98-
export default function Cluster({ params }: { params: { namespace: string; cluster: string } }) {
98+
export default function Cluster(props: { params: Promise<{ namespace: string; cluster: string }> }) {
99+
const params = use(props.params);
99100
const { namespace, cluster } = params;
100101
const [shardsData, setShardsData] = useState<ShardData[]>([]);
101102
const [resourceCounts, setResourceCounts] = useState<ResourceCounts>({

webui/src/app/namespaces/[namespace]/clusters/[cluster]/shards/[shard]/nodes/[node]/page.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import { listNodes } from "@/app/lib/api";
2323
import { NodeSidebar } from "@/app/ui/sidebar";
2424
import { Box, Typography, Chip, Paper, Divider, Grid, Alert, IconButton } from "@mui/material";
25-
import { useEffect, useState } from "react";
25+
import { useEffect, useState, use } from "react";
2626
import { useRouter } from "next/navigation";
2727
import { LoadingSpinner } from "@/app/ui/loadingSpinner";
2828
import { truncateText } from "@/app/utils";
@@ -39,11 +39,12 @@ import NetworkCheckIcon from "@mui/icons-material/NetworkCheck";
3939
import SecurityIcon from "@mui/icons-material/Security";
4040
import LinkIcon from "@mui/icons-material/Link";
4141

42-
export default function Node({
43-
params,
44-
}: {
45-
params: { namespace: string; cluster: string; shard: string; node: string };
46-
}) {
42+
export default function Node(
43+
props: {
44+
params: Promise<{ namespace: string; cluster: string; shard: string; node: string }>;
45+
}
46+
) {
47+
const params = use(props.params);
4748
const { namespace, cluster, shard, node } = params;
4849
const router = useRouter();
4950
const [nodeData, setNodeData] = useState<any[]>([]);

webui/src/app/namespaces/[namespace]/clusters/[cluster]/shards/[shard]/page.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import {
3737
import { ShardSidebar } from "@/app/ui/sidebar";
3838
import { fetchShard, deleteNode } from "@/app/lib/api";
3939
import { useRouter } from "next/navigation";
40-
import { useState, useEffect } from "react";
40+
import { useState, useEffect, use } from "react";
4141
import { AddNodeCard } from "@/app/ui/createCard";
4242
import Link from "next/link";
4343
import { LoadingSpinner } from "@/app/ui/loadingSpinner";
@@ -60,11 +60,12 @@ import DeleteIcon from "@mui/icons-material/Delete";
6060
import SwapHorizIcon from "@mui/icons-material/SwapHoriz";
6161
import { FailoverDialog } from "@/app/ui/failoverDialog";
6262

63-
export default function Shard({
64-
params,
65-
}: {
66-
params: { namespace: string; cluster: string; shard: string };
67-
}) {
63+
export default function Shard(
64+
props: {
65+
params: Promise<{ namespace: string; cluster: string; shard: string }>;
66+
}
67+
) {
68+
const params = use(props.params);
6869
const { namespace, cluster, shard } = params;
6970
const [nodesData, setNodesData] = useState<any>(null);
7071
const [loading, setLoading] = useState<boolean>(true);

webui/src/app/namespaces/[namespace]/page.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import {
4545
} from "@/app/lib/api";
4646
import Link from "next/link";
4747
import { useRouter, notFound } from "next/navigation";
48-
import { useState, useEffect } from "react";
48+
import { useState, useEffect, use } from "react";
4949
import { LoadingSpinner } from "@/app/ui/loadingSpinner";
5050
import StorageIcon from "@mui/icons-material/Storage";
5151
import FolderIcon from "@mui/icons-material/Folder";
@@ -106,7 +106,8 @@ type SortOption =
106106
| "nodes-desc"
107107
| "nodes-asc";
108108

109-
export default function Namespace({ params }: { params: { namespace: string } }) {
109+
export default function Namespace(props: { params: Promise<{ namespace: string }> }) {
110+
const params = use(props.params);
110111
const [clusterData, setClusterData] = useState<ClusterData[]>([]);
111112
const [resourceCounts, setResourceCounts] = useState<ResourceCounts>({
112113
clusters: 0,

webui/src/app/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export default function Home() {
6060
const [scrollY, setScrollY] = useState(0);
6161
const [cursorPosition, setCursorPosition] = useState({ x: 0, y: 0 });
6262
const [cursorVisible, setCursorVisible] = useState(true);
63-
const requestRef = useRef<number>();
63+
const requestRef = useRef<number>(undefined);
6464
const prevScrollY = useRef(0);
6565

6666
const terminalRef = useRef({ lineIndex: 0, charIndex: 0 });

webui/src/middleware.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { NextResponse } from "next/server";
2+
import type { NextRequest } from "next/server";
3+
4+
export function middleware(req: NextRequest) {
5+
const url = req.nextUrl.clone()
6+
7+
if (url.pathname.startsWith("/api/v1")) {
8+
const host = process.env.KVCTL_API_HOST || "localhost:9379"
9+
url.host = host;
10+
11+
return NextResponse.rewrite(url)
12+
}
13+
14+
return NextResponse.next()
15+
}
16+
17+
export const config = {
18+
matcher: "/api/v1/:path*",
19+
runtime: 'nodejs',
20+
}

webui/tsconfig.json

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,31 +15,43 @@
1515
* KIND, either express or implied. See the License for the
1616
* specific language governing permissions and limitations
1717
* under the License.
18-
*/
19-
20-
{
21-
"compilerOptions": {
22-
"lib": ["dom", "dom.iterable", "esnext"],
23-
"allowJs": true,
24-
"skipLibCheck": true,
25-
"strict": true,
26-
"noEmit": true,
27-
"esModuleInterop": true,
28-
"module": "esnext",
29-
"moduleResolution": "bundler",
30-
"resolveJsonModule": true,
31-
"isolatedModules": true,
32-
"jsx": "preserve",
33-
"incremental": true,
34-
"plugins": [
35-
{
36-
"name": "next"
37-
}
38-
],
39-
"paths": {
40-
"@/*": ["./src/*"]
41-
}
18+
*/{
19+
"compilerOptions": {
20+
"lib": [
21+
"dom",
22+
"dom.iterable",
23+
"esnext"
24+
],
25+
"allowJs": true,
26+
"skipLibCheck": true,
27+
"strict": true,
28+
"noEmit": true,
29+
"esModuleInterop": true,
30+
"module": "esnext",
31+
"moduleResolution": "bundler",
32+
"resolveJsonModule": true,
33+
"isolatedModules": true,
34+
"jsx": "preserve",
35+
"incremental": true,
36+
"plugins": [
37+
{
38+
"name": "next"
39+
}
40+
],
41+
"paths": {
42+
"@/*": [
43+
"./src/*"
44+
]
4245
},
43-
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
44-
"exclude": ["node_modules"]
46+
"target": "ES2017"
47+
},
48+
"include": [
49+
"next-env.d.ts",
50+
"**/*.ts",
51+
"**/*.tsx",
52+
".next/types/**/*.ts"
53+
],
54+
"exclude": [
55+
"node_modules"
56+
]
4557
}

0 commit comments

Comments
 (0)