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

likeguo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu-dashboard.git


The following commit(s) were added to refs/heads/master by this push:
     new c7a1d978 add instance info (#524)
c7a1d978 is described below

commit c7a1d9786150cce303962fc9f20d8d930d35d75a
Author: aias00 <rok...@163.com>
AuthorDate: Fri Jul 4 11:03:06 2025 +0800

    add instance info (#524)
    
    * instance info
    
    * instance info
---
 src/common/menu.js                  |   5 +
 src/common/router.js                |   7 +
 src/locales/en-US.json              |   7 +
 src/locales/zh-CN.json              |   7 +
 src/models/instance.js              |  79 +++++++++
 src/routes/System/Instance/index.js | 316 ++++++++++++++++++++++++++++++++++++
 src/services/api.js                 |  38 +++++
 7 files changed, 459 insertions(+)

diff --git a/src/common/menu.js b/src/common/menu.js
index 9c34d834..0f1ce8bd 100644
--- a/src/common/menu.js
+++ b/src/common/menu.js
@@ -100,6 +100,11 @@ export const menuData = [
         path: "dict",
         locale: "SHENYU.MENU.SYSTEM.MANAGMENT.DICTIONARY",
       },
+      {
+        name: getIntlContent("SHENYU.MENU.SYSTEM.MANAGMENT.INSTANCE"),
+        path: "instance",
+        locale: "SHENYU.MENU.SYSTEM.MANAGMENT.INSTANCE",
+      },
     ],
   },
   {
diff --git a/src/common/router.js b/src/common/router.js
index 8c178f55..3847aa11 100644
--- a/src/common/router.js
+++ b/src/common/router.js
@@ -168,6 +168,13 @@ export const getRouterData = (app) => {
         () => import("../routes/System/Plugin"),
       ),
     },
