This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new eeee8ac9 fix(ui): P0 frontend fixes - gzip, delete confirm, audit
search debounce, StrictMode (#1453)
eeee8ac9 is described below
commit eeee8ac979d59e687ab60d2835c9dbdaa2f4c7cf
Author: zhaohai <[email protected]>
AuthorDate: Mon Aug 10 20:59:18 2026 +0800
fix(ui): P0 frontend fixes - gzip, delete confirm, audit search debounce,
StrictMode (#1453)
* fix: batch of small backend validation and export fixes (#1103, #1105,
#1233, #1241, #1242)
* fix(rocketmq): audit failure must not fail an already-delivered message
* fix(rocketmq): tolerate missing broker tables in dashboard overview
* fix(ai): close error response stream in LLM client
* fix(ai): enforce stream timeout and avoid pipe deadlock in CLI agent
* fix(cluster): keep write/read queue counts in sync on partial updates
* fix(ai): persist maxTokens and temperature across restarts
* fix(ai): bound the LLM gateway thread pool to avoid thread exhaustion
* fix(dlq): tolerate single-queue scan failures during resend collection
* fix(topic): validate required topic on message send
* fix(api): return 400 for invalid request parameters
* fix(topic): require newTTL on TTL update
* fix(topic): require positive queue counts on topic creation
* fix(metrics): validate nested query in datasource queries
* chore: keep only this change on top of the batch branch
---------
Co-authored-by: yyqdbngt <[email protected]>
* fix(web): gzip serving, delete confirmations, audit search debounce,
StrictMode remount fix
- nginx: enable gzip for text assets so the ~1MB entry bundle is no
longer served uncompressed in the Docker deployment
- GroupManagement: reset mountedRef on remount; under StrictMode the
cleanup cleared it and nothing restored it, so the remounted list
load never applied and the page stayed loading forever in dev
- alerts/settings: require Popconfirm before deleting an alert rule or
a data source, matching the confirmation convention used elsewhere
- audit: debounce the free-text search (300ms) so the record list is
not re-fetched on every keystroke
---------
Co-authored-by: yyqdbngt <[email protected]>
Co-authored-by: yyqdbngt <[email protected]>
---
web/nginx.conf | 8 ++++++++
web/src/pages/ops/alerts.tsx | 27 +++++++++++++++++----------
web/src/pages/ops/audit.tsx | 12 ++++++++++--
web/src/pages/settings/index.tsx | 18 ++++++++++--------
web/src/pages/studio/GroupManagement.tsx | 3 +++
5 files changed, 48 insertions(+), 20 deletions(-)
diff --git a/web/nginx.conf b/web/nginx.conf
index 11b9786d..41833596 100644
--- a/web/nginx.conf
+++ b/web/nginx.conf
@@ -2,6 +2,14 @@ server {
listen 80;
server_name _;
+ # Compress text assets; the entry JS bundle is ~1MB uncompressed.
+ gzip on;
+ gzip_comp_level 5;
+ gzip_min_length 1024;
+ gzip_proxied any;
+ gzip_vary on;
+ gzip_types text/plain text/css application/javascript application/json
image/svg+xml;
+
# RESOLVER is injected by 15-resolver.sh (podman network gateway only).
# Variable proxy_pass re-resolves on every request, so a recreated
# rocketmq-server container with a new IP is picked up without restarting
diff --git a/web/src/pages/ops/alerts.tsx b/web/src/pages/ops/alerts.tsx
index 62b8a890..65e93c73 100644
--- a/web/src/pages/ops/alerts.tsx
+++ b/web/src/pages/ops/alerts.tsx
@@ -31,6 +31,7 @@ import {
Checkbox,
Flex,
message,
+ Popconfirm,
theme,
} from 'antd';
import type { ColumnsType, TableRowSelection } from 'antd/es/table/interface';
@@ -274,17 +275,23 @@ const AlertsPage = () => {
>
{t('common.edit')}
</Button>
- <Button
- size="small"
- icon={<Trash size={14} />}
- danger
- loading={actionId === `delete-${record.id}`}
- disabled={isActionRunning}
- style={{ borderColor: '#ff4d4f', color: '#ff4d4f' }}
- onClick={() => void handleDelete(record)}
+ <Popconfirm
+ title={t('common.areYouSureToDelete')}
+ onConfirm={() => void handleDelete(record)}
+ okText={t('common.confirm')}
+ cancelText={t('common.cancel')}
>
- {t('common.delete')}
- </Button>
+ <Button
+ size="small"
+ icon={<Trash size={14} />}
+ danger
+ loading={actionId === `delete-${record.id}`}
+ disabled={isActionRunning}
+ style={{ borderColor: '#ff4d4f', color: '#ff4d4f' }}
+ >
+ {t('common.delete')}
+ </Button>
+ </Popconfirm>
</Flex>
),
},
diff --git a/web/src/pages/ops/audit.tsx b/web/src/pages/ops/audit.tsx
index 68a82473..d3f3b710 100644
--- a/web/src/pages/ops/audit.tsx
+++ b/web/src/pages/ops/audit.tsx
@@ -89,6 +89,7 @@ const AuditPage: React.FC = () => {
const [loading, setLoading] = useState(true);
const [refreshKey, setRefreshKey] = useState(0);
const [searchText, setSearchText] = useState('');
+ const [debouncedSearchText, setDebouncedSearchText] = useState('');
const [selectedType, setSelectedType] = useState<string |
undefined>(undefined);
const [selectedResourceType, setSelectedResourceType] = useState<string |
undefined>(undefined);
const [selectedClusterId, setSelectedClusterId] = useState<string |
undefined>(undefined);
@@ -115,6 +116,13 @@ const AuditPage: React.FC = () => {
};
}, [refreshKey]);
+ // Debounce free-text search so the record list is not re-fetched on every
+ // keystroke; typing pauses for 300ms before the query hits the server.
+ useEffect(() => {
+ const timer = window.setTimeout(() => setDebouncedSearchText(searchText),
300);
+ return () => window.clearTimeout(timer);
+ }, [searchText]);
+
useEffect(() => {
let cancelled = false;
@@ -122,7 +130,7 @@ const AuditPage: React.FC = () => {
page,
pageSize,
...buildAuditFilter(
- searchText,
+ debouncedSearchText,
selectedType,
selectedResourceType,
selectedClusterId,
@@ -148,7 +156,7 @@ const AuditPage: React.FC = () => {
}, [
page,
pageSize,
- searchText,
+ debouncedSearchText,
selectedType,
selectedResourceType,
selectedClusterId,
diff --git a/web/src/pages/settings/index.tsx b/web/src/pages/settings/index.tsx
index 64bb0bb0..cc9d9b55 100644
--- a/web/src/pages/settings/index.tsx
+++ b/web/src/pages/settings/index.tsx
@@ -26,6 +26,7 @@ import {
Input,
InputNumber,
Modal,
+ Popconfirm,
Radio,
Select,
Space,
@@ -447,15 +448,16 @@ export const DataSourceTab = () => {
>
编辑
</Button>
- <Button
- type="link"
- size="small"
- danger
- icon={<DeleteOutlined />}
- onClick={() => void handleDelete(record)}
+ <Popconfirm
+ title="确定要删除该数据源吗?"
+ onConfirm={() => void handleDelete(record)}
+ okText="确定"
+ cancelText="取消"
>
- 删除
- </Button>
+ <Button type="link" size="small" danger icon={<DeleteOutlined />}>
+ 删除
+ </Button>
+ </Popconfirm>
</Space>
),
},
diff --git a/web/src/pages/studio/GroupManagement.tsx
b/web/src/pages/studio/GroupManagement.tsx
index 4459e2d1..f3c01ee1 100644
--- a/web/src/pages/studio/GroupManagement.tsx
+++ b/web/src/pages/studio/GroupManagement.tsx
@@ -106,6 +106,9 @@ const GroupManagementPage = () => {
}, [t]);
useEffect(() => {
+ // Reset on (re)mount: under StrictMode the previous cleanup has already
+ // cleared the flag, and without this the remounted load never applies.
+ mountedRef.current = true;
const timeoutId = window.setTimeout(() => {
void loadGroups();
});