You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@hertzbeat.apache.org by zh...@apache.org on 2026/06/03 08:18:21 UTC

(hertzbeat) 11/50: feat(web-next): restore collector plugin label parity

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

zqr10159 pushed a commit to branch 2.0.0
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git

commit cae4d7576509ad76380796d6553f7915344aaac4
Author: Logic <zq...@dromara.org>
AuthorDate: Fri May 29 01:02:59 2026 +0800

    feat(web-next): restore collector plugin label parity
---
 web-next/app/setting/collector/page.test.tsx       |  433 ++++++-
 web-next/app/setting/collector/page.tsx            |   44 +-
 .../setting/collector/setting-collector-page.tsx   |  340 ++++++
 web-next/app/setting/define/page.test.tsx          |  910 +++++++++++++-
 web-next/app/setting/define/page.tsx               |  153 +--
 .../app/setting/define/setting-define-page.tsx     |  257 ++++
 web-next/app/setting/labels/page.test.tsx          |  602 +++++++++-
 web-next/app/setting/labels/page.tsx               |   71 +-
 .../app/setting/labels/setting-labels-page.tsx     |  215 ++++
 web-next/app/setting/plugins/page.test.tsx         |  404 ++++++-
 web-next/app/setting/plugins/page.tsx              |   70 +-
 .../app/setting/plugins/setting-plugins-page.tsx   |  325 +++++
 .../pages/collector-manage-surface.test.tsx        |  830 ++++++++++++-
 .../components/pages/collector-manage-surface.tsx  |  645 +++++++++-
 .../components/pages/label-manage-surface.test.tsx |  887 +++++++++++++-
 web-next/components/pages/label-manage-surface.tsx |  493 ++++++--
 .../pages/plugin-manage-surface.test.tsx           | 1037 +++++++++++++++-
 .../components/pages/plugin-manage-surface.tsx     | 1253 +++++++++++++++++---
 .../pages/setting-define-surface.test.tsx          | 1151 ++++++++++++++----
 .../components/pages/setting-define-surface.tsx    |  751 ++++++++----
 web-next/lib/collector-health-evidence.test.ts     |   40 +-
 web-next/lib/collector-health-evidence.ts          |   33 +-
 web-next/lib/collector-manage/controller.test.ts   |   53 +-
 web-next/lib/collector-manage/controller.ts        |   81 ++
 web-next/lib/collector-manage/query-state.test.ts  |   12 +-
 web-next/lib/collector-manage/query-state.ts       |    6 +-
 web-next/lib/collector-manage/view-model.test.ts   |   29 +-
 web-next/lib/collector-manage/view-model.ts        |   32 +-
 web-next/lib/label-manage/controller.test.ts       |   87 +-
 web-next/lib/label-manage/controller.ts            |   26 +-
 web-next/lib/label-manage/query-state.test.ts      |    4 +-
 web-next/lib/label-manage/query-state.ts           |    6 +-
 web-next/lib/label-manage/view-model.test.ts       |   16 +-
 web-next/lib/label-manage/view-model.ts            |   25 +-
 web-next/lib/plugin-manage/controller.test.ts      |  128 +-
 web-next/lib/plugin-manage/controller.ts           |  140 ++-
 web-next/lib/plugin-manage/query-state.test.ts     |    6 +-
 web-next/lib/plugin-manage/query-state.ts          |    6 +-
 web-next/lib/plugin-manage/view-model.test.ts      |   33 +-
 web-next/lib/plugin-manage/view-model.ts           |   24 +-
 web-next/lib/setting-define/controller.test.ts     |  138 ++-
 web-next/lib/setting-define/controller.ts          |  156 ++-
 web-next/lib/setting-define/query-state.test.ts    |   25 +-
 web-next/lib/setting-define/query-state.ts         |   32 +-
 web-next/lib/setting-define/view-model.test.ts     |  186 ++-
 web-next/lib/setting-define/view-model.ts          |  126 +-
 46 files changed, 10801 insertions(+), 1520 deletions(-)

diff --git a/web-next/app/setting/collector/page.test.tsx b/web-next/app/setting/collector/page.test.tsx
index d34c05efa6..9f2406a33a 100644
--- a/web-next/app/setting/collector/page.test.tsx
+++ b/web-next/app/setting/collector/page.test.tsx
@@ -1,12 +1,21 @@
+// @vitest-environment jsdom
+
 import React from 'react';
+import { readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+import { act } from 'react';
+import { createRoot, type Root } from 'react-dom/client';
 import { renderToStaticMarkup } from 'react-dom/server';
-import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
 import { createTranslatorMock } from '../../../test/i18n-test-helper';
 
 (globalThis as { React?: typeof React }).React = React;
+(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
 
 const mockState = vi.hoisted(() => ({
   lastLoad: null as null | (() => Promise<unknown>),
+  lastSurfaceProps: null as null | Record<string, any>,
+  renderErrorMessage: null as string | null,
   renderData: {
     list: {
       content: [
@@ -31,23 +40,20 @@ const mockState = vi.hoisted(() => ({
 }));
 
 const apiMessageGet = vi.hoisted(() => vi.fn());
+const apiMessageDelete = vi.hoisted(() => vi.fn());
+const apiMessagePost = vi.hoisted(() => vi.fn());
+const apiMessagePut = vi.hoisted(() => vi.fn());
 const loadCollectorData = vi.hoisted(() => vi.fn());
+const buildCollectorDeployCommands = vi.hoisted(() => vi.fn());
+const deleteCollectors = vi.hoisted(() => vi.fn());
+const generateCollectorIdentity = vi.hoisted(() => vi.fn());
+const goOnlineCollectors = vi.hoisted(() => vi.fn());
+const goOfflineCollectors = vi.hoisted(() => vi.fn());
 
 vi.mock('@/components/providers/i18n-provider', () => ({
   useI18n: () => ({
     t: createTranslatorMock({
-      locale: 'zh-CN',
-      overrides: {
-        'menu.advanced.collector': '采集集群',
-        'collector.deploy': '部署采集器',
-        'collector.online': '上线采集器',
-        'collector.offline': '下线采集器',
-        'collector.delete': '删除采集器',
-        'collector.name': '采集器名称',
-        'collector.status': '运行状态',
-        'common.refresh': '刷新',
-        'common.search': '搜索'
-      }
+      locale: 'zh-CN'
     })
   })
 }));
@@ -55,37 +61,144 @@ vi.mock('@/components/providers/i18n-provider', () => ({
 vi.mock('@/components/workbench/client-workbench', () => ({
   ClientWorkbench: ({
     children,
-    load
+    load,
+    renderError
   }: {
     children: (data: unknown) => React.ReactNode;
     load: () => Promise<unknown>;
+    renderError?: (message: string, retry: () => void) => React.ReactNode;
   }) => {
     mockState.lastLoad = load;
+    if (mockState.renderErrorMessage && renderError) {
+      return <div data-client-workbench="true">{renderError(mockState.renderErrorMessage, vi.fn())}</div>;
+    }
     return <div data-client-workbench="true">{children(mockState.renderData)}</div>;
   }
 }));
 
 vi.mock('@/components/pages/collector-manage-surface', () => ({
-  CollectorManageSurface: ({ search, data }: any) => (
-    <div data-collector-manage-surface="otlp-cold-collector-console" data-collector-manage-style-baseline="hertzbeat-cold-matte">
-      <section data-collector-admin-layout="full-width-admin-list">
-        <span>刷新</span>
-        <span>部署采集器</span>
-        <span>上线采集器</span>
-        <span>下线采集器</span>
-        <span>删除采集器</span>
-        <span>搜索</span>
-        <span>采集集群</span>
-        <span>{search}</span>
-        <span>{data.list.content[0].collector.name}</span>
-        <span>{data.list.content[0].collector.ip}</span>
-      </section>
-    </div>
-  )
+  CollectorManageSurface: (props: any) => {
+    mockState.lastSurfaceProps = props;
+    const {
+      search,
+      data,
+      selectedCollectors,
+      actionMessage,
+      actionError,
+      actionMeta,
+      actionTone,
+      actionFeedbackContract,
+      loadError,
+      deleteTarget,
+      isDeletePending,
+      isLoadPending,
+      deployOpen,
+      deployName,
+      deployIdentity,
+      deployError,
+      deployCopyMessage,
+      isDeployPending,
+      deployNameValidationVisible,
+      onDeploy,
+      onDeployNameChange,
+      onDeployGenerate,
+      onDeployClose
+    } = props;
+    return (
+      <div
+        data-collector-manage-surface="otlp-cold-collector-console"
+        data-collector-manage-style-baseline="hertzbeat-cold-matte"
+        data-collector-delete-warning-contract="angular-no-select-warning"
+        data-collector-delete-confirm-contract="angular-modal-confirm"
+        data-collector-delete-confirm-contract-owner="hertzbeat-ui-confirm-dialog"
+        data-collector-delete-feedback-contract="angular-delete-notify"
+        data-collector-delete-feedback-contract-owner="hertzbeat-ui-inline-feedback"
+        data-collector-delete-api-contract="angular-repeated-collectors-query"
+        data-collector-delete-page-clamp-contract="angular-update-page-index"
+        data-collector-delete-page-clamp-owner="route-state-contract"
+        data-collector-search-submit-contract="angular-enter-and-clear"
+        data-collector-search-clear-contract="angular-cleared-load"
+        data-collector-search-clear-owner="shared-search-row"
+        data-collector-operate-warning-contract="angular-no-select-online-offline"
+        data-collector-operate-confirm-contract="angular-modal-confirm"
+        data-collector-operate-confirm-contract-owner="hertzbeat-ui-confirm-dialog"
+        data-collector-operate-feedback-contract="angular-operate-notify"
+        data-collector-operate-feedback-contract-owner="hertzbeat-ui-inline-feedback"
+        data-collector-operate-api-contract="angular-put-repeated-collectors-query"
+        data-collector-mutation-loading-contract="angular-nz-table-loading"
+        data-collector-load-failure-contract="angular-console-only-shell"
+        data-collector-load-failure-owner="collector-route-controller"
+        data-collector-load-failure={loadError ? 'angular-console-only-shell' : 'none'}
+        data-collector-table-loading-contract="angular-load-collectors-table"
+        data-collector-table-loading-owner="angular-nz-table-loading"
+        data-collector-table-loading-scope="load-search-refresh-pagination-mutation"
+        data-collector-table-loading-source={isLoadPending || isDeletePending ? 'route-load-or-mutation' : 'idle'}
+        data-collector-deploy-modal-contract="angular-generate-identity-modal"
+        data-collector-deploy-api-contract="angular-post-generate-identity"
+        data-collector-deploy-failure-contract="angular-apply-fail-notification"
+        data-collector-deploy-validation-contract="angular-submit-marks-required"
+        data-collector-deploy-validation-raw-contract="angular-required-before-trim"
+        data-collector-deploy-validation-raw-owner="collector-route-controller"
+        data-collector-deploy-loading-contract="angular-nz-ok-loading"
+        data-collector-deploy-mask-contract="angular-mask-closable-false"
+        data-collector-deploy-width-contract="angular-width-45-percent"
+        data-collector-deploy-field-layout-contract="angular-label-7-control-12"
+        data-collector-deploy-field-layout-owner="route-form-field-grid"
+        data-collector-deploy-command-contract="angular-docker-package-shells"
+        data-collector-deploy-command-owner="hertzbeat-ui-code-editor"
+        data-collector-deploy-package-link-contract="angular-github-releases-link"
+        data-collector-deploy-copy-contract="angular-copy-identity"
+        data-collector-deploy-copy-feedback-contract="angular-copy-success-duration-800"
+        data-collector-deploy-close-reset-contract="angular-close-clears-name"
+        data-collector-deploy-close-reset-owner="collector-route-controller"
+        data-collector-deploy-close-pending-result-contract="angular-close-ignores-late-generate"
+        data-collector-deploy-close-pending-result-owner="collector-route-controller"
+        data-collector-pagination-contract="angular-search-pagination"
+        data-collector-pagination-owner="hertzbeat-ui-pagination-bar"
+      >
+        <section data-collector-admin-layout="full-width-admin-list">
+          <span>刷新</span>
+          <span>部署采集器</span>
+          <span>上线采集器</span>
+          <span>下线采集器</span>
+          <span>删除采集器</span>
+          <span>搜索</span>
+          <span>采集集群</span>
+          <span>{search}</span>
+          <span>{selectedCollectors?.length ?? 0}</span>
+          <span>{actionMessage ?? 'no-action-message'}</span>
+          <span>{actionError ?? 'no-action-error'}</span>
+          <span>{actionMeta ?? 'no-action-meta'}</span>
+          <span>{actionTone ?? 'no-action-tone'}</span>
+          <span>{actionFeedbackContract ?? 'no-action-feedback-contract'}</span>
+          <span>{deleteTarget ? deleteTarget.label : 'no-delete-target'}</span>
+          <span>{isDeletePending ? 'delete-pending' : 'delete-idle'}</span>
+          <span>{isLoadPending ? 'route-load-pending' : 'route-load-idle'}</span>
+          <span data-test-deploy-open={deployOpen ? 'true' : 'false'}>{deployOpen ? 'deploy-open' : 'deploy-closed'}</span>
+          <span data-test-deploy-name={deployName ?? ''}>{deployName ?? ''}</span>
+          <span data-test-deploy-identity={deployIdentity ?? ''}>{deployIdentity ?? ''}</span>
+          <span data-test-deploy-error={deployError ?? ''}>{deployError ?? ''}</span>
+          <span data-test-deploy-copy={deployCopyMessage ?? ''}>{deployCopyMessage ?? ''}</span>
+          <span data-test-deploy-pending={isDeployPending ? 'true' : 'false'}>{isDeployPending ? 'deploy-pending' : 'deploy-idle'}</span>
+          <span>{deployNameValidationVisible ? 'deploy-name-dirty-invalid' : 'deploy-name-pristine'}</span>
+          <span>{data.list.content[0]?.collector.name ?? 'no-collector-name'}</span>
+          <span>{data.list.content[0]?.collector.ip ?? 'no-collector-ip'}</span>
+          <span>collector-pagination</span>
+          <button data-test-deploy-open-button="true" onClick={onDeploy}>deploy</button>
+          <button data-test-deploy-name-button="true" onClick={() => onDeployNameChange?.(' edge-b ')}>name</button>
+          <button data-test-deploy-generate-button="true" onClick={onDeployGenerate}>generate</button>
+          <button data-test-deploy-close-button="true" onClick={onDeployClose}>close</button>
+        </section>
+      </div>
+    );
+  }
 }));
 
 vi.mock('@/lib/api-client', () => ({
-  apiMessageGet
+  apiMessageGet,
+  apiMessageDelete,
+  apiMessagePost,
+  apiMessagePut
 }));
 
 vi.mock('@/lib/format', () => ({
@@ -93,17 +206,45 @@ vi.mock('@/lib/format', () => ({
 }));
 
 vi.mock('@/lib/collector-manage/controller', () => ({
-  loadCollectorData
+  loadCollectorData,
+  buildCollectorDeployCommands,
+  deleteCollectors,
+  generateCollectorIdentity,
+  goOnlineCollectors,
+  goOfflineCollectors
 }));
 
 describe('setting collector page', () => {
+  let interactionContainer: HTMLDivElement | null = null;
+  let interactionRoot: Root | null = null;
+
   beforeEach(() => {
     mockState.lastLoad = null;
+    mockState.lastSurfaceProps = null;
+    mockState.renderErrorMessage = null;
     apiMessageGet.mockReset();
+    apiMessageDelete.mockReset();
+    apiMessagePost.mockReset();
+    apiMessagePut.mockReset();
     loadCollectorData.mockReset().mockImplementation(async (apiGet, query) => {
-      await apiGet(`/collector?pageIndex=0&pageSize=8${query.search ? `&name=${query.search}` : ''}`);
+      await apiGet(`/collector?pageIndex=${query.pageIndex}&pageSize=${query.pageSize}${query.search ? `&name=${query.search}` : ''}`);
       return mockState.renderData;
     });
+    buildCollectorDeployCommands.mockReset().mockReturnValue({ dockerShell: 'docker shell', packageShell: 'package shell' });
+    deleteCollectors.mockReset().mockResolvedValue(undefined);
+    generateCollectorIdentity.mockReset().mockResolvedValue({ identity: 'identity-123', host: '10.0.0.10' });
+    goOnlineCollectors.mockReset().mockResolvedValue(undefined);
+    goOfflineCollectors.mockReset().mockResolvedValue(undefined);
+  });
+
+  afterEach(async () => {
+    await act(async () => {
+      interactionRoot?.unmount();
+      await Promise.resolve();
+    });
+    interactionRoot = null;
+    interactionContainer?.remove();
+    interactionContainer = null;
   });
 
   it('renders the collector toolbar and table shell inside the route contract', async () => {
@@ -113,6 +254,53 @@ describe('setting collector page', () => {
     expect(html).toContain('data-client-workbench="true"');
     expect(html).toContain('data-collector-manage-surface="otlp-cold-collector-console"');
     expect(html).toContain('data-collector-manage-style-baseline="hertzbeat-cold-matte"');
+    expect(html).toContain('data-collector-delete-warning-contract="angular-no-select-warning"');
+    expect(html).toContain('data-collector-delete-confirm-contract="angular-modal-confirm"');
+    expect(html).toContain('data-collector-delete-confirm-contract-owner="hertzbeat-ui-confirm-dialog"');
+    expect(html).toContain('data-collector-delete-feedback-contract="angular-delete-notify"');
+    expect(html).toContain('data-collector-delete-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-collector-delete-api-contract="angular-repeated-collectors-query"');
+    expect(html).toContain('data-collector-delete-page-clamp-contract="angular-update-page-index"');
+    expect(html).toContain('data-collector-delete-page-clamp-owner="route-state-contract"');
+    expect(html).toContain('data-collector-search-submit-contract="angular-enter-and-clear"');
+    expect(html).toContain('data-collector-search-clear-contract="angular-cleared-load"');
+    expect(html).toContain('data-collector-search-clear-owner="shared-search-row"');
+    expect(html).toContain('data-collector-operate-warning-contract="angular-no-select-online-offline"');
+    expect(html).toContain('data-collector-operate-confirm-contract="angular-modal-confirm"');
+    expect(html).toContain('data-collector-operate-confirm-contract-owner="hertzbeat-ui-confirm-dialog"');
+    expect(html).toContain('data-collector-operate-feedback-contract="angular-operate-notify"');
+    expect(html).toContain('data-collector-operate-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-collector-operate-api-contract="angular-put-repeated-collectors-query"');
+    expect(html).toContain('data-collector-mutation-loading-contract="angular-nz-table-loading"');
+    expect(html).toContain('data-collector-load-failure-contract="angular-console-only-shell"');
+    expect(html).toContain('data-collector-load-failure-owner="collector-route-controller"');
+    expect(html).toContain('data-collector-load-failure="none"');
+    expect(html).toContain('data-collector-table-loading-contract="angular-load-collectors-table"');
+    expect(html).toContain('data-collector-table-loading-owner="angular-nz-table-loading"');
+    expect(html).toContain('data-collector-table-loading-scope="load-search-refresh-pagination-mutation"');
+    expect(html).toContain('data-collector-table-loading-source="idle"');
+    expect(html).toContain('data-collector-deploy-modal-contract="angular-generate-identity-modal"');
+    expect(html).toContain('data-collector-deploy-api-contract="angular-post-generate-identity"');
+    expect(html).toContain('data-collector-deploy-failure-contract="angular-apply-fail-notification"');
+    expect(html).toContain('data-collector-deploy-validation-contract="angular-submit-marks-required"');
+    expect(html).toContain('data-collector-deploy-validation-raw-contract="angular-required-before-trim"');
+    expect(html).toContain('data-collector-deploy-validation-raw-owner="collector-route-controller"');
+    expect(html).toContain('data-collector-deploy-loading-contract="angular-nz-ok-loading"');
+    expect(html).toContain('data-collector-deploy-mask-contract="angular-mask-closable-false"');
+    expect(html).toContain('data-collector-deploy-width-contract="angular-width-45-percent"');
+    expect(html).toContain('data-collector-deploy-field-layout-contract="angular-label-7-control-12"');
+    expect(html).toContain('data-collector-deploy-field-layout-owner="route-form-field-grid"');
+    expect(html).toContain('data-collector-deploy-command-contract="angular-docker-package-shells"');
+    expect(html).toContain('data-collector-deploy-command-owner="hertzbeat-ui-code-editor"');
+    expect(html).toContain('data-collector-deploy-package-link-contract="angular-github-releases-link"');
+    expect(html).toContain('data-collector-deploy-copy-contract="angular-copy-identity"');
+    expect(html).toContain('data-collector-deploy-copy-feedback-contract="angular-copy-success-duration-800"');
+    expect(html).toContain('data-collector-deploy-close-reset-contract="angular-close-clears-name"');
+    expect(html).toContain('data-collector-deploy-close-reset-owner="collector-route-controller"');
+    expect(html).toContain('data-collector-deploy-close-pending-result-contract="angular-close-ignores-late-generate"');
+    expect(html).toContain('data-collector-deploy-close-pending-result-owner="collector-route-controller"');
+    expect(html).toContain('data-collector-pagination-contract="angular-search-pagination"');
+    expect(html).toContain('data-collector-pagination-owner="hertzbeat-ui-pagination-bar"');
     expect(html).toContain('data-collector-admin-layout="full-width-admin-list"');
     expect(html).not.toContain('data-collector-summary-rail=');
     expect(html).not.toContain('当前采集集群');
@@ -124,11 +312,190 @@ describe('setting collector page', () => {
     expect(html).toContain('删除采集器');
     expect(html).toContain('搜索');
     expect(html).toContain('采集集群');
+    expect(html).toContain('no-action-message');
+    expect(html).toContain('no-action-error');
+    expect(html).toContain('no-action-meta');
+    expect(html).toContain('success');
+    expect(html).toContain('no-action-feedback-contract');
+    expect(html).toContain('no-delete-target');
+    expect(html).toContain('delete-idle');
+    expect(html).toContain('route-load-idle');
+    expect(html).toContain('deploy-name-pristine');
     expect(html).toContain('edge-a');
     expect(html).toContain('10.0.0.1');
+    expect(html).toContain('collector-pagination');
 
     await mockState.lastLoad?.();
 
     expect(apiMessageGet).toHaveBeenCalledWith('/collector?pageIndex=0&pageSize=8');
   });
+
+  it('keeps the collector shell mounted on list load failure like Angular console-only handling', async () => {
+    mockState.renderErrorMessage = 'backend refused collector load';
+    const { default: SettingCollectorPage } = await import('./page');
+    const html = renderToStaticMarkup(<SettingCollectorPage />);
+
+    expect(html).toContain('data-client-workbench="true"');
+    expect(html).toContain('data-collector-manage-surface="otlp-cold-collector-console"');
+    expect(html).toContain('data-collector-load-failure-contract="angular-console-only-shell"');
+    expect(html).toContain('data-collector-load-failure-owner="collector-route-controller"');
+    expect(html).toContain('data-collector-load-failure="angular-console-only-shell"');
+    expect(html).toContain('data-collector-admin-layout="full-width-admin-list"');
+    expect(html).toContain('collector-pagination');
+    expect(html).not.toContain('backend refused collector load');
+  });
+
+  it('keeps collector management remounts on a short settled cache window while reloads invalidate it', () => {
+    const source = readFileSync(resolve(process.cwd(), 'app/setting/collector/setting-collector-page.tsx'), 'utf8');
+
+    expect(source).toContain('SETTING_COLLECTOR_SETTLED_CACHE_TTL_MS = 10_000');
+    expect(source).toContain("['setting-collector', collectorListUrl, reloadVersion].join(':')");
+    expect(source).toContain('[collectorListUrl, reloadVersion]');
+    expect(source).toContain("const [query, setQuery] = useState<CollectorQueryState>({ pageIndex: 0, pageSize: 8, search: '' })");
+    expect(source).toContain('const [selectedCollectors, setSelectedCollectors] = useState<string[]>([])');
+    expect(source).toContain('const [actionMessage, setActionMessage] = useState<string | null>(null)');
+    expect(source).toContain('const [actionError, setActionError] = useState<string | null>(null)');
+    expect(source).toContain('const [actionMeta, setActionMeta] = useState<string | null>(null)');
+    expect(source).toContain("const [actionTone, setActionTone] = useState<'success' | 'warning' | 'critical'>('success')");
+    expect(source).toContain('const [actionFeedbackContract, setActionFeedbackContract] = useState<string | null>(null)');
+    expect(source).toContain('const [deleteTarget, setDeleteTarget] = useState<CollectorDeleteTarget | null>(null)');
+    expect(source).toContain('const [isDeletePending, setIsDeletePending] = useState(false)');
+    expect(source).toContain('const [isLoadPending, setIsLoadPending] = useState(false)');
+    expect(source).toContain('buildFallbackCollectorData');
+    expect(source).toContain('renderError={(message, retry) => renderCollectorSurface(buildFallbackCollectorData(), message, retry)}');
+    expect(source).toContain('loadError={loadError}');
+    expect(source).toContain('const [deployOpen, setDeployOpen] = useState(false)');
+    expect(source).toContain("const [deployHost, setDeployHost] = useState('127.0.0.1')");
+    expect(source).toContain('const [deployDockerShell, setDeployDockerShell] = useState');
+    expect(source).toContain('const [deployPackageShell, setDeployPackageShell] = useState');
+    expect(source).toContain('const [deployNameValidationVisible, setDeployNameValidationVisible] = useState(false)');
+    expect(source).toContain("setActionError(t('common.notify.no-select-delete'))");
+    expect(source).toContain("setActionError(t(action === 'online' ? 'collector.notify.no-select-online' : 'collector.notify.no-select-offline'))");
+    expect(source).toContain("setActionMessage(t(deleteTarget.action === 'online' || deleteTarget.action === 'offline' ? 'common.notify.operate-success' : 'common.notify.delete-success'))");
+    expect(source).toContain("setActionError(t(isOperateAction ? 'common.notify.operate-fail' : 'common.notify.delete-fail'))");
+    expect(source).toContain('setActionMeta(error instanceof Error ? error.message : null)');
+    expect(source).toContain('const remainingTotal = Math.max(0, data.list.totalElements - targetCollectors.length)');
+    expect(source).toContain('const lastPageIndex = Math.max(0, Math.ceil(remainingTotal / current.pageSize) - 1)');
+    expect(source).toContain('current.pageIndex > lastPageIndex ? { ...current, pageIndex: lastPageIndex } : current');
+    expect(source).toContain('setSelectedCollectors([]);');
+    expect(source).not.toContain('setSelectedCollectors(collectors => collectors.filter');
+    expect(source).toContain('deleteCollectors(apiMessageDelete, targetCollectors)');
+    expect(source).toContain('goOnlineCollectors(apiMessagePut, targetCollectors)');
+    expect(source).toContain('goOfflineCollectors(apiMessagePut, targetCollectors)');
+    expect(source).toContain('generateCollectorIdentity(apiMessagePost, collector)');
+    expect(source).toContain('buildCollectorDeployCommands(t, identity, managerHost)');
+    expect(source).toContain('COLLECTOR_DEPLOY_COPY_SUCCESS_DURATION_MS = 800');
+    expect(source).toContain('const deployCopyClearTimerRef = useRef');
+    expect(source).toContain('const deployGenerationRequestRef = useRef(0);');
+    expect(source).toContain('const clearDeployCopyTimer = useCallback');
+    expect(source).toContain('const closeDeployDialog = useCallback');
+    expect(source).toContain('deployGenerationRequestRef.current += 1;');
+    expect(source).toContain("setDeployOpen(false);");
+    expect(source).toContain("setDeployName('');");
+    expect(source).toContain('window.clearTimeout(deployCopyClearTimerRef.current)');
+    expect(source).toContain('deployCopyClearTimerRef.current = window.setTimeout');
+    expect(source).toContain('COLLECTOR_DEPLOY_COPY_SUCCESS_DURATION_MS');
+    expect(source).toContain('setIsDeployPending(true);');
+    expect(source).toContain('setIsDeployPending(false);');
+    expect(source).toContain("setDeployError(error instanceof Error ? error.message : t('common.notify.apply-fail'))");
+    expect(source).toContain('setIsLoadPending(true);');
+    expect(source).toContain('setIsLoadPending(false);');
+    expect(source).toContain('return await loadCollectorData(apiMessageGet, query);');
+    expect(source).toContain("setDeployError(error instanceof Error ? error.message : t('common.notify.apply-fail'))");
+    expect(source).toContain("setDeployCopyMessage(t('common.notify.copy-success'))");
+    expect(source).toContain('onRefresh={() => {');
+    expect(source).toContain('setReloadVersion(version => version + 1);');
+    expect(source).toContain('selectedCollectors={selectedCollectors}');
+    expect(source).toContain('actionMeta={actionMeta}');
+    expect(source).toContain('actionFeedbackContract={actionFeedbackContract}');
+    expect(source).toContain('deployOpen={deployOpen}');
+    expect(source).toContain('deployIdentity={deployIdentity}');
+    expect(source).toContain('deployDockerShell={deployDockerShell}');
+    expect(source).toContain('deployPackageShell={deployPackageShell}');
+    expect(source).toContain('deployNameValidationVisible={deployNameValidationVisible}');
+    expect(source).toContain('onSelectedCollectorsChange={setSelectedCollectors}');
+    expect(source).toContain('onPageIndexChange={pageIndex => {');
+    expect(source).toContain('setQuery(current => ({ ...current, pageIndex }))');
+    expect(source).toContain('onPageSizeChange={pageSize => {');
+    expect(source).toContain('setQuery(current => ({ ...current, pageIndex: 0, pageSize }))');
+    expect(source).toContain('setQuery(current => ({ ...current, pageIndex: 0, search }))');
+    expect(source).toContain('onSearchClear={() => {');
+    expect(source).toContain("setSearch('');");
+    expect(source).toContain("setQuery(current => ({ ...current, pageIndex: 0, search: '' }))");
+    expect(source).toContain('onDeployGenerate={() => {');
+    expect(source).toContain('if (isDeployPending) return;');
+    expect(source).toContain('const collector = deployName.trim();');
+    expect(source).toContain('if (deployName.length === 0) {');
+    expect(source).toContain('const deployGenerationRequest = deployGenerationRequestRef.current + 1;');
+    expect(source).toContain('deployGenerationRequestRef.current = deployGenerationRequest;');
+    expect(source).toContain('if (deployGenerationRequestRef.current !== deployGenerationRequest) return;');
+    expect(source).toContain('setDeployNameValidationVisible(true);');
+    expect(source).toContain('setDeployNameValidationVisible(false);');
+    expect(source).toContain('onDeployCopy={value => {');
+    expect(source).toContain("onGoOnline={() => openBatchOperateTarget('online')}");
+    expect(source).toContain("onGoOffline={() => openBatchOperateTarget('offline')}");
+    expect(source).toContain('deleteTarget={deleteTarget}');
+    expect(source).toContain('isDeletePending={isDeletePending}');
+    expect(source).toContain('isLoadPending={isLoadPending}');
+    expect(source).toContain('cacheKey={collectorManageCacheKey}');
+    expect(source).toContain('cacheSettledTtlMs={SETTING_COLLECTOR_SETTLED_CACHE_TTL_MS}');
+  });
+
+  it('ignores late collector deploy identity results after the dialog is closed', async () => {
+    let resolveGenerate: ((value: { identity: string; host: string }) => void) | undefined;
+    generateCollectorIdentity.mockImplementationOnce(() => new Promise(resolve => {
+      resolveGenerate = resolve as (value: { identity: string; host: string }) => void;
+    }));
+    const { default: SettingCollectorPage } = await import('./page');
+    interactionContainer = document.createElement('div');
+    document.body.appendChild(interactionContainer);
+    interactionRoot = createRoot(interactionContainer);
+
+    await act(async () => {
+      interactionRoot?.render(<SettingCollectorPage />);
+      await Promise.resolve();
+    });
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onDeploy();
+      await Promise.resolve();
+    });
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onDeployNameChange(' edge-b ');
+      await Promise.resolve();
+    });
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onDeployGenerate();
+      await Promise.resolve();
+    });
+
+    expect(generateCollectorIdentity).toHaveBeenCalledWith(apiMessagePost, 'edge-b');
+    expect(mockState.lastSurfaceProps?.isDeployPending).toBe(true);
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onDeployClose();
+      await Promise.resolve();
+    });
+
+    expect(mockState.lastSurfaceProps?.deployOpen).toBe(false);
+    expect(mockState.lastSurfaceProps?.deployName).toBe('');
+    expect(mockState.lastSurfaceProps?.deployIdentity).toBe('');
+    expect(mockState.lastSurfaceProps?.isDeployPending).toBe(false);
+
+    await act(async () => {
+      resolveGenerate?.({ identity: 'late-identity', host: '10.0.0.20' });
+      await Promise.resolve();
+      await Promise.resolve();
+    });
+
+    expect(mockState.lastSurfaceProps?.deployOpen).toBe(false);
+    expect(mockState.lastSurfaceProps?.deployName).toBe('');
+    expect(mockState.lastSurfaceProps?.deployIdentity).toBe('');
+    expect(mockState.lastSurfaceProps?.deployDockerShell).toBe('');
+    expect(mockState.lastSurfaceProps?.deployPackageShell).toBe('');
+    expect(mockState.lastSurfaceProps?.isDeployPending).toBe(false);
+    expect(buildCollectorDeployCommands).not.toHaveBeenCalled();
+  });
 });
diff --git a/web-next/app/setting/collector/page.tsx b/web-next/app/setting/collector/page.tsx
index f2f93c3abe..fa779d1efd 100644
--- a/web-next/app/setting/collector/page.tsx
+++ b/web-next/app/setting/collector/page.tsx
@@ -1,43 +1,7 @@
-'use client';
+import React from 'react';
 
-import { useCallback, useState } from 'react';
-import { ClientWorkbench } from '@/components/workbench/client-workbench';
-import { useI18n } from '@/components/providers/i18n-provider';
-import { CollectorManageSurface } from '@/components/pages/collector-manage-surface';
-import { apiMessageGet } from '@/lib/api-client';
-import { formatTime } from '@/lib/format';
-import { loadCollectorData } from '@/lib/collector-manage/controller';
-import type { CollectorQueryState } from '@/lib/collector-manage/query-state';
+import SettingCollectorPage from './setting-collector-page';
 
-export default function SettingCollectorPage() {
-  const { t } = useI18n();
-  const [search, setSearch] = useState('');
-  const [query, setQuery] = useState<CollectorQueryState>({ search: '' });
-
-  const load = useCallback(async () => {
-    return loadCollectorData(apiMessageGet, query);
-  }, [query]);
-
-  return (
-    <ClientWorkbench load={load} loadingCopy={t('menu.advanced.collector')}>
-      {data => (
-        <CollectorManageSurface
-          t={t}
-          data={data}
-          search={search}
-          formatTime={formatTime}
-          onSearchChange={setSearch}
-          onSearch={() => setQuery({ search })}
-          onRefresh={() => setQuery(current => ({ ...current }))}
-          onDeploy={() => {}}
-          onGoOnline={() => {}}
-          onGoOffline={() => {}}
-          onDelete={() => {}}
-          onRowGoOnline={() => {}}
-          onRowGoOffline={() => {}}
-          onRowDelete={() => {}}
-        />
-      )}
-    </ClientWorkbench>
-  );
+export default function SettingCollectorRoutePage() {
+  return <SettingCollectorPage />;
 }
diff --git a/web-next/app/setting/collector/setting-collector-page.tsx b/web-next/app/setting/collector/setting-collector-page.tsx
new file mode 100644
index 0000000000..14dd340616
--- /dev/null
+++ b/web-next/app/setting/collector/setting-collector-page.tsx
@@ -0,0 +1,340 @@
+'use client';
+
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import { ClientWorkbench } from '@/components/workbench/client-workbench';
+import { useI18n } from '@/components/providers/i18n-provider';
+import { CollectorManageSurface, type CollectorDeleteTarget } from '@/components/pages/collector-manage-surface';
+import { apiMessageDelete, apiMessageGet, apiMessagePost, apiMessagePut } from '@/lib/api-client';
+import { formatTime } from '@/lib/format';
+import {
+  buildCollectorDeployCommands,
+  deleteCollectors,
+  generateCollectorIdentity,
+  goOfflineCollectors,
+  goOnlineCollectors,
+  loadCollectorData
+} from '@/lib/collector-manage/controller';
+import { buildCollectorUrl, type CollectorQueryState } from '@/lib/collector-manage/query-state';
+
+const SETTING_COLLECTOR_SETTLED_CACHE_TTL_MS = 10_000;
+const COLLECTOR_DEPLOY_COPY_SUCCESS_DURATION_MS = 800;
+type CollectorManagePageData = Awaited<ReturnType<typeof loadCollectorData>>;
+
+export default function SettingCollectorPage() {
+  const { t } = useI18n();
+  const [search, setSearch] = useState('');
+  const [query, setQuery] = useState<CollectorQueryState>({ pageIndex: 0, pageSize: 8, search: '' });
+  const [reloadVersion, setReloadVersion] = useState(0);
+  const [selectedCollectors, setSelectedCollectors] = useState<string[]>([]);
+  const [actionMessage, setActionMessage] = useState<string | null>(null);
+  const [actionError, setActionError] = useState<string | null>(null);
+  const [actionMeta, setActionMeta] = useState<string | null>(null);
+  const [actionTone, setActionTone] = useState<'success' | 'warning' | 'critical'>('success');
+  const [actionFeedbackContract, setActionFeedbackContract] = useState<string | null>(null);
+  const [deleteTarget, setDeleteTarget] = useState<CollectorDeleteTarget | null>(null);
+  const [isDeletePending, setIsDeletePending] = useState(false);
+  const [isLoadPending, setIsLoadPending] = useState(false);
+  const [deployOpen, setDeployOpen] = useState(false);
+  const [deployName, setDeployName] = useState('');
+  const [deployIdentity, setDeployIdentity] = useState('');
+  const [deployHost, setDeployHost] = useState('127.0.0.1');
+  const [deployDockerShell, setDeployDockerShell] = useState('');
+  const [deployPackageShell, setDeployPackageShell] = useState('');
+  const [deployError, setDeployError] = useState<string | null>(null);
+  const [deployCopyMessage, setDeployCopyMessage] = useState<string | null>(null);
+  const [isDeployPending, setIsDeployPending] = useState(false);
+  const [deployNameValidationVisible, setDeployNameValidationVisible] = useState(false);
+  const deployCopyClearTimerRef = useRef<ReturnType<typeof window.setTimeout> | null>(null);
+  const deployGenerationRequestRef = useRef(0);
+  const collectorListUrl = useMemo(() => buildCollectorUrl(query), [query]);
+  const collectorManageCacheKey = useMemo(
+    () => ['setting-collector', collectorListUrl, reloadVersion].join(':'),
+    [collectorListUrl, reloadVersion]
+  );
+
+  const load = useCallback(async () => {
+    setIsLoadPending(true);
+    try {
+      return await loadCollectorData(apiMessageGet, query);
+    } finally {
+      setIsLoadPending(false);
+    }
+  }, [query]);
+
+  const clearActionFeedback = useCallback(() => {
+    setActionMessage(null);
+    setActionError(null);
+    setActionMeta(null);
+    setActionTone('success');
+    setActionFeedbackContract(null);
+  }, []);
+
+  const buildFallbackCollectorData = useCallback((): CollectorManagePageData => ({
+    list: {
+      content: [],
+      totalElements: 0,
+      pageIndex: query.pageIndex ?? 0,
+      pageSize: query.pageSize ?? 8
+    }
+  }), [query.pageIndex, query.pageSize]);
+
+  const clearDeployCopyTimer = useCallback(() => {
+    if (!deployCopyClearTimerRef.current) return;
+    window.clearTimeout(deployCopyClearTimerRef.current);
+    deployCopyClearTimerRef.current = null;
+  }, []);
+
+  useEffect(() => clearDeployCopyTimer, [clearDeployCopyTimer]);
+
+  const openBatchOperateTarget = useCallback((action: 'online' | 'offline') => {
+    clearActionFeedback();
+    if (selectedCollectors.length === 0) {
+      setActionTone('warning');
+      setActionFeedbackContract(action === 'online' ? 'angular-no-select-online' : 'angular-no-select-offline');
+      setActionError(t(action === 'online' ? 'collector.notify.no-select-online' : 'collector.notify.no-select-offline'));
+      return;
+    }
+    setDeleteTarget({
+      action,
+      collectors: selectedCollectors,
+      label: t('setting.collector.selected-count', { count: selectedCollectors.length }),
+      mode: 'batch'
+    });
+  }, [clearActionFeedback, selectedCollectors, t]);
+
+  const closeDeployDialog = useCallback(() => {
+    clearDeployCopyTimer();
+    deployGenerationRequestRef.current += 1;
+    setDeployOpen(false);
+    setDeployName('');
+    setDeployIdentity('');
+    setDeployHost('127.0.0.1');
+    setDeployDockerShell('');
+    setDeployPackageShell('');
+    setDeployError(null);
+    setDeployCopyMessage(null);
+    setIsDeployPending(false);
+    setDeployNameValidationVisible(false);
+  }, [clearDeployCopyTimer]);
+
+  function renderCollectorSurface(data: CollectorManagePageData, loadError: string | null = null, retryLoad?: () => void) {
+    return (
+      <CollectorManageSurface
+          t={t}
+          data={data}
+          search={search}
+          selectedCollectors={selectedCollectors}
+          actionMessage={actionMessage}
+          actionError={actionError}
+          actionMeta={actionMeta}
+          actionTone={actionTone}
+          actionFeedbackContract={actionFeedbackContract}
+          loadError={loadError}
+          deleteTarget={deleteTarget}
+          isDeletePending={isDeletePending}
+          isLoadPending={isLoadPending}
+          deployOpen={deployOpen}
+          deployName={deployName}
+          deployIdentity={deployIdentity}
+          deployHost={deployHost}
+          deployDockerShell={deployDockerShell}
+          deployPackageShell={deployPackageShell}
+          deployError={deployError}
+          deployCopyMessage={deployCopyMessage}
+          isDeployPending={isDeployPending}
+          deployNameValidationVisible={deployNameValidationVisible}
+          formatTime={formatTime}
+          onSearchChange={setSearch}
+          onSearch={() => {
+            clearActionFeedback();
+            setSelectedCollectors([]);
+            setQuery(current => ({ ...current, pageIndex: 0, search }));
+          }}
+          onSearchClear={() => {
+            clearActionFeedback();
+            setSelectedCollectors([]);
+            setSearch('');
+            setQuery(current => ({ ...current, pageIndex: 0, search: '' }));
+          }}
+          onRefresh={() => {
+            clearActionFeedback();
+            setSelectedCollectors([]);
+            if (retryLoad) {
+              retryLoad();
+              return;
+            }
+            setReloadVersion(version => version + 1);
+          }}
+          onDeploy={() => {
+            clearDeployCopyTimer();
+            clearActionFeedback();
+            setDeployOpen(true);
+            setDeployIdentity('');
+            setDeployError(null);
+            setDeployCopyMessage(null);
+            setDeployNameValidationVisible(false);
+          }}
+          onGoOnline={() => openBatchOperateTarget('online')}
+          onGoOffline={() => openBatchOperateTarget('offline')}
+          onDelete={() => {
+            clearActionFeedback();
+            if (selectedCollectors.length === 0) {
+              setActionTone('warning');
+              setActionFeedbackContract('angular-no-select-warning');
+              setActionError(t('common.notify.no-select-delete'));
+              return;
+            }
+            setDeleteTarget({
+              action: 'delete',
+              collectors: selectedCollectors,
+              label: t('setting.collector.selected-count', { count: selectedCollectors.length }),
+              mode: 'batch'
+            });
+          }}
+          onSelectedCollectorsChange={setSelectedCollectors}
+          onPageIndexChange={pageIndex => {
+            clearActionFeedback();
+            setSelectedCollectors([]);
+            setQuery(current => ({ ...current, pageIndex }));
+          }}
+          onPageSizeChange={pageSize => {
+            clearActionFeedback();
+            setSelectedCollectors([]);
+            setQuery(current => ({ ...current, pageIndex: 0, pageSize }));
+          }}
+          onDeleteCancel={() => {
+            if (!isDeletePending) setDeleteTarget(null);
+          }}
+          onDeleteConfirm={() => {
+            if (!deleteTarget) return;
+            clearActionFeedback();
+            setIsDeletePending(true);
+            const targetCollectors = deleteTarget.collectors;
+            const mutation =
+              deleteTarget.action === 'online'
+                ? goOnlineCollectors(apiMessagePut, targetCollectors)
+                : deleteTarget.action === 'offline'
+                  ? goOfflineCollectors(apiMessagePut, targetCollectors)
+                  : deleteCollectors(apiMessageDelete, targetCollectors);
+            void mutation
+              .then(() => {
+                setActionTone('success');
+                setActionFeedbackContract(deleteTarget.action === 'online' || deleteTarget.action === 'offline' ? 'angular-operate-notify' : 'angular-delete-notify');
+                setActionMessage(t(deleteTarget.action === 'online' || deleteTarget.action === 'offline' ? 'common.notify.operate-success' : 'common.notify.delete-success'));
+                setSelectedCollectors([]);
+                if (deleteTarget.action !== 'online' && deleteTarget.action !== 'offline') {
+                  setQuery(current => {
+                    const remainingTotal = Math.max(0, data.list.totalElements - targetCollectors.length);
+                    const lastPageIndex = Math.max(0, Math.ceil(remainingTotal / current.pageSize) - 1);
+                    return current.pageIndex > lastPageIndex ? { ...current, pageIndex: lastPageIndex } : current;
+                  });
+                }
+                setDeleteTarget(null);
+                setReloadVersion(version => version + 1);
+              })
+              .catch(error => {
+                const isOperateAction = deleteTarget.action === 'online' || deleteTarget.action === 'offline';
+                setActionTone('critical');
+                setActionFeedbackContract(isOperateAction ? 'angular-operate-fail' : 'angular-delete-fail');
+                setActionError(t(isOperateAction ? 'common.notify.operate-fail' : 'common.notify.delete-fail'));
+                setActionMeta(error instanceof Error ? error.message : null);
+              })
+              .finally(() => {
+                setIsDeletePending(false);
+              });
+          }}
+          onDeployNameChange={value => {
+            clearDeployCopyTimer();
+            setDeployName(value);
+            setDeployError(null);
+            setDeployCopyMessage(null);
+            setDeployNameValidationVisible(false);
+          }}
+          onDeployGenerate={() => {
+            const collector = deployName.trim();
+            if (isDeployPending) return;
+            if (deployName.length === 0) {
+              setDeployNameValidationVisible(true);
+              return;
+            }
+            setDeployError(null);
+            clearDeployCopyTimer();
+            setDeployCopyMessage(null);
+            setDeployNameValidationVisible(false);
+            setIsDeployPending(true);
+            const deployGenerationRequest = deployGenerationRequestRef.current + 1;
+            deployGenerationRequestRef.current = deployGenerationRequest;
+            void generateCollectorIdentity(apiMessagePost, collector)
+              .then(result => {
+                if (deployGenerationRequestRef.current !== deployGenerationRequest) return;
+                const identity = result.identity ?? '';
+                const managerHost = result.host ?? '127.0.0.1';
+                const commands = buildCollectorDeployCommands(t, identity, managerHost);
+                setDeployName(collector);
+                setDeployIdentity(identity);
+                setDeployHost(managerHost);
+                setDeployDockerShell(commands.dockerShell);
+                setDeployPackageShell(commands.packageShell);
+              })
+              .catch(error => {
+                if (deployGenerationRequestRef.current !== deployGenerationRequest) return;
+                setDeployError(error instanceof Error ? error.message : t('common.notify.apply-fail'));
+              })
+              .finally(() => {
+                if (deployGenerationRequestRef.current !== deployGenerationRequest) return;
+                setIsDeployPending(false);
+              });
+          }}
+          onDeployClose={closeDeployDialog}
+          onDeployCopy={value => {
+            clearDeployCopyTimer();
+            setDeployCopyMessage(t('common.notify.copy-success'));
+            deployCopyClearTimerRef.current = window.setTimeout(() => {
+              setDeployCopyMessage(null);
+              deployCopyClearTimerRef.current = null;
+            }, COLLECTOR_DEPLOY_COPY_SUCCESS_DURATION_MS);
+            void navigator.clipboard?.writeText(value).catch(() => undefined);
+          }}
+          onRowGoOnline={collector => {
+            clearActionFeedback();
+            setDeleteTarget({
+              action: 'online',
+              collectors: [collector],
+              label: collector,
+              mode: 'single'
+            });
+          }}
+          onRowGoOffline={collector => {
+            clearActionFeedback();
+            setDeleteTarget({
+              action: 'offline',
+              collectors: [collector],
+              label: collector,
+              mode: 'single'
+            });
+          }}
+          onRowDelete={collector => {
+            clearActionFeedback();
+            setDeleteTarget({
+              action: 'delete',
+              collectors: [collector],
+              label: collector,
+              mode: 'single'
+            });
+          }}
+        />
+    );
+  }
+
+  return (
+    <ClientWorkbench
+      load={load}
+      loadingCopy={t('setting.collector.loading')}
+      cacheKey={collectorManageCacheKey}
+      cacheSettledTtlMs={SETTING_COLLECTOR_SETTLED_CACHE_TTL_MS}
+      renderError={(message, retry) => renderCollectorSurface(buildFallbackCollectorData(), message, retry)}
+    >
+      {data => renderCollectorSurface(data)}
+    </ClientWorkbench>
+  );
+}
diff --git a/web-next/app/setting/define/page.test.tsx b/web-next/app/setting/define/page.test.tsx
index 5d74653518..357cf19468 100644
--- a/web-next/app/setting/define/page.test.tsx
+++ b/web-next/app/setting/define/page.test.tsx
@@ -1,52 +1,64 @@
+// @vitest-environment jsdom
+
 import React from 'react';
+import { readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+import { act } from 'react';
+import { createRoot } from 'react-dom/client';
 import { renderToStaticMarkup } from 'react-dom/server';
 import { beforeEach, describe, expect, it, vi } from 'vitest';
 import { createTranslatorMock } from '../../../test/i18n-test-helper';
 
 (globalThis as { React?: typeof React }).React = React;
+(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
 
 const mockState = vi.hoisted(() => ({
   lastLoad: null as null | (() => Promise<unknown>),
+  lastSurfaceProps: null as null | Record<string, any>,
+  renderErrorMessage: null as string | null,
   renderData: {
-    list: {
-      content: [
-        {
-          id: 1,
-          name: 'cpu-alert',
-          type: 'realtime_metric',
-          datasource: 'http',
-          expr: 'up == 0',
-          enable: true,
-          period: 60
-        }
-      ],
-      totalElements: 1,
-      pageIndex: 0,
-      pageSize: 8
-    },
-    datasourceStatus: {
-      code: 0,
-      data: {
-        http: 'ready'
+    menuGroups: [
+      {
+        key: 'database',
+        label: 'DATABASE',
+        items: [{ category: 'database', value: 'mysql', label: 'MySQL', hide: false }]
       }
-    }
+    ],
+    appLabels: { mysql: 'MySQL' },
+    selectedApp: 'mysql',
+    yaml: 'app: mysql\ncategory: database',
+    originalYaml: 'app: mysql\ncategory: database'
+  },
+  defaultRenderData: {
+    menuGroups: [
+      {
+        key: 'database',
+        label: 'DATABASE',
+        items: [{ category: 'database', value: 'mysql', label: 'MySQL', hide: false }]
+      }
+    ],
+    appLabels: { mysql: 'MySQL' },
+    selectedApp: 'mysql',
+    yaml: 'app: mysql\ncategory: database',
+    originalYaml: 'app: mysql\ncategory: database'
   }
 }));
 
-const apiGet = vi.hoisted(() => vi.fn());
 const apiMessageGet = vi.hoisted(() => vi.fn());
 const apiMessagePost = vi.hoisted(() => vi.fn());
 const apiMessagePut = vi.hoisted(() => vi.fn());
+const apiMessageDelete = vi.hoisted(() => vi.fn());
 const loadDefineCenterData = vi.hoisted(() => vi.fn());
+const saveTemplateDefine = vi.hoisted(() => vi.fn());
+const deleteTemplateDefine = vi.hoisted(() => vi.fn());
+const updateTemplateVisibility = vi.hoisted(() => vi.fn());
+const reloadTemplateDefinitionStartupContext = vi.hoisted(() => vi.fn());
 
 vi.mock('@/components/providers/i18n-provider', () => ({
   useI18n: () => ({
+    locale: 'zh-CN',
     t: createTranslatorMock({
-      locale: 'zh-CN',
-      overrides: {
-        'setting.define.loading': '加载定义中心',
-        'menu.advanced.define': '定义'
-      }
+      locale: 'zh-CN'
     })
   })
 }));
@@ -54,80 +66,848 @@ vi.mock('@/components/providers/i18n-provider', () => ({
 vi.mock('@/components/workbench/client-workbench', () => ({
   ClientWorkbench: ({
     children,
-    load
+    load,
+    renderError
   }: {
     children: (data: unknown) => React.ReactNode;
     load: () => Promise<unknown>;
+    renderError?: (message: string, retry: () => void) => React.ReactNode;
   }) => {
     mockState.lastLoad = load;
+    if (mockState.renderErrorMessage && renderError) {
+      return <div data-client-workbench="true">{renderError(mockState.renderErrorMessage, vi.fn())}</div>;
+    }
     return <div data-client-workbench="true">{children(mockState.renderData)}</div>;
   }
 }));
 
 vi.mock('@/components/pages/setting-define-surface', () => ({
-  SettingDefineSurface: ({ selectedDefine, yamlLabel, darkMode, isEditing }: any) => (
-    <div data-setting-define-surface="otlp-cold-define-console">
-      <span>定义</span>
-      <span>{selectedDefine?.name}</span>
-      <span>{yamlLabel}</span>
-      <span>{String(darkMode)}</span>
-      <span>{String(isEditing)}</span>
-    </div>
-  )
+  SettingDefineSurface: (props: any) => {
+    mockState.lastSurfaceProps = props;
+    const {
+      selectedApp,
+      yamlLabel,
+      editorValue,
+      originalYaml,
+      darkMode,
+      isEditing,
+      menuLoading,
+      editorLoading,
+      loadError,
+      message,
+      messageMeta,
+      messageContract,
+      onNew,
+      onSave,
+      onDelete,
+      onToggleTemplateVisibility
+    } = props;
+    return (
+      <div
+        data-setting-define-surface="otlp-cold-define-console"
+        data-setting-define-menu-filtered-contract="angular-hide-prometheus-system"
+        data-setting-define-menu-filtered-owner="setting-define-controller"
+        data-setting-define-menu-loading-contract="angular-monitor-select-list-loading"
+        data-setting-define-menu-loading-owner="hertzbeat-ui-template-picker"
+        data-setting-define-menu-loading={menuLoading ? 'true' : 'false'}
+        data-setting-define-load-failure-contract="angular-console-only-shell"
+        data-setting-define-load-failure-owner="setting-define-controller"
+        data-setting-define-load-failure={loadError ? 'angular-console-only-shell' : 'none'}
+        data-setting-define-theme-switch-contract="angular-nz-switch-code-editor-theme"
+        data-setting-define-theme-switch-owner="hertzbeat-ui-switch"
+        data-setting-define-theme-switch-state={darkMode ? 'vs-dark' : 'vs'}
+        data-setting-define-save-reload-contract="angular-load-app-define-content-after-save"
+        data-setting-define-save-reload-owner="setting-define-controller"
+        data-setting-define-save-reload-scope={selectedApp ? 'existing-template' : 'new-template-draft'}
+        data-setting-define-startup-reload-contract="angular-startup-load-after-success"
+        data-setting-define-startup-reload-owner="startup-service"
+        data-setting-define-startup-reload-scope="save-delete-visibility-success"
+        data-setting-define-startup-reload-failure-contract="angular-fire-and-forget"
+        data-setting-define-startup-reload-failure-owner="startup-service"
+        data-setting-define-editor-option-contract="angular-yaml-vs-folding-automatic-layout"
+        data-setting-define-editor-option-owner="cold-code-editor"
+        data-setting-define-editor-loading-contract="angular-nz-code-editor-loading"
+        data-setting-define-editor-loading-owner="cold-code-editor"
+        data-setting-define-editor-loading={editorLoading ? 'true' : 'false'}
+        data-setting-define-editor-loading-save-contract="angular-save-hidden-while-editor-loading"
+        data-setting-define-new-draft-contract="angular-locale-comment-five-newlines"
+        data-setting-define-new-draft-owner="setting-define-controller"
+        data-setting-define-new-draft-state={selectedApp ? 'existing-template' : 'new-template'}
+        data-setting-define-new-action-contract="angular-current-app-reset-url-retained"
+        data-setting-define-new-action-owner="setting-define-controller"
+        data-setting-define-new-action-state={selectedApp ? 'available' : 'hidden'}
+        data-setting-define-menu-select-query-contract="angular-replace-with-app-only"
+        data-setting-define-menu-select-query-owner="setting-define-page-router"
+      >
+        <span>定义</span>
+        <span>{selectedApp}</span>
+        <span>{yamlLabel}</span>
+        <span>{editorValue}</span>
+        <span data-setting-define-original-yaml="true">{originalYaml}</span>
+        <span>{String(darkMode)}</span>
+        <span>{String(isEditing)}</span>
+        <span data-setting-define-load-error={loadError ? 'present' : 'none'} />
+        <span data-setting-define-test-save-pending="true">{String(props.savePending)}</span>
+        <span
+          data-setting-define-test-message={message || ''}
+          data-setting-define-test-message-meta={messageMeta || ''}
+          data-setting-define-test-message-contract={messageContract || ''}
+        />
+        <button data-setting-define-test-new="true" onClick={onNew}>new</button>
+        <button data-setting-define-test-save="true" onClick={onSave}>save</button>
+        <button data-setting-define-test-delete="true" onClick={onDelete}>delete</button>
+        <button data-setting-define-test-visibility="true" onClick={() => onToggleTemplateVisibility('mysql', true)}>visibility</button>
+      </div>
+    );
+  }
 }));
 
 vi.mock('@/lib/api-client', () => ({
-  apiGet,
   apiMessageGet,
   apiMessagePost,
-  apiMessagePut
-}));
-
-vi.mock('@/lib/format', () => ({
-  formatTime: () => '2026-04-10 18:00:00'
+  apiMessagePut,
+  apiMessageDelete
 }));
 
 vi.mock('@/lib/setting-define/controller', () => ({
   loadDefineCenterData,
-  buildPreviewUrl: () => '/alert/define/preview/http?type=realtime_metric&expr=up+%3D%3D+0',
-  buildSkeletonDefine: () => ({
-    id: 0,
-    name: 'next-migrated-define',
-    datasource: 'promql',
-    expr: 'up == 0'
-  }),
-  createSkeletonDefine: vi.fn(),
-  saveDefine: vi.fn()
+  buildNewTemplateYaml: () => 'app: custom',
+  buildNewTemplateDraft: () => ({ yaml: 'app: custom\n\n\n\n\n', originalYaml: 'app: custom' }),
+  saveTemplateDefine,
+  deleteTemplateDefine,
+  updateTemplateVisibility,
+  reloadTemplateDefinitionStartupContext
 }));
 
 describe('setting define page', () => {
   beforeEach(() => {
+    window.history.pushState({}, '', '/setting/define');
+    window.localStorage.clear();
+    document.body.removeAttribute('data-theme');
+    document.documentElement.removeAttribute('data-theme');
     mockState.lastLoad = null;
-    apiGet.mockReset();
+    mockState.lastSurfaceProps = null;
+    mockState.renderErrorMessage = null;
+    mockState.renderData = {
+      menuGroups: [
+        {
+          key: 'database',
+          label: 'DATABASE',
+          items: [{ category: 'database', value: 'mysql', label: 'MySQL', hide: false }]
+        }
+      ],
+      appLabels: { mysql: 'MySQL' },
+      selectedApp: 'mysql',
+      yaml: 'app: mysql\ncategory: database',
+      originalYaml: 'app: mysql\ncategory: database'
+    };
     apiMessageGet.mockReset();
     apiMessagePost.mockReset();
     apiMessagePut.mockReset();
-    loadDefineCenterData.mockReset().mockImplementation(async (apiGetFn, search) => {
-      await apiGetFn(`/alert/defines?pageIndex=0&pageSize=8&sort=id&order=desc${search ? `&search=${search}` : ''}`);
-      await apiGetFn('/alert/define/datasource/status');
-      return mockState.renderData;
-    });
+    apiMessageDelete.mockReset();
+    loadDefineCenterData.mockReset().mockResolvedValue(mockState.renderData);
+    saveTemplateDefine.mockReset().mockResolvedValue(undefined);
+    deleteTemplateDefine.mockReset().mockResolvedValue(undefined);
+    updateTemplateVisibility.mockReset().mockResolvedValue(undefined);
+    reloadTemplateDefinitionStartupContext.mockReset().mockResolvedValue(undefined);
   });
 
-  it('renders the shared define surface and keeps the route focused on load composition', async () => {
+  it('renders the monitor-template YML surface and loads through old app define APIs', async () => {
     const { default: SettingDefinePage } = await import('./page');
     const html = renderToStaticMarkup(<SettingDefinePage />);
 
     expect(html).toContain('data-client-workbench="true"');
     expect(html).toContain('data-setting-define-surface="otlp-cold-define-console"');
-    expect(html).toContain('定义');
-    expect(html).toContain('cpu-alert');
-    expect(html).toContain('http.yml');
-    expect(html).toContain('false');
+    expect(html).toContain('data-setting-define-menu-filtered-contract="angular-hide-prometheus-system"');
+    expect(html).toContain('data-setting-define-menu-filtered-owner="setting-define-controller"');
+    expect(html).toContain('data-setting-define-menu-loading-contract="angular-monitor-select-list-loading"');
+    expect(html).toContain('data-setting-define-menu-loading-owner="hertzbeat-ui-template-picker"');
+    expect(html).toContain('data-setting-define-menu-loading="false"');
+    expect(html).toContain('data-setting-define-load-failure-contract="angular-console-only-shell"');
+    expect(html).toContain('data-setting-define-load-failure-owner="setting-define-controller"');
+    expect(html).toContain('data-setting-define-load-failure="none"');
+    expect(html).toContain('data-setting-define-theme-switch-contract="angular-nz-switch-code-editor-theme"');
+    expect(html).toContain('data-setting-define-theme-switch-owner="hertzbeat-ui-switch"');
+    expect(html).toContain('data-setting-define-theme-switch-state="vs-dark"');
+    expect(html).toContain('data-setting-define-save-reload-contract="angular-load-app-define-content-after-save"');
+    expect(html).toContain('data-setting-define-save-reload-owner="setting-define-controller"');
+    expect(html).toContain('data-setting-define-save-reload-scope="new-template-draft"');
+    expect(html).toContain('data-setting-define-startup-reload-contract="angular-startup-load-after-success"');
+    expect(html).toContain('data-setting-define-startup-reload-owner="startup-service"');
+    expect(html).toContain('data-setting-define-startup-reload-scope="save-delete-visibility-success"');
+    expect(html).toContain('data-setting-define-startup-reload-failure-contract="angular-fire-and-forget"');
+    expect(html).toContain('data-setting-define-startup-reload-failure-owner="startup-service"');
+    expect(html).toContain('data-setting-define-editor-option-contract="angular-yaml-vs-folding-automatic-layout"');
+    expect(html).toContain('data-setting-define-editor-option-owner="cold-code-editor"');
+    expect(html).toContain('data-setting-define-editor-loading-contract="angular-nz-code-editor-loading"');
+    expect(html).toContain('data-setting-define-editor-loading-owner="cold-code-editor"');
+    expect(html).toContain('data-setting-define-editor-loading="false"');
+    expect(html).toContain('data-setting-define-editor-loading-save-contract="angular-save-hidden-while-editor-loading"');
+    expect(html).toContain('data-setting-define-new-draft-contract="angular-locale-comment-five-newlines"');
+    expect(html).toContain('data-setting-define-new-draft-owner="setting-define-controller"');
+    expect(html).toContain('data-setting-define-new-draft-state="new-template"');
+    expect(html).toContain('data-setting-define-new-action-contract="angular-current-app-reset-url-retained"');
+    expect(html).toContain('data-setting-define-new-action-owner="setting-define-controller"');
+    expect(html).toContain('data-setting-define-new-action-state="hidden"');
+    expect(html).toContain('data-setting-define-menu-select-query-contract="angular-replace-with-app-only"');
+    expect(html).toContain('data-setting-define-menu-select-query-owner="setting-define-page-router"');
+    expect(html).toContain('mysql');
+    expect(html).toContain('app-mysql.yml');
+    expect(html).toContain('app: mysql');
+    expect(html).toContain('data-setting-define-original-yaml="true"');
+    expect(html).toContain('true');
+
+    await mockState.lastLoad?.();
+
+    expect(loadDefineCenterData).toHaveBeenCalledWith(apiMessageGet, null, 'zh-CN');
+  });
+
+  it('keeps the Angular setting define shell visible when menu or YML loading fails', async () => {
+    mockState.renderErrorMessage = 'backend refused define load';
+    const { default: SettingDefinePage } = await import('./page');
+    const html = renderToStaticMarkup(<SettingDefinePage />);
+
+    expect(html).toContain('data-client-workbench="true"');
+    expect(html).toContain('data-setting-define-surface="otlp-cold-define-console"');
+    expect(html).toContain('data-setting-define-load-failure-contract="angular-console-only-shell"');
+    expect(html).toContain('data-setting-define-load-failure-owner="setting-define-controller"');
+    expect(html).toContain('data-setting-define-load-failure="angular-console-only-shell"');
+    expect(html).toContain('app-custom.yml');
+    expect(html).toContain('app: custom');
+    expect(html).not.toContain('backend refused define load');
+    expect(html).not.toContain('common.load-failed');
+    expect(html).not.toContain('data-observability-status');
+    expect(mockState.lastSurfaceProps?.data.menuGroups).toEqual([]);
+    expect(mockState.lastSurfaceProps?.loadError).toBe('backend refused define load');
+  });
+
+  it('initializes the YML editor dark toggle from the Angular theme service contract', async () => {
+    const { default: SettingDefinePage } = await import('./page');
+
+    window.localStorage.setItem('theme', 'light-ops');
+    renderToStaticMarkup(<SettingDefinePage />);
+    expect(mockState.lastSurfaceProps?.darkMode).toBe(false);
+
+    document.body.setAttribute('data-theme', 'dark-ops');
+    renderToStaticMarkup(<SettingDefinePage />);
+    expect(mockState.lastSurfaceProps?.darkMode).toBe(true);
+  });
+
+  it('keeps setting define remounts keyed by selected app and reload version', () => {
+    const source = readFileSync(resolve(process.cwd(), 'app/setting/define/setting-define-page.tsx'), 'utf8');
+
+    expect(source).toContain('SETTING_DEFINE_SETTLED_CACHE_TTL_MS = 10_000');
+    expect(source).toContain('loadDefineCenterData(apiMessageGet, selectedApp ?? null, locale)');
+    expect(source).toContain('buildNewTemplateDraft');
+    expect(source).toContain('buildFallbackDefineData');
+    expect(source).toContain('renderError={(message) => renderDefineSurface(buildFallbackDefineData(), message)}');
+    expect(source).toContain('loadError={loadError}');
+    expect(source).toContain('menuLoading={false}');
+    expect(source).toContain('editorLoading={false}');
+    expect(source).toContain('reloadTemplateDefinitionStartupContext');
+    expect(source).toContain("if (typeof window === 'undefined') return undefined;");
+    expect(source).toContain('useState<string | null | undefined>(() => readInitialSelectedApp())');
+    expect(source).toContain('writeSelectedAppToUrl(app)');
+    expect(source).toContain("const nextSearch = app ? `?app=${encodeURIComponent(app)}` : '';");
+    expect(source).toContain("window.history.pushState({}, '', `${url.pathname}${nextSearch}`);");
+    expect(source).not.toContain("url.searchParams.set('app', app)");
+    expect(source).toContain('setEditorValue(null)');
+    expect(source).toContain('setOriginalYaml(null)');
+    expect(source).toContain('resolveDefineWorkbenchTheme');
+    expect(source).toContain("window.localStorage.getItem('theme')");
+    expect(source).toContain("document.body?.getAttribute('data-theme')");
+    expect(source).toContain("document.documentElement?.getAttribute('data-theme')");
+    expect(source).toContain("const [darkMode, setDarkMode] = useState(() => readInitialDefineDarkMode());");
+    expect(source).toContain("['setting-define-yml', selectedApp || 'new', reloadVersion].join(':')");
+    expect(source).toContain('[selectedApp, reloadVersion]');
+    expect(source).toContain('[selectedApp, locale]');
+    expect(source).toContain('cacheKey={settingDefineCacheKey}');
+    expect(source).toContain('cacheSettledTtlMs={SETTING_DEFINE_SETTLED_CACHE_TTL_MS}');
+    expect(source).toContain('const activeApp = selectedApp === undefined ? data.selectedApp : selectedApp;');
+    expect(source).not.toContain('const activeApp = selectedApp ?? data.selectedApp;');
+    expect(source).toContain('originalYaml={resolvedOriginalYaml}');
+    expect(source).not.toContain('const [darkMode, setDarkMode] = useState(false);');
+    expect(source).toContain('const [savePending, setSavePending] = useState(false);');
+    expect(source).toContain('const [messageMeta, setMessageMeta] = useState<string | null>(null);');
+    expect(source).toContain('const [messageContract, setMessageContract] = useState<string | null>(null);');
+    expect(source).toContain('function readMutationFailureDetail(error: unknown, fallback = \'\')');
+    expect(source).toContain('savePending={savePending}');
+    expect(source).toContain('messageMeta={messageMeta}');
+    expect(source).toContain('messageContract={messageContract}');
+    expect(source).toContain('setEditorValue(draft.yaml)');
+    expect(source).toContain('setOriginalYaml(draft.originalYaml)');
+    expect(source).not.toContain('writeSelectedAppToUrl(null);');
+    expect(source).toContain('currentYaml === currentOriginalYaml');
+    expect(source).toContain("currentYaml === ''");
+    expect(source).toContain("t('define.save-apply.no-code')");
+    expect(source).toContain("await saveTemplateDefine(apiMessagePost, apiMessagePut, currentYaml, !currentApp);");
+    expect(source).toContain("setMessage(t('common.notify.apply-success'));");
+    expect(source).toContain("setMessage(t('common.notify.apply-fail'));");
+    expect(source).toContain("setMessageMeta(readMutationFailureDetail(error, t('setting.define.save.failed')));");
+    expect(source).toContain("setMessageContract('angular-apply-fail-notification');");
+    expect(source).toContain("setMessage(t('common.notify.delete-fail'));");
+    expect(source).toContain('setMessageMeta(readMutationFailureDetail(error));');
+    expect(source).toContain("setMessageContract('angular-delete-fail-notification');");
+    expect(source).not.toContain('buildMutationFailureMessage(');
+    expect(source).toContain('setEditorValue(null);');
+    expect(source).toContain('setOriginalYaml(null);');
+    expect(source).toContain('refreshStartupContextAfterMutation();');
+    expect(source).toContain('void reloadTemplateDefinitionStartupContext(apiMessageGet, locale).catch(() => undefined);');
+    expect(source).not.toContain("setMessage(t('setting.define.save.success'))");
+  });
+
+  it('re-reads existing app YML from workbench data after a successful save like Angular loadAppDefineContent', async () => {
+    window.history.pushState({}, '', '/setting/define?app=mysql');
+    mockState.renderData = {
+      menuGroups: [
+        {
+          key: 'database',
+          label: 'DATABASE',
+          items: [{ category: 'database', value: 'mysql', label: 'MySQL', hide: false }]
+        }
+      ],
+      appLabels: { mysql: 'MySQL' },
+      selectedApp: 'mysql',
+      yaml: 'app: mysql\ncategory: database\n# backend canonical',
+      originalYaml: 'app: mysql\ncategory: database\n# backend canonical'
+    };
+    const { default: SettingDefinePage } = await import('./page');
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+    const root = createRoot(container);
+
+    await act(async () => {
+      root.render(<SettingDefinePage />);
+      await Promise.resolve();
+    });
+
+    expect(mockState.lastSurfaceProps?.editorValue).toBe('app: mysql\ncategory: database\n# backend canonical');
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onEditorValueChange('app: mysql\ncategory: database\n# local edit before save');
+      await Promise.resolve();
+    });
+
+    expect(mockState.lastSurfaceProps?.editorValue).toBe('app: mysql\ncategory: database\n# local edit before save');
+    expect(mockState.lastSurfaceProps?.isEditing).toBe(true);
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onSave();
+      await Promise.resolve();
+      await Promise.resolve();
+      await Promise.resolve();
+    });
+
+    expect(saveTemplateDefine).toHaveBeenCalledWith(
+      apiMessagePost,
+      apiMessagePut,
+      'app: mysql\ncategory: database\n# local edit before save',
+      false
+    );
+    expect(mockState.lastSurfaceProps?.editorValue).toBe('app: mysql\ncategory: database\n# backend canonical');
+    expect(mockState.lastSurfaceProps?.originalYaml).toBe('app: mysql\ncategory: database\n# backend canonical');
+    expect(mockState.lastSurfaceProps?.isEditing).toBe(false);
+    expect(reloadTemplateDefinitionStartupContext).toHaveBeenCalledWith(apiMessageGet, 'zh-CN');
+
+    await act(async () => {
+      root.unmount();
+      await Promise.resolve();
+    });
+    container.remove();
+  });
+
+  it('refreshes startup config context after successful YML mutations', async () => {
+    window.history.pushState({}, '', '/setting/define?app=mysql');
+    mockState.renderData = {
+      menuGroups: [
+        {
+          key: 'database',
+          label: 'DATABASE',
+          items: [{ category: 'database', value: 'mysql', label: 'MySQL', hide: false }]
+        }
+      ],
+      appLabels: { mysql: 'MySQL' },
+      selectedApp: 'mysql',
+      yaml: 'app: mysql\ncategory: database\n# changed',
+      originalYaml: 'app: mysql\ncategory: database'
+    };
+    const { default: SettingDefinePage } = await import('./page');
+    const html = renderToStaticMarkup(<SettingDefinePage />);
+    expect(html).toContain('data-setting-define-test-save="true"');
+
+    mockState.lastSurfaceProps?.onSave();
+    await Promise.resolve();
+    await Promise.resolve();
+
+    expect(saveTemplateDefine).toHaveBeenCalledWith(
+      apiMessagePost,
+      apiMessagePut,
+      'app: mysql\ncategory: database\n# changed',
+      false
+    );
+    expect(reloadTemplateDefinitionStartupContext).toHaveBeenCalledWith(apiMessageGet, 'zh-CN');
+
+    mockState.lastSurfaceProps?.onDelete();
+    await Promise.resolve();
+    await Promise.resolve();
+
+    expect(deleteTemplateDefine).toHaveBeenCalledWith(apiMessageDelete, 'mysql');
+    expect(reloadTemplateDefinitionStartupContext).toHaveBeenCalledTimes(2);
+
+    mockState.lastSurfaceProps?.onToggleTemplateVisibility('mysql', true);
+    await Promise.resolve();
+    await Promise.resolve();
+
+    expect(updateTemplateVisibility).toHaveBeenCalledWith(apiMessagePut, 'mysql', true);
+    expect(reloadTemplateDefinitionStartupContext).toHaveBeenCalledTimes(3);
+  });
+
+  it('keeps mutation success when the Angular startup refresh side effect fails', async () => {
+    window.history.pushState({}, '', '/setting/define?app=mysql');
+    mockState.renderData = {
+      menuGroups: [
+        {
+          key: 'database',
+          label: 'DATABASE',
+          items: [{ category: 'database', value: 'mysql', label: 'MySQL', hide: false }]
+        }
+      ],
+      appLabels: { mysql: 'MySQL' },
+      selectedApp: 'mysql',
+      yaml: 'app: mysql\ncategory: database\n# changed',
+      originalYaml: 'app: mysql\ncategory: database'
+    };
+    reloadTemplateDefinitionStartupContext.mockRejectedValueOnce(new Error('startup refresh down'));
+    const { default: SettingDefinePage } = await import('./page');
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+    const root = createRoot(container);
+
+    await act(async () => {
+      root.render(<SettingDefinePage />);
+      await Promise.resolve();
+    });
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onSave();
+      await Promise.resolve();
+      await Promise.resolve();
+      await Promise.resolve();
+    });
+
+    expect(saveTemplateDefine).toHaveBeenCalledWith(
+      apiMessagePost,
+      apiMessagePut,
+      'app: mysql\ncategory: database\n# changed',
+      false
+    );
+    expect(reloadTemplateDefinitionStartupContext).toHaveBeenCalledWith(apiMessageGet, 'zh-CN');
+    expect(mockState.lastSurfaceProps?.message).toContain('应用成功');
+    expect(mockState.lastSurfaceProps?.message).not.toContain('startup refresh down');
+
+    await act(async () => {
+      root.unmount();
+      await Promise.resolve();
+    });
+    container.remove();
+  });
+
+  it('keeps the Angular URL app parameter when starting a new draft or after delete success', async () => {
+    window.history.pushState({}, '', '/setting/define?app=mysql&returnTo=%2Fsettings');
+    mockState.renderData = {
+      menuGroups: [
+        {
+          key: 'database',
+          label: 'DATABASE',
+          items: [{ category: 'database', value: 'mysql', label: 'MySQL', hide: false }]
+        }
+      ],
+      appLabels: { mysql: 'MySQL' },
+      selectedApp: 'mysql',
+      yaml: 'app: mysql\ncategory: database',
+      originalYaml: 'app: mysql\ncategory: database'
+    };
+    const { default: SettingDefinePage } = await import('./page');
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+    const root = createRoot(container);
+
+    await act(async () => {
+      root.render(<SettingDefinePage />);
+      await Promise.resolve();
+    });
+
+    expect(window.location.search).toContain('app=mysql');
+    expect(window.location.search).toContain('returnTo=%2Fsettings');
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onNew();
+      await Promise.resolve();
+    });
+
+    expect(mockState.lastSurfaceProps?.selectedApp).toBeNull();
+    expect(mockState.lastSurfaceProps?.editorValue).toBe('app: custom\n\n\n\n\n');
+    expect(window.location.search).toContain('app=mysql');
+    expect(window.location.search).toContain('returnTo=%2Fsettings');
+
+    await act(async () => {
+      root.unmount();
+      await Promise.resolve();
+    });
+    container.remove();
+
+    window.history.pushState({}, '', '/setting/define?app=mysql&returnTo=%2Fsettings');
+    const deleteContainer = document.createElement('div');
+    document.body.appendChild(deleteContainer);
+    const deleteRoot = createRoot(deleteContainer);
+
+    await act(async () => {
+      deleteRoot.render(<SettingDefinePage />);
+      await Promise.resolve();
+    });
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onDelete();
+      await Promise.resolve();
+      await Promise.resolve();
+      await Promise.resolve();
+    });
+
+    expect(deleteTemplateDefine).toHaveBeenCalledWith(apiMessageDelete, 'mysql');
+    expect(mockState.lastSurfaceProps?.selectedApp).toBeNull();
+    expect(mockState.lastSurfaceProps?.editorValue).toBe('app: custom\n\n\n\n\n');
+    expect(mockState.lastSurfaceProps?.originalYaml).toBe('app: custom');
+    expect(mockState.lastSurfaceProps?.isEditing).toBe(false);
+    expect(window.location.search).toContain('app=mysql');
+    expect(window.location.search).toContain('returnTo=%2Fsettings');
+
+    await act(async () => {
+      deleteRoot.unmount();
+      await Promise.resolve();
+    });
+    deleteContainer.remove();
+  });
+
+  it('navigates selected menu templates through the Angular app query contract', async () => {
+    window.history.pushState({}, '', '/setting/define?app=mysql&returnTo=%2Fsettings');
+    const { default: SettingDefinePage } = await import('./page');
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+    const root = createRoot(container);
+
+    await act(async () => {
+      root.render(<SettingDefinePage />);
+      await Promise.resolve();
+    });
+
+    expect(mockState.lastSurfaceProps?.selectedApp).toBe('mysql');
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onSelectApp('linux');
+      await Promise.resolve();
+    });
+
+    expect(window.location.search).toContain('app=linux');
+    expect(window.location.search).toBe('?app=linux');
+    expect(window.location.search).not.toContain('returnTo=%2Fsettings');
+    expect(mockState.lastSurfaceProps?.selectedApp).toBe('linux');
+    expect(mockState.lastSurfaceProps?.isEditing).toBe(false);
 
     await mockState.lastLoad?.();
+    expect(loadDefineCenterData).toHaveBeenLastCalledWith(apiMessageGet, 'linux', 'zh-CN');
+
+    await act(async () => {
+      root.unmount();
+      await Promise.resolve();
+    });
+    container.remove();
+  });
+
+  it('keeps the surface save action busy while save/apply or visibility updates are pending', async () => {
+    window.history.pushState({}, '', '/setting/define?app=mysql');
+    mockState.renderData = {
+      menuGroups: [
+        {
+          key: 'database',
+          label: 'DATABASE',
+          items: [{ category: 'database', value: 'mysql', label: 'MySQL', hide: false }]
+        }
+      ],
+      appLabels: { mysql: 'MySQL' },
+      selectedApp: 'mysql',
+      yaml: 'app: mysql\ncategory: database\n# changed',
+      originalYaml: 'app: mysql\ncategory: database'
+    };
+    let resolveSave: (() => void) | undefined;
+    saveTemplateDefine.mockImplementationOnce(() => new Promise<void>(resolve => {
+      resolveSave = resolve;
+    }));
+    const { default: SettingDefinePage } = await import('./page');
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+    const root = createRoot(container);
+
+    await act(async () => {
+      root.render(<SettingDefinePage />);
+      await Promise.resolve();
+    });
+
+    expect(mockState.lastSurfaceProps?.savePending).toBe(false);
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onSave();
+      await Promise.resolve();
+    });
+
+    expect(mockState.lastSurfaceProps?.savePending).toBe(true);
+
+    await act(async () => {
+      resolveSave?.();
+      await Promise.resolve();
+      await Promise.resolve();
+    });
+
+    expect(mockState.lastSurfaceProps?.savePending).toBe(false);
+
+    let resolveVisibility: (() => void) | undefined;
+    updateTemplateVisibility.mockImplementationOnce(() => new Promise<void>(resolve => {
+      resolveVisibility = resolve;
+    }));
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onToggleTemplateVisibility('mysql', true);
+      await Promise.resolve();
+    });
+
+    expect(mockState.lastSurfaceProps?.savePending).toBe(true);
+
+    await act(async () => {
+      resolveVisibility?.();
+      await Promise.resolve();
+      await Promise.resolve();
+    });
+
+    expect(mockState.lastSurfaceProps?.savePending).toBe(false);
+
+    await act(async () => {
+      root.unmount();
+      await Promise.resolve();
+    });
+    container.remove();
+  });
+
+  it('preserves the old Angular edit flag when saving a direct new-template YML draft', async () => {
+    const draftYaml = 'app: custom\ncategory: custom\n# still drafting';
+    const draftOriginalYaml = '# 请在此通过编写YML内容来定义新的监控类型';
+    mockState.renderData = {
+      menuGroups: [
+        {
+          key: 'database',
+          label: 'DATABASE',
+          items: [{ category: 'database', value: 'mysql', label: 'MySQL', hide: false }]
+        }
+      ],
+      appLabels: { mysql: 'MySQL' },
+      selectedApp: null,
+      yaml: draftYaml,
+      originalYaml: draftOriginalYaml
+    };
+    window.history.pushState({}, '', '/setting/define');
+    const { default: SettingDefinePage } = await import('./page');
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+    const root = createRoot(container);
+
+    await act(async () => {
+      root.render(<SettingDefinePage />);
+      await Promise.resolve();
+    });
+
+    expect(mockState.lastSurfaceProps?.selectedApp).toBeNull();
+    expect(mockState.lastSurfaceProps?.isEditing).toBe(false);
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onSave();
+      await Promise.resolve();
+      await Promise.resolve();
+      await Promise.resolve();
+    });
+
+    expect(saveTemplateDefine).toHaveBeenCalledWith(apiMessagePost, apiMessagePut, draftYaml, true);
+    expect(mockState.lastSurfaceProps?.selectedApp).toBeNull();
+    expect(mockState.lastSurfaceProps?.editorValue).toBe(draftYaml);
+    expect(mockState.lastSurfaceProps?.originalYaml).toBe(draftOriginalYaml);
+    expect(mockState.lastSurfaceProps?.isEditing).toBe(false);
+
+    await act(async () => {
+      root.unmount();
+      await Promise.resolve();
+    });
+    container.remove();
+  });
+
+  it('keeps old Angular apply-fail title with backend detail when YML save fails', async () => {
+    window.history.pushState({}, '', '/setting/define?app=mysql');
+    mockState.renderData = {
+      menuGroups: [
+        {
+          key: 'database',
+          label: 'DATABASE',
+          items: [{ category: 'database', value: 'mysql', label: 'MySQL', hide: false }]
+        }
+      ],
+      appLabels: { mysql: 'MySQL' },
+      selectedApp: 'mysql',
+      yaml: 'app: mysql\ncategory: database\n# invalid',
+      originalYaml: 'app: mysql\ncategory: database'
+    };
+    saveTemplateDefine.mockRejectedValueOnce(new Error('schema invalid'));
+    const { default: SettingDefinePage } = await import('./page');
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+    const root = createRoot(container);
+
+    await act(async () => {
+      root.render(<SettingDefinePage />);
+      await Promise.resolve();
+    });
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onSave();
+      await Promise.resolve();
+      await Promise.resolve();
+      await Promise.resolve();
+    });
+
+    expect(saveTemplateDefine).toHaveBeenCalledWith(
+      apiMessagePost,
+      apiMessagePut,
+      'app: mysql\ncategory: database\n# invalid',
+      false
+    );
+    expect(mockState.lastSurfaceProps?.message).toContain('应用失败');
+    expect(mockState.lastSurfaceProps?.message).not.toContain('schema invalid');
+    expect(mockState.lastSurfaceProps?.messageMeta).toBe('schema invalid');
+    expect(mockState.lastSurfaceProps?.messageContract).toBe('angular-apply-fail-notification');
+    expect(reloadTemplateDefinitionStartupContext).not.toHaveBeenCalled();
+
+    await act(async () => {
+      root.unmount();
+      await Promise.resolve();
+    });
+    container.remove();
+  });
+
+  it('keeps old Angular delete-fail title with backend detail when YML delete fails', async () => {
+    window.history.pushState({}, '', '/setting/define?app=mysql');
+    mockState.renderData = {
+      menuGroups: [
+        {
+          key: 'database',
+          label: 'DATABASE',
+          items: [{ category: 'database', value: 'mysql', label: 'MySQL', hide: false }]
+        }
+      ],
+      appLabels: { mysql: 'MySQL' },
+      selectedApp: 'mysql',
+      yaml: 'app: mysql\ncategory: database',
+      originalYaml: 'app: mysql\ncategory: database'
+    };
+    deleteTemplateDefine.mockRejectedValueOnce(new Error('template is still in use'));
+    const { default: SettingDefinePage } = await import('./page');
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+    const root = createRoot(container);
+
+    await act(async () => {
+      root.render(<SettingDefinePage />);
+      await Promise.resolve();
+    });
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onDelete();
+      await Promise.resolve();
+      await Promise.resolve();
+      await Promise.resolve();
+    });
+
+    expect(deleteTemplateDefine).toHaveBeenCalledWith(apiMessageDelete, 'mysql');
+    expect(mockState.lastSurfaceProps?.message).toContain('删除失败');
+    expect(mockState.lastSurfaceProps?.message).not.toContain('template is still in use');
+    expect(mockState.lastSurfaceProps?.messageMeta).toBe('template is still in use');
+    expect(mockState.lastSurfaceProps?.messageContract).toBe('angular-delete-fail-notification');
+    expect(mockState.lastSurfaceProps?.selectedApp).toBe('mysql');
+    expect(mockState.lastSurfaceProps?.editorValue).toBe('app: mysql\ncategory: database');
+    expect(mockState.lastSurfaceProps?.originalYaml).toBe('app: mysql\ncategory: database');
+    expect(reloadTemplateDefinitionStartupContext).not.toHaveBeenCalled();
+
+    await act(async () => {
+      root.unmount();
+      await Promise.resolve();
+    });
+    container.remove();
+  });
+
+  it('keeps old Angular apply-fail title with backend detail when template visibility update fails', async () => {
+    window.history.pushState({}, '', '/setting/define?app=mysql');
+    mockState.renderData = {
+      menuGroups: [
+        {
+          key: 'database',
+          label: 'DATABASE',
+          items: [{ category: 'database', value: 'mysql', label: 'MySQL', hide: false }]
+        }
+      ],
+      appLabels: { mysql: 'MySQL' },
+      selectedApp: 'mysql',
+      yaml: 'app: mysql\ncategory: database',
+      originalYaml: 'app: mysql\ncategory: database'
+    };
+    updateTemplateVisibility.mockRejectedValueOnce(new Error('visibility locked by policy'));
+    const { default: SettingDefinePage } = await import('./page');
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+    const root = createRoot(container);
+
+    await act(async () => {
+      root.render(<SettingDefinePage />);
+      await Promise.resolve();
+    });
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onToggleTemplateVisibility('mysql', true);
+      await Promise.resolve();
+      await Promise.resolve();
+      await Promise.resolve();
+    });
+
+    expect(updateTemplateVisibility).toHaveBeenCalledWith(apiMessagePut, 'mysql', true);
+    expect(mockState.lastSurfaceProps?.message).toContain('应用失败');
+    expect(mockState.lastSurfaceProps?.message).not.toContain('visibility locked by policy');
+    expect(mockState.lastSurfaceProps?.messageMeta).toBe('visibility locked by policy');
+    expect(mockState.lastSurfaceProps?.messageContract).toBe('angular-apply-fail-notification');
+    expect(mockState.lastSurfaceProps?.selectedApp).toBe('mysql');
+    expect(mockState.lastSurfaceProps?.editorValue).toBe('app: mysql\ncategory: database');
+    expect(reloadTemplateDefinitionStartupContext).not.toHaveBeenCalled();
+
+    await act(async () => {
+      root.unmount();
+      await Promise.resolve();
+    });
+    container.remove();
+  });
+
+  it('does not depend on alert definition preview or datasource APIs', () => {
+    const source = readFileSync(resolve(process.cwd(), 'app/setting/define/setting-define-page.tsx'), 'utf8');
 
-    expect(apiMessageGet).toHaveBeenNthCalledWith(1, '/alert/defines?pageIndex=0&pageSize=8&sort=id&order=desc');
-    expect(apiMessageGet).toHaveBeenNthCalledWith(2, '/alert/define/datasource/status');
+    expect(source).toContain('saveTemplateDefine');
+    expect(source).toContain('deleteTemplateDefine');
+    expect(source).toContain('updateTemplateVisibility');
+    expect(source).toContain('refreshStartupContextAfterMutation');
+    expect(source).toContain('.catch(() => undefined)');
+    expect(source).toContain('apiMessageDelete');
+    expect(source).not.toContain('/alert/define');
+    expect(source).not.toContain('buildPreviewUrl');
+    expect(source).not.toContain('buildSkeletonDefine');
   });
 });
diff --git a/web-next/app/setting/define/page.tsx b/web-next/app/setting/define/page.tsx
index eb4f0480b5..4a44cd227b 100644
--- a/web-next/app/setting/define/page.tsx
+++ b/web-next/app/setting/define/page.tsx
@@ -1,152 +1,7 @@
-'use client';
+import React from 'react';
 
-import { useCallback, useState } from 'react';
-import { ClientWorkbench } from '@/components/workbench/client-workbench';
-import { SettingDefineSurface } from '@/components/pages/setting-define-surface';
-import { useI18n } from '@/components/providers/i18n-provider';
-import { apiGet, apiMessageGet, apiMessagePost, apiMessagePut } from '@/lib/api-client';
-import { formatTime } from '@/lib/format';
-import { buildPreviewUrl, buildSkeletonDefine, loadDefineCenterData, saveDefine } from '@/lib/setting-define/controller';
-import type { AlertDefine } from '@/lib/types';
+import SettingDefinePage from './setting-define-page';
 
-function buildDefineEditorValue(define: AlertDefine | null) {
-  const draft = define || buildSkeletonDefine();
-  return [
-    `name: ${draft.name || ''}`,
-    `type: ${draft.type || ''}`,
-    `datasource: ${draft.datasource || ''}`,
-    `enable: ${draft.enable === false ? 'false' : 'true'}`,
-    `period: ${draft.period || 0}`,
-    `times: ${draft.times || 0}`,
-    'expr: |',
-    `  ${(draft.expr || '').replace(/\n/g, '\n  ')}`
-  ].join('\n');
-}
-
-function buildCurrentYamlLabel(define: AlertDefine | null) {
-  if (!define) return null;
-  const stem = define.datasource || define.name || 'current';
-  return `${stem}.yml`;
-}
-
-function cloneDefine(define: AlertDefine) {
-  return {
-    ...define,
-    labels: define.labels ? { ...define.labels } : define.labels,
-    annotations: define.annotations ? { ...define.annotations } : define.annotations
-  };
-}
-
-export default function SettingDefinePage() {
-  const { t } = useI18n();
-  const [search, setSearch] = useState('');
-  const [query, setQuery] = useState('');
-  const [selected, setSelected] = useState<AlertDefine | null>(null);
-  const [preview, setPreview] = useState<unknown>(null);
-  const [message, setMessage] = useState<string | null>(null);
-  const [saving, setSaving] = useState(false);
-  const [editorValue, setEditorValue] = useState('');
-  const [darkMode, setDarkMode] = useState(false);
-  const [isEditing, setIsEditing] = useState(false);
-
-  const load = useCallback(async () => {
-    return loadDefineCenterData(apiMessageGet, query);
-  }, [query]);
-
-  async function previewDefine(current: AlertDefine | null) {
-    if (!current?.datasource || !current?.expr) return;
-    setMessage(null);
-    try {
-      const result = await apiGet<{ code: number; data?: unknown; msg?: string }>(buildPreviewUrl(current));
-      setPreview(result.data);
-      setMessage(result.code === 0 ? t('setting.define.preview.success') : result.msg || t('setting.define.preview.failed'));
-    } catch (error) {
-      setPreview(null);
-      setMessage(error instanceof Error ? error.message : t('setting.define.preview.failed'));
-    }
-  }
-
-  async function saveSelected(current: AlertDefine | null) {
-    if (!current) return;
-    setSaving(true);
-    setMessage(null);
-    try {
-      if (current.id && current.id > 0) {
-        await saveDefine(apiMessagePut, current);
-      } else {
-        await apiMessagePost('/alert/define', current);
-      }
-      setMessage(t('setting.define.save.success'));
-      setIsEditing(false);
-    } catch (error) {
-      setMessage(error instanceof Error ? error.message : t('setting.define.save.failed'));
-    } finally {
-      setSaving(false);
-    }
-  }
-
-  return (
-    <ClientWorkbench load={load} loadingCopy={t('setting.define.loading')}>
-      {data => {
-        const activeDefine = selected ?? data.list.content[0] ?? null;
-        const resolvedEditorValue = editorValue || buildDefineEditorValue(activeDefine);
-
-        return (
-          <SettingDefineSurface
-            t={t}
-            data={data}
-            search={search}
-            selectedDefine={activeDefine}
-            editorValue={resolvedEditorValue}
-            yamlLabel={buildCurrentYamlLabel(activeDefine)}
-            darkMode={darkMode}
-            isEditing={isEditing}
-            formatTime={formatTime}
-            message={message}
-            preview={preview}
-            onSearchChange={setSearch}
-            onSearch={() => setQuery(search)}
-            onSelectDefine={define => {
-              setSelected(cloneDefine(define));
-              setEditorValue(buildDefineEditorValue(define));
-              setMessage(null);
-              setPreview(null);
-              setIsEditing(false);
-            }}
-            onNew={() => {
-              const skeleton = buildSkeletonDefine();
-              setSelected(skeleton);
-              setEditorValue(buildDefineEditorValue(skeleton));
-              setMessage(null);
-              setPreview(null);
-              setIsEditing(true);
-            }}
-            onEdit={() => {
-              if (activeDefine) {
-                setSelected(cloneDefine(activeDefine));
-                setEditorValue(resolvedEditorValue);
-                setIsEditing(true);
-              }
-            }}
-            onCancel={() => {
-              setEditorValue(buildDefineEditorValue(activeDefine));
-              setMessage(null);
-              setPreview(null);
-              setIsEditing(false);
-            }}
-            onSave={() => void saveSelected(activeDefine)}
-            onDelete={() => setMessage(t('define.delete', { app: activeDefine?.name || 'current' }))}
-            onToggleDarkMode={setDarkMode}
-            onPreview={() => void previewDefine(activeDefine)}
-            onEditorValueChange={value => {
-              setEditorValue(value);
-              if (!isEditing) {
-                setIsEditing(true);
-              }
-            }}
-          />
-        );
-      }}
-    </ClientWorkbench>
-  );
+export default function SettingDefineRoutePage() {
+  return <SettingDefinePage />;
 }
diff --git a/web-next/app/setting/define/setting-define-page.tsx b/web-next/app/setting/define/setting-define-page.tsx
new file mode 100644
index 0000000000..daa9b32f8f
--- /dev/null
+++ b/web-next/app/setting/define/setting-define-page.tsx
@@ -0,0 +1,257 @@
+'use client';
+
+import { useCallback, useMemo, useState } from 'react';
+import { ClientWorkbench } from '@/components/workbench/client-workbench';
+import { SettingDefineSurface } from '@/components/pages/setting-define-surface';
+import { useI18n } from '@/components/providers/i18n-provider';
+import { apiMessageDelete, apiMessageGet, apiMessagePost, apiMessagePut } from '@/lib/api-client';
+import {
+  buildNewTemplateDraft,
+  deleteTemplateDefine,
+  loadDefineCenterData,
+  reloadTemplateDefinitionStartupContext,
+  saveTemplateDefine,
+  updateTemplateVisibility,
+  type SettingDefinePageData
+} from '@/lib/setting-define/controller';
+import { readSettingDefineRouteStateFromSearch } from '@/lib/setting-define/query-state';
+
+const SETTING_DEFINE_SETTLED_CACHE_TTL_MS = 10_000;
+
+function readInitialSelectedApp() {
+  if (typeof window === 'undefined') return undefined;
+  return readSettingDefineRouteStateFromSearch(window.location.search).app;
+}
+
+function writeSelectedAppToUrl(app: string | null) {
+  if (typeof window === 'undefined') return;
+  const url = new URL(window.location.href);
+  const nextSearch = app ? `?app=${encodeURIComponent(app)}` : '';
+  window.history.pushState({}, '', `${url.pathname}${nextSearch}`);
+}
+
+function buildYamlLabel(app: string | null, yaml: string) {
+  if (app) return `app-${app}.yml`;
+  const match = yaml.match(/^\s*app:\s*([^\s#]+)/m);
+  return `app-${match?.[1]?.trim() || 'custom'}.yml`;
+}
+
+function resolveDefineWorkbenchTheme(theme: string | null | undefined): 'dark-ops' | 'light-ops' {
+  return theme === 'light-ops' || theme === 'default' || theme === 'compact' ? 'light-ops' : 'dark-ops';
+}
+
+function readMutationFailureDetail(error: unknown, fallback = '') {
+  if (error instanceof Error) return error.message;
+  return fallback;
+}
+
+function readStoredTheme() {
+  if (typeof window === 'undefined') return null;
+  try {
+    return window.localStorage.getItem('theme');
+  } catch {
+    return null;
+  }
+}
+
+function readInitialDefineDarkMode() {
+  if (typeof document === 'undefined') return false;
+  const domTheme = document.body?.getAttribute('data-theme') || document.documentElement?.getAttribute('data-theme');
+  const theme = resolveDefineWorkbenchTheme(domTheme || readStoredTheme() || 'dark-ops');
+  return theme === 'dark-ops';
+}
+
+export default function SettingDefinePage() {
+  const { locale, t } = useI18n();
+  const [search, setSearch] = useState('');
+  const [selectedApp, setSelectedApp] = useState<string | null | undefined>(() => readInitialSelectedApp());
+  const [message, setMessage] = useState<string | null>(null);
+  const [messageMeta, setMessageMeta] = useState<string | null>(null);
+  const [messageContract, setMessageContract] = useState<string | null>(null);
+  const [editorValue, setEditorValue] = useState<string | null>(null);
+  const [originalYaml, setOriginalYaml] = useState<string | null>(null);
+  const [darkMode, setDarkMode] = useState(() => readInitialDefineDarkMode());
+  const [isEditing, setIsEditing] = useState(false);
+  const [savePending, setSavePending] = useState(false);
+  const [reloadVersion, setReloadVersion] = useState(0);
+  const settingDefineCacheKey = useMemo(
+    () => ['setting-define-yml', selectedApp || 'new', reloadVersion].join(':'),
+    [selectedApp, reloadVersion]
+  );
+
+  const load = useCallback(async () => {
+    return loadDefineCenterData(apiMessageGet, selectedApp ?? null, locale);
+  }, [selectedApp, locale]);
+
+  const refreshStartupContextAfterMutation = useCallback(() => {
+    void reloadTemplateDefinitionStartupContext(apiMessageGet, locale).catch(() => undefined);
+  }, [locale]);
+
+  const clearMutationMessage = useCallback(() => {
+    setMessage(null);
+    setMessageMeta(null);
+    setMessageContract(null);
+  }, []);
+
+  const buildFallbackDefineData = useCallback((): SettingDefinePageData => {
+    const draft = buildNewTemplateDraft(locale);
+    return {
+      menuGroups: [],
+      appLabels: {},
+      selectedApp: selectedApp ?? null,
+      yaml: draft.yaml,
+      originalYaml: draft.originalYaml
+    };
+  }, [locale, selectedApp]);
+
+  function selectApp(app: string) {
+    setSelectedApp(app);
+    writeSelectedAppToUrl(app);
+    setEditorValue(null);
+    setOriginalYaml(null);
+    clearMutationMessage();
+    setIsEditing(false);
+  }
+
+  function startNewTemplate() {
+    const draft = buildNewTemplateDraft(locale);
+    setSelectedApp(null);
+    setEditorValue(draft.yaml);
+    setOriginalYaml(draft.originalYaml);
+    clearMutationMessage();
+    setIsEditing(true);
+  }
+
+  async function saveCurrent(currentApp: string | null, currentYaml: string, currentOriginalYaml: string) {
+    if (currentYaml === currentOriginalYaml) return;
+    if (currentYaml === '') {
+      setMessage(t('define.save-apply.no-code'));
+      setMessageMeta(null);
+      setMessageContract(null);
+      return;
+    }
+    clearMutationMessage();
+    setSavePending(true);
+    try {
+      await saveTemplateDefine(apiMessagePost, apiMessagePut, currentYaml, !currentApp);
+      setMessage(t('common.notify.apply-success'));
+      setMessageMeta(null);
+      setMessageContract(null);
+      if (currentApp) {
+        setEditorValue(null);
+        setOriginalYaml(null);
+        setIsEditing(false);
+      } else {
+        setOriginalYaml(currentOriginalYaml);
+      }
+      setReloadVersion(version => version + 1);
+      refreshStartupContextAfterMutation();
+    } catch (error) {
+      setMessage(t('common.notify.apply-fail'));
+      setMessageMeta(readMutationFailureDetail(error, t('setting.define.save.failed')));
+      setMessageContract('angular-apply-fail-notification');
+    } finally {
+      setSavePending(false);
+    }
+  }
+
+  async function deleteCurrent(currentApp: string | null) {
+    if (!currentApp) return;
+    clearMutationMessage();
+    try {
+      await deleteTemplateDefine(apiMessageDelete, currentApp);
+      const draft = buildNewTemplateDraft(locale);
+      setSelectedApp(null);
+      setEditorValue(draft.yaml);
+      setOriginalYaml(draft.originalYaml);
+      setMessage(t('common.notify.delete-success'));
+      setMessageMeta(null);
+      setMessageContract(null);
+      setReloadVersion(version => version + 1);
+      refreshStartupContextAfterMutation();
+    } catch (error) {
+      setMessage(t('common.notify.delete-fail'));
+      setMessageMeta(readMutationFailureDetail(error));
+      setMessageContract('angular-delete-fail-notification');
+    }
+  }
+
+  async function toggleTemplateVisibility(app: string, hide: boolean) {
+    clearMutationMessage();
+    setSavePending(true);
+    try {
+      await updateTemplateVisibility(apiMessagePut, app, hide);
+      setMessage(t('common.notify.apply-success'));
+      setMessageMeta(null);
+      setMessageContract(null);
+      setReloadVersion(version => version + 1);
+      refreshStartupContextAfterMutation();
+    } catch (error) {
+      setMessage(t('common.notify.apply-fail'));
+      setMessageMeta(readMutationFailureDetail(error));
+      setMessageContract('angular-apply-fail-notification');
+    } finally {
+      setSavePending(false);
+    }
+  }
+
+  function renderDefineSurface(data: SettingDefinePageData, loadError: string | null = null) {
+    const activeApp = selectedApp === undefined ? data.selectedApp : selectedApp;
+    const resolvedEditorValue = editorValue ?? data.yaml;
+    const resolvedOriginalYaml = originalYaml ?? data.originalYaml;
+
+    return (
+      <SettingDefineSurface
+        t={t}
+        data={data}
+        search={search}
+        selectedApp={activeApp}
+        editorValue={resolvedEditorValue}
+        originalYaml={resolvedOriginalYaml}
+        yamlLabel={buildYamlLabel(activeApp, resolvedEditorValue)}
+        darkMode={darkMode}
+        isEditing={isEditing}
+        menuLoading={false}
+        editorLoading={false}
+        savePending={savePending}
+        message={message}
+        messageMeta={messageMeta}
+        messageContract={messageContract}
+        loadError={loadError}
+        onSearchChange={setSearch}
+        onSearch={() => undefined}
+        onSelectApp={selectApp}
+        onNew={startNewTemplate}
+        onEdit={() => setIsEditing(true)}
+        onCancel={() => {
+          setEditorValue(resolvedOriginalYaml);
+          clearMutationMessage();
+          setIsEditing(false);
+        }}
+        onSave={() => void saveCurrent(activeApp, resolvedEditorValue, resolvedOriginalYaml)}
+        onDelete={() => void deleteCurrent(activeApp)}
+        onToggleTemplateVisibility={(app, hide) => void toggleTemplateVisibility(app, hide)}
+        onToggleDarkMode={setDarkMode}
+        onEditorValueChange={value => {
+          setEditorValue(value);
+          clearMutationMessage();
+          if (!isEditing) {
+            setIsEditing(true);
+          }
+        }}
+      />
+    );
+  }
+
+  return (
+    <ClientWorkbench
+      load={load}
+      loadingCopy={t('setting.define.loading')}
+      cacheKey={settingDefineCacheKey}
+      cacheSettledTtlMs={SETTING_DEFINE_SETTLED_CACHE_TTL_MS}
+      renderError={(message) => renderDefineSurface(buildFallbackDefineData(), message)}
+    >
+      {data => renderDefineSurface(data)}
+    </ClientWorkbench>
+  );
+}
diff --git a/web-next/app/setting/labels/page.test.tsx b/web-next/app/setting/labels/page.test.tsx
index 22e16e93e5..e90db4cb8e 100644
--- a/web-next/app/setting/labels/page.test.tsx
+++ b/web-next/app/setting/labels/page.test.tsx
@@ -1,14 +1,22 @@
+// @vitest-environment jsdom
+
 import React from 'react';
 import { readFileSync } from 'node:fs';
 import { resolve } from 'node:path';
+import { act } from 'react';
+import { createRoot } from 'react-dom/client';
 import { renderToStaticMarkup } from 'react-dom/server';
-import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
 import { createTranslatorMock } from '../../../test/i18n-test-helper';
 
 (globalThis as { React?: typeof React }).React = React;
+(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
 
 const mockState = vi.hoisted(() => ({
   lastLoad: null as null | (() => Promise<unknown>),
+  lastRetry: null as null | (() => void),
+  renderErrorMessage: null as string | null,
+  lastSurfaceProps: null as null | Record<string, any>,
   renderData: {
     list: {
       content: [
@@ -32,14 +40,14 @@ const apiMessagePost = vi.hoisted(() => vi.fn());
 const apiMessagePut = vi.hoisted(() => vi.fn());
 const apiMessageDelete = vi.hoisted(() => vi.fn());
 const loadLabelData = vi.hoisted(() => vi.fn());
+const saveLabel = vi.hoisted(() => vi.fn());
+const deleteLabel = vi.hoisted(() => vi.fn());
+let SettingLabelsPage: React.ComponentType;
 
 vi.mock('@/components/providers/i18n-provider', () => ({
   useI18n: () => ({
     t: createTranslatorMock({
-      locale: 'zh-CN',
-      overrides: {
-        'setting.labels.loading': '正在加载标签'
-      }
+      locale: 'zh-CN'
     })
   })
 }));
@@ -47,30 +55,136 @@ vi.mock('@/components/providers/i18n-provider', () => ({
 vi.mock('@/components/workbench/client-workbench', () => ({
   ClientWorkbench: ({
     children,
-    load
+    load,
+    renderError
   }: {
     children: (data: unknown) => React.ReactNode;
     load: () => Promise<unknown>;
+    renderError?: (message: string, retry: () => void) => React.ReactNode;
   }) => {
     mockState.lastLoad = load;
+    if (mockState.renderErrorMessage && renderError) {
+      const retry = vi.fn();
+      mockState.lastRetry = retry;
+      return <div data-client-workbench="true">{renderError(mockState.renderErrorMessage, retry)}</div>;
+    }
     return <div data-client-workbench="true">{children(mockState.renderData)}</div>;
   }
 }));
 
 vi.mock('@/components/pages/label-manage-surface', () => ({
-  LabelManageSurface: ({ search, draftLabel, isManageModalAdd }: any) => (
+  LabelManageSurface: (props: any) => {
+    mockState.lastSurfaceProps = props;
+    const {
+      data,
+      search,
+      draftLabel,
+      isManageModalAdd,
+      isNameValidationVisible,
+      deleteTarget,
+      isLoadPending,
+      isDeletePending,
+      isSavePending,
+      actionMessage,
+      actionError,
+      actionMeta,
+      loadError,
+      onSearchClear,
+      onEdit,
+      onDraftChange,
+      onCloseDialog
+    } = props;
+    return (
     <div
       data-label-manage-surface="otlp-cold-label-console"
       data-label-manage-style-baseline="hertzbeat-cold-matte"
+      data-label-action-feedback={actionError ? 'angular-action-error' : actionMessage ? 'angular-action-success' : 'none'}
+      data-label-save-feedback={actionError ? 'angular-save-error' : actionMessage ? 'angular-save-success' : 'none'}
+      data-label-action-meta={actionMeta ?? 'no-action-meta'}
+      data-label-save-feedback-contract="angular-new-edit-notify"
+      data-label-save-feedback-contract-owner="hertzbeat-ui-inline-feedback"
+      data-label-save-failure-contract="angular-new-edit-fail-notification"
+      data-label-save-failure-owner="hertzbeat-ui-inline-feedback"
+      data-label-load-failure-contract="angular-console-only-shell"
+      data-label-load-failure-owner="label-route-controller"
+      data-label-load-failure={loadError ? 'angular-console-only-shell' : 'none'}
+      data-label-copy-feedback-contract="angular-copy-notify"
+      data-label-copy-feedback-contract-owner="hertzbeat-ui-inline-feedback"
+      data-label-delete-confirm-contract="angular-modal-confirm"
+      data-label-delete-confirm-contract-owner="hertzbeat-ui-confirm-dialog"
+      data-label-delete-confirm-state-contract="angular-modal-confirm-state"
+      data-label-delete-confirm-state-contract-owner="hertzbeat-ui-confirm-dialog"
+      data-label-delete-confirm-state={deleteTarget ? 'open' : 'closed'}
+      data-label-delete-confirm-close-contract="angular-close-before-delete-result"
+      data-label-delete-confirm-close-owner="route-state-contract"
+      data-label-delete-feedback-contract="angular-delete-notify"
+      data-label-delete-feedback-contract-owner="hertzbeat-ui-inline-feedback"
+      data-label-delete-failure-contract="angular-delete-fail-notification"
+      data-label-delete-failure-owner="hertzbeat-ui-inline-feedback"
+      data-label-delete-query-contract="angular-repeated-ids-query"
+      data-label-delete-query-owner="route-mutation-contract"
+      data-label-card-loading-contract="angular-table-loading"
+      data-label-card-loading-owner="label-route-controller"
+      data-label-card-loading={isLoadPending || isDeletePending ? 'true' : 'false'}
+      data-label-save-loading-contract="angular-nz-ok-loading"
+      data-label-save-loading-contract-owner="angular-nz-ok-loading"
+      data-label-save-trim-contract="angular-trim-before-save"
+      data-label-save-trim-owner="label-manage-controller"
+      data-label-optional-save-payload-contract="angular-preserve-undefined-optional-fields"
+      data-label-optional-save-payload-owner="label-manage-controller"
+      data-label-type-save-payload-contract="angular-preserve-implicit-type"
+      data-label-type-save-payload-owner="label-manage-controller"
+      data-label-description-display-contract="angular-truthy-description-raw"
+      data-label-description-display-owner="label-manage-surface"
+      data-label-dialog-mask-closable-contract="angular-mask-closable-false"
+      data-label-dialog-mask-closable-owner="angular-nz-modal"
+      data-label-dialog-width-contract="angular-width-30-percent"
+      data-label-dialog-width-owner="angular-nz-modal"
+      data-label-dialog-field-layout-contract="angular-label-7-control-12"
+      data-label-dialog-field-layout-owner="route-form-field-grid"
+      data-label-dialog-preview-visibility-contract="angular-name-defined-preview"
+      data-label-dialog-preview-visibility-owner="route-form-state"
+      data-label-dialog-preview-frame-contract="angular-inline-form-item"
+      data-label-dialog-preview-frame-owner="route-form-field-grid"
+      data-label-edit-reference-contract="angular-edit-direct-reference"
+      data-label-edit-reference-owner="route-form-state"
+      data-label-save-ok-loading={isSavePending ? 'true' : 'false'}
+      data-label-save-ok-loading-owner="angular-nz-ok-loading"
+      data-label-name-validation-contract="angular-required-before-submit"
+      data-label-name-validation-trigger="angular-ok-marks-dirty"
+      data-label-name-validation-trigger-owner="route-form-contract"
+      data-label-name-validation-raw-contract="angular-required-before-trim"
+      data-label-name-validation-raw-owner="route-form-contract"
+      data-label-name-validation-visible={isNameValidationVisible ? 'true' : 'false'}
+      data-label-query-contract="angular-load-all-labels"
+      data-label-query-owner="label-query-state"
+      data-label-query-param-order-contract="angular-page-index-size-type-search"
+      data-label-query-param-order-owner="label-query-state"
+      data-label-search-submit="angular-enter-and-clear"
+      data-label-search-submit-owner="cold-search-row"
+      data-label-search-clear-wired={typeof onSearchClear === 'function' ? 'true' : 'false'}
+      data-label-card-grid-contract="angular-card-grid"
+      data-label-card-grid-owner="hertzbeat-ui-label-tag"
+      data-label-monitor-handoff-contract="angular-routerlink-monitors-labels"
+      data-label-monitor-handoff-owner="next-monitor-query-link"
     >
       <section data-label-admin-layout="full-width-admin-list">
         <span>标签管理</span>
         <span>{search}</span>
+        <span data-label-test-first-name="true">{data.list.content[0]?.name || 'no-row'}</span>
         <span>{draftLabel ? draftLabel.name : 'no-draft'}</span>
         <span>{String(isManageModalAdd)}</span>
+        <span>{deleteTarget ? deleteTarget.name : 'no-delete-target'}</span>
+        <span>{actionMessage || actionError || 'no-feedback'}</span>
+        <span>{actionMeta || 'no-meta'}</span>
+        <span>{loadError || 'no-load-error'}</span>
+        <button data-label-test-edit="true" onClick={() => onEdit(data.list.content[0])}>edit</button>
+        <button data-label-test-change-name="true" onClick={() => onDraftChange({ name: 'platform' })}>change name</button>
+        <button data-label-test-close="true" onClick={onCloseDialog}>close</button>
       </section>
     </div>
-  )
+    );
+  }
 }));
 
 vi.mock('@/lib/api-client', () => ({
@@ -86,27 +200,52 @@ vi.mock('@/lib/format', () => ({
 
 vi.mock('@/lib/label-manage/controller', () => ({
   loadLabelData,
-  createEmptyLabelDraft: () => ({ id: 0, name: '', tagValue: '', description: '', type: 1 }),
+  createEmptyLabelDraft: () => ({ id: 0, name: undefined, tagValue: undefined, description: undefined, type: undefined }),
   cloneLabelDraft: (label: any) => ({ ...label }),
-  saveLabel: vi.fn(),
-  deleteLabel: vi.fn()
+  saveLabel,
+  deleteLabel
 }));
 
 describe('setting labels page', () => {
+  beforeAll(async () => {
+    const pageModule = await import('./page');
+    SettingLabelsPage = pageModule.default;
+  }, 30000);
+
   beforeEach(() => {
     mockState.lastLoad = null;
+    mockState.lastRetry = null;
+    mockState.renderErrorMessage = null;
+    mockState.lastSurfaceProps = null;
+    mockState.renderData = {
+      list: {
+        content: [
+          {
+            id: 1,
+            name: 'team',
+            tagValue: 'ops',
+            description: 'ops team',
+            type: 1
+          }
+        ],
+        totalElements: 1,
+        pageIndex: 0,
+        pageSize: 8
+      }
+    };
     apiMessageGet.mockReset();
     apiMessagePost.mockReset();
     apiMessagePut.mockReset();
     apiMessageDelete.mockReset();
+    saveLabel.mockReset().mockResolvedValue(undefined);
+    deleteLabel.mockReset().mockResolvedValue(undefined);
     loadLabelData.mockReset().mockImplementation(async (apiGetFn, query) => {
-      await apiGetFn(`/label?pageIndex=0&pageSize=8${query.search ? `&search=${query.search}` : ''}`);
+      await apiGetFn(`/label?pageIndex=0&pageSize=9999${query.search ? `&search=${query.search}` : ''}`);
       return mockState.renderData;
     });
   });
 
   it('renders the shared label surface and keeps the route focused on load composition', async () => {
-    const { default: SettingLabelsPage } = await import('./page');
     const html = renderToStaticMarkup(<SettingLabelsPage />);
 
     expect(html).toContain('data-client-workbench="true"');
@@ -119,16 +258,156 @@ describe('setting labels page', () => {
     expect(html).toContain('标签管理');
     expect(html).toContain('no-draft');
     expect(html).toContain('false');
+    expect(html).toContain('data-label-save-feedback="none"');
+    expect(html).toContain('data-label-save-feedback-contract="angular-new-edit-notify"');
+    expect(html).toContain('data-label-save-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-label-save-failure-contract="angular-new-edit-fail-notification"');
+    expect(html).toContain('data-label-save-failure-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-label-load-failure-contract="angular-console-only-shell"');
+    expect(html).toContain('data-label-load-failure-owner="label-route-controller"');
+    expect(html).toContain('data-label-load-failure="none"');
+    expect(html).toContain('data-label-copy-feedback-contract="angular-copy-notify"');
+    expect(html).toContain('data-label-copy-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-label-delete-confirm-contract="angular-modal-confirm"');
+    expect(html).toContain('data-label-delete-confirm-contract-owner="hertzbeat-ui-confirm-dialog"');
+    expect(html).toContain('data-label-delete-confirm-state-contract="angular-modal-confirm-state"');
+    expect(html).toContain('data-label-delete-confirm-state-contract-owner="hertzbeat-ui-confirm-dialog"');
+    expect(html).toContain('data-label-delete-confirm-state="closed"');
+    expect(html).toContain('data-label-delete-confirm-close-contract="angular-close-before-delete-result"');
+    expect(html).toContain('data-label-delete-confirm-close-owner="route-state-contract"');
+    expect(html).toContain('data-label-delete-feedback-contract="angular-delete-notify"');
+    expect(html).toContain('data-label-delete-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-label-delete-failure-contract="angular-delete-fail-notification"');
+    expect(html).toContain('data-label-delete-failure-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-label-delete-query-contract="angular-repeated-ids-query"');
+    expect(html).toContain('data-label-delete-query-owner="route-mutation-contract"');
+    expect(html).toContain('data-label-card-loading-contract="angular-table-loading"');
+    expect(html).toContain('data-label-card-loading-owner="label-route-controller"');
+    expect(html).toContain('data-label-card-loading="false"');
+    expect(html).toContain('data-label-dialog-field-layout-contract="angular-label-7-control-12"');
+    expect(html).toContain('data-label-dialog-field-layout-owner="route-form-field-grid"');
+    expect(html).toContain('data-label-dialog-preview-visibility-contract="angular-name-defined-preview"');
+    expect(html).toContain('data-label-dialog-preview-visibility-owner="route-form-state"');
+    expect(html).toContain('data-label-dialog-preview-frame-contract="angular-inline-form-item"');
+    expect(html).toContain('data-label-dialog-preview-frame-owner="route-form-field-grid"');
+    expect(html).toContain('data-label-edit-reference-contract="angular-edit-direct-reference"');
+    expect(html).toContain('data-label-edit-reference-owner="route-form-state"');
+    expect(html).toContain('data-label-save-loading-contract="angular-nz-ok-loading"');
+    expect(html).toContain('data-label-save-loading-contract-owner="angular-nz-ok-loading"');
+    expect(html).toContain('data-label-save-trim-contract="angular-trim-before-save"');
+    expect(html).toContain('data-label-save-trim-owner="label-manage-controller"');
+    expect(html).toContain('data-label-optional-save-payload-contract="angular-preserve-undefined-optional-fields"');
+    expect(html).toContain('data-label-optional-save-payload-owner="label-manage-controller"');
+    expect(html).toContain('data-label-type-save-payload-contract="angular-preserve-implicit-type"');
+    expect(html).toContain('data-label-type-save-payload-owner="label-manage-controller"');
+    expect(html).toContain('data-label-dialog-mask-closable-contract="angular-mask-closable-false"');
+    expect(html).toContain('data-label-dialog-mask-closable-owner="angular-nz-modal"');
+    expect(html).toContain('data-label-dialog-width-contract="angular-width-30-percent"');
+    expect(html).toContain('data-label-dialog-width-owner="angular-nz-modal"');
+    expect(html).toContain('data-label-save-ok-loading="false"');
+    expect(html).toContain('data-label-save-ok-loading-owner="angular-nz-ok-loading"');
+    expect(html).toContain('data-label-name-validation-contract="angular-required-before-submit"');
+    expect(html).toContain('data-label-name-validation-trigger="angular-ok-marks-dirty"');
+    expect(html).toContain('data-label-name-validation-trigger-owner="route-form-contract"');
+    expect(html).toContain('data-label-name-validation-raw-contract="angular-required-before-trim"');
+    expect(html).toContain('data-label-name-validation-raw-owner="route-form-contract"');
+    expect(html).toContain('data-label-name-validation-visible="false"');
+    expect(html).toContain('data-label-query-contract="angular-load-all-labels"');
+    expect(html).toContain('data-label-query-owner="label-query-state"');
+    expect(html).toContain('data-label-query-param-order-contract="angular-page-index-size-type-search"');
+    expect(html).toContain('data-label-query-param-order-owner="label-query-state"');
+    expect(html).toContain('data-label-search-submit="angular-enter-and-clear"');
+    expect(html).toContain('data-label-search-submit-owner="cold-search-row"');
+    expect(html).toContain('data-label-search-clear-wired="true"');
+    expect(html).toContain('data-label-card-grid-contract="angular-card-grid"');
+    expect(html).toContain('data-label-card-grid-owner="hertzbeat-ui-label-tag"');
+    expect(html).toContain('data-label-monitor-handoff-contract="angular-routerlink-monitors-labels"');
+    expect(html).toContain('data-label-monitor-handoff-owner="next-monitor-query-link"');
+    expect(html).toContain('no-delete-target');
+    expect(html).toContain('no-feedback');
+    expect(html).toContain('no-meta');
+    expect(html).toContain('no-load-error');
 
     await mockState.lastLoad?.();
 
-    expect(apiMessageGet).toHaveBeenCalledWith('/label?pageIndex=0&pageSize=8');
+    expect(apiMessageGet).toHaveBeenCalledWith('/label?pageIndex=0&pageSize=9999');
+  }, 30000);
+
+  it('keeps the Angular label shell visible when list loading fails', () => {
+    mockState.renderErrorMessage = 'backend refused label load';
+
+    const html = renderToStaticMarkup(<SettingLabelsPage />);
+
+    expect(html).toContain('data-client-workbench="true"');
+    expect(html).toContain('data-label-manage-surface="otlp-cold-label-console"');
+    expect(html).toContain('data-label-load-failure-contract="angular-console-only-shell"');
+    expect(html).toContain('data-label-load-failure-owner="label-route-controller"');
+    expect(html).toContain('data-label-load-failure="angular-console-only-shell"');
+    expect(html).toContain('backend refused label load');
+    expect(html).toContain('data-label-card-loading="false"');
+    expect(html).toContain('data-label-save-feedback="none"');
+    expect(html).toContain('data-label-test-first-name="true">no-row');
+    expect(html).not.toContain('data-observability-status');
+    expect(html).not.toContain('common.load-failed');
+    expect(mockState.lastRetry).toBeTypeOf('function');
   });
 
+  it('keeps the Angular edit direct-reference behavior when canceling the label dialog', async () => {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+    const root = createRoot(container);
+
+    await act(async () => {
+      root.render(<SettingLabelsPage />);
+      await Promise.resolve();
+    });
+
+    expect(container.querySelector('[data-label-test-first-name="true"]')?.textContent).toBe('team');
+    expect(mockState.lastSurfaceProps?.draftLabel).toBeNull();
+
+    await act(async () => {
+      (container.querySelector('[data-label-test-edit="true"]') as HTMLButtonElement).click();
+      await Promise.resolve();
+    });
+
+    expect(mockState.lastSurfaceProps?.draftLabel).toBe(mockState.renderData.list.content[0]);
+    expect(mockState.lastSurfaceProps?.draftLabel?.name).toBe('team');
+
+    await act(async () => {
+      (container.querySelector('[data-label-test-change-name="true"]') as HTMLButtonElement).click();
+      await Promise.resolve();
+    });
+
+    expect(mockState.renderData.list.content[0].name).toBe('platform');
+    expect(container.querySelector('[data-label-test-first-name="true"]')?.textContent).toBe('platform');
+    expect(mockState.lastSurfaceProps?.draftLabel?.name).toBe('platform');
+
+    await act(async () => {
+      (container.querySelector('[data-label-test-close="true"]') as HTMLButtonElement).click();
+      await Promise.resolve();
+    });
+
+    expect(mockState.lastSurfaceProps?.draftLabel).toBeNull();
+    expect(mockState.renderData.list.content[0].name).toBe('platform');
+    expect(container.querySelector('[data-label-test-first-name="true"]')?.textContent).toBe('platform');
+
+    await act(async () => {
+      root.unmount();
+      await Promise.resolve();
+    });
+    container.remove();
+  }, 30000);
+
   it('keeps the route thin and leaves cold label visuals in the shared surface', () => {
-    const source = readFileSync(resolve(process.cwd(), 'app/setting/labels/page.tsx'), 'utf8');
+    const source = readFileSync(resolve(process.cwd(), 'app/setting/labels/setting-labels-page.tsx'), 'utf8');
 
     expect(source).toContain('LabelManageSurface');
+    expect(source).toContain('EMPTY_LABEL_DATA');
+    expect(source).toContain('renderError={(message, retry) => renderSurface(EMPTY_LABEL_DATA, message, retry)}');
+    expect(source).toContain('buildLabelUrl(query)');
+    expect(source).toContain('setDraftLabel(label)');
+    expect(source).toContain('Object.assign(sourceLabel, patch)');
+    expect(source).not.toContain('cloneLabelDraft');
     expect(source).not.toContain('WorkbenchPage');
     expect(source).not.toContain('SurfaceSection');
     expect(source).not.toContain('ToolbarRow');
@@ -137,4 +416,297 @@ describe('setting labels page', () => {
     expect(source).not.toContain('data-label-toolbar');
     expect(source).not.toContain('otlp-cold-label-console');
   });
+
+  it('keeps label management remounts on a short settled cache window while reloads invalidate it', () => {
+    const source = readFileSync(resolve(process.cwd(), 'app/setting/labels/setting-labels-page.tsx'), 'utf8');
+
+    expect(source).toContain('SETTING_LABELS_SETTLED_CACHE_TTL_MS = 10_000');
+    expect(source).toContain('const [isLoadPending, setIsLoadPending] = useState(false)');
+    expect(source).toContain('setIsLoadPending(true);');
+    expect(source).toContain('return await loadLabelData(apiMessageGet, query);');
+    expect(source).toContain('setIsLoadPending(false);');
+    expect(source).toContain('isLoadPending={isLoadPending}');
+    expect(source).toContain("['setting-labels', labelListUrl, reloadVersion].join(':')");
+    expect(source).toContain('[labelListUrl, reloadVersion]');
+    expect(source).toContain('onRefresh={() => {');
+    expect(source).toContain('if (retry) {');
+    expect(source).toContain('retry();');
+    expect(source).toContain('setReloadVersion(version => version + 1)');
+    expect(source).toContain('cacheKey={labelManageCacheKey}');
+    expect(source).toContain('cacheSettledTtlMs={SETTING_LABELS_SETTLED_CACHE_TTL_MS}');
+  });
+
+  it('resets the search draft and query when the Angular clear event fires', () => {
+    const source = readFileSync(resolve(process.cwd(), 'app/setting/labels/setting-labels-page.tsx'), 'utf8');
+
+    expect(source).toContain("onSearch={() => setQuery({ search, type: '' })}");
+    expect(source).toContain('onSearchClear={() => {');
+    expect(source).toContain("setSearch('');");
+    expect(source).toContain("setQuery({ search: '', type: '' });");
+  });
+
+  it('routes label save feedback through Angular create/edit notification keys', () => {
+    const source = readFileSync(resolve(process.cwd(), 'app/setting/labels/setting-labels-page.tsx'), 'utf8');
+
+    expect(source).toContain('const [actionMessage, setActionMessage] = useState<string | null>(null)');
+    expect(source).toContain('const [actionError, setActionError] = useState<string | null>(null)');
+    expect(source).toContain('const [actionMeta, setActionMeta] = useState<string | null>(null)');
+    expect(source).toContain('const [isNameValidationVisible, setIsNameValidationVisible] = useState(false)');
+    expect(source).toContain('actionMessage={actionMessage}');
+    expect(source).toContain('actionError={actionError}');
+    expect(source).toContain('actionMeta={actionMeta}');
+    expect(source).toContain('isNameValidationVisible={isNameValidationVisible}');
+    expect(source).toContain('if (draftLabel.name == undefined || draftLabel.name.length === 0) {');
+    expect(source).toContain('setIsNameValidationVisible(true);');
+    expect(source).toContain('return;');
+    expect(source).toContain('setIsNameValidationVisible(false);');
+    expect(source).toContain("setActionMessage(t(isEdit ? 'common.notify.edit-success' : 'common.notify.new-success'))");
+    expect(source).toContain("setActionError(t(isEdit ? 'common.notify.edit-fail' : 'common.notify.new-fail'))");
+    expect(source).toContain('setActionMeta(error instanceof Error ? error.message : null)');
+    expect(source).not.toContain("setActionMessage(t('common.save-success'))");
+    expect(source).not.toContain("setActionError(t('common.save-failed'))");
+  });
+
+  it('lets a whitespace-only label name follow Angular required-before-trim save behavior', async () => {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+    const root = createRoot(container);
+
+    await act(async () => {
+      root.render(<SettingLabelsPage />);
+      await Promise.resolve();
+    });
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onNew();
+      await Promise.resolve();
+    });
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onDraftChange({ name: '   ', tagValue: 'ops' });
+      await Promise.resolve();
+    });
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onSaveDialog();
+      await Promise.resolve();
+      await Promise.resolve();
+    });
+
+    expect(saveLabel).toHaveBeenCalledWith(
+      apiMessagePost,
+      apiMessagePut,
+      expect.objectContaining({ name: '   ', tagValue: 'ops' }),
+      true
+    );
+    expect(mockState.lastSurfaceProps?.isNameValidationVisible).toBe(false);
+
+    await act(async () => {
+      root.unmount();
+      await Promise.resolve();
+    });
+    container.remove();
+  }, 30000);
+
+  it('keeps Angular label create failure title with backend detail', async () => {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+    const root = createRoot(container);
+    saveLabel.mockRejectedValue(new Error('backend refused label create'));
+
+    await act(async () => {
+      root.render(<SettingLabelsPage />);
+      await Promise.resolve();
+    });
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onNew();
+      await Promise.resolve();
+    });
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onDraftChange({ name: 'team', tagValue: 'ops' });
+      await Promise.resolve();
+    });
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onSaveDialog();
+      await Promise.resolve();
+      await Promise.resolve();
+    });
+
+    expect(saveLabel).toHaveBeenCalledWith(
+      apiMessagePost,
+      apiMessagePut,
+      expect.objectContaining({ name: 'team', tagValue: 'ops' }),
+      true
+    );
+    expect(mockState.lastSurfaceProps?.actionError).toBe('新增失败!');
+    expect(mockState.lastSurfaceProps?.actionMeta).toBe('backend refused label create');
+
+    await act(async () => {
+      root.unmount();
+      await Promise.resolve();
+    });
+    container.remove();
+  }, 30000);
+
+  it('keeps Angular label edit failure title with backend detail', async () => {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+    const root = createRoot(container);
+    saveLabel.mockRejectedValue(new Error('backend refused label edit'));
+
+    await act(async () => {
+      root.render(<SettingLabelsPage />);
+      await Promise.resolve();
+    });
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onEdit(mockState.renderData.list.content[0]);
+      await Promise.resolve();
+    });
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onSaveDialog();
+      await Promise.resolve();
+      await Promise.resolve();
+    });
+
+    expect(saveLabel).toHaveBeenCalledWith(
+      apiMessagePost,
+      apiMessagePut,
+      mockState.renderData.list.content[0],
+      false
+    );
+    expect(mockState.lastSurfaceProps?.actionError).toBe('修改失败!');
+    expect(mockState.lastSurfaceProps?.actionMeta).toBe('backend refused label edit');
+
+    await act(async () => {
+      root.unmount();
+      await Promise.resolve();
+    });
+    container.remove();
+  }, 30000);
+
+  it('routes label copy feedback through Angular clipboard notification keys', () => {
+    const source = readFileSync(resolve(process.cwd(), 'app/setting/labels/setting-labels-page.tsx'), 'utf8');
+
+    expect(source).toContain("import { buildLabelDisplayName } from '@/lib/label-manage/view-model'");
+    expect(source).toContain('const text = buildLabelDisplayName(label)');
+    expect(source).toContain("setActionError(t('common.notify.copy-fail'))");
+    expect(source).toContain("then(() => setActionMessage(t('common.notify.copy-success')))");
+    expect(source).toContain("catch(() => setActionError(t('common.notify.copy-fail')))");
+    expect(source).not.toContain('navigator.clipboard.writeText(text);');
+  });
+
+  it('closes the Angular label delete confirmation before the delete result settles', async () => {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+    const root = createRoot(container);
+    deleteLabel.mockReturnValue(new Promise(() => undefined));
+
+    await act(async () => {
+      root.render(<SettingLabelsPage />);
+      await Promise.resolve();
+    });
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onDeleteRequest(mockState.renderData.list.content[0]);
+      await Promise.resolve();
+    });
+
+    expect(mockState.lastSurfaceProps?.deleteTarget?.name).toBe('team');
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onDeleteConfirm();
+      await Promise.resolve();
+    });
+
+    expect(deleteLabel).toHaveBeenCalledWith(apiMessageDelete, 1);
+    expect(mockState.lastSurfaceProps?.deleteTarget).toBeNull();
+    expect(mockState.lastSurfaceProps?.isDeletePending).toBe(true);
+
+    await act(async () => {
+      root.unmount();
+      await Promise.resolve();
+    });
+    container.remove();
+  }, 30000);
+
+  it('keeps Angular label delete failure title with backend detail', async () => {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+    const root = createRoot(container);
+    deleteLabel.mockRejectedValue(new Error('backend refused label delete'));
+
+    await act(async () => {
+      root.render(<SettingLabelsPage />);
+      await Promise.resolve();
+    });
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onDeleteRequest(mockState.renderData.list.content[0]);
+      await Promise.resolve();
+    });
+
+    await act(async () => {
+      mockState.lastSurfaceProps?.onDeleteConfirm();
+      await Promise.resolve();
+      await Promise.resolve();
+    });
+
+    expect(deleteLabel).toHaveBeenCalledWith(apiMessageDelete, 1);
+    expect(mockState.lastSurfaceProps?.deleteTarget).toBeNull();
+    expect(mockState.lastSurfaceProps?.actionError).toBe('删除失败!');
+    expect(mockState.lastSurfaceProps?.actionMeta).toBe('backend refused label delete');
+
+    await act(async () => {
+      root.unmount();
+      await Promise.resolve();
+    });
+    container.remove();
+  }, 30000);
+
+  it('routes label delete through Angular confirmation and notification keys', () => {
+    const source = readFileSync(resolve(process.cwd(), 'app/setting/labels/setting-labels-page.tsx'), 'utf8');
+
+    expect(source).toContain('const [deleteTarget, setDeleteTarget] = useState<Label | null>(null)');
+    expect(source).toContain('const [isDeletePending, setIsDeletePending] = useState(false)');
+    expect(source).toContain('const [isSavePending, setIsSavePending] = useState(false)');
+    expect(source).toContain('deleteTarget={deleteTarget}');
+    expect(source).toContain('isDeletePending={isDeletePending}');
+    expect(source).toContain('isSavePending={isSavePending}');
+    expect(source).toContain('onDeleteRequest={label =>');
+    expect(source).toContain('setDeleteTarget(label)');
+    expect(source).toContain('onDeleteConfirm={() =>');
+    expect(source).toContain('const target = deleteTarget;');
+    expect(source).toContain('setDeleteTarget(null);');
+    expect(source).toContain('void deleteLabel(apiMessageDelete, target.id)');
+    expect(source).toContain("setActionMessage(t('common.notify.delete-success'))");
+    expect(source).toContain("setActionError(t('common.notify.delete-fail'))");
+    expect(source).toContain('setActionMeta(error instanceof Error ? error.message : null)');
+    expect(source).not.toContain('onDelete={label =>');
+  });
+
+  it('routes label save pending state through Angular nzOkLoading semantics', () => {
+    const source = readFileSync(resolve(process.cwd(), 'app/setting/labels/setting-labels-page.tsx'), 'utf8');
+
+    expect(source).toContain('const [isSavePending, setIsSavePending] = useState(false)');
+    expect(source).toContain('isSavePending={isSavePending}');
+    expect(source).toContain('setIsSavePending(true);');
+    expect(source).toContain('setIsSavePending(false);');
+    expect(source).toContain('.finally(() => {');
+    expect(source).not.toContain('isManageModalOkLoading');
+  });
+
+  it('keeps Angular trim-before-save normalization in the label controller', () => {
+    const source = readFileSync(resolve(process.cwd(), 'lib/label-manage/controller.ts'), 'utf8');
+
+    expect(source).toContain('function normalizeLabelDraft(draft: Label): Label');
+    expect(source).toContain("name: (draft.name ?? '').trim()");
+    expect(source).toContain('tagValue: draft.tagValue == undefined ? undefined : draft.tagValue.trim()');
+    expect(source).toContain('description: draft.description == undefined ? undefined : draft.description.trim()');
+    expect(source).toContain('const payload = normalizeLabelDraft(draft)');
+  });
 });
diff --git a/web-next/app/setting/labels/page.tsx b/web-next/app/setting/labels/page.tsx
index 5a041ce0a7..63839a61ce 100644
--- a/web-next/app/setting/labels/page.tsx
+++ b/web-next/app/setting/labels/page.tsx
@@ -1,70 +1,7 @@
-'use client';
+import React from 'react';
 
-import { useCallback, useState } from 'react';
-import { ClientWorkbench } from '@/components/workbench/client-workbench';
-import { LabelManageSurface } from '@/components/pages/label-manage-surface';
-import { useI18n } from '@/components/providers/i18n-provider';
-import { apiMessageDelete, apiMessageGet, apiMessagePost, apiMessagePut } from '@/lib/api-client';
-import { formatTime } from '@/lib/format';
-import { cloneLabelDraft, createEmptyLabelDraft, deleteLabel, loadLabelData, saveLabel } from '@/lib/label-manage/controller';
-import type { LabelQueryState } from '@/lib/label-manage/query-state';
-import type { Label } from '@/lib/types';
+import SettingLabelsPage from './setting-labels-page';
 
-export default function SettingLabelsPage() {
-  const { t } = useI18n();
-  const [search, setSearch] = useState('');
-  const [query, setQuery] = useState<LabelQueryState>({ search: '', type: '' });
-  const [draftLabel, setDraftLabel] = useState<Label | null>(null);
-  const [isManageModalAdd, setIsManageModalAdd] = useState(false);
-  const [reloadVersion, setReloadVersion] = useState(0);
-
-  const load = useCallback(async () => {
-    return loadLabelData(apiMessageGet, query);
-  }, [query, reloadVersion]);
-
-  return (
-    <ClientWorkbench load={load} loadingCopy={t('setting.labels.loading')}>
-      {data => (
-        <LabelManageSurface
-          t={t}
-          data={data}
-          search={search}
-          draftLabel={draftLabel}
-          isManageModalAdd={isManageModalAdd}
-          formatTime={formatTime}
-          onSearchChange={setSearch}
-          onSearch={() => setQuery({ search, type: '' })}
-          onRefresh={() => setReloadVersion(version => version + 1)}
-          onNew={() => {
-            setDraftLabel(createEmptyLabelDraft());
-            setIsManageModalAdd(true);
-          }}
-          onCopy={label => {
-            const text = `${label.name}${label.tagValue ? `:${label.tagValue}` : ''}`;
-            if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
-              void navigator.clipboard.writeText(text);
-            }
-          }}
-          onEdit={label => {
-            setDraftLabel(cloneLabelDraft(label));
-            setIsManageModalAdd(false);
-          }}
-          onDelete={label => {
-            void deleteLabel(apiMessageDelete, label.id).then(() => {
-              setReloadVersion(version => version + 1);
-            });
-          }}
-          onDraftChange={patch => setDraftLabel(previous => (previous ? { ...previous, ...patch } : previous))}
-          onCloseDialog={() => setDraftLabel(null)}
-          onSaveDialog={() => {
-            if (!draftLabel) return;
-            void saveLabel(apiMessagePost, apiMessagePut, draftLabel, isManageModalAdd).then(() => {
-              setDraftLabel(null);
-              setReloadVersion(version => version + 1);
-            });
-          }}
-        />
-      )}
-    </ClientWorkbench>
-  );
+export default function SettingLabelsRoutePage() {
+  return <SettingLabelsPage />;
 }
diff --git a/web-next/app/setting/labels/setting-labels-page.tsx b/web-next/app/setting/labels/setting-labels-page.tsx
new file mode 100644
index 0000000000..912928a90e
--- /dev/null
+++ b/web-next/app/setting/labels/setting-labels-page.tsx
@@ -0,0 +1,215 @@
+'use client';
+
+import { useCallback, useMemo, useState } from 'react';
+import { ClientWorkbench } from '@/components/workbench/client-workbench';
+import { LabelManageSurface } from '@/components/pages/label-manage-surface';
+import { useI18n } from '@/components/providers/i18n-provider';
+import { apiMessageDelete, apiMessageGet, apiMessagePost, apiMessagePut } from '@/lib/api-client';
+import { formatTime } from '@/lib/format';
+import { createEmptyLabelDraft, deleteLabel, loadLabelData, saveLabel } from '@/lib/label-manage/controller';
+import { buildLabelUrl, type LabelQueryState } from '@/lib/label-manage/query-state';
+import { buildLabelDisplayName } from '@/lib/label-manage/view-model';
+import type { Label } from '@/lib/types';
+
+const SETTING_LABELS_SETTLED_CACHE_TTL_MS = 10_000;
+const EMPTY_LABEL_DATA = {
+  list: {
+    content: [],
+    totalElements: 0,
+    pageIndex: 0,
+    pageSize: 9999
+  }
+};
+
+export default function SettingLabelsPage() {
+  const { t } = useI18n();
+  const [search, setSearch] = useState('');
+  const [query, setQuery] = useState<LabelQueryState>({ search: '', type: '' });
+  const [draftLabel, setDraftLabel] = useState<Label | null>(null);
+  const [isManageModalAdd, setIsManageModalAdd] = useState(false);
+  const [deleteTarget, setDeleteTarget] = useState<Label | null>(null);
+  const [isLoadPending, setIsLoadPending] = useState(false);
+  const [isDeletePending, setIsDeletePending] = useState(false);
+  const [isSavePending, setIsSavePending] = useState(false);
+  const [isNameValidationVisible, setIsNameValidationVisible] = useState(false);
+  const [actionMessage, setActionMessage] = useState<string | null>(null);
+  const [actionError, setActionError] = useState<string | null>(null);
+  const [actionMeta, setActionMeta] = useState<string | null>(null);
+  const [reloadVersion, setReloadVersion] = useState(0);
+  const labelListUrl = useMemo(() => buildLabelUrl(query), [query]);
+  const labelManageCacheKey = useMemo(
+    () => ['setting-labels', labelListUrl, reloadVersion].join(':'),
+    [labelListUrl, reloadVersion]
+  );
+
+  const load = useCallback(async () => {
+    setIsLoadPending(true);
+    try {
+      return await loadLabelData(apiMessageGet, query);
+    } finally {
+      setIsLoadPending(false);
+    }
+  }, [query]);
+
+  const renderSurface = useCallback(
+    (data: Awaited<ReturnType<typeof loadLabelData>>, loadError: string | null = null, retry?: () => void) => (
+      <LabelManageSurface
+        t={t}
+        data={data}
+        search={search}
+        draftLabel={draftLabel}
+        isManageModalAdd={isManageModalAdd}
+        isNameValidationVisible={isNameValidationVisible}
+        deleteTarget={deleteTarget}
+        isLoadPending={isLoadPending}
+        isDeletePending={isDeletePending}
+        isSavePending={isSavePending}
+        actionMessage={actionMessage}
+        actionError={actionError}
+        actionMeta={actionMeta}
+        loadError={loadError}
+        formatTime={formatTime}
+        onSearchChange={setSearch}
+        onSearch={() => setQuery({ search, type: '' })}
+        onSearchClear={() => {
+          setSearch('');
+          setQuery({ search: '', type: '' });
+        }}
+        onRefresh={() => {
+          if (retry) {
+            retry();
+            return;
+          }
+          setReloadVersion(version => version + 1);
+        }}
+        onNew={() => {
+          setActionMessage(null);
+          setActionError(null);
+          setActionMeta(null);
+          setIsNameValidationVisible(false);
+          setDeleteTarget(null);
+          setDraftLabel(createEmptyLabelDraft());
+          setIsManageModalAdd(true);
+        }}
+        onCopy={label => {
+          const text = buildLabelDisplayName(label);
+          setActionMessage(null);
+          setActionError(null);
+          setActionMeta(null);
+          if (typeof navigator === 'undefined' || !navigator.clipboard?.writeText) {
+            setActionError(t('common.notify.copy-fail'));
+            return;
+          }
+          void navigator.clipboard
+            .writeText(text)
+            .then(() => setActionMessage(t('common.notify.copy-success')))
+            .catch(() => setActionError(t('common.notify.copy-fail')));
+        }}
+        onEdit={label => {
+          setActionMessage(null);
+          setActionError(null);
+          setActionMeta(null);
+          setIsNameValidationVisible(false);
+          setDeleteTarget(null);
+          setDraftLabel(label);
+          setIsManageModalAdd(false);
+        }}
+        onDeleteRequest={label => {
+          setActionMessage(null);
+          setActionError(null);
+          setActionMeta(null);
+          setDeleteTarget(label);
+        }}
+        onDeleteCancel={() => {
+          if (!isDeletePending) setDeleteTarget(null);
+        }}
+        onDeleteConfirm={() => {
+          if (!deleteTarget) return;
+          const target = deleteTarget;
+          setActionMessage(null);
+          setActionError(null);
+          setActionMeta(null);
+          setDeleteTarget(null);
+          setIsDeletePending(true);
+          void deleteLabel(apiMessageDelete, target.id)
+            .then(() => {
+              setActionMessage(t('common.notify.delete-success'));
+              setReloadVersion(version => version + 1);
+            })
+            .catch(error => {
+              setActionError(t('common.notify.delete-fail'));
+              setActionMeta(error instanceof Error ? error.message : null);
+            })
+            .finally(() => {
+              setIsDeletePending(false);
+            });
+        }}
+        onDraftChange={patch => setDraftLabel(previous => {
+          if (!previous) return previous;
+          if (!isManageModalAdd && previous.id) {
+            const sourceLabel = data.list.content.find(label => label.id === previous.id);
+            if (sourceLabel) Object.assign(sourceLabel, patch);
+          }
+          return { ...previous, ...patch };
+        })}
+        onCloseDialog={() => {
+          setIsNameValidationVisible(false);
+          setDraftLabel(null);
+        }}
+        onSaveDialog={() => {
+          if (!draftLabel) return;
+          if (draftLabel.name == undefined || draftLabel.name.length === 0) {
+            setIsNameValidationVisible(true);
+            return;
+          }
+          const isEdit = !isManageModalAdd && Boolean(draftLabel.id);
+          setActionMessage(null);
+          setActionError(null);
+          setActionMeta(null);
+          setIsNameValidationVisible(false);
+          setIsSavePending(true);
+          void saveLabel(apiMessagePost, apiMessagePut, draftLabel, isManageModalAdd)
+            .then(() => {
+              setIsNameValidationVisible(false);
+              setDraftLabel(null);
+              setActionMessage(t(isEdit ? 'common.notify.edit-success' : 'common.notify.new-success'));
+              setReloadVersion(version => version + 1);
+            })
+            .catch(error => {
+              setActionError(t(isEdit ? 'common.notify.edit-fail' : 'common.notify.new-fail'));
+              setActionMeta(error instanceof Error ? error.message : null);
+            })
+            .finally(() => {
+              setIsSavePending(false);
+            });
+        }}
+      />
+    ),
+    [
+      actionError,
+      actionMessage,
+      actionMeta,
+      deleteTarget,
+      draftLabel,
+      isDeletePending,
+      isLoadPending,
+      isManageModalAdd,
+      isNameValidationVisible,
+      isSavePending,
+      search,
+      t
+    ]
+  );
+
+  return (
+    <ClientWorkbench
+      load={load}
+      loadingCopy={t('setting.labels.loading')}
+      cacheKey={labelManageCacheKey}
+      cacheSettledTtlMs={SETTING_LABELS_SETTLED_CACHE_TTL_MS}
+      renderError={(message, retry) => renderSurface(EMPTY_LABEL_DATA, message, retry)}
+    >
+      {data => renderSurface(data)}
+    </ClientWorkbench>
+  );
+}
diff --git a/web-next/app/setting/plugins/page.test.tsx b/web-next/app/setting/plugins/page.test.tsx
index 0b0efc5612..50cedeec34 100644
--- a/web-next/app/setting/plugins/page.test.tsx
+++ b/web-next/app/setting/plugins/page.test.tsx
@@ -9,6 +9,7 @@ import { createTranslatorMock } from '../../../test/i18n-test-helper';
 
 const mockState = vi.hoisted(() => ({
   lastLoad: null as null | (() => Promise<unknown>),
+  renderErrorMessage: null as string | null,
   renderData: {
     list: {
       content: [
@@ -35,11 +36,9 @@ const loadPluginData = vi.hoisted(() => vi.fn());
 
 vi.mock('@/components/providers/i18n-provider', () => ({
   useI18n: () => ({
+    locale: 'zh-CN',
     t: createTranslatorMock({
-      locale: 'zh-CN',
-      overrides: {
-        'setting.plugins.loading': '加载插件'
-      }
+      locale: 'zh-CN'
     })
   })
 }));
@@ -47,28 +46,161 @@ vi.mock('@/components/providers/i18n-provider', () => ({
 vi.mock('@/components/workbench/client-workbench', () => ({
   ClientWorkbench: ({
     children,
-    load
+    load,
+    renderError
   }: {
     children: (data: unknown) => React.ReactNode;
     load: () => Promise<unknown>;
+    renderError?: (message: string, retry: () => void) => React.ReactNode;
   }) => {
     mockState.lastLoad = load;
+    if (mockState.renderErrorMessage && renderError) {
+      return <div data-client-workbench="true">{renderError(mockState.renderErrorMessage, vi.fn())}</div>;
+    }
     return <div data-client-workbench="true">{children(mockState.renderData)}</div>;
   }
 }));
 
 vi.mock('@/components/pages/plugin-manage-surface', () => ({
-  PluginManageSurface: ({ search, draftPlugin }: any) => (
+  PluginManageSurface: ({ data, search, draftPlugin, isUploadDialogOpen, paramDraft, actionMessage, actionError, actionMeta, actionTone, actionKind, loadError, uploadValidation, isUploadPending, isParamPending, isLoadPending, isTogglePending, optimisticEnableStatus, deleteTarget }: any) => (
     <div
       data-plugin-manage-surface="otlp-cold-plugin-console"
       data-plugin-manage-style-baseline="hertzbeat-cold-matte"
+      data-plugin-enable-feedback-contract="angular-edit-notify"
+      data-plugin-enable-feedback-contract-owner="hertzbeat-ui-inline-feedback"
+      data-plugin-enable-failure-contract="angular-edit-fail-notification"
+      data-plugin-enable-failure-owner="hertzbeat-ui-inline-feedback"
+      data-plugin-enable-optimistic-contract="angular-toggle-mutates-row-before-put"
+      data-plugin-enable-optimistic-owner="route-state-contract"
+      data-plugin-table-loading-contract="angular-load-plugins-table"
+      data-plugin-table-loading-scope="load-search-refresh-pagination-mutation"
+      data-plugin-load-failure-contract="angular-console-only-shell"
+      data-plugin-load-failure-owner="plugin-route-controller"
+      data-plugin-load-failure={loadError ? 'angular-console-only-shell' : 'none'}
+      data-plugin-toggle-loading-contract="angular-nz-table-loading"
+      data-plugin-toggle-loading-contract-owner="angular-nz-table-loading"
+      data-plugin-selection-reset-contract="angular-load-clears-selection"
+      data-plugin-selection-reset-owner="route-state-contract"
+      data-plugin-query-param-order-contract="angular-page-index-size-search"
+      data-plugin-query-param-order-owner="plugin-query-state"
+      data-plugin-search-submit-contract="angular-enter-and-clear"
+      data-plugin-search-clear-contract="angular-cleared-load"
+      data-plugin-search-clear-owner="shared-search-row"
+      data-plugin-delete-warning-contract="angular-no-select-warning"
+      data-plugin-delete-confirm-contract="angular-modal-confirm"
+      data-plugin-delete-confirm-contract-owner="hertzbeat-ui-confirm-dialog"
+      data-plugin-delete-feedback-contract="angular-delete-notify"
+      data-plugin-delete-feedback-contract-owner="hertzbeat-ui-inline-feedback"
+      data-plugin-delete-failure-contract="angular-delete-fail-notification"
+      data-plugin-delete-failure-owner="hertzbeat-ui-inline-feedback"
+      data-plugin-delete-query-contract="angular-repeated-ids-query"
+      data-plugin-delete-query-owner="route-mutation-contract"
+      data-plugin-delete-page-clamp-contract="angular-update-page-index"
+      data-plugin-table-columns-contract="angular-five-column-edit-actions"
+      data-plugin-table-columns-owner="angular-nz-table"
+      data-plugin-table-stable-height-contract="viewport-fill-on-empty-or-short"
+      data-plugin-table-stable-height-owner="route-layout-contract"
+      data-plugin-toolbar-delete-menu-contract="angular-toolbar-ellipsis-delete"
+      data-plugin-toolbar-delete-menu-owner="hertzbeat-ui-table-row-action-button"
+      data-plugin-row-delete-menu-contract="angular-ellipsis-dropdown-delete"
+      data-plugin-row-delete-menu-owner="hertzbeat-ui-table-row-action-button"
+      data-plugin-upload-validation-contract="angular-required-before-submit"
+      data-plugin-upload-invalid-submit-contract="angular-mark-dirty-keep-open"
+      data-plugin-upload-invalid-submit-owner="route-validation-contract"
+      data-plugin-upload-loading-contract="angular-nz-ok-loading"
+      data-plugin-upload-payload-contract="angular-form-data"
+      data-plugin-upload-name-payload-contract="angular-raw-name-no-trim"
+      data-plugin-upload-name-payload-owner="plugin-manage-controller"
+      data-plugin-upload-save-lifecycle-contract="angular-close-success-keep-open-fail"
+      data-plugin-upload-save-lifecycle-owner="route-state-contract"
+      data-plugin-upload-success-reset-contract="angular-reset-form-after-success"
+      data-plugin-upload-success-reset-owner="route-state-contract"
+      data-plugin-upload-cancel-contract="angular-cancel-preserves-form"
+      data-plugin-upload-cancel-contract-owner="route-state-contract"
+      data-plugin-upload-cancel-pending-contract="angular-cancel-allowed-during-ok-loading"
+      data-plugin-upload-cancel-pending-owner="angular-nz-modal"
+      data-plugin-upload-mask-closable-contract="angular-mask-closable-false"
+      data-plugin-upload-width-contract="angular-width-30-percent"
+      data-plugin-upload-modal-owner="angular-nz-modal"
+      data-plugin-upload-field-layout-contract="angular-label-8-control-14"
+      data-plugin-upload-field-layout-owner="route-form-field-grid"
+      data-plugin-upload-status-control-contract="angular-nz-switch"
+      data-plugin-upload-status-control-owner="hertzbeat-ui-switch"
+      data-plugin-upload-file-list-contract="angular-before-upload-single-replace"
+      data-plugin-upload-file-list-owner="hertzbeat-ui-file-input"
+      data-plugin-upload-file-remove-contract="angular-nz-remove-clears-jar"
+      data-plugin-upload-file-remove-owner="hertzbeat-ui-file-input"
+      data-plugin-upload-feedback-contract="angular-new-notify"
+      data-plugin-upload-feedback-contract-owner="hertzbeat-ui-inline-feedback"
+      data-plugin-upload-failure-contract="angular-new-fail-notification"
+      data-plugin-upload-failure-owner="hertzbeat-ui-inline-feedback"
+      data-plugin-param-edit-contract="angular-params-define-modal"
+      data-plugin-param-action-visibility-contract="angular-paramcount-zero-only"
+      data-plugin-param-action-visibility-owner="plugin-view-model"
+      data-plugin-param-mask-closable-contract="angular-mask-closable-false"
+      data-plugin-param-modal-owner="angular-nz-modal"
+      data-plugin-param-save-contract="angular-params-post"
+      data-plugin-param-payload-contract="angular-object-values-payload"
+      data-plugin-param-save-lifecycle-contract="angular-close-success-keep-open-fail"
+      data-plugin-param-save-lifecycle-owner="route-state-contract"
+      data-plugin-param-save-loading-contract="angular-no-ok-loading"
+      data-plugin-param-save-loading-owner="angular-modal-ok-contract"
+      data-plugin-param-feedback-contract="angular-edit-notify"
+      data-plugin-param-failure-contract="angular-edit-fail-notification"
+      data-plugin-param-failure-owner="hertzbeat-ui-inline-feedback"
+      data-plugin-param-empty-contract="angular-empty-params-modal"
+      data-plugin-param-empty-owner="hertzbeat-ui-monitor-editor-field-grid"
+      data-plugin-param-pending-editable-contract="angular-controls-remain-enabled"
+      data-plugin-param-pending-editable-owner="angular-param-modal"
+      data-plugin-param-width-contract="angular-default-modal-width"
+      data-plugin-param-width-owner="angular-nz-modal"
+      data-plugin-param-radio-contract="angular-nz-radio-group"
+      data-plugin-param-radio-owner="hertzbeat-ui-radio-button-group"
+      data-plugin-param-textarea-contract="angular-textarea-rows-8"
+      data-plugin-param-textarea-owner="hertzbeat-ui-textarea"
+      data-plugin-param-advanced-fields-contract="angular-configurable-and-multi-func"
+      data-plugin-param-advanced-fields-owner="hertzbeat-ui-advanced-param-controls"
+      data-plugin-param-configurable-field-contract="angular-app-configurable-field"
+      data-plugin-param-configurable-field-owner="hertzbeat-ui-configurable-field"
+      data-plugin-param-key-value-contract="angular-app-configurable-field"
+      data-plugin-param-key-value-owner="hertzbeat-ui-key-value-editor"
+      data-plugin-param-labels-contract="angular-app-configurable-field"
+      data-plugin-param-labels-owner="hertzbeat-ui-key-value-editor"
+      data-plugin-param-label-selector-contract="angular-app-label-selector"
+      data-plugin-param-label-selector-owner="cold-label-selector"
+      data-plugin-param-metrics-field-contract="angular-app-configurable-field"
+      data-plugin-param-metrics-field-owner="hertzbeat-ui-configurable-field-editor"
+      data-plugin-param-array-contract="angular-app-multi-func-input"
+      data-plugin-param-array-owner="hertzbeat-ui-input"
+      data-plugin-param-field-layout-contract="angular-label-7-control-8"
+      data-plugin-param-field-layout-owner="hertzbeat-ui-monitor-editor-field-grid"
+      data-plugin-param-form-owner="hertzbeat-ui-monitor-editor-field-grid"
+      data-plugin-pagination="cold-dense-pagination"
+      data-plugin-pagination-owner="hertzbeat-ui-pagination-bar"
+      data-plugin-pagination-contract="angular-search-pagination"
     >
       <section data-plugin-admin-layout="full-width-admin-list">
         <span>插件表格</span>
       </section>
+      <span data-plugin-test-row-count="true">{data.list.content.length}</span>
       <span>插件管理</span>
       <span>{search}</span>
       <span>{draftPlugin ? draftPlugin.name : 'no-draft'}</span>
+      <span>{isUploadDialogOpen ? 'upload-dialog-open' : 'upload-dialog-closed'}</span>
+      <span>{paramDraft ? paramDraft.plugin.name : 'no-param-draft'}</span>
+      <span>{actionMessage ?? 'no-action-message'}</span>
+      <span>{actionError ?? 'no-action-error'}</span>
+      <span>{actionMeta ?? 'no-action-meta'}</span>
+      <span>{actionTone ?? 'no-action-tone'}</span>
+      <span>{actionKind ?? 'no-action-kind'}</span>
+      <span data-plugin-test-load-error={loadError ? 'present' : 'none'} />
+      <span>{uploadValidation?.name ? 'name-invalid' : 'name-valid'}</span>
+      <span>{isUploadPending ? 'upload-pending' : 'upload-idle'}</span>
+      <span>{isParamPending ? 'param-pending' : 'param-idle'}</span>
+      <span>{isLoadPending ? 'load-pending' : 'load-idle'}</span>
+      <span>{isTogglePending ? 'toggle-pending' : 'toggle-idle'}</span>
+      <span>{Object.keys(optimisticEnableStatus ?? {}).length === 0 ? 'no-optimistic-enable-status' : 'optimistic-enable-status'}</span>
+      <span>{deleteTarget ? deleteTarget.label : 'no-delete-target'}</span>
     </div>
   )
 }));
@@ -82,15 +214,30 @@ vi.mock('@/lib/api-client', () => ({
 
 vi.mock('@/lib/plugin-manage/controller', () => ({
   loadPluginData,
-  createEmptyPluginDraft: () => ({ name: '', jarFileName: '', enableStatus: true }),
+  createEmptyPluginDraft: () => ({ name: '', jarFileName: '', jarFile: null, enableStatus: true }),
   savePlugin: vi.fn(),
+  loadPluginParamDraft: vi.fn(),
+  updatePluginParamDraft: vi.fn((draft, field, value) => ({
+    ...draft,
+    params: {
+      ...draft.params,
+      [field]: {
+        ...draft.params[field],
+        paramValue: value
+      }
+    }
+  })),
+  savePluginParams: vi.fn(),
   togglePluginStatus: vi.fn(),
-  deletePlugin: vi.fn()
+  validatePluginUploadDraft: vi.fn(() => ({ name: true, jarFile: true })),
+  deletePlugins: vi.fn(),
+  clampPluginPageIndexAfterDelete: vi.fn(() => 0)
 }));
 
 describe('setting plugins page', () => {
   beforeEach(() => {
     mockState.lastLoad = null;
+    mockState.renderErrorMessage = null;
     apiMessageGet.mockReset();
     apiMessagePost.mockReset();
     apiMessagePut.mockReset();
@@ -108,25 +255,264 @@ describe('setting plugins page', () => {
     expect(html).toContain('data-client-workbench="true"');
     expect(html).toContain('data-plugin-manage-surface="otlp-cold-plugin-console"');
     expect(html).toContain('data-plugin-manage-style-baseline="hertzbeat-cold-matte"');
+    expect(html).toContain('data-plugin-enable-feedback-contract="angular-edit-notify"');
+    expect(html).toContain('data-plugin-enable-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-plugin-enable-failure-contract="angular-edit-fail-notification"');
+    expect(html).toContain('data-plugin-enable-failure-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-plugin-enable-optimistic-contract="angular-toggle-mutates-row-before-put"');
+    expect(html).toContain('data-plugin-enable-optimistic-owner="route-state-contract"');
+    expect(html).toContain('data-plugin-table-loading-contract="angular-load-plugins-table"');
+    expect(html).toContain('data-plugin-table-loading-scope="load-search-refresh-pagination-mutation"');
+    expect(html).toContain('data-plugin-load-failure-contract="angular-console-only-shell"');
+    expect(html).toContain('data-plugin-load-failure-owner="plugin-route-controller"');
+    expect(html).toContain('data-plugin-load-failure="none"');
+    expect(html).toContain('data-plugin-toggle-loading-contract="angular-nz-table-loading"');
+    expect(html).toContain('data-plugin-toggle-loading-contract-owner="angular-nz-table-loading"');
+    expect(html).toContain('data-plugin-selection-reset-contract="angular-load-clears-selection"');
+    expect(html).toContain('data-plugin-selection-reset-owner="route-state-contract"');
+    expect(html).toContain('data-plugin-search-submit-contract="angular-enter-and-clear"');
+    expect(html).toContain('data-plugin-search-clear-contract="angular-cleared-load"');
+    expect(html).toContain('data-plugin-search-clear-owner="shared-search-row"');
+    expect(html).toContain('data-plugin-query-param-order-contract="angular-page-index-size-search"');
+    expect(html).toContain('data-plugin-query-param-order-owner="plugin-query-state"');
+    expect(html).toContain('data-plugin-delete-warning-contract="angular-no-select-warning"');
+    expect(html).toContain('data-plugin-delete-confirm-contract="angular-modal-confirm"');
+    expect(html).toContain('data-plugin-delete-confirm-contract-owner="hertzbeat-ui-confirm-dialog"');
+    expect(html).toContain('data-plugin-delete-feedback-contract="angular-delete-notify"');
+    expect(html).toContain('data-plugin-delete-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-plugin-delete-failure-contract="angular-delete-fail-notification"');
+    expect(html).toContain('data-plugin-delete-failure-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-plugin-delete-query-contract="angular-repeated-ids-query"');
+    expect(html).toContain('data-plugin-delete-query-owner="route-mutation-contract"');
+    expect(html).toContain('data-plugin-delete-page-clamp-contract="angular-update-page-index"');
+    expect(html).toContain('data-plugin-table-columns-contract="angular-five-column-edit-actions"');
+    expect(html).toContain('data-plugin-table-columns-owner="angular-nz-table"');
+    expect(html).toContain('data-plugin-table-stable-height-contract="viewport-fill-on-empty-or-short"');
+    expect(html).toContain('data-plugin-table-stable-height-owner="route-layout-contract"');
+    expect(html).toContain('data-plugin-toolbar-delete-menu-contract="angular-toolbar-ellipsis-delete"');
+    expect(html).toContain('data-plugin-toolbar-delete-menu-owner="hertzbeat-ui-table-row-action-button"');
+    expect(html).toContain('data-plugin-row-delete-menu-contract="angular-ellipsis-dropdown-delete"');
+    expect(html).toContain('data-plugin-row-delete-menu-owner="hertzbeat-ui-table-row-action-button"');
+    expect(html).toContain('data-plugin-upload-validation-contract="angular-required-before-submit"');
+    expect(html).toContain('data-plugin-upload-invalid-submit-contract="angular-mark-dirty-keep-open"');
+    expect(html).toContain('data-plugin-upload-invalid-submit-owner="route-validation-contract"');
+    expect(html).toContain('data-plugin-upload-loading-contract="angular-nz-ok-loading"');
+    expect(html).toContain('data-plugin-upload-payload-contract="angular-form-data"');
+    expect(html).toContain('data-plugin-upload-name-payload-contract="angular-raw-name-no-trim"');
+    expect(html).toContain('data-plugin-upload-name-payload-owner="plugin-manage-controller"');
+    expect(html).toContain('data-plugin-upload-save-lifecycle-contract="angular-close-success-keep-open-fail"');
+    expect(html).toContain('data-plugin-upload-save-lifecycle-owner="route-state-contract"');
+    expect(html).toContain('data-plugin-upload-success-reset-contract="angular-reset-form-after-success"');
+    expect(html).toContain('data-plugin-upload-success-reset-owner="route-state-contract"');
+    expect(html).toContain('data-plugin-upload-cancel-contract="angular-cancel-preserves-form"');
+    expect(html).toContain('data-plugin-upload-cancel-contract-owner="route-state-contract"');
+    expect(html).toContain('data-plugin-upload-cancel-pending-contract="angular-cancel-allowed-during-ok-loading"');
+    expect(html).toContain('data-plugin-upload-cancel-pending-owner="angular-nz-modal"');
+    expect(html).toContain('data-plugin-upload-mask-closable-contract="angular-mask-closable-false"');
+    expect(html).toContain('data-plugin-upload-width-contract="angular-width-30-percent"');
+    expect(html).toContain('data-plugin-upload-modal-owner="angular-nz-modal"');
+    expect(html).toContain('data-plugin-upload-field-layout-contract="angular-label-8-control-14"');
+    expect(html).toContain('data-plugin-upload-field-layout-owner="route-form-field-grid"');
+    expect(html).toContain('data-plugin-upload-status-control-contract="angular-nz-switch"');
+    expect(html).toContain('data-plugin-upload-status-control-owner="hertzbeat-ui-switch"');
+    expect(html).toContain('data-plugin-upload-file-list-contract="angular-before-upload-single-replace"');
+    expect(html).toContain('data-plugin-upload-file-list-owner="hertzbeat-ui-file-input"');
+    expect(html).toContain('data-plugin-upload-file-remove-contract="angular-nz-remove-clears-jar"');
+    expect(html).toContain('data-plugin-upload-file-remove-owner="hertzbeat-ui-file-input"');
+    expect(html).toContain('data-plugin-upload-feedback-contract="angular-new-notify"');
+    expect(html).toContain('data-plugin-upload-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-plugin-upload-failure-contract="angular-new-fail-notification"');
+    expect(html).toContain('data-plugin-upload-failure-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-plugin-param-edit-contract="angular-params-define-modal"');
+    expect(html).toContain('data-plugin-param-action-visibility-contract="angular-paramcount-zero-only"');
+    expect(html).toContain('data-plugin-param-action-visibility-owner="plugin-view-model"');
+    expect(html).toContain('data-plugin-param-mask-closable-contract="angular-mask-closable-false"');
+    expect(html).toContain('data-plugin-param-modal-owner="angular-nz-modal"');
+    expect(html).toContain('data-plugin-param-save-contract="angular-params-post"');
+    expect(html).toContain('data-plugin-param-payload-contract="angular-object-values-payload"');
+    expect(html).toContain('data-plugin-param-save-lifecycle-contract="angular-close-success-keep-open-fail"');
+    expect(html).toContain('data-plugin-param-save-lifecycle-owner="route-state-contract"');
+    expect(html).toContain('data-plugin-param-save-loading-contract="angular-no-ok-loading"');
+    expect(html).toContain('data-plugin-param-save-loading-owner="angular-modal-ok-contract"');
+    expect(html).toContain('data-plugin-param-feedback-contract="angular-edit-notify"');
+    expect(html).toContain('data-plugin-param-failure-contract="angular-edit-fail-notification"');
+    expect(html).toContain('data-plugin-param-failure-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-plugin-param-empty-contract="angular-empty-params-modal"');
+    expect(html).toContain('data-plugin-param-empty-owner="hertzbeat-ui-monitor-editor-field-grid"');
+    expect(html).toContain('data-plugin-param-pending-editable-contract="angular-controls-remain-enabled"');
+    expect(html).toContain('data-plugin-param-pending-editable-owner="angular-param-modal"');
+    expect(html).toContain('data-plugin-param-width-contract="angular-default-modal-width"');
+    expect(html).toContain('data-plugin-param-width-owner="angular-nz-modal"');
+    expect(html).toContain('data-plugin-param-radio-contract="angular-nz-radio-group"');
+    expect(html).toContain('data-plugin-param-radio-owner="hertzbeat-ui-radio-button-group"');
+    expect(html).toContain('data-plugin-param-textarea-contract="angular-textarea-rows-8"');
+    expect(html).toContain('data-plugin-param-textarea-owner="hertzbeat-ui-textarea"');
+    expect(html).toContain('data-plugin-param-advanced-fields-contract="angular-configurable-and-multi-func"');
+    expect(html).toContain('data-plugin-param-advanced-fields-owner="hertzbeat-ui-advanced-param-controls"');
+    expect(html).toContain('data-plugin-param-configurable-field-contract="angular-app-configurable-field"');
+    expect(html).toContain('data-plugin-param-configurable-field-owner="hertzbeat-ui-configurable-field"');
+    expect(html).toContain('data-plugin-param-key-value-contract="angular-app-configurable-field"');
+    expect(html).toContain('data-plugin-param-key-value-owner="hertzbeat-ui-key-value-editor"');
+    expect(html).toContain('data-plugin-param-labels-contract="angular-app-configurable-field"');
+    expect(html).toContain('data-plugin-param-labels-owner="hertzbeat-ui-key-value-editor"');
+    expect(html).toContain('data-plugin-param-label-selector-contract="angular-app-label-selector"');
+    expect(html).toContain('data-plugin-param-label-selector-owner="cold-label-selector"');
+    expect(html).toContain('data-plugin-param-metrics-field-contract="angular-app-configurable-field"');
+    expect(html).toContain('data-plugin-param-metrics-field-owner="hertzbeat-ui-configurable-field-editor"');
+    expect(html).toContain('data-plugin-param-array-contract="angular-app-multi-func-input"');
+    expect(html).toContain('data-plugin-param-array-owner="hertzbeat-ui-input"');
+    expect(html).toContain('data-plugin-param-field-layout-contract="angular-label-7-control-8"');
+    expect(html).toContain('data-plugin-param-field-layout-owner="hertzbeat-ui-monitor-editor-field-grid"');
+    expect(html).toContain('data-plugin-param-form-owner="hertzbeat-ui-monitor-editor-field-grid"');
+    expect(html).toContain('data-plugin-pagination="cold-dense-pagination"');
+    expect(html).toContain('data-plugin-pagination-owner="hertzbeat-ui-pagination-bar"');
+    expect(html).toContain('data-plugin-pagination-contract="angular-search-pagination"');
     expect(html).toContain('data-plugin-admin-layout="full-width-admin-list"');
     expect(html).not.toContain('data-plugin-summary-rail=');
     expect(html).not.toContain('插件摘要');
     expect(html).not.toContain('当前插件');
     expect(html).toContain('插件管理');
     expect(html).toContain('no-draft');
+    expect(html).toContain('upload-dialog-closed');
+    expect(html).toContain('no-param-draft');
+    expect(html).toContain('no-action-message');
+    expect(html).toContain('no-action-error');
+    expect(html).toContain('no-action-meta');
+    expect(html).toContain('success');
+    expect(html).toContain('enable');
+    expect(html).toContain('name-valid');
+    expect(html).toContain('upload-idle');
+    expect(html).toContain('param-idle');
+    expect(html).toContain('load-idle');
+    expect(html).toContain('toggle-idle');
+    expect(html).toContain('no-optimistic-enable-status');
+    expect(html).toContain('no-delete-target');
 
     await mockState.lastLoad?.();
 
     expect(apiMessageGet).toHaveBeenCalledWith('/plugin?pageIndex=0&pageSize=8');
   });
 
+  it('keeps the Angular plugin table shell visible when plugin loading fails', async () => {
+    mockState.renderErrorMessage = 'backend refused plugin load';
+    const { default: SettingPluginsPage } = await import('./page');
+    const html = renderToStaticMarkup(<SettingPluginsPage />);
+
+    expect(html).toContain('data-client-workbench="true"');
+    expect(html).toContain('data-plugin-manage-surface="otlp-cold-plugin-console"');
+    expect(html).toContain('data-plugin-load-failure-contract="angular-console-only-shell"');
+    expect(html).toContain('data-plugin-load-failure-owner="plugin-route-controller"');
+    expect(html).toContain('data-plugin-load-failure="angular-console-only-shell"');
+    expect(html).toContain('data-plugin-test-row-count="true">0');
+    expect(html).toContain('插件表格');
+    expect(html).not.toContain('backend refused plugin load');
+    expect(html).not.toContain('common.load-failed');
+    expect(html).not.toContain('data-observability-status');
+  });
+
   it('keeps route composition out of the old observability/workbench visual owner', () => {
-    const source = readFileSync(resolve(process.cwd(), 'app/setting/plugins/page.tsx'), 'utf8');
+    const source = readFileSync(resolve(process.cwd(), 'app/setting/plugins/setting-plugins-page.tsx'), 'utf8');
 
     expect(source).toContain('PluginManageSurface');
+    expect(source).toContain('const [isUploadDialogOpen, setIsUploadDialogOpen] = useState(false)');
+    expect(source).toContain('const [actionMessage, setActionMessage] = useState<string | null>(null)');
+    expect(source).toContain('const [actionError, setActionError] = useState<string | null>(null)');
+    expect(source).toContain("const [actionTone, setActionTone] = useState<'success' | 'warning' | 'critical'>('success')");
+    expect(source).toContain("const [paramDraft, setParamDraft] = useState<PluginParamDraft | null>(null)");
+    expect(source).toContain("const [actionKind, setActionKind] = useState<'enable' | 'delete' | 'upload' | 'params'>('enable')");
+    expect(source).toContain('const [optimisticEnableStatus, setOptimisticEnableStatus] = useState<Record<number, boolean>>({})');
+    expect(source).toContain('const [uploadValidation, setUploadValidation] = useState<{ name?: boolean; jarFile?: boolean }>({})');
+    expect(source).toContain('const [isUploadPending, setIsUploadPending] = useState(false)');
+    expect(source).toContain('const [isParamPending, setIsParamPending] = useState(false)');
+    expect(source).toContain('const [deleteTarget, setDeleteTarget] = useState<PluginDeleteTarget | null>(null)');
+    expect(source).toContain('const [isLoadPending, setIsLoadPending] = useState(false)');
+    expect(source).toContain('buildFallbackPluginData');
+    expect(source).toContain('renderError={(message, retry) => renderPluginSurface(buildFallbackPluginData(), message, retry)}');
+    expect(source).toContain('loadError={loadError}');
+    expect(source).toContain('const [isDeletePending, setIsDeletePending] = useState(false)');
+    expect(source).toContain('const [isTogglePending, setIsTogglePending] = useState(false)');
+    expect(source).toContain("setActionMessage(t('common.notify.edit-success'))");
+    expect(source).toContain("setActionError(t('common.notify.edit-fail'))");
+    expect(source).toContain('setActionMeta(error instanceof Error ? error.message : null)');
+    expect(source).toContain("setActionError(t('common.notify.no-select-delete'))");
+    expect(source).toContain("setActionMessage(t('common.notify.delete-success'))");
+    expect(source).toContain("setActionError(t('common.notify.delete-fail'))");
+    expect(source).toContain('validatePluginUploadDraft(draftPlugin)');
+    expect(source).toContain('if (!validation.name || !validation.jarFile) {');
+    expect(source).toContain('setUploadValidation({');
+    expect(source).toContain('isUploadDialogOpen={isUploadDialogOpen}');
+    expect(source).toContain('setIsUploadDialogOpen(true)');
+    expect(source).toContain('setIsUploadDialogOpen(false)');
+    expect(source).toContain('onCloseDialog={() => {');
+    expect(source).toContain('setIsUploadDialogOpen(false);');
+    expect(source).not.toContain('if (!isUploadPending) {');
+    expect(source).toContain('if (!draftPlugin) {');
+    expect(source).toContain("setActionKind('upload')");
+    expect(source).toContain("setActionMessage(t('common.notify.new-success'))");
+    expect(source).toContain("setActionError(t('common.notify.new-fail'))");
+    expect(source).toContain('setUploadValidation({});');
+    expect(source).toContain('setDraftPlugin(null);');
+    expect(source).toContain('void deletePlugins(apiMessageDelete, targetIds)');
+    expect(source).toContain('void savePlugin(apiMessagePost, draftPlugin)');
+    expect(source).toContain('void loadPluginParamDraft(apiMessageGet, plugin, locale)');
+    expect(source).toContain("setActionKind('params')");
+    expect(source).toContain('updatePluginParamDraft(previous, field, value)');
+    expect(source).toContain('void savePluginParams(apiMessagePost, paramDraft.params)');
+    expect(source).toContain("setActionMessage(t('common.notify.edit-success'))");
+    expect(source).toContain('setParamDraft(null);');
+    expect(source).toContain("setActionError(t('common.notify.edit-fail'))");
+    expect(source).toContain('onCloseParamDialog={() => {');
+    expect(source).toContain('setParamDraft(null);');
+    expect(source).not.toContain('if (!isParamPending) setParamDraft(null);');
+    expect(source).toContain('isParamPending={isParamPending}');
+    expect(source).toContain('clampPluginPageIndexAfterDelete({');
+    expect(source).toContain('onPageIndexChange={pageIndex => {');
+    expect(source).toContain('onPageSizeChange={pageSize => {');
+    expect(source.match(/setSelectedIds\(\[\]\)/g)?.length).toBeGreaterThanOrEqual(6);
+    expect(source).toContain('onSearch={() => {');
+    expect(source).toContain('onSearchClear={() => {');
+    expect(source).toContain("setQuery({ search: '', pageIndex: 0, pageSize: query.pageSize });");
+    expect(source).toContain('onRefresh={() => {');
+    expect(source).toContain('setQuery(current => ({ ...current, pageIndex }))');
+    expect(source).toContain('setQuery(current => ({ ...current, pageIndex: 0, pageSize }))');
+    expect(source).toContain('actionMessage={actionMessage}');
+    expect(source).toContain('actionError={actionError}');
+    expect(source).toContain('actionMeta={actionMeta}');
+    expect(source).toContain('actionTone={actionTone}');
+    expect(source).toContain('actionKind={actionKind}');
+    expect(source).toContain('uploadValidation={uploadValidation}');
+    expect(source).toContain('isUploadPending={isUploadPending}');
+    expect(source).toContain('paramDraft={paramDraft}');
+    expect(source).toContain('isParamPending={isParamPending}');
+    expect(source).toContain('deleteTarget={deleteTarget}');
+    expect(source).toContain('isLoadPending={isLoadPending}');
+    expect(source).toContain('isDeletePending={isDeletePending}');
+    expect(source).toContain('isTogglePending={isTogglePending}');
+    expect(source).toContain('optimisticEnableStatus={optimisticEnableStatus}');
+    expect(source).toContain('setIsLoadPending(true)');
+    expect(source).toContain('setOptimisticEnableStatus({});');
+    expect(source).toContain('setIsLoadPending(false)');
+    expect(source).toContain('setIsTogglePending(true)');
+    expect(source).toContain('setOptimisticEnableStatus(current => ({');
+    expect(source).toContain('[plugin.id]: !Boolean(plugin.enableStatus)');
+    expect(source).toContain('setIsTogglePending(false)');
     expect(source).not.toContain('WorkbenchPage');
     expect(source).not.toContain('SurfaceSection');
     expect(source).not.toContain('StatusState');
     expect(source).not.toContain('buildPluginFacts');
   });
+
+  it('keeps plugin management remounts on a short settled cache window while reloads invalidate it', () => {
+    const source = readFileSync(resolve(process.cwd(), 'app/setting/plugins/setting-plugins-page.tsx'), 'utf8');
+
+    expect(source).toContain('SETTING_PLUGINS_SETTLED_CACHE_TTL_MS = 10_000');
+    expect(source).toContain("['setting-plugins', pluginListUrl, reloadVersion].join(':')");
+    expect(source).toContain('[pluginListUrl, reloadVersion]');
+    expect(source).toContain('onRefresh={() => {');
+    expect(source).toContain('setActionMessage(null);');
+    expect(source).toContain('setActionError(null);');
+    expect(source).toContain('setReloadVersion(version => version + 1)');
+    expect(source).toContain('cacheKey={pluginManageCacheKey}');
+    expect(source).toContain('cacheSettledTtlMs={SETTING_PLUGINS_SETTLED_CACHE_TTL_MS}');
+  });
 });
diff --git a/web-next/app/setting/plugins/page.tsx b/web-next/app/setting/plugins/page.tsx
index 4ea0b59a07..4cce6d5647 100644
--- a/web-next/app/setting/plugins/page.tsx
+++ b/web-next/app/setting/plugins/page.tsx
@@ -1,69 +1,7 @@
-'use client';
+import React from 'react';
 
-import { useCallback, useState } from 'react';
-import { ClientWorkbench } from '@/components/workbench/client-workbench';
-import { PluginManageSurface } from '@/components/pages/plugin-manage-surface';
-import { useI18n } from '@/components/providers/i18n-provider';
-import { apiMessageDelete, apiMessageGet, apiMessagePost, apiMessagePut } from '@/lib/api-client';
-import { createEmptyPluginDraft, deletePlugin, loadPluginData, savePlugin, togglePluginStatus, type PluginUploadDraft } from '@/lib/plugin-manage/controller';
-import type { PluginQueryState } from '@/lib/plugin-manage/query-state';
-import type { Plugin } from '@/lib/types';
+import SettingPluginsPage from './setting-plugins-page';
 
-export default function SettingPluginsPage() {
-  const { t } = useI18n();
-  const [search, setSearch] = useState('');
-  const [query, setQuery] = useState<PluginQueryState>({ search: '' });
-  const [reloadVersion, setReloadVersion] = useState(0);
-  const [selectedIds, setSelectedIds] = useState<number[]>([]);
-  const [draftPlugin, setDraftPlugin] = useState<PluginUploadDraft | null>(null);
-
-  const load = useCallback(async () => {
-    return loadPluginData(apiMessageGet, query);
-  }, [query, reloadVersion]);
-
-  return (
-    <ClientWorkbench load={load} loadingCopy={t('setting.plugins.loading')}>
-      {data => (
-        <PluginManageSurface
-          t={t}
-          data={data}
-          search={search}
-          selectedIds={selectedIds}
-          draftPlugin={draftPlugin}
-          onSearchChange={setSearch}
-          onSearch={() => setQuery({ search })}
-          onRefresh={() => setReloadVersion(version => version + 1)}
-          onNew={() => setDraftPlugin(createEmptyPluginDraft())}
-          onDeleteSelected={() => {
-            selectedIds.forEach(id => {
-              void deletePlugin(apiMessageDelete, id);
-            });
-            setSelectedIds([]);
-            setReloadVersion(version => version + 1);
-          }}
-          onSelectedIdsChange={setSelectedIds}
-          onEditParams={(_plugin: Plugin) => {}}
-          onToggleEnabled={plugin => {
-            void togglePluginStatus(apiMessagePut, plugin).then(() => {
-              setReloadVersion(version => version + 1);
-            });
-          }}
-          onDeleteOne={plugin => {
-            void deletePlugin(apiMessageDelete, plugin.id).then(() => {
-              setReloadVersion(version => version + 1);
-            });
-          }}
-          onDraftChange={patch => setDraftPlugin(previous => (previous ? { ...previous, ...patch } : previous))}
-          onCloseDialog={() => setDraftPlugin(null)}
-          onSaveDialog={() => {
-            if (!draftPlugin) return;
-            void savePlugin(apiMessagePost, draftPlugin).then(() => {
-              setDraftPlugin(null);
-              setReloadVersion(version => version + 1);
-            });
-          }}
-        />
-      )}
-    </ClientWorkbench>
-  );
+export default function SettingPluginsRoutePage() {
+  return <SettingPluginsPage />;
 }
diff --git a/web-next/app/setting/plugins/setting-plugins-page.tsx b/web-next/app/setting/plugins/setting-plugins-page.tsx
new file mode 100644
index 0000000000..eb16afa8b2
--- /dev/null
+++ b/web-next/app/setting/plugins/setting-plugins-page.tsx
@@ -0,0 +1,325 @@
+'use client';
+
+import { useCallback, useMemo, useState } from 'react';
+import { ClientWorkbench } from '@/components/workbench/client-workbench';
+import { PluginManageSurface, type PluginDeleteTarget } from '@/components/pages/plugin-manage-surface';
+import { useI18n } from '@/components/providers/i18n-provider';
+import { apiMessageDelete, apiMessageGet, apiMessagePost, apiMessagePut } from '@/lib/api-client';
+import { clampPluginPageIndexAfterDelete, createEmptyPluginDraft, deletePlugins, loadPluginData, loadPluginParamDraft, savePlugin, savePluginParams, togglePluginStatus, updatePluginParamDraft, validatePluginUploadDraft, type PluginManagePageData, type PluginParamDraft, type PluginUploadDraft } from '@/lib/plugin-manage/controller';
+import { buildPluginUrl, type PluginQueryState } from '@/lib/plugin-manage/query-state';
+import type { Plugin } from '@/lib/types';
+
+const SETTING_PLUGINS_SETTLED_CACHE_TTL_MS = 10_000;
+
+export default function SettingPluginsPage() {
+  const { t, locale } = useI18n();
+  const [search, setSearch] = useState('');
+  const [query, setQuery] = useState<PluginQueryState>({ search: '' });
+  const [reloadVersion, setReloadVersion] = useState(0);
+  const [selectedIds, setSelectedIds] = useState<number[]>([]);
+  const [draftPlugin, setDraftPlugin] = useState<PluginUploadDraft | null>(null);
+  const [isUploadDialogOpen, setIsUploadDialogOpen] = useState(false);
+  const [paramDraft, setParamDraft] = useState<PluginParamDraft | null>(null);
+  const [actionMessage, setActionMessage] = useState<string | null>(null);
+  const [actionError, setActionError] = useState<string | null>(null);
+  const [actionMeta, setActionMeta] = useState<string | null>(null);
+  const [actionTone, setActionTone] = useState<'success' | 'warning' | 'critical'>('success');
+  const [actionKind, setActionKind] = useState<'enable' | 'delete' | 'upload' | 'params'>('enable');
+  const [uploadValidation, setUploadValidation] = useState<{ name?: boolean; jarFile?: boolean }>({});
+  const [isUploadPending, setIsUploadPending] = useState(false);
+  const [isParamPending, setIsParamPending] = useState(false);
+  const [deleteTarget, setDeleteTarget] = useState<PluginDeleteTarget | null>(null);
+  const [isLoadPending, setIsLoadPending] = useState(false);
+  const [isDeletePending, setIsDeletePending] = useState(false);
+  const [isTogglePending, setIsTogglePending] = useState(false);
+  const [optimisticEnableStatus, setOptimisticEnableStatus] = useState<Record<number, boolean>>({});
+  const pluginListUrl = useMemo(() => buildPluginUrl(query), [query]);
+  const pluginManageCacheKey = useMemo(
+    () => ['setting-plugins', pluginListUrl, reloadVersion].join(':'),
+    [pluginListUrl, reloadVersion]
+  );
+
+  const load = useCallback(async () => {
+    setIsLoadPending(true);
+    try {
+      return await loadPluginData(apiMessageGet, query);
+    } finally {
+      setOptimisticEnableStatus({});
+      setIsLoadPending(false);
+    }
+  }, [query]);
+
+  const clearActionFeedback = useCallback(() => {
+    setActionMessage(null);
+    setActionError(null);
+    setActionMeta(null);
+    setActionTone('success');
+  }, []);
+
+  const buildFallbackPluginData = useCallback((): PluginManagePageData => ({
+    list: {
+      content: [],
+      totalElements: 0,
+      pageIndex: query.pageIndex ?? 0,
+      pageSize: query.pageSize ?? 8
+    }
+  }), [query.pageIndex, query.pageSize]);
+
+  function renderPluginSurface(data: PluginManagePageData, loadError: string | null = null, retryLoad?: () => void) {
+    return (
+      <PluginManageSurface
+        t={t}
+        data={data}
+        search={search}
+        selectedIds={selectedIds}
+        draftPlugin={draftPlugin}
+        isUploadDialogOpen={isUploadDialogOpen}
+        paramDraft={paramDraft}
+        actionMessage={actionMessage}
+        actionError={actionError}
+        actionMeta={actionMeta}
+        actionTone={actionTone}
+        actionKind={actionKind}
+        loadError={loadError}
+        uploadValidation={uploadValidation}
+        isUploadPending={isUploadPending}
+        isParamPending={isParamPending}
+        deleteTarget={deleteTarget}
+        isLoadPending={isLoadPending}
+        isDeletePending={isDeletePending}
+        isTogglePending={isTogglePending}
+        optimisticEnableStatus={optimisticEnableStatus}
+        onSearchChange={setSearch}
+        onSearch={() => {
+          clearActionFeedback();
+          setSelectedIds([]);
+          setQuery({ search, pageIndex: 0, pageSize: query.pageSize });
+        }}
+        onSearchClear={() => {
+          clearActionFeedback();
+          setSelectedIds([]);
+          setSearch('');
+          setQuery({ search: '', pageIndex: 0, pageSize: query.pageSize });
+        }}
+        onRefresh={() => {
+          clearActionFeedback();
+          setSelectedIds([]);
+          if (retryLoad) {
+            retryLoad();
+            return;
+          }
+          setReloadVersion(version => version + 1);
+        }}
+        onNew={() => {
+          clearActionFeedback();
+          setDeleteTarget(null);
+          setParamDraft(null);
+          if (!draftPlugin) {
+            setUploadValidation({});
+            setDraftPlugin(createEmptyPluginDraft());
+          }
+          setIsUploadDialogOpen(true);
+        }}
+        onDeleteSelected={() => {
+          clearActionFeedback();
+          if (selectedIds.length === 0) {
+            setActionKind('delete');
+            setActionTone('warning');
+            setActionError(t('common.notify.no-select-delete'));
+            return;
+          }
+          setActionKind('delete');
+          setDeleteTarget({
+            ids: selectedIds,
+            label: t('setting.plugins.selected-count', { count: selectedIds.length }),
+            mode: 'batch'
+          });
+        }}
+        onDeleteCancel={() => {
+          if (!isDeletePending) setDeleteTarget(null);
+        }}
+        onDeleteConfirm={() => {
+          if (!deleteTarget) return;
+          clearActionFeedback();
+          setActionKind('delete');
+          setIsDeletePending(true);
+          const targetIds = deleteTarget.ids;
+          void deletePlugins(apiMessageDelete, targetIds)
+            .then(() => {
+              const pageSize = data.list.pageSize ?? query.pageSize ?? 8;
+              const currentPageIndex = data.list.pageIndex ?? query.pageIndex ?? 0;
+              const nextPageIndex = clampPluginPageIndexAfterDelete({
+                pageIndex: currentPageIndex + 1,
+                pageSize,
+                totalElements: data.list.totalElements ?? 0,
+                deleteCount: targetIds.length
+              });
+              setActionTone('success');
+              setActionMessage(t('common.notify.delete-success'));
+              setSelectedIds([]);
+              setDeleteTarget(null);
+              setQuery(previous => ({
+                ...previous,
+                pageIndex: nextPageIndex,
+                pageSize
+              }));
+              setReloadVersion(version => version + 1);
+            })
+            .catch(error => {
+              setActionTone('critical');
+              setActionError(t('common.notify.delete-fail'));
+              setActionMeta(error instanceof Error ? error.message : null);
+            })
+            .finally(() => {
+              setIsDeletePending(false);
+            });
+        }}
+        onSelectedIdsChange={setSelectedIds}
+        onPageIndexChange={pageIndex => {
+          clearActionFeedback();
+          setSelectedIds([]);
+          setQuery(current => ({ ...current, pageIndex }));
+        }}
+        onPageSizeChange={pageSize => {
+          clearActionFeedback();
+          setSelectedIds([]);
+          setQuery(current => ({ ...current, pageIndex: 0, pageSize }));
+        }}
+        onEditParams={(plugin: Plugin) => {
+          clearActionFeedback();
+          setDraftPlugin(null);
+          setDeleteTarget(null);
+          setActionKind('params');
+          setIsParamPending(true);
+          void loadPluginParamDraft(apiMessageGet, plugin, locale)
+            .then(draft => {
+              setParamDraft(draft);
+            })
+            .catch(error => {
+              setActionTone('critical');
+              setActionError(t('common.notify.edit-fail'));
+              setActionMeta(error instanceof Error ? error.message : null);
+            })
+            .finally(() => {
+              setIsParamPending(false);
+            });
+        }}
+        onParamChange={(field, value) => {
+          setParamDraft(previous => (previous ? updatePluginParamDraft(previous, field, value) : previous));
+        }}
+        onCloseParamDialog={() => {
+          setParamDraft(null);
+        }}
+        onSaveParamDialog={() => {
+          if (!paramDraft) return;
+          clearActionFeedback();
+          setActionKind('params');
+          setIsParamPending(true);
+          void savePluginParams(apiMessagePost, paramDraft.params)
+            .then(() => {
+              setActionTone('success');
+              setActionMessage(t('common.notify.edit-success'));
+              setParamDraft(null);
+            })
+            .catch(error => {
+              setActionTone('critical');
+              setActionError(t('common.notify.edit-fail'));
+              setActionMeta(error instanceof Error ? error.message : null);
+            })
+            .finally(() => {
+              setIsParamPending(false);
+            });
+        }}
+        onToggleEnabled={plugin => {
+          clearActionFeedback();
+          setActionKind('enable');
+          setOptimisticEnableStatus(current => ({
+            ...current,
+            [plugin.id]: !Boolean(plugin.enableStatus)
+          }));
+          setIsTogglePending(true);
+          void togglePluginStatus(apiMessagePut, plugin)
+            .then(() => {
+              setActionTone('success');
+              setActionMessage(t('common.notify.edit-success'));
+              setSelectedIds([]);
+              setReloadVersion(version => version + 1);
+            })
+            .catch(error => {
+              setActionTone('critical');
+              setActionError(t('common.notify.edit-fail'));
+              setActionMeta(error instanceof Error ? error.message : null);
+            })
+            .finally(() => {
+              setIsTogglePending(false);
+            });
+        }}
+        onDeleteOne={plugin => {
+          clearActionFeedback();
+          setActionKind('delete');
+          setDeleteTarget({
+            ids: [plugin.id],
+            label: plugin.name,
+            mode: 'single'
+          });
+        }}
+        onDraftChange={patch => {
+          setUploadValidation(previous => ({
+            ...previous,
+            ...(Object.prototype.hasOwnProperty.call(patch, 'name') ? { name: false } : {}),
+            ...(Object.prototype.hasOwnProperty.call(patch, 'jarFile') ? { jarFile: false } : {})
+          }));
+          setDraftPlugin(previous => (previous ? { ...previous, ...patch } : previous));
+        }}
+        onCloseDialog={() => {
+          setIsUploadDialogOpen(false);
+        }}
+        onSaveDialog={() => {
+          if (!draftPlugin) return;
+          clearActionFeedback();
+          setActionKind('upload');
+          const validation = validatePluginUploadDraft(draftPlugin);
+          if (!validation.name || !validation.jarFile) {
+            setUploadValidation({
+              name: !validation.name,
+              jarFile: !validation.jarFile
+            });
+            return;
+          }
+          setUploadValidation({});
+          setIsUploadPending(true);
+          void savePlugin(apiMessagePost, draftPlugin)
+            .then(() => {
+              setActionTone('success');
+              setActionMessage(t('common.notify.new-success'));
+              setUploadValidation({});
+              setIsUploadDialogOpen(false);
+              setDraftPlugin(null);
+              setSelectedIds([]);
+              setReloadVersion(version => version + 1);
+            })
+            .catch(error => {
+              setActionTone('critical');
+              setActionError(t('common.notify.new-fail'));
+              setActionMeta(error instanceof Error ? error.message : null);
+            })
+            .finally(() => {
+              setIsUploadPending(false);
+            });
+        }}
+      />
+    );
+  }
+
+  return (
+    <ClientWorkbench
+      load={load}
+      loadingCopy={t('setting.plugins.loading')}
+      cacheKey={pluginManageCacheKey}
+      cacheSettledTtlMs={SETTING_PLUGINS_SETTLED_CACHE_TTL_MS}
+      renderError={(message, retry) => renderPluginSurface(buildFallbackPluginData(), message, retry)}
+    >
+      {data => renderPluginSurface(data)}
+    </ClientWorkbench>
+  );
+}
diff --git a/web-next/components/pages/collector-manage-surface.test.tsx b/web-next/components/pages/collector-manage-surface.test.tsx
index fe189bfbb2..1fea926bc8 100644
--- a/web-next/components/pages/collector-manage-surface.test.tsx
+++ b/web-next/components/pages/collector-manage-surface.test.tsx
@@ -4,10 +4,10 @@ import { resolve } from 'node:path';
 import { renderToStaticMarkup } from 'react-dom/server';
 import { describe, expect, it } from 'vitest';
 import { createTranslatorMock } from '../../test/i18n-test-helper';
+import { CollectorManageSurface } from './collector-manage-surface';
 
 describe('collector manage surface', () => {
-  it('renders the cold-matte collector toolbar and dense table shell', async () => {
-    const { CollectorManageSurface } = await import('./collector-manage-surface');
+  it('renders the cold-matte collector toolbar and dense table shell', () => {
     const t = createTranslatorMock({
       locale: 'zh-CN',
       overrides: {
@@ -68,8 +68,8 @@ describe('collector manage surface', () => {
                 dispatchMonitorNum: 3
               }
             ],
-            totalElements: 2,
-            pageIndex: 0,
+            totalElements: 18,
+            pageIndex: 1,
             pageSize: 8
           }
         }}
@@ -77,6 +77,7 @@ describe('collector manage surface', () => {
         formatTime={() => '2026-04-10 18:00:00'}
         onSearchChange={() => {}}
         onSearch={() => {}}
+        onSearchClear={() => {}}
         onRefresh={() => {}}
         onDeploy={() => {}}
         onGoOnline={() => {}}
@@ -96,17 +97,99 @@ describe('collector manage surface', () => {
     expect(html).toContain('data-collector-health-tone="warning"');
     expect(html).toContain('data-collector-health-freshness="last-seen"');
     expect(html).toContain('data-collector-admin-layout="full-width-admin-list"');
+    expect(html).toContain('data-collector-delete-warning-contract="angular-no-select-warning"');
+    expect(html).toContain('data-collector-delete-confirm-contract="angular-modal-confirm"');
+    expect(html).toContain('data-collector-delete-confirm-contract-owner="hertzbeat-ui-confirm-dialog"');
+    expect(html).toContain('data-collector-delete-feedback-contract="angular-delete-notify"');
+    expect(html).toContain('data-collector-delete-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-collector-delete-api-contract="angular-repeated-collectors-query"');
+    expect(html).toContain('data-collector-delete-page-clamp-contract="angular-update-page-index"');
+    expect(html).toContain('data-collector-delete-page-clamp-owner="route-state-contract"');
+    expect(html).toContain('data-collector-operate-warning-contract="angular-no-select-online-offline"');
+    expect(html).toContain('data-collector-operate-confirm-contract="angular-modal-confirm"');
+    expect(html).toContain('data-collector-operate-confirm-contract-owner="hertzbeat-ui-confirm-dialog"');
+    expect(html).toContain('data-collector-operate-feedback-contract="angular-operate-notify"');
+    expect(html).toContain('data-collector-operate-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-collector-operate-api-contract="angular-put-repeated-collectors-query"');
+    expect(html).toContain('data-collector-mutation-loading-contract="angular-nz-table-loading"');
+    expect(html).toContain('data-collector-load-failure-contract="angular-console-only-shell"');
+    expect(html).toContain('data-collector-load-failure-owner="collector-route-controller"');
+    expect(html).toContain('data-collector-load-failure="none"');
+    expect(html).toContain('data-collector-deploy-modal-contract="angular-generate-identity-modal"');
+    expect(html).toContain('data-collector-deploy-api-contract="angular-post-generate-identity"');
+    expect(html).toContain('data-collector-deploy-failure-contract="angular-apply-fail-notification"');
+    expect(html).toContain('data-collector-deploy-loading-contract="angular-nz-ok-loading"');
+    expect(html).toContain('data-collector-deploy-validation-raw-contract="angular-required-before-trim"');
+    expect(html).toContain('data-collector-deploy-validation-raw-owner="collector-route-controller"');
+    expect(html).toContain('data-collector-deploy-mask-contract="angular-mask-closable-false"');
+    expect(html).toContain('data-collector-deploy-width-contract="angular-width-45-percent"');
+    expect(html).toContain('data-collector-deploy-field-layout-contract="angular-label-7-control-12"');
+    expect(html).toContain('data-collector-deploy-field-layout-owner="route-form-field-grid"');
+    expect(html).toContain('data-collector-deploy-command-contract="angular-docker-package-shells"');
+    expect(html).toContain('data-collector-deploy-command-owner="hertzbeat-ui-code-editor"');
+    expect(html).toContain('data-collector-deploy-package-link-contract="angular-github-releases-link"');
+    expect(html).toContain('data-collector-deploy-copy-contract="angular-copy-identity"');
+    expect(html).toContain('data-collector-deploy-copy-feedback-contract="angular-copy-success-duration-800"');
+    expect(html).toContain('data-collector-deploy-result-close-contract="angular-danger-close-after-identity"');
+    expect(html).toContain('data-collector-deploy-result-close-owner="hertzbeat-ui-button"');
+    expect(html).toContain('data-collector-deploy-close-reset-contract="angular-close-clears-name"');
+    expect(html).toContain('data-collector-deploy-close-reset-owner="collector-route-controller"');
+    expect(html).toContain('data-collector-deploy-close-pending-result-contract="angular-close-ignores-late-generate"');
+    expect(html).toContain('data-collector-deploy-close-pending-result-owner="collector-route-controller"');
+    expect(html).toContain('data-collector-pagination="cold-dense-pagination"');
+    expect(html).toContain('data-collector-pagination-owner="hertzbeat-ui-pagination-bar"');
+    expect(html).toContain('data-collector-pagination-contract="angular-search-pagination"');
+    expect(html).toContain('data-hz-ui="pagination-bar"');
+    expect(html).toContain('data-hz-pagination-page-size="select-menu"');
+    expect(html).toContain('data-hz-pagination-page-jump="number-input"');
+    expect(html).toContain('data-collector-pagination-page-size-owner="hertzbeat-ui-select"');
+    expect(html).toContain('data-collector-pagination-page-jump-owner="hertzbeat-ui-input"');
+    expect(html).toContain('data-collector-deploy-open="angular-deploy-modal"');
+    expect(html).toContain('data-collector-online-selected="angular-batch-online-entry"');
+    expect(html).toContain('data-collector-offline-selected="angular-batch-offline-entry"');
+    expect(html).toContain('data-collector-delete-selected="angular-batch-delete-entry"');
     expect(html).toContain('data-collector-toolbar="cold-table-toolbar"');
     expect(html).toContain('data-collector-search-owner="shared-search-row"');
+    expect(html).toContain('data-collector-search-submit-contract="angular-enter-and-clear"');
+    expect(html).toContain('data-collector-search-clear-contract="angular-cleared-load"');
+    expect(html).toContain('data-collector-search-clear-owner="shared-search-row"');
     expect(html).toContain('data-cold-search-row-owner="cold-search-row"');
     expect(html).toContain('data-cold-search-input="fixed-width-direct"');
     expect(html).toContain('data-cold-search-control="direct-input"');
     expect(html).toContain('data-cold-search-chrome="no-extra-input-shell"');
     expect(html).toContain('data-cold-search-action="submit"');
+    expect(html).toContain('data-cold-search-action="clear"');
     expect(html).toContain('data-collector-table-shell="cold-dense-table"');
+    expect(html).toContain('data-collector-table-loading="false"');
+    expect(html).toContain('data-collector-table-loading-contract="angular-load-collectors-table"');
+    expect(html).toContain('data-collector-table-loading-owner="angular-nz-table-loading"');
+    expect(html).toContain('data-collector-table-loading-scope="load-search-refresh-pagination-mutation"');
+    expect(html).toContain('data-collector-table-loading-source="idle"');
+    expect(html).toContain('aria-busy="false"');
     expect(html).toContain('data-collector-manage-table="cold-collector-table"');
+    expect(html).toContain('data-collector-select-all="cold-checkbox"');
+    expect(html).toContain('data-collector-row-select="edge-a"');
+    expect(html).toContain('data-collector-row-select="edge-b"');
     expect(html).toContain('data-collector-row-health="collector-status"');
     expect(html).toContain('data-collector-row-actions="cold-icon-actions"');
+    expect(html).toContain('data-collector-row-delete-menu="edge-a"');
+    expect(html).toContain('data-collector-row-delete-menu-contract="angular-ellipsis-dropdown-delete"');
+    expect(html).toContain('data-collector-row-delete-menu-layer="overlay-visible-above-panel"');
+    expect(html).toContain('data-collector-row-delete-menu-clearance="floating-overlay-no-panel-crop"');
+    expect(html).toContain('data-collector-row-delete-menu-open="false"');
+    expect(html).toContain('aria-expanded="false"');
+    expect(html).toContain('data-collector-row-delete-menu-trigger="edge-a"');
+    expect(html).toContain('data-collector-row-delete-menu-panel="edge-a"');
+    expect(html).toContain('data-collector-row-delete-menu-layer-panel="overlay-visible-above-panel"');
+    expect(html).toContain('data-collector-row-delete-menu-clearance-panel="floating-overlay-no-panel-crop"');
+    expect(html).toContain('data-collector-row-delete-menu-owner="hertzbeat-ui-table-row-action-button"');
+    expect(html).toContain('data-collector-row-delete-menu-panel-open="false"');
+    expect(html).toContain('data-collector-delete-one="edge-a"');
+    expect(html).toContain('data-collector-delete-one-owner="hertzbeat-ui-table-row-action-button"');
+    expect(html).toContain('data-collector-offline-one="edge-a"');
+    expect(html).toContain('data-collector-online-one="edge-b"');
+    expect(html).toContain('data-collector-status-tone="success"');
+    expect(html).toContain('data-collector-status-tone="danger"');
     expect(html).not.toContain('data-cold-search-input-shell');
     expect(html).not.toContain('data-collector-summary-rail=');
     expect(html).toContain('刷新');
@@ -117,6 +200,9 @@ describe('collector manage surface', () => {
     expect(html).toContain('采集器 1 / 2 在线');
     expect(html).toContain('任务 11 · 离线 1');
     expect(html).toContain('最近上报 2026-04-10 18:00:00');
+    expect(html).toContain('第 2 / 3 页 · 9-10 / 18');
+    expect(html).toContain('每页条数');
+    expect(html).toContain('页码');
     expect(html).toContain('搜索');
     expect(html).toContain('采集集群');
     expect(html).toContain('采集器名称');
@@ -146,10 +232,148 @@ describe('collector manage surface', () => {
     expect(source).toContain('data-collector-manage-surface="otlp-cold-collector-console"');
     expect(source).toContain('data-collector-manage-style-baseline={coldCollectorVisual.canvasName}');
     expect(source).toContain('data-collector-admin-layout="full-width-admin-list"');
+    expect(source).toContain('data-collector-delete-warning-contract="angular-no-select-warning"');
+    expect(source).toContain('data-collector-delete-confirm-contract="angular-modal-confirm"');
+    expect(source).toContain('data-collector-delete-confirm-contract-owner="hertzbeat-ui-confirm-dialog"');
+    expect(source).toContain('data-collector-delete-feedback-contract="angular-delete-notify"');
+    expect(source).toContain('data-collector-delete-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(source).toContain('data-collector-delete-api-contract="angular-repeated-collectors-query"');
+    expect(source).toContain('data-collector-delete-page-clamp-contract="angular-update-page-index"');
+    expect(source).toContain('data-collector-delete-page-clamp-owner="route-state-contract"');
+    expect(source).toContain('data-collector-selection-reset-contract="angular-load-clears-selection"');
+    expect(source).toContain('data-collector-selection-reset-owner="route-state-contract"');
+    expect(source).toContain('data-collector-search-submit-contract="angular-enter-and-clear"');
+    expect(source).toContain('data-collector-search-clear-contract="angular-cleared-load"');
+    expect(source).toContain('data-collector-search-clear-owner="shared-search-row"');
+    expect(source).toContain('data-collector-operate-warning-contract="angular-no-select-online-offline"');
+    expect(source).toContain('data-collector-operate-confirm-contract="angular-modal-confirm"');
+    expect(source).toContain('data-collector-operate-feedback-contract="angular-operate-notify"');
+    expect(source).toContain('data-collector-operate-api-contract="angular-put-repeated-collectors-query"');
+    expect(source).toContain('data-collector-mutation-loading-contract="angular-nz-table-loading"');
+    expect(source).toContain('data-collector-load-failure-contract="angular-console-only-shell"');
+    expect(source).toContain('data-collector-load-failure-owner="collector-route-controller"');
+    expect(source).toContain("data-collector-load-failure={loadError ? 'angular-console-only-shell' : 'none'}");
+    expect(source).toContain('meta={actionMeta ?? undefined}');
+    expect(source).toContain('data-collector-delete-feedback-title');
+    expect(source).toContain('data-collector-delete-feedback-detail');
+    expect(source).toContain('data-collector-operate-feedback-title');
+    expect(source).toContain('data-collector-operate-feedback-detail');
+    expect(source).toContain('const mutationPending = Boolean(isDeletePending)');
+    expect(source).toContain('const tableLoading = Boolean(isLoadPending || mutationPending)');
+    expect(source).toContain('data-collector-deploy-modal-contract="angular-generate-identity-modal"');
+    expect(source).toContain('data-collector-deploy-api-contract="angular-post-generate-identity"');
+    expect(source).toContain('data-collector-deploy-failure-contract="angular-apply-fail-notification"');
+    expect(source).toContain('data-collector-deploy-validation-contract="angular-submit-marks-required"');
+    expect(source).toContain('data-collector-deploy-validation-raw-contract="angular-required-before-trim"');
+    expect(source).toContain('data-collector-deploy-validation-raw-owner="collector-route-controller"');
+    expect(source).toContain('data-collector-deploy-command-contract="angular-docker-package-shells"');
+    expect(source).toContain('data-collector-deploy-command-owner="hertzbeat-ui-code-editor"');
+    expect(source).toContain('data-collector-deploy-package-link-contract="angular-github-releases-link"');
+    expect(source).toContain('data-collector-deploy-package-link="angular-github-releases-link"');
+    expect(source).toContain('href="https://github.com/apache/hertzbeat/releases"');
+    expect(source).toContain('target="_blank"');
+    expect(source).toContain("title={t('common.notify.apply-fail')}");
+    expect(source).toContain('meta={deployError}');
+    expect(source).toContain('data-collector-deploy-feedback="angular-apply-fail-notification"');
+    expect(source).toContain('data-collector-deploy-feedback-title="common.notify.apply-fail"');
+    expect(source).toContain('data-collector-deploy-feedback-detail="backend-message"');
+    expect(source).toContain('data-collector-deploy-copy-contract="angular-copy-identity"');
+    expect(source).toContain('data-collector-deploy-copy-feedback-contract="angular-copy-success-duration-800"');
+    expect(source).toContain('data-collector-deploy-copy-feedback-duration-ms="800"');
+    expect(source).toContain('data-collector-deploy-dialog="angular-generate-identity-modal"');
+    expect(source).toContain('data-collector-deploy-mask-contract="angular-mask-closable-false"');
+    expect(source).toContain('data-collector-deploy-mask-closable="angular-mask-closable-false"');
+    expect(source).toContain('data-collector-deploy-mask-closable-owner="angular-nz-modal"');
+    expect(source).toContain('data-collector-deploy-width-contract="angular-width-45-percent"');
+    expect(source).toContain('data-collector-deploy-width="angular-width-45-percent"');
+    expect(source).toContain('data-collector-deploy-width-owner="angular-nz-modal"');
+    expect(source).toContain('md:w-[45vw] md:max-w-[45vw]');
+    expect(source).toContain('data-collector-deploy-field-layout-contract="angular-label-7-control-12"');
+    expect(source).toContain('data-collector-deploy-field-layout-owner="route-form-field-grid"');
+    expect(source).toContain('data-collector-deploy-field="name"');
+    expect(source).toContain('data-collector-deploy-field-layout="angular-label-7-control-12"');
+    expect(source).toContain('data-collector-deploy-label-span="7"');
+    expect(source).toContain('data-collector-deploy-control-span="12"');
+    expect(source).toContain('md:grid-cols-[7fr_12fr]');
+    expect(source).toContain('data-collector-deploy-name-input="angular-required-name"');
+    expect(source).toContain('data-collector-deploy-name-validation-raw="angular-required-before-trim"');
+    expect(source).toContain('data-collector-deploy-name-validation-raw-owner="collector-route-controller"');
+    expect(source).toContain("data-collector-deploy-name-lock={resolvedDeployIdentity ? 'angular-disable-after-identity' : 'editable-before-identity'}");
+    expect(source).toContain('data-collector-deploy-name-lock-owner="angular-ngmodel-input"');
+    expect(source).toContain("data-collector-deploy-name-disabled={resolvedDeployIdentity ? 'true' : 'false'}");
+    expect(source).toContain('data-collector-deploy-name-validation-state');
+    expect(source).toContain('data-collector-deploy-name-validation-contract="angular-submit-marks-required"');
+    expect(source).toContain('data-collector-deploy-loading-contract="angular-nz-ok-loading"');
+    expect(source).toContain('data-collector-deploy-generate="angular-generate-identity"');
+    expect(source).toContain('data-collector-deploy-generate-validation="angular-click-before-required"');
+    expect(source).toContain("data-collector-deploy-ok-loading={isDeployPending ? 'true' : 'false'}");
+    expect(source).toContain('data-collector-deploy-ok-loading-owner="angular-nz-ok-loading"');
+    expect(source).toContain('disabled={Boolean(isDeployPending || resolvedDeployIdentity)}');
+    expect(source).toContain('data-collector-deploy-result-close-contract="angular-danger-close-after-identity"');
+    expect(source).toContain('data-collector-deploy-result-close-owner="hertzbeat-ui-button"');
+    expect(source).toContain('data-collector-deploy-close-reset-contract="angular-close-clears-name"');
+    expect(source).toContain('data-collector-deploy-close-reset="angular-close-clears-name"');
+    expect(source).toContain('data-collector-deploy-close-reset-owner="collector-route-controller"');
+    expect(source).toContain('data-collector-deploy-result-close-reset="angular-close-clears-name"');
+    expect(source).toContain('data-collector-deploy-result-close-reset-owner="collector-route-controller"');
+    expect(source).toContain('data-collector-deploy-docker-shell="angular-docker-shell"');
+    expect(source).toContain('data-collector-deploy-package-shell="angular-package-shell"');
+    expect(source).toContain('data-collector-online-selected="angular-batch-online-entry"');
+    expect(source).toContain('data-collector-offline-selected="angular-batch-offline-entry"');
+    expect(source).toContain('data-collector-delete-selected="angular-batch-delete-entry"');
+    expect(source).toContain('HzConfirmDialog');
+    expect(source).toContain('HzInlineFeedback');
+    expect(source).toContain('HzPaginationBar');
+    expect(source).toContain('HzTableRowActionButton');
+    expect(source).toContain('pageSizeOptions = [8, 15, 25]');
+    expect(source).toContain('data-collector-pagination="cold-dense-pagination"');
+    expect(source).toContain('data-collector-pagination-owner="hertzbeat-ui-pagination-bar"');
+    expect(source).toContain('data-collector-pagination-contract="angular-search-pagination"');
+    expect(source).toContain('data-collector-pagination-page-size-owner');
+    expect(source).toContain('data-collector-pagination-page-jump-owner');
+    expect(source).toContain("t('collector.pagination.summary'");
+    expect(source).toContain("t('collector.pagination.page-size')");
+    expect(source).toContain("t('collector.pagination.page')");
+    expect(source).toContain('data-collector-select-all="cold-checkbox"');
+    expect(source).toContain('data-collector-select-all-contract="angular-header-includes-disabled-default"');
+    expect(source).toContain('data-collector-selectable-names={collectorNames.join');
+    expect(source).toContain('data-collector-immutable-names={immutableCollectorNames.join');
+    expect(source).toContain('event.target.checked ? collectorNames : []');
+    expect(source).toContain('data-collector-row-select={row.name}');
+    expect(source).toContain("row.canMutate ? 'angular-selectable' : 'angular-disabled-visual-only'");
+    expect(source).toContain('checked={row.canMutate && resolvedSelectedCollectors.includes(row.name)}');
+    expect(source).toContain('data-collector-online-one={row.name}');
+    expect(source).toContain('data-collector-offline-one={row.name}');
+    expect(source).toContain('const [openRowActionMenuId, setOpenRowActionMenuId] = React.useState<string | null>(null)');
+    expect(source).toContain('const isRowActionMenuOpen = openRowActionMenuId === rowActionMenuId');
+    expect(source).toContain('data-collector-row-delete-menu={rowActionMenuId}');
+    expect(source).toContain('data-collector-row-delete-menu-contract="angular-ellipsis-dropdown-delete"');
+    expect(source).toContain('data-collector-row-delete-menu-layer="overlay-visible-above-panel"');
+    expect(source).toContain('data-collector-row-delete-menu-clearance="floating-overlay-no-panel-crop"');
+    expect(source).toContain("data-collector-row-delete-menu-open={isRowActionMenuOpen ? 'true' : 'false'}");
+    expect(source).toContain('aria-expanded={isRowActionMenuOpen}');
+    expect(source).toContain('data-collector-row-delete-menu-trigger={rowActionMenuId}');
+    expect(source).toContain('data-collector-row-delete-menu-panel={rowActionMenuId}');
+    expect(source).toContain('data-collector-row-delete-menu-layer-panel="overlay-visible-above-panel"');
+    expect(source).toContain('data-collector-row-delete-menu-clearance-panel="floating-overlay-no-panel-crop"');
+    expect(source).toContain('data-collector-row-delete-menu-owner="hertzbeat-ui-table-row-action-button"');
+    expect(source).toContain("data-collector-row-delete-menu-panel-open={isRowActionMenuOpen ? 'true' : 'false'}");
+    expect(source).not.toContain('<details');
+    expect(source).not.toContain('</details>');
+    expect(source).toContain('collectorActionMenuRootClassName');
+    expect(source).toContain('collectorActionMenuPanelBaseClassName');
+    expect(source).toContain('data-collector-delete-one={row.name}');
+    expect(source).toContain('data-collector-delete-one-owner="hertzbeat-ui-table-row-action-button"');
+    expect(source).toContain('data-collector-delete-confirm="angular-modal-confirm"');
     expect(source).toContain('data-collector-health-evidence="cluster-status"');
     expect(source).toContain('data-collector-health-freshness="last-seen"');
     expect(source).toContain('data-collector-row-health="collector-status"');
     expect(source).toContain('data-collector-table-shell="cold-dense-table"');
+    expect(source).toContain("data-collector-table-loading={tableLoading ? 'true' : 'false'}");
+    expect(source).toContain('data-collector-table-loading-contract="angular-load-collectors-table"');
+    expect(source).toContain('data-collector-table-loading-owner="angular-nz-table-loading"');
+    expect(source).toContain('data-collector-table-loading-scope="load-search-refresh-pagination-mutation"');
+    expect(source).toContain("data-collector-table-loading-source={tableLoading ? 'route-load-or-mutation' : 'idle'}");
     expect(source).toContain('data-collector-manage-table="cold-collector-table"');
     expect(source).not.toContain('data-collector-summary-rail');
     expect(source).not.toContain('coldCollectorVisual.search.row');
@@ -180,4 +404,602 @@ describe('collector manage surface', () => {
     expect(source).toContain('text-[#a9b0bb]');
     expect(source).toContain('text-[#858d9a]');
   });
+
+  it('keeps the table busy while route reloads run like Angular loadCollectorsTable', () => {
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <CollectorManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [],
+            totalElements: 0,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        isLoadPending
+        formatTime={() => '2026-04-10 18:00:00'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onDeploy={() => {}}
+        onGoOnline={() => {}}
+        onGoOffline={() => {}}
+        onDelete={() => {}}
+        onRowGoOnline={() => {}}
+        onRowGoOffline={() => {}}
+        onRowDelete={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-collector-table-loading="true"');
+    expect(html).toContain('data-collector-table-loading-contract="angular-load-collectors-table"');
+    expect(html).toContain('data-collector-table-loading-scope="load-search-refresh-pagination-mutation"');
+    expect(html).toContain('data-collector-table-loading-source="route-load-or-mutation"');
+    expect(html).toContain('aria-busy="true"');
+  });
+
+  it('keeps the collector shell mounted after list load failure without rendering backend text', () => {
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <CollectorManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [],
+            totalElements: 0,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        loadError="backend refused collector load"
+        formatTime={() => '2026-04-10 18:00:00'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onDeploy={() => {}}
+        onGoOnline={() => {}}
+        onGoOffline={() => {}}
+        onDelete={() => {}}
+        onRowGoOnline={() => {}}
+        onRowGoOffline={() => {}}
+        onRowDelete={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-collector-manage-surface="otlp-cold-collector-console"');
+    expect(html).toContain('data-collector-admin-layout="full-width-admin-list"');
+    expect(html).toContain('data-collector-load-failure-contract="angular-console-only-shell"');
+    expect(html).toContain('data-collector-load-failure-owner="collector-route-controller"');
+    expect(html).toContain('data-collector-load-failure="angular-console-only-shell"');
+    expect(html).toContain('data-collector-table-shell="cold-dense-table"');
+    expect(html).not.toContain('backend refused collector load');
+  });
+
+  it('renders Angular collector delete warning, confirmation, and feedback contracts', () => {
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <CollectorManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [
+              {
+                collector: {
+                  name: 'edge-a',
+                  ip: '10.0.0.1',
+                  status: 0,
+                  mode: 'public',
+                  version: '1.0.0',
+                  gmtUpdate: '2026-04-10T10:00:00Z'
+                },
+                pinMonitorNum: 2,
+                dispatchMonitorNum: 5
+              }
+            ],
+            totalElements: 1,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        selectedCollectors={['edge-a']}
+        actionError="请先选择要删除的监控"
+        actionTone="warning"
+        deleteTarget={{
+          collectors: ['edge-a'],
+          label: 'edge-a',
+          mode: 'single'
+        }}
+        isDeletePending
+        formatTime={() => '2026-04-10 18:00:00'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onDeploy={() => {}}
+        onGoOnline={() => {}}
+        onGoOffline={() => {}}
+        onDelete={() => {}}
+        onSelectedCollectorsChange={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onRowGoOnline={() => {}}
+        onRowGoOffline={() => {}}
+        onRowDelete={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-collector-delete-warning-contract="angular-no-select-warning"');
+    expect(html).toContain('data-collector-delete-confirm-contract="angular-modal-confirm"');
+    expect(html).toContain('data-collector-delete-confirm-contract-owner="hertzbeat-ui-confirm-dialog"');
+    expect(html).toContain('data-collector-delete-feedback-contract="angular-delete-notify"');
+    expect(html).toContain('data-collector-delete-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-collector-delete-api-contract="angular-repeated-collectors-query"');
+    expect(html).toContain('data-collector-delete-page-clamp-contract="angular-update-page-index"');
+    expect(html).toContain('data-collector-delete-page-clamp-owner="route-state-contract"');
+    expect(html).toContain('data-collector-delete-selected="angular-batch-delete-entry"');
+    expect(html).toContain('data-collector-delete-feedback="angular-no-select-warning"');
+    expect(html).toContain('data-collector-delete-feedback-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-collector-delete-confirm="angular-modal-confirm"');
+    expect(html).toContain('data-collector-delete-confirm-owner="hertzbeat-ui-confirm-dialog"');
+    expect(html).toContain('data-collector-delete-confirm-submit="angular-modal-confirm"');
+    expect(html).toContain('data-collector-table-loading="true"');
+    expect(html).toContain('aria-busy="true"');
+    expect(html).toContain('data-collector-delete-confirm-target="edge-a"');
+    expect(html).toContain('data-collector-row-select="edge-a"');
+    expect(html).toContain('data-collector-row-delete-menu-contract="angular-ellipsis-dropdown-delete"');
+    expect(html).toContain('data-collector-row-delete-menu-layer="overlay-visible-above-panel"');
+    expect(html).toContain('data-collector-row-delete-menu-clearance="floating-overlay-no-panel-crop"');
+    expect(html).toContain('data-collector-row-delete-menu-owner="hertzbeat-ui-table-row-action-button"');
+    expect(html).toContain('data-collector-row-delete-menu-panel-open="false"');
+    expect(html).toContain('data-collector-delete-one="edge-a"');
+    expect(html).toContain('请先选择要删除的监控');
+    expect(html).toContain('请确认是否删除!');
+  });
+
+  it('renders Angular collector delete and operate failure titles with backend detail metadata', () => {
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const deleteFailureHtml = renderToStaticMarkup(
+      <CollectorManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [],
+            totalElements: 0,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        actionError="删除失败!"
+        actionMeta="backend delete refused"
+        actionTone="critical"
+        actionFeedbackContract="angular-delete-fail"
+        formatTime={() => '2026-04-10 18:00:00'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onDeploy={() => {}}
+        onGoOnline={() => {}}
+        onGoOffline={() => {}}
+        onDelete={() => {}}
+        onRowGoOnline={() => {}}
+        onRowGoOffline={() => {}}
+        onRowDelete={() => {}}
+      />
+    );
+
+    expect(deleteFailureHtml).toContain('data-collector-delete-feedback="angular-delete-fail"');
+    expect(deleteFailureHtml).toContain('data-collector-delete-feedback-title="common.notify.delete-fail"');
+    expect(deleteFailureHtml).toContain('data-collector-delete-feedback-detail="backend-message"');
+    expect(deleteFailureHtml).toContain('删除失败!');
+    expect(deleteFailureHtml).toContain('backend delete refused');
+
+    const operateFailureHtml = renderToStaticMarkup(
+      <CollectorManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [],
+            totalElements: 0,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        actionError="操作失败!"
+        actionMeta="backend operate refused"
+        actionTone="critical"
+        actionFeedbackContract="angular-operate-fail"
+        formatTime={() => '2026-04-10 18:00:00'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onDeploy={() => {}}
+        onGoOnline={() => {}}
+        onGoOffline={() => {}}
+        onDelete={() => {}}
+        onRowGoOnline={() => {}}
+        onRowGoOffline={() => {}}
+        onRowDelete={() => {}}
+      />
+    );
+
+    expect(operateFailureHtml).toContain('data-collector-operate-feedback="angular-operate-fail"');
+    expect(operateFailureHtml).toContain('data-collector-operate-feedback-title="common.notify.operate-fail"');
+    expect(operateFailureHtml).toContain('data-collector-operate-feedback-detail="backend-message"');
+    expect(operateFailureHtml).toContain('操作失败!');
+    expect(operateFailureHtml).toContain('backend operate refused');
+  });
+
+  it('renders Angular collector online/offline warning, confirmation, and feedback contracts', () => {
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <CollectorManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [
+              {
+                collector: {
+                  name: 'edge-a',
+                  ip: '10.0.0.1',
+                  status: 0,
+                  mode: 'public',
+                  version: '1.0.0',
+                  gmtUpdate: '2026-04-10T10:00:00Z'
+                },
+                pinMonitorNum: 2,
+                dispatchMonitorNum: 5
+              }
+            ],
+            totalElements: 1,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        selectedCollectors={['edge-a']}
+        actionError="未选中任何待下线项!"
+        actionTone="warning"
+        actionFeedbackContract="angular-no-select-offline"
+        deleteTarget={{
+          action: 'offline',
+          collectors: ['edge-a'],
+          label: 'edge-a',
+          mode: 'single'
+        }}
+        isDeletePending
+        formatTime={() => '2026-04-10 18:00:00'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onDeploy={() => {}}
+        onGoOnline={() => {}}
+        onGoOffline={() => {}}
+        onDelete={() => {}}
+        onSelectedCollectorsChange={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onRowGoOnline={() => {}}
+        onRowGoOffline={() => {}}
+        onRowDelete={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-collector-operate-warning-contract="angular-no-select-online-offline"');
+    expect(html).toContain('data-collector-operate-confirm-contract="angular-modal-confirm"');
+    expect(html).toContain('data-collector-operate-confirm-contract-owner="hertzbeat-ui-confirm-dialog"');
+    expect(html).toContain('data-collector-operate-feedback-contract="angular-operate-notify"');
+    expect(html).toContain('data-collector-operate-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-collector-operate-api-contract="angular-put-repeated-collectors-query"');
+    expect(html).toContain('data-collector-action-feedback-contract="angular-no-select-offline"');
+    expect(html).toContain('data-collector-operate-feedback="angular-no-select-offline"');
+    expect(html).toContain('data-collector-operate-feedback-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-collector-operate-confirm="angular-modal-confirm"');
+    expect(html).toContain('data-collector-operate-confirm-owner="hertzbeat-ui-confirm-dialog"');
+    expect(html).toContain('data-collector-operate-confirm-submit="angular-modal-confirm"');
+    expect(html).toContain('data-collector-table-loading="true"');
+    expect(html).toContain('aria-busy="true"');
+    expect(html).toContain('data-collector-delete-confirm-target="edge-a"');
+    expect(html).toContain('data-collector-offline-one="edge-a"');
+    expect(html).toContain('未选中任何待下线项!');
+    expect(html).toContain('请确认是否下线此采集器!');
+  });
+
+  it('keeps the Angular header select-all batch payload while the default row stays visually disabled', () => {
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <CollectorManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [
+              {
+                collector: {
+                  name: 'main-default-collector',
+                  ip: '127.0.0.1',
+                  status: 0,
+                  mode: 'public',
+                  version: '2.0.0',
+                  gmtUpdate: '2026-04-10T10:00:00Z'
+                },
+                pinMonitorNum: 5,
+                dispatchMonitorNum: 8
+              },
+              {
+                collector: {
+                  name: 'edge-a',
+                  ip: '10.0.0.1',
+                  status: 1,
+                  mode: 'private',
+                  version: '1.0.0',
+                  gmtUpdate: '2026-04-09T10:00:00Z'
+                },
+                pinMonitorNum: 2,
+                dispatchMonitorNum: 5
+              }
+            ],
+            totalElements: 2,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        selectedCollectors={['edge-a']}
+        formatTime={() => '2026-04-10 18:00:00'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onDeploy={() => {}}
+        onGoOnline={() => {}}
+        onGoOffline={() => {}}
+        onDelete={() => {}}
+        onSelectedCollectorsChange={() => {}}
+        onRowGoOnline={() => {}}
+        onRowGoOffline={() => {}}
+        onRowDelete={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-collector-select-all-contract="angular-header-includes-disabled-default"');
+    expect(html).toContain('data-collector-selectable-names="main-default-collector,edge-a"');
+    expect(html).toContain('data-collector-immutable-names="main-default-collector"');
+    expect(html).toContain('data-collector-row-select="main-default-collector"');
+    expect(html).toContain('data-collector-row-select-contract="angular-disabled-visual-only"');
+    expect(html).toContain('data-collector-row-select="edge-a"');
+    expect(html).toContain('data-collector-row-select-contract="angular-selectable"');
+  });
+
+  it('renders Angular collector deploy identity modal and code editor contracts', () => {
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <CollectorManageSurface
+        t={t}
+        data={{ list: { content: [], totalElements: 0, pageIndex: 0, pageSize: 8 } }}
+        search=""
+        deployOpen
+        deployName="edge-a"
+        deployIdentity="identity-123"
+        deployHost="10.0.0.10"
+        deployDockerShell="$ docker run -d -e IDENTITY=identity-123"
+        deployPackageShell="identity: identity-123\nmanager-host: 10.0.0.10"
+        deployCopyMessage="复制成功!"
+        formatTime={() => '2026-04-10 18:00:00'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onDeploy={() => {}}
+        onGoOnline={() => {}}
+        onGoOffline={() => {}}
+        onDelete={() => {}}
+        onDeployNameChange={() => {}}
+        onDeployGenerate={() => {}}
+        onDeployClose={() => {}}
+        onDeployCopy={() => {}}
+        onRowGoOnline={() => {}}
+        onRowGoOffline={() => {}}
+        onRowDelete={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-collector-deploy-dialog="angular-generate-identity-modal"');
+    expect(html).toContain('data-collector-deploy-mask-closable="angular-mask-closable-false"');
+    expect(html).toContain('data-collector-deploy-mask-closable-owner="angular-nz-modal"');
+    expect(html).toContain('data-collector-deploy-width="angular-width-45-percent"');
+    expect(html).toContain('data-collector-deploy-width-owner="angular-nz-modal"');
+    expect(html).toContain('data-collector-deploy-field="name"');
+    expect(html).toContain('data-collector-deploy-field-layout="angular-label-7-control-12"');
+    expect(html).toContain('data-collector-deploy-label-span="7"');
+    expect(html).toContain('data-collector-deploy-control-span="12"');
+    expect(html).toContain('data-collector-deploy-name-input="angular-required-name"');
+    expect(html).toContain('data-collector-deploy-name-lock="angular-disable-after-identity"');
+    expect(html).toContain('data-collector-deploy-name-lock-owner="angular-ngmodel-input"');
+    expect(html).toContain('data-collector-deploy-name-disabled="true"');
+    expect(html).toContain('disabled=""');
+    expect(html).toContain('data-collector-deploy-result="angular-identity-shells"');
+    expect(html).toContain('data-collector-deploy-copy-identity="angular-copy-identity"');
+    expect(html).toContain('data-collector-deploy-copy-feedback="angular-copy-success"');
+    expect(html).toContain('data-collector-deploy-result-close-contract="angular-danger-close-after-identity"');
+    expect(html).toContain('data-collector-deploy-copy-feedback-duration-ms="800"');
+    expect(html).toContain('data-collector-deploy-result-close-owner="hertzbeat-ui-button"');
+    expect(html).toContain('data-collector-deploy-close-reset="angular-close-clears-name"');
+    expect(html).toContain('data-collector-deploy-close-reset-owner="collector-route-controller"');
+    expect(html).toContain('data-collector-deploy-close-pending-result="angular-close-ignores-late-generate"');
+    expect(html).toContain('data-collector-deploy-close-pending-result-owner="collector-route-controller"');
+    expect(html).toContain('data-collector-deploy-result-close-reset="angular-close-clears-name"');
+    expect(html).toContain('data-collector-deploy-result-close-reset-owner="collector-route-controller"');
+    expect(html).toContain('data-hz-button-tier="solid-danger"');
+    expect(html).toContain('data-collector-deploy-docker-shell="angular-docker-shell"');
+    expect(html).toContain('data-collector-deploy-package-shell="angular-package-shell"');
+    expect(html).toContain('data-collector-deploy-code-owner="hertzbeat-ui-code-editor"');
+    expect(html).toContain('data-collector-deploy-package-link="angular-github-releases-link"');
+    expect(html).toContain('data-collector-deploy-package-link-owner="angular-nz-modal"');
+    expect(html).toContain('href="https://github.com/apache/hertzbeat/releases"');
+    expect(html).toContain('target="_blank"');
+    expect(html).toContain('identity-123');
+    expect(html).toContain('10.0.0.10');
+    expect(html).toContain('通过 Docker 方式部署');
+    expect(html).toContain('通过安装包部署');
+  });
+
+  it('keeps collector deploy required-name validation submit-triggered like Angular', () => {
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const pristineHtml = renderToStaticMarkup(
+      <CollectorManageSurface
+        t={t}
+        data={{ list: { content: [], totalElements: 0, pageIndex: 0, pageSize: 8 } }}
+        search=""
+        deployOpen
+        deployName=""
+        deployNameValidationVisible={false}
+        formatTime={() => '2026-04-10 18:00:00'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onDeploy={() => {}}
+        onGoOnline={() => {}}
+        onGoOffline={() => {}}
+        onDelete={() => {}}
+        onDeployNameChange={() => {}}
+        onDeployGenerate={() => {}}
+        onDeployClose={() => {}}
+        onRowGoOnline={() => {}}
+        onRowGoOffline={() => {}}
+        onRowDelete={() => {}}
+      />
+    );
+    const dirtyHtml = renderToStaticMarkup(
+      <CollectorManageSurface
+        t={t}
+        data={{ list: { content: [], totalElements: 0, pageIndex: 0, pageSize: 8 } }}
+        search=""
+        deployOpen
+        deployName=""
+        deployNameValidationVisible
+        formatTime={() => '2026-04-10 18:00:00'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onDeploy={() => {}}
+        onGoOnline={() => {}}
+        onGoOffline={() => {}}
+        onDelete={() => {}}
+        onDeployNameChange={() => {}}
+        onDeployGenerate={() => {}}
+        onDeployClose={() => {}}
+        onRowGoOnline={() => {}}
+        onRowGoOffline={() => {}}
+        onRowDelete={() => {}}
+      />
+    );
+
+    expect(pristineHtml).toContain('data-collector-deploy-validation-contract="angular-submit-marks-required"');
+    expect(pristineHtml).toContain('data-collector-deploy-validation-raw-contract="angular-required-before-trim"');
+    expect(pristineHtml).toContain('data-collector-deploy-name-validation-state="pristine"');
+    expect(pristineHtml).toContain('data-collector-deploy-name-validation-raw="angular-required-before-trim"');
+    expect(pristineHtml).toContain('data-collector-deploy-name-lock="editable-before-identity"');
+    expect(pristineHtml).toContain('data-collector-deploy-name-disabled="false"');
+    expect(pristineHtml).toContain('data-collector-deploy-generate-validation="angular-click-before-required"');
+    expect(pristineHtml).not.toContain('data-collector-deploy-name-validation="validation.required"');
+    expect(dirtyHtml).toContain('data-collector-deploy-name-validation-state="dirty-invalid"');
+    expect(dirtyHtml).toContain('data-collector-deploy-name-validation="validation.required"');
+    expect(dirtyHtml).toContain('data-collector-deploy-name-validation-contract="angular-submit-marks-required"');
+    expect(dirtyHtml).toContain('请填充必填项!');
+  });
+
+  it('keeps collector deploy OK loading visible while identity generation is pending', () => {
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <CollectorManageSurface
+        t={t}
+        data={{ list: { content: [], totalElements: 0, pageIndex: 0, pageSize: 8 } }}
+        search=""
+        deployOpen
+        deployName="edge-a"
+        isDeployPending
+        formatTime={() => '2026-04-10 18:00:00'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onDeploy={() => {}}
+        onGoOnline={() => {}}
+        onGoOffline={() => {}}
+        onDelete={() => {}}
+        onDeployNameChange={() => {}}
+        onDeployGenerate={() => {}}
+        onDeployClose={() => {}}
+        onRowGoOnline={() => {}}
+        onRowGoOffline={() => {}}
+        onRowDelete={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-collector-deploy-loading-contract="angular-nz-ok-loading"');
+    expect(html).toContain('data-collector-deploy-ok-loading="true"');
+    expect(html).toContain('data-collector-deploy-ok-loading-owner="angular-nz-ok-loading"');
+    expect(html).toContain('aria-busy="true"');
+    expect(html).toContain('disabled=""');
+    expect(html).toContain('加载中');
+  });
+
+  it('keeps collector deploy generate failures as Angular apply-fail notifications', () => {
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <CollectorManageSurface
+        t={t}
+        data={{ list: { content: [], totalElements: 0, pageIndex: 0, pageSize: 8 } }}
+        search=""
+        deployOpen
+        deployName="edge-a"
+        deployError="collector already exists"
+        formatTime={() => '2026-04-10 18:00:00'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onDeploy={() => {}}
+        onGoOnline={() => {}}
+        onGoOffline={() => {}}
+        onDelete={() => {}}
+        onDeployNameChange={() => {}}
+        onDeployGenerate={() => {}}
+        onDeployClose={() => {}}
+        onRowGoOnline={() => {}}
+        onRowGoOffline={() => {}}
+        onRowDelete={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-collector-deploy-failure-contract="angular-apply-fail-notification"');
+    expect(html).toContain('data-collector-deploy-feedback="angular-apply-fail-notification"');
+    expect(html).toContain('data-collector-deploy-feedback-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-collector-deploy-feedback-title="common.notify.apply-fail"');
+    expect(html).toContain('data-collector-deploy-feedback-detail="backend-message"');
+    expect(html).toContain('collector already exists');
+  });
 });
diff --git a/web-next/components/pages/collector-manage-surface.tsx b/web-next/components/pages/collector-manage-surface.tsx
index 950313a96f..2aedcc3523 100644
--- a/web-next/components/pages/collector-manage-surface.tsx
+++ b/web-next/components/pages/collector-manage-surface.tsx
@@ -1,27 +1,68 @@
 'use client';
 
 import React from 'react';
-import { Cloud, Inbox, Power, PowerOff, RefreshCw, Trash2, UploadCloud } from 'lucide-react';
+import { Cloud, Copy, Inbox, MoreHorizontal, Power, PowerOff, RefreshCw, Trash2, UploadCloud, X } from 'lucide-react';
+import { HzButton, HzCodeEditor, HzConfirmDialog, HzInlineFeedback, HzPaginationBar, HzTableRowActionButton } from '@hertzbeat/ui';
 import { Button } from '../ui/button';
+import { Checkbox } from '../ui/checkbox';
 import { SearchRow } from '../ui/search-row';
 import { coldOpsCatalogVisual } from '../../lib/cold-ops-visual';
 import { buildCollectorClusterHealthEvidence, buildCollectorTableRows } from '../../lib/collector-manage/view-model';
 import type { CollectorSummary, PageResult } from '../../lib/types';
 
 type Translator = (key: string, params?: Record<string, string | number | null | undefined>) => string;
+type CollectorActionTone = 'success' | 'warning' | 'critical';
+type CollectorMutationAction = 'delete' | 'online' | 'offline';
+export type CollectorDeleteTarget = {
+  collectors: string[];
+  label: string;
+  mode: 'single' | 'batch';
+  action?: CollectorMutationAction;
+};
 
 type CollectorManageSurfaceProps = {
   t: Translator;
   data: { list: PageResult<CollectorSummary> };
   search: string;
+  selectedCollectors?: string[];
+  actionMessage?: string | null;
+  actionError?: string | null;
+  actionMeta?: string | null;
+  actionTone?: CollectorActionTone;
+  actionFeedbackContract?: string | null;
+  loadError?: string | null;
+  deleteTarget?: CollectorDeleteTarget | null;
+  isDeletePending?: boolean;
+  isLoadPending?: boolean;
+  deployOpen?: boolean;
+  deployName?: string;
+  deployIdentity?: string;
+  deployHost?: string;
+  deployDockerShell?: string;
+  deployPackageShell?: string;
+  deployError?: string | null;
+  deployCopyMessage?: string | null;
+  isDeployPending?: boolean;
+  deployNameValidationVisible?: boolean;
   formatTime: (value?: number | string | null) => string;
   onSearchChange: (value: string) => void;
   onSearch: () => void;
+  onSearchClear: () => void;
   onRefresh: () => void;
   onDeploy: () => void;
   onGoOnline: () => void;
   onGoOffline: () => void;
   onDelete: () => void;
+  onSelectedCollectorsChange?: (collectors: string[]) => void;
+  pageSizeOptions?: number[];
+  onPageIndexChange?: (pageIndex: number) => void;
+  onPageSizeChange?: (pageSize: number) => void;
+  onDeleteCancel?: () => void;
+  onDeleteConfirm?: () => void;
+  onDeployNameChange?: (value: string) => void;
+  onDeployGenerate?: () => void;
+  onDeployClose?: () => void;
+  onDeployCopy?: (value: string) => void;
   onRowGoOnline: (collector: string) => void;
   onRowGoOffline: (collector: string) => void;
   onRowDelete: (collector: string) => void;
@@ -38,14 +79,29 @@ const coldPrimaryButtonClassName =
 const coldIconButtonClassName =
   'h-8 w-8 min-w-0 rounded-[3px] border-[#2b3039] bg-[#101217] text-[#dbe4f0] hover:border-[#4e74f8] hover:bg-[#151b28] hover:text-white disabled:opacity-40';
 
-function statusClassName(statusLabel: string) {
-  if (statusLabel.includes('在线') || statusLabel.toLowerCase().includes('online')) {
+const collectorActionMenuRootClassName = 'relative z-40 inline-block overflow-visible';
+const collectorActionMenuPanelBaseClassName =
+  'absolute top-9 z-[100] min-w-[132px] rounded-[3px] border border-[#2b3039] bg-[#101217] p-1 shadow-[0_18px_42px_rgba(0,0,0,0.42)]';
+
+function statusClassName(statusTone: 'success' | 'danger') {
+  if (statusTone === 'success') {
     return 'border-[#166534]/45 bg-[#0f2f23] text-[#86efac]';
   }
   return 'border-[#7f1d1d]/55 bg-[#2a1214] text-[#fca5a5]';
 }
 
-function EmptyTableRow({ colSpan }: { colSpan: number }) {
+function resolveCollectorTargetAction(target?: CollectorDeleteTarget | null): CollectorMutationAction {
+  return target?.action ?? 'delete';
+}
+
+function resolveCollectorConfirmTitle(target: CollectorDeleteTarget | null | undefined, t: Translator) {
+  const action = resolveCollectorTargetAction(target);
+  if (action === 'online') return t(target?.mode === 'batch' ? 'collector.confirm.online-batch' : 'collector.confirm.online');
+  if (action === 'offline') return t(target?.mode === 'batch' ? 'collector.confirm.offline-batch' : 'collector.confirm.offline');
+  return t(target?.mode === 'batch' ? 'common.confirm.delete-batch' : 'common.confirm.delete');
+}
+
+function EmptyTableRow({ colSpan, t }: { colSpan: number; t: Translator }) {
   return (
     <tr data-collector-empty-state="cold-table-empty" className="bg-[#0b0c0e]">
       <td colSpan={colSpan} className="h-[240px] px-3 py-10 text-center text-[#858d9a]">
@@ -53,7 +109,7 @@ function EmptyTableRow({ colSpan }: { colSpan: number }) {
           <span className="inline-flex h-10 w-10 items-center justify-center rounded-[4px] border border-[#303743] bg-[#101217] text-[#cbd5e1]">
             <Inbox className="h-5 w-5" aria-hidden="true" />
           </span>
-          <div className="text-[13px] font-semibold text-[#dbe4f0]">暂无数据</div>
+          <div className="text-[13px] font-semibold text-[#dbe4f0]">{t('common.no-data')}</div>
         </div>
       </td>
     </tr>
@@ -64,30 +120,99 @@ export function CollectorManageSurface({
   t,
   data,
   search,
+  selectedCollectors,
+  actionMessage,
+  actionError,
+  actionMeta,
+  actionTone,
+  actionFeedbackContract,
+  loadError,
+  deleteTarget,
+  isDeletePending,
+  isLoadPending,
+  deployOpen,
+  deployName,
+  deployIdentity,
+  deployHost,
+  deployDockerShell,
+  deployPackageShell,
+  deployError,
+  deployCopyMessage,
+  isDeployPending,
+  deployNameValidationVisible,
   formatTime,
   onSearchChange,
   onSearch,
+  onSearchClear,
   onRefresh,
   onDeploy,
   onGoOnline,
   onGoOffline,
   onDelete,
+  onSelectedCollectorsChange,
+  pageSizeOptions = [8, 15, 25],
+  onPageIndexChange,
+  onPageSizeChange,
+  onDeleteCancel,
+  onDeleteConfirm,
+  onDeployNameChange,
+  onDeployGenerate,
+  onDeployClose,
+  onDeployCopy,
   onRowGoOnline,
   onRowGoOffline,
   onRowDelete
 }: CollectorManageSurfaceProps) {
   const collectors = data.list.content ?? [];
   const rows = buildCollectorTableRows(collectors, t, formatTime);
-  const clusterHealth = buildCollectorClusterHealthEvidence(collectors, formatTime);
+  const clusterHealth = buildCollectorClusterHealthEvidence(collectors, formatTime, t);
+  const collectorNames = rows.map(row => row.name);
+  const immutableCollectorNames = rows.filter(row => !row.canMutate).map(row => row.name);
+  const resolvedSelectedCollectors = selectedCollectors ?? [];
+  const currentPageIndex = Math.max(0, data.list.pageIndex ?? 0);
+  const currentPageSize = Math.max(1, data.list.pageSize ?? pageSizeOptions[0] ?? 8);
+  const totalElements = data.list.totalElements || 0;
+  const totalPages = Math.max(1, Math.ceil(totalElements / currentPageSize));
+  const currentPage = Math.min(currentPageIndex + 1, totalPages);
+  const pageStart = totalElements === 0 || rows.length === 0 ? 0 : currentPageIndex * currentPageSize + 1;
+  const pageEnd = totalElements === 0 ? 0 : Math.min(totalElements, currentPageIndex * currentPageSize + rows.length);
+  const allSelected = collectorNames.length > 0 && collectorNames.every(name => resolvedSelectedCollectors.includes(name));
+  const actionFeedbackTitle = actionError ?? actionMessage ?? '';
+  const resolvedActionTone = actionTone ?? (actionError ? 'critical' : 'success');
+  const targetAction = resolveCollectorTargetAction(deleteTarget);
+  const mutationPending = Boolean(isDeletePending);
+  const tableLoading = Boolean(isLoadPending || mutationPending);
+  const resolvedActionFeedbackContract =
+    actionFeedbackContract ??
+    (resolvedActionTone === 'warning' ? 'angular-no-select-warning' : resolvedActionTone === 'critical' ? 'angular-delete-fail' : 'angular-delete-notify');
+  const [openRowActionMenuId, setOpenRowActionMenuId] = React.useState<string | null>(null);
+  const trimmedDeployName = deployName?.trim() ?? '';
+  const resolvedDeployIdentity = deployIdentity ?? '';
+  const showDeployNameValidation = Boolean(deployNameValidationVisible && !trimmedDeployName && !resolvedDeployIdentity);
+  const paginationSummary = t('collector.pagination.summary', {
+    page: currentPage,
+    totalPages,
+    from: pageStart,
+    to: pageEnd,
+    total: totalElements
+  });
+
+  function handlePageJumpChange(value: string) {
+    const parsed = Number.parseInt(value, 10);
+    if (!Number.isFinite(parsed)) return;
+    const nextPageIndex = Math.min(Math.max(parsed, 1), totalPages) - 1;
+    onPageIndexChange?.(nextPageIndex);
+  }
 
   return (
-    <div
-      data-collector-manage-surface="otlp-cold-collector-console"
-      data-collector-manage-style-baseline={coldCollectorVisual.canvasName}
-      className={coldCollectorVisual.canvas.root}
-      style={coldCollectorVisual.canvas.backgroundStyle}
-    >
-      <section className={coldCollectorVisual.layout.pageSection}>
+    <>
+      <div
+        data-collector-manage-surface="otlp-cold-collector-console"
+        data-collector-manage-style-baseline={coldCollectorVisual.canvasName}
+        className={coldCollectorVisual.canvas.root}
+        style={coldCollectorVisual.canvas.backgroundStyle}
+      >
+        <section className={coldCollectorVisual.layout.pageSection}>
         <div className="mx-auto max-w-[1480px]">
           <div className="mb-5">
             <div data-collector-header="cold-compact-header" className={coldCollectorVisual.panel.hero}>
@@ -96,26 +221,50 @@ export function CollectorManageSurface({
                   {t('menu.advanced.collector')}
                 </h1>
                 <p className="mt-4 max-w-[780px] text-[13px] leading-6 text-[#a9b0bb]">
-                  管理已注册采集器节点、运行状态和任务分布,保持部署、搜索、上下线和删除操作在同一个高密度后台工作面中完成。
+                  {t('setting.collector.copy')}
                 </p>
                 <div data-collector-command-row="standard-equal-buttons" className={coldCollectorVisual.button.row}>
                   <Button size="sm" variant="default" className={coldButtonClassName} onClick={onRefresh}>
                     <RefreshCw className="h-3.5 w-3.5" aria-hidden="true" />
                     {t('common.refresh')}
                   </Button>
-                  <Button size="sm" variant="default" className={coldPrimaryButtonClassName} onClick={onDeploy}>
+                  <Button
+                    size="sm"
+                    variant="default"
+                    className={coldPrimaryButtonClassName}
+                    onClick={onDeploy}
+                    data-collector-deploy-open="angular-deploy-modal"
+                  >
                     <UploadCloud className="h-3.5 w-3.5" aria-hidden="true" />
                     {t('collector.deploy')}
                   </Button>
-                  <Button size="sm" variant="default" className={coldButtonClassName} onClick={onGoOnline}>
+                  <Button
+                    size="sm"
+                    variant="default"
+                    className={coldButtonClassName}
+                    onClick={onGoOnline}
+                    data-collector-online-selected="angular-batch-online-entry"
+                  >
                     <Power className="h-3.5 w-3.5" aria-hidden="true" />
                     {t('collector.online')}
                   </Button>
-                  <Button size="sm" variant="default" className={coldButtonClassName} onClick={onGoOffline}>
+                  <Button
+                    size="sm"
+                    variant="default"
+                    className={coldButtonClassName}
+                    onClick={onGoOffline}
+                    data-collector-offline-selected="angular-batch-offline-entry"
+                  >
                     <PowerOff className="h-3.5 w-3.5" aria-hidden="true" />
                     {t('collector.offline')}
                   </Button>
-                  <Button size="sm" variant="default" className={coldButtonClassName} onClick={onDelete}>
+                  <Button
+                    size="sm"
+                    variant="default"
+                    className={coldButtonClassName}
+                    onClick={onDelete}
+                    data-collector-delete-selected="angular-batch-delete-entry"
+                  >
                     <Trash2 className="h-3.5 w-3.5" aria-hidden="true" />
                     {t('collector.delete')}
                   </Button>
@@ -146,7 +295,55 @@ export function CollectorManageSurface({
             </div>
           </div>
 
-          <div data-collector-admin-layout="full-width-admin-list" className="space-y-5">
+          <div
+            data-collector-admin-layout="full-width-admin-list"
+            data-collector-delete-warning-contract="angular-no-select-warning"
+            data-collector-delete-confirm-contract="angular-modal-confirm"
+            data-collector-delete-confirm-contract-owner="hertzbeat-ui-confirm-dialog"
+            data-collector-delete-feedback-contract="angular-delete-notify"
+            data-collector-delete-feedback-contract-owner="hertzbeat-ui-inline-feedback"
+            data-collector-delete-api-contract="angular-repeated-collectors-query"
+            data-collector-delete-page-clamp-contract="angular-update-page-index"
+            data-collector-delete-page-clamp-owner="route-state-contract"
+            data-collector-selection-reset-contract="angular-load-clears-selection"
+            data-collector-selection-reset-owner="route-state-contract"
+            data-collector-search-submit-contract="angular-enter-and-clear"
+            data-collector-search-clear-contract="angular-cleared-load"
+            data-collector-search-clear-owner="shared-search-row"
+            data-collector-operate-warning-contract="angular-no-select-online-offline"
+            data-collector-operate-confirm-contract="angular-modal-confirm"
+            data-collector-operate-confirm-contract-owner="hertzbeat-ui-confirm-dialog"
+            data-collector-operate-feedback-contract="angular-operate-notify"
+            data-collector-operate-feedback-contract-owner="hertzbeat-ui-inline-feedback"
+            data-collector-operate-api-contract="angular-put-repeated-collectors-query"
+            data-collector-mutation-loading-contract="angular-nz-table-loading"
+            data-collector-load-failure-contract="angular-console-only-shell"
+            data-collector-load-failure-owner="collector-route-controller"
+            data-collector-load-failure={loadError ? 'angular-console-only-shell' : 'none'}
+            data-collector-deploy-modal-contract="angular-generate-identity-modal"
+            data-collector-deploy-api-contract="angular-post-generate-identity"
+            data-collector-deploy-failure-contract="angular-apply-fail-notification"
+            data-collector-deploy-validation-contract="angular-submit-marks-required"
+            data-collector-deploy-validation-raw-contract="angular-required-before-trim"
+            data-collector-deploy-validation-raw-owner="collector-route-controller"
+            data-collector-deploy-loading-contract="angular-nz-ok-loading"
+            data-collector-deploy-mask-contract="angular-mask-closable-false"
+            data-collector-deploy-width-contract="angular-width-45-percent"
+            data-collector-deploy-field-layout-contract="angular-label-7-control-12"
+            data-collector-deploy-field-layout-owner="route-form-field-grid"
+            data-collector-deploy-command-contract="angular-docker-package-shells"
+            data-collector-deploy-command-owner="hertzbeat-ui-code-editor"
+            data-collector-deploy-package-link-contract="angular-github-releases-link"
+            data-collector-deploy-copy-contract="angular-copy-identity"
+            data-collector-deploy-copy-feedback-contract="angular-copy-success-duration-800"
+            data-collector-deploy-result-close-contract="angular-danger-close-after-identity"
+            data-collector-deploy-result-close-owner="hertzbeat-ui-button"
+            data-collector-deploy-close-reset-contract="angular-close-clears-name"
+            data-collector-deploy-close-reset-owner="collector-route-controller"
+            data-collector-deploy-close-pending-result-contract="angular-close-ignores-late-generate"
+            data-collector-deploy-close-pending-result-owner="collector-route-controller"
+            className="space-y-5"
+          >
             <section className="min-w-0">
               <SearchRow
                 data-collector-toolbar="cold-table-toolbar"
@@ -154,18 +351,75 @@ export function CollectorManageSurface({
                 value={search}
                 placeholder={t('collector.name')}
                 searchLabel={t('common.search')}
+                clearLabel={t('common.clear')}
                 inputWidthClassName="w-[360px]"
                 onValueChange={onSearchChange}
                 onSearch={onSearch}
+                onClear={onSearchClear}
+                data-collector-search-submit-contract="angular-enter-and-clear"
+                data-collector-search-clear-contract="angular-cleared-load"
+                data-collector-search-clear-owner="shared-search-row"
+                trailingActions={
+                  <div className="text-[12px] font-semibold text-[#858d9a]">
+                    {t('setting.collector.selected-count', { count: resolvedSelectedCollectors.length })}
+                  </div>
+                }
               />
+              {actionFeedbackTitle ? (
+                <HzInlineFeedback
+                  tone={resolvedActionTone}
+                  title={actionFeedbackTitle}
+                  meta={actionMeta ?? undefined}
+                  variant="embedded"
+                  data-collector-action-feedback={resolvedActionTone}
+                  data-collector-action-feedback-contract={resolvedActionFeedbackContract}
+                  data-collector-delete-feedback={
+                    resolvedActionFeedbackContract.startsWith('angular-delete') || resolvedActionFeedbackContract === 'angular-no-select-warning'
+                      ? resolvedActionFeedbackContract
+                      : undefined
+                  }
+                  data-collector-delete-feedback-owner="hertzbeat-ui-inline-feedback"
+                  data-collector-delete-feedback-title={resolvedActionFeedbackContract === 'angular-delete-fail' ? 'common.notify.delete-fail' : undefined}
+                  data-collector-delete-feedback-detail={resolvedActionFeedbackContract === 'angular-delete-fail' && actionMeta ? 'backend-message' : undefined}
+                  data-collector-operate-feedback={
+                    resolvedActionFeedbackContract.startsWith('angular-operate') || resolvedActionFeedbackContract.startsWith('angular-no-select-o')
+                      ? resolvedActionFeedbackContract
+                      : undefined
+                  }
+                  data-collector-operate-feedback-owner="hertzbeat-ui-inline-feedback"
+                  data-collector-operate-feedback-title={resolvedActionFeedbackContract === 'angular-operate-fail' ? 'common.notify.operate-fail' : undefined}
+                  data-collector-operate-feedback-detail={resolvedActionFeedbackContract === 'angular-operate-fail' && actionMeta ? 'backend-message' : undefined}
+                />
+              ) : null}
 
               <div
                 data-collector-table-shell="cold-dense-table"
-                className="overflow-hidden rounded-[4px] border border-[#2b3039] bg-[#0b0c0e] shadow-[0_20px_56px_rgba(0,0,0,0.32)]"
+                data-collector-table-loading={tableLoading ? 'true' : 'false'}
+                data-collector-table-loading-contract="angular-load-collectors-table"
+                data-collector-table-loading-owner="angular-nz-table-loading"
+                data-collector-table-loading-scope="load-search-refresh-pagination-mutation"
+                data-collector-table-loading-source={tableLoading ? 'route-load-or-mutation' : 'idle'}
+                aria-busy={tableLoading ? 'true' : 'false'}
+                className="overflow-visible rounded-[4px] border border-[#2b3039] bg-[#0b0c0e] shadow-[0_20px_56px_rgba(0,0,0,0.32)]"
               >
                 <table data-collector-manage-table="cold-collector-table" className="w-full table-fixed border-collapse text-left text-[12px] text-[#a9b0bb]">
                   <thead className="border-b border-[#252b34] bg-[#101217] text-[11px] font-semibold uppercase tracking-[0.12em] text-[#7e8494]">
                     <tr>
+                      <th className="w-[44px] px-3 py-2.5">
+                        <Checkbox
+                          data-collector-select-all="cold-checkbox"
+                          data-collector-select-all-contract="angular-header-includes-disabled-default"
+                          data-collector-selectable-names={collectorNames.join(',')}
+                          data-collector-immutable-names={immutableCollectorNames.join(',')}
+                          aria-label={t('common.select')}
+                          containerClassName="min-h-0"
+                          checked={allSelected}
+                          disabled={collectorNames.length === 0}
+                          onChange={event => {
+                            onSelectedCollectorsChange?.(event.target.checked ? collectorNames : []);
+                          }}
+                        />
+                      </th>
                       <th className="w-[18%] px-3 py-2.5">{t('collector.name')}</th>
                       <th className="w-[11%] px-3 py-2.5">{t('collector.status')}</th>
                       <th className="w-[14%] px-3 py-2.5">{t('collector.mode')}</th>
@@ -179,8 +433,29 @@ export function CollectorManageSurface({
                     </tr>
                   </thead>
                   <tbody>
-                    {rows.length > 0 ? rows.map(row => (
-                      <tr key={row.key} data-collector-row={row.key} className="border-t border-[#252b34] bg-[#0b0c0e] transition hover:bg-[#111721]">
+                    {rows.length > 0 ? rows.map(row => {
+                      const rowActionMenuId = row.name;
+                      const isRowActionMenuOpen = openRowActionMenuId === rowActionMenuId;
+
+                      return (
+                        <tr key={row.key} data-collector-row={row.key} className="border-t border-[#252b34] bg-[#0b0c0e] transition hover:bg-[#111721]">
+                          <td className="px-3 py-2.5">
+                            <Checkbox
+                              aria-label={t('common.select')}
+                              data-collector-row-select={row.name}
+                              data-collector-row-select-contract={row.canMutate ? 'angular-selectable' : 'angular-disabled-visual-only'}
+                              containerClassName="min-h-0"
+                              checked={row.canMutate && resolvedSelectedCollectors.includes(row.name)}
+                              disabled={!row.canMutate}
+                              onChange={event => {
+                                onSelectedCollectorsChange?.(
+                                  event.target.checked
+                                    ? [...resolvedSelectedCollectors, row.name]
+                                    : resolvedSelectedCollectors.filter(value => value !== row.name)
+                                );
+                              }}
+                            />
+                          </td>
                         <td className="px-3 py-2.5 font-semibold text-[#eef2f7]">
                           <div className="min-w-0">
                             <span className="inline-flex max-w-full items-center gap-1.5">
@@ -200,7 +475,10 @@ export function CollectorManageSurface({
                           </div>
                         </td>
                         <td className="px-3 py-2.5">
-                          <span className={`rounded-[3px] border px-2 py-0.5 text-[11px] font-semibold leading-4 ${statusClassName(row.statusLabel)}`}>
+                          <span
+                            data-collector-status-tone={row.statusTone}
+                            className={`rounded-[3px] border px-2 py-0.5 text-[11px] font-semibold leading-4 ${statusClassName(row.statusTone)}`}
+                          >
                             {row.statusLabel}
                           </span>
                         </td>
@@ -223,8 +501,12 @@ export function CollectorManageSurface({
                                 variant="default"
                                 className={coldIconButtonClassName}
                                 title={t('collector.online')}
-                                onClick={() => onRowGoOnline(row.name)}
+                                onClick={() => {
+                                  setOpenRowActionMenuId(null);
+                                  onRowGoOnline(row.name);
+                                }}
                                 disabled={!row.canMutate}
+                                data-collector-online-one={row.name}
                               >
                                 <Power className="h-3.5 w-3.5" aria-hidden="true" />
                                 <span className="sr-only">{t('collector.online')}</span>
@@ -235,35 +517,322 @@ export function CollectorManageSurface({
                                 variant="default"
                                 className={coldIconButtonClassName}
                                 title={t('collector.offline')}
-                                onClick={() => onRowGoOffline(row.name)}
+                                onClick={() => {
+                                  setOpenRowActionMenuId(null);
+                                  onRowGoOffline(row.name);
+                                }}
                                 disabled={!row.canMutate}
+                                data-collector-offline-one={row.name}
                               >
                                 <PowerOff className="h-3.5 w-3.5" aria-hidden="true" />
                                 <span className="sr-only">{t('collector.offline')}</span>
                               </Button>
                             )}
-                            <Button
-                              size="icon"
-                              variant="default"
-                              className={coldIconButtonClassName}
-                              title={t('collector.delete')}
-                              onClick={() => onRowDelete(row.name)}
-                              disabled={!row.canMutate}
+                            <div
+                              className={collectorActionMenuRootClassName}
+                              data-collector-row-delete-menu={rowActionMenuId}
+                              data-collector-row-delete-menu-contract="angular-ellipsis-dropdown-delete"
+                              data-collector-row-delete-menu-layer="overlay-visible-above-panel"
+                              data-collector-row-delete-menu-clearance="floating-overlay-no-panel-crop"
+                              data-collector-row-delete-menu-open={isRowActionMenuOpen ? 'true' : 'false'}
                             >
-                              <Trash2 className="h-3.5 w-3.5" aria-hidden="true" />
-                              <span className="sr-only">{t('collector.delete')}</span>
-                            </Button>
+                              <button
+                                type="button"
+                                aria-expanded={isRowActionMenuOpen}
+                                aria-label={t('common.edit')}
+                                title={t('common.edit')}
+                                className={`${coldIconButtonClassName} inline-flex cursor-pointer list-none items-center justify-center [&::-webkit-details-marker]:hidden`}
+                                onClick={() => {
+                                  setOpenRowActionMenuId(current => (current === rowActionMenuId ? null : rowActionMenuId));
+                                }}
+                                data-collector-row-delete-menu-trigger={rowActionMenuId}
+                              >
+                                <MoreHorizontal className="h-3.5 w-3.5" aria-hidden="true" />
+                                <span className="sr-only">{t('common.edit')}</span>
+                              </button>
+                              <div
+                                role="menu"
+                                hidden={!isRowActionMenuOpen}
+                                className={`${collectorActionMenuPanelBaseClassName} right-0`}
+                                data-collector-row-delete-menu-panel={rowActionMenuId}
+                                data-collector-row-delete-menu-layer-panel="overlay-visible-above-panel"
+                                data-collector-row-delete-menu-clearance-panel="floating-overlay-no-panel-crop"
+                                data-collector-row-delete-menu-owner="hertzbeat-ui-table-row-action-button"
+                                data-collector-row-delete-menu-panel-open={isRowActionMenuOpen ? 'true' : 'false'}
+                              >
+                                <HzTableRowActionButton
+                                  width="root-span"
+                                  intent="ghost"
+                                  onClick={() => {
+                                    setOpenRowActionMenuId(null);
+                                    onRowDelete(row.name);
+                                  }}
+                                  disabled={!row.canMutate}
+                                  data-collector-delete-one={row.name}
+                                  data-collector-delete-one-owner="hertzbeat-ui-table-row-action-button"
+                                  className="w-full text-[#fecaca] hover:text-white"
+                                >
+                                  <Trash2 className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />
+                                  <span className="truncate">{t('collector.delete')}</span>
+                                </HzTableRowActionButton>
+                              </div>
+                            </div>
                           </div>
                         </td>
                       </tr>
-                    )) : <EmptyTableRow colSpan={10} />}
+                      );
+                    }) : <EmptyTableRow colSpan={11} t={t} />}
                   </tbody>
                 </table>
+                <div
+                  data-collector-pagination="cold-dense-pagination"
+                  data-collector-pagination-owner="hertzbeat-ui-pagination-bar"
+                  data-collector-pagination-contract="angular-search-pagination"
+                >
+                  <HzPaginationBar
+                    summary={paginationSummary}
+                    pageSizeLabel={t('collector.pagination.page-size')}
+                    pageSizeValue={String(currentPageSize)}
+                    pageSizeOptions={pageSizeOptions.map(value => ({ value: String(value), label: String(value) }))}
+                    pageJumpLabel={t('collector.pagination.page')}
+                    pageJumpValue={String(currentPage)}
+                    pageJumpMax={totalPages}
+                    previousLabel={t('common.previous-page')}
+                    nextLabel={t('common.next-page')}
+                    previousDisabled={currentPageIndex <= 0}
+                    nextDisabled={currentPage >= totalPages}
+                    onPrevious={() => onPageIndexChange?.(Math.max(currentPageIndex - 1, 0))}
+                    onNext={() => onPageIndexChange?.(Math.min(currentPageIndex + 1, totalPages - 1))}
+                    onPageSizeChange={value => onPageSizeChange?.(Number.parseInt(value, 10))}
+                    onPageJumpChange={handlePageJumpChange}
+                    pageJumpInputProps={
+                      {
+                        'data-collector-pagination-page-jump-owner': 'hertzbeat-ui-input'
+                      } as React.ComponentProps<typeof HzPaginationBar>['pageJumpInputProps']
+                    }
+                    pageSizeSelectProps={
+                      {
+                        'data-collector-pagination-page-size-owner': 'hertzbeat-ui-select'
+                      } as React.ComponentProps<typeof HzPaginationBar>['pageSizeSelectProps']
+                    }
+                    className="border-x-0"
+                  />
+                </div>
               </div>
             </section>
           </div>
         </div>
-      </section>
-    </div>
+        </section>
+      </div>
+      <HzConfirmDialog
+        open={Boolean(deleteTarget)}
+        tone="critical"
+        kicker={t('menu.advanced.collector')}
+        title={resolveCollectorConfirmTitle(deleteTarget, t)}
+        cancelLabel={t('common.button.cancel')}
+        confirmLabel={t('common.button.ok')}
+        confirmDisabled={mutationPending}
+        onCancel={onDeleteCancel ?? (() => {})}
+        onConfirm={onDeleteConfirm ?? (() => {})}
+        data-collector-delete-confirm="angular-modal-confirm"
+        data-collector-delete-confirm-owner="hertzbeat-ui-confirm-dialog"
+        data-collector-operate-confirm={targetAction === 'online' || targetAction === 'offline' ? 'angular-modal-confirm' : undefined}
+        data-collector-operate-confirm-owner="hertzbeat-ui-confirm-dialog"
+        confirmButtonProps={{
+          'data-collector-delete-confirm-submit': 'angular-modal-confirm',
+          'data-collector-operate-confirm-submit': targetAction === 'online' || targetAction === 'offline' ? 'angular-modal-confirm' : undefined
+        } as React.ComponentProps<typeof HzConfirmDialog>['confirmButtonProps']}
+      >
+        <div data-collector-delete-confirm-target={deleteTarget?.label ?? ''}>
+          {deleteTarget?.label ?? t('common.none')}
+        </div>
+      </HzConfirmDialog>
+      {deployOpen ? (
+        <div
+          className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-6"
+          data-collector-deploy-dialog="angular-generate-identity-modal"
+          data-collector-deploy-dialog-owner="collector-manage-surface"
+          data-collector-deploy-mask-closable="angular-mask-closable-false"
+          data-collector-deploy-mask-closable-owner="angular-nz-modal"
+          data-collector-deploy-close-reset="angular-close-clears-name"
+          data-collector-deploy-close-reset-owner="collector-route-controller"
+          data-collector-deploy-close-pending-result="angular-close-ignores-late-generate"
+          data-collector-deploy-close-pending-result-owner="collector-route-controller"
+        >
+          <section
+            className="grid max-h-[88vh] w-[min(92vw,920px)] overflow-hidden rounded-[4px] border border-[#2b3039] bg-[#0b0c0e] shadow-[0_28px_70px_rgba(0,0,0,0.48)] md:w-[45vw] md:max-w-[45vw]"
+            data-collector-deploy-width="angular-width-45-percent"
+            data-collector-deploy-width-owner="angular-nz-modal"
+          >
+            <header className="grid min-h-[56px] grid-cols-[minmax(0,1fr)_auto] items-center gap-3 border-b border-[#252b34] bg-[#101217] px-5">
+              <div className="min-w-0">
+                <div className="text-[11px] font-semibold uppercase tracking-[0.12em] text-[#7e8494]">{t('menu.advanced.collector')}</div>
+                <h2 className="truncate text-[18px] font-semibold text-[#eef2f7]">{t('collector.deploy')}</h2>
+              </div>
+              <button
+                type="button"
+                className="inline-flex h-8 w-8 items-center justify-center rounded-[3px] border border-[#2b3039] bg-[#101217] text-[#dbe4f0] hover:border-[#4e74f8] hover:bg-[#151b28]"
+                onClick={onDeployClose}
+                data-collector-deploy-close="angular-modal-cancel"
+                data-collector-deploy-close-reset="angular-close-clears-name"
+                data-collector-deploy-close-reset-owner="collector-route-controller"
+                aria-label={t('collector.deploy.close')}
+              >
+                <X className="h-4 w-4" aria-hidden="true" />
+              </button>
+            </header>
+            <div className="min-h-0 overflow-y-auto p-5">
+              <div
+                className="grid gap-2 md:grid-cols-[7fr_12fr] md:items-start"
+                data-collector-deploy-field="name"
+                data-collector-deploy-field-layout="angular-label-7-control-12"
+              >
+                <label
+                  htmlFor="collector-deploy-name"
+                  className="pt-2 text-[12px] font-semibold text-[#dbe4f0] md:text-right"
+                  data-collector-deploy-label-span="7"
+                >
+                  {t('collector.name')} <span className="text-[#f97316]">*</span>
+                </label>
+                <div className="min-w-0" data-collector-deploy-control-span="12">
+                  <input
+                    id="collector-deploy-name"
+                    value={deployName ?? ''}
+                    disabled={Boolean(resolvedDeployIdentity)}
+                    onChange={event => onDeployNameChange?.(event.target.value)}
+                    placeholder={t('collector.name.placeholder')}
+                    className="h-8 w-full rounded-[3px] border border-[#2b3039] bg-[#101217] px-3 text-[12px] text-[#dbe4f0] outline-none transition placeholder:text-[#6f7788] focus:border-[#4e74f8] focus:ring-2 focus:ring-[#4e74f8]/10 disabled:opacity-60"
+                    data-collector-deploy-name-input="angular-required-name"
+                    data-collector-deploy-name-validation-raw="angular-required-before-trim"
+                    data-collector-deploy-name-validation-raw-owner="collector-route-controller"
+                    data-collector-deploy-name-validation-state={showDeployNameValidation ? 'dirty-invalid' : 'pristine'}
+                    data-collector-deploy-name-lock={resolvedDeployIdentity ? 'angular-disable-after-identity' : 'editable-before-identity'}
+                    data-collector-deploy-name-lock-owner="angular-ngmodel-input"
+                    data-collector-deploy-name-disabled={resolvedDeployIdentity ? 'true' : 'false'}
+                  />
+                  {showDeployNameValidation ? (
+                    <div
+                      className="mt-1 text-[11px] font-medium text-[#fca5a5]"
+                      data-collector-deploy-name-validation="validation.required"
+                      data-collector-deploy-name-validation-contract="angular-submit-marks-required"
+                    >
+                      {t('validation.required')}
+                    </div>
+                  ) : null}
+                </div>
+              </div>
+              {deployError ? (
+                <div className="mt-4">
+                  <HzInlineFeedback
+                    tone="critical"
+                    title={t('common.notify.apply-fail')}
+                    meta={deployError}
+                    variant="embedded"
+                    data-collector-deploy-feedback="angular-apply-fail-notification"
+                    data-collector-deploy-feedback-owner="hertzbeat-ui-inline-feedback"
+                    data-collector-deploy-feedback-title="common.notify.apply-fail"
+                    data-collector-deploy-feedback-detail="backend-message"
+                  />
+                </div>
+              ) : null}
+              {deployCopyMessage ? (
+                <div className="mt-4">
+                  <HzInlineFeedback
+                    tone="success"
+                    title={deployCopyMessage}
+                    variant="embedded"
+                    data-collector-deploy-copy-feedback="angular-copy-success"
+                    data-collector-deploy-copy-feedback-owner="hertzbeat-ui-inline-feedback"
+                    data-collector-deploy-copy-feedback-duration-ms="800"
+                  />
+                </div>
+              ) : null}
+              {resolvedDeployIdentity ? (
+                <div className="mt-5 grid gap-4" data-collector-deploy-result="angular-identity-shells">
+                  <div className="grid gap-2 text-center">
+                    <div className="text-[14px] font-semibold text-[#eef2f7]">{t('collector.deploy.identity')}</div>
+                    <div className="text-[12px] text-[#858d9a]">{t('collector.deploy.identity.tip')}</div>
+                    <button
+                      type="button"
+                      className="mx-auto inline-flex max-w-full items-center gap-2 rounded-[3px] border border-[#553d1f] bg-[#241711] px-3 py-2 font-mono text-[12px] font-semibold text-[#fed7aa] hover:border-[#f97316]"
+                      onClick={() => onDeployCopy?.(resolvedDeployIdentity)}
+                      data-collector-deploy-copy-identity="angular-copy-identity"
+                      title={t('common.button.copy.tip')}
+                    >
+                      <span className="truncate">{resolvedDeployIdentity}</span>
+                      <Copy className="h-3.5 w-3.5" aria-hidden="true" />
+                      <span>{t('common.copy.button')}</span>
+                    </button>
+                  </div>
+                  <div className="grid gap-4 lg:grid-cols-2">
+                    <HzCodeEditor
+                      readOnly
+                      language="shell"
+                      value={deployDockerShell ?? ''}
+                      title={t('collector.deploy.docker')}
+                      meta={deployHost ?? 'MANAGER_HOST'}
+                      minHeight="240px"
+                      data-collector-deploy-docker-shell="angular-docker-shell"
+                      data-collector-deploy-code-owner="hertzbeat-ui-code-editor"
+                    />
+                    <HzCodeEditor
+                      readOnly
+                      language="yaml"
+                      value={deployPackageShell ?? ''}
+                      title={t('collector.deploy.package')}
+                      meta={
+                        <a
+                          href="https://github.com/apache/hertzbeat/releases"
+                          target="_blank"
+                          rel="noreferrer"
+                          data-collector-deploy-package-link="angular-github-releases-link"
+                          data-collector-deploy-package-link-owner="angular-nz-modal"
+                        >
+                          {t('collector.deploy.package.github')}
+                        </a>
+                      }
+                      minHeight="260px"
+                      data-collector-deploy-package-shell="angular-package-shell"
+                      data-collector-deploy-code-owner="hertzbeat-ui-code-editor"
+                    />
+                  </div>
+                </div>
+              ) : null}
+            </div>
+            <footer className="flex justify-end gap-2 border-t border-[#252b34] bg-[#101217] px-5 py-3">
+              {resolvedDeployIdentity ? (
+                <HzButton
+                  size="md"
+                  intent="danger"
+                  onClick={onDeployClose}
+                  data-collector-deploy-result-close="angular-close-saved"
+                  data-collector-deploy-result-close-contract="angular-danger-close-after-identity"
+                  data-collector-deploy-result-close-owner="hertzbeat-ui-button"
+                  data-collector-deploy-result-close-reset="angular-close-clears-name"
+                  data-collector-deploy-result-close-reset-owner="collector-route-controller"
+                >
+                  {t('collector.deploy.close')}
+                </HzButton>
+              ) : (
+                <Button
+                  size="sm"
+                  variant="default"
+                  className={coldPrimaryButtonClassName}
+                  onClick={onDeployGenerate}
+                  disabled={Boolean(isDeployPending || resolvedDeployIdentity)}
+                  data-collector-deploy-generate="angular-generate-identity"
+                  data-collector-deploy-generate-validation="angular-click-before-required"
+                  data-collector-deploy-ok-loading={isDeployPending ? 'true' : 'false'}
+                  data-collector-deploy-ok-loading-owner="angular-nz-ok-loading"
+                  aria-busy={isDeployPending ? 'true' : 'false'}
+                >
+                  {isDeployPending ? t('common.loading') : t('collector.deploy.ok')}
+                </Button>
+              )}
+            </footer>
+          </section>
+        </div>
+      ) : null}
+    </>
   );
 }
diff --git a/web-next/components/pages/label-manage-surface.test.tsx b/web-next/components/pages/label-manage-surface.test.tsx
index 4a5b00d029..8665493263 100644
--- a/web-next/components/pages/label-manage-surface.test.tsx
+++ b/web-next/components/pages/label-manage-surface.test.tsx
@@ -4,10 +4,10 @@ import { resolve } from 'node:path';
 import { renderToStaticMarkup } from 'react-dom/server';
 import { describe, expect, it } from 'vitest';
 import { createTranslatorMock } from '../../test/i18n-test-helper';
+import { LabelManageSurface } from './label-manage-surface';
 
 describe('label manage surface', () => {
-  it('renders the OTLP cold-matte label console, dense table, and authoring dialog shell', async () => {
-    const { LabelManageSurface } = await import('./label-manage-surface');
+  it('renders the OTLP cold-matte label console, Angular card grid, and authoring dialog shell', () => {
     const t = createTranslatorMock({
       locale: 'zh-CN',
       overrides: {
@@ -15,11 +15,13 @@ describe('label manage surface', () => {
         'common.refresh': '刷新',
         'common.button.new': '新增',
         'common.search': '搜索',
+        'common.clear': '清除',
         'common.copy.button': '复制',
         'common.button.edit': '编辑',
         'common.button.delete': '删除选中项',
         'common.button.cancel': '取消',
         'common.button.save': '保存',
+        'common.none': '无标签预览',
         'label.search': '搜索标签',
         'label.new': '新增标签',
         'label.edit': '编辑标签',
@@ -50,9 +52,17 @@ describe('label manage surface', () => {
                 id: 2,
                 name: 'source',
                 tagValue: 'system',
-                description: 'preset label',
+                description: '',
                 type: 2,
                 gmtUpdate: '2026-04-09T10:00:00Z'
+              },
+              {
+                id: 3,
+                name: 'legacy',
+                tagValue: 'custom',
+                description: 'unknown type',
+                type: 99,
+                gmtUpdate: '2026-04-08T10:00:00Z'
               }
             ],
             totalElements: 2,
@@ -69,14 +79,21 @@ describe('label manage surface', () => {
           type: 1
         }}
         isManageModalAdd
+        deleteTarget={null}
+        isDeletePending={false}
+        isSavePending
+        actionMessage="新增成功"
         formatTime={() => '2026-04-10 18:00:00'}
         onSearchChange={() => {}}
         onSearch={() => {}}
+        onSearchClear={() => {}}
         onRefresh={() => {}}
         onNew={() => {}}
         onCopy={() => {}}
         onEdit={() => {}}
-        onDelete={() => {}}
+        onDeleteRequest={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
         onDraftChange={() => {}}
         onCloseDialog={() => {}}
         onSaveDialog={() => {}}
@@ -88,32 +105,144 @@ describe('label manage surface', () => {
     expect(html).toContain('data-label-header="cold-compact-header"');
     expect(html).toContain('data-label-command-row="standard-equal-buttons"');
     expect(html).toContain('data-label-admin-layout="full-width-admin-list"');
+    expect(html).toContain('data-label-save-feedback-contract="angular-new-edit-notify"');
+    expect(html).toContain('data-label-save-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-label-save-failure-contract="angular-new-edit-fail-notification"');
+    expect(html).toContain('data-label-save-failure-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-label-load-failure-contract="angular-console-only-shell"');
+    expect(html).toContain('data-label-load-failure-owner="label-route-controller"');
+    expect(html).toContain('data-label-load-failure="none"');
+    expect(html).toContain('data-label-copy-feedback-contract="angular-copy-notify"');
+    expect(html).toContain('data-label-copy-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-label-delete-confirm-contract="angular-modal-confirm"');
+    expect(html).toContain('data-label-delete-confirm-contract-owner="hertzbeat-ui-confirm-dialog"');
+    expect(html).toContain('data-label-delete-confirm-state-contract="angular-modal-confirm-state"');
+    expect(html).toContain('data-label-delete-confirm-state-contract-owner="hertzbeat-ui-confirm-dialog"');
+    expect(html).toContain('data-label-delete-confirm-state="closed"');
+    expect(html).toContain('data-label-delete-confirm-state-owner="hertzbeat-ui-confirm-dialog"');
+    expect(html).toContain('data-label-delete-confirm-pending="false"');
+    expect(html).toContain('data-label-delete-feedback-contract="angular-delete-notify"');
+    expect(html).toContain('data-label-delete-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-label-delete-failure-contract="angular-delete-fail-notification"');
+    expect(html).toContain('data-label-delete-failure-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-label-delete-query-contract="angular-repeated-ids-query"');
+    expect(html).toContain('data-label-delete-query-owner="route-mutation-contract"');
+    expect(html).toContain('data-label-card-loading-contract="angular-table-loading"');
+    expect(html).toContain('data-label-card-loading-owner="label-route-controller"');
+    expect(html).toContain('data-label-card-loading="false"');
+    expect(html).toContain('data-label-save-loading-contract="angular-nz-ok-loading"');
+    expect(html).toContain('data-label-save-loading-contract-owner="angular-nz-ok-loading"');
+    expect(html).toContain('data-label-dialog-mask-closable-contract="angular-mask-closable-false"');
+    expect(html).toContain('data-label-dialog-mask-closable-owner="angular-nz-modal"');
+    expect(html).toContain('data-label-dialog-width-contract="angular-width-30-percent"');
+    expect(html).toContain('data-label-dialog-width-owner="angular-nz-modal"');
+    expect(html).toContain('data-label-dialog-field-layout-contract="angular-label-7-control-12"');
+    expect(html).toContain('data-label-dialog-field-layout-owner="route-form-field-grid"');
+    expect(html).toContain('data-label-dialog-preview-visibility-contract="angular-name-defined-preview"');
+    expect(html).toContain('data-label-dialog-preview-visibility-owner="route-form-state"');
+    expect(html).toContain('data-label-dialog-preview-frame-contract="angular-inline-tag-no-extra-frame"');
+    expect(html).toContain('data-label-dialog-preview-frame-owner="route-form-field-grid"');
+    expect(html).toContain('data-label-dialog-preview-chrome-contract="angular-tag-only-no-extra-frame"');
+    expect(html).toContain('data-label-dialog-preview-chrome-owner="hertzbeat-ui-label-tag"');
+    expect(html).toContain('data-label-edit-reference-contract="angular-edit-direct-reference"');
+    expect(html).toContain('data-label-edit-reference-owner="route-form-state"');
+    expect(html).toContain('data-label-name-validation-contract="angular-required-before-submit"');
+    expect(html).toContain('data-label-name-validation-trigger-contract="angular-ok-marks-dirty"');
+    expect(html).toContain('data-label-name-validation-trigger-owner="route-form-contract"');
+    expect(html).toContain('data-label-name-validation-raw-contract="angular-required-before-trim"');
+    expect(html).toContain('data-label-name-validation-raw-owner="route-form-contract"');
+    expect(html).toContain('data-label-query-contract="angular-load-all-labels"');
+    expect(html).toContain('data-label-query-owner="label-query-state"');
+    expect(html).toContain('data-label-query-param-order-contract="angular-page-index-size-type-search"');
+    expect(html).toContain('data-label-query-param-order-owner="label-query-state"');
+    expect(html).toContain('data-label-card-grid-contract="angular-card-grid"');
+    expect(html).toContain('data-label-card-grid-owner="hertzbeat-ui-label-tag"');
+    expect(html).toContain('aria-busy="false"');
+    expect(html).toContain('data-label-monitor-handoff-contract="angular-routerlink-monitors-labels"');
+    expect(html).toContain('data-label-monitor-handoff-owner="next-monitor-query-link"');
+    expect(html).toContain('data-hz-ui="label-tag"');
+    expect(html).toContain('data-hz-label-tag-owner="hertzbeat-ui-label-tag"');
+    expect(html).toContain('data-hz-label-color-token="geekblue"');
+    expect(html).toContain('data-label-color-token="geekblue"');
+    expect(html).toContain('data-label-color-owner="hertzbeat-ui-label-tag"');
+    expect(html).toContain('data-label-dialog-preview-color="angular-render-label-color"');
+    expect(html).toContain('data-label-dialog-preview-chrome="angular-nz-tag-inline"');
+    expect(html).toContain('data-label-dialog-preview-chrome-mode="angular-tag-only-no-extra-frame"');
+    expect(html).toContain('data-overlay-dialog-mask-closable="false"');
+    expect(html).toContain('data-label-dialog-mask-closable="false"');
+    expect(html).toContain('data-label-dialog-width="angular-width-30-percent"');
+    expect(html).toContain('data-label-dialog-field-layout="angular-label-7-control-12"');
+    expect(html).toContain('data-label-dialog-field-layout-owner="route-form-field-grid"');
+    expect(html).toContain('data-label-dialog-field-layout-row="angular-label-7-control-12"');
+    expect(html).toContain('data-label-dialog-label-span="7"');
+    expect(html).toContain('data-label-dialog-control-span="12"');
+    expect(html).toContain('sm:grid-cols-[minmax(96px,7fr)_minmax(0,12fr)]');
+    expect(html).toContain('lg:max-w-[30vw]');
+    expect(html).toContain('data-label-save-ok-loading="true"');
+    expect(html).toContain('data-label-save-ok-loading-owner="angular-nz-ok-loading"');
+    expect(html).toContain('aria-busy="true"');
     expect(html).toContain('data-label-toolbar="cold-table-toolbar"');
     expect(html).toContain('data-label-search-owner="shared-search-row"');
+    expect(html).toContain('data-label-search-submit="angular-enter-and-clear"');
+    expect(html).toContain('data-label-search-submit-owner="cold-search-row"');
+    expect(html).toContain('data-label-action-feedback="angular-action-success"');
+    expect(html).toContain('data-label-action-feedback-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-label-save-feedback="angular-save-success"');
+    expect(html).toContain('data-label-save-feedback-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-label-save-trim-contract="angular-trim-before-save"');
+    expect(html).toContain('data-label-save-trim-owner="label-manage-controller"');
+    expect(html).toContain('data-label-optional-save-payload-contract="angular-preserve-undefined-optional-fields"');
+    expect(html).toContain('data-label-optional-save-payload-owner="label-manage-controller"');
+    expect(html).toContain('data-label-type-save-payload-contract="angular-preserve-implicit-type"');
+    expect(html).toContain('data-label-type-save-payload-owner="label-manage-controller"');
+    expect(html).toContain('data-label-display-format-contract="angular-format-label-name-raw-tag-value"');
+    expect(html).toContain('data-label-display-format-owner="label-view-model"');
+    expect(html).toContain('data-label-description-display-contract="angular-truthy-description-raw"');
+    expect(html).toContain('data-label-description-display-owner="label-manage-surface"');
+    expect(html).toContain('data-hz-ui="inline-feedback"');
+    expect(html).toContain('data-hz-feedback-tone="success"');
+    expect(html).toContain('新增成功');
     expect(html).toContain('data-cold-search-row-owner="cold-search-row"');
     expect(html).toContain('data-cold-search-input="fixed-width-direct"');
     expect(html).toContain('data-cold-search-control="direct-input"');
     expect(html).toContain('data-cold-search-chrome="no-extra-input-shell"');
     expect(html).toContain('data-cold-search-action="submit"');
-    expect(html).toContain('data-label-table-shell="cold-dense-table"');
-    expect(html).toContain('data-label-table="cold-label-table"');
+    expect(html).toContain('data-cold-search-action="clear"');
+    expect(html).toContain('data-label-card-grid="angular-card-grid"');
+    expect(html).toContain('data-label-card-grid-owner="hertzbeat-ui-label-tag"');
+    expect(html).toContain('data-label-card-column-contract="nz-xs-12-sm-8-md-6-lg-4"');
     expect(html).toContain('data-label-row="1"');
-    expect(html).toContain('data-label-row-actions="cold-icon-actions"');
+    expect(html).toContain('data-label-card-shell="angular-card"');
+    expect(html).toContain('data-label-card-size="small"');
+    expect(html).toContain('data-label-card-content="angular-label-content"');
+    expect(html).toContain('data-label-card-description="angular-description"');
+    expect(html).toContain('data-label-card-description-display="angular-truthy-description-raw"');
+    expect(html).toContain('data-label-card-description-display-owner="label-manage-surface"');
+    expect(html).toContain('data-label-card-actions="angular-card-actions"');
+    expect(html).toContain('data-label-monitor-handoff="angular-routerlink-monitors-labels"');
+    expect(html).toContain('data-label-monitor-handoff-query="team:ops"');
+    expect(html).toContain('data-label-monitor-handoff-owner="next-monitor-query-link"');
+    expect(html).toContain('data-label-row-actions="angular-card-actions-contextual"');
+    expect(html).toContain('data-label-row-actions-owner="hertzbeat-ui-action-group"');
+    expect(html).toContain('data-label-row-action="copy"');
+    expect(html).toContain('data-label-row-action="edit"');
+    expect(html).toContain('data-label-row-action="delete"');
+    expect(html).toContain('data-label-row-action-owner="row-contextual-icon-button"');
+    expect(html).toContain('data-label-row-action-label="team:ops"');
     expect(html).not.toContain('data-label-summary-rail=');
     expect(html).not.toContain('data-cold-search-input-shell');
     expect(html).toContain('标签管理');
     expect(html).toContain('新增');
     expect(html).toContain('搜索标签');
     expect(html).toContain('搜索');
-    expect(html).toContain('aria-label="复制"');
-    expect(html).toContain('aria-label="编辑"');
-    expect(html).toContain('aria-label="删除选中项"');
+    expect(html).toContain('aria-label="复制标签 team:ops"');
+    expect(html).toContain('aria-label="编辑标签 team:ops"');
+    expect(html).toContain('aria-label="删除标签 team:ops"');
+    expect(html).toContain('href="/monitors?labels=team%3Aops"');
     expect(html).toContain('team:ops');
     expect(html).toContain('source:system');
     expect(html).toContain('ops team');
-    expect(html).toContain('preset label');
-    expect(html).toContain('用户标签');
-    expect(html).toContain('预置标签');
+    expect(html).not.toContain('用户标签');
     expect(html).toContain('data-overlay-dialog="true"');
     expect(html).toContain('新增标签');
     expect(html).toContain('标签名');
@@ -122,13 +251,11 @@ describe('label manage surface', () => {
     expect(html).toContain('效果');
     expect(html).toContain('取消');
     expect(html).toContain('保存');
-    expect(html).not.toContain('data-label-manage-route="angular-label-cards"');
-    expect(html).not.toContain('data-label-card-shell="angular-card"');
+    expect(html).not.toContain('data-label-table="cold-label-table"');
     expect(html).not.toContain('setting.labels.subtitle');
-  });
+  }, 30000);
 
-  it('renders a cold table empty state with Chinese fallback copy when no labels exist', async () => {
-    const { LabelManageSurface } = await import('./label-manage-surface');
+  it('renders a cold card empty state with Chinese fallback copy when no labels exist', () => {
     const t = createTranslatorMock({
       locale: 'zh-CN',
       overrides: {
@@ -136,6 +263,7 @@ describe('label manage surface', () => {
         'common.refresh': '刷新',
         'common.button.new': '新增',
         'common.search': '搜索',
+        'common.clear': '清除',
         'label.search': '搜索标签'
       }
     });
@@ -154,21 +282,26 @@ describe('label manage surface', () => {
         search=""
         draftLabel={null}
         isManageModalAdd={false}
+        deleteTarget={null}
+        isDeletePending={false}
         formatTime={() => '-'}
         onSearchChange={() => {}}
         onSearch={() => {}}
+        onSearchClear={() => {}}
         onRefresh={() => {}}
         onNew={() => {}}
         onCopy={() => {}}
         onEdit={() => {}}
-        onDelete={() => {}}
+        onDeleteRequest={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
         onDraftChange={() => {}}
         onCloseDialog={() => {}}
         onSaveDialog={() => {}}
       />
     );
 
-    expect(html).toContain('data-label-empty-state="cold-table-empty"');
+    expect(html).toContain('data-label-empty-state="cold-card-empty"');
     expect(html).toContain('data-label-empty-icon="cold-empty-box"');
     expect(html).toContain('暂无标签');
     expect(html).toContain('请先新增标签。');
@@ -176,22 +309,724 @@ describe('label manage surface', () => {
     expect(html).not.toContain('setting.labels.empty.copy');
   });
 
+  it('keeps Angular formatLabelName raw tag value spacing for display, copy, and monitor handoff', () => {
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <LabelManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [
+              {
+                id: 9,
+                name: 'team',
+                tagValue: ' ops ',
+                description: '',
+                type: 1
+              }
+            ],
+            totalElements: 1,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        draftLabel={null}
+        isManageModalAdd={false}
+        deleteTarget={null}
+        isDeletePending={false}
+        formatTime={() => '-'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onNew={() => {}}
+        onCopy={() => {}}
+        onEdit={() => {}}
+        onDeleteRequest={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onDraftChange={() => {}}
+        onCloseDialog={() => {}}
+        onSaveDialog={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-label-display-format-contract="angular-format-label-name-raw-tag-value"');
+    expect(html).toContain('data-label-monitor-handoff-query="team: ops "');
+    expect(html).toContain('data-label-row-action-label="team: ops "');
+    expect(html).toContain('href="/monitors?labels=team%3A%20ops%20"');
+    expect(html).toContain('team: ops ');
+    expect(html).not.toContain('team:ops');
+  });
+
+  it('renders whitespace-only descriptions because Angular only checks description truthiness', () => {
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <LabelManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [
+              {
+                id: 10,
+                name: 'team',
+                tagValue: 'ops',
+                description: '   ',
+                type: 1
+              }
+            ],
+            totalElements: 1,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        draftLabel={null}
+        isManageModalAdd={false}
+        deleteTarget={null}
+        isDeletePending={false}
+        formatTime={() => '-'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onNew={() => {}}
+        onCopy={() => {}}
+        onEdit={() => {}}
+        onDeleteRequest={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onDraftChange={() => {}}
+        onCloseDialog={() => {}}
+        onSaveDialog={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-label-description-display-contract="angular-truthy-description-raw"');
+    expect(html).toContain('data-label-description-display-owner="label-manage-surface"');
+    expect(html).toContain('data-label-card-description="angular-description"');
+    expect(html).toContain('data-label-card-description-display="angular-truthy-description-raw"');
+    expect(html).toContain('data-label-card-description-display-owner="label-manage-surface"');
+    expect(html).toContain('title="   "');
+  });
+
+  it('keeps the new-label dialog preview hidden until the Angular name-defined path runs', () => {
+    const t = createTranslatorMock({
+      locale: 'zh-CN',
+      overrides: {
+        'menu.advanced.labels': '标签管理',
+        'common.refresh': '刷新',
+        'common.button.new': '新增',
+        'common.search': '搜索',
+        'common.button.cancel': '取消',
+        'common.button.save': '保存',
+        'common.none': '无标签预览',
+        'label.search': '搜索标签',
+        'label.new': '新增标签',
+        'label.edit': '编辑标签',
+        'label.name': '标签名',
+        'label.value': '标签值',
+        'label.description': '标签描述',
+        'label.display': '效果',
+        'setting.labels.empty.title': '暂无标签',
+        'setting.labels.empty.copy': '请先新增标签。'
+      }
+    });
+
+    const html = renderToStaticMarkup(
+      <LabelManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [],
+            totalElements: 0,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        draftLabel={{
+          id: 0,
+          name: undefined as unknown as string,
+          tagValue: undefined,
+          description: undefined,
+          type: 1
+        }}
+        isManageModalAdd
+        deleteTarget={null}
+        isDeletePending={false}
+        formatTime={() => '-'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onNew={() => {}}
+        onCopy={() => {}}
+        onEdit={() => {}}
+        onDeleteRequest={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onDraftChange={() => {}}
+        onCloseDialog={() => {}}
+        onSaveDialog={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-label-dialog-preview-visibility="hidden"');
+    expect(html).toContain('data-label-dialog-preview-visibility-owner="route-form-state"');
+    expect(html).not.toContain('data-label-dialog-preview="cold-preview"');
+    expect(html).toContain('data-label-dialog-name-input="angular-required-name"');
+    expect(html).toContain('data-label-dialog-name-state="pristine"');
+    expect(html).toContain('data-label-save-submit-state="pristine"');
+    expect(html).not.toContain('data-label-dialog-name-validation="validation.required"');
+    expect(html).toContain('data-label-save-submit="angular-required-before-submit"');
+    expect(html).toContain('data-label-name-validation-trigger="angular-ok-marks-dirty"');
+    expect(html).toContain('data-label-name-validation-trigger-owner="route-form-contract"');
+    expect(html).not.toContain('disabled=""');
+    expect(html).toContain('data-label-save-trim="angular-trim-before-save"');
+    expect(html).toContain('data-label-save-trim-owner="label-manage-controller"');
+    expect(html).toContain('data-label-optional-save-payload-contract="angular-preserve-undefined-optional-fields"');
+    expect(html).toContain('data-label-optional-save-payload-owner="label-manage-controller"');
+    expect(html).toContain('data-label-type-save-payload-contract="angular-preserve-implicit-type"');
+    expect(html).toContain('data-label-type-save-payload-owner="label-manage-controller"');
+    expect(html).not.toContain('无标签预览');
+  });
+
+  it('shows the Angular display preview row once the label name is defined', () => {
+    const t = createTranslatorMock({
+      locale: 'zh-CN',
+      overrides: {
+        'menu.advanced.labels': '标签管理',
+        'common.refresh': '刷新',
+        'common.button.new': '新增',
+        'common.search': '搜索',
+        'common.button.cancel': '取消',
+        'common.button.save': '保存',
+        'common.none': '无标签预览',
+        'label.search': '搜索标签',
+        'label.new': '新增标签',
+        'label.edit': '编辑标签',
+        'label.name': '标签名',
+        'label.value': '标签值',
+        'label.description': '标签描述',
+        'label.display': '效果',
+        'setting.labels.empty.title': '暂无标签',
+        'setting.labels.empty.copy': '请先新增标签。'
+      }
+    });
+
+    const html = renderToStaticMarkup(
+      <LabelManageSurface
+        t={t}
+        data={{ list: { content: [], totalElements: 0, pageIndex: 0, pageSize: 8 } }}
+        search=""
+        draftLabel={{ id: 0, name: 'team', tagValue: '', description: '', type: 1 }}
+        isManageModalAdd
+        deleteTarget={null}
+        isDeletePending={false}
+        formatTime={() => '-'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onNew={() => {}}
+        onCopy={() => {}}
+        onEdit={() => {}}
+        onDeleteRequest={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onDraftChange={() => {}}
+        onCloseDialog={() => {}}
+        onSaveDialog={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-label-dialog-preview="cold-preview"');
+    expect(html).toContain('data-label-dialog-preview-visibility="visible"');
+    expect(html).toContain('data-label-dialog-preview-frame="angular-inline-tag-no-extra-frame"');
+    expect(html).toContain('data-label-dialog-preview-frame-owner="route-form-field-grid"');
+    expect(html).toContain('data-label-dialog-preview-chrome="angular-nz-tag-inline"');
+    expect(html).toContain('data-label-dialog-preview-chrome-mode="angular-tag-only-no-extra-frame"');
+    expect(html).toContain('data-label-dialog-preview-chrome-owner="hertzbeat-ui-label-tag"');
+    expect(html).toContain('data-label-dialog-field="display"');
+    expect(html).toContain('data-label-dialog-label-span="7"');
+    expect(html).toContain('data-label-dialog-control-span="12"');
+    expect(html).not.toContain('data-label-dialog-preview=&quot;cold-preview&quot; data-label-dialog-preview-visibility=&quot;visible&quot; data-label-dialog-preview-visibility-owner=&quot;route-form-state&quot; data-label-dialog-field=&quot;display&quot; data-label-dialog-field-layout-row=&quot;angular-label-7-control-12&quot; class=&quot;grid gap-2 text-[12px] font-semibold text-[#a9b0bb] sm:grid-cols-[minmax(96px,7fr)_minmax(0,12fr)] sm:items-start rounded-[4px] border');
+    expect(html).toContain('team');
+  });
+
+  it('marks the missing label name dirty only after the Angular OK-submit validation path runs', () => {
+    const t = createTranslatorMock({
+      locale: 'zh-CN',
+      overrides: {
+        'menu.advanced.labels': '标签管理',
+        'common.refresh': '刷新',
+        'common.button.new': '新增',
+        'common.search': '搜索',
+        'common.button.cancel': '取消',
+        'common.button.save': '保存',
+        'common.none': '无标签预览',
+        'validation.required': '必填项',
+        'label.search': '搜索标签',
+        'label.new': '新增标签',
+        'label.name': '标签名',
+        'label.value': '标签值',
+        'label.description': '标签描述',
+        'label.display': '效果',
+        'setting.labels.empty.title': '暂无标签',
+        'setting.labels.empty.copy': '请先新增标签。'
+      }
+    });
+
+    const html = renderToStaticMarkup(
+      <LabelManageSurface
+        t={t}
+        data={{ list: { content: [], totalElements: 0, pageIndex: 0, pageSize: 8 } }}
+        search=""
+        draftLabel={{ id: 0, name: '', tagValue: '', description: '', type: 1 }}
+        isManageModalAdd
+        isNameValidationVisible
+        deleteTarget={null}
+        isDeletePending={false}
+        formatTime={() => '-'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onNew={() => {}}
+        onCopy={() => {}}
+        onEdit={() => {}}
+        onDeleteRequest={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onDraftChange={() => {}}
+        onCloseDialog={() => {}}
+        onSaveDialog={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-label-dialog-name-state="dirty-invalid"');
+    expect(html).toContain('data-label-save-submit-state="dirty-invalid"');
+    expect(html).toContain('data-label-dialog-name-validation="validation.required"');
+    expect(html).toContain('必填项');
+    expect(html).not.toContain('disabled=""');
+  });
+
+  it('does not mark a whitespace-only label name invalid before Angular trim-before-save runs', () => {
+    const t = createTranslatorMock({
+      locale: 'zh-CN',
+      overrides: {
+        'menu.advanced.labels': '标签管理',
+        'common.refresh': '刷新',
+        'common.button.new': '新增',
+        'common.search': '搜索',
+        'common.button.cancel': '取消',
+        'common.button.save': '保存',
+        'common.none': '无标签预览',
+        'validation.required': '必填项',
+        'label.search': '搜索标签',
+        'label.new': '新增标签',
+        'label.name': '标签名',
+        'label.value': '标签值',
+        'label.description': '标签描述',
+        'label.display': '效果',
+        'setting.labels.empty.title': '暂无标签',
+        'setting.labels.empty.copy': '请先新增标签。'
+      }
+    });
+
+    const html = renderToStaticMarkup(
+      <LabelManageSurface
+        t={t}
+        data={{ list: { content: [], totalElements: 0, pageIndex: 0, pageSize: 8 } }}
+        search=""
+        draftLabel={{ id: 0, name: '   ', tagValue: undefined, description: undefined, type: 1 } as any}
+        isManageModalAdd
+        isNameValidationVisible
+        deleteTarget={null}
+        isDeletePending={false}
+        formatTime={() => '-'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onNew={() => {}}
+        onCopy={() => {}}
+        onEdit={() => {}}
+        onDeleteRequest={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onDraftChange={() => {}}
+        onCloseDialog={() => {}}
+        onSaveDialog={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-label-name-validation-raw-contract="angular-required-before-trim"');
+    expect(html).toContain('data-label-name-validation-raw-owner="route-form-contract"');
+    expect(html).toContain('data-label-dialog-name-state="pristine"');
+    expect(html).toContain('data-label-save-submit-state="pristine"');
+    expect(html).not.toContain('data-label-dialog-name-validation="validation.required"');
+    expect(html).not.toContain('必填项');
+  });
+
+  it('renders the shared Angular-style delete confirmation dialog when a label is targeted', () => {
+    const t = createTranslatorMock({
+      locale: 'zh-CN',
+      overrides: {
+        'menu.advanced.labels': '标签管理',
+        'common.confirm.delete': '请确认是否删除!',
+        'common.button.cancel': '取消',
+        'common.button.ok': '确定',
+        'common.refresh': '刷新',
+        'common.button.new': '新增',
+        'common.search': '搜索',
+        'label.search': '搜索标签',
+        'setting.labels.empty.title': '暂无标签',
+        'setting.labels.empty.copy': '请先新增标签。'
+      }
+    });
+
+    const html = renderToStaticMarkup(
+      <LabelManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [],
+            totalElements: 0,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        draftLabel={null}
+        isManageModalAdd={false}
+        deleteTarget={{
+          id: 7,
+          name: 'team',
+          tagValue: 'ops',
+          description: '',
+          type: 1
+        }}
+        isDeletePending
+        formatTime={() => '-'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onNew={() => {}}
+        onCopy={() => {}}
+        onEdit={() => {}}
+        onDeleteRequest={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onDraftChange={() => {}}
+        onCloseDialog={() => {}}
+        onSaveDialog={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-hz-ui="confirm-dialog"');
+    expect(html).toContain('data-hz-confirm-tone="critical"');
+    expect(html).toContain('data-label-delete-confirm="angular-modal-confirm"');
+    expect(html).toContain('data-label-delete-confirm-owner="hertzbeat-ui-confirm-dialog"');
+    expect(html).toContain('data-label-delete-confirm-state="open"');
+    expect(html).toContain('data-label-delete-confirm-pending="true"');
+    expect(html).toContain('data-label-delete-confirm-state-target="team:ops"');
+    expect(html).toContain('data-label-delete-confirm-submit="angular-modal-confirm"');
+    expect(html).toContain('data-label-delete-confirm-close="angular-close-before-delete-result"');
+    expect(html).toContain('data-label-delete-confirm-close-owner="route-state-contract"');
+    expect(html).toContain('data-label-delete-confirm-target="team:ops"');
+    expect(html).toContain('请确认是否删除!');
+    expect(html).toContain('确定');
+  });
+
+  it('renders Angular label delete failure title with backend detail', () => {
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <LabelManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [],
+            totalElements: 0,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        draftLabel={null}
+        isManageModalAdd={false}
+        deleteTarget={null}
+        actionError="删除失败!"
+        actionMeta="backend refused label delete"
+        formatTime={() => '-'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onNew={() => {}}
+        onCopy={() => {}}
+        onEdit={() => {}}
+        onDeleteRequest={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onDraftChange={() => {}}
+        onCloseDialog={() => {}}
+        onSaveDialog={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-label-delete-feedback="angular-delete-fail"');
+    expect(html).toContain('data-label-delete-feedback-title="common.notify.delete-fail"');
+    expect(html).toContain('data-label-delete-feedback-detail="backend-message"');
+    expect(html).toContain('删除失败!');
+    expect(html).toContain('backend refused label delete');
+  });
+
+  it('renders Angular label save failure title with backend detail', () => {
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <LabelManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [],
+            totalElements: 0,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        draftLabel={null}
+        isManageModalAdd={false}
+        deleteTarget={null}
+        actionError="修改失败!"
+        actionMeta="backend refused label edit"
+        formatTime={() => '-'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onNew={() => {}}
+        onCopy={() => {}}
+        onEdit={() => {}}
+        onDeleteRequest={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onDraftChange={() => {}}
+        onCloseDialog={() => {}}
+        onSaveDialog={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-label-save-failure="angular-save-fail"');
+    expect(html).toContain('data-label-save-failure-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-label-save-feedback-title="common.notify.edit-fail"');
+    expect(html).toContain('data-label-save-feedback-detail="backend-message"');
+    expect(html).toContain('修改失败!');
+    expect(html).toContain('backend refused label edit');
+  });
+
+  it('keeps the Angular label shell visible without action feedback on load failure', () => {
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <LabelManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [],
+            totalElements: 0,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        draftLabel={null}
+        isManageModalAdd={false}
+        deleteTarget={null}
+        loadError="backend refused label load"
+        formatTime={() => '-'}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onSearchClear={() => {}}
+        onRefresh={() => {}}
+        onNew={() => {}}
+        onCopy={() => {}}
+        onEdit={() => {}}
+        onDeleteRequest={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onDraftChange={() => {}}
+        onCloseDialog={() => {}}
+        onSaveDialog={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-label-load-failure="angular-console-only-shell"');
+    expect(html).toContain('data-label-load-failure-owner="label-route-controller"');
+    expect(html).toContain('data-label-card-loading="false"');
+    expect(html).not.toContain('backend refused label load');
+    expect(html).not.toContain('data-label-action-feedback="angular-action-error"');
+  });
+
   it('keeps the labels page on the cold visual owner instead of WorkbenchPage or alert primitives', () => {
     const source = readFileSync(resolve(process.cwd(), 'components/pages/label-manage-surface.tsx'), 'utf8');
 
     expect(source).toContain('coldOpsCatalogVisual');
     expect(source).toContain("from '../ui/search-row'");
+    expect(source).toContain("from '@hertzbeat/ui'");
+    expect(source).toContain('HzInlineFeedback');
+    expect(source).toContain('HzConfirmDialog');
+    expect(source).toContain('HzLabelTag');
+    expect(source).toContain('renderAngularLabelColor');
+    expect(source).toContain('data-label-color-token');
+    expect(source).toContain('data-label-color-owner="hertzbeat-ui-label-tag"');
+    expect(source).toContain('data-label-dialog-preview-color="angular-render-label-color"');
+    expect(source).toContain('maskClosable={false}');
+    expect(source).toContain('data-label-dialog-mask-closable');
+    expect(source).toContain('data-label-dialog-width');
+    expect(source).toContain('data-label-dialog-field-layout');
+    expect(source).toContain('lg:max-w-[30vw]');
+    expect(source).toContain('data-label-delete-confirm="angular-modal-confirm"');
+    expect(source).toContain('data-label-delete-confirm-owner="hertzbeat-ui-confirm-dialog"');
+    expect(source).toContain("data-label-delete-confirm-state={deleteTarget ? 'open' : 'closed'}");
+    expect(source).toContain('data-label-delete-confirm-state-owner="hertzbeat-ui-confirm-dialog"');
+    expect(source).toContain("data-label-delete-confirm-pending={isDeletePending ? 'true' : 'false'}");
+    expect(source).toContain('data-label-delete-confirm-submit');
+    expect(source).toContain('data-label-delete-confirm-close-contract="angular-close-before-delete-result"');
+    expect(source).toContain('data-label-delete-confirm-close-owner="route-state-contract"');
+    expect(source).toContain("'data-label-delete-confirm-close': 'angular-close-before-delete-result'");
+    expect(source).toContain('data-label-delete-confirm-target');
+    expect(source).toContain("data-label-action-feedback={actionError ? 'angular-action-error' : 'angular-action-success'}");
+    expect(source).toContain('data-label-action-feedback-owner="hertzbeat-ui-inline-feedback"');
+    expect(source).toContain("data-label-save-feedback={actionError ? 'angular-save-error' : 'angular-save-success'}");
+    expect(source).toContain('data-label-save-feedback-owner="hertzbeat-ui-inline-feedback"');
     expect(source).toContain('inputWidthClassName="w-[360px]"');
     expect(source).toContain('data-label-manage-surface="otlp-cold-label-console"');
     expect(source).toContain('data-label-manage-style-baseline={coldLabelVisual.canvasName}');
     expect(source).toContain('data-label-header="cold-compact-header"');
     expect(source).toContain('data-label-command-row="standard-equal-buttons"');
     expect(source).toContain('data-label-admin-layout="full-width-admin-list"');
+    expect(source).toContain('data-label-save-feedback-contract="angular-new-edit-notify"');
+    expect(source).toContain('data-label-save-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(source).toContain('data-label-optional-save-payload-contract="angular-preserve-undefined-optional-fields"');
+    expect(source).toContain('data-label-optional-save-payload-owner="label-manage-controller"');
+    expect(source).toContain('data-label-type-save-payload-contract="angular-preserve-implicit-type"');
+    expect(source).toContain('data-label-type-save-payload-owner="label-manage-controller"');
+    expect(source).toContain('data-label-display-format-contract="angular-format-label-name-raw-tag-value"');
+    expect(source).toContain('data-label-display-format-owner="label-view-model"');
+    expect(source).toContain('data-label-description-display-contract="angular-truthy-description-raw"');
+    expect(source).toContain('data-label-description-display-owner="label-manage-surface"');
+    expect(source).toContain('data-label-save-failure-contract="angular-new-edit-fail-notification"');
+    expect(source).toContain('data-label-save-failure-owner="hertzbeat-ui-inline-feedback"');
+    expect(source).toContain('data-label-load-failure-contract="angular-console-only-shell"');
+    expect(source).toContain('data-label-load-failure-owner="label-route-controller"');
+    expect(source).toContain("data-label-load-failure={loadError ? 'angular-console-only-shell' : 'none'}");
+    expect(source).toContain("actionError === t('common.notify.new-fail')");
+    expect(source).toContain("actionError === t('common.notify.edit-fail')");
+    expect(source).toContain("data-label-save-failure={saveFailureKey ? 'angular-save-fail' : undefined}");
+    expect(source).toContain('data-label-save-feedback-title={saveFailureKey}');
+    expect(source).toContain("data-label-save-feedback-detail={saveFailureKey && actionMeta ? 'backend-message' : undefined}");
+    expect(source).toContain('data-label-copy-feedback-contract="angular-copy-notify"');
+    expect(source).toContain('data-label-copy-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(source).toContain('data-label-delete-confirm-contract="angular-modal-confirm"');
+    expect(source).toContain('data-label-delete-confirm-contract-owner="hertzbeat-ui-confirm-dialog"');
+    expect(source).toContain('data-label-delete-confirm-state-contract="angular-modal-confirm-state"');
+    expect(source).toContain('data-label-delete-confirm-state-contract-owner="hertzbeat-ui-confirm-dialog"');
+    expect(source).toContain('data-label-delete-confirm-close-contract="angular-close-before-delete-result"');
+    expect(source).toContain('data-label-delete-confirm-close-owner="route-state-contract"');
+    expect(source).toContain('data-label-delete-feedback-contract="angular-delete-notify"');
+    expect(source).toContain('data-label-delete-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(source).toContain('data-label-delete-failure-contract="angular-delete-fail-notification"');
+    expect(source).toContain('data-label-delete-failure-owner="hertzbeat-ui-inline-feedback"');
+    expect(source).toContain('data-label-delete-feedback-title={isDeleteFailureFeedback ?');
+    expect(source).toContain("data-label-delete-feedback-detail={isDeleteFailureFeedback && actionMeta ? 'backend-message' : undefined}");
+    expect(source).toContain('data-label-delete-query-contract="angular-repeated-ids-query"');
+    expect(source).toContain('data-label-delete-query-owner="route-mutation-contract"');
+    expect(source).toContain('data-label-card-loading-contract="angular-table-loading"');
+    expect(source).toContain('data-label-card-loading-owner="label-route-controller"');
+    expect(source).toContain("data-label-card-loading={isCardGridLoading ? 'true' : 'false'}");
+    expect(source).toContain("aria-busy={isCardGridLoading ? 'true' : 'false'}");
+    expect(source).toContain('data-label-save-loading-contract="angular-nz-ok-loading"');
+    expect(source).toContain('data-label-save-loading-contract-owner="angular-nz-ok-loading"');
+    expect(source).toContain('data-label-dialog-mask-closable-contract="angular-mask-closable-false"');
+    expect(source).toContain('data-label-dialog-width-contract="angular-width-30-percent"');
+    expect(source).toContain('data-label-dialog-field-layout-contract="angular-label-7-control-12"');
+    expect(source).toContain('data-label-dialog-field-layout-owner="route-form-field-grid"');
+    expect(source).toContain('data-label-dialog-preview-visibility-contract="angular-name-defined-preview"');
+    expect(source).toContain('data-label-dialog-preview-visibility-owner="route-form-state"');
+    expect(source).toContain('data-label-dialog-preview-frame-contract="angular-inline-tag-no-extra-frame"');
+    expect(source).toContain('data-label-dialog-preview-frame-owner="route-form-field-grid"');
+    expect(source).toContain('data-label-dialog-preview-chrome-contract="angular-tag-only-no-extra-frame"');
+    expect(source).toContain('data-label-dialog-preview-chrome-owner="hertzbeat-ui-label-tag"');
+    expect(source).toContain('data-label-edit-reference-contract="angular-edit-direct-reference"');
+    expect(source).toContain('data-label-edit-reference-owner="route-form-state"');
+    expect(source).toContain('data-label-name-validation-contract="angular-required-before-submit"');
+    expect(source).toContain('data-label-name-validation-trigger-contract="angular-ok-marks-dirty"');
+    expect(source).toContain('data-label-name-validation-trigger-owner="route-form-contract"');
+    expect(source).toContain('data-label-name-validation-raw-contract="angular-required-before-trim"');
+    expect(source).toContain('data-label-name-validation-raw-owner="route-form-contract"');
+    expect(source).toContain('data-label-query-contract="angular-load-all-labels"');
+    expect(source).toContain('data-label-query-owner="label-query-state"');
+    expect(source).toContain('data-label-query-param-order-contract="angular-page-index-size-type-search"');
+    expect(source).toContain('data-label-query-param-order-owner="label-query-state"');
+    expect(source).toContain('data-label-card-grid-contract="angular-card-grid"');
+    expect(source).toContain('data-label-card-grid-owner="hertzbeat-ui-label-tag"');
+    expect(source).toContain('data-label-monitor-handoff-contract="angular-routerlink-monitors-labels"');
+    expect(source).toContain('data-label-monitor-handoff-owner="next-monitor-query-link"');
+    expect(source).toContain('data-label-dialog-name-input="angular-required-name"');
+    expect(source).toContain('data-label-dialog-field-layout-row="angular-label-7-control-12"');
+    expect(source).toContain('data-label-dialog-label-span="7"');
+    expect(source).toContain('data-label-dialog-control-span="12"');
+    expect(source).toContain('sm:grid-cols-[minmax(96px,7fr)_minmax(0,12fr)]');
+    expect(source).toContain('isLabelPreviewVisible');
+    expect(source).toContain("draftLabel?.name !== undefined");
+    expect(source).toContain('data-label-dialog-preview-frame="angular-inline-tag-no-extra-frame"');
+    expect(source).toContain('data-label-dialog-preview-chrome="angular-nz-tag-inline"');
+    expect(source).toContain('data-label-dialog-preview-chrome-mode="angular-tag-only-no-extra-frame"');
+    expect(source).toContain('className={coldDialogFieldClassName}');
+    expect(source).not.toContain('className={`${coldDialogFieldClassName} rounded-[4px] border border-[#2b3039] bg-[#101217] p-3`}');
+    expect(source).toContain('data-label-dialog-name-state');
+    expect(source).toContain('data-label-dialog-name-validation="validation.required"');
+    expect(source).toContain('data-label-save-submit="angular-required-before-submit"');
+    expect(source).toContain('data-label-name-validation-trigger="angular-ok-marks-dirty"');
+    expect(source).toContain('data-label-save-submit-state');
+    expect(source).toContain('disabled={Boolean(isSavePending)}');
+    expect(source).toContain("data-label-save-ok-loading={isSavePending ? 'true' : 'false'}");
+    expect(source).toContain('data-label-save-ok-loading-owner="angular-nz-ok-loading"');
     expect(source).toContain('data-label-toolbar="cold-table-toolbar"');
     expect(source).toContain('data-label-search-owner="shared-search-row"');
-    expect(source).toContain('data-label-table-shell="cold-dense-table"');
-    expect(source).toContain('data-label-table="cold-label-table"');
-    expect(source).toContain('data-label-empty-state="cold-table-empty"');
+    expect(source).toContain('data-label-search-submit="angular-enter-and-clear"');
+    expect(source).toContain('data-label-search-submit-owner="cold-search-row"');
+    expect(source).toContain("clearLabel={t('common.clear')}");
+    expect(source).toContain('onClear={onSearchClear}');
+    expect(source).toContain('data-label-card-grid="angular-card-grid"');
+    expect(source).toContain('data-label-card-column-contract="nz-xs-12-sm-8-md-6-lg-4"');
+    expect(source).toContain('data-label-card-shell="angular-card"');
+    expect(source).toContain('data-label-card-size="small"');
+    expect(source).toContain('data-label-card-content="angular-label-content"');
+    expect(source).toContain('data-label-card-description="angular-description"');
+    expect(source).toContain('data-label-card-description-display="angular-truthy-description-raw"');
+    expect(source).toContain('data-label-card-description-display-owner="label-manage-surface"');
+    expect(source).toContain('data-label-card-actions="angular-card-actions"');
+    expect(source).toContain('data-label-monitor-handoff="angular-routerlink-monitors-labels"');
+    expect(source).toContain('data-label-monitor-handoff-query={labelText}');
+    expect(source).toContain('data-label-monitor-handoff-owner="next-monitor-query-link"');
+    expect(source).toContain("t('setting.labels.action.copy-aria', actionLabelParams)");
+    expect(source).toContain("t('setting.labels.action.edit-aria', actionLabelParams)");
+    expect(source).toContain("t('setting.labels.action.delete-aria', actionLabelParams)");
+    expect(source).toContain('data-label-row-actions="angular-card-actions-contextual"');
+    expect(source).toContain('data-label-row-actions-owner="hertzbeat-ui-action-group"');
+    expect(source).toContain('data-label-row-action-owner="row-contextual-icon-button"');
+    expect(source).toContain('data-label-row-action-label={labelText}');
+    expect(source).toContain('data-label-empty-state="cold-card-empty"');
     expect(source).not.toContain('data-label-summary-rail');
     expect(source).not.toContain('coldLabelVisual.search.row');
     expect(source).not.toContain('coldLabelVisual.search.input');
@@ -202,11 +1037,7 @@ describe('label manage surface', () => {
     expect(source).not.toContain('WorkbenchPage');
     expect(source).not.toContain("from './alert-surface-primitives'");
     expect(source).not.toContain('AlertSurfacePanel');
-    expect(source).not.toContain('data-label-manage-route="angular-label-cards"');
-    expect(source).not.toContain('data-label-toolbar="angular-card-toolbar"');
-    expect(source).not.toContain('data-label-card-grid="angular-card-grid"');
-    expect(source).not.toContain('data-label-card-shell="angular-card"');
-    expect(source).not.toContain('data-label-card-actions="angular-card-actions"');
+    expect(source).not.toContain('data-label-table="cold-label-table"');
     expect(source).not.toContain('SurfaceSection');
     expect(source).not.toContain('StatusState');
     expect(source).not.toContain('ToolbarField');
diff --git a/web-next/components/pages/label-manage-surface.tsx b/web-next/components/pages/label-manage-surface.tsx
index 60ecfec966..e402e4ad9e 100644
--- a/web-next/components/pages/label-manage-surface.tsx
+++ b/web-next/components/pages/label-manage-surface.tsx
@@ -1,13 +1,15 @@
 'use client';
 
 import React from 'react';
+import Link from 'next/link';
 import { Copy, Inbox, Pencil, Plus, RefreshCw, Trash2 } from 'lucide-react';
 import { OverlayDialog } from '../workbench/overlay-dialog';
 import { Button } from '../ui/button';
 import { Input } from '../ui/input';
 import { SearchRow } from '../ui/search-row';
+import { HzActionGroup, HzConfirmDialog, HzIconButton, HzInlineFeedback, HzLabelTag } from '@hertzbeat/ui';
 import { coldOpsCatalogVisual } from '../../lib/cold-ops-visual';
-import { buildLabelDisplayName } from '../../lib/label-manage/view-model';
+import { buildLabelDisplayName, renderAngularLabelColor } from '../../lib/label-manage/view-model';
 import type { LabelManagePageData } from '../../lib/label-manage/controller';
 import type { Label } from '../../lib/types';
 
@@ -19,14 +21,26 @@ type LabelManageSurfaceProps = {
   search: string;
   draftLabel: Label | null;
   isManageModalAdd: boolean;
+  isNameValidationVisible?: boolean;
+  deleteTarget: Label | null;
+  isLoadPending?: boolean;
+  isDeletePending?: boolean;
+  isSavePending?: boolean;
+  actionMessage?: string | null;
+  actionError?: string | null;
+  actionMeta?: string | null;
+  loadError?: string | null;
   formatTime: (value?: number | string | null) => string;
   onSearchChange: (value: string) => void;
   onSearch: () => void;
+  onSearchClear: () => void;
   onRefresh: () => void;
   onNew: () => void;
   onCopy: (label: Label) => void;
   onEdit: (label: Label) => void;
-  onDelete: (label: Label) => void;
+  onDeleteRequest: (label: Label) => void;
+  onDeleteCancel: () => void;
+  onDeleteConfirm: () => void;
   onDraftChange: (patch: Partial<Label>) => void;
   onCloseDialog: () => void;
   onSaveDialog: () => void;
@@ -40,28 +54,13 @@ const coldButtonClassName =
 const coldPrimaryButtonClassName =
   'h-8 min-w-[104px] rounded-[3px] border-[#31405c] bg-[#182238] px-3 text-[12px] font-semibold text-[#d8e4ff] shadow-[inset_0_1px_0_rgba(255,255,255,0.04)] hover:border-[#4e74f8] hover:bg-[#202a42] hover:text-white';
 
-const coldIconButtonClassName =
-  'h-8 w-8 min-w-0 rounded-[3px] border-[#2b3039] bg-[#101217] text-[#dbe4f0] hover:border-[#4e74f8] hover:bg-[#151b28] hover:text-white';
-
 const coldDialogInputClassName =
   'h-8 rounded-[3px] border border-[#2b3039] bg-[#101217] px-3 text-[12px] font-semibold text-[#eef2f7] outline-none placeholder:text-[#6f7788] focus:border-[#4e74f8] focus:ring-2 focus:ring-[rgba(78,116,248,0.16)]';
 
-function isChineseTranslator(t: Translator): boolean {
-  return t('common.refresh') === '刷新' || t('menu.advanced.labels') === '标签管理';
-}
+const coldDialogFieldClassName =
+  'grid gap-2 text-[12px] font-semibold text-[#a9b0bb] sm:grid-cols-[minmax(96px,7fr)_minmax(0,12fr)] sm:items-start';
 
-function resolveCopy(t: Translator, key: string, fallback: string): string {
-  const value = t(key);
-  return value === key ? fallback : value;
-}
-
-function resolveLabelType(type: number | null | undefined, t: Translator): string {
-  const chinese = isChineseTranslator(t);
-  if (type === 0) return chinese ? '自动标签' : 'Auto label';
-  if (type === 1) return chinese ? '用户标签' : 'User label';
-  if (type === 2) return chinese ? '预置标签' : 'Preset label';
-  return '-';
-}
+const coldDialogLabelClassName = 'pt-2 leading-4 sm:text-right';
 
 export function LabelManageSurface({
   t,
@@ -69,14 +68,25 @@ export function LabelManageSurface({
   search,
   draftLabel,
   isManageModalAdd,
-  formatTime,
+  isNameValidationVisible,
+  deleteTarget,
+  isLoadPending,
+  isDeletePending,
+  isSavePending,
+  actionMessage,
+  actionError,
+  actionMeta,
+  loadError,
   onSearchChange,
   onSearch,
+  onSearchClear,
   onRefresh,
   onNew,
   onCopy,
   onEdit,
-  onDelete,
+  onDeleteRequest,
+  onDeleteCancel,
+  onDeleteConfirm,
   onDraftChange,
   onCloseDialog,
   onSaveDialog
@@ -84,8 +94,20 @@ export function LabelManageSurface({
   const labels = data.list.content ?? [];
   const dialogTitle = isManageModalAdd ? t('label.new') : t('label.edit');
   const displayName = draftLabel ? buildLabelDisplayName(draftLabel) : '';
-  const emptyTitle = resolveCopy(t, 'setting.labels.empty.title', '暂无标签');
-  const emptyCopy = resolveCopy(t, 'setting.labels.empty.copy', '请先新增标签。');
+  const previewDisplayName = displayName || t('common.none');
+  const isLabelPreviewVisible = Boolean(draftLabel) && draftLabel?.name !== undefined;
+  const isDraftNameMissing = Boolean(draftLabel) && (draftLabel.name == undefined || draftLabel.name.length === 0);
+  const shouldShowNameValidation = isDraftNameMissing && Boolean(isNameValidationVisible);
+  const isCardGridLoading = Boolean(isLoadPending || isDeletePending);
+  const isDeleteFailureFeedback = actionError === t('common.notify.delete-fail');
+  const saveFailureKey =
+    actionError === t('common.notify.new-fail')
+      ? 'common.notify.new-fail'
+      : actionError === t('common.notify.edit-fail')
+        ? 'common.notify.edit-fail'
+        : undefined;
+  const emptyTitle = t('setting.labels.empty.title');
+  const emptyCopy = t('setting.labels.empty.copy');
 
   return (
     <>
@@ -104,7 +126,7 @@ export function LabelManageSurface({
                     {t('menu.advanced.labels')}
                   </h1>
                   <p className="mt-4 max-w-[780px] text-[13px] leading-6 text-[#a9b0bb]">
-                    统一管理监控标签名称、标签值和描述,用紧凑表格保持检索、复制和编辑动作在同一工作面里完成。
+                    {t('setting.labels.copy')}
                   </p>
                   <div data-label-command-row="standard-equal-buttons" className={coldLabelVisual.button.row}>
                     <Button size="sm" variant="default" className={coldButtonClassName} onClick={onRefresh}>
@@ -120,94 +142,226 @@ export function LabelManageSurface({
               </div>
             </div>
 
-            <div data-label-admin-layout="full-width-admin-list" className="space-y-5">
+            <div
+              data-label-admin-layout="full-width-admin-list"
+              data-label-save-feedback-contract="angular-new-edit-notify"
+              data-label-save-feedback-contract-owner="hertzbeat-ui-inline-feedback"
+              data-label-save-failure-contract="angular-new-edit-fail-notification"
+              data-label-save-failure-owner="hertzbeat-ui-inline-feedback"
+              data-label-load-failure-contract="angular-console-only-shell"
+              data-label-load-failure-owner="label-route-controller"
+              data-label-load-failure={loadError ? 'angular-console-only-shell' : 'none'}
+              data-label-copy-feedback-contract="angular-copy-notify"
+              data-label-copy-feedback-contract-owner="hertzbeat-ui-inline-feedback"
+              data-label-delete-confirm-contract="angular-modal-confirm"
+              data-label-delete-confirm-contract-owner="hertzbeat-ui-confirm-dialog"
+              data-label-delete-confirm-state-contract="angular-modal-confirm-state"
+              data-label-delete-confirm-state-contract-owner="hertzbeat-ui-confirm-dialog"
+              data-label-delete-confirm-close-contract="angular-close-before-delete-result"
+              data-label-delete-confirm-close-owner="route-state-contract"
+              data-label-delete-feedback-contract="angular-delete-notify"
+              data-label-delete-feedback-contract-owner="hertzbeat-ui-inline-feedback"
+              data-label-delete-failure-contract="angular-delete-fail-notification"
+              data-label-delete-failure-owner="hertzbeat-ui-inline-feedback"
+              data-label-delete-query-contract="angular-repeated-ids-query"
+              data-label-delete-query-owner="route-mutation-contract"
+              data-label-card-loading-contract="angular-table-loading"
+              data-label-card-loading-owner="label-route-controller"
+              data-label-card-loading={isCardGridLoading ? 'true' : 'false'}
+              data-label-save-loading-contract="angular-nz-ok-loading"
+              data-label-save-loading-contract-owner="angular-nz-ok-loading"
+              data-label-save-trim-contract="angular-trim-before-save"
+              data-label-save-trim-owner="label-manage-controller"
+              data-label-optional-save-payload-contract="angular-preserve-undefined-optional-fields"
+              data-label-optional-save-payload-owner="label-manage-controller"
+              data-label-type-save-payload-contract="angular-preserve-implicit-type"
+              data-label-type-save-payload-owner="label-manage-controller"
+              data-label-display-format-contract="angular-format-label-name-raw-tag-value"
+              data-label-display-format-owner="label-view-model"
+              data-label-description-display-contract="angular-truthy-description-raw"
+              data-label-description-display-owner="label-manage-surface"
+              data-label-dialog-mask-closable-contract="angular-mask-closable-false"
+              data-label-dialog-mask-closable-owner="angular-nz-modal"
+              data-label-dialog-width-contract="angular-width-30-percent"
+              data-label-dialog-width-owner="angular-nz-modal"
+              data-label-dialog-field-layout-contract="angular-label-7-control-12"
+              data-label-dialog-field-layout-owner="route-form-field-grid"
+              data-label-dialog-preview-visibility-contract="angular-name-defined-preview"
+              data-label-dialog-preview-visibility-owner="route-form-state"
+              data-label-dialog-preview-frame-contract="angular-inline-tag-no-extra-frame"
+              data-label-dialog-preview-frame-owner="route-form-field-grid"
+              data-label-dialog-preview-chrome-contract="angular-tag-only-no-extra-frame"
+              data-label-dialog-preview-chrome-owner="hertzbeat-ui-label-tag"
+              data-label-edit-reference-contract="angular-edit-direct-reference"
+              data-label-edit-reference-owner="route-form-state"
+              data-label-name-validation-contract="angular-required-before-submit"
+              data-label-name-validation-trigger-contract="angular-ok-marks-dirty"
+              data-label-name-validation-trigger-owner="route-form-contract"
+              data-label-name-validation-raw-contract="angular-required-before-trim"
+              data-label-name-validation-raw-owner="route-form-contract"
+              data-label-query-contract="angular-load-all-labels"
+              data-label-query-owner="label-query-state"
+              data-label-query-param-order-contract="angular-page-index-size-type-search"
+              data-label-query-param-order-owner="label-query-state"
+              data-label-card-grid-contract="angular-card-grid"
+              data-label-card-grid-owner="hertzbeat-ui-label-tag"
+              data-label-monitor-handoff-contract="angular-routerlink-monitors-labels"
+              data-label-monitor-handoff-owner="next-monitor-query-link"
+              className="space-y-5"
+            >
               <section className="min-w-0">
                 <SearchRow
                   data-label-toolbar="cold-table-toolbar"
                   data-label-search-owner="shared-search-row"
+                  data-label-search-submit="angular-enter-and-clear"
+                  data-label-search-submit-owner="cold-search-row"
                   value={search}
                   placeholder={t('label.search')}
                   searchLabel={t('common.search')}
+                  clearLabel={t('common.clear')}
                   inputWidthClassName="w-[360px]"
                   onValueChange={onSearchChange}
                   onSearch={onSearch}
+                  onClear={onSearchClear}
                 />
 
+                {actionMessage || actionError ? (
+                  <HzInlineFeedback
+                    tone={actionError ? 'warning' : 'success'}
+                    title={actionError || actionMessage}
+                    meta={actionMeta ?? undefined}
+                    variant="embedded"
+                    data-label-action-feedback={actionError ? 'angular-action-error' : 'angular-action-success'}
+                    data-label-action-feedback-owner="hertzbeat-ui-inline-feedback"
+                    data-label-save-feedback={actionError ? 'angular-save-error' : 'angular-save-success'}
+                    data-label-save-feedback-owner="hertzbeat-ui-inline-feedback"
+                    data-label-save-failure={saveFailureKey ? 'angular-save-fail' : undefined}
+                    data-label-save-failure-owner={saveFailureKey ? 'hertzbeat-ui-inline-feedback' : undefined}
+                    data-label-save-feedback-title={saveFailureKey}
+                    data-label-save-feedback-detail={saveFailureKey && actionMeta ? 'backend-message' : undefined}
+                    data-label-delete-feedback={
+                      isDeleteFailureFeedback
+                        ? 'angular-delete-fail'
+                        : actionMessage === t('common.notify.delete-success')
+                          ? 'angular-delete-notify'
+                          : undefined
+                    }
+                    data-label-delete-feedback-owner={isDeleteFailureFeedback ? 'hertzbeat-ui-inline-feedback' : undefined}
+                    data-label-delete-feedback-title={isDeleteFailureFeedback ? 'common.notify.delete-fail' : undefined}
+                    data-label-delete-feedback-detail={isDeleteFailureFeedback && actionMeta ? 'backend-message' : undefined}
+                  />
+                ) : null}
+
                 <div
-                  data-label-table-shell="cold-dense-table"
-                  className="overflow-hidden rounded-[4px] border border-[#2b3039] bg-[#0b0c0e] shadow-[0_20px_56px_rgba(0,0,0,0.32)]"
+                  data-label-card-grid="angular-card-grid"
+                  data-label-card-grid-owner="hertzbeat-ui-label-tag"
+                  data-label-card-column-contract="nz-xs-12-sm-8-md-6-lg-4"
+                  data-label-card-loading={isCardGridLoading ? 'true' : 'false'}
+                  data-label-card-loading-owner="label-route-controller"
+                  aria-busy={isCardGridLoading ? 'true' : 'false'}
+                  className="grid grid-cols-2 gap-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6"
                 >
-                  <table data-label-table="cold-label-table" className="w-full table-fixed border-collapse text-left text-[12px] text-[#a9b0bb]">
-                    <thead className="border-b border-[#252b34] bg-[#101217] text-[11px] font-semibold uppercase tracking-[0.12em] text-[#7e8494]">
-                      <tr>
-                        <th className="w-[24%] px-3 py-2.5">标签名称</th>
-                        <th className="w-[18%] px-3 py-2.5">标签类型</th>
-                        <th className="w-[28%] px-3 py-2.5">标签描述</th>
-                        <th className="w-[18%] px-3 py-2.5">{t('common.edit-time')}</th>
-                        <th className="w-[96px] px-3 py-2.5">{t('common.edit')}</th>
-                      </tr>
-                    </thead>
-                    <tbody>
-                      {labels.length > 0 ? labels.map(label => {
-                        const labelText = buildLabelDisplayName(label);
-                        const copyLabel = t('common.copy.button');
-                        const editLabel = t('common.button.edit');
-                        const deleteLabel = t('common.button.delete');
+                  {labels.length > 0 ? labels.map(label => {
+                    const labelText = buildLabelDisplayName(label);
+                    const description = label.description ? label.description : '';
+                    const actionLabelParams = { name: labelText };
+                    const copyLabel = t('setting.labels.action.copy-aria', actionLabelParams);
+                    const editLabel = t('setting.labels.action.edit-aria', actionLabelParams);
+                    const deleteLabel = t('setting.labels.action.delete-aria', actionLabelParams);
 
-                        return (
-                          <tr key={label.id} data-label-row={label.id} className="border-t border-[#252b34] bg-[#0b0c0e] transition hover:bg-[#111721]">
-                            <td className="px-3 py-2.5 font-semibold text-[#eef2f7]">
-                              <a
-                                href={`/monitors?labels=${encodeURIComponent(labelText)}`}
-                                className="inline-flex max-w-full rounded-[3px] border border-[#303743] bg-[#101217] px-2 py-0.5 text-[12px] leading-5 text-[#dbe4f0] hover:border-[#4e74f8] hover:text-white"
-                              >
-                                <span className="truncate">{labelText}</span>
-                              </a>
-                            </td>
-                            <td className="px-3 py-2.5">
-                              <span className="rounded-[3px] border border-[#303743] bg-[#101217] px-2 py-0.5 text-[11px] leading-4 text-[#cbd5e1]">
-                                {resolveLabelType(label.type, t)}
-                              </span>
-                            </td>
-                            <td className="truncate px-3 py-2.5" title={label.description || '-'}>
-                              {label.description || '-'}
-                            </td>
-                            <td className="px-3 py-2.5 text-[#858d9a]">
-                              {formatTime(label.gmtUpdate || label.gmtCreate || null)}
-                            </td>
-                            <td className="px-3 py-2.5">
-                              <div data-label-row-actions="cold-icon-actions" className="flex gap-1.5">
-                                <Button size="icon" variant="default" className={coldIconButtonClassName} aria-label={copyLabel} title={copyLabel} onClick={() => onCopy(label)}>
-                                  <Copy className="h-3.5 w-3.5" aria-hidden="true" />
-                                </Button>
-                                <Button size="icon" variant="default" className={coldIconButtonClassName} aria-label={editLabel} title={editLabel} onClick={() => onEdit(label)}>
-                                  <Pencil className="h-3.5 w-3.5" aria-hidden="true" />
-                                </Button>
-                                <Button size="icon" variant="default" className={coldIconButtonClassName} aria-label={deleteLabel} title={deleteLabel} onClick={() => onDelete(label)}>
-                                  <Trash2 className="h-3.5 w-3.5" aria-hidden="true" />
-                                </Button>
-                              </div>
-                            </td>
-                          </tr>
-                        );
-                      }) : (
-                        <tr data-label-empty-state="cold-table-empty" className="border-t border-[#252b34] bg-[#0b0c0e]">
-                          <td colSpan={5} className="h-[240px] px-3 text-center text-[#a9b0bb]">
-                            <div className="inline-flex flex-col items-center gap-2.5">
-                              <span
-                                data-label-empty-icon="cold-empty-box"
-                                className="inline-flex h-10 w-10 items-center justify-center rounded-[4px] border border-[#303743] bg-[#101217] text-[#cbd5e1]"
-                              >
-                                <Inbox className="h-5 w-5" aria-hidden="true" />
-                              </span>
-                              <div className="text-[13px] font-semibold text-[#eef2f7]">{emptyTitle}</div>
-                              <div className="text-[12px] leading-5 text-[#8f99ab]">{emptyCopy}</div>
+                    return (
+                      <article
+                        key={label.id}
+                        data-label-row={label.id}
+                        data-label-card-shell="angular-card"
+                        data-label-card-size="small"
+                        className="flex min-h-[112px] min-w-0 flex-col overflow-hidden rounded-[4px] border border-[#2b3039] bg-[#0b0c0e] shadow-[0_12px_32px_rgba(0,0,0,0.28)] transition hover:border-[#4e74f8] hover:bg-[#111721]"
+                      >
+                        <div data-label-card-content="angular-label-content" className="flex min-h-[72px] flex-1 flex-col gap-2 p-3">
+                          <Link
+                            href={`/monitors?labels=${encodeURIComponent(labelText)}`}
+                            title={labelText}
+                            className="inline-flex max-w-full"
+                            data-label-monitor-handoff="angular-routerlink-monitors-labels"
+                            data-label-monitor-handoff-query={labelText}
+                            data-label-monitor-handoff-owner="next-monitor-query-link"
+                          >
+                            <HzLabelTag
+                              colorToken={renderAngularLabelColor(label.name || '')}
+                              className="hover:border-[#4e74f8]"
+                              data-label-color-token={renderAngularLabelColor(label.name || '')}
+                              data-label-color-owner="hertzbeat-ui-label-tag"
+                            >
+                              <span className="truncate">{labelText}</span>
+                            </HzLabelTag>
+                          </Link>
+                          {description ? (
+                            <div
+                              data-label-card-description="angular-description"
+                              data-label-card-description-display="angular-truthy-description-raw"
+                              data-label-card-description-display-owner="label-manage-surface"
+                              className="line-clamp-2 min-h-[34px] text-[12px] leading-[17px] text-[#8f99ab]"
+                              title={description}
+                            >
+                              {description}
                             </div>
-                          </td>
-                        </tr>
-                      )}
-                    </tbody>
-                  </table>
+                          ) : null}
+                        </div>
+                        <div data-label-card-actions="angular-card-actions" className="border-t border-[#252b34] bg-[#101217] px-2 py-1.5">
+                          <HzActionGroup
+                            density="compact-icons"
+                            layout="center"
+                            data-label-row-actions="angular-card-actions-contextual"
+                            data-label-row-actions-owner="hertzbeat-ui-action-group"
+                          >
+                            <HzIconButton
+                              label={copyLabel}
+                              intent="ghost"
+                              onClick={() => onCopy(label)}
+                              data-label-row-action="copy"
+                              data-label-row-action-owner="row-contextual-icon-button"
+                              data-label-row-action-label={labelText}
+                            >
+                              <Copy size={13} />
+                            </HzIconButton>
+                            <HzIconButton
+                              label={editLabel}
+                              intent="ghost"
+                              onClick={() => onEdit(label)}
+                              data-label-row-action="edit"
+                              data-label-row-action-owner="row-contextual-icon-button"
+                              data-label-row-action-label={labelText}
+                            >
+                              <Pencil size={13} />
+                            </HzIconButton>
+                            <HzIconButton
+                              label={deleteLabel}
+                              intent="ghost"
+                              onClick={() => onDeleteRequest(label)}
+                              data-label-row-action="delete"
+                              data-label-row-action-owner="row-contextual-icon-button"
+                              data-label-row-action-label={labelText}
+                            >
+                              <Trash2 size={13} />
+                            </HzIconButton>
+                          </HzActionGroup>
+                        </div>
+                      </article>
+                    );
+                  }) : (
+                    <div data-label-empty-state="cold-card-empty" className="col-span-full h-[240px] rounded-[4px] border border-[#2b3039] bg-[#0b0c0e] px-3 text-center text-[#a9b0bb]">
+                      <div className="flex h-full flex-col items-center justify-center gap-2.5">
+                        <span
+                          data-label-empty-icon="cold-empty-box"
+                          className="inline-flex h-10 w-10 items-center justify-center rounded-[4px] border border-[#303743] bg-[#101217] text-[#cbd5e1]"
+                        >
+                          <Inbox className="h-5 w-5" aria-hidden="true" />
+                        </span>
+                        <div className="text-[13px] font-semibold text-[#eef2f7]">{emptyTitle}</div>
+                        <div className="text-[12px] leading-5 text-[#8f99ab]">{emptyCopy}</div>
+                      </div>
+                    </div>
+                  )}
                 </div>
               </section>
             </div>
@@ -219,55 +373,168 @@ export function LabelManageSurface({
         open={Boolean(draftLabel)}
         title={dialogTitle}
         onClose={onCloseDialog}
-        maxWidthClassName="max-w-2xl"
+        maskClosable={false}
+        maxWidthClassName="max-w-[min(92vw,30rem)] lg:max-w-[30vw]"
+        overlayProps={{
+          'data-label-dialog-mask-closable': 'false',
+          'data-label-dialog-mask-closable-owner': 'angular-nz-modal',
+          'data-label-dialog-width': 'angular-width-30-percent',
+          'data-label-dialog-width-owner': 'angular-nz-modal',
+          'data-label-dialog-field-layout': 'angular-label-7-control-12',
+          'data-label-dialog-field-layout-owner': 'route-form-field-grid',
+          'data-label-dialog-preview-visibility': isLabelPreviewVisible ? 'visible' : 'hidden',
+          'data-label-dialog-preview-visibility-owner': 'route-form-state'
+        }}
         footer={
           <div className="flex flex-wrap justify-end gap-2">
             <Button size="sm" variant="default" className={coldButtonClassName} onClick={onCloseDialog}>
               {t('common.button.cancel')}
             </Button>
-            <Button size="sm" variant="default" className={coldPrimaryButtonClassName} onClick={onSaveDialog}>
+            <Button
+              size="sm"
+              variant="default"
+              className={coldPrimaryButtonClassName}
+              onClick={onSaveDialog}
+              disabled={Boolean(isSavePending)}
+              aria-busy={isSavePending ? 'true' : 'false'}
+              data-label-save-submit="angular-required-before-submit"
+              data-label-name-validation-trigger="angular-ok-marks-dirty"
+              data-label-name-validation-trigger-owner="route-form-contract"
+              data-label-save-submit-state={shouldShowNameValidation ? 'dirty-invalid' : 'pristine'}
+              data-label-save-trim="angular-trim-before-save"
+              data-label-save-trim-owner="label-manage-controller"
+              data-label-save-ok-loading={isSavePending ? 'true' : 'false'}
+              data-label-save-ok-loading-owner="angular-nz-ok-loading"
+            >
               {t('common.button.save')}
             </Button>
           </div>
         }
       >
         {draftLabel ? (
-          <div className="grid gap-4 md:grid-cols-[minmax(0,1fr)_220px]">
-            <div className="space-y-3">
-              <label className="flex flex-col gap-2 text-[12px] font-semibold text-[#a9b0bb]">
-                <span>{t('label.name')}</span>
+          <div className="grid gap-3" data-label-dialog-field-layout="angular-label-7-control-12" data-label-dialog-field-layout-owner="route-form-field-grid">
+            <div
+              className={coldDialogFieldClassName}
+              data-label-dialog-field="name"
+              data-label-dialog-field-layout-row="angular-label-7-control-12"
+            >
+              <label htmlFor="label-name" className={coldDialogLabelClassName} data-label-dialog-label-span="7">
+                {t('label.name')}
+              </label>
+              <div className="min-w-0" data-label-dialog-control-span="12">
                 <Input
+                  id="label-name"
                   className={coldDialogInputClassName}
                   value={draftLabel.name || ''}
                   onChange={event => onDraftChange({ name: event.target.value })}
+                  data-label-dialog-name-input="angular-required-name"
+                  data-label-dialog-name-state={shouldShowNameValidation ? 'dirty-invalid' : 'pristine'}
                 />
+                {shouldShowNameValidation ? (
+                  <span className="mt-1 block text-[11px] font-medium text-[#fca5a5]" data-label-dialog-name-validation="validation.required">
+                    {t('validation.required')}
+                  </span>
+                ) : null}
+              </div>
+            </div>
+            <div
+              className={coldDialogFieldClassName}
+              data-label-dialog-field="value"
+              data-label-dialog-field-layout-row="angular-label-7-control-12"
+            >
+              <label htmlFor="label-value" className={coldDialogLabelClassName} data-label-dialog-label-span="7">
+                {t('label.value')}
               </label>
-              <label className="flex flex-col gap-2 text-[12px] font-semibold text-[#a9b0bb]">
-                <span>{t('label.value')}</span>
+              <div className="min-w-0" data-label-dialog-control-span="12">
                 <Input
+                  id="label-value"
                   className={coldDialogInputClassName}
                   value={draftLabel.tagValue || ''}
                   onChange={event => onDraftChange({ tagValue: event.target.value })}
                 />
+              </div>
+            </div>
+            <div
+              className={coldDialogFieldClassName}
+              data-label-dialog-field="description"
+              data-label-dialog-field-layout-row="angular-label-7-control-12"
+            >
+              <label htmlFor="label-description" className={coldDialogLabelClassName} data-label-dialog-label-span="7">
+                {t('label.description')}
               </label>
-              <label className="flex flex-col gap-2 text-[12px] font-semibold text-[#a9b0bb]">
-                <span>{t('label.description')}</span>
+              <div className="min-w-0" data-label-dialog-control-span="12">
                 <Input
+                  id="label-description"
                   className={coldDialogInputClassName}
                   value={draftLabel.description || ''}
                   onChange={event => onDraftChange({ description: event.target.value })}
                 />
-              </label>
-            </div>
-            <div data-label-dialog-preview="cold-preview" className="rounded-[4px] border border-[#2b3039] bg-[#101217] p-3">
-              <div className="mb-3 text-[13px] font-semibold text-[#eef2f7]">{t('label.display')}</div>
-              <div className="inline-flex min-h-8 max-w-full items-center rounded-[3px] border border-[#303743] bg-[#0b0c0e] px-2.5 text-[12px] font-semibold text-[#dbe4f0]">
-                <span className="truncate">{displayName || '-'}</span>
               </div>
             </div>
+            {isLabelPreviewVisible ? (
+              <div
+                data-label-dialog-preview="cold-preview"
+                data-label-dialog-preview-visibility="visible"
+                data-label-dialog-preview-visibility-owner="route-form-state"
+                data-label-dialog-preview-frame="angular-inline-tag-no-extra-frame"
+                data-label-dialog-preview-frame-owner="route-form-field-grid"
+                data-label-dialog-field="display"
+                data-label-dialog-field-layout-row="angular-label-7-control-12"
+                className={coldDialogFieldClassName}
+              >
+                <div className={coldDialogLabelClassName} data-label-dialog-label-span="7">
+                  {t('label.display')}
+                </div>
+                <HzLabelTag
+                  colorToken={renderAngularLabelColor(draftLabel.name || '')}
+                  className="max-w-full align-middle"
+                  data-label-dialog-preview-color="angular-render-label-color"
+                  data-label-dialog-preview-chrome="angular-nz-tag-inline"
+                  data-label-dialog-preview-chrome-mode="angular-tag-only-no-extra-frame"
+                  data-label-dialog-preview-chrome-owner="hertzbeat-ui-label-tag"
+                  data-label-color-owner="hertzbeat-ui-label-tag"
+                  data-label-dialog-control-span="12"
+                >
+                  <span className="truncate">{previewDisplayName}</span>
+                </HzLabelTag>
+              </div>
+            ) : (
+              <div hidden data-label-dialog-preview-visibility="hidden" data-label-dialog-preview-visibility-owner="route-form-state" />
+            )}
           </div>
         ) : null}
       </OverlayDialog>
+      <div
+        hidden
+        data-label-delete-confirm-state={deleteTarget ? 'open' : 'closed'}
+        data-label-delete-confirm-state-owner="hertzbeat-ui-confirm-dialog"
+        data-label-delete-confirm-pending={isDeletePending ? 'true' : 'false'}
+        data-label-delete-confirm-state-target={deleteTarget ? buildLabelDisplayName(deleteTarget) : ''}
+      />
+      <HzConfirmDialog
+        open={Boolean(deleteTarget)}
+        tone="critical"
+        kicker={t('menu.advanced.labels')}
+        title={t('common.confirm.delete')}
+        cancelLabel={t('common.button.cancel')}
+        confirmLabel={t('common.button.ok')}
+        confirmDisabled={Boolean(isDeletePending)}
+        onClose={onDeleteCancel}
+        onConfirm={onDeleteConfirm}
+        bodyRhythm="stack"
+        data-label-delete-confirm="angular-modal-confirm"
+        data-label-delete-confirm-owner="hertzbeat-ui-confirm-dialog"
+        data-label-delete-confirm-state={deleteTarget ? 'open' : 'closed'}
+        confirmButtonProps={{
+          'data-label-delete-confirm-submit': 'angular-modal-confirm',
+          'data-label-delete-confirm-close': 'angular-close-before-delete-result',
+          'data-label-delete-confirm-close-owner': 'route-state-contract'
+        } as React.ComponentProps<typeof HzConfirmDialog>['confirmButtonProps']}
+      >
+        <div data-label-delete-confirm-target={deleteTarget ? buildLabelDisplayName(deleteTarget) : ''}>
+          {deleteTarget ? buildLabelDisplayName(deleteTarget) : t('common.none')}
+        </div>
+      </HzConfirmDialog>
     </>
   );
 }
diff --git a/web-next/components/pages/plugin-manage-surface.test.tsx b/web-next/components/pages/plugin-manage-surface.test.tsx
index e36d828059..59f980c6b8 100644
--- a/web-next/components/pages/plugin-manage-surface.test.tsx
+++ b/web-next/components/pages/plugin-manage-surface.test.tsx
@@ -11,6 +11,7 @@ describe('plugin manage surface', () => {
     const t = createTranslatorMock({
       locale: 'zh-CN',
       overrides: {
+        'common.none': '无类型',
         'common.file.select': '选择文件'
       }
     });
@@ -32,7 +33,7 @@ describe('plugin manage surface', () => {
                 id: 2,
                 name: 'slack',
                 enableStatus: false,
-                items: [{ type: 'POST_ALERT' }],
+                items: [],
                 paramCount: 0
               }
             ],
@@ -48,8 +49,13 @@ describe('plugin manage surface', () => {
           jarFileName: 'smtp.jar',
           enableStatus: true
         }}
+        actionMessage="Edit Success!"
+        actionKind="enable"
+        isTogglePending
+        optimisticEnableStatus={{ 2: false }}
         onSearchChange={() => {}}
         onSearch={() => {}}
+        onSearchClear={() => {}}
         onRefresh={() => {}}
         onNew={() => {}}
         onDeleteSelected={() => {}}
@@ -67,20 +73,96 @@ describe('plugin manage surface', () => {
     expect(html).toContain('data-plugin-header="cold-compact-header"');
     expect(html).toContain('data-plugin-command-row="standard-equal-buttons"');
     expect(html).toContain('data-plugin-admin-layout="full-width-admin-list"');
+    expect(html).toContain('data-plugin-enable-feedback-contract="angular-edit-notify"');
+    expect(html).toContain('data-plugin-enable-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-plugin-enable-optimistic-contract="angular-toggle-mutates-row-before-put"');
+    expect(html).toContain('data-plugin-enable-optimistic-owner="route-state-contract"');
+    expect(html).toContain('data-plugin-enable-optimistic-state="overridden"');
+    expect(html).toContain('data-plugin-enable-optimistic-next-status="false"');
+    expect(html).toContain('data-plugin-table-loading-contract="angular-load-plugins-table"');
+    expect(html).toContain('data-plugin-table-loading-scope="load-search-refresh-pagination-mutation"');
+    expect(html).toContain('data-plugin-load-failure-contract="angular-console-only-shell"');
+    expect(html).toContain('data-plugin-load-failure-owner="plugin-route-controller"');
+    expect(html).toContain('data-plugin-load-failure="none"');
+    expect(html).toContain('data-plugin-enable-feedback="angular-edit-notify"');
+    expect(html).toContain('data-plugin-enable-feedback-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-plugin-toggle-loading-contract="angular-nz-table-loading"');
+    expect(html).toContain('data-plugin-toggle-loading-contract-owner="angular-nz-table-loading"');
+    expect(html).toContain('data-plugin-selection-reset-contract="angular-load-clears-selection"');
+    expect(html).toContain('data-plugin-selection-reset-owner="route-state-contract"');
+    expect(html).toContain('data-plugin-table-columns-contract="angular-five-column-edit-actions"');
+    expect(html).toContain('data-plugin-table-columns-owner="angular-nz-table"');
+    expect(html).toContain('data-plugin-table-stable-height-contract="viewport-fill-on-empty-or-short"');
+    expect(html).toContain('data-plugin-table-stable-height-owner="route-layout-contract"');
+    expect(html).toContain('data-plugin-table-loading="true"');
+    expect(html).toContain('data-plugin-table-loading-owner="angular-nz-table-loading"');
+    expect(html).toContain('aria-busy="true"');
+    expect(html).toContain('Edit Success!');
     expect(html).toContain('data-plugin-manage-toolbar="cold-table-toolbar"');
     expect(html).toContain('data-plugin-manage-search-owner="shared-search-row"');
+    expect(html).toContain('data-plugin-search-submit-contract="angular-enter-and-clear"');
+    expect(html).toContain('data-plugin-search-clear-contract="angular-cleared-load"');
+    expect(html).toContain('data-plugin-search-clear-owner="shared-search-row"');
     expect(html).toContain('data-cold-search-row-owner="cold-search-row"');
     expect(html).toContain('data-cold-search-input="fixed-width-direct"');
     expect(html).toContain('data-cold-search-control="direct-input"');
     expect(html).toContain('data-cold-search-chrome="no-extra-input-shell"');
     expect(html).toContain('data-cold-search-action="submit"');
+    expect(html).toContain('data-cold-search-action="clear"');
     expect(html).toContain('data-cold-search-trailing-actions="detached-secondary"');
     expect(html).toContain('data-plugin-manage-table-shell="cold-dense-table"');
+    expect(html).toContain('data-plugin-table-stable-height="viewport-fill-on-empty-or-short"');
+    expect(html).toContain('data-plugin-table-fill-stage="viewport-fill-on-empty-or-short"');
     expect(html).toContain('data-plugin-manage-table="cold-plugin-table"');
+    expect(html).toContain('data-plugin-table-columns="angular-five-column-edit-actions"');
+    expect(html).not.toContain('>参数</th>');
+    expect(html).toContain('data-plugin-pagination="cold-dense-pagination"');
+    expect(html).toContain('data-plugin-pagination-owner="hertzbeat-ui-pagination-bar"');
+    expect(html).toContain('data-plugin-pagination-contract="angular-search-pagination"');
+    expect(html).toContain('data-plugin-query-param-order-contract="angular-page-index-size-search"');
+    expect(html).toContain('data-plugin-query-param-order-owner="plugin-query-state"');
+    expect(html).toContain('data-hz-pagination-page-size="select-menu"');
+    expect(html).toContain('data-hz-pagination-page-jump="number-input"');
+    expect(html).toContain('data-plugin-pagination-page-jump-owner="hertzbeat-ui-input"');
+    expect(html).toContain('data-plugin-pagination-page-size-owner="hertzbeat-ui-select"');
     expect(html).toContain('data-plugin-select-all="cold-checkbox"');
+    expect(html).toContain('data-plugin-action-menu-clipping="none"');
+    expect(html).toContain('data-plugin-toolbar-delete-menu="angular-toolbar-ellipsis-delete"');
+    expect(html).toContain('data-plugin-toolbar-delete-menu-contract="angular-toolbar-ellipsis-delete"');
+    expect(html).toContain('data-plugin-toolbar-delete-menu-layer="overlay-visible-above-panel"');
+    expect(html).toContain('data-plugin-toolbar-delete-menu-clearance="floating-overlay-no-panel-crop"');
+    expect(html).toContain('data-plugin-toolbar-delete-menu-open="false"');
+    expect(html).toContain('aria-expanded="false"');
+    expect(html).toContain('data-plugin-toolbar-delete-menu-trigger="angular-toolbar-ellipsis-delete"');
+    expect(html).toContain('data-plugin-toolbar-delete-menu-panel="angular-toolbar-ellipsis-delete"');
+    expect(html).toContain('data-plugin-toolbar-delete-menu-layer-panel="overlay-visible-above-panel"');
+    expect(html).toContain('data-plugin-toolbar-delete-menu-clearance-panel="floating-overlay-no-panel-crop"');
+    expect(html).toContain('data-plugin-toolbar-delete-menu-owner="hertzbeat-ui-table-row-action-button"');
+    expect(html).toContain('data-plugin-row-delete-menu-contract="angular-ellipsis-dropdown-delete"');
+    expect(html).toContain('data-plugin-row-delete-menu-layer="overlay-visible-above-panel"');
+    expect(html).toContain('data-plugin-row-delete-menu-clearance="floating-overlay-no-panel-crop"');
+    expect(html).toContain('data-plugin-row-delete-menu-open="false"');
+    expect(html).toContain('data-plugin-row-delete-menu-layer-panel="overlay-visible-above-panel"');
+    expect(html).toContain('data-plugin-row-delete-menu-clearance-panel="floating-overlay-no-panel-crop"');
+    expect(html).toContain('data-plugin-row-delete-menu-owner="hertzbeat-ui-table-row-action-button"');
+    expect(html).toContain('data-plugin-row-delete-menu-panel-open="false"');
+    expect(html).toContain('data-plugin-delete-selected="angular-batch-delete-entry"');
+    expect(html).toContain('data-plugin-delete-selected-owner="hertzbeat-ui-table-row-action-button"');
     expect(html).toContain('data-plugin-row="1"');
     expect(html).toContain('data-plugin-row="2"');
-    expect(html).toContain('data-plugin-row-actions="cold-icon-actions"');
+    expect(html).toContain('data-plugin-row-delete-menu="1"');
+    expect(html).toContain('data-plugin-row-delete-menu-contract="angular-ellipsis-dropdown-delete"');
+    expect(html).toContain('data-plugin-row-delete-menu-trigger="1"');
+    expect(html).toContain('data-plugin-row-delete-menu-panel="1"');
+    expect(html).toContain('data-plugin-row-delete-menu-owner="hertzbeat-ui-table-row-action-button"');
+    expect(html).toContain('data-plugin-delete-one="1"');
+    expect(html).toContain('data-plugin-delete-one-owner="hertzbeat-ui-table-row-action-button"');
+    expect(html).toContain('data-plugin-row-actions="angular-row-actions-contextual"');
+    expect(html).toContain('data-plugin-row-actions-owner="cold-icon-actions"');
+    expect(html).toContain('data-plugin-row-action="params"');
+    expect(html).toContain('data-plugin-row-action="delete"');
+    expect(html).toContain('data-plugin-row-action-owner="row-contextual-icon-button"');
+    expect(html).toContain('data-plugin-row-action-label="smtp"');
     expect(html).not.toContain('data-plugin-summary-rail=');
     expect(html).not.toContain('data-cold-search-input-shell');
     expect(html).toContain('插件管理');
@@ -94,13 +176,29 @@ describe('plugin manage surface', () => {
     expect(html).toContain('slack');
     expect(html).toContain('告警后');
     expect(html).toContain('采集后');
+    expect(html).toContain('无类型');
     expect(html).toContain('已启用');
     expect(html).toContain('已停用');
     expect(html).toContain('已选择');
+    expect(html).toContain('第 1 / 1 页 · 1-2 / 2');
     expect(html).not.toContain('POST_ALERT');
     expect(html).not.toContain('POST_COLLECT');
-    expect(html).toContain('编辑参数');
+    expect(html).toContain('编辑插件参数 smtp');
+    expect(html).toContain('删除插件 smtp');
     expect(html).toContain('data-overlay-dialog="true"');
+    expect(html).toContain('data-overlay-dialog-mask-closable="false"');
+    expect(html).toContain('data-plugin-upload-mask-closable="false"');
+    expect(html).toContain('data-plugin-upload-width="angular-width-30-percent"');
+    expect(html).toContain('data-plugin-upload-modal-owner="angular-nz-modal"');
+    expect(html).toContain('data-plugin-upload-field-layout-contract="angular-label-8-control-14"');
+    expect(html).toContain('data-plugin-upload-field-layout-owner="route-form-field-grid"');
+    expect(html).toContain('data-plugin-upload-field-layout="angular-label-8-control-14"');
+    expect(html).toContain('data-plugin-upload-label-span="8"');
+    expect(html).toContain('data-plugin-upload-control-span="14"');
+    expect(html).toContain('data-plugin-upload-status-control="angular-nz-switch"');
+    expect(html).toContain('data-plugin-upload-status-control-owner="hertzbeat-ui-switch"');
+    expect(html).toContain('data-hz-ui="switch"');
+    expect(html).toContain('role="switch"');
     expect(html).toContain('Jar包');
     expect(html).toContain('选择文件');
     expect(html).toContain('取消');
@@ -143,12 +241,62 @@ describe('plugin manage surface', () => {
     );
 
     expect(html).toContain('data-plugin-manage-table-shell="cold-dense-table"');
+    expect(html).toContain('data-plugin-table-stable-height="viewport-fill-on-empty-or-short"');
+    expect(html).toContain('data-plugin-table-stable-height-owner="route-layout-contract"');
+    expect(html).toContain('data-plugin-table-fill-stage="viewport-fill-on-empty-or-short"');
     expect(html).toContain('data-plugin-manage-table="cold-plugin-table"');
+    expect(html).toContain('colSpan="5"');
     expect(html).toContain('data-plugin-manage-empty-state="cold-table-empty"');
     expect(html).toContain('data-plugin-manage-empty-icon="cold-empty-box"');
     expect(html).toContain('暂无数据');
   });
 
+  it('keeps the Angular plugin management shell mounted without showing backend load errors', async () => {
+    const { PluginManageSurface } = await import('./plugin-manage-surface');
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <PluginManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [],
+            totalElements: 0,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        selectedIds={[]}
+        draftPlugin={null}
+        loadError="backend refused plugin load"
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onRefresh={() => {}}
+        onNew={() => {}}
+        onDeleteSelected={() => {}}
+        onSelectedIdsChange={() => {}}
+        onEditParams={() => {}}
+        onToggleEnabled={() => {}}
+        onDeleteOne={() => {}}
+        onDraftChange={() => {}}
+        onCloseDialog={() => {}}
+        onSaveDialog={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-plugin-manage-surface="otlp-cold-plugin-console"');
+    expect(html).toContain('data-plugin-load-failure-contract="angular-console-only-shell"');
+    expect(html).toContain('data-plugin-load-failure-owner="plugin-route-controller"');
+    expect(html).toContain('data-plugin-load-failure="angular-console-only-shell"');
+    expect(html).toContain('data-plugin-manage-toolbar="cold-table-toolbar"');
+    expect(html).toContain('data-plugin-manage-table-shell="cold-dense-table"');
+    expect(html).toContain('data-plugin-manage-empty-state="cold-table-empty"');
+    expect(html).toContain('插件管理');
+    expect(html).not.toContain('backend refused plugin load');
+    expect(html).not.toContain('data-observability-status');
+  });
+
   it('keeps the plugin page on the cold visual owner instead of WorkbenchPage or alert primitives', () => {
     const source = readFileSync(resolve(process.cwd(), 'components/pages/plugin-manage-surface.tsx'), 'utf8');
 
@@ -160,10 +308,240 @@ describe('plugin manage surface', () => {
     expect(source).toContain('data-plugin-header="cold-compact-header"');
     expect(source).toContain('data-plugin-command-row="standard-equal-buttons"');
     expect(source).toContain('data-plugin-admin-layout="full-width-admin-list"');
+    expect(source).toContain('data-plugin-enable-feedback-contract="angular-edit-notify"');
+    expect(source).toContain('data-plugin-enable-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(source).toContain('data-plugin-enable-optimistic-contract="angular-toggle-mutates-row-before-put"');
+    expect(source).toContain('data-plugin-enable-optimistic-owner="route-state-contract"');
+    expect(source).toContain('const resolveEnableStatus = (plugin: Plugin) => (');
+    expect(source).toContain("data-plugin-enable-optimistic-state={hasOptimisticEnableStatus ? 'overridden' : 'source'}");
+    expect(source).toContain("data-plugin-enable-optimistic-next-status={displayEnableStatus ? 'true' : 'false'}");
+    expect(source).toContain('data-plugin-table-loading-contract="angular-load-plugins-table"');
+    expect(source).toContain('data-plugin-table-loading-scope="load-search-refresh-pagination-mutation"');
+    expect(source).toContain('data-plugin-load-failure-contract="angular-console-only-shell"');
+    expect(source).toContain('data-plugin-load-failure-owner="plugin-route-controller"');
+    expect(source).toContain("data-plugin-load-failure={loadError ? 'angular-console-only-shell' : 'none'}");
+    expect(source).toContain("actionKind === 'enable'");
+    expect(source).toContain('data-plugin-enable-feedback-owner="hertzbeat-ui-inline-feedback"');
+    expect(source).toContain('data-plugin-toggle-loading-contract="angular-nz-table-loading"');
+    expect(source).toContain('data-plugin-toggle-loading-contract-owner="angular-nz-table-loading"');
+    expect(source).toContain('data-plugin-selection-reset-contract="angular-load-clears-selection"');
+    expect(source).toContain('data-plugin-selection-reset-owner="route-state-contract"');
+    expect(source).toContain('data-plugin-table-columns-contract="angular-five-column-edit-actions"');
+    expect(source).toContain('data-plugin-table-columns-owner="angular-nz-table"');
+    expect(source).toContain('const tableLoading = Boolean(isLoadPending || isDeletePending || isTogglePending);');
+    expect(source).toContain("data-plugin-table-loading={tableLoading ? 'true' : 'false'}");
+    expect(source).toContain('data-plugin-table-loading-owner="angular-nz-table-loading"');
+    expect(source).toContain('data-plugin-delete-warning-contract="angular-no-select-warning"');
+    expect(source).toContain('data-plugin-delete-confirm-contract="angular-modal-confirm"');
+    expect(source).toContain('data-plugin-delete-confirm-contract-owner="hertzbeat-ui-confirm-dialog"');
+    expect(source).toContain('data-plugin-delete-feedback-contract="angular-delete-notify"');
+    expect(source).toContain('data-plugin-delete-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(source).toContain('data-plugin-delete-failure-contract="angular-delete-fail-notification"');
+    expect(source).toContain('data-plugin-delete-failure-owner="hertzbeat-ui-inline-feedback"');
+    expect(source).toContain('data-plugin-enable-failure-contract="angular-edit-fail-notification"');
+    expect(source).toContain('data-plugin-enable-failure-owner="hertzbeat-ui-inline-feedback"');
+    expect(source).toContain('data-plugin-delete-failure={isDeleteFailure ? \'angular-delete-fail-notification\' : undefined}');
+    expect(source).toContain('data-plugin-delete-feedback-title={');
+    expect(source).toContain("data-plugin-delete-feedback-detail={isDeleteFailure ? 'backend-message' : undefined}");
+    expect(source).toContain('data-plugin-enable-failure={isEnableFailure ? \'angular-edit-fail-notification\' : undefined}');
+    expect(source).toContain('data-plugin-enable-failure-owner={isEnableFailure ? \'hertzbeat-ui-inline-feedback\' : undefined}');
+    expect(source).toContain('data-plugin-enable-feedback-title={');
+    expect(source).toContain("data-plugin-enable-feedback-detail={isEnableFailure ? 'backend-message' : undefined}");
+    expect(source).toContain('data-plugin-delete-query-contract="angular-repeated-ids-query"');
+    expect(source).toContain('data-plugin-delete-query-owner="route-mutation-contract"');
+    expect(source).toContain('data-plugin-delete-page-clamp-contract="angular-update-page-index"');
+    expect(source).toContain('data-plugin-table-stable-height-contract="viewport-fill-on-empty-or-short"');
+    expect(source).toContain('data-plugin-table-stable-height-owner="route-layout-contract"');
+    expect(source).toContain('data-plugin-upload-validation-contract="angular-required-before-submit"');
+    expect(source).toContain('data-plugin-upload-invalid-submit-contract="angular-mark-dirty-keep-open"');
+    expect(source).toContain('data-plugin-upload-invalid-submit-owner="route-validation-contract"');
+    expect(source).toContain('data-plugin-upload-loading-contract="angular-nz-ok-loading"');
+    expect(source).toContain('data-plugin-upload-payload-contract="angular-form-data"');
+    expect(source).toContain('data-plugin-upload-name-payload-contract="angular-raw-name-no-trim"');
+    expect(source).toContain('data-plugin-upload-name-payload-owner="plugin-manage-controller"');
+    expect(source).toContain('data-plugin-upload-save-lifecycle-contract="angular-close-success-keep-open-fail"');
+    expect(source).toContain('data-plugin-upload-save-lifecycle-owner="route-state-contract"');
+    expect(source).toContain('data-plugin-upload-success-reset-contract="angular-reset-form-after-success"');
+    expect(source).toContain('data-plugin-upload-success-reset-owner="route-state-contract"');
+    expect(source).toContain('data-plugin-upload-cancel-contract="angular-cancel-preserves-form"');
+    expect(source).toContain('data-plugin-upload-cancel-contract-owner="route-state-contract"');
+    expect(source).toContain('data-plugin-upload-cancel-pending-contract="angular-cancel-allowed-during-ok-loading"');
+    expect(source).toContain('data-plugin-upload-cancel-pending-owner="angular-nz-modal"');
+    expect(source).toContain('data-plugin-upload-mask-closable-contract="angular-mask-closable-false"');
+    expect(source).toContain('data-plugin-upload-width-contract="angular-width-30-percent"');
+    expect(source).toContain('data-plugin-upload-field-layout-contract="angular-label-8-control-14"');
+    expect(source).toContain('data-plugin-upload-field-layout-owner="route-form-field-grid"');
+    expect(source).toContain('data-plugin-upload-field-layout="angular-label-8-control-14"');
+    expect(source).toContain('data-plugin-upload-label-span="8"');
+    expect(source).toContain('data-plugin-upload-control-span="14"');
+    expect(source).toContain('sm:grid-cols-[minmax(96px,8fr)_minmax(0,14fr)]');
+    expect(source).toContain('data-plugin-upload-status-control-contract="angular-nz-switch"');
+    expect(source).toContain('data-plugin-upload-status-control-owner="hertzbeat-ui-switch"');
+    expect(source).toContain('data-plugin-upload-file-remove-contract="angular-nz-remove-clears-jar"');
+    expect(source).toContain('data-plugin-upload-file-remove-owner="hertzbeat-ui-file-input"');
+    expect(source).toContain('data-plugin-upload-modal-owner="angular-nz-modal"');
+    expect(source).toContain('data-plugin-upload-feedback-contract="angular-new-notify"');
+    expect(source).toContain('data-plugin-upload-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(source).toContain('data-plugin-upload-failure-contract="angular-new-fail-notification"');
+    expect(source).toContain('data-plugin-upload-failure-owner="hertzbeat-ui-inline-feedback"');
+    expect(source).toContain("const isUploadFailure = actionKind === 'upload' && Boolean(actionError);");
+    expect(source).toContain("const isParamFailure = actionKind === 'params' && Boolean(actionError);");
+    expect(source).toContain("? t('common.notify.edit-fail')");
+    expect(source).toContain('const actionFeedbackMeta = isUploadFailure || isParamFailure || isEnableFailure || isDeleteFailure ? actionMeta ?? actionError ?? undefined : undefined;');
+    expect(source).toContain('data-plugin-upload-feedback-title={');
+    expect(source).toContain('data-plugin-upload-feedback-detail={');
+    expect(source).toContain('HzFileInput');
+    expect(source).toContain('data-plugin-upload-open="angular-upload-modal"');
+    expect(source).toContain('data-plugin-upload-file-input="angular-jar-before-upload"');
+    expect(source).toContain('data-plugin-upload-name-payload="angular-raw-name-no-trim"');
+    expect(source).toContain('data-plugin-upload-file-list="angular-before-upload-single-replace"');
+    expect(source).toContain('data-plugin-upload-cancel-pending="angular-cancel-allowed-during-ok-loading"');
+    expect(source).toContain('event.target.files?.[0]');
+    expect(source).toContain("pluginJarInputRef.current.value = '';");
+    expect(source).toContain('data-plugin-upload-file-remove="angular-nz-remove-clears-jar"');
+    expect(source).toContain("jarFileName: ''");
+    expect(source).toContain('data-plugin-upload-save="angular-form-data"');
+    expect(source).toContain('open={Boolean((isUploadDialogOpen ?? Boolean(draftPlugin)) && draftPlugin)}');
+    expect(source).toContain('data-plugin-upload-dialog="angular-cancel-preserves-form"');
+    expect(source).toContain('data-plugin-upload-mask-closable');
+    expect(source).toContain('data-plugin-upload-width');
+    expect(source).toContain('max-w-[min(92vw,30rem)] lg:max-w-[30vw]');
+    expect(source).toContain("data-plugin-upload-ok-loading={isUploadPending ? 'true' : 'false'}");
+    expect(source).toContain('data-plugin-upload-ok-loading-owner="angular-nz-ok-loading"');
+    expect(source).toContain('data-plugin-param-edit-contract="angular-params-define-modal"');
+    expect(source).toContain('data-plugin-param-action-visibility-contract="angular-paramcount-zero-only"');
+    expect(source).toContain('data-plugin-param-action-visibility-owner="plugin-view-model"');
+    expect(source).toContain('data-plugin-param-mask-closable-contract="angular-mask-closable-false"');
+    expect(source).toContain('data-plugin-param-modal-owner="angular-nz-modal"');
+    expect(source).toContain('data-plugin-param-mask-closable');
+    expect(source).toContain('data-plugin-param-save-contract="angular-params-post"');
+    expect(source).toContain('data-plugin-param-payload-contract="angular-object-values-payload"');
+    expect(source).toContain('data-plugin-param-save-lifecycle-contract="angular-close-success-keep-open-fail"');
+    expect(source).toContain('data-plugin-param-save-lifecycle-owner="route-state-contract"');
+    expect(source).toContain('data-plugin-param-save-loading-contract="angular-no-ok-loading"');
+    expect(source).toContain('data-plugin-param-save-loading-owner="angular-modal-ok-contract"');
+    expect(source).toContain('data-plugin-param-save-loading="angular-no-ok-loading"');
+    expect(source).toContain("data-plugin-param-save-pending={isParamPending ? 'true' : 'false'}");
+    expect(source).toContain('data-plugin-param-payload="angular-object-values-payload"');
+    expect(source).toContain('data-plugin-param-save-lifecycle="angular-close-success-keep-open-fail"');
+    expect(source).toContain('data-plugin-param-feedback-contract="angular-edit-notify"');
+    expect(source).toContain('data-plugin-param-failure-contract="angular-edit-fail-notification"');
+    expect(source).toContain('data-plugin-param-failure-owner="hertzbeat-ui-inline-feedback"');
+    expect(source).toContain("data-plugin-param-failure={isParamFailure ? 'angular-edit-fail-notification' : undefined}");
+    expect(source).toContain("data-plugin-param-feedback-title={");
+    expect(source).toContain("data-plugin-param-feedback-detail={isParamFailure ? 'backend-message' : undefined}");
+    expect(source).toContain('data-plugin-param-pending-editable-contract="angular-controls-remain-enabled"');
+    expect(source).toContain('data-plugin-param-pending-editable-owner="angular-param-modal"');
+    expect(source).toContain("data-plugin-param-pending-editable={isParamPending ? 'angular-controls-remain-enabled' : 'idle'}");
+    expect(source).not.toContain('disabled={isParamPending}');
+    expect(source).toContain('data-plugin-param-width-contract="angular-default-modal-width"');
+    expect(source).toContain('data-plugin-param-width-owner="angular-nz-modal"');
+    expect(source).toContain('maxWidthClassName="max-w-[min(92vw,520px)]"');
+    expect(source).toContain("'data-plugin-param-width': 'angular-default-modal-width'");
+    expect(source).toContain("'data-plugin-param-width-owner': 'angular-nz-modal'");
+    expect(source).not.toContain('maxWidthClassName="max-w-3xl"');
+    expect(source).toContain('HzRadioButtonGroup');
+    expect(source).toContain('data-plugin-param-radio-contract="angular-nz-radio-group"');
+    expect(source).toContain('data-plugin-param-radio-owner="hertzbeat-ui-radio-button-group"');
+    expect(source).toContain("define.type === 'radio' && (define.options ?? []).length > 0");
+    expect(source).toContain('data-plugin-param-radio="angular-nz-radio-group"');
+    expect(source).toContain('data-plugin-param-radio-field={define.field}');
+    expect(source).toContain('data-plugin-param-textarea-contract="angular-textarea-rows-8"');
+    expect(source).toContain('data-plugin-param-textarea-owner="hertzbeat-ui-textarea"');
+    expect(source).toContain('function getPluginParamTextareaRows(define: ParamDefine): 4 | 8');
+    expect(source).toContain("return define.type === 'json' || define.type === 'textarea' ? 8 : 4;");
+    expect(source).toContain('rows={getPluginParamTextareaRows(define)}');
+    expect(source).toContain("data-plugin-param-textarea={define.type === 'textarea' ? 'angular-textarea-rows-8' : undefined}");
+    expect(source).toContain("data-plugin-param-textarea-rows={define.type === 'textarea' ? '8' : undefined}");
+    expect(source).toContain('data-plugin-param-field-layout-contract="angular-label-7-control-8"');
+    expect(source).toContain('data-plugin-param-field-layout-owner="hertzbeat-ui-monitor-editor-field-grid"');
+    expect(source).toContain('data-plugin-param-form-owner="hertzbeat-ui-monitor-editor-field-grid"');
+    expect(source).toContain("t('setting.plugins.action.params-aria', actionLabelParams)");
+    expect(source).toContain("t('setting.plugins.action.delete-aria', actionLabelParams)");
+    expect(source).toContain('data-plugin-param-edit-open={String(original.id)}');
+    expect(source).toContain('data-plugin-row-actions="angular-row-actions-contextual"');
+    expect(source).toContain('data-plugin-row-actions-owner="cold-icon-actions"');
+    expect(source).toContain('data-plugin-row-action-owner="row-contextual-icon-button"');
+    expect(source).toContain('data-plugin-row-action-label={row.name}');
+    expect(source).toContain('const [openRowActionMenuId, setOpenRowActionMenuId] = React.useState<string | null>(null)');
+    expect(source).toContain('const rowActionMenuId = String(original.id);');
+    expect(source).toContain('const isRowActionMenuOpen = openRowActionMenuId === rowActionMenuId;');
+    expect(source).toContain('data-plugin-row-delete-menu={rowActionMenuId}');
+    expect(source).toContain('data-plugin-row-delete-menu-contract="angular-ellipsis-dropdown-delete"');
+    expect(source).toContain('data-plugin-row-delete-menu-layer="overlay-visible-above-panel"');
+    expect(source).toContain("data-plugin-row-delete-menu-open={isRowActionMenuOpen ? 'true' : 'false'}");
+    expect(source).toContain('aria-expanded={isRowActionMenuOpen}');
+    expect(source).toContain('setOpenRowActionMenuId(current => (current === rowActionMenuId ? null : rowActionMenuId))');
+    expect(source).toContain('data-plugin-row-delete-menu-trigger={rowActionMenuId}');
+    expect(source).toContain('hidden={!isRowActionMenuOpen}');
+    expect(source).toContain('data-plugin-row-delete-menu-panel={rowActionMenuId}');
+    expect(source).toContain('data-plugin-row-delete-menu-layer-panel="overlay-visible-above-panel"');
+    expect(source).toContain('data-plugin-row-delete-menu-owner="hertzbeat-ui-table-row-action-button"');
+    expect(source).toContain("data-plugin-row-delete-menu-panel-open={isRowActionMenuOpen ? 'true' : 'false'}");
+    expect(source).toContain('setOpenRowActionMenuId(null);');
+    expect(source).not.toContain('<details');
+    expect(source).not.toContain('</details>');
+    expect(source).toContain('data-plugin-param-dialog="angular-params-define-modal"');
+    expect(source).toContain('data-plugin-param-form="angular-dynamic-form-field"');
+    expect(source).toContain('data-plugin-param-field-layout="angular-label-7-control-8"');
+    expect(source).toContain('data-plugin-param-label-span="7"');
+    expect(source).toContain('data-plugin-param-control-span="8"');
+    expect(source).toContain('data-plugin-param-save="angular-params-post"');
+    expect(source).toContain('HzMonitorEditorFieldGrid');
+    expect(source).toContain('HzTextarea');
+    expect(source).toContain('HzPaginationBar');
+    expect(source).toContain('pageSizeOptions = [8, 15, 25]');
+    expect(source).toContain('data-plugin-pagination="cold-dense-pagination"');
+    expect(source).toContain('data-plugin-pagination-owner="hertzbeat-ui-pagination-bar"');
+    expect(source).toContain('data-plugin-pagination-contract="angular-search-pagination"');
+    expect(source).toContain('data-plugin-query-param-order-contract="angular-page-index-size-search"');
+    expect(source).toContain('data-plugin-query-param-order-owner="plugin-query-state"');
+    expect(source).toContain("t('setting.plugins.pagination.summary'");
+    expect(source).toContain("t('setting.plugins.pagination.page-size')");
+    expect(source).toContain("t('setting.plugins.pagination.page')");
+    expect(source).toContain('data-plugin-pagination-page-jump-owner');
+    expect(source).toContain('data-plugin-pagination-page-size-owner');
+    expect(source).toContain('data-plugin-delete-feedback={');
+    expect(source).toContain('HzTableRowActionButton');
+    expect(source).toContain('data-plugin-toolbar-delete-menu="angular-toolbar-ellipsis-delete"');
+    expect(source).toContain('data-plugin-toolbar-delete-menu-contract="angular-toolbar-ellipsis-delete"');
+    expect(source).toContain('data-plugin-toolbar-delete-menu-layer="overlay-visible-above-panel"');
+    expect(source).toContain('data-plugin-toolbar-delete-menu-clearance="floating-overlay-no-panel-crop"');
+    expect(source).toContain('data-plugin-toolbar-delete-menu-open={isToolbarActionMenuOpen ?');
+    expect(source).toContain('setToolbarActionMenuOpen(open => !open)');
+    expect(source).toContain('data-plugin-toolbar-delete-menu-trigger="angular-toolbar-ellipsis-delete"');
+    expect(source).toContain('data-plugin-toolbar-delete-menu-panel="angular-toolbar-ellipsis-delete"');
+    expect(source).toContain('data-plugin-toolbar-delete-menu-layer-panel="overlay-visible-above-panel"');
+    expect(source).toContain('data-plugin-toolbar-delete-menu-clearance-panel="floating-overlay-no-panel-crop"');
+    expect(source).toContain('data-plugin-toolbar-delete-menu-owner="hertzbeat-ui-table-row-action-button"');
+    expect(source).toContain('data-plugin-row-delete-menu-contract="angular-ellipsis-dropdown-delete"');
+    expect(source).toContain('data-plugin-row-delete-menu-clearance="floating-overlay-no-panel-crop"');
+    expect(source).toContain('data-plugin-row-delete-menu-owner="hertzbeat-ui-table-row-action-button"');
+    expect(source).toContain('pluginActionMenuRootClassName');
+    expect(source).toContain('pluginActionMenuPanelBaseClassName');
+    expect(source).toContain('data-plugin-action-menu-clipping="none"');
+    expect(source).toContain("style={{ overflow: 'visible' }}");
+    expect(source).toContain('z-[9999] inline-block overflow-visible');
+    expect(source).toContain('mt-2 z-[9999] min-w-[132px]');
+    expect(source).toContain('data-plugin-delete-selected="angular-batch-delete-entry"');
+    expect(source).toContain('data-plugin-delete-selected-owner="hertzbeat-ui-table-row-action-button"');
+    expect(source).toContain('data-plugin-delete-one={String(original.id)}');
+    expect(source).toContain('data-plugin-delete-one-owner="hertzbeat-ui-table-row-action-button"');
+    expect(source).toContain('HzConfirmDialog');
+    expect(source).toContain('HzInlineFeedback');
     expect(source).toContain('data-plugin-manage-toolbar="cold-table-toolbar"');
     expect(source).toContain('data-plugin-manage-search-owner="shared-search-row"');
+    expect(source).toContain('data-plugin-search-submit-contract="angular-enter-and-clear"');
+    expect(source).toContain('data-plugin-search-clear-contract="angular-cleared-load"');
+    expect(source).toContain('data-plugin-search-clear-owner="shared-search-row"');
+    expect(source).toContain('onClear={onSearchClear}');
     expect(source).toContain('data-plugin-manage-table-shell="cold-dense-table"');
+    expect(source).toContain('data-plugin-table-stable-height="viewport-fill-on-empty-or-short"');
+    expect(source).toContain('data-plugin-table-fill-stage="viewport-fill-on-empty-or-short"');
     expect(source).toContain('data-plugin-manage-table="cold-plugin-table"');
+    expect(source).toContain('data-plugin-table-columns="angular-five-column-edit-actions"');
+    expect(source).toContain('colSpan={5}');
+    expect(source).not.toContain("t('setting.plugins.table.params')");
     expect(source).toContain('data-plugin-manage-empty-state="cold-table-empty"');
     expect(source).not.toContain('data-plugin-summary-rail');
     expect(source).not.toContain('coldPluginVisual.search.row');
@@ -207,7 +585,7 @@ describe('plugin manage surface', () => {
     expect(source).not.toContain('className="inline-flex min-h-7 items-center rounded-[2px] border border-[var(--ops-border-color)] bg-[var(--ops-surface-raised)] px-2 text-[12px] text-[var(--ops-text-primary)]"');
   });
 
-  it('uses the shared cold checkbox for table selection and draft enable state', async () => {
+  it('uses cold checkboxes for table selection and the shared switch for draft enable state', async () => {
     const { PluginManageSurface } = await import('./plugin-manage-surface');
     const t = createTranslatorMock({ locale: 'zh-CN' });
 
@@ -262,17 +640,656 @@ describe('plugin manage surface', () => {
     const source = readFileSync(resolve(process.cwd(), 'components/pages/plugin-manage-surface.tsx'), 'utf8');
 
     expect(source).toContain("from '../ui/checkbox'");
+    expect(source).toContain('HzSwitch');
     expect(source).toContain('data-plugin-select-all="cold-checkbox"');
     expect(source).toContain('data-plugin-row-select={String(original.id)}');
-    expect(source).toContain('data-plugin-draft-enabled-checkbox="cold-checkbox"');
+    expect(source).toContain('data-plugin-upload-status-control="angular-nz-switch"');
     expect(source).not.toContain('className="h-3.5 w-3.5 accent-[#4e74f8]"');
     expect(html).toContain('data-plugin-select-all="cold-checkbox"');
     expect(html).toContain('data-plugin-row-select="11"');
     expect(html).toContain('data-plugin-row-select="12"');
-    expect(html).toContain('data-plugin-draft-enabled-checkbox="cold-checkbox"');
-    expect(html.match(/data-cold-checkbox-owner="cold-checkbox"/g)?.length ?? 0).toBe(4);
-    expect(html.match(/data-cold-checkbox-control="native-hidden"/g)?.length ?? 0).toBe(4);
-    expect(html.match(/data-cold-checkbox-box="indicator"/g)?.length ?? 0).toBe(4);
-    expect(html).toContain('data-cold-checkbox-label="true"');
+    expect(html).toContain('data-plugin-upload-status-control="angular-nz-switch"');
+    expect(html).toContain('data-plugin-upload-status-control-owner="hertzbeat-ui-switch"');
+    expect(html).toContain('data-hz-ui="switch"');
+    expect(html.match(/data-cold-checkbox-owner="cold-checkbox"/g)?.length ?? 0).toBe(3);
+    expect(html.match(/data-cold-checkbox-control="native-hidden"/g)?.length ?? 0).toBe(3);
+    expect(html.match(/data-cold-checkbox-box="indicator"/g)?.length ?? 0).toBe(3);
+    expect(html).toContain('data-hz-switch-owner="hertzbeat-ui-switch"');
+  });
+
+  it('renders Angular plugin delete warning, confirmation, feedback, and page-clamp contracts through shared UI', async () => {
+    const { PluginManageSurface } = await import('./plugin-manage-surface');
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <PluginManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [
+              {
+                id: 21,
+                name: 'webhook',
+                enableStatus: true,
+                items: [{ type: 'POST_ALERT' }],
+                paramCount: 0
+              }
+            ],
+            totalElements: 1,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        selectedIds={[]}
+        draftPlugin={null}
+        actionError="请先选择要删除的监控"
+        actionTone="warning"
+        actionKind="delete"
+        deleteTarget={{
+          ids: [21],
+          label: 'webhook',
+          mode: 'single'
+        }}
+        isDeletePending
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onRefresh={() => {}}
+        onNew={() => {}}
+        onDeleteSelected={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onSelectedIdsChange={() => {}}
+        onEditParams={() => {}}
+        onToggleEnabled={() => {}}
+        onDeleteOne={() => {}}
+        onDraftChange={() => {}}
+        onCloseDialog={() => {}}
+        onSaveDialog={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-plugin-delete-warning-contract="angular-no-select-warning"');
+    expect(html).toContain('data-plugin-delete-confirm-contract="angular-modal-confirm"');
+    expect(html).toContain('data-plugin-delete-confirm-contract-owner="hertzbeat-ui-confirm-dialog"');
+    expect(html).toContain('data-plugin-delete-feedback-contract="angular-delete-notify"');
+    expect(html).toContain('data-plugin-delete-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-plugin-delete-query-contract="angular-repeated-ids-query"');
+    expect(html).toContain('data-plugin-delete-query-owner="route-mutation-contract"');
+    expect(html).toContain('data-plugin-delete-page-clamp-contract="angular-update-page-index"');
+    expect(html).toContain('data-plugin-query-param-order-contract="angular-page-index-size-search"');
+    expect(html).toContain('data-plugin-query-param-order-owner="plugin-query-state"');
+    expect(html).toContain('data-plugin-table-stable-height-contract="viewport-fill-on-empty-or-short"');
+    expect(html).toContain('data-plugin-table-stable-height-owner="route-layout-contract"');
+    expect(html).toContain('data-plugin-delete-feedback="angular-no-select-warning"');
+    expect(html).toContain('data-plugin-delete-feedback-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-plugin-delete-confirm="angular-modal-confirm"');
+    expect(html).toContain('data-plugin-delete-confirm-owner="hertzbeat-ui-confirm-dialog"');
+    expect(html).toContain('data-plugin-delete-confirm-submit="angular-modal-confirm"');
+    expect(html).toContain('data-plugin-delete-confirm-target="webhook"');
+    expect(html).toContain('请先选择要删除的监控');
+    expect(html).toContain('请确认是否删除!');
+  });
+
+  it('renders Angular plugin upload required validation, jar file input, and save feedback contracts', async () => {
+    const { PluginManageSurface } = await import('./plugin-manage-surface');
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <PluginManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [],
+            totalElements: 0,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        selectedIds={[]}
+        draftPlugin={{
+          name: '',
+          jarFileName: '',
+          jarFile: null,
+          enableStatus: true
+        }}
+        actionMessage="Add Success!"
+        actionKind="upload"
+        uploadValidation={{ name: true, jarFile: true }}
+        isUploadPending
+        deleteTarget={null}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onRefresh={() => {}}
+        onNew={() => {}}
+        onDeleteSelected={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onSelectedIdsChange={() => {}}
+        onEditParams={() => {}}
+        onToggleEnabled={() => {}}
+        onDeleteOne={() => {}}
+        onDraftChange={() => {}}
+        onCloseDialog={() => {}}
+        onSaveDialog={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-plugin-upload-validation-contract="angular-required-before-submit"');
+    expect(html).toContain('data-plugin-upload-invalid-submit-contract="angular-mark-dirty-keep-open"');
+    expect(html).toContain('data-plugin-upload-invalid-submit-owner="route-validation-contract"');
+    expect(html).toContain('data-plugin-upload-loading-contract="angular-nz-ok-loading"');
+    expect(html).toContain('data-plugin-upload-payload-contract="angular-form-data"');
+    expect(html).toContain('data-plugin-upload-name-payload-contract="angular-raw-name-no-trim"');
+    expect(html).toContain('data-plugin-upload-name-payload-owner="plugin-manage-controller"');
+    expect(html).toContain('data-plugin-upload-save-lifecycle-contract="angular-close-success-keep-open-fail"');
+    expect(html).toContain('data-plugin-upload-save-lifecycle-owner="route-state-contract"');
+    expect(html).toContain('data-plugin-upload-success-reset-contract="angular-reset-form-after-success"');
+    expect(html).toContain('data-plugin-upload-success-reset-owner="route-state-contract"');
+    expect(html).toContain('data-plugin-upload-cancel-contract="angular-cancel-preserves-form"');
+    expect(html).toContain('data-plugin-upload-cancel-contract-owner="route-state-contract"');
+    expect(html).toContain('data-plugin-upload-cancel-pending-contract="angular-cancel-allowed-during-ok-loading"');
+    expect(html).toContain('data-plugin-upload-cancel-pending-owner="angular-nz-modal"');
+    expect(html).toContain('data-plugin-upload-feedback-contract="angular-new-notify"');
+    expect(html).toContain('data-plugin-upload-feedback-contract-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-plugin-upload-failure-contract="angular-new-fail-notification"');
+    expect(html).toContain('data-plugin-upload-failure-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-plugin-upload-field-layout-contract="angular-label-8-control-14"');
+    expect(html).toContain('data-plugin-upload-field-layout-owner="route-form-field-grid"');
+    expect(html).toContain('data-plugin-upload-field="name"');
+    expect(html).toContain('data-plugin-upload-field="jarFile"');
+    expect(html).toContain('data-plugin-upload-cancel-pending="angular-cancel-allowed-during-ok-loading"');
+    expect(html).toContain('data-plugin-upload-field="enableStatus"');
+    expect(html).toContain('data-plugin-upload-field-layout="angular-label-8-control-14"');
+    expect(html).toContain('data-plugin-upload-label-span="8"');
+    expect(html).toContain('data-plugin-upload-control-span="14"');
+    expect(html).toContain('data-plugin-upload-feedback="angular-new-notify"');
+    expect(html).toContain('data-plugin-upload-feedback-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-plugin-upload-name-input="angular-required"');
+    expect(html).toContain('data-plugin-upload-name-payload="angular-raw-name-no-trim"');
+    expect(html).toContain('data-plugin-upload-file-input="angular-jar-before-upload"');
+    expect(html).toContain('data-plugin-upload-file-input-owner="hertzbeat-ui-file-input"');
+    expect(html).toContain('data-plugin-upload-file-list="angular-before-upload-single-replace"');
+    expect(html).toContain('data-plugin-upload-file-list-owner="hertzbeat-ui-file-input"');
+    expect(html).toContain('data-plugin-upload-file-remove-contract="angular-nz-remove-clears-jar"');
+    expect(html).toContain('data-plugin-upload-file-remove-owner="hertzbeat-ui-file-input"');
+    expect(html).toContain('data-plugin-upload-file-remove="angular-nz-remove-clears-jar"');
+    expect(html).toContain('data-plugin-upload-file-remove-state="empty"');
+    expect(html).toContain('data-plugin-upload-file-trigger="angular-jar-before-upload"');
+    expect(html).toContain('data-plugin-upload-file-name="angular-jar-before-upload"');
+    expect(html).toContain('data-plugin-upload-status-field="angular-nz-switch"');
+    expect(html).toContain('data-plugin-upload-status-control="angular-nz-switch"');
+    expect(html).toContain('data-plugin-upload-status-control-owner="hertzbeat-ui-switch"');
+    expect(html).toContain('data-plugin-upload-dialog="angular-cancel-preserves-form"');
+    expect(html).toContain('data-plugin-upload-invalid-submit="angular-mark-dirty-keep-open"');
+    expect(html).toContain('data-plugin-upload-invalid-submit-owner="route-validation-contract"');
+    expect(html).toContain('data-plugin-upload-save="angular-form-data"');
+    expect(html).toContain('data-plugin-upload-save-lifecycle="angular-close-success-keep-open-fail"');
+    expect(html).toContain('data-plugin-upload-save-lifecycle-owner="route-state-contract"');
+    expect(html).toContain('data-plugin-upload-ok-loading="true"');
+    expect(html).toContain('data-plugin-upload-ok-loading-owner="angular-nz-ok-loading"');
+    expect(html).toContain('aria-busy="true"');
+    expect(html).toContain('data-plugin-upload-validation="name-required"');
+    expect(html).toContain('data-plugin-upload-validation="jar-required"');
+    expect(html).toContain('Add Success!');
+    expect(html).toContain('请填充必填项!');
+  });
+
+  it('renders Angular plugin upload failure title with backend detail', async () => {
+    const { PluginManageSurface } = await import('./plugin-manage-surface');
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <PluginManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [],
+            totalElements: 0,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        selectedIds={[]}
+        draftPlugin={null}
+        actionError="backend rejected duplicate plugin"
+        actionKind="upload"
+        deleteTarget={null}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onRefresh={() => {}}
+        onNew={() => {}}
+        onDeleteSelected={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onSelectedIdsChange={() => {}}
+        onEditParams={() => {}}
+        onToggleEnabled={() => {}}
+        onDeleteOne={() => {}}
+        onDraftChange={() => {}}
+        onCloseDialog={() => {}}
+        onSaveDialog={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-plugin-upload-feedback="angular-new-fail"');
+    expect(html).toContain('data-plugin-upload-feedback-title="common.notify.new-fail"');
+    expect(html).toContain('data-plugin-upload-feedback-detail="backend-message"');
+    expect(html).toContain('新增失败!');
+    expect(html).toContain('backend rejected duplicate plugin');
+  });
+
+  it('renders Angular plugin delete failure title with backend detail', async () => {
+    const { PluginManageSurface } = await import('./plugin-manage-surface');
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <PluginManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [],
+            totalElements: 0,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        selectedIds={[]}
+        draftPlugin={null}
+        actionError="删除失败!"
+        actionMeta="backend refused plugin delete"
+        actionKind="delete"
+        deleteTarget={null}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onRefresh={() => {}}
+        onNew={() => {}}
+        onDeleteSelected={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onSelectedIdsChange={() => {}}
+        onEditParams={() => {}}
+        onToggleEnabled={() => {}}
+        onDeleteOne={() => {}}
+        onDraftChange={() => {}}
+        onCloseDialog={() => {}}
+        onSaveDialog={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-plugin-delete-feedback="angular-delete-fail"');
+    expect(html).toContain('data-plugin-delete-failure="angular-delete-fail-notification"');
+    expect(html).toContain('data-plugin-delete-failure-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-plugin-delete-feedback-title="common.notify.delete-fail"');
+    expect(html).toContain('data-plugin-delete-feedback-detail="backend-message"');
+    expect(html).toContain('删除失败!');
+    expect(html).toContain('backend refused plugin delete');
+  });
+
+  it('renders Angular plugin enable failure title with backend detail', async () => {
+    const { PluginManageSurface } = await import('./plugin-manage-surface');
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <PluginManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [],
+            totalElements: 0,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        selectedIds={[]}
+        draftPlugin={null}
+        actionError="修改失败!"
+        actionMeta="backend refused plugin toggle"
+        actionKind="enable"
+        deleteTarget={null}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onRefresh={() => {}}
+        onNew={() => {}}
+        onDeleteSelected={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onSelectedIdsChange={() => {}}
+        onEditParams={() => {}}
+        onToggleEnabled={() => {}}
+        onDeleteOne={() => {}}
+        onDraftChange={() => {}}
+        onCloseDialog={() => {}}
+        onSaveDialog={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-plugin-enable-feedback="angular-edit-fail"');
+    expect(html).toContain('data-plugin-enable-failure="angular-edit-fail-notification"');
+    expect(html).toContain('data-plugin-enable-failure-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-plugin-enable-feedback-title="common.notify.edit-fail"');
+    expect(html).toContain('data-plugin-enable-feedback-detail="backend-message"');
+    expect(html).toContain('修改失败!');
+    expect(html).toContain('backend refused plugin toggle');
+  });
+
+  it('renders Angular plugin parameter failure title with backend detail', async () => {
+    const { PluginManageSurface } = await import('./plugin-manage-surface');
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <PluginManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [],
+            totalElements: 0,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        selectedIds={[]}
+        draftPlugin={null}
+        actionError="backend rejected plugin params"
+        actionKind="params"
+        deleteTarget={null}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onRefresh={() => {}}
+        onNew={() => {}}
+        onDeleteSelected={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onSelectedIdsChange={() => {}}
+        onEditParams={() => {}}
+        onToggleEnabled={() => {}}
+        onDeleteOne={() => {}}
+        onDraftChange={() => {}}
+        onCloseDialog={() => {}}
+        onSaveDialog={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-plugin-param-feedback="angular-edit-fail"');
+    expect(html).toContain('data-plugin-param-failure="angular-edit-fail-notification"');
+    expect(html).toContain('data-plugin-param-failure-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-plugin-param-feedback-title="common.notify.edit-fail"');
+    expect(html).toContain('data-plugin-param-feedback-detail="backend-message"');
+    expect(html).toContain('修改失败!');
+    expect(html).toContain('backend rejected plugin params');
+  });
+
+  it('renders Angular plugin parameter define modal and save feedback contracts', async () => {
+    const { PluginManageSurface } = await import('./plugin-manage-surface');
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+
+    const html = renderToStaticMarkup(
+      <PluginManageSurface
+        t={t}
+        data={{
+          list: {
+            content: [
+              {
+                id: 31,
+                name: 'webhook',
+                enableStatus: true,
+                items: [{ type: 'POST_ALERT' }],
+                paramCount: 2
+              }
+            ],
+            totalElements: 1,
+            pageIndex: 0,
+            pageSize: 8
+          }
+        }}
+        search=""
+        selectedIds={[]}
+        draftPlugin={null}
+        paramDraft={{
+          plugin: { id: 31, name: 'webhook', enableStatus: true } as any,
+          paramDefines: [
+            { field: 'endpoint', type: 'text', name: '端点', required: true },
+            { field: 'authType', type: 'radio', name: '认证类型', options: [{ label: 'Basic', value: 'basic' }, { label: 'Bearer', value: 'bearer' }] },
+            { field: 'script', type: 'textarea', name: '脚本' },
+            { field: 'payload', type: 'json', name: 'JSON' },
+            { field: 'headers', type: 'key-value', name: '请求头' },
+            { field: 'labels', type: 'labels', name: '标签' },
+            { field: 'selector', type: 'label-selector', name: '标签选择器' },
+            { field: 'metrics', type: 'metrics-field', name: '指标字段' },
+            { field: 'recipients', type: 'array', name: '收件人' }
+          ],
+          params: {
+            endpoint: { pluginMetadataId: 31, type: 1, field: 'endpoint', paramValue: 'https://hooks.example' },
+            authType: { pluginMetadataId: 31, type: 2, field: 'authType', paramValue: 'basic' },
+            script: { pluginMetadataId: 31, type: 2, field: 'script', paramValue: 'line one\nline two' },
+            payload: { pluginMetadataId: 31, type: 3, field: 'payload', paramValue: '{"ok":true}' },
+            headers: { pluginMetadataId: 31, type: 2, field: 'headers', paramValue: '{"Authorization":"Bearer token"}' },
+            labels: { pluginMetadataId: 31, type: 2, field: 'labels', paramValue: { env: 'prod' } },
+            selector: { pluginMetadataId: 31, type: 2, field: 'selector', paramValue: { service: 'checkout', severity: 'critical' } },
+            metrics: { pluginMetadataId: 31, type: 2, field: 'metrics', paramValue: [{ field: 'usage', unit: '%', type: 'number' }] },
+            recipients: { pluginMetadataId: 31, type: 2, field: 'recipients', paramValue: 'alpha,beta' }
+          }
+        }}
+        actionMessage="Edit Success!"
+        actionKind="params"
+        isParamPending
+        deleteTarget={null}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onRefresh={() => {}}
+        onNew={() => {}}
+        onDeleteSelected={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onSelectedIdsChange={() => {}}
+        onEditParams={() => {}}
+        onParamChange={() => {}}
+        onCloseParamDialog={() => {}}
+        onSaveParamDialog={() => {}}
+        onToggleEnabled={() => {}}
+        onDeleteOne={() => {}}
+        onDraftChange={() => {}}
+        onCloseDialog={() => {}}
+        onSaveDialog={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-plugin-param-edit-contract="angular-params-define-modal"');
+    expect(html).toContain('data-plugin-param-action-visibility-contract="angular-paramcount-zero-only"');
+    expect(html).toContain('data-plugin-param-action-visibility-owner="plugin-view-model"');
+    expect(html).toContain('data-plugin-param-save-contract="angular-params-post"');
+    expect(html).toContain('data-plugin-param-payload-contract="angular-object-values-payload"');
+    expect(html).toContain('data-plugin-param-save-lifecycle-contract="angular-close-success-keep-open-fail"');
+    expect(html).toContain('data-plugin-param-save-lifecycle-owner="route-state-contract"');
+    expect(html).toContain('data-plugin-param-save-loading-contract="angular-no-ok-loading"');
+    expect(html).toContain('data-plugin-param-save-loading="angular-no-ok-loading"');
+    expect(html).toContain('data-plugin-param-save-loading-owner="angular-modal-ok-contract"');
+    expect(html).toContain('data-plugin-param-save-pending="true"');
+    expect(html).toContain('data-plugin-param-payload="angular-object-values-payload"');
+    expect(html).toContain('data-plugin-param-save-lifecycle="angular-close-success-keep-open-fail"');
+    expect(html).toContain('data-plugin-param-feedback-contract="angular-edit-notify"');
+    expect(html).toContain('data-plugin-param-pending-editable-contract="angular-controls-remain-enabled"');
+    expect(html).toContain('data-plugin-param-pending-editable="angular-controls-remain-enabled"');
+    expect(html).toContain('data-plugin-param-pending-editable-owner="angular-param-modal"');
+    expect(html).toContain('data-plugin-param-empty-contract="angular-empty-params-modal"');
+    expect(html).toContain('data-plugin-param-empty-owner="hertzbeat-ui-monitor-editor-field-grid"');
+    expect(html).toContain('data-plugin-param-width-contract="angular-default-modal-width"');
+    expect(html).toContain('data-plugin-param-width="angular-default-modal-width"');
+    expect(html).toContain('data-plugin-param-width-owner="angular-nz-modal"');
+    expect(html).toContain('max-w-[min(92vw,520px)]');
+    expect(html).not.toContain('max-w-3xl');
+    expect(html).toContain('data-plugin-param-radio-contract="angular-nz-radio-group"');
+    expect(html).toContain('data-plugin-param-radio-owner="hertzbeat-ui-radio-button-group"');
+    expect(html).toContain('data-plugin-param-textarea-contract="angular-textarea-rows-8"');
+    expect(html).toContain('data-plugin-param-textarea-owner="hertzbeat-ui-textarea"');
+    expect(html).toContain('data-plugin-param-advanced-fields-contract="angular-configurable-and-multi-func"');
+    expect(html).toContain('data-plugin-param-advanced-fields-owner="hertzbeat-ui-advanced-param-controls"');
+    expect(html).toContain('data-plugin-param-configurable-field-contract="angular-app-configurable-field"');
+    expect(html).toContain('data-plugin-param-configurable-field-owner="hertzbeat-ui-configurable-field"');
+    expect(html).toContain('data-plugin-param-key-value-contract="angular-app-configurable-field"');
+    expect(html).toContain('data-plugin-param-key-value-owner="hertzbeat-ui-key-value-editor"');
+    expect(html).toContain('data-plugin-param-labels-contract="angular-app-configurable-field"');
+    expect(html).toContain('data-plugin-param-labels-owner="hertzbeat-ui-key-value-editor"');
+    expect(html).toContain('data-plugin-param-label-selector-contract="angular-app-label-selector"');
+    expect(html).toContain('data-plugin-param-label-selector-owner="cold-label-selector"');
+    expect(html).toContain('data-plugin-param-metrics-field-contract="angular-app-configurable-field"');
+    expect(html).toContain('data-plugin-param-metrics-field-owner="hertzbeat-ui-configurable-field-editor"');
+    expect(html).toContain('data-plugin-param-array-contract="angular-app-multi-func-input"');
+    expect(html).toContain('data-plugin-param-array-owner="hertzbeat-ui-input"');
+    expect(html).toContain('data-plugin-param-field-layout-contract="angular-label-7-control-8"');
+    expect(html).toContain('data-plugin-param-field-layout-owner="hertzbeat-ui-monitor-editor-field-grid"');
+    expect(html).toContain('data-plugin-param-form-owner="hertzbeat-ui-monitor-editor-field-grid"');
+    expect(html).toContain('data-plugin-param-feedback="angular-edit-notify"');
+    expect(html).toContain('data-plugin-param-feedback-owner="hertzbeat-ui-inline-feedback"');
+    expect(html).toContain('data-plugin-param-edit-open="31"');
+    expect(html).toContain('data-plugin-param-dialog="angular-params-define-modal"');
+    expect(html).toContain('data-plugin-param-dialog-plugin="webhook"');
+    expect(html).toContain('data-plugin-param-form="angular-dynamic-form-field"');
+    expect(html).toContain('data-plugin-param-field-layout="angular-label-7-control-8"');
+    expect(html).toContain('data-hz-ui="monitor-editor-field-grid"');
+    expect(html).toContain('data-plugin-param-field="endpoint"');
+    expect(html).toContain('data-plugin-param-label-span="7"');
+    expect(html).toContain('data-plugin-param-control-span="8"');
+    expect(html).toContain('data-plugin-param-field-type="text"');
+    expect(html).toContain('data-plugin-param-field-required="true"');
+    expect(html).toContain('data-plugin-param-input="endpoint"');
+    expect(html).toContain('data-plugin-param-input-owner="hertzbeat-ui-input"');
+    expect(html).toContain('data-plugin-param-multi-func-input="angular-app-multi-func-input"');
+    expect(html).toContain('data-plugin-param-multi-func-type="text"');
+    expect(html).toContain('data-plugin-param-multi-func-clear="angular-allow-clear"');
+    expect(html).toContain('data-plugin-param-multi-func-clear-field="endpoint"');
+    expect(html).toContain('data-plugin-param-multi-func-clear-owner="hertzbeat-ui-input-affordance"');
+    expect(html).toContain('data-plugin-param-field="authType"');
+    expect(html).toContain('data-plugin-param-field-type="radio"');
+    expect(html).toContain('data-plugin-param-radio="angular-nz-radio-group"');
+    expect(html).toContain('data-plugin-param-radio-field="authType"');
+    expect(html).toContain('data-hz-ui="radio-button-group"');
+    expect(html).toContain('data-hz-radio-button-option="basic"');
+    expect(html).toContain('data-hz-radio-button-option="bearer"');
+    expect(html).toContain('data-hz-radio-button-checked="true"');
+    expect(html).toContain('data-plugin-param-field="script"');
+    expect(html).toContain('data-plugin-param-field-type="textarea"');
+    expect(html).toContain('data-plugin-param-textarea="angular-textarea-rows-8"');
+    expect(html).toContain('data-plugin-param-textarea-rows="8"');
+    expect(html).toContain('rows="8"');
+    expect(html).toContain('data-plugin-param-field="payload"');
+    expect(html).toContain('data-plugin-param-input-owner="hertzbeat-ui-textarea"');
+    expect(html).toContain('data-plugin-param-field="headers"');
+    expect(html).toContain('data-plugin-param-field-type="key-value"');
+    expect(html).toContain('data-plugin-param-configurable-field="angular-app-configurable-field"');
+    expect(html).toContain('data-plugin-param-key-value-editor="headers"');
+    expect(html).toContain('data-plugin-param-key-value-owner="hertzbeat-ui-key-value-editor"');
+    expect(html).toContain('data-hz-ui="key-value-editor"');
+    expect(html).toContain('data-hz-key-value-input="key"');
+    expect(html).toContain('data-hz-key-value-input="value"');
+    expect(html).toContain('data-plugin-param-key-value-input="key"');
+    expect(html).toContain('data-plugin-param-key-value-input="value"');
+    expect(html).toContain('data-plugin-param-field="labels"');
+    expect(html).toContain('data-plugin-param-field-type="labels"');
+    expect(html).toContain('data-plugin-param-labels-editor="labels"');
+    expect(html).toContain('data-plugin-param-labels-owner="hertzbeat-ui-key-value-editor"');
+    expect(html).toContain('data-plugin-param-labels-input="key"');
+    expect(html).toContain('data-plugin-param-labels-input="value"');
+    expect(html).toContain('data-plugin-param-field="selector"');
+    expect(html).toContain('data-plugin-param-field-type="label-selector"');
+    expect(html).toContain('data-plugin-param-label-selector="angular-app-label-selector"');
+    expect(html).toContain('data-plugin-param-label-selector-field="selector"');
+    expect(html).toContain('data-plugin-param-label-selector-owner="cold-label-selector"');
+    expect(html).toContain('data-cold-label-selector-owner="cold-label-selector"');
+    expect(html).toContain('data-cold-label-selector-record-row="service:checkout"');
+    expect(html).toContain('data-cold-label-selector-record-row="severity:critical"');
+    expect(html).toContain('data-cold-label-selector-key-input="searchable-key"');
+    expect(html).toContain('data-cold-label-selector-value-input="searchable-value"');
+    expect(html).toContain('data-cold-label-selector-value="hidden"');
+    expect(html).toContain('data-plugin-param-field="metrics"');
+    expect(html).toContain('data-plugin-param-field-type="metrics-field"');
+    expect(html).toContain('data-plugin-param-metrics-field-editor="metrics"');
+    expect(html).toContain('data-plugin-param-metrics-field-owner="hertzbeat-ui-configurable-field-editor"');
+    expect(html).toContain('data-hz-ui="configurable-field-editor"');
+    expect(html).toContain('data-hz-configurable-field-input="field"');
+    expect(html).toContain('data-hz-configurable-field-input="unit"');
+    expect(html).toContain('data-hz-configurable-field-input="type"');
+    expect(html).toContain('data-plugin-param-metrics-field-input="field"');
+    expect(html).toContain('data-plugin-param-metrics-field-input="unit"');
+    expect(html).toContain('data-plugin-param-metrics-field-input="type"');
+    expect(html).toContain('data-plugin-param-field="recipients"');
+    expect(html).toContain('data-plugin-param-field-type="array"');
+    expect(html).toContain('data-plugin-param-input="recipients"');
+    expect(html).toContain('data-plugin-param-array="angular-app-multi-func-input"');
+    expect(html).toContain('data-plugin-param-array-owner="hertzbeat-ui-input"');
+    expect(html).toContain('data-plugin-param-multi-func-type="array"');
+    expect(html).toContain('data-plugin-param-multi-func-clear-field="recipients"');
+    expect(html).toContain('data-plugin-param-save="angular-params-post"');
+    expect(html).toContain('data-plugin-param-cancel-loading="angular-no-ok-loading"');
+    expect(html).not.toContain('name="selector" disabled');
+    expect(html).toContain('Edit Success!');
+    expect(html).toContain('https://hooks.example');
+    expect(html).toContain('{&quot;ok&quot;:true}');
+    expect(html).toContain('Authorization');
+    expect(html).toContain('Bearer token');
+    expect(html).toContain('env');
+    expect(html).toContain('prod');
+    expect(html).toContain('usage');
+    expect(html).toContain('number');
+    expect(html).toContain('alpha,beta');
+  });
+
+  it('keeps the Angular plugin params modal open with an empty field set', async () => {
+    const { PluginManageSurface } = await import('./plugin-manage-surface');
+    const t = createTranslatorMock({
+      locale: 'zh-CN',
+      overrides: {
+        'common.no-data': '暂无数据',
+        'plugin.edit': '编辑插件'
+      }
+    });
+
+    const html = renderToStaticMarkup(
+      <PluginManageSurface
+        t={t}
+        data={{ list: { content: [], totalElements: 0, pageIndex: 0, pageSize: 8 } }}
+        search=""
+        selectedIds={[]}
+        draftPlugin={null}
+        paramDraft={{
+          plugin: { id: 42, name: 'empty-param-plugin', enableStatus: true } as any,
+          paramDefines: [],
+          params: {}
+        }}
+        deleteTarget={null}
+        onSearchChange={() => {}}
+        onSearch={() => {}}
+        onRefresh={() => {}}
+        onNew={() => {}}
+        onDeleteSelected={() => {}}
+        onDeleteCancel={() => {}}
+        onDeleteConfirm={() => {}}
+        onSelectedIdsChange={() => {}}
+        onEditParams={() => {}}
+        onParamChange={() => {}}
+        onCloseParamDialog={() => {}}
+        onSaveParamDialog={() => {}}
+        onToggleEnabled={() => {}}
+        onDeleteOne={() => {}}
+        onDraftChange={() => {}}
+        onCloseDialog={() => {}}
+        onSaveDialog={() => {}}
+      />
+    );
+
+    expect(html).toContain('data-plugin-param-empty-contract="angular-empty-params-modal"');
+    expect(html).toContain('data-plugin-param-empty-owner="hertzbeat-ui-monitor-editor-field-grid"');
+    expect(html).toContain('data-plugin-param-dialog="angular-params-define-modal"');
+    expect(html).toContain('data-plugin-param-dialog-plugin="empty-param-plugin"');
+    expect(html).toContain('data-plugin-param-form="angular-dynamic-form-field"');
+    expect(html).toContain('data-plugin-param-empty="angular-empty-params-modal"');
+    expect(html).toContain('data-plugin-param-empty-owner="hertzbeat-ui-monitor-editor-field-grid"');
+    expect(html).toContain('暂无数据');
+    expect(html).not.toContain('data-plugin-param-field=');
   });
 });
diff --git a/web-next/components/pages/plugin-manage-surface.tsx b/web-next/components/pages/plugin-manage-surface.tsx
index 3beb70ac70..2ba41f981c 100644
--- a/web-next/components/pages/plugin-manage-surface.tsx
+++ b/web-next/components/pages/plugin-manage-surface.tsx
@@ -1,18 +1,27 @@
 'use client';
 
 import React from 'react';
-import { Inbox, Pencil, RefreshCw, Trash2, Upload } from 'lucide-react';
+import { Inbox, MoreHorizontal, Pencil, RefreshCw, Trash2, Upload, X } from 'lucide-react';
+import { HzConfigurableFieldEditor, HzConfirmDialog, HzFileInput, HzInlineFeedback, HzInput, HzKeyValueEditor, HzMonitorEditorFieldGrid, HzPaginationBar, HzRadioButtonGroup, HzSwitch, HzTableRowActionButton, HzTextarea } from '@hertzbeat/ui';
 import { OverlayDialog } from '../workbench/overlay-dialog';
 import { Button } from '../ui/button';
 import { Checkbox } from '../ui/checkbox';
 import { Input } from '../ui/input';
+import { LabelRecordInput } from '../ui/label-record-input';
 import { SearchRow } from '../ui/search-row';
 import { coldOpsCatalogVisual } from '../../lib/cold-ops-visual';
 import { buildPluginTableRows } from '../../lib/plugin-manage/view-model';
-import type { PluginManagePageData, PluginUploadDraft } from '../../lib/plugin-manage/controller';
-import type { Plugin } from '../../lib/types';
+import type { PluginManagePageData, PluginParamDraft, PluginUploadDraft } from '../../lib/plugin-manage/controller';
+import type { ParamDefine, Plugin } from '../../lib/types';
 
 type Translator = (key: string, params?: Record<string, string | number | null | undefined>) => string;
+type PluginActionTone = 'success' | 'warning' | 'critical';
+type PluginActionKind = 'enable' | 'delete' | 'upload' | 'params';
+export type PluginDeleteTarget = {
+  ids: number[];
+  label: string;
+  mode: 'single' | 'batch';
+};
 
 type PluginManageSurfaceProps = {
   t: Translator;
@@ -20,13 +29,38 @@ type PluginManageSurfaceProps = {
   search: string;
   selectedIds: number[];
   draftPlugin: PluginUploadDraft | null;
+  isUploadDialogOpen?: boolean;
+  paramDraft?: PluginParamDraft | null;
+  actionMessage?: string | null;
+  actionError?: string | null;
+  actionMeta?: string | null;
+  actionTone?: PluginActionTone;
+  actionKind?: PluginActionKind;
+  loadError?: string | null;
+  uploadValidation?: { name?: boolean; jarFile?: boolean };
+  isUploadPending?: boolean;
+  isParamPending?: boolean;
+  deleteTarget: PluginDeleteTarget | null;
+  isLoadPending?: boolean;
+  isDeletePending?: boolean;
+  isTogglePending?: boolean;
+  optimisticEnableStatus?: Record<number, boolean>;
   onSearchChange: (value: string) => void;
   onSearch: () => void;
+  onSearchClear?: () => void;
   onRefresh: () => void;
   onNew: () => void;
   onDeleteSelected: () => void;
+  onDeleteCancel: () => void;
+  onDeleteConfirm: () => void;
   onSelectedIdsChange: (ids: number[]) => void;
+  pageSizeOptions?: number[];
+  onPageIndexChange?: (pageIndex: number) => void;
+  onPageSizeChange?: (pageSize: number) => void;
   onEditParams: (plugin: Plugin) => void;
+  onParamChange?: (field: string, value: unknown) => void;
+  onCloseParamDialog?: () => void;
+  onSaveParamDialog?: () => void;
   onToggleEnabled: (plugin: Plugin) => void;
   onDeleteOne: (plugin: Plugin) => void;
   onDraftChange: (patch: Partial<PluginUploadDraft>) => void;
@@ -48,19 +82,149 @@ const coldIconButtonClassName =
 const coldDialogInputClassName =
   'h-8 rounded-[3px] border border-[#2b3039] bg-[#101217] px-3 text-[12px] font-semibold text-[#eef2f7] outline-none placeholder:text-[#6f7788] focus:border-[#4e74f8] focus:ring-2 focus:ring-[rgba(78,116,248,0.16)]';
 
-function isChineseTranslator(t: Translator): boolean {
-  return t('common.refresh') === '刷新' || t('menu.advanced.plugins') === '插件管理';
-}
+const pluginActionMenuRootClassName = 'group relative z-[9999] inline-block overflow-visible open:z-[9999]';
+const pluginActionMenuPanelBaseClassName =
+  'mt-2 z-[9999] min-w-[132px] rounded-[3px] border border-[#2b3039] bg-[#101217] p-1 shadow-[0_18px_42px_rgba(0,0,0,0.42)]';
 
 function pluginTypeLabel(t: Translator, type?: string) {
   return type ? t(`plugin.type.${type}`) : t('common.none');
 }
 
 function pluginStatusLabel(enabled: boolean, t: Translator): string {
-  if (isChineseTranslator(t)) {
-    return enabled ? '已启用' : '已停用';
+  return enabled ? t('common.enabled') : t('common.disabled');
+}
+
+function stringifyPluginParamValue(value: unknown): string {
+  if (value === null || value === undefined) return '';
+  return typeof value === 'string' ? value : String(value);
+}
+
+type PluginParamKeyValueRow = {
+  key: string;
+  value: string;
+};
+
+type PluginParamConfigurableRow = Record<string, string>;
+
+function parsePluginParamKeyValueRows(value: unknown): PluginParamKeyValueRow[] {
+  if (Array.isArray(value)) {
+    const rows = value.map(item => {
+      if (item && typeof item === 'object') {
+        const record = item as Record<string, unknown>;
+        return {
+          key: String(record.key ?? ''),
+          value: String(record.value ?? '')
+        };
+      }
+      return { key: String(item ?? ''), value: '' };
+    });
+    return rows.length > 0 ? rows : [{ key: '', value: '' }];
+  }
+
+  if (value && typeof value === 'object') {
+    const entries = Object.entries(value as Record<string, unknown>);
+    return entries.length > 0
+      ? entries.map(([key, entryValue]) => ({ key, value: String(entryValue ?? '') }))
+      : [{ key: '', value: '' }];
   }
-  return enabled ? 'Enabled' : 'Disabled';
+
+  if (typeof value !== 'string') return [{ key: '', value: '' }];
+  const trimmed = value.trim();
+  if (!trimmed || trimmed === '""') return [{ key: '', value: '' }];
+
+  try {
+    const parsed = JSON.parse(trimmed);
+    return parsePluginParamKeyValueRows(parsed);
+  } catch {
+    return [{ key: '', value: trimmed }];
+  }
+}
+
+function stringifyPluginParamKeyValueRows(rows: PluginParamKeyValueRow[]): string {
+  return JSON.stringify(
+    rows.reduce<Record<string, string>>((record, row) => {
+      const key = row.key.trim();
+      if (!key) return record;
+      record[key] = row.value.trim();
+      return record;
+    }, {})
+  );
+}
+
+function objectifyPluginParamLabelRows(rows: PluginParamKeyValueRow[]): Record<string, string> {
+  return rows.reduce<Record<string, string>>((record, row) => {
+    const key = row.key.trim();
+    const value = row.value.trim();
+    if (!key || !value) return record;
+    record[key] = value;
+    return record;
+  }, {});
+}
+
+function stringifyPluginParamLabelSelectorValue(value: unknown): string {
+  if (typeof value === 'string') {
+    const trimmed = value.trim();
+    if (!trimmed || trimmed === '""') return '';
+    try {
+      const parsed = JSON.parse(trimmed);
+      return stringifyPluginParamLabelSelectorValue(parsed);
+    } catch {
+      return trimmed;
+    }
+  }
+
+  return parsePluginParamKeyValueRows(value)
+    .filter(row => row.key.trim())
+    .map(row => {
+      const key = row.key.trim();
+      const rowValue = row.value.trim();
+      return rowValue ? `${key}:${rowValue}` : key;
+    })
+    .join(', ');
+}
+
+function objectifyPluginParamLabelSelectorValue(value: string): Record<string, string> {
+  return value.split(',').reduce<Record<string, string>>((record, item) => {
+    const [keyPart, ...valueParts] = item.split(':');
+    const key = keyPart.trim();
+    const rowValue = valueParts.join(':').trim();
+    if (!key || !rowValue) return record;
+    record[key] = rowValue;
+    return record;
+  }, {});
+}
+
+function parsePluginParamConfigurableRows(value: unknown, keys: string[]): PluginParamConfigurableRow[] {
+  if (Array.isArray(value)) {
+    const rows = value.map(item => {
+      if (item && typeof item === 'object') {
+        const record = item as Record<string, unknown>;
+        return Object.fromEntries(keys.map(key => [key, String(record[key] ?? '')]));
+      }
+      return Object.fromEntries(keys.map((key, index) => [key, index === 0 ? String(item ?? '') : '']));
+    });
+    return rows.length > 0 ? rows : [{}];
+  }
+  return [{}];
+}
+
+function isPluginParamTextarea(define: ParamDefine): boolean {
+  return define.type === 'json' || define.type === 'textarea' || define.type === 'textarea-limit';
+}
+
+function isPluginParamMultiFuncInput(define: ParamDefine): boolean {
+  return define.type === 'text' || define.type === 'string' || define.type === 'array' || define.type === 'password';
+}
+
+function getPluginParamTextareaRows(define: ParamDefine): 4 | 8 {
+  return define.type === 'json' || define.type === 'textarea' ? 8 : 4;
+}
+
+function buildPluginParamRadioOptions(define: ParamDefine) {
+  return (define.options ?? []).map(option => ({
+    value: option.value ?? '',
+    label: typeof option.label === 'string' ? option.label : option.value ?? ''
+  }));
 }
 
 function PluginSwitch({
@@ -99,13 +263,38 @@ export function PluginManageSurface({
   search,
   selectedIds,
   draftPlugin,
+  isUploadDialogOpen,
+  paramDraft,
+  actionMessage,
+  actionError,
+  actionMeta,
+  actionTone,
+  actionKind,
+  loadError,
+  uploadValidation,
+  isUploadPending,
+  isParamPending,
+  deleteTarget,
+  isLoadPending,
+  isDeletePending,
+  isTogglePending,
+  optimisticEnableStatus,
   onSearchChange,
   onSearch,
+  onSearchClear,
   onRefresh,
   onNew,
   onDeleteSelected,
+  onDeleteCancel,
+  onDeleteConfirm,
   onSelectedIdsChange,
+  pageSizeOptions = [8, 15, 25],
+  onPageIndexChange,
+  onPageSizeChange,
   onEditParams,
+  onParamChange,
+  onCloseParamDialog,
+  onSaveParamDialog,
   onToggleEnabled,
   onDeleteOne,
   onDraftChange,
@@ -114,8 +303,53 @@ export function PluginManageSurface({
 }: PluginManageSurfaceProps) {
   const resolvedSelectedIds = selectedIds ?? [];
   const plugins = data.list.content ?? [];
-  const rows = buildPluginTableRows(plugins);
+  const rows = buildPluginTableRows(plugins, t);
+  const currentPageIndex = Math.max(0, data.list.pageIndex ?? 0);
+  const currentPageSize = Math.max(1, data.list.pageSize ?? pageSizeOptions[0] ?? 8);
+  const totalElements = data.list.totalElements || 0;
+  const totalPages = Math.max(1, Math.ceil(totalElements / currentPageSize));
+  const currentPage = Math.min(currentPageIndex + 1, totalPages);
+  const pageStart = totalElements === 0 || rows.length === 0 ? 0 : currentPageIndex * currentPageSize + 1;
+  const pageEnd = totalElements === 0 ? 0 : Math.min(totalElements, currentPageIndex * currentPageSize + rows.length);
   const allSelected = plugins.length > 0 && plugins.every(plugin => resolvedSelectedIds.includes(plugin.id));
+  const resolvedActionTone = actionTone ?? (actionError ? 'critical' : 'success');
+  const isUploadFailure = actionKind === 'upload' && Boolean(actionError);
+  const isParamFailure = actionKind === 'params' && Boolean(actionError);
+  const isEnableFailure = actionKind === 'enable' && Boolean(actionError);
+  const isDeleteFailure = actionKind === 'delete' && resolvedActionTone === 'critical' && Boolean(actionError);
+  const actionFeedbackTitle = isUploadFailure
+    ? t('common.notify.new-fail')
+    : isParamFailure
+      ? t('common.notify.edit-fail')
+      : isEnableFailure
+        ? t('common.notify.edit-fail')
+        : isDeleteFailure
+          ? t('common.notify.delete-fail')
+          : actionError ?? actionMessage ?? '';
+  const actionFeedbackMeta = isUploadFailure || isParamFailure || isEnableFailure || isDeleteFailure ? actionMeta ?? actionError ?? undefined : undefined;
+  const tableLoading = Boolean(isLoadPending || isDeletePending || isTogglePending);
+  const resolveEnableStatus = (plugin: Plugin) => (
+    optimisticEnableStatus && Object.prototype.hasOwnProperty.call(optimisticEnableStatus, plugin.id)
+      ? Boolean(optimisticEnableStatus[plugin.id])
+      : Boolean(plugin.enableStatus)
+  );
+  const pluginJarInputRef = React.useRef<HTMLInputElement>(null);
+  const [isToolbarActionMenuOpen, setToolbarActionMenuOpen] = React.useState(false);
+  const [openRowActionMenuId, setOpenRowActionMenuId] = React.useState<string | null>(null);
+  const paginationSummary = t('setting.plugins.pagination.summary', {
+    page: currentPage,
+    totalPages,
+    from: pageStart,
+    to: pageEnd,
+    total: totalElements
+  });
+
+  function handlePageJumpChange(value: string) {
+    const parsed = Number.parseInt(value, 10);
+    if (!Number.isFinite(parsed)) return;
+    const nextPageIndex = Math.min(Math.max(parsed, 1), totalPages) - 1;
+    onPageIndexChange?.(nextPageIndex);
+  }
 
   return (
     <>
@@ -128,159 +362,520 @@ export function PluginManageSurface({
         <section className={coldPluginVisual.layout.pageSection}>
           <div className="mx-auto max-w-[1480px]">
             <div className="mb-5">
-              <div data-plugin-header="cold-compact-header" className={coldPluginVisual.panel.hero}>
+              <div
+                data-plugin-header="cold-compact-header"
+                data-plugin-action-menu-clipping="none"
+                className={`${coldPluginVisual.panel.hero} z-[80] overflow-visible`}
+                style={{ overflow: 'visible' }}
+              >
                 <div className="max-w-[820px]">
                   <h1 className="text-[30px] font-semibold leading-tight text-[#f5f7fb]">
                     {t('menu.advanced.plugins')}
                   </h1>
                   <p className="mt-4 max-w-[780px] text-[13px] leading-6 text-[#a9b0bb]">
-                    管理自定义插件、执行类型和启用状态,保持上传、筛选、批量删除和参数编辑在同一个紧凑工作面中完成。
+                    {t('setting.plugins.copy')}
                   </p>
                   <div data-plugin-command-row="standard-equal-buttons" className={coldPluginVisual.button.row}>
                     <Button size="sm" variant="default" className={coldButtonClassName} onClick={onRefresh}>
                       <RefreshCw className="h-3.5 w-3.5" aria-hidden="true" />
                       {t('common.refresh')}
                     </Button>
-                    <Button size="sm" variant="default" className={coldPrimaryButtonClassName} onClick={onNew}>
-                      <Upload className="h-3.5 w-3.5" aria-hidden="true" />
-                      {t('plugin.upload')}
-                    </Button>
                     <Button
                       size="sm"
                       variant="default"
-                      className={coldButtonClassName}
-                      onClick={onDeleteSelected}
-                      disabled={resolvedSelectedIds.length === 0}
+                      className={coldPrimaryButtonClassName}
+                      onClick={onNew}
+                      data-plugin-upload-open="angular-upload-modal"
                     >
-                      <Trash2 className="h-3.5 w-3.5" aria-hidden="true" />
-                      {t('plugin.delete')}
+                      <Upload className="h-3.5 w-3.5" aria-hidden="true" />
+                      {t('plugin.upload')}
                     </Button>
+                    <div
+                      className={pluginActionMenuRootClassName}
+                      data-plugin-toolbar-delete-menu="angular-toolbar-ellipsis-delete"
+                      data-plugin-toolbar-delete-menu-contract="angular-toolbar-ellipsis-delete"
+                      data-plugin-toolbar-delete-menu-layer="overlay-visible-above-panel"
+                      data-plugin-toolbar-delete-menu-clearance="floating-overlay-no-panel-crop"
+                      data-plugin-toolbar-delete-menu-open={isToolbarActionMenuOpen ? 'true' : 'false'}
+                    >
+                      <button
+                        type="button"
+                        aria-expanded={isToolbarActionMenuOpen}
+                        aria-label={t('common.edit')}
+                        title={t('common.edit')}
+                        className={`${coldIconButtonClassName} inline-flex cursor-pointer list-none items-center justify-center [&::-webkit-details-marker]:hidden`}
+                        onClick={() => setToolbarActionMenuOpen(open => !open)}
+                        data-plugin-toolbar-delete-menu-trigger="angular-toolbar-ellipsis-delete"
+                      >
+                        <MoreHorizontal className="h-3.5 w-3.5" aria-hidden="true" />
+                        <span className="sr-only">{t('common.edit')}</span>
+                      </button>
+                      <div
+                        role="menu"
+                        hidden={!isToolbarActionMenuOpen}
+                        className={`${pluginActionMenuPanelBaseClassName} left-0`}
+                        data-plugin-toolbar-delete-menu-panel="angular-toolbar-ellipsis-delete"
+                        data-plugin-toolbar-delete-menu-layer-panel="overlay-visible-above-panel"
+                        data-plugin-toolbar-delete-menu-clearance-panel="floating-overlay-no-panel-crop"
+                        data-plugin-toolbar-delete-menu-owner="hertzbeat-ui-table-row-action-button"
+                      >
+                        <HzTableRowActionButton
+                          width="root-span"
+                          intent="ghost"
+                          onClick={() => {
+                            setToolbarActionMenuOpen(false);
+                            onDeleteSelected();
+                          }}
+                          data-plugin-delete-selected="angular-batch-delete-entry"
+                          data-plugin-delete-selected-owner="hertzbeat-ui-table-row-action-button"
+                          className="w-full text-[#fecaca] hover:text-white"
+                        >
+                          <Trash2 className="h-3.5 w-3.5" aria-hidden="true" />
+                          <span className="truncate">{t('plugin.delete')}</span>
+                        </HzTableRowActionButton>
+                      </div>
+                    </div>
                   </div>
                 </div>
               </div>
             </div>
 
-            <div data-plugin-admin-layout="full-width-admin-list" className="space-y-5">
+            <div
+              data-plugin-admin-layout="full-width-admin-list"
+              data-plugin-enable-feedback-contract="angular-edit-notify"
+              data-plugin-enable-feedback-contract-owner="hertzbeat-ui-inline-feedback"
+              data-plugin-enable-optimistic-contract="angular-toggle-mutates-row-before-put"
+              data-plugin-enable-optimistic-owner="route-state-contract"
+              data-plugin-table-loading-contract="angular-load-plugins-table"
+              data-plugin-table-loading-scope="load-search-refresh-pagination-mutation"
+              data-plugin-load-failure-contract="angular-console-only-shell"
+              data-plugin-load-failure-owner="plugin-route-controller"
+              data-plugin-load-failure={loadError ? 'angular-console-only-shell' : 'none'}
+              data-plugin-toggle-loading-contract="angular-nz-table-loading"
+              data-plugin-toggle-loading-contract-owner="angular-nz-table-loading"
+              data-plugin-selection-reset-contract="angular-load-clears-selection"
+              data-plugin-selection-reset-owner="route-state-contract"
+              data-plugin-query-param-order-contract="angular-page-index-size-search"
+              data-plugin-query-param-order-owner="plugin-query-state"
+              data-plugin-delete-warning-contract="angular-no-select-warning"
+              data-plugin-delete-confirm-contract="angular-modal-confirm"
+              data-plugin-delete-confirm-contract-owner="hertzbeat-ui-confirm-dialog"
+              data-plugin-delete-feedback-contract="angular-delete-notify"
+              data-plugin-delete-feedback-contract-owner="hertzbeat-ui-inline-feedback"
+              data-plugin-delete-failure-contract="angular-delete-fail-notification"
+              data-plugin-delete-failure-owner="hertzbeat-ui-inline-feedback"
+              data-plugin-enable-failure-contract="angular-edit-fail-notification"
+              data-plugin-enable-failure-owner="hertzbeat-ui-inline-feedback"
+              data-plugin-delete-query-contract="angular-repeated-ids-query"
+              data-plugin-delete-query-owner="route-mutation-contract"
+              data-plugin-delete-page-clamp-contract="angular-update-page-index"
+              data-plugin-table-columns-contract="angular-five-column-edit-actions"
+              data-plugin-table-columns-owner="angular-nz-table"
+              data-plugin-table-stable-height-contract="viewport-fill-on-empty-or-short"
+              data-plugin-table-stable-height-owner="route-layout-contract"
+              data-plugin-row-delete-menu-contract="angular-ellipsis-dropdown-delete"
+              data-plugin-row-delete-menu-owner="hertzbeat-ui-table-row-action-button"
+              data-plugin-upload-validation-contract="angular-required-before-submit"
+              data-plugin-upload-invalid-submit-contract="angular-mark-dirty-keep-open"
+              data-plugin-upload-invalid-submit-owner="route-validation-contract"
+              data-plugin-upload-loading-contract="angular-nz-ok-loading"
+              data-plugin-upload-payload-contract="angular-form-data"
+              data-plugin-upload-name-payload-contract="angular-raw-name-no-trim"
+              data-plugin-upload-name-payload-owner="plugin-manage-controller"
+              data-plugin-upload-save-lifecycle-contract="angular-close-success-keep-open-fail"
+              data-plugin-upload-save-lifecycle-owner="route-state-contract"
+              data-plugin-upload-success-reset-contract="angular-reset-form-after-success"
+              data-plugin-upload-success-reset-owner="route-state-contract"
+              data-plugin-upload-cancel-contract="angular-cancel-preserves-form"
+              data-plugin-upload-cancel-contract-owner="route-state-contract"
+              data-plugin-upload-cancel-pending-contract="angular-cancel-allowed-during-ok-loading"
+              data-plugin-upload-cancel-pending-owner="angular-nz-modal"
+              data-plugin-upload-mask-closable-contract="angular-mask-closable-false"
+              data-plugin-upload-width-contract="angular-width-30-percent"
+              data-plugin-upload-modal-owner="angular-nz-modal"
+              data-plugin-upload-field-layout-contract="angular-label-8-control-14"
+              data-plugin-upload-field-layout-owner="route-form-field-grid"
+              data-plugin-upload-status-control-contract="angular-nz-switch"
+              data-plugin-upload-status-control-owner="hertzbeat-ui-switch"
+              data-plugin-upload-file-list-contract="angular-before-upload-single-replace"
+              data-plugin-upload-file-list-owner="hertzbeat-ui-file-input"
+              data-plugin-upload-file-remove-contract="angular-nz-remove-clears-jar"
+              data-plugin-upload-file-remove-owner="hertzbeat-ui-file-input"
+              data-plugin-upload-feedback-contract="angular-new-notify"
+              data-plugin-upload-feedback-contract-owner="hertzbeat-ui-inline-feedback"
+              data-plugin-upload-failure-contract="angular-new-fail-notification"
+              data-plugin-upload-failure-owner="hertzbeat-ui-inline-feedback"
+              data-plugin-param-edit-contract="angular-params-define-modal"
+              data-plugin-param-action-visibility-contract="angular-paramcount-zero-only"
+              data-plugin-param-action-visibility-owner="plugin-view-model"
+              data-plugin-param-mask-closable-contract="angular-mask-closable-false"
+              data-plugin-param-modal-owner="angular-nz-modal"
+              data-plugin-param-save-contract="angular-params-post"
+              data-plugin-param-payload-contract="angular-object-values-payload"
+              data-plugin-param-save-lifecycle-contract="angular-close-success-keep-open-fail"
+              data-plugin-param-save-lifecycle-owner="route-state-contract"
+              data-plugin-param-save-loading-contract="angular-no-ok-loading"
+              data-plugin-param-save-loading-owner="angular-modal-ok-contract"
+              data-plugin-param-feedback-contract="angular-edit-notify"
+              data-plugin-param-failure-contract="angular-edit-fail-notification"
+              data-plugin-param-failure-owner="hertzbeat-ui-inline-feedback"
+              data-plugin-param-empty-contract="angular-empty-params-modal"
+              data-plugin-param-empty-owner="hertzbeat-ui-monitor-editor-field-grid"
+              data-plugin-param-pending-editable-contract="angular-controls-remain-enabled"
+              data-plugin-param-pending-editable-owner="angular-param-modal"
+              data-plugin-param-width-contract="angular-default-modal-width"
+              data-plugin-param-width-owner="angular-nz-modal"
+              data-plugin-param-radio-contract="angular-nz-radio-group"
+              data-plugin-param-radio-owner="hertzbeat-ui-radio-button-group"
+              data-plugin-param-textarea-contract="angular-textarea-rows-8"
+              data-plugin-param-textarea-owner="hertzbeat-ui-textarea"
+              data-plugin-param-advanced-fields-contract="angular-configurable-and-multi-func"
+              data-plugin-param-advanced-fields-owner="hertzbeat-ui-advanced-param-controls"
+              data-plugin-param-configurable-field-contract="angular-app-configurable-field"
+              data-plugin-param-configurable-field-owner="hertzbeat-ui-configurable-field"
+              data-plugin-param-key-value-contract="angular-app-configurable-field"
+              data-plugin-param-key-value-owner="hertzbeat-ui-key-value-editor"
+              data-plugin-param-labels-contract="angular-app-configurable-field"
+              data-plugin-param-labels-owner="hertzbeat-ui-key-value-editor"
+              data-plugin-param-label-selector-contract="angular-app-label-selector"
+              data-plugin-param-label-selector-owner="cold-label-selector"
+              data-plugin-param-metrics-field-contract="angular-app-configurable-field"
+              data-plugin-param-metrics-field-owner="hertzbeat-ui-configurable-field-editor"
+              data-plugin-param-array-contract="angular-app-multi-func-input"
+              data-plugin-param-array-owner="hertzbeat-ui-input"
+              data-plugin-param-field-layout-contract="angular-label-7-control-8"
+              data-plugin-param-field-layout-owner="hertzbeat-ui-monitor-editor-field-grid"
+              data-plugin-param-form-owner="hertzbeat-ui-monitor-editor-field-grid"
+              className="space-y-5"
+            >
               <section className="min-w-0">
                 <SearchRow
                   data-plugin-manage-toolbar="cold-table-toolbar"
                   data-plugin-manage-search-owner="shared-search-row"
+                  data-plugin-search-submit-contract="angular-enter-and-clear"
+                  data-plugin-search-clear-contract="angular-cleared-load"
+                  data-plugin-search-clear-owner="shared-search-row"
                   value={search}
                   placeholder={t('plugin.search')}
                   searchLabel={t('common.search')}
+                  clearLabel={t('common.clear')}
                   inputWidthClassName="w-[360px]"
                   onValueChange={onSearchChange}
                   onSearch={onSearch}
+                  onClear={onSearchClear}
                   trailingActions={
                     <div className="text-[12px] font-semibold text-[#858d9a]">
-                      已选择 <span className="text-[#dbe4f0]">{resolvedSelectedIds.length}</span>
+                      {t('setting.plugins.selected-count', { count: resolvedSelectedIds.length })}
                     </div>
                   }
                 />
+                {actionFeedbackTitle ? (
+                  <HzInlineFeedback
+                    tone={resolvedActionTone}
+                    title={actionFeedbackTitle}
+                    meta={actionFeedbackMeta}
+                    variant="embedded"
+                    data-plugin-action-feedback={resolvedActionTone}
+                    data-plugin-enable-feedback={
+                      actionKind === 'enable'
+                        ? resolvedActionTone === 'critical'
+                          ? 'angular-edit-fail'
+                          : 'angular-edit-notify'
+                        : undefined
+                    }
+                    data-plugin-enable-feedback-owner="hertzbeat-ui-inline-feedback"
+                    data-plugin-enable-failure={isEnableFailure ? 'angular-edit-fail-notification' : undefined}
+                    data-plugin-enable-failure-owner={isEnableFailure ? 'hertzbeat-ui-inline-feedback' : undefined}
+                    data-plugin-enable-feedback-title={
+                      actionKind === 'enable'
+                        ? resolvedActionTone === 'critical'
+                          ? 'common.notify.edit-fail'
+                          : 'common.notify.edit-success'
+                        : undefined
+                    }
+                    data-plugin-enable-feedback-detail={isEnableFailure ? 'backend-message' : undefined}
+                    data-plugin-delete-feedback={
+                      actionKind === 'delete'
+                        ? resolvedActionTone === 'warning'
+                          ? 'angular-no-select-warning'
+                          : resolvedActionTone === 'critical'
+                            ? 'angular-delete-fail'
+                            : 'angular-delete-notify'
+                        : undefined
+                    }
+                    data-plugin-delete-feedback-owner="hertzbeat-ui-inline-feedback"
+                    data-plugin-delete-failure={isDeleteFailure ? 'angular-delete-fail-notification' : undefined}
+                    data-plugin-delete-failure-owner={isDeleteFailure ? 'hertzbeat-ui-inline-feedback' : undefined}
+                    data-plugin-delete-feedback-title={
+                      actionKind === 'delete'
+                        ? resolvedActionTone === 'critical'
+                          ? 'common.notify.delete-fail'
+                          : resolvedActionTone === 'warning'
+                            ? 'common.notify.no-select-delete'
+                            : 'common.notify.delete-success'
+                        : undefined
+                    }
+                    data-plugin-delete-feedback-detail={isDeleteFailure ? 'backend-message' : undefined}
+                    data-plugin-upload-feedback={
+                      actionKind === 'upload'
+                        ? resolvedActionTone === 'critical'
+                          ? 'angular-new-fail'
+                          : 'angular-new-notify'
+                        : undefined
+                    }
+                    data-plugin-upload-feedback-owner="hertzbeat-ui-inline-feedback"
+                    data-plugin-upload-feedback-title={
+                      actionKind === 'upload'
+                        ? resolvedActionTone === 'critical'
+                          ? 'common.notify.new-fail'
+                          : 'common.notify.new-success'
+                        : undefined
+                    }
+                    data-plugin-upload-feedback-detail={
+                      actionKind === 'upload' && resolvedActionTone === 'critical' ? 'backend-message' : undefined
+                    }
+                    data-plugin-param-feedback={
+                      actionKind === 'params'
+                        ? resolvedActionTone === 'critical'
+                          ? 'angular-edit-fail'
+                          : 'angular-edit-notify'
+                        : undefined
+                    }
+                    data-plugin-param-feedback-owner="hertzbeat-ui-inline-feedback"
+                    data-plugin-param-failure={isParamFailure ? 'angular-edit-fail-notification' : undefined}
+                    data-plugin-param-failure-owner={
+                      isParamFailure ? 'hertzbeat-ui-inline-feedback' : undefined
+                    }
+                    data-plugin-param-feedback-title={
+                      actionKind === 'params'
+                        ? resolvedActionTone === 'critical'
+                          ? 'common.notify.edit-fail'
+                          : 'common.notify.edit-success'
+                        : undefined
+                    }
+                    data-plugin-param-feedback-detail={isParamFailure ? 'backend-message' : undefined}
+                  />
+                ) : null}
 
                 <div
                   data-plugin-manage-table-shell="cold-dense-table"
-                  className="overflow-hidden rounded-[4px] border border-[#2b3039] bg-[#0b0c0e] shadow-[0_20px_56px_rgba(0,0,0,0.32)]"
+                  data-plugin-table-stable-height="viewport-fill-on-empty-or-short"
+                  data-plugin-table-stable-height-owner="route-layout-contract"
+                  data-plugin-table-loading={tableLoading ? 'true' : 'false'}
+                  data-plugin-table-loading-owner="angular-nz-table-loading"
+                  aria-busy={tableLoading ? 'true' : 'false'}
+                  className="flex min-h-[360px] flex-col overflow-visible rounded-[4px] border border-[#2b3039] bg-[#0b0c0e] shadow-[0_20px_56px_rgba(0,0,0,0.32)] lg:min-h-[calc(100vh-390px)]"
                 >
-                  <table data-plugin-manage-table="cold-plugin-table" className="w-full table-fixed border-collapse text-left text-[12px] text-[#a9b0bb]">
-                    <thead className="border-b border-[#252b34] bg-[#101217] text-[11px] font-semibold uppercase tracking-[0.12em] text-[#7e8494]">
-                      <tr>
-                        <th className="w-[44px] px-3 py-2.5">
-                          <Checkbox
-                            data-plugin-select-all="cold-checkbox"
-                            aria-label={t('common.select')}
-                            containerClassName="min-h-0"
-                            checked={allSelected}
-                            disabled={plugins.length === 0}
-                            onChange={event => {
-                              onSelectedIdsChange(event.target.checked ? plugins.map(plugin => plugin.id) : []);
-                            }}
-                          />
-                        </th>
-                        <th className="w-[24%] px-3 py-2.5">{t('plugin.name')}</th>
-                        <th className="w-[28%] px-3 py-2.5">{t('plugin.type')}</th>
-                        <th className="w-[14%] px-3 py-2.5">{t('plugin.status')}</th>
-                        <th className="w-[12%] px-3 py-2.5">参数</th>
-                        <th className="w-[96px] px-3 py-2.5">{t('common.edit')}</th>
-                      </tr>
-                    </thead>
-                    <tbody>
-                      {rows.length > 0 ? rows.map((row, index) => {
-                        const original = plugins[index];
-                        const checked = resolvedSelectedIds.includes(original.id);
-                        return (
-                          <tr key={row.key} data-plugin-row={row.key} className="border-t border-[#252b34] bg-[#0b0c0e] transition hover:bg-[#111721]">
-                            <td className="px-3 py-2.5">
-                              <Checkbox
-                                aria-label={t('common.select')}
-                                data-plugin-row-select={String(original.id)}
-                                containerClassName="min-h-0"
-                                checked={checked}
-                                onChange={event => {
-                                  onSelectedIdsChange(
-                                    event.target.checked
-                                      ? [...resolvedSelectedIds, original.id]
-                                      : resolvedSelectedIds.filter(value => value !== original.id)
-                                  );
-                                }}
-                              />
-                            </td>
-                            <td className="px-3 py-2.5 font-semibold text-[#eef2f7]">{row.name}</td>
-                            <td className="px-3 py-2.5">
-                              <div className="flex flex-wrap gap-1.5">
-                                {row.typeLabels.length > 0 ? row.typeLabels.map(typeLabel => (
-                                  <span key={`${row.key}-${typeLabel}`} className="rounded-[3px] border border-[#303743] bg-[#101217] px-2 py-0.5 text-[11px] leading-4 text-[#cbd5e1]">
-                                    {pluginTypeLabel(t, typeLabel)}
-                                  </span>
-                                )) : <span className="text-[#6f7681]">-</span>}
-                              </div>
-                            </td>
-                            <td className="px-3 py-2.5">
-                              <div className="flex items-center gap-2">
-                                <PluginSwitch
-                                  checked={Boolean(original.enableStatus)}
-                                  label={pluginStatusLabel(Boolean(original.enableStatus), t)}
-                                  onChange={() => onToggleEnabled(original)}
+                  <div className="min-h-0 flex-1 bg-[#0b0c0e]" data-plugin-table-fill-stage="viewport-fill-on-empty-or-short">
+                    <table
+                      data-plugin-manage-table="cold-plugin-table"
+                      data-plugin-table-columns="angular-five-column-edit-actions"
+                      data-plugin-table-columns-owner="angular-nz-table"
+                      className="w-full table-fixed border-collapse text-left text-[12px] text-[#a9b0bb]"
+                    >
+                      <thead className="border-b border-[#252b34] bg-[#101217] text-[11px] font-semibold uppercase tracking-[0.12em] text-[#7e8494]">
+                        <tr>
+                          <th className="w-[44px] px-3 py-2.5">
+                            <Checkbox
+                              data-plugin-select-all="cold-checkbox"
+                              aria-label={t('common.select')}
+                              containerClassName="min-h-0"
+                              checked={allSelected}
+                              disabled={plugins.length === 0}
+                              onChange={event => {
+                                onSelectedIdsChange(event.target.checked ? plugins.map(plugin => plugin.id) : []);
+                              }}
+                            />
+                          </th>
+                          <th className="w-[26%] px-3 py-2.5">{t('plugin.name')}</th>
+                          <th className="w-[34%] px-3 py-2.5">{t('plugin.type')}</th>
+                          <th className="w-[16%] px-3 py-2.5">{t('plugin.status')}</th>
+                          <th className="w-[96px] px-3 py-2.5">{t('common.edit')}</th>
+                        </tr>
+                      </thead>
+                      <tbody>
+                        {rows.length > 0 ? rows.map((row, index) => {
+                          const original = plugins[index];
+                          const checked = resolvedSelectedIds.includes(original.id);
+                          const hasOptimisticEnableStatus = Boolean(
+                            optimisticEnableStatus && Object.prototype.hasOwnProperty.call(optimisticEnableStatus, original.id)
+                          );
+                          const displayEnableStatus = resolveEnableStatus(original);
+                          const rowActionMenuId = String(original.id);
+                          const isRowActionMenuOpen = openRowActionMenuId === rowActionMenuId;
+                          const actionLabelParams = { name: row.name };
+                          const editParamsLabel = t('setting.plugins.action.params-aria', actionLabelParams);
+                          const deleteLabel = t('setting.plugins.action.delete-aria', actionLabelParams);
+                          return (
+                            <tr key={row.key} data-plugin-row={row.key} className="border-t border-[#252b34] bg-[#0b0c0e] transition hover:bg-[#111721]">
+                              <td className="px-3 py-2.5">
+                                <Checkbox
+                                  aria-label={t('common.select')}
+                                  data-plugin-row-select={String(original.id)}
+                                  containerClassName="min-h-0"
+                                  checked={checked}
+                                  onChange={event => {
+                                    onSelectedIdsChange(
+                                      event.target.checked
+                                        ? [...resolvedSelectedIds, original.id]
+                                        : resolvedSelectedIds.filter(value => value !== original.id)
+                                    );
+                                  }}
                                 />
-                                <span className="text-[11px] font-semibold text-[#cbd5e1]">
-                                  {pluginStatusLabel(Boolean(original.enableStatus), t)}
+                              </td>
+                              <td className="px-3 py-2.5 font-semibold text-[#eef2f7]">{row.name}</td>
+                              <td className="px-3 py-2.5">
+                                <div className="flex flex-wrap gap-1.5">
+                                  {row.typeLabels.length > 0 ? row.typeLabels.map(typeLabel => (
+                                    <span key={`${row.key}-${typeLabel}`} className="rounded-[3px] border border-[#303743] bg-[#101217] px-2 py-0.5 text-[11px] leading-4 text-[#cbd5e1]">
+                                      {pluginTypeLabel(t, typeLabel)}
+                                    </span>
+                                  )) : <span className="text-[#6f7681]">{t('common.none')}</span>}
+                                </div>
+                              </td>
+                              <td className="px-3 py-2.5">
+                                <div
+                                  className="flex items-center gap-2"
+                                  data-plugin-enable-optimistic-state={hasOptimisticEnableStatus ? 'overridden' : 'source'}
+                                  data-plugin-enable-optimistic-next-status={displayEnableStatus ? 'true' : 'false'}
+                                >
+                                  <PluginSwitch
+                                    checked={displayEnableStatus}
+                                    label={pluginStatusLabel(displayEnableStatus, t)}
+                                    onChange={() => onToggleEnabled(original)}
+                                  />
+                                  <span className="text-[11px] font-semibold text-[#cbd5e1]">
+                                    {pluginStatusLabel(displayEnableStatus, t)}
+                                  </span>
+                                </div>
+                              </td>
+                              <td className="px-3 py-2.5">
+                                <div data-plugin-row-actions="angular-row-actions-contextual" data-plugin-row-actions-owner="cold-icon-actions" className="flex gap-1.5">
+                                  {row.canEditParams ? (
+                                    <Button
+                                      size="icon"
+                                      variant="default"
+                                      className={coldIconButtonClassName}
+                                      onClick={() => {
+                                        setOpenRowActionMenuId(null);
+                                        onEditParams(original);
+                                      }}
+                                      aria-label={editParamsLabel}
+                                      title={editParamsLabel}
+                                      data-plugin-param-edit-open={String(original.id)}
+                                      data-plugin-row-action="params"
+                                      data-plugin-row-action-owner="row-contextual-icon-button"
+                                      data-plugin-row-action-label={row.name}
+                                    >
+                                      <Pencil className="h-3.5 w-3.5" aria-hidden="true" />
+                                      <span className="sr-only">{editParamsLabel}</span>
+                                    </Button>
+                                  ) : null}
+                                  <div
+                                    className={pluginActionMenuRootClassName}
+                                    data-plugin-row-delete-menu={rowActionMenuId}
+                                    data-plugin-row-delete-menu-contract="angular-ellipsis-dropdown-delete"
+                                    data-plugin-row-delete-menu-layer="overlay-visible-above-panel"
+                                    data-plugin-row-delete-menu-clearance="floating-overlay-no-panel-crop"
+                                    data-plugin-row-delete-menu-open={isRowActionMenuOpen ? 'true' : 'false'}
+                                  >
+                                    <button
+                                      type="button"
+                                      aria-expanded={isRowActionMenuOpen}
+                                      aria-label={t('common.edit')}
+                                      title={t('common.edit')}
+                                      className={`${coldIconButtonClassName} inline-flex cursor-pointer list-none items-center justify-center [&::-webkit-details-marker]:hidden`}
+                                      onClick={() => setOpenRowActionMenuId(current => (current === rowActionMenuId ? null : rowActionMenuId))}
+                                      data-plugin-row-delete-menu-trigger={rowActionMenuId}
+                                      data-plugin-row-action-label={row.name}
+                                    >
+                                      <MoreHorizontal className="h-3.5 w-3.5" aria-hidden="true" />
+                                      <span className="sr-only">{deleteLabel}</span>
+                                    </button>
+                                    <div
+                                      role="menu"
+                                      hidden={!isRowActionMenuOpen}
+                                      className={`${pluginActionMenuPanelBaseClassName} right-0`}
+                                      data-plugin-row-delete-menu-panel={rowActionMenuId}
+                                      data-plugin-row-delete-menu-layer-panel="overlay-visible-above-panel"
+                                      data-plugin-row-delete-menu-clearance-panel="floating-overlay-no-panel-crop"
+                                      data-plugin-row-delete-menu-owner="hertzbeat-ui-table-row-action-button"
+                                      data-plugin-row-delete-menu-panel-open={isRowActionMenuOpen ? 'true' : 'false'}
+                                    >
+                                      <HzTableRowActionButton
+                                        width="root-span"
+                                        intent="ghost"
+                                        onClick={() => {
+                                          setOpenRowActionMenuId(null);
+                                          onDeleteOne(original);
+                                        }}
+                                        data-plugin-delete-one={String(original.id)}
+                                        data-plugin-delete-one-owner="hertzbeat-ui-table-row-action-button"
+                                        data-plugin-row-action="delete"
+                                        data-plugin-row-action-label={row.name}
+                                        className="w-full text-[#fecaca] hover:text-white"
+                                      >
+                                        <Trash2 className="h-3.5 w-3.5" aria-hidden="true" />
+                                        <span className="truncate">{t('plugin.delete')}</span>
+                                      </HzTableRowActionButton>
+                                    </div>
+                                  </div>
+                                </div>
+                              </td>
+                            </tr>
+                          );
+                        }) : (
+                          <tr data-plugin-manage-empty-state="cold-table-empty" className="border-t border-[#252b34] bg-[#0b0c0e]">
+                            <td colSpan={5} className="h-[320px] px-3 text-center text-[#a9b0bb]">
+                              <div className="inline-flex flex-col items-center gap-2.5">
+                                <span
+                                  data-plugin-manage-empty-icon="cold-empty-box"
+                                  className="inline-flex h-10 w-10 items-center justify-center rounded-[4px] border border-[#303743] bg-[#101217] text-[#cbd5e1]"
+                                >
+                                  <Inbox className="h-5 w-5" aria-hidden="true" />
                                 </span>
-                              </div>
-                            </td>
-                            <td className="px-3 py-2.5 tabular-nums">{row.paramCount}</td>
-                            <td className="px-3 py-2.5">
-                              <div data-plugin-row-actions="cold-icon-actions" className="flex gap-1.5">
-                                {row.canEditParams ? (
-                                  <Button size="icon" variant="default" className={coldIconButtonClassName} onClick={() => onEditParams(original)} title={t('plugin.param.edit')}>
-                                    <Pencil className="h-3.5 w-3.5" aria-hidden="true" />
-                                    <span className="sr-only">{t('plugin.param.edit')}</span>
-                                  </Button>
-                                ) : null}
-                                <Button size="icon" variant="default" className={coldIconButtonClassName} onClick={() => onDeleteOne(original)} title={t('plugin.delete')}>
-                                  <Trash2 className="h-3.5 w-3.5" aria-hidden="true" />
-                                  <span className="sr-only">{t('plugin.delete')}</span>
-                                </Button>
+                                <div className="text-[13px] font-semibold text-[#eef2f7]">{t('common.no-data')}</div>
                               </div>
                             </td>
                           </tr>
-                        );
-                      }) : (
-                        <tr data-plugin-manage-empty-state="cold-table-empty" className="border-t border-[#252b34] bg-[#0b0c0e]">
-                          <td colSpan={6} className="h-[240px] px-3 text-center text-[#a9b0bb]">
-                            <div className="inline-flex flex-col items-center gap-2.5">
-                              <span
-                                data-plugin-manage-empty-icon="cold-empty-box"
-                                className="inline-flex h-10 w-10 items-center justify-center rounded-[4px] border border-[#303743] bg-[#101217] text-[#cbd5e1]"
-                              >
-                                <Inbox className="h-5 w-5" aria-hidden="true" />
-                              </span>
-                              <div className="text-[13px] font-semibold text-[#eef2f7]">暂无数据</div>
-                            </div>
-                          </td>
-                        </tr>
-                      )}
-                    </tbody>
-                  </table>
+                        )}
+                      </tbody>
+                    </table>
+                  </div>
+                  <div
+                    data-plugin-pagination="cold-dense-pagination"
+                    data-plugin-pagination-owner="hertzbeat-ui-pagination-bar"
+                    data-plugin-pagination-contract="angular-search-pagination"
+                  >
+                    <HzPaginationBar
+                      summary={paginationSummary}
+                      pageSizeLabel={t('setting.plugins.pagination.page-size')}
+                      pageSizeValue={String(currentPageSize)}
+                      pageSizeOptions={pageSizeOptions.map(value => ({ value: String(value), label: String(value) }))}
+                      pageJumpLabel={t('setting.plugins.pagination.page')}
+                      pageJumpValue={String(currentPage)}
+                      pageJumpMax={totalPages}
+                      previousLabel={t('common.previous-page')}
+                      nextLabel={t('common.next-page')}
+                      previousDisabled={currentPageIndex <= 0}
+                      nextDisabled={currentPage >= totalPages}
+                      onPrevious={() => onPageIndexChange?.(Math.max(currentPageIndex - 1, 0))}
+                      onNext={() => onPageIndexChange?.(Math.min(currentPageIndex + 1, totalPages - 1))}
+                      onPageSizeChange={value => onPageSizeChange?.(Number.parseInt(value, 10))}
+                      onPageJumpChange={handlePageJumpChange}
+                      pageJumpInputProps={
+                        {
+                          'data-plugin-pagination-page-jump-owner': 'hertzbeat-ui-input'
+                        } as React.ComponentProps<typeof HzPaginationBar>['pageJumpInputProps']
+                      }
+                      pageSizeSelectProps={
+                        {
+                          'data-plugin-pagination-page-size-owner': 'hertzbeat-ui-select'
+                        } as React.ComponentProps<typeof HzPaginationBar>['pageSizeSelectProps']
+                      }
+                      className="border-x-0"
+                    />
+                  </div>
                 </div>
               </section>
             </div>
@@ -289,55 +884,427 @@ export function PluginManageSurface({
       </div>
 
       <OverlayDialog
-        open={Boolean(draftPlugin)}
+        open={Boolean((isUploadDialogOpen ?? Boolean(draftPlugin)) && draftPlugin)}
         title={t('plugin.upload')}
         onClose={onCloseDialog}
-        maxWidthClassName="max-w-2xl"
+        maskClosable={false}
+        maxWidthClassName="max-w-[min(92vw,30rem)] lg:max-w-[30vw]"
+        overlayProps={{
+          'data-plugin-upload-mask-closable': 'false',
+          'data-plugin-upload-width': 'angular-width-30-percent',
+          'data-plugin-upload-modal-owner': 'angular-nz-modal'
+        }}
         footer={
           <div className="flex flex-wrap justify-end gap-2">
-            <Button size="sm" variant="default" className={coldButtonClassName} onClick={onCloseDialog}>
+            <Button
+              size="sm"
+              variant="default"
+              className={coldButtonClassName}
+              onClick={onCloseDialog}
+              data-plugin-upload-cancel="angular-cancel-preserves-form"
+              data-plugin-upload-cancel-pending="angular-cancel-allowed-during-ok-loading"
+              data-plugin-upload-cancel-pending-owner="angular-nz-modal"
+            >
               {t('common.button.cancel')}
             </Button>
-            <Button size="sm" variant="default" className={coldPrimaryButtonClassName} onClick={onSaveDialog}>
+            <Button
+              size="sm"
+              variant="default"
+              className={coldPrimaryButtonClassName}
+              onClick={onSaveDialog}
+              disabled={Boolean(isUploadPending)}
+              data-plugin-upload-save="angular-form-data"
+              data-plugin-upload-ok-loading={isUploadPending ? 'true' : 'false'}
+              data-plugin-upload-ok-loading-owner="angular-nz-ok-loading"
+              data-plugin-upload-save-lifecycle="angular-close-success-keep-open-fail"
+              data-plugin-upload-save-lifecycle-owner="route-state-contract"
+              aria-busy={isUploadPending ? 'true' : 'false'}
+            >
               {t('common.button.save')}
             </Button>
           </div>
         }
       >
         {draftPlugin ? (
-          <div className="space-y-3">
-            <label className="flex flex-col gap-2 text-[12px] font-semibold text-[#a9b0bb]">
-              <span>{t('plugin.name')}</span>
-              <Input
-                className={coldDialogInputClassName}
-                value={draftPlugin.name}
-                onChange={event => onDraftChange({ name: event.target.value })}
-              />
+          <div
+            className="space-y-3"
+            data-plugin-upload-dialog="angular-cancel-preserves-form"
+            data-plugin-upload-field-layout="angular-label-8-control-14"
+            data-plugin-upload-invalid-submit={uploadValidation?.name || uploadValidation?.jarFile ? 'angular-mark-dirty-keep-open' : undefined}
+            data-plugin-upload-invalid-submit-owner={uploadValidation?.name || uploadValidation?.jarFile ? 'route-validation-contract' : undefined}
+          >
+            <label
+              className="grid gap-2 text-[12px] font-semibold text-[#a9b0bb] sm:grid-cols-[minmax(96px,8fr)_minmax(0,14fr)] sm:items-start"
+              data-plugin-upload-field="name"
+              data-plugin-upload-field-layout="angular-label-8-control-14"
+            >
+              <span className="pt-2 text-right max-sm:text-left" data-plugin-upload-label-span="8">{t('plugin.name')}</span>
+              <span className="min-w-0" data-plugin-upload-control-span="14">
+                <Input
+                  className={coldDialogInputClassName}
+                  value={draftPlugin.name}
+                  onChange={event => onDraftChange({ name: event.target.value })}
+                  data-plugin-upload-name-input="angular-required"
+                  data-plugin-upload-name-payload="angular-raw-name-no-trim"
+                  data-plugin-upload-name-payload-owner="plugin-manage-controller"
+                />
+              </span>
+              {uploadValidation?.name ? (
+                <span className="text-[11px] font-semibold text-[#ffb4a8] sm:col-start-2" data-plugin-upload-validation="name-required">
+                  {t('validation.required')}
+                </span>
+              ) : null}
             </label>
-            <label className="flex flex-col gap-2 text-[12px] font-semibold text-[#a9b0bb]">
-              <span>{t('plugin.jar.file')}</span>
-              <div className="flex flex-wrap items-center gap-2">
+            <label
+              className="grid gap-2 text-[12px] font-semibold text-[#a9b0bb] sm:grid-cols-[minmax(96px,8fr)_minmax(0,14fr)] sm:items-start"
+              data-plugin-upload-field="jarFile"
+              data-plugin-upload-field-layout="angular-label-8-control-14"
+            >
+              <span className="pt-2 text-right max-sm:text-left" data-plugin-upload-label-span="8">{t('plugin.jar.file')}</span>
+              <span className="flex min-w-0 flex-wrap items-center gap-2" data-plugin-upload-control-span="14">
+                <HzFileInput
+                  ref={pluginJarInputRef}
+                  accept=".jar"
+                  aria-label={t('plugin.jar.file')}
+                  data-plugin-upload-file-input="angular-jar-before-upload"
+                  data-plugin-upload-file-input-owner="hertzbeat-ui-file-input"
+                  data-plugin-upload-file-list="angular-before-upload-single-replace"
+                  data-plugin-upload-file-list-owner="hertzbeat-ui-file-input"
+                  onChange={event => {
+                    const file = event.target.files?.[0] ?? null;
+                    onDraftChange({
+                      jarFile: file,
+                      jarFileName: file?.name ?? ''
+                    });
+                  }}
+                />
                 <Input
                   className={`${coldDialogInputClassName} min-w-0 flex-1`}
                   value={draftPlugin.jarFileName}
-                  onChange={event => onDraftChange({ jarFileName: event.target.value })}
+                  readOnly
+                  placeholder={t('plugin.jar.file')}
+                  data-plugin-upload-file-name="angular-jar-before-upload"
                 />
-                <Button size="sm" variant="default" className={coldButtonClassName} type="button">
+                <Button
+                  size="sm"
+                  variant="default"
+                  className={coldButtonClassName}
+                  type="button"
+                  data-plugin-upload-file-trigger="angular-jar-before-upload"
+                  onClick={() => pluginJarInputRef.current?.click()}
+                >
                   <Upload className="h-3.5 w-3.5" aria-hidden="true" />
                   {t('common.file.select')}
                 </Button>
-              </div>
+                <Button
+                  size="sm"
+                  variant="default"
+                  className={coldIconButtonClassName}
+                  type="button"
+                  aria-label={t('common.remove')}
+                  disabled={!draftPlugin.jarFileName}
+                  data-plugin-upload-file-remove="angular-nz-remove-clears-jar"
+                  data-plugin-upload-file-remove-owner="hertzbeat-ui-file-input"
+                  data-plugin-upload-file-remove-state={draftPlugin.jarFileName ? 'selected' : 'empty'}
+                  onClick={() => {
+                    if (pluginJarInputRef.current) {
+                      pluginJarInputRef.current.value = '';
+                    }
+                    onDraftChange({
+                      jarFile: null,
+                      jarFileName: ''
+                    });
+                  }}
+                >
+                  <X className="h-3.5 w-3.5" aria-hidden="true" />
+                </Button>
+              </span>
+              {uploadValidation?.jarFile ? (
+                <span className="text-[11px] font-semibold text-[#ffb4a8] sm:col-start-2" data-plugin-upload-validation="jar-required">
+                  {t('validation.required')}
+                </span>
+              ) : null}
             </label>
-            <Checkbox
-              data-plugin-draft-enabled-checkbox="cold-checkbox"
-              checked={draftPlugin.enableStatus}
-              onChange={event => onDraftChange({ enableStatus: event.target.checked })}
-              label={t('plugin.status')}
-              containerClassName="text-[#a9b0bb]"
-            />
+            <div
+              className="grid gap-2 text-[12px] font-semibold text-[#a9b0bb] sm:grid-cols-[minmax(96px,8fr)_minmax(0,14fr)] sm:items-center"
+              data-plugin-upload-field="enableStatus"
+              data-plugin-upload-field-layout="angular-label-8-control-14"
+              data-plugin-upload-status-field="angular-nz-switch"
+            >
+              <span className="text-right max-sm:text-left" data-plugin-upload-label-span="8">{t('plugin.status')}</span>
+              <span className="min-w-0" data-plugin-upload-control-span="14">
+                <HzSwitch
+                  checked={draftPlugin.enableStatus}
+                  onCheckedChange={checked => onDraftChange({ enableStatus: checked })}
+                  aria-label={t('plugin.status')}
+                  data-plugin-upload-status-control="angular-nz-switch"
+                  data-plugin-upload-status-control-owner="hertzbeat-ui-switch"
+                />
+              </span>
+            </div>
           </div>
         ) : null}
       </OverlayDialog>
+      <OverlayDialog
+        open={Boolean(paramDraft)}
+        title={t('plugin.edit')}
+        onClose={onCloseParamDialog ?? (() => {})}
+        maskClosable={false}
+        maxWidthClassName="max-w-[min(92vw,520px)]"
+        overlayProps={{
+          'data-plugin-param-mask-closable': 'false',
+          'data-plugin-param-modal-owner': 'angular-nz-modal',
+          'data-plugin-param-width': 'angular-default-modal-width',
+          'data-plugin-param-width-owner': 'angular-nz-modal'
+        }}
+        footer={
+          <div
+            className="flex flex-wrap justify-end gap-2"
+            data-plugin-param-save-loading-contract="angular-no-ok-loading"
+            data-plugin-param-save-loading="angular-no-ok-loading"
+            data-plugin-param-save-loading-owner="angular-modal-ok-contract"
+            data-plugin-param-save-pending={isParamPending ? 'true' : 'false'}
+            data-plugin-param-payload="angular-object-values-payload"
+            data-plugin-param-save-lifecycle="angular-close-success-keep-open-fail"
+            data-plugin-param-save-lifecycle-owner="route-state-contract"
+            data-plugin-param-pending-editable={isParamPending ? 'angular-controls-remain-enabled' : 'idle'}
+            data-plugin-param-pending-editable-owner="angular-param-modal"
+          >
+            <Button
+              size="sm"
+              variant="default"
+              className={coldButtonClassName}
+              onClick={onCloseParamDialog}
+              data-plugin-param-cancel-loading="angular-no-ok-loading"
+            >
+              {t('common.button.cancel')}
+            </Button>
+            <Button
+              size="sm"
+              variant="default"
+              className={coldPrimaryButtonClassName}
+              onClick={onSaveParamDialog}
+              data-plugin-param-save="angular-params-post"
+              data-plugin-param-save-loading="angular-no-ok-loading"
+              data-plugin-param-save-loading-owner="angular-modal-ok-contract"
+            >
+              {t('common.button.save')}
+            </Button>
+          </div>
+        }
+      >
+        {paramDraft ? (
+          <div
+            className="space-y-3"
+            data-plugin-param-dialog="angular-params-define-modal"
+            data-plugin-param-dialog-plugin={paramDraft.plugin.name}
+          >
+            <HzMonitorEditorFieldGrid
+              columns={1}
+              data-plugin-param-form="angular-dynamic-form-field"
+              data-plugin-param-form-owner="hertzbeat-ui-monitor-editor-field-grid"
+              data-plugin-param-field-layout="angular-label-7-control-8"
+              data-plugin-param-field-layout-owner="hertzbeat-ui-monitor-editor-field-grid"
+            >
+              {paramDraft.paramDefines.length > 0 ? paramDraft.paramDefines.map(define => {
+                const current = paramDraft.params[define.field];
+                const value = current?.paramValue;
+                const stringValue = stringifyPluginParamValue(value);
+                const label = typeof define.name === 'string' ? define.name : define.field;
+                const isMultiFuncInput = isPluginParamMultiFuncInput(define);
+                return (
+                  <label
+                    key={define.field}
+                    className="grid gap-2 text-[12px] font-semibold text-[#a9b0bb] sm:grid-cols-[minmax(112px,7fr)_minmax(0,8fr)] sm:items-start"
+                    data-plugin-param-field={define.field}
+                    data-plugin-param-field-layout="angular-label-7-control-8"
+                    data-plugin-param-field-type={define.type ?? 'password'}
+                    data-plugin-param-field-required={define.required ? 'true' : 'false'}
+                  >
+                    <span className="pt-2 text-right max-sm:text-left" data-plugin-param-label-span="7">
+                      {label}
+                      {define.required ? <span className="ml-1 text-[#ffb4a8]">*</span> : null}
+                    </span>
+                    <span className="min-w-0" data-plugin-param-control-span="8">
+                      {define.type === 'radio' && (define.options ?? []).length > 0 ? (
+                        <HzRadioButtonGroup
+                          name={define.field}
+                          value={stringifyPluginParamValue(value)}
+                          options={buildPluginParamRadioOptions(define)}
+                          onChange={nextValue => onParamChange?.(define.field, nextValue)}
+                          data-plugin-param-radio="angular-nz-radio-group"
... 4381 lines suppressed ...


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@hertzbeat.apache.org
For additional commands, e-mail: notifications-help@hertzbeat.apache.org