This is an automated email from the ASF dual-hosted git repository.

twice pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks-controller.git


The following commit(s) were added to refs/heads/unstable by this push:
     new d9f8c40  feat(webui): use nextjs middleware to dynamically redirect 
API calls (#360)
d9f8c40 is described below

commit d9f8c40db01c9d14c5dc179177291127490f4b59
Author: Twice <[email protected]>
AuthorDate: Wed Oct 8 18:39:22 2025 +0800

    feat(webui): use nextjs middleware to dynamically redirect API calls (#360)
    
    * feat(webui): use nextjs middleware to dynamically redirect API calls
    
    * lint
---
 webui/next.config.mjs                              | 19 ++----------------
 webui/package.json                                 | 23 +++++++++++++---------
 .../[namespace]/clusters/[cluster]/page.tsx        |  7 +++++--
 .../[cluster]/shards/[shard]/nodes/[node]/page.tsx |  9 ++++-----
 .../clusters/[cluster]/shards/[shard]/page.tsx     |  9 ++++-----
 webui/src/app/namespaces/[namespace]/page.tsx      |  5 +++--
 webui/src/app/page.tsx                             |  2 +-
 webui/src/middleware.ts                            | 20 +++++++++++++++++++
 webui/tsconfig.json                                |  7 +++----
 9 files changed, 56 insertions(+), 45 deletions(-)

diff --git a/webui/next.config.mjs b/webui/next.config.mjs
index 4a75f77..fd8c840 100644
--- a/webui/next.config.mjs
+++ b/webui/next.config.mjs
@@ -17,25 +17,10 @@
  * under the License.
  */
 
-import { PHASE_DEVELOPMENT_SERVER } from "next/constants.js";
-
-const apiPrefix = "/api/v1";
-const devHost = "127.0.0.1:9379";
-const prodHost = "production-api.yourdomain.com";
-
 const nextConfig = (phase, { defaultConfig }) => {
-    const isDev = phase === PHASE_DEVELOPMENT_SERVER;
-    const host = isDev ? devHost : prodHost;
-
     return {
-        async rewrites() {
-            return [
-                {
-                    source: `${apiPrefix}/:slug*`,
-                    destination: `http://${host}${apiPrefix}/:slug*`,
-                },
-            ];
-        },
+        reactStrictMode: true,
+        output: "standalone",
     };
 };
 
diff --git a/webui/package.json b/webui/package.json
index 77e7828..67f62c9 100644
--- a/webui/package.json
+++ b/webui/package.json
@@ -3,10 +3,11 @@
     "version": "0.1.0",
     "private": true,
     "scripts": {
-        "dev": "next dev",
+        "dev": "next dev --turbopack",
         "build": "next build",
         "start": "next start",
-        "lint": "next lint"
+        "lint": "next lint",
+        "deploy": "next build && cp -r .next/static .next/standalone/.next/ && 
cp -r public .next/standalone/"
     },
     "dependencies": {
         "@emotion/react": "^11.11.3",
@@ -20,22 +21,26 @@
         "@types/js-yaml": "^4.0.9",
         "axios": "^1.6.7",
         "js-yaml": "^4.1.0",
-        "next": "^14.2.29",
-        "react": "^18",
-        "react-dom": "^18"
+        "next": "15.5.4",
+        "react": "19.2.0",
+        "react-dom": "19.2.0"
     },
     "devDependencies": {
         "@types/node": "^20",
-        "@types/react": "^18",
-        "@types/react-dom": "^18",
+        "@types/react": "19.2.2",
+        "@types/react-dom": "19.2.1",
         "autoprefixer": "^10.0.1",
         "eslint": "^8",
-        "eslint-config-next": "14.1.0",
+        "eslint-config-next": "15.5.4",
         "eslint-config-prettier": "^10.1.1",
         "postcss": "^8",
-        "prettier": "^3.5.3",
+        "prettier": "^3.6.2",
         "prettier-plugin-tailwindcss": "^0.6.11",
         "tailwindcss": "^3.3.0",
         "typescript": "^5"
+    },
+    "overrides": {
+        "@types/react": "19.2.2",
+        "@types/react-dom": "19.2.1"
     }
 }
diff --git a/webui/src/app/namespaces/[namespace]/clusters/[cluster]/page.tsx 
b/webui/src/app/namespaces/[namespace]/clusters/[cluster]/page.tsx
index 5132a1c..6288541 100644
--- a/webui/src/app/namespaces/[namespace]/clusters/[cluster]/page.tsx
+++ b/webui/src/app/namespaces/[namespace]/clusters/[cluster]/page.tsx
@@ -38,7 +38,7 @@ import {
     Divider,
 } from "@mui/material";
 import { ClusterSidebar } from "../../../../ui/sidebar";
-import { useState, useEffect } from "react";
+import { useState, useEffect, use } from "react";
 import { listShards, listNodes, fetchCluster, deleteShard } from 
"@/app/lib/api";
 import { AddShardCard, ResourceCard } from "@/app/ui/createCard";
 import Link from "next/link";
@@ -95,7 +95,10 @@ type FilterOption =
     | "with-importing";
 type SortOption = "index-asc" | "index-desc" | "nodes-desc" | "nodes-asc";
 
-export default function Cluster({ params }: { params: { namespace: string; 
cluster: string } }) {
+export default function Cluster(props: {
+    params: Promise<{ namespace: string; cluster: string }>;
+}) {
+    const params = use(props.params);
     const { namespace, cluster } = params;
     const [shardsData, setShardsData] = useState<ShardData[]>([]);
     const [resourceCounts, setResourceCounts] = useState<ResourceCounts>({
diff --git 
a/webui/src/app/namespaces/[namespace]/clusters/[cluster]/shards/[shard]/nodes/[node]/page.tsx
 
b/webui/src/app/namespaces/[namespace]/clusters/[cluster]/shards/[shard]/nodes/[node]/page.tsx
index 549053d..b785cb4 100644
--- 
a/webui/src/app/namespaces/[namespace]/clusters/[cluster]/shards/[shard]/nodes/[node]/page.tsx
+++ 
b/webui/src/app/namespaces/[namespace]/clusters/[cluster]/shards/[shard]/nodes/[node]/page.tsx
@@ -22,7 +22,7 @@
 import { listNodes } from "@/app/lib/api";
 import { NodeSidebar } from "@/app/ui/sidebar";
 import { Box, Typography, Chip, Paper, Divider, Grid, Alert, IconButton } from 
"@mui/material";
-import { useEffect, useState } from "react";
+import { useEffect, useState, use } from "react";
 import { useRouter } from "next/navigation";
 import { LoadingSpinner } from "@/app/ui/loadingSpinner";
 import { truncateText } from "@/app/utils";
@@ -39,11 +39,10 @@ import NetworkCheckIcon from 
"@mui/icons-material/NetworkCheck";
 import SecurityIcon from "@mui/icons-material/Security";
 import LinkIcon from "@mui/icons-material/Link";
 
-export default function Node({
-    params,
-}: {
-    params: { namespace: string; cluster: string; shard: string; node: string 
};
+export default function Node(props: {
+    params: Promise<{ namespace: string; cluster: string; shard: string; node: 
string }>;
 }) {
+    const params = use(props.params);
     const { namespace, cluster, shard, node } = params;
     const router = useRouter();
     const [nodeData, setNodeData] = useState<any[]>([]);
diff --git 
a/webui/src/app/namespaces/[namespace]/clusters/[cluster]/shards/[shard]/page.tsx
 
b/webui/src/app/namespaces/[namespace]/clusters/[cluster]/shards/[shard]/page.tsx
index e0d5b80..a7406a6 100644
--- 
a/webui/src/app/namespaces/[namespace]/clusters/[cluster]/shards/[shard]/page.tsx
+++ 
b/webui/src/app/namespaces/[namespace]/clusters/[cluster]/shards/[shard]/page.tsx
@@ -37,7 +37,7 @@ import {
 import { ShardSidebar } from "@/app/ui/sidebar";
 import { fetchShard, deleteNode } from "@/app/lib/api";
 import { useRouter } from "next/navigation";
-import { useState, useEffect } from "react";
+import { useState, useEffect, use } from "react";
 import { AddNodeCard } from "@/app/ui/createCard";
 import Link from "next/link";
 import { LoadingSpinner } from "@/app/ui/loadingSpinner";
@@ -60,11 +60,10 @@ import DeleteIcon from "@mui/icons-material/Delete";
 import SwapHorizIcon from "@mui/icons-material/SwapHoriz";
 import { FailoverDialog } from "@/app/ui/failoverDialog";
 
-export default function Shard({
-    params,
-}: {
-    params: { namespace: string; cluster: string; shard: string };
+export default function Shard(props: {
+    params: Promise<{ namespace: string; cluster: string; shard: string }>;
 }) {
+    const params = use(props.params);
     const { namespace, cluster, shard } = params;
     const [nodesData, setNodesData] = useState<any>(null);
     const [loading, setLoading] = useState<boolean>(true);
diff --git a/webui/src/app/namespaces/[namespace]/page.tsx 
b/webui/src/app/namespaces/[namespace]/page.tsx
index e1ede3d..4927802 100644
--- a/webui/src/app/namespaces/[namespace]/page.tsx
+++ b/webui/src/app/namespaces/[namespace]/page.tsx
@@ -45,7 +45,7 @@ import {
 } from "@/app/lib/api";
 import Link from "next/link";
 import { useRouter, notFound } from "next/navigation";
-import { useState, useEffect } from "react";
+import { useState, useEffect, use } from "react";
 import { LoadingSpinner } from "@/app/ui/loadingSpinner";
 import StorageIcon from "@mui/icons-material/Storage";
 import FolderIcon from "@mui/icons-material/Folder";
@@ -106,7 +106,8 @@ type SortOption =
     | "nodes-desc"
     | "nodes-asc";
 
-export default function Namespace({ params }: { params: { namespace: string } 
}) {
+export default function Namespace(props: { params: Promise<{ namespace: string 
}> }) {
+    const params = use(props.params);
     const [clusterData, setClusterData] = useState<ClusterData[]>([]);
     const [resourceCounts, setResourceCounts] = useState<ResourceCounts>({
         clusters: 0,
diff --git a/webui/src/app/page.tsx b/webui/src/app/page.tsx
index cc049e4..64e157e 100644
--- a/webui/src/app/page.tsx
+++ b/webui/src/app/page.tsx
@@ -60,7 +60,7 @@ export default function Home() {
     const [scrollY, setScrollY] = useState(0);
     const [cursorPosition, setCursorPosition] = useState({ x: 0, y: 0 });
     const [cursorVisible, setCursorVisible] = useState(true);
-    const requestRef = useRef<number>();
+    const requestRef = useRef<number>(undefined);
     const prevScrollY = useRef(0);
 
     const terminalRef = useRef({ lineIndex: 0, charIndex: 0 });
diff --git a/webui/src/middleware.ts b/webui/src/middleware.ts
new file mode 100644
index 0000000..f83dd6b
--- /dev/null
+++ b/webui/src/middleware.ts
@@ -0,0 +1,20 @@
+import { NextResponse } from "next/server";
+import type { NextRequest } from "next/server";
+
+export function middleware(req: NextRequest) {
+    const url = req.nextUrl.clone();
+
+    if (url.pathname.startsWith("/api/v1")) {
+        const host = process.env.KVCTL_API_HOST || "localhost:9379";
+        url.host = host;
+
+        return NextResponse.rewrite(url);
+    }
+
+    return NextResponse.next();
+}
+
+export const config = {
+    matcher: "/api/v1/:path*",
+    runtime: "nodejs",
+};
diff --git a/webui/tsconfig.json b/webui/tsconfig.json
index a125ba4..8ec9a25 100644
--- a/webui/tsconfig.json
+++ b/webui/tsconfig.json
@@ -15,9 +15,7 @@
  * KIND, either express or implied.  See the License for the
  * specific language governing permissions and limitations
  * under the License.
- */
-
-{
+ */ {
     "compilerOptions": {
         "lib": ["dom", "dom.iterable", "esnext"],
         "allowJs": true,
@@ -38,7 +36,8 @@
         ],
         "paths": {
             "@/*": ["./src/*"]
-        }
+        },
+        "target": "ES2017"
     },
     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
     "exclude": ["node_modules"]

Reply via email to