You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@rocketmq.apache.org by li...@apache.org on 2026/10/19 12:53:06 UTC
(rocketmq-dashboard) branch rocketmq-studio updated: fix(export): date CSV filenames in the operator timezone (#6898)
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 97f668006 fix(export): date CSV filenames in the operator timezone (#6898)
97f668006 is described below
commit 97f668006bb0b8d6bb8e1f6a6fc661b961ad28b2
Author: Loyal-Young <hj...@gmail.com>
AuthorDate: Mon Oct 19 20:52:59 2026 +0800
fix(export): date CSV filenames in the operator timezone (#6898)
Verified against `rocketmq-studio` at a4dee573c before merging: the defect is still present on the
current trunk and the change merges cleanly.
Merged as-is (squash); no maintainer edits were needed.
Locally verified: targeted suites green on the integration branch; a full backend and frontend
build plus test pass runs on the trunk once this batch lands.
---
web/src/pages/cluster/clients.tsx | 4 ++--
web/src/pages/instance/consumer.tsx | 4 ++--
web/src/pages/instance/topic.tsx | 4 ++--
web/src/pages/ops/systemAlerts.tsx | 4 ++--
web/src/pages/settings/CloudCredentialTab.tsx | 4 ++--
web/src/pages/settings/DataSourceTab.tsx | 4 ++--
.../settings/__tests__/CloudCredentialTab.test.tsx | 3 ++-
web/src/pages/studio/BrokerCluster.tsx | 4 ++--
web/src/pages/studio/LiteTopic.tsx | 4 ++--
web/src/pages/studio/Producer.tsx | 4 ++--
web/src/pages/studio/UserManagement.tsx | 4 ++--
web/src/utils/download.test.ts | 21 ++++++++++++++++++++-
web/src/utils/download.ts | 5 +++++
13 files changed, 47 insertions(+), 22 deletions(-)
diff --git a/web/src/pages/cluster/clients.tsx b/web/src/pages/cluster/clients.tsx
index 5e92a7eea..65d0e261f 100644
--- a/web/src/pages/cluster/clients.tsx
+++ b/web/src/pages/cluster/clients.tsx
@@ -45,7 +45,7 @@ import { listConnections } from '../../services/connectionsService';
import { listRegistryClusters } from '../../services/clusterService';
import type { ClusterInfo } from '../../api/cluster';
import { formatDateTime } from '../../utils/format';
-import { buildCsv, downloadCsv, type CsvColumn } from '../../utils/download';
+import { buildCsv, downloadCsv, localExportDateStamp, type CsvColumn } from '../../utils/download';
import { describeThrownMessage } from '../../utils/apiError';
import { tableScrollX } from '../../utils/table';
import {
@@ -382,7 +382,7 @@ const ClientsPage = () => {
const clampedCurrentPage = Math.min(currentPage, lastPage);
const handleExport = () => {
- const filename = `rocketmq-client-connections-${new Date().toISOString().slice(0, 10)}.csv`;
+ const filename = `rocketmq-client-connections-${localExportDateStamp()}.csv`;
const csv = buildCsv(CLIENT_CONNECTION_EXPORT_COLUMNS, exportConnections);
downloadCsv(filename, csv);
};
diff --git a/web/src/pages/instance/consumer.tsx b/web/src/pages/instance/consumer.tsx
index e68ef4029..1864c020c 100644
--- a/web/src/pages/instance/consumer.tsx
+++ b/web/src/pages/instance/consumer.tsx
@@ -103,7 +103,7 @@ import {
validateConsumerGroupCsvImport,
type ResourceImportRow,
} from '../../utils/resourceCsvImport';
-import { downloadCsv } from '../../utils/download';
+import { downloadCsv, localExportDateStamp } from '../../utils/download';
import { formatLag, isLagAvailable, lagSortValue } from '../../utils/consumerLag';
import { formatOnlineInstances, onlineInstancesCount } from '../../utils/consumerConnections';
import { tableScrollX } from '../../utils/table';
@@ -529,7 +529,7 @@ const ConsumerPageContent = ({
search: search.trim() || undefined,
subscriptionMode: modeFilter !== 'ALL' ? modeFilter : undefined,
});
- downloadCsv(`rocketmq-consumer-groups-${new Date().toISOString().slice(0, 10)}.csv`, csv);
+ downloadCsv(`rocketmq-consumer-groups-${localExportDateStamp()}.csv`, csv);
message.success('Group 导出完成');
} catch {
message.error('导出 Group 失败,请稍后重试');
diff --git a/web/src/pages/instance/topic.tsx b/web/src/pages/instance/topic.tsx
index 081da44aa..76b6dd88e 100644
--- a/web/src/pages/instance/topic.tsx
+++ b/web/src/pages/instance/topic.tsx
@@ -84,7 +84,7 @@ import {
type ResourceImportRow,
} from '../../utils/resourceCsvImport';
import { isLagAvailable } from '../../utils/consumerLag';
-import { downloadCsv } from '../../utils/download';
+import { downloadCsv, localExportDateStamp } from '../../utils/download';
import { formatDateTime, formatNumber } from '../../utils/format';
import { tableScrollX } from '../../utils/table';
import {
@@ -684,7 +684,7 @@ const TopicPageContent = ({
search: searchText.trim() || undefined,
})
.then((csv) => {
- downloadCsv(`rocketmq-topics-${new Date().toISOString().slice(0, 10)}.csv`, csv);
+ downloadCsv(`rocketmq-topics-${localExportDateStamp()}.csv`, csv);
message.success('Topic 导出完成');
})
.catch(() => {
diff --git a/web/src/pages/ops/systemAlerts.tsx b/web/src/pages/ops/systemAlerts.tsx
index d41785cc0..1812e35b9 100644
--- a/web/src/pages/ops/systemAlerts.tsx
+++ b/web/src/pages/ops/systemAlerts.tsx
@@ -62,7 +62,7 @@ import type {
SystemAlert,
} from '../../api/ops';
import { formatUtcDateTime, formatNumber } from '../../utils/format';
-import { buildCsv, downloadCsv, type CsvColumn } from '../../utils/download';
+import { buildCsv, downloadCsv, localExportDateStamp, type CsvColumn } from '../../utils/download';
import { zonedLocalDateTimeToUtc } from '../../utils/timeZone';
const { Text } = Typography;
@@ -321,7 +321,7 @@ const SystemAlertsPage = () => {
currentPage += 1;
}
downloadCsv(
- `rocketmq-system-alerts-${new Date().toISOString().slice(0, 10)}.csv`,
+ `rocketmq-system-alerts-${localExportDateStamp()}.csv`,
buildCsv(ALERT_EXPORT_COLUMNS, rows),
);
message.success(t('sysAlerts.exportSuccess', { count: rows.length }));
diff --git a/web/src/pages/settings/CloudCredentialTab.tsx b/web/src/pages/settings/CloudCredentialTab.tsx
index d16fe7b4c..83e2bdd34 100644
--- a/web/src/pages/settings/CloudCredentialTab.tsx
+++ b/web/src/pages/settings/CloudCredentialTab.tsx
@@ -45,7 +45,7 @@ import {
} from '../../api/cloudCredential';
import type { CloudCredential } from '../../api/cloudCredential';
import type { InstanceVendor } from '../../api/instance';
-import { downloadBlob } from '../../utils/download';
+import { downloadBlob, localExportDateStamp } from '../../utils/download';
const vendorTagColor: Record<string, string> = {
ALIYUN: 'orange',
@@ -153,7 +153,7 @@ export const CloudCredentialTab = () => {
const csv = await exportCloudCredentials(vendorFilter, debouncedSearch);
downloadBlob(
new Blob([csv], { type: 'text/csv;charset=utf-8' }),
- `rocketmq-cloud-credentials-${new Date().toISOString().slice(0, 10)}.csv`,
+ `rocketmq-cloud-credentials-${localExportDateStamp()}.csv`,
);
} catch {
message.error(t('settings.credentialExportFailed'));
diff --git a/web/src/pages/settings/DataSourceTab.tsx b/web/src/pages/settings/DataSourceTab.tsx
index 84f360010..e6de0b90d 100644
--- a/web/src/pages/settings/DataSourceTab.tsx
+++ b/web/src/pages/settings/DataSourceTab.tsx
@@ -48,7 +48,7 @@ import type { DataSource } from '../../api/settings';
import { STATUS_MAP } from '../../constants/theme';
import { listInstances } from '../../services/instanceService';
import type { Instance } from '../../api/instance';
-import { buildCsv, downloadCsv, type CsvColumn } from '../../utils/download';
+import { buildCsv, downloadCsv, localExportDateStamp, type CsvColumn } from '../../utils/download';
const { Text } = Typography;
@@ -326,7 +326,7 @@ export const DataSourceTab = () => {
instanceNames: formatInstanceIds(source.instanceIds),
statusLabel: formatStatus(source.status),
}));
- const filename = `rocketmq-data-sources-${new Date().toISOString().slice(0, 10)}.csv`;
+ const filename = `rocketmq-data-sources-${localExportDateStamp()}.csv`;
downloadCsv(filename, buildCsv(DATA_SOURCE_EXPORT_COLUMNS, rows));
message.success(t('settings.dataSourceExported', { total: rows.length }));
} catch {
diff --git a/web/src/pages/settings/__tests__/CloudCredentialTab.test.tsx b/web/src/pages/settings/__tests__/CloudCredentialTab.test.tsx
index a75d176b6..10d1c6f30 100644
--- a/web/src/pages/settings/__tests__/CloudCredentialTab.test.tsx
+++ b/web/src/pages/settings/__tests__/CloudCredentialTab.test.tsx
@@ -40,7 +40,8 @@ vi.mock('../../../api/cloudCredential', () => ({
updateCloudCredential: vi.fn(),
}));
-vi.mock('../../../utils/download', () => ({
+vi.mock('../../../utils/download', async (importOriginal) => ({
+ ...(await importOriginal<typeof import('../../../utils/download')>()),
downloadBlob: vi.fn(),
}));
diff --git a/web/src/pages/studio/BrokerCluster.tsx b/web/src/pages/studio/BrokerCluster.tsx
index 6c07639aa..3696bb1f5 100644
--- a/web/src/pages/studio/BrokerCluster.tsx
+++ b/web/src/pages/studio/BrokerCluster.tsx
@@ -44,7 +44,7 @@ import type { ClusterInfo } from '../../api/cluster';
import { supportsApacheRuntime, type Instance } from '../../api/instance';
import { listInstances } from '../../services/instanceService';
import { useVisiblePolling } from '../../hooks/useVisiblePolling';
-import { buildCsv, downloadCsv, type CsvColumn } from '../../utils/download';
+import { buildCsv, downloadCsv, localExportDateStamp, type CsvColumn } from '../../utils/download';
import { roundPercent } from '../../utils/format';
// ─── Types ──────────────────────────────────────────────────────
@@ -325,7 +325,7 @@ const BrokerClusterPage = () => {
};
function handleExport() {
- const today = new Date().toISOString().slice(0, 10);
+ const today = localExportDateStamp();
if (activeTab === 'nameserver') {
downloadCsv(
`rocketmq-nameserver-topology-${today}.csv`,
diff --git a/web/src/pages/studio/LiteTopic.tsx b/web/src/pages/studio/LiteTopic.tsx
index 64d1f519d..be301bb67 100644
--- a/web/src/pages/studio/LiteTopic.tsx
+++ b/web/src/pages/studio/LiteTopic.tsx
@@ -61,7 +61,7 @@ import {
type LiteTopicSession,
} from '../../api/liteTopic';
import { listInstances, supportsApacheRuntime, type Instance } from '../../api/instance';
-import { buildCsv, downloadCsv, type CsvColumn } from '../../utils/download';
+import { buildCsv, downloadCsv, localExportDateStamp, type CsvColumn } from '../../utils/download';
const formatDuration = (ms: number | undefined | null): string => {
if (ms == null) return '-';
@@ -439,7 +439,7 @@ const LiteTopicPage: React.FC = () => {
ttlStatusLabel: getTTLStatusLabel(item.ttlStatus),
sessionCount: item.sessionIds?.length ?? 0,
}));
- const filename = `rocketmq-lite-topics-${new Date().toISOString().slice(0, 10)}.csv`;
+ const filename = `rocketmq-lite-topics-${localExportDateStamp()}.csv`;
downloadCsv(filename, buildCsv(LITE_TOPIC_EXPORT_COLUMNS, rows));
message.success(t('liteTopic.exportSuccess', { total: rows.length }));
} catch {
diff --git a/web/src/pages/studio/Producer.tsx b/web/src/pages/studio/Producer.tsx
index 6177d120c..c606a2967 100644
--- a/web/src/pages/studio/Producer.tsx
+++ b/web/src/pages/studio/Producer.tsx
@@ -42,7 +42,7 @@ import {
} from '../../api/producer';
import { supportsApacheRuntime, type Instance } from '../../api/instance';
import { listInstances } from '../../services/instanceService';
-import { buildCsv, downloadCsv, type CsvColumn } from '../../utils/download';
+import { buildCsv, downloadCsv, localExportDateStamp, type CsvColumn } from '../../utils/download';
const readinessConfig: Record<ProducerReadiness, { color: string; type: 'success' | 'warning' }> = {
READY: { color: 'success', type: 'success' },
@@ -314,7 +314,7 @@ const ProducerPage = () => {
readiness: connectionSummary?.readiness,
warnings,
}));
- const filename = `rocketmq-producer-connections-${new Date().toISOString().slice(0, 10)}.csv`;
+ const filename = `rocketmq-producer-connections-${localExportDateStamp()}.csv`;
const csv = buildCsv(PRODUCER_CONNECTION_EXPORT_COLUMNS, rows);
downloadCsv(filename, csv);
};
diff --git a/web/src/pages/studio/UserManagement.tsx b/web/src/pages/studio/UserManagement.tsx
index 8b5897c5b..f5149bf91 100644
--- a/web/src/pages/studio/UserManagement.tsx
+++ b/web/src/pages/studio/UserManagement.tsx
@@ -60,7 +60,7 @@ import {
type StudioUserSessionOverview,
} from '../../api/studioUsers';
import useAuthStore from '../../stores/authStore';
-import { buildCsv, downloadCsv, type CsvColumn } from '../../utils/download';
+import { buildCsv, downloadCsv, localExportDateStamp, type CsvColumn } from '../../utils/download';
import { formatDelay, formatUtcDateTime } from '../../utils/format';
import { tableScrollX } from '../../utils/table';
import { useLang } from '../../i18n/LangContext';
@@ -379,7 +379,7 @@ const UserManagementPage = () => {
admin: roleFilter === undefined ? undefined : roleFilter === 'admin',
enabled: statusFilter === undefined ? undefined : statusFilter === 'enabled',
});
- const today = new Date().toISOString().slice(0, 10);
+ const today = localExportDateStamp();
downloadCsv(
`rocketmq-studio-users-${today}.csv`,
buildCsv(STUDIO_USER_EXPORT_COLUMNS, exportedUsers),
diff --git a/web/src/utils/download.test.ts b/web/src/utils/download.test.ts
index 1b3be406a..f7ab86c4b 100644
--- a/web/src/utils/download.test.ts
+++ b/web/src/utils/download.test.ts
@@ -16,7 +16,26 @@
*/
import { afterEach, describe, expect, it, vi } from 'vitest';
-import { buildCsv, downloadBlob, downloadCsv } from './download';
+import { buildCsv, downloadBlob, downloadCsv, localExportDateStamp } from './download';
+
+describe('localExportDateStamp', () => {
+ afterEach(() => vi.unstubAllEnvs());
+
+ it('uses the operator date when UTC is still on the previous day', () => {
+ vi.stubEnv('TZ', 'Asia/Shanghai');
+ const now = new Date('2026-10-16T16:30:00Z');
+ expect(now.toISOString().slice(0, 10)).toBe('2026-10-16');
+ expect(localExportDateStamp(now)).toBe('2026-10-17');
+ });
+
+ it('uses the operator date when UTC has already advanced to the next day', () => {
+ vi.stubEnv('TZ', 'America/Los_Angeles');
+ const now = new Date('2026-10-17T03:30:00Z');
+ expect(now.toISOString().slice(0, 10)).toBe('2026-10-17');
+ expect(localExportDateStamp(now)).toBe('2026-10-16');
+ });
+});
+
describe('buildCsv', () => {
it('escapes quotes, empty values, and spreadsheet formulas', () => {
const csv = buildCsv(
diff --git a/web/src/utils/download.ts b/web/src/utils/download.ts
index 83af33d54..8ea1b7483 100644
--- a/web/src/utils/download.ts
+++ b/web/src/utils/download.ts
@@ -15,6 +15,11 @@
* limitations under the License.
*/
+import { formatDate } from './format';
+
+/** Stamp export filenames in the operator's local date, including across a UTC date boundary. */
+export const localExportDateStamp = (now = new Date()): string => formatDate(now);
+
export const downloadBlob = (blob: Blob, filename: string) => {
const url = URL.createObjectURL(blob);
const anchor = document.createElement('a');