+    "/config/instance": {
+      component: dynamicWrapper(
+        app,
+        ["instance"],
+        () => import("../routes/System/Instance"),
+      ),
+    },
     "/config/namespacePlugin": {
       component: dynamicWrapper(
         app,
diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index 6cb6342b..4a436f44 100644
--- a/src/locales/en-US.json
+++ b/src/locales/en-US.json
@@ -77,6 +77,7 @@
   "SHENYU.MENU.SYSTEM.MANAGMENT.AUTHEN": "Authentication",
   "SHENYU.MENU.SYSTEM.MANAGMENT.METADATA": "Metadata",
   "SHENYU.MENU.SYSTEM.MANAGMENT.DICTIONARY": "Dictionary",
+  "SHENYU.MENU.SYSTEM.MANAGMENT.INSTANCE": "Instance",
   "SHENYU.MENU.SYSTEM.MANAGMENT.NAMESPACE": "Namespace",
   "SHENYU.MENU.CONFIG.MANAGMENT": "BasicConfig",
   "SHENYU.PLUGIN.SELECTOR.LIST.TITLE": "SelectorList",
@@ -336,6 +337,12 @@
   "SHENYU.BUTTON.DATA.PERMISSION.CONFIG": "ConfigureDataPermission",
   "SHENYU.MESSAGE.SESSION.INVALID": "Session is invalid",
   "SHENYU.MESSAGE.SESSION.RELOGIN": "Please login in again",
+  "SHENYU.INSTANCE.IP": "Instance IP",
+  "SHENYU.INSTANCE.PORT": "Instance Port",
+  "SHENYU.INSTANCE.INFO": "Instance Info",
+  "SHENYU.INSTANCE.SELECT.TYPE": "Instance Type",
+  "SHENYU.INSTANCE.SELECT.TYPE.BOOTSTRAP": "Bootstrap",
+  "SHENYU.INSTANCE.SELECT.TYPE.CLIENT": "Client",
   "SHENYU.PLUGIN.SELECT.STATUS": "Select Status",
   "SHENYU.PLUGIN.REQUEST.HEADER.KEY": "Header Key",
   "SHENYU.PLUGIN.REQUEST.HEADER.VALUE": "Header Value",
diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json
index ed08cd83..dc3e33f5 100644
--- a/src/locales/zh-CN.json
+++ b/src/locales/zh-CN.json
@@ -78,6 +78,7 @@
   "SHENYU.MENU.SYSTEM.MANAGMENT.AUTHEN": "认证管理",
   "SHENYU.MENU.SYSTEM.MANAGMENT.METADATA": "元数据管理",
   "SHENYU.MENU.SYSTEM.MANAGMENT.DICTIONARY": "字典管理",
+  "SHENYU.MENU.SYSTEM.MANAGMENT.INSTANCE": "实例管理",
   "SHENYU.MENU.CONFIG.MANAGMENT": "基础配置",
   "SHENYU.MENU.SYSTEM.MANAGMENT.NAMESPACE": "命名空间管理",
   "SHENYU.PLUGIN.SELECTOR.LIST.TITLE": "选择器列表",
@@ -340,6 +341,12 @@
   "SHENYU.BUTTON.DATA.PERMISSION.CONFIG": "配置数据权限",
   "SHENYU.MESSAGE.SESSION.INVALID": "会话超时",
   "SHENYU.MESSAGE.SESSION.RELOGIN": "会话超时,请重新登录",
+  "SHENYU.INSTANCE.IP": "实例IP",
+  "SHENYU.INSTANCE.PORT": "实例端口",
+  "SHENYU.INSTANCE.INFO": "实例信息",
+  "SHENYU.INSTANCE.SELECT.TYPE": "实例类型",
+  "SHENYU.INSTANCE.SELECT.TYPE.BOOTSTRAP": "网关实例",
+  "SHENYU.INSTANCE.SELECT.TYPE.CLIENT": "客户端实例",
   "SHENYU.PLUGIN.SELECT.STATUS": "选择状态",
   "SHENYU.PLUGIN.REQUEST.HEADER.KEY": "Header Key",
   "SHENYU.PLUGIN.REQUEST.HEADER.VALUE": "Header Value",
diff --git a/src/models/instance.js b/src/models/instance.js
new file mode 100644
index 00000000..89a04937
--- /dev/null
+++ b/src/models/instance.js
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { getInstancesByNamespace, findInstance } from "../services/api";
+
+export default {
+  namespace: "instance",
+
+  state: {
+    instanceList: [],
+    total: 0,
+  },
+
+  effects: {
+    *fetch(params, { call, put }) {
+      const { payload } = params;
+      const json = yield call(getInstancesByNamespace, payload);
+      if (json.code === 200) {
+        let { page, dataList } = json.data;
+        dataList = dataList.map((item) => {
+          item.key = item.id;
+          return item;
+        });
+        yield put({
+          type: "saveInstances",
+          payload: {
+            total: page.totalCount,
+            dataList,
+          },
+        });
+      }
+    },
+    *fetchItem(params, { call }) {
+      const { payload, callback } = params;
+      const json = yield call(findInstance, payload);
+      if (json.code === 200) {
+        const instance = json.data;
+        callback(instance);
+      }
+    },
+    *reload(params, { put }) {
+      const { fetchValue } = params;
+      const { name, currentPage, instanceType, instanceIp, pageSize } =
+        fetchValue;
+      const payload = {
+        name,
+        instanceType,
+        instanceIp,
+        currentPage,
+        pageSize,
+      };
+      yield put({ type: "fetch", payload });
+    },
+  },
+
+  reducers: {
+    saveInstances(state, { payload }) {
+      return {
+        ...state,
+        instanceList: payload.dataList,
+        total: payload.total,
+      };
+    },
+  },
+};
diff --git a/src/routes/System/Instance/index.js 
b/src/routes/System/Instance/index.js
new file mode 100644
index 00000000..e368a7e6
--- /dev/null
+++ b/src/routes/System/Instance/index.js
@@ -0,0 +1,316 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { Component } from "react";
+import { Button, Input, Popover, Select, Table, Tag, Typography } from "antd";
+import { connect } from "dva";
+import { resizableComponents } from "../../../utils/resizable";
+import { getCurrentLocale, getIntlContent } from "../../../utils/IntlUtils";
+import AuthButton from "../../../utils/AuthButton";
+
+const { Text } = Typography;
+
+const { Option } = Select;
+
+@connect(({ instance, loading, global }) => ({
+  instance,
+  language: global.language,
+  currentNamespaceId: global.currentNamespaceId,
+  loading: loading.effects["instance/fetch"],
+}))
+export default class Instance extends Component {
+  components = resizableComponents;
+
+  constructor(props) {
+    super(props);
+    this.state = {
+      currentPage: 1,
+      pageSize: 12,
+      selectedRowKeys: [],
+      instanceIp: "",
+      instanceType: null,
+      localeName: window.sessionStorage.getItem("locale")
+        ? window.sessionStorage.getItem("locale")
+        : "en-US",
+      columns: [],
+    };
+  }
+
+  componentDidMount() {
+    this.query();
+    this.initInstanceColumns();
+  }
+
+  componentDidUpdate(prevProps) {
+    const { language, currentNamespaceId } = this.props;
+    const { localeName } = this.state;
+    if (language !== localeName) {
+      this.initInstanceColumns();
+      this.changeLocale(language);
+    }
+    if (prevProps.currentNamespaceId !== currentNamespaceId) {
+      this.query();
+    }
+  }
+
+  handleResize =
+    (index) =>
+    (e, { size }) => {
+      this.setState(({ columns }) => {
+        const nextColumns = [...columns];
+        nextColumns[index] = {
+          ...nextColumns[index],
+          width: size.width,
+        };
+        return { columns: nextColumns };
+      });
+    };
+
+  onSelectChange = (selectedRowKeys) => {
+    this.setState({ selectedRowKeys });
+  };
+
+  currentQueryPayload = (override) => {
+    const { instanceIp, instanceType, currentPage, pageSize } = this.state;
+    const { currentNamespaceId } = this.props;
+    return {
+      instanceIp,
+      instanceType,
+      namespaceId: currentNamespaceId,
+      currentPage,
+      pageSize,
+      ...override,
+    };
+  };
+
+  query = () => {
+    const { dispatch } = this.props;
+    dispatch({
+      type: "instance/fetch",
+      payload: this.currentQueryPayload(),
+    });
+  };
+
+  pageOnchange = (page) => {
+    this.setState({ currentPage: page }, this.query);
+  };
+
+  onShowSizeChange = (currentPage, pageSize) => {
+    this.setState({ currentPage: 1, pageSize }, this.query);
+  };
+
+  instanceIpOnchange = (e) => {
+    this.setState({ instanceIp: e.target.value }, this.query);
+  };
+
+  instanceTypeOnchange = (e) => {
+    this.setState({ instanceType: e }, this.query);
+  };
+
+  searchClick = () => {
+    this.setState({ currentPage: 1 }, this.query);
+  };
+
+  changeLocale(locale) {
+    this.setState({
+      localeName: locale,
+    });
+    getCurrentLocale(this.state.localeName);
+  }
+
+  initInstanceColumns() {
+    this.setState({
+      columns: [
+        {
+          align: "center",
+          title: getIntlContent("SHENYU.INSTANCE.IP"),
+          dataIndex: "instanceIp",
+          key: "instanceIp",
+          ellipsis: true,
+          width: 120,
+          render: (text, record) => {
+            return record.instanceIp ? (
+              <div
+                style={{
+                  color: "#1890ff",
+                  fontWeight: "bold",
+                }}
+              >
+                {text || "----"}
+              </div>
+            ) : (
+              <div style={{ color: "#260033", fontWeight: "bold" }}>
+                {text || "----"}
+              </div>
+            );
+          },
+        },
+        {
+          align: "center",
+          title: getIntlContent("SHENYU.INSTANCE.PORT"),
+          dataIndex: "instancePort",
+          key: "instancePort",
+          ellipsis: true,
+          width: 120,
+          render: (text, record) => {
+            return record.instancePort ? (
+              <div
+                style={{
+                  color: "#1890ff",
+                  fontWeight: "bold",
+                }}
+              >
+                {text || "----"}
+              </div>
+            ) : (
+              <div style={{ color: "#260033", fontWeight: "bold" }}>
+                {text || "----"}
+              </div>
+            );
+          },
+        },
+        {
+          align: "center",
+          title: getIntlContent("SHENYU.INSTANCE.SELECT.TYPE"),
+          dataIndex: "instanceType",
+          ellipsis: true,
+          key: "instanceType",
+          width: 120,
+          sorter: (a, b) => (a.instanceType > b.instanceType ? 1 : -1),
+          render: (text) => {
+            return <div style={{ color: "#1f640a" }}>{text || "----"}</div>;
+          },
+        },
+        {
+          align: "center",
+          title: getIntlContent("SHENYU.INSTANCE.INFO"),
+          dataIndex: "instanceInfo",
+          key: "instanceInfo",
+          ellipsis: true,
+          render: (text, record) => {
+            const tag = (
+              <div>
+                <Tag color="#9dd3a8">{record.instanceType}</Tag>
+                <Tag color="#CCCC99">{record.instanceIp}</Tag>
+                <Tag color="#DCDC17">{record.instancePort}</Tag>
+              </div>
+            );
+            const t = JSON.stringify(
+              JSON.parse(text !== null && text.length > 0 ? text : "{}"),
+              null,
+              4,
+            );
+            const content = (
+              <div>
+                <Text 
type="secondary">{`${getIntlContent("SHENYU.SYSTEM.CREATETIME")}: 
${record.dateCreated ? new Date(record.dateCreated).toLocaleString() : 
"----"}`}</Text>
+                <br />
+                <Text 
type="secondary">{`${getIntlContent("SHENYU.SYSTEM.UPDATETIME")}: 
${record.dateUpdated ? new Date(record.dateUpdated).toLocaleString() : 
"----"}`}</Text>
+                <hr />
+                <div style={{ fontWeight: "bold" }}>
+                  <pre>
+                    <code>{t}</code>
+                  </pre>
+                </div>
+              </div>
+            );
+            return (
+              <Popover content={content} title={tag}>
+                <div>{text || "----"}</div>
+              </Popover>
+            );
+          },
+        },
+      ],
+    });
+  }
+
+  render() {
+    const { instance, loading } = this.props;
+    const { instanceList, total } = instance;
+    const { currentPage, pageSize, selectedRowKeys, instanceIp, instanceType } 
=
+      this.state;
+    const columns = this.state.columns.map((col, index) => ({
+      ...col,
+      onHeaderCell: (column) => ({
+        width: column.width,
+        onResize: this.handleResize(index),
+      }),
+    }));
+    const rowSelection = {
+      selectedRowKeys,
+      onChange: this.onSelectChange,
+    };
+
+    return (
+      <div className="plug-content-wrap">
+        <div style={{ display: "flex" }}>
+          <Input
+            allowClear
+            value={instanceIp}
+            onChange={this.instanceIpOnchange}
+            placeholder={getIntlContent("SHENYU.INSTANCE.IP")}
+            style={{ width: 240 }}
+          />
+          <Select
+            value={instanceType != null ? instanceType : undefined}
+            onChange={this.instanceTypeOnchange}
+            placeholder={getIntlContent("SHENYU.INSTANCE.SELECT.TYPE")}
+            style={{ width: 150, marginLeft: 20 }}
+            allowClear
+          >
+            <Option value="bootstrap">
+              {getIntlContent("SHENYU.INSTANCE.SELECT.TYPE.BOOTSTRAP")}
+            </Option>
+            <Option value="client">
+              {getIntlContent("SHENYU.INSTANCE.SELECT.TYPE.CLIENT")}
+            </Option>
+          </Select>
+          <AuthButton perms="system:instance:list">
+            <Button
+              type="primary"
+              style={{ marginLeft: 20 }}
+              onClick={this.searchClick}
+            >
+              {getIntlContent("SHENYU.SYSTEM.SEARCH")}
+            </Button>
+          </AuthButton>
+        </div>
+
+        <Table
+          size="small"
+          components={this.components}
+          style={{ marginTop: 30 }}
+          bordered
+          loading={loading}
+          columns={columns}
+          dataSource={instanceList}
+          rowSelection={rowSelection}
+          pagination={{
+            total,
+            showTotal: (showTotal) => `${showTotal}`,
+            showSizeChanger: true,
+            pageSizeOptions: ["12", "20", "50", "100"],
+            current: currentPage,
+            pageSize,
+            onShowSizeChange: this.onShowSizeChange,
+            onChange: this.pageOnchange,
+          }}
+        />
+      </div>
+    );
+  }
+}
diff --git a/src/services/api.js b/src/services/api.js
index 92b6e5b0..2b6ad181 100644
--- a/src/services/api.js
+++ b/src/services/api.js
@@ -1355,3 +1355,41 @@ export async function asyncNamespacePlugin(params) {
     body: params,
   });
 }
+
+/* getInstancesByNamespace */
+export async function getInstancesByNamespace(params) {
+  return request(`${baseUrl}/instance?${stringify(params)}`, {
+    method: `GET`,
+  });
+}
+
+/* findInstance */
+export async function findInstance(params) {
+  return request(`${baseUrl}/instance/${params.id}`, {
+    method: `GET`,
+  });
+}
+
+/* addInstance */
+export async function addInstance(params) {
+  return request(`${baseUrl}/instance`, {
+    method: `POST`,
+    body: params,
+  });
+}
+
+/* updateInstance */
+export async function updateInstance(params) {
+  return request(`${baseUrl}/instance/${params.id}`, {
+    method: `PUT`,
+    body: params,
+  });
+}
+
+/* deleteInstance */
+export async function deleteInstance(params) {
+  return request(`${baseUrl}/instance/batch`, {
+    method: `DELETE`,
+    body: [...params.list],
+  });
+}

Reply via email to