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 <[email protected]>
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],
+ });
+}