You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kvrocks.apache.org by tw...@apache.org on 2025/10/08 10:21:59 UTC
(kvrocks-controller) branch webui-middleware created (now c68ae3a)
This is an automated email from the ASF dual-hosted git repository.
twice pushed a change to branch webui-middleware
in repository https://gitbox.apache.org/repos/asf/kvrocks-controller.git
at c68ae3a feat(webui): use nextjs middleware to dynamically redirect API calls
This branch includes the following new commits:
new c68ae3a feat(webui): use nextjs middleware to dynamically redirect API calls
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(kvrocks-controller) 01/01: feat(webui): use nextjs middleware to dynamically redirect API calls
Posted by tw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
twice pushed a commit to branch webui-middleware
in repository https://gitbox.apache.org/repos/asf/kvrocks-controller.git
commit c68ae3a2d26efb5caff043743fad5fa76852d303
Author: PragmaTwice <tw...@apache.org>
AuthorDate: Wed Oct 8 18:21:13 2025 +0800
feat(webui): use nextjs middleware to dynamically redirect API calls
---
webui/next.config.mjs | 19 +----
webui/package.json | 83 ++++++++++++----------
.../[namespace]/clusters/[cluster]/page.tsx | 5 +-
.../[cluster]/shards/[shard]/nodes/[node]/page.tsx | 13 ++--
.../clusters/[cluster]/shards/[shard]/page.tsx | 13 ++--
webui/src/app/namespaces/[namespace]/page.tsx | 5 +-
webui/src/app/page.tsx | 2 +-
webui/src/middleware.ts | 20 ++++++
webui/tsconfig.json | 64 ++++++++++-------
9 files changed, 125 insertions(+), 99 deletions(-)
diff --git a/webui/next.config.mjs b/webui/next.config.mjs
index 4a75f77..920cfcd 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..79ade28 100644
--- a/webui/package.json
+++ b/webui/package.json
@@ -1,41 +1,46 @@
{
- "name": "kvrocks-controller-ui",
- "version": "0.1.0",
- "private": true,
- "scripts": {
- "dev": "next dev",
- "build": "next build",
- "start": "next start",
- "lint": "next lint"
- },
- "dependencies": {
- "@emotion/react": "^11.11.3",
- "@emotion/styled": "^11.11.0",
- "@fortawesome/fontawesome-svg-core": "^6.6.0",
- "@fortawesome/free-brands-svg-icons": "^6.6.0",
- "@fortawesome/free-solid-svg-icons": "^6.6.0",
- "@fortawesome/react-fontawesome": "^0.2.2",
- "@mui/icons-material": "^5.15.7",
- "@mui/material": "^5.15.5",
- "@types/js-yaml": "^4.0.9",
- "axios": "^1.6.7",
- "js-yaml": "^4.1.0",
- "next": "^14.2.29",
- "react": "^18",
- "react-dom": "^18"
- },
- "devDependencies": {
- "@types/node": "^20",
- "@types/react": "^18",
- "@types/react-dom": "^18",
- "autoprefixer": "^10.0.1",
- "eslint": "^8",
- "eslint-config-next": "14.1.0",
- "eslint-config-prettier": "^10.1.1",
- "postcss": "^8",
- "prettier": "^3.5.3",
- "prettier-plugin-tailwindcss": "^0.6.11",
- "tailwindcss": "^3.3.0",
- "typescript": "^5"
- }
+ "name": "kvrocks-controller-ui",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev --turbopack",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint",
+ "deploy": "next build && cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/"
+ },
+ "dependencies": {
+ "@emotion/react": "^11.11.3",
+ "@emotion/styled": "^11.11.0",
+ "@fortawesome/fontawesome-svg-core": "^6.6.0",
+ "@fortawesome/free-brands-svg-icons": "^6.6.0",
+ "@fortawesome/free-solid-svg-icons": "^6.6.0",
+ "@fortawesome/react-fontawesome": "^0.2.2",
+ "@mui/icons-material": "^5.15.7",
+ "@mui/material": "^5.15.5",
+ "@types/js-yaml": "^4.0.9",
+ "axios": "^1.6.7",
+ "js-yaml": "^4.1.0",
+ "next": "15.5.4",
+ "react": "19.2.0",
+ "react-dom": "19.2.0"
+ },
+ "devDependencies": {
+ "@types/node": "^20",
+ "@types/react": "19.2.2",
+ "@types/react-dom": "19.2.1",
+ "autoprefixer": "^10.0.1",
+ "eslint": "^8",
+ "eslint-config-next": "15.5.4",
+ "eslint-config-prettier": "^10.1.1",
+ "postcss": "^8",
+ "prettier": "^3.5.3",
+ "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..fd763cf 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,8 @@ 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..7cd6a6e 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,12 @@ 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..62a4e9f 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,12 @@ 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..150404b
--- /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..8a9fe32 100644
--- a/webui/tsconfig.json
+++ b/webui/tsconfig.json
@@ -15,31 +15,43 @@
* 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,
- "skipLibCheck": true,
- "strict": true,
- "noEmit": true,
- "esModuleInterop": true,
- "module": "esnext",
- "moduleResolution": "bundler",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "jsx": "preserve",
- "incremental": true,
- "plugins": [
- {
- "name": "next"
- }
- ],
- "paths": {
- "@/*": ["./src/*"]
- }
+ */{
+ "compilerOptions": {
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": [
+ "./src/*"
+ ]
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
- "exclude": ["node_modules"]
+ "target": "ES2017"
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
}