This is an automated email from the ASF dual-hosted git repository.
LiteSun 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 89fc2ed17 feat: show a translated empty state on every list page
(#3454)
89fc2ed17 is described below
commit 89fc2ed17cf5ca21551d113ce79cd0899383cbec
Author: Yuhan <[email protected]>
AuthorDate: Mon Aug 3 09:25:46 2026 +0800
feat: show a translated empty state on every list page (#3454)
---
.../integration/empty-state.all-resources.spec.ts | 18 ++++++++
.../regression/credentials.empty-state.spec.ts | 50 ++++++++++++++++++++++
e2e/tests/services.empty.spec.ts | 3 +-
src/components/page/ListEmptyState.tsx | 44 +++++++++++++++++++
src/locales/de/common.json | 6 ++-
src/locales/en/common.json | 6 ++-
src/locales/es/common.json | 6 ++-
src/locales/tr/common.json | 6 ++-
src/locales/zh/common.json | 6 ++-
src/routes/consumer_groups/index.tsx | 4 ++
.../detail.$username/credentials/index.tsx | 4 ++
src/routes/consumers/index.tsx | 4 ++
src/routes/global_rules/index.tsx | 4 ++
src/routes/plugin_configs/index.tsx | 4 ++
src/routes/protos/index.tsx | 4 ++
src/routes/routes/index.tsx | 4 ++
src/routes/secrets/index.tsx | 4 ++
src/routes/services/index.tsx | 9 +---
src/routes/ssls/index.tsx | 4 ++
src/routes/stream_routes/index.tsx | 4 ++
src/routes/upstreams/index.tsx | 4 ++
21 files changed, 179 insertions(+), 19 deletions(-)
diff --git a/e2e/tests/integration/empty-state.all-resources.spec.ts
b/e2e/tests/integration/empty-state.all-resources.spec.ts
index 2eb0ae46e..cb0386397 100644
--- a/e2e/tests/integration/empty-state.all-resources.spec.ts
+++ b/e2e/tests/integration/empty-state.all-resources.spec.ts
@@ -31,6 +31,7 @@ import { deleteAllConsumers } from '@/apis/consumers';
import { deleteAllRoutes } from '@/apis/routes';
import { deleteAllServices } from '@/apis/services';
import { deleteAllSSLs } from '@/apis/ssls';
+import { deleteAllStreamRoutes } from '@/apis/stream_routes';
import { deleteAllUpstreams } from '@/apis/upstreams';
type Resource = {
@@ -39,6 +40,12 @@ type Resource = {
apiPath: string;
};
+// `secrets` is deliberately absent: its Admin API key is compound
+// (`/secrets/{manager}/{id}`) while the list response has already split
+// `manager` out of `value.id`, so the generic `purge()` below would issue
+// `DELETE /secrets/{id}` and silently leave rows behind — the empty-state
+// assertion would then fail for the wrong reason. Covering it needs a
+// manager-aware cleanup.
const RESOURCES: Resource[] = [
{ path: '/services', label: 'Services', apiPath: '/services' },
{ path: '/routes', label: 'Routes', apiPath: '/routes' },
@@ -57,6 +64,11 @@ const RESOURCES: Resource[] = [
apiPath: '/plugin_configs',
},
{ path: '/protos', label: 'Protos', apiPath: '/protos' },
+ {
+ path: '/stream_routes',
+ label: 'Stream Routes',
+ apiPath: '/stream_routes',
+ },
];
// Inline delete-all for resources that don't have a product-side helper.
@@ -86,6 +98,7 @@ test.beforeAll(async () => {
() => deleteAllConsumers(e2eReq),
() => deleteAllConsumerGroups(e2eReq),
() => deleteAllSSLs(e2eReq),
+ () => deleteAllStreamRoutes(e2eReq),
() => purge('/global_rules'),
() => purge('/plugin_configs'),
() => purge('/protos')
@@ -105,6 +118,11 @@ for (const resource of RESOURCES) {
page.getByRole('heading', { name: resource.label, exact: true })
).toBeVisible();
+ // The empty state must name the resource, not just avoid leaking keys.
+ // `resource.label` is the same plural noun the message interpolates, so
+ // this also proves the interpolation resolved.
+ await expect(page.getByText(`No ${resource.label} yet`)).toBeVisible();
+
// Hard-fail symptoms of #3321 — raw translation key in the visible UI.
const bodyText = await page.locator('body').innerText();
for (const suspect of [
diff --git a/e2e/tests/regression/credentials.empty-state.spec.ts
b/e2e/tests/regression/credentials.empty-state.spec.ts
new file mode 100644
index 000000000..d61aefdf1
--- /dev/null
+++ b/e2e/tests/regression/credentials.empty-state.spec.ts
@@ -0,0 +1,50 @@
+/**
+ * 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 { e2eReq } from '@e2e/utils/req';
+import { test } from '@e2e/utils/test';
+import { uiGoto } from '@e2e/utils/ui';
+import { expect } from '@playwright/test';
+
+import { deleteAllConsumers, putConsumerReq } from '@/apis/consumers';
+import type { APISIXType } from '@/types/schema/apisix';
+
+// Routes and stream routes also render nested (under
+// /services/detail/$id/...), but this spec is the only coverage of the
+// credentials list specifically, so it stays separate from the all-resources
+// loop above.
+
+const USERNAME = 'reg_empty_state_consumer';
+
+// A fresh consumer has no credentials, which is exactly the state under test.
+test.beforeAll(async () => {
+ await putConsumerReq(e2eReq, {
+ username: USERNAME,
+ } as APISIXType['ConsumerPut']);
+});
+
+test.afterAll(async () => {
+ await deleteAllConsumers(e2eReq);
+});
+
+test('the credentials empty state names the resource on a nested list', async
({
+ page,
+}) => {
+ await uiGoto(page, `/consumers/detail/${USERNAME}/credentials`);
+
+ await expect(page.getByText('No Credentials yet')).toBeVisible();
+});
diff --git a/e2e/tests/services.empty.spec.ts b/e2e/tests/services.empty.spec.ts
index a616b18d6..baeb7dc3d 100644
--- a/e2e/tests/services.empty.spec.ts
+++ b/e2e/tests/services.empty.spec.ts
@@ -39,8 +39,7 @@ test.describe('Services Empty State', () => {
const table = page.getByRole('table');
await expect(table).toBeVisible();
- const emptyDescription = page.getByText('No services found. Click
Add Service to create your first one.');
- await expect(emptyDescription).toBeVisible();
+ await expect(page.getByText('No Services yet')).toBeVisible();
const emptyImage = page.locator('.ant-empty-image');
await expect(emptyImage).toBeVisible();
diff --git a/src/components/page/ListEmptyState.tsx
b/src/components/page/ListEmptyState.tsx
new file mode 100644
index 000000000..b18765720
--- /dev/null
+++ b/src/components/page/ListEmptyState.tsx
@@ -0,0 +1,44 @@
+/**
+ * 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 { Empty } from 'antd';
+import { useTranslation } from 'react-i18next';
+
+import type { Resources } from '@/config/i18n';
+
+export type ListEmptyStateProps = {
+ /**
+ * Resource key, used to look up the already-translated plural name in
+ * `sources.*`. Typing it this way means a typo cannot compile.
+ */
+ resource: keyof Resources['en']['common']['sources'];
+};
+
+/**
+ * Shown in place of an empty list. Deliberately text-only: the toolbar's Add
+ * button sits directly above the table, so repeating it here would be a
+ * duplicate control rather than a shortcut.
+ */
+export const ListEmptyState = (props: ListEmptyStateProps) => {
+ const { resource } = props;
+ const { t } = useTranslation();
+ return (
+ <Empty
+ image={Empty.PRESENTED_IMAGE_SIMPLE}
+ description={t('empty.description', { name: t(`sources.${resource}`) })}
+ />
+ );
+};
diff --git a/src/locales/de/common.json b/src/locales/de/common.json
index 81c40c901..87cac851f 100644
--- a/src/locales/de/common.json
+++ b/src/locales/de/common.json
@@ -13,6 +13,9 @@
"singular": "Anmeldeinformation"
},
"docs": "Dokumentation",
+ "empty": {
+ "description": "Noch keine {{name}}"
+ },
"form": {
"basic": {
"desc": "Beschreibung",
@@ -348,8 +351,7 @@
"singular": "Secret"
},
"services": {
- "singular": "Service",
- "empty": "Keine Services gefunden. Klicken Sie auf Service hinzufügen, um
Ihren ersten zu erstellen."
+ "singular": "Service"
},
"settings": {
"adminKey": "Admin Key",
diff --git a/src/locales/en/common.json b/src/locales/en/common.json
index 8020f6da1..ae22e2c6f 100644
--- a/src/locales/en/common.json
+++ b/src/locales/en/common.json
@@ -13,6 +13,9 @@
"singular": "Credential"
},
"docs": "Docs",
+ "empty": {
+ "description": "No {{name}} yet"
+ },
"form": {
"basic": {
"desc": "Description",
@@ -348,8 +351,7 @@
"singular": "Secret"
},
"services": {
- "singular": "Service",
- "empty": "No services found. Click Add Service to create your first one."
+ "singular": "Service"
},
"settings": {
"adminKey": "Admin Key",
diff --git a/src/locales/es/common.json b/src/locales/es/common.json
index 693e09db0..e62229622 100644
--- a/src/locales/es/common.json
+++ b/src/locales/es/common.json
@@ -13,6 +13,9 @@
"singular": "Credencial"
},
"docs": "Documentación",
+ "empty": {
+ "description": "Aún no hay {{name}}"
+ },
"form": {
"basic": {
"desc": "Descripción",
@@ -348,8 +351,7 @@
"singular": "Secreto"
},
"services": {
- "singular": "Servicio",
- "empty": "No se encontraron servicios. Haga clic en Añadir Servicio para
crear el primero."
+ "singular": "Servicio"
},
"settings": {
"adminKey": "Clave de Administrador",
diff --git a/src/locales/tr/common.json b/src/locales/tr/common.json
index e2b48a65e..91909d3b7 100644
--- a/src/locales/tr/common.json
+++ b/src/locales/tr/common.json
@@ -13,6 +13,9 @@
"singular": "Credential"
},
"docs": "Dokümanlar",
+ "empty": {
+ "description": "Henüz {{name}} yok"
+ },
"form": {
"basic": {
"desc": "Açıklama",
@@ -348,8 +351,7 @@
"singular": "Secret"
},
"services": {
- "singular": "Service",
- "empty": "Service bulunamadı. İlk Service'inizi oluşturmak için Service
Ekle'ye tıklayın."
+ "singular": "Service"
},
"settings": {
"adminKey": "Admin Key",
diff --git a/src/locales/zh/common.json b/src/locales/zh/common.json
index 7223c63c5..6a6ca3fc4 100644
--- a/src/locales/zh/common.json
+++ b/src/locales/zh/common.json
@@ -13,6 +13,9 @@
"singular": "凭证"
},
"docs": "文档",
+ "empty": {
+ "description": "还没有{{name}}"
+ },
"form": {
"basic": {
"desc": "描述",
@@ -348,8 +351,7 @@
"singular": "Secret"
},
"services": {
- "singular": "服务",
- "empty": "未找到服务。创建您的第一个服务。"
+ "singular": "服务"
},
"settings": {
"adminKey": "管理员密钥",
diff --git a/src/routes/consumer_groups/index.tsx
b/src/routes/consumer_groups/index.tsx
index a5cd679ee..e0a9522ab 100644
--- a/src/routes/consumer_groups/index.tsx
+++ b/src/routes/consumer_groups/index.tsx
@@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next';
import { getConsumerGroupListQueryOptions, useConsumerGroupList } from
'@/apis/hooks';
import { CopyableId } from '@/components/page/CopyableId';
import { DeleteResourceBtn } from '@/components/page/DeleteResourceBtn';
+import { ListEmptyState } from '@/components/page/ListEmptyState';
import PageHeader from '@/components/page/PageHeader';
import { ToAddPageBtn, ToDetailPageBtn } from '@/components/page/ToAddPageBtn';
import { AntdConfigProvider } from '@/config/antdConfigProvider';
@@ -95,6 +96,9 @@ function ConsumerGroupsList() {
return (
<AntdConfigProvider>
<ProTable
+ locale={{
+ emptyText: <ListEmptyState resource="consumerGroups" />,
+ }}
columns={columns}
dataSource={data.list}
rowKey="id"
diff --git a/src/routes/consumers/detail.$username/credentials/index.tsx
b/src/routes/consumers/detail.$username/credentials/index.tsx
index d433ba2b5..46d01d73f 100644
--- a/src/routes/consumers/detail.$username/credentials/index.tsx
+++ b/src/routes/consumers/detail.$username/credentials/index.tsx
@@ -25,6 +25,7 @@ import {
useCredentialsList,
} from '@/apis/hooks';
import { DeleteResourceBtn } from '@/components/page/DeleteResourceBtn';
+import { ListEmptyState } from '@/components/page/ListEmptyState';
import PageHeader from '@/components/page/PageHeader';
import { ToAddPageBtn, ToDetailPageBtn } from '@/components/page/ToAddPageBtn';
import { AntdConfigProvider } from '@/config/antdConfigProvider';
@@ -95,6 +96,9 @@ function CredentialsList() {
return (
<AntdConfigProvider>
<ProTable
+ locale={{
+ emptyText: <ListEmptyState resource="credentials" />,
+ }}
columns={columns}
dataSource={data.list}
rowKey="id"
diff --git a/src/routes/consumers/index.tsx b/src/routes/consumers/index.tsx
index 35f966783..a316ca56a 100644
--- a/src/routes/consumers/index.tsx
+++ b/src/routes/consumers/index.tsx
@@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next';
import { getConsumerListQueryOptions, useConsumerList } from '@/apis/hooks';
import { CopyableId } from '@/components/page/CopyableId';
import { DeleteResourceBtn } from '@/components/page/DeleteResourceBtn';
+import { ListEmptyState } from '@/components/page/ListEmptyState';
import PageHeader from '@/components/page/PageHeader';
import { ToAddPageBtn, ToDetailPageBtn } from '@/components/page/ToAddPageBtn';
import { AntdConfigProvider } from '@/config/antdConfigProvider';
@@ -87,6 +88,9 @@ function ConsumersList() {
return (
<AntdConfigProvider>
<ProTable
+ locale={{
+ emptyText: <ListEmptyState resource="consumers" />,
+ }}
columns={columns}
dataSource={data.list}
rowKey="username"
diff --git a/src/routes/global_rules/index.tsx
b/src/routes/global_rules/index.tsx
index bc0325e19..f6b236f32 100644
--- a/src/routes/global_rules/index.tsx
+++ b/src/routes/global_rules/index.tsx
@@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next';
import { getGlobalRuleListQueryOptions, useGlobalRuleList } from
'@/apis/hooks';
import { CopyableId } from '@/components/page/CopyableId';
import { DeleteResourceBtn } from '@/components/page/DeleteResourceBtn';
+import { ListEmptyState } from '@/components/page/ListEmptyState';
import PageHeader from '@/components/page/PageHeader';
import { ToAddPageBtn, ToDetailPageBtn } from '@/components/page/ToAddPageBtn';
import { AntdConfigProvider } from '@/config/antdConfigProvider';
@@ -85,6 +86,9 @@ function GlobalRulesList() {
return (
<AntdConfigProvider>
<ProTable
+ locale={{
+ emptyText: <ListEmptyState resource="globalRules" />,
+ }}
columns={columns}
dataSource={data.list}
rowKey="id"
diff --git a/src/routes/plugin_configs/index.tsx
b/src/routes/plugin_configs/index.tsx
index 1716b179b..690536dcc 100644
--- a/src/routes/plugin_configs/index.tsx
+++ b/src/routes/plugin_configs/index.tsx
@@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next';
import { getPluginConfigListQueryOptions, usePluginConfigList } from
'@/apis/hooks';
import { CopyableId } from '@/components/page/CopyableId';
import { DeleteResourceBtn } from '@/components/page/DeleteResourceBtn';
+import { ListEmptyState } from '@/components/page/ListEmptyState';
import PageHeader from '@/components/page/PageHeader';
import { ToAddPageBtn, ToDetailPageBtn } from '@/components/page/ToAddPageBtn';
import { AntdConfigProvider } from '@/config/antdConfigProvider';
@@ -95,6 +96,9 @@ function PluginConfigsList() {
return (
<AntdConfigProvider>
<ProTable
+ locale={{
+ emptyText: <ListEmptyState resource="pluginConfigs" />,
+ }}
columns={columns}
dataSource={data.list}
rowKey="id"
diff --git a/src/routes/protos/index.tsx b/src/routes/protos/index.tsx
index c1b1075fa..3249614bd 100644
--- a/src/routes/protos/index.tsx
+++ b/src/routes/protos/index.tsx
@@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next';
import { getProtoListQueryOptions, useProtoList } from '@/apis/hooks';
import { CopyableId } from '@/components/page/CopyableId';
import { DeleteResourceBtn } from '@/components/page/DeleteResourceBtn';
+import { ListEmptyState } from '@/components/page/ListEmptyState';
import PageHeader from '@/components/page/PageHeader';
import { ToAddPageBtn, ToDetailPageBtn } from '@/components/page/ToAddPageBtn';
import { AntdConfigProvider } from '@/config/antdConfigProvider';
@@ -75,6 +76,9 @@ function RouteComponent() {
<PageHeader title={t('sources.protos')} />
<AntdConfigProvider>
<ProTable
+ locale={{
+ emptyText: <ListEmptyState resource="protos" />,
+ }}
columns={columns}
dataSource={data?.list || []}
rowKey="id"
diff --git a/src/routes/routes/index.tsx b/src/routes/routes/index.tsx
index d7af823da..773c83864 100644
--- a/src/routes/routes/index.tsx
+++ b/src/routes/routes/index.tsx
@@ -24,6 +24,7 @@ import { getRouteListQueryOptions, useRouteList } from
'@/apis/hooks';
import type { WithServiceIdFilter } from '@/apis/routes';
import { CopyableId } from '@/components/page/CopyableId';
import { DeleteResourceBtn } from '@/components/page/DeleteResourceBtn';
+import { ListEmptyState } from '@/components/page/ListEmptyState';
import PageHeader from '@/components/page/PageHeader';
import { ToAddPageBtn, ToDetailPageBtn } from '@/components/page/ToAddPageBtn';
import { AntdConfigProvider } from '@/config/antdConfigProvider';
@@ -98,6 +99,9 @@ export const RouteList = (props: RouteListProps) => {
return (
<AntdConfigProvider>
<ProTable
+ locale={{
+ emptyText: <ListEmptyState resource="routes" />,
+ }}
columns={columns}
dataSource={data.list}
rowKey="id"
diff --git a/src/routes/secrets/index.tsx b/src/routes/secrets/index.tsx
index 769426a55..8d61834bb 100644
--- a/src/routes/secrets/index.tsx
+++ b/src/routes/secrets/index.tsx
@@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next';
import { getSecretListQueryOptions, useSecretList } from '@/apis/hooks';
import { CopyableId } from '@/components/page/CopyableId';
import { DeleteResourceBtn } from '@/components/page/DeleteResourceBtn';
+import { ListEmptyState } from '@/components/page/ListEmptyState';
import PageHeader from '@/components/page/PageHeader';
import { ToAddPageBtn, ToDetailPageBtn } from '@/components/page/ToAddPageBtn';
import { AntdConfigProvider } from '@/config/antdConfigProvider';
@@ -83,6 +84,9 @@ function SecretList() {
return (
<AntdConfigProvider>
<ProTable
+ locale={{
+ emptyText: <ListEmptyState resource="secrets" />,
+ }}
columns={columns}
dataSource={data?.list || []}
rowKey="id"
diff --git a/src/routes/services/index.tsx b/src/routes/services/index.tsx
index d507ae626..e6e5d7d6b 100644
--- a/src/routes/services/index.tsx
+++ b/src/routes/services/index.tsx
@@ -17,13 +17,13 @@
import type { ProColumns } from '@ant-design/pro-components';
import { ProTable } from '@ant-design/pro-components';
import { createFileRoute } from '@tanstack/react-router';
-import { Empty } from 'antd';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { getServiceListQueryOptions, useServiceList } from '@/apis/hooks';
import { CopyableId } from '@/components/page/CopyableId';
import { DeleteResourceBtn } from '@/components/page/DeleteResourceBtn';
+import { ListEmptyState } from '@/components/page/ListEmptyState';
import PageHeader from '@/components/page/PageHeader';
import { ToAddPageBtn, ToDetailPageBtn } from '@/components/page/ToAddPageBtn';
import { AntdConfigProvider } from '@/config/antdConfigProvider';
@@ -95,12 +95,7 @@ const ServiceList = () => {
<AntdConfigProvider>
<ProTable
locale={{
- emptyText: (
- <Empty
- description={t('services.empty')}
- image={Empty.PRESENTED_IMAGE_SIMPLE}
- />
- ),
+ emptyText: <ListEmptyState resource="services" />,
}}
columns={columns}
dataSource={data.list}
diff --git a/src/routes/ssls/index.tsx b/src/routes/ssls/index.tsx
index 92e0f6a72..10b1f9661 100644
--- a/src/routes/ssls/index.tsx
+++ b/src/routes/ssls/index.tsx
@@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next';
import { getSSLListQueryOptions, useSSLList } from '@/apis/hooks';
import { CopyableId } from '@/components/page/CopyableId';
import { DeleteResourceBtn } from '@/components/page/DeleteResourceBtn';
+import { ListEmptyState } from '@/components/page/ListEmptyState';
import PageHeader from '@/components/page/PageHeader';
import { ToAddPageBtn, ToDetailPageBtn } from '@/components/page/ToAddPageBtn';
import { AntdConfigProvider } from '@/config/antdConfigProvider';
@@ -95,6 +96,9 @@ function RouteComponent() {
<PageHeader title={t('sources.ssls')} />
<AntdConfigProvider>
<ProTable
+ locale={{
+ emptyText: <ListEmptyState resource="ssls" />,
+ }}
columns={columns}
dataSource={data?.list}
rowKey="id"
diff --git a/src/routes/stream_routes/index.tsx
b/src/routes/stream_routes/index.tsx
index 95209ffc5..38f40f75d 100644
--- a/src/routes/stream_routes/index.tsx
+++ b/src/routes/stream_routes/index.tsx
@@ -24,6 +24,7 @@ import { getStreamRouteListQueryOptions, useStreamRouteList }
from '@/apis/hooks
import type { WithServiceIdFilter } from '@/apis/routes';
import { CopyableId } from '@/components/page/CopyableId';
import { DeleteResourceBtn } from '@/components/page/DeleteResourceBtn';
+import { ListEmptyState } from '@/components/page/ListEmptyState';
import PageHeader from '@/components/page/PageHeader';
import { ToAddPageBtn, ToDetailPageBtn } from '@/components/page/ToAddPageBtn';
import { StreamRoutesErrorComponent } from
'@/components/page-slice/stream_routes/ErrorComponent';
@@ -104,6 +105,9 @@ export const StreamRouteList = (props:
StreamRouteListProps) => {
return (
<AntdConfigProvider>
<ProTable
+ locale={{
+ emptyText: <ListEmptyState resource="streamRoutes" />,
+ }}
columns={columns}
dataSource={data.list}
rowKey="id"
diff --git a/src/routes/upstreams/index.tsx b/src/routes/upstreams/index.tsx
index 55a014184..5d47814c3 100644
--- a/src/routes/upstreams/index.tsx
+++ b/src/routes/upstreams/index.tsx
@@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next';
import { getUpstreamListQueryOptions, useUpstreamList } from '@/apis/hooks';
import { CopyableId } from '@/components/page/CopyableId';
import { DeleteResourceBtn } from '@/components/page/DeleteResourceBtn';
+import { ListEmptyState } from '@/components/page/ListEmptyState';
import PageHeader from '@/components/page/PageHeader';
import { ToAddPageBtn, ToDetailPageBtn } from '@/components/page/ToAddPageBtn';
import { AntdConfigProvider } from '@/config/antdConfigProvider';
@@ -96,6 +97,9 @@ function RouteComponent() {
<PageHeader title={t('sources.upstreams')} />
<AntdConfigProvider>
<ProTable
+ locale={{
+ emptyText: <ListEmptyState resource="upstreams" />,
+ }}
columns={columns}
dataSource={data?.list}
rowKey="id"