You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by ba...@apache.org on 2022/05/06 02:05:38 UTC

[apisix-dashboard] branch master updated: feat: add page reload judgment (#2370)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 30fa852c feat: add page reload judgment (#2370)
30fa852c is described below

commit 30fa852ca0da1b6d5a8c90ba899e300b52d2b864
Author: oil欧呦 <22...@qq.com>
AuthorDate: Fri May 6 10:05:33 2022 +0800

    feat: add page reload judgment (#2370)
---
 .../consumer/table-auto-jump-when-no-data.spec.js  | 82 +++++++++++++++++++
 ...js => create_and_edit_and_delete_proto.spec.js} |  3 +-
 .../proto/table-auto-jump-when-no-data.spec.js     | 68 ++++++++++++++++
 .../route/table-auto-jump-when-no-data.spec.js     | 94 ++++++++++++++++++++++
 .../service/table-auto-jump-when-no-data.spec.js   | 85 +++++++++++++++++++
 .../upstream/table-auto-jump-when-no-data.spec.js  | 83 +++++++++++++++++++
 web/cypress/support/commands.js                    | 14 ++++
 web/src/hooks/usePagination.ts                     | 14 +++-
 web/src/pages/Consumer/List.tsx                    |  5 +-
 web/src/pages/Plugin/List.tsx                      |  4 +-
 web/src/pages/Proto/List.tsx                       | 18 ++---
 web/src/pages/Proto/locales/en-US.ts               |  8 +-
 web/src/pages/Proto/locales/zh-CN.ts               |  9 ++-
 web/src/pages/Route/List.tsx                       | 80 +++++++++---------
 web/src/pages/SSL/List.tsx                         |  5 +-
 web/src/pages/Service/List.tsx                     |  5 +-
 web/src/pages/Upstream/List.tsx                    |  5 +-
 17 files changed, 514 insertions(+), 68 deletions(-)

diff --git a/web/cypress/integration/consumer/table-auto-jump-when-no-data.spec.js b/web/cypress/integration/consumer/table-auto-jump-when-no-data.spec.js
new file mode 100644
index 00000000..209a540c
--- /dev/null
+++ b/web/cypress/integration/consumer/table-auto-jump-when-no-data.spec.js
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+context('Table Auto Jump When No Data', () => {
+  const selector = {
+    username: '#username',
+    page_item: '.ant-pagination-item-2',
+    deleteAlert: '.ant-modal-body',
+    notificationCloseIcon: '.ant-notification-close-icon',
+    notification: '.ant-notification-notice-message',
+    table_row: '.ant-table-row',
+    pluginCard: '.ant-card',
+    drawer: '.ant-drawer-content',
+    monacoScroll: '.monaco-scrollable-element',
+    monacoViewZones: '.view-zones',
+    disabledSwitcher: '#disable',
+    popoper: '.ant-popover',
+    popoprerHiden: '.ant-popover-hidden',
+  };
+
+  const data = {
+    consumerName: 'test_consumer',
+    createConsumerSuccess: 'Create Consumer Successfully',
+    deleteConsumerSuccess: 'Delete Consumer Successfully',
+  };
+
+  before(() => {
+    cy.login().then(() => {
+      Array.from({ length: 11 }).forEach((value, key) => {
+        const payload = {
+          username: data.consumerName + key,
+          plugins: {
+            'key-auth': {
+              key: 'test',
+              disable: false,
+            },
+          },
+        };
+        cy.requestWithToken({ method: 'PUT', payload, url: '/apisix/admin/consumers' });
+      });
+    });
+  });
+
+  it('should delete last data and jump to first page', () => {
+    cy.visit('/');
+    cy.contains('Consumer').click();
+    cy.get(selector.page_item).click();
+    cy.wait(1000);
+    cy.contains('Delete').click();
+    cy.get(selector.popoper)
+      .not(selector.popoprerHiden)
+      .contains('Confirm')
+      .should('be.visible')
+      .click();
+    cy.get(selector.notification).should('contain', data.deleteConsumerSuccess);
+    cy.get(selector.notificationCloseIcon).click();
+    cy.url().should('contains', '/consumer/list?page=1&pageSize=10');
+    cy.get(selector.table_row).should((consumer) => {
+      expect(consumer).to.have.length(10);
+    });
+    cy.get(`.ant-table-cell:contains(${data.consumerName})`).each((elem) => {
+      cy.requestWithToken({
+        method: 'DELETE',
+        url: `/apisix/admin/consumers/${elem.text()}`,
+      });
+    });
+  });
+});
diff --git a/web/cypress/integration/proto/create_and_edit_and_delete_proto.spce.js b/web/cypress/integration/proto/create_and_edit_and_delete_proto.spec.js
similarity index 97%
rename from web/cypress/integration/proto/create_and_edit_and_delete_proto.spce.js
rename to web/cypress/integration/proto/create_and_edit_and_delete_proto.spec.js
index 7443aa86..bad76862 100644
--- a/web/cypress/integration/proto/create_and_edit_and_delete_proto.spce.js
+++ b/web/cypress/integration/proto/create_and_edit_and_delete_proto.spec.js
@@ -14,7 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/* eslint-disable no-undef */
 
 context('Create and Delete Proto', () => {
   const selector = {
@@ -41,7 +40,7 @@ optional string email = 3;
 }`,
     createProtoSuccess: 'Create proto Successfully',
     configureProtoSuccess: 'Configure proto Successfully',
-    deleteProtoSuccess: 'Delete Upstream Successfully',
+    deleteProtoSuccess: 'Delete proto Successfully',
   };
 
   beforeEach(() => {
diff --git a/web/cypress/integration/proto/table-auto-jump-when-no-data.spec.js b/web/cypress/integration/proto/table-auto-jump-when-no-data.spec.js
new file mode 100644
index 00000000..d81d862a
--- /dev/null
+++ b/web/cypress/integration/proto/table-auto-jump-when-no-data.spec.js
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+context('Batch Create Proto And Delete Proto', () => {
+  const selector = {
+    id: '#id',
+    content: '.view-lines',
+    page_item: '.ant-pagination-item-2',
+    draw: '.ant-drawer-content',
+    deleteAlert: '.ant-modal-body',
+    notificationCloseIcon: '.ant-notification-close-icon',
+    notification: '.ant-notification-notice-message',
+    table_row: '.ant-table-row',
+  };
+
+  const data = {
+    createProtoSuccess: 'Create proto Successfully',
+    deleteProtoSuccess: 'Delete proto Successfully',
+  };
+
+  before(() => {
+    cy.login().then(() => {
+      Array.from({ length: 11 }).forEach(async (value, key) => {
+        const payload = {
+          content: 'test',
+          desc: '',
+          id: `protoId${key}`,
+        };
+        cy.requestWithToken({ method: 'POST', payload, url: '/apisix/admin/proto' });
+      });
+    });
+  });
+
+  it('should delete last data and jump to first page', () => {
+    cy.visit('/');
+    cy.contains('Protocol Buffers').click();
+    cy.get(selector.page_item).click();
+    cy.wait(1000);
+    cy.contains('Delete').click();
+    cy.contains('button', 'Confirm').click();
+    cy.get(selector.notification).should('contain', data.deleteProtoSuccess);
+    cy.get(selector.notificationCloseIcon).click();
+    cy.url().should('contains', '/proto/list?page=1&pageSize=10');
+    cy.get(selector.table_row).should((proto) => {
+      expect(proto).to.have.length(10);
+    });
+    cy.get('.ant-table-cell:contains(protoId)').each((elem) => {
+      cy.requestWithToken({
+        method: 'DELETE',
+        url: `/apisix/admin/proto/${elem.text()}`,
+      });
+    });
+  });
+});
diff --git a/web/cypress/integration/route/table-auto-jump-when-no-data.spec.js b/web/cypress/integration/route/table-auto-jump-when-no-data.spec.js
new file mode 100644
index 00000000..5f0a1256
--- /dev/null
+++ b/web/cypress/integration/route/table-auto-jump-when-no-data.spec.js
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+context('Table Auto Jump When No Data', () => {
+  const selector = {
+    name: '#name',
+    nodes_0_host: '#submitNodes_0_host',
+    page_item: '.ant-pagination-item-2',
+    deleteAlert: '.ant-modal-body',
+    notificationCloseIcon: '.ant-notification-close-icon',
+    notification: '.ant-notification-notice-message',
+    table_row: '.ant-table-row',
+  };
+
+  const data = {
+    submitSuccess: 'Submit Successfully',
+    deleteRouteSuccess: 'Delete Route Successfully',
+  };
+
+  before(() => {
+    cy.login().then(() => {
+      Array.from({ length: 11 }).forEach((value, key) => {
+        const payload = {
+          methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'CONNECT', 'TRACE'],
+          priority: 0,
+          name: `routeName${key}`,
+          desc: '',
+          status: 1,
+          labels: {},
+          uri: '/*',
+          upstream: {
+            type: 'roundrobin',
+            pass_host: 'pass',
+            scheme: 'http',
+            timeout: {
+              connect: 6,
+              send: 6,
+              read: 6,
+            },
+            keepalive_pool: {
+              size: 320,
+              idle_timeout: 60,
+              requests: 1000,
+            },
+            nodes: {
+              '127.0.0.1': 1,
+            },
+          },
+        };
+        cy.requestWithToken({ method: 'POST', payload, url: '/apisix/admin/routes' });
+      });
+    });
+  });
+
+  it('should delete last data and jump to first page', () => {
+    cy.visit('/');
+    cy.contains('Route').click();
+    cy.get(selector.page_item).click();
+    cy.wait(1000);
+    cy.contains('routeName').siblings().contains('More').click();
+    cy.contains('Delete').click();
+    cy.get(selector.deleteAlert)
+      .should('be.visible')
+      .within(() => {
+        cy.contains('OK').click();
+      });
+    cy.get(selector.notification).should('contain', data.deleteRouteSuccess);
+    cy.get(selector.notificationCloseIcon).click();
+    cy.url().should('contains', '/routes/list?page=1&pageSize=10');
+    cy.get(selector.table_row).should((route) => {
+      expect(route).to.have.length(10);
+    });
+    cy.get('.ant-table-cell:contains(routeName)').each((elem) => {
+      cy.requestWithToken({
+        method: 'DELETE',
+        url: `/apisix/admin/routes/${elem.next().text()}`,
+      });
+    });
+  });
+});
diff --git a/web/cypress/integration/service/table-auto-jump-when-no-data.spec.js b/web/cypress/integration/service/table-auto-jump-when-no-data.spec.js
new file mode 100644
index 00000000..98f9eb80
--- /dev/null
+++ b/web/cypress/integration/service/table-auto-jump-when-no-data.spec.js
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+/* eslint-disable no-undef */
+
+context('Table Auto Jump When No Data', () => {
+  const selector = {
+    name: '#name',
+    nodes_0_host: '#submitNodes_0_host',
+    page_item: '.ant-pagination-item-2',
+    deleteAlert: '.ant-modal-body',
+    notificationCloseIcon: '.ant-notification-close-icon',
+    notification: '.ant-notification-notice-message',
+    table_row: '.ant-table-row',
+  };
+
+  const data = {
+    createServiceSuccess: 'Create Service Successfully',
+    deleteServiceSuccess: 'Delete Service Successfully',
+  };
+
+  beforeEach(() => {
+    cy.login().then(() => {
+      Array.from({ length: 11 }).forEach((value, key) => {
+        const payload = {
+          name: `serviceName${key}`,
+          plugins: {},
+          upstream: {
+            type: 'roundrobin',
+            pass_host: 'pass',
+            scheme: 'http',
+            timeout: {
+              connect: 6,
+              send: 6,
+              read: 6,
+            },
+            keepalive_pool: {
+              size: 320,
+              idle_timeout: 60,
+              requests: 1000,
+            },
+            nodes: {
+              '127.0.0.1': 1,
+            },
+          },
+        };
+        cy.requestWithToken({ method: 'POST', payload, url: '/apisix/admin/services' });
+      });
+    });
+  });
+
+  it('should delete last data and jump to first page', () => {
+    cy.visit('/');
+    cy.contains('Service').click();
+    cy.get(selector.page_item).click();
+    cy.wait(1000);
+    cy.contains('serviceName').siblings().contains('Delete').click();
+    cy.contains('button', 'Confirm').click();
+    cy.get(selector.notification).should('contain', data.deleteServiceSuccess);
+    cy.get(selector.notificationCloseIcon).click();
+    cy.url().should('contains', '/service/list?page=1&pageSize=10');
+    cy.get(selector.table_row).should((service) => {
+      expect(service).to.have.length(10);
+    });
+    cy.get('.ant-table-cell:contains(serviceName)').each((elem) => {
+      cy.requestWithToken({
+        method: 'DELETE',
+        url: `/apisix/admin/services/${elem.prev().text()}`,
+      });
+    });
+  });
+});
diff --git a/web/cypress/integration/upstream/table-auto-jump-when-no-data.spec.js b/web/cypress/integration/upstream/table-auto-jump-when-no-data.spec.js
new file mode 100644
index 00000000..bc803713
--- /dev/null
+++ b/web/cypress/integration/upstream/table-auto-jump-when-no-data.spec.js
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+/* eslint-disable no-undef */
+
+context('Table Auto Jump When No Data', () => {
+  const selector = {
+    id: '#id',
+    name: '#name',
+    nodes_0_host: '#submitNodes_0_host',
+    page_item: '.ant-pagination-item-2',
+    deleteAlert: '.ant-modal-body',
+    notificationCloseIcon: '.ant-notification-close-icon',
+    notification: '.ant-notification-notice-message',
+    table_row: ' .ant-table-row',
+  };
+
+  const data = {
+    createUpstreamSuccess: 'Create Upstream Successfully',
+    deleteUpstreamSuccess: 'Delete Upstream Successfully',
+  };
+
+  beforeEach(() => {
+    cy.login().then(() => {
+      Array.from({ length: 11 }).forEach((value, key) => {
+        const payload = {
+          name: `upstreamName${key}`,
+          type: 'roundrobin',
+          pass_host: 'pass',
+          scheme: 'http',
+          timeout: {
+            connect: 6,
+            send: 6,
+            read: 6,
+          },
+          keepalive_pool: {
+            size: 320,
+            idle_timeout: 60,
+            requests: 1000,
+          },
+          nodes: {
+            '127.0.0.1': 1,
+          },
+        };
+        cy.requestWithToken({ method: 'POST', payload, url: `/apisix/admin/upstreams` });
+      });
+    });
+  });
+
+  it('should delete the upstream', () => {
+    cy.visit('/');
+    cy.contains('Upstream').click();
+    cy.get(selector.page_item).click();
+    cy.wait(1000);
+    cy.contains('upstreamName').siblings().contains('Delete').click();
+    cy.contains('button', 'Confirm').click();
+    cy.get(selector.notification).should('contain', data.deleteUpstreamSuccess);
+    cy.get('.ant-notification-close-x').click();
+    cy.url().should('contains', '/upstream/list?page=1&pageSize=10');
+    cy.get(selector.table_row).should((upstream) => {
+      expect(upstream).to.have.length(10);
+    });
+    cy.get('.ant-table-cell:contains(upstreamName)').each((elem) => {
+      cy.requestWithToken({
+        method: 'DELETE',
+        url: `/apisix/admin/upstreams/${elem.prev().text()}`,
+      });
+    });
+  });
+});
diff --git a/web/cypress/support/commands.js b/web/cypress/support/commands.js
index 990288a8..0a7ae8c7 100644
--- a/web/cypress/support/commands.js
+++ b/web/cypress/support/commands.js
@@ -143,3 +143,17 @@ Cypress.Commands.add('configurePlugin', ({ name, cases }) => {
     }
   });
 });
+
+Cypress.Commands.add('requestWithToken', ({ method, url, payload }) => {
+  const { SERVE_ENV = 'dev' } = Cypress.env();
+  // Make sure the request is synchronous
+  cy.request({
+    method,
+    url: defaultSettings.serveUrlMap[SERVE_ENV] + url,
+    body: payload,
+    headers: { Authorization: localStorage.getItem('token') },
+  }).then((res) => {
+    expect(res.body.code).to.equal(0);
+    return res;
+  });
+});
diff --git a/web/src/hooks/usePagination.ts b/web/src/hooks/usePagination.ts
index 41938c70..c6b1a02d 100644
--- a/web/src/hooks/usePagination.ts
+++ b/web/src/hooks/usePagination.ts
@@ -17,6 +17,9 @@
 import { useEffect, useState } from 'react';
 import { useLocation, history } from 'umi';
 import querystring from 'query-string';
+import type { PageInfo } from '@ant-design/pro-table/lib/typing';
+import type { ActionType } from '@ant-design/pro-table';
+import type { MutableRefObject } from 'react';
 
 export default function usePagination() {
   const location = useLocation();
@@ -30,5 +33,14 @@ export default function usePagination() {
     history.replace(`${location.pathname}?page=${page}&pageSize=${pageSize}`);
   };
 
-  return { paginationConfig, savePageList };
+  const checkPageList = (ref: MutableRefObject<ActionType | undefined>) => {
+    const { current, pageSize, total } = ref.current?.pageInfo as PageInfo;
+    if (current > pageSize / total && current > 1) {
+      savePageList(paginationConfig.current - 1, paginationConfig.pageSize);
+    } else {
+      ref.current?.reload();
+    }
+  };
+
+  return { paginationConfig, savePageList, checkPageList };
 }
diff --git a/web/src/pages/Consumer/List.tsx b/web/src/pages/Consumer/List.tsx
index 9bb0f0a9..bcd27e8a 100644
--- a/web/src/pages/Consumer/List.tsx
+++ b/web/src/pages/Consumer/List.tsx
@@ -37,7 +37,7 @@ const Page: React.FC = () => {
   const [rawData, setRawData] = useState<Record<string, any>>({});
   const [id, setId] = useState('');
   const [editorMode, setEditorMode] = useState<'create' | 'update'>('create');
-  const { paginationConfig, savePageList } = usePagination();
+  const { paginationConfig, savePageList, checkPageList } = usePagination();
 
   const columns: ProColumns<ConsumerModule.ResEntity>[] = [
     {
@@ -95,8 +95,7 @@ const Page: React.FC = () => {
                 notification.success({
                   message: `${formatMessage({ id: 'component.global.delete.consumer.success' })}`,
                 });
-                /* eslint-disable no-unused-expressions */
-                ref.current?.reload();
+                checkPageList(ref);
               });
             }}
           >
diff --git a/web/src/pages/Plugin/List.tsx b/web/src/pages/Plugin/List.tsx
index c9b4a933..7d9be5db 100644
--- a/web/src/pages/Plugin/List.tsx
+++ b/web/src/pages/Plugin/List.tsx
@@ -35,7 +35,7 @@ const Page: React.FC = () => {
   const [initialData, setInitialData] = useState({});
   const [pluginList, setPluginList] = useState<PluginComponent.Meta[]>([]);
   const [name, setName] = useState('');
-  const { paginationConfig, savePageList } = usePagination();
+  const { paginationConfig, savePageList, checkPageList } = usePagination();
 
   useEffect(() => {
     fetchPluginList().then(setPluginList);
@@ -85,7 +85,7 @@ const Page: React.FC = () => {
                       id: 'menu.plugin',
                     })} ${formatMessage({ id: 'component.status.success' })}`,
                   });
-                  ref.current?.reload();
+                  checkPageList(ref);
                   setInitialData(plugins);
                   setName('');
                 });
diff --git a/web/src/pages/Proto/List.tsx b/web/src/pages/Proto/List.tsx
index b4c80da3..07f344e8 100755
--- a/web/src/pages/Proto/List.tsx
+++ b/web/src/pages/Proto/List.tsx
@@ -16,7 +16,8 @@
  */
 import React, { useRef, useState } from 'react';
 import { PageHeaderWrapper } from '@ant-design/pro-layout';
-import ProTable, { ProColumns, ActionType } from '@ant-design/pro-table';
+import type { ProColumns, ActionType } from '@ant-design/pro-table';
+import ProTable from '@ant-design/pro-table';
 import ProtoDrawer from './components/ProtoDrawer';
 import { Button, notification, Popconfirm, Space } from 'antd';
 import { useIntl } from 'umi';
@@ -31,7 +32,7 @@ const Page: React.FC = () => {
   const ref = useRef<ActionType>();
   const { formatMessage } = useIntl();
   const [drawerVisible, setDrawerVisible] = useState(false);
-  const { paginationConfig, savePageList } = usePagination();
+  const { paginationConfig, savePageList, checkPageList } = usePagination();
   const emptyProtoData = {
     id: null,
     content: '',
@@ -92,21 +93,20 @@ const Page: React.FC = () => {
             {formatMessage({ id: 'component.global.edit' })}
           </Button>
           <Popconfirm
-            title={formatMessage({ id: 'page.upstream.list.confirm.delete' })}
-            okText={formatMessage({ id: 'page.upstream.list.confirm' })}
-            cancelText={formatMessage({ id: 'page.upstream.list.cancel' })}
+            title={formatMessage({ id: 'page.proto.list.confirm.delete' })}
+            okText={formatMessage({ id: 'page.proto.list.confirm' })}
+            cancelText={formatMessage({ id: 'page.proto.list.cancel' })}
             onConfirm={() => {
               remove(record.id).then(() => {
                 notification.success({
-                  message: formatMessage({ id: 'page.upstream.list.delete.successfully' }),
+                  message: formatMessage({ id: 'page.proto.list.delete.successfully' }),
                 });
-                /* eslint-disable no-unused-expressions */
-                ref.current?.reload();
+                checkPageList(ref);
               });
             }}
           >
             <Button type="primary" danger>
-              {formatMessage({ id: 'page.upstream.list.delete' })}
+              {formatMessage({ id: 'page.proto.list.delete' })}
             </Button>
           </Popconfirm>
         </Space>
diff --git a/web/src/pages/Proto/locales/en-US.ts b/web/src/pages/Proto/locales/en-US.ts
index 69074cc5..13f1f1f7 100644
--- a/web/src/pages/Proto/locales/en-US.ts
+++ b/web/src/pages/Proto/locales/en-US.ts
@@ -18,7 +18,12 @@ export default {
   'page.proto.list': 'Proto List',
   'page.proto.list.description':
     "Protocol buffers are Google's language-neutral, platform-neutral, extensible mechanism for serializing structured data.You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.The protocol buffers list contains the created proto files. When the grpc transcode plug-in is enabled, the ID can be configured to read the conten [...]
-
+  'page.proto.list.edit': 'Configure',
+  'page.proto.list.confirm.delete': 'Are you sure to delete ?',
+  'page.proto.list.confirm': 'Confirm',
+  'page.proto.list.cancel': 'Cancel',
+  'page.proto.list.delete.successfully': 'Delete proto Successfully',
+  'page.proto.list.delete': 'Delete',
   'page.proto.id.tooltip': ".proto file's id",
 
   'page.proto.desc': 'description',
@@ -31,4 +36,5 @@ export default {
   'page.proto.drawer.edit': 'Configure proto',
   'page.proto.drawer.create.successfully': 'Create proto Successfully',
   'page.proto.drawer.edit.successfully': 'Configure proto Successfully',
+  'page.proto.drawer.delete.successfully': 'Delete proto Successfully',
 };
diff --git a/web/src/pages/Proto/locales/zh-CN.ts b/web/src/pages/Proto/locales/zh-CN.ts
index 2fe11235..ac68d8c0 100644
--- a/web/src/pages/Proto/locales/zh-CN.ts
+++ b/web/src/pages/Proto/locales/zh-CN.ts
@@ -18,6 +18,12 @@ export default {
   'page.proto.list': 'Proto 列表',
   'page.proto.list.description':
     'Protocol Buffers 是 Google 用于序列化结构化数据的框架,它具有语言中立、平台中立、可扩展机制的特性,您只需定义一次数据的结构化方式,然后就可以使用各种语言通过特殊生成的源代码轻松地将结构化数据写入和读取各种数据流。Protocol Buffers 列表包含了已创建的 proto 文件,在启用 grpc-transcode 插件时可配置 ID 读取对应的 proto 文件内容。',
+  'page.proto.list.edit': '配置',
+  'page.proto.list.confirm.delete': '确定删除该条记录吗?',
+  'page.proto.list.confirm': '确定',
+  'page.proto.list.cancel': '取消',
+  'page.proto.list.delete.successfully': '删除记录成功',
+  'page.proto.list.delete': '删除',
 
   'page.proto.id.tooltip': '.proto 文件的 id',
 
@@ -30,5 +36,6 @@ export default {
   'page.proto.drawer.create': '创建 proto',
   'page.proto.drawer.edit': '配置 proto',
   'page.proto.drawer.create.successfully': '创建 proto 成功',
-  'page.proto.drawer.edit.successfully': '配置 proto',
+  'page.proto.drawer.edit.successfully': '配置 proto 成功',
+  'page.proto.drawer.delete.successfully': '删除 proto 成功',
 };
diff --git a/web/src/pages/Route/List.tsx b/web/src/pages/Route/List.tsx
index 1b1b21cc..8634c2c3 100755
--- a/web/src/pages/Route/List.tsx
+++ b/web/src/pages/Route/List.tsx
@@ -87,7 +87,7 @@ const Page: React.FC = () => {
   const [rawData, setRawData] = useState<Record<string, any>>({});
   const [id, setId] = useState('');
   const [editorMode, setEditorMode] = useState<'create' | 'update'>('create');
-  const { paginationConfig, savePageList } = usePagination();
+  const { paginationConfig, savePageList, checkPageList } = usePagination();
   const [debugDrawVisible, setDebugDrawVisible] = useState(false);
 
   useEffect(() => {
@@ -107,7 +107,7 @@ const Page: React.FC = () => {
       message: msgTip,
     });
 
-    ref.current?.reload();
+    checkPageList(ref);
   };
 
   const handlePublishOffline = (rid: string, status: RouteModule.RouteStatus) => {
@@ -228,47 +228,47 @@ const Page: React.FC = () => {
       onClick: () => void;
       icon?: ReactNode;
     }[] = [
-      {
-        name: formatMessage({ id: 'component.global.view' }),
-        onClick: () => {
-          setId(record.id);
-          setRawData(omit(record, DELETE_FIELDS));
-          setVisible(true);
-          setEditorMode('update');
+        {
+          name: formatMessage({ id: 'component.global.view' }),
+          onClick: () => {
+            setId(record.id);
+            setRawData(omit(record, DELETE_FIELDS));
+            setVisible(true);
+            setEditorMode('update');
+          },
         },
-      },
-      {
-        name: formatMessage({ id: 'component.global.duplicate' }),
-        onClick: () => {
-          history.push(`/routes/${record.id}/duplicate`);
+        {
+          name: formatMessage({ id: 'component.global.duplicate' }),
+          onClick: () => {
+            history.push(`/routes/${record.id}/duplicate`);
+          },
         },
-      },
-      {
-        name: formatMessage({ id: 'component.global.delete' }),
-        onClick: () => {
-          Modal.confirm({
-            type: 'warning',
-            title: formatMessage({ id: 'component.global.popconfirm.title.delete' }),
-            content: (
-              <>
-                {formatMessage({ id: 'component.global.name' })} - {record.name}
-                <br />
-                ID - {record.id}
-              </>
-            ),
-            onOk: () => {
-              return remove(record.id!).then(() => {
-                handleTableActionSuccessResponse(
-                  `${formatMessage({ id: 'component.global.delete' })} ${formatMessage({
-                    id: 'menu.routes',
-                  })} ${formatMessage({ id: 'component.status.success' })}`,
-                );
-              });
-            },
-          });
+        {
+          name: formatMessage({ id: 'component.global.delete' }),
+          onClick: () => {
+            Modal.confirm({
+              type: 'warning',
+              title: formatMessage({ id: 'component.global.popconfirm.title.delete' }),
+              content: (
+                <>
+                  {formatMessage({ id: 'component.global.name' })} - {record.name}
+                  <br />
+                  ID - {record.id}
+                </>
+              ),
+              onOk: () => {
+                return remove(record.id!).then(() => {
+                  handleTableActionSuccessResponse(
+                    `${formatMessage({ id: 'component.global.delete' })} ${formatMessage({
+                      id: 'menu.routes',
+                    })} ${formatMessage({ id: 'component.status.success' })}`,
+                  );
+                });
+              },
+            });
+          },
         },
-      },
-    ];
+      ];
 
     return (
       <Dropdown
diff --git a/web/src/pages/SSL/List.tsx b/web/src/pages/SSL/List.tsx
index 145f56f3..47e4d005 100644
--- a/web/src/pages/SSL/List.tsx
+++ b/web/src/pages/SSL/List.tsx
@@ -28,7 +28,7 @@ import { timestampToLocaleString } from '@/helpers';
 const Page: React.FC = () => {
   const tableRef = useRef<ActionType>();
   const { formatMessage } = useIntl();
-  const { paginationConfig, savePageList } = usePagination();
+  const { paginationConfig, savePageList, checkPageList } = usePagination();
 
   const columns: ProColumns<SSLModule.ResponseBody>[] = [
     {
@@ -75,8 +75,7 @@ const Page: React.FC = () => {
                 notification.success({
                   message: formatMessage({ id: 'component.ssl.removeSSLSuccess' }),
                 });
-                /* eslint-disable no-unused-expressions */
-                requestAnimationFrame(() => tableRef.current?.reload());
+                requestAnimationFrame(() => checkPageList(tableRef));
               })
             }
             cancelText={formatMessage({ id: 'component.global.cancel' })}
diff --git a/web/src/pages/Service/List.tsx b/web/src/pages/Service/List.tsx
index 0946a4b1..948f1127 100644
--- a/web/src/pages/Service/List.tsx
+++ b/web/src/pages/Service/List.tsx
@@ -35,7 +35,7 @@ const Page: React.FC = () => {
   const [rawData, setRawData] = useState<Record<string, any>>({});
   const [id, setId] = useState('');
   const [editorMode, setEditorMode] = useState<'create' | 'update'>('create');
-  const { paginationConfig, savePageList } = usePagination();
+  const { paginationConfig, savePageList, checkPageList } = usePagination();
 
   const columns: ProColumns<ServiceModule.ResponseBody>[] = [
     {
@@ -82,8 +82,7 @@ const Page: React.FC = () => {
                       id: 'menu.service',
                     })} ${formatMessage({ id: 'component.status.success' })}`,
                   });
-                  /* eslint-disable no-unused-expressions */
-                  ref.current?.reload();
+                  checkPageList(ref);
                 });
               }}
               okText={formatMessage({ id: 'component.global.confirm' })}
diff --git a/web/src/pages/Upstream/List.tsx b/web/src/pages/Upstream/List.tsx
index 5d693133..c0348743 100644
--- a/web/src/pages/Upstream/List.tsx
+++ b/web/src/pages/Upstream/List.tsx
@@ -37,7 +37,7 @@ const Page: React.FC = () => {
   const [rawData, setRawData] = useState<Record<string, any>>({});
   const [id, setId] = useState('');
   const [editorMode, setEditorMode] = useState<'create' | 'update'>('create');
-  const { paginationConfig, savePageList } = usePagination();
+  const { paginationConfig, savePageList, checkPageList } = usePagination();
   const { formatMessage } = useIntl();
 
   const columns: ProColumns<UpstreamModule.ResponseBody>[] = [
@@ -95,8 +95,7 @@ const Page: React.FC = () => {
                 notification.success({
                   message: formatMessage({ id: 'page.upstream.list.delete.successfully' }),
                 });
-                /* eslint-disable no-unused-expressions */
-                ref.current?.reload();
+                checkPageList(ref);
               });
             }}
           >