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:14 UTC

(hertzbeat) 04/50: feat(web-next): restore monitor editor 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 388b76446e7444524ee058c7456fcf65d4328eca
Author: Logic <zq...@dromara.org>
AuthorDate: Fri May 29 00:21:14 2026 +0800

    feat(web-next): restore monitor editor parity
---
 .../[monitorId]/edit/monitor-edit-page.tsx         |  103 ++
 .../app/monitors/[monitorId]/edit/page.test.tsx    |  154 ++-
 web-next/app/monitors/[monitorId]/edit/page.tsx    |   49 +-
 web-next/app/monitors/new/monitor-new-page.tsx     |   71 ++
 web-next/app/monitors/new/page.test.tsx            |  158 ++-
 web-next/app/monitors/new/page.tsx                 |   53 +-
 .../pages/monitor-editor-surface.test.tsx          | 1280 +++++++++++++++++++-
 .../components/pages/monitor-editor-surface.tsx    | 1150 +++++++++++++-----
 web-next/lib/monitor-api-facade.test.ts            |   76 ++
 web-next/lib/monitor-api-facade.ts                 |   84 ++
 web-next/lib/monitor-editor/controller.test.ts     |  316 ++++-
 web-next/lib/monitor-editor/controller.ts          |  197 ++-
 web-next/lib/monitor-editor/navigation.test.ts     |    9 +-
 web-next/lib/monitor-editor/navigation.ts          |    4 +
 web-next/lib/monitor-editor/query-state.test.ts    |  107 ++
 web-next/lib/monitor-editor/query-state.ts         |   57 +
 16 files changed, 3396 insertions(+), 472 deletions(-)

diff --git a/web-next/app/monitors/[monitorId]/edit/monitor-edit-page.tsx b/web-next/app/monitors/[monitorId]/edit/monitor-edit-page.tsx
new file mode 100644
index 0000000000..9a1dde940e
--- /dev/null
+++ b/web-next/app/monitors/[monitorId]/edit/monitor-edit-page.tsx
@@ -0,0 +1,103 @@
+'use client';
+
+import React, { useCallback } from 'react';
+import { HzButton, HzInlineFeedback } from '@hertzbeat/ui';
+import { ClientWorkbench } from '@/components/workbench/client-workbench';
+import { MonitorEditorSurface } from '../../../../components/pages/monitor-editor-surface';
+import { useI18n } from '@/components/providers/i18n-provider';
+import { api } from '@/lib/monitor-api-facade';
+import { buildMonitorEditCacheKey, loadMonitorEditorDraftFromFacade } from '@/lib/monitor-editor/controller';
+import type { MonitorEditRouteState } from '@/lib/monitor-editor/query-state';
+
+const MONITOR_EDIT_SETTLED_CACHE_TTL_MS = 10_000;
+const EMPTY_MONITOR_EDIT_ROUTE_STATE: MonitorEditRouteState = {
+  returnContext: {}
+};
+
+export default function MonitorEditPage({
+  monitorId,
+  initialRouteState
+}: {
+  monitorId: string;
+  initialRouteState?: MonitorEditRouteState;
+}) {
+  const { t } = useI18n();
+  const monitorEditRouteState = initialRouteState ?? EMPTY_MONITOR_EDIT_ROUTE_STATE;
+  const { returnContext } = monitorEditRouteState;
+  const monitorEditCacheKey = React.useMemo(() => buildMonitorEditCacheKey(monitorId), [monitorId]);
+
+  const load = useCallback(async () => {
+    return loadMonitorEditorDraftFromFacade(
+      {
+        readMonitorDetail: api.monitors.editorDetail,
+        readCollectors: api.monitors.editorCollectors,
+        readParamDefines: api.monitors.editorParamDefines
+      },
+      'edit',
+      { monitorId }
+    );
+  }, [monitorId]);
+
+  return (
+    <div
+      data-monitor-editor-app-source-contract="angular-route-context-hidden-field"
+      data-monitor-editor-static-host-position-contract="angular-before-name"
+      data-monitor-editor-field-order-contract="angular-monitor-form-sequence"
+      data-monitor-editor-detect-payload-contract="angular-monitor-collector-params-no-grafana"
+      data-monitor-editor-save-payload-contract="angular-monitor-collector-params-grafana"
+      data-monitor-editor-payload-param-merge-contract="angular-params-advanced-sdparams"
+      data-monitor-editor-payload-host-instance-contract="angular-host-param-as-instance"
+      data-monitor-editor-service-discovery-params="angular-nonstatic-only"
+      data-monitor-editor-ssl-port-notice="angular-info-notification"
+      data-monitor-editor-advanced-collapse="angular-ghost-collapse-dashed-trigger"
+      data-monitor-editor-label-selector="angular-app-label-selector"
+    >
+      <ClientWorkbench
+        load={load}
+        loadingCopy={t('monitor.editor.loading.edit')}
+        cacheKey={monitorEditCacheKey}
+        cacheSettledTtlMs={MONITOR_EDIT_SETTLED_CACHE_TTL_MS}
+        renderError={(message, retry) => (
+          <section
+            className="mx-auto w-full max-w-[1320px] px-3 py-6"
+            data-monitor-editor-route-state="error"
+            data-monitor-editor-route-state-owner="hertzbeat-ui-inline-feedback"
+            data-monitor-editor-edit-error-state="angular-route-state"
+          >
+            <HzInlineFeedback
+              tone="critical"
+              title={t('monitor.route-state.form.error.title')}
+              description={
+                <>
+                  {t('monitor.route-state.form.error.copy')}
+                  <span className="sr-only">{message}</span>
+                </>
+              }
+              variant="embedded"
+              data-monitor-editor-route-state-feedback="error"
+            />
+            <div className="mt-3 flex justify-center">
+              <HzButton
+                size="sm"
+                intent="primary"
+                onClick={retry}
+                data-monitor-editor-route-state-retry="true"
+                data-monitor-editor-route-state-retry-owner="hertzbeat-ui-button"
+              >
+                {t('monitor.route-state.action.retry')}
+              </HzButton>
+            </div>
+          </section>
+        )}
+      >
+        {data => (
+          <MonitorEditorSurface
+            initial={data}
+            mode="edit"
+            returnContext={returnContext}
+          />
+        )}
+      </ClientWorkbench>
+    </div>
+  );
+}
diff --git a/web-next/app/monitors/[monitorId]/edit/page.test.tsx b/web-next/app/monitors/[monitorId]/edit/page.test.tsx
index a34af5e414..66af81e4b5 100644
--- a/web-next/app/monitors/[monitorId]/edit/page.test.tsx
+++ b/web-next/app/monitors/[monitorId]/edit/page.test.tsx
@@ -1,11 +1,13 @@
 import React from 'react';
+import { readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
 import { renderToStaticMarkup } from 'react-dom/server';
 import { beforeEach, describe, expect, it, vi } from 'vitest';
-import MonitorEditPage from './page';
+import MonitorEditPage from './monitor-edit-page';
 import { createTranslatorMock } from '../../../../test/i18n-test-helper';
+import type { MonitorEditRouteState } from '../../../../lib/monitor-editor/query-state';
 
 const mockState = vi.hoisted(() => ({
-  searchParams: new URLSearchParams(),
   lastLoad: null as null | (() => Promise<unknown>),
   routerPush: vi.fn(),
   draft: {
@@ -33,35 +35,39 @@ const mockState = vi.hoisted(() => ({
   }
 }));
 
-const loadMonitorEditorDraft = vi.hoisted(() => vi.fn(async () => mockState.draft));
+const loadMonitorEditorDraftFromFacade = vi.hoisted(() => vi.fn(async () => mockState.draft));
 
 vi.mock('next/navigation', () => ({
   useRouter: () => ({
     push: mockState.routerPush
-  }),
-  useSearchParams: () =>
-    ({
-      get: (key: string) => mockState.searchParams.get(key)
-    }) as { get(name: string): string | null }
+  })
 }));
 
 vi.mock('@/components/providers/i18n-provider', () => ({
   useI18n: () => ({
-    t: createTranslatorMock(),
-    locale: 'en-US'
+    t: createTranslatorMock({ locale: 'zh-CN' }),
+    locale: 'zh-CN'
   })
 }));
 
 vi.mock('@/components/workbench/client-workbench', () => ({
   ClientWorkbench: ({
     children,
-    load
+    load,
+    cacheKey,
+    cacheSettledTtlMs
   }: {
     children: (data: any) => React.ReactNode;
     load: () => Promise<unknown>;
+    cacheKey?: string;
+    cacheSettledTtlMs?: number;
   }) => {
     mockState.lastLoad = load;
-    return <div data-client-workbench="true">{children(mockState.draft)}</div>;
+    return (
+      <div data-client-workbench="true" data-cache-key={cacheKey} data-cache-settled-ttl={cacheSettledTtlMs}>
+        {children(mockState.draft)}
+      </div>
+    );
   }
 }));
 
@@ -118,10 +124,23 @@ vi.mock('@/lib/api-client', () => ({
   apiMessagePut: vi.fn()
 }));
 
+vi.mock('@/lib/monitor-api-facade', () => ({
+  api: {
+    monitors: {
+      editorDetail: vi.fn(),
+      editorCollectors: vi.fn(),
+      editorParamDefines: vi.fn()
+    }
+  }
+}));
+
 vi.mock('@/lib/monitor-editor/controller', () => ({
+  buildMonitorEditCacheKey: (monitorId: string) => `monitor-editor-edit:/monitor/${monitorId}:/collector`,
+  buildMonitorEditorCollectorsUrl: () => '/collector',
+  buildMonitorEditorMonitorUrl: (monitorId: string) => `/monitor/${monitorId}`,
   createMonitor: vi.fn(),
   detectMonitor: vi.fn(),
-  loadMonitorEditorDraft,
+  loadMonitorEditorDraftFromFacade,
   loadMonitorScrapeDraft: vi.fn(async () => ({
     scrapeParams: [],
     scrapeParamDefines: []
@@ -136,6 +155,7 @@ vi.mock('@/lib/monitor-editor/controller', () => ({
 }));
 
 vi.mock('@/lib/monitor-editor/navigation', () => ({
+  buildMonitorEditorCancelUrl: () => '/monitors',
   buildMonitorEditorReturnUrl: () => '/monitors?app=website'
 }));
 
@@ -170,29 +190,121 @@ vi.mock('@/lib/utils', () => ({
   cn: (...values: Array<string | undefined | null | false>) => values.filter(Boolean).join(' ')
 }));
 
+function renderMonitorEditPage(
+  initialRouteState: MonitorEditRouteState = {
+    returnContext: { returnTo: '/entities/7' }
+  }
+) {
+  return renderToStaticMarkup(<MonitorEditPage monitorId="42" initialRouteState={initialRouteState} />);
+}
+
 describe('MonitorEditPage', () => {
   beforeEach(() => {
-    mockState.searchParams = new URLSearchParams('returnTo=%2Fentities%2F7');
     mockState.lastLoad = null;
     mockState.routerPush.mockReset();
-    loadMonitorEditorDraft.mockClear().mockResolvedValue(mockState.draft);
+    loadMonitorEditorDraftFromFacade.mockClear().mockResolvedValue(mockState.draft);
   });
 
   it('renders the HertzBeat monitor form surface for the edit route', () => {
-    const html = renderToStaticMarkup(<MonitorEditPage params={Promise.resolve({ monitorId: '42' })} />);
+    const html = renderMonitorEditPage();
 
     expect(html).toContain('<form');
+    expect(html).toContain('data-cache-key="monitor-editor-edit:/monitor/42:/collector"');
+    expect(html).toContain('data-cache-settled-ttl="10000"');
+    expect(html).toContain('data-monitor-editor-app-source-contract="angular-route-context-hidden-field"');
+    expect(html).toContain('data-monitor-editor-static-host-position-contract="angular-before-name"');
+    expect(html).toContain('data-monitor-editor-field-order-contract="angular-monitor-form-sequence"');
+    expect(html).toContain('data-monitor-editor-detect-payload-contract="angular-monitor-collector-params-no-grafana"');
+    expect(html).toContain('data-monitor-editor-save-payload-contract="angular-monitor-collector-params-grafana"');
+    expect(html).toContain('data-monitor-editor-payload-param-merge-contract="angular-params-advanced-sdparams"');
+    expect(html).toContain('data-monitor-editor-payload-host-instance-contract="angular-host-param-as-instance"');
+    expect(html).toContain('data-monitor-editor-service-discovery-params="angular-nonstatic-only"');
+    expect(html).toContain('data-monitor-editor-ssl-port-notice="angular-info-notification"');
+    expect(html).toContain('data-monitor-editor-advanced-collapse="angular-ghost-collapse-dashed-trigger"');
+    expect(html).toContain('data-monitor-editor-label-selector="angular-app-label-selector"');
     expect(html).toContain('checkout');
-    expect(html).toContain('OK');
+    expect(html).toContain('确定');
   });
 
   it('loads the edit-monitor draft with the current monitor id', async () => {
-    renderToStaticMarkup(<MonitorEditPage params={Promise.resolve({ monitorId: '42' })} />);
+    renderMonitorEditPage();
 
     await mockState.lastLoad?.();
 
-    expect(loadMonitorEditorDraft).toHaveBeenCalledWith(expect.any(Function), 'edit', {
-      monitorId: '42'
-    });
+    expect(loadMonitorEditorDraftFromFacade).toHaveBeenCalledWith(
+      expect.objectContaining({
+        readMonitorDetail: expect.any(Function),
+        readCollectors: expect.any(Function),
+        readParamDefines: expect.any(Function)
+      }),
+      'edit',
+      {
+        monitorId: '42'
+      }
+    );
+  });
+
+  it('accepts typed return context from the edit route wrapper without changing the draft loader', async () => {
+    const initialRouteState: MonitorEditRouteState = {
+      returnContext: {
+        labels: 'team=platform',
+        entityId: '7',
+        entityName: 'Checkout',
+        timeRange: '1h',
+        returnTo: '/entities/7'
+      }
+    };
+    const html = renderMonitorEditPage(initialRouteState);
+
+    expect(html).toContain('data-cache-key="monitor-editor-edit:/monitor/42:/collector"');
+
+    await mockState.lastLoad?.();
+
+    expect(loadMonitorEditorDraftFromFacade).toHaveBeenCalledWith(
+      expect.objectContaining({
+        readMonitorDetail: expect.any(Function),
+        readCollectors: expect.any(Function),
+        readParamDefines: expect.any(Function)
+      }),
+      'edit',
+      {
+        monitorId: '42'
+      }
+    );
+  });
+
+  it('loads monitor editor draft through the monitor domain facade instead of a raw getter', () => {
+    const source = readFileSync(resolve(process.cwd(), 'app/monitors/[monitorId]/edit/monitor-edit-page.tsx'), 'utf8');
+
+    expect(source).toContain('loadMonitorEditorDraftFromFacade(');
+    expect(source).toContain('readMonitorDetail: api.monitors.editorDetail');
+    expect(source).toContain('readCollectors: api.monitors.editorCollectors');
+    expect(source).toContain('readParamDefines: api.monitors.editorParamDefines');
+    expect(source).not.toContain("import { apiMessageGet } from '@/lib/api-client'");
+    expect(source).not.toContain('loadMonitorEditorDraft(apiMessageGet');
+  });
+
+  it('keeps edit load failures in the Angular-style monitor form route-state surface', () => {
+    const source = readFileSync(resolve(process.cwd(), 'app/monitors/[monitorId]/edit/monitor-edit-page.tsx'), 'utf8');
+
+    expect(source).toContain('renderError={(message, retry) =>');
+    expect(source).toContain('data-monitor-editor-route-state="error"');
+    expect(source).toContain('data-monitor-editor-edit-error-state="angular-route-state"');
+    expect(source).toContain('data-monitor-editor-route-state-owner="hertzbeat-ui-inline-feedback"');
+    expect(source).toContain('data-monitor-editor-app-source-contract="angular-route-context-hidden-field"');
+    expect(source).toContain('data-monitor-editor-static-host-position-contract="angular-before-name"');
+    expect(source).toContain('data-monitor-editor-field-order-contract="angular-monitor-form-sequence"');
+    expect(source).toContain('data-monitor-editor-detect-payload-contract="angular-monitor-collector-params-no-grafana"');
+    expect(source).toContain('data-monitor-editor-save-payload-contract="angular-monitor-collector-params-grafana"');
+    expect(source).toContain('data-monitor-editor-payload-param-merge-contract="angular-params-advanced-sdparams"');
+    expect(source).toContain('data-monitor-editor-payload-host-instance-contract="angular-host-param-as-instance"');
+    expect(source).toContain('data-monitor-editor-service-discovery-params="angular-nonstatic-only"');
+    expect(source).toContain('data-monitor-editor-ssl-port-notice="angular-info-notification"');
+    expect(source).toContain('data-monitor-editor-advanced-collapse="angular-ghost-collapse-dashed-trigger"');
+    expect(source).toContain('data-monitor-editor-label-selector="angular-app-label-selector"');
+    expect(source).toContain("t('monitor.route-state.form.error.title')");
+    expect(source).toContain("t('monitor.route-state.form.error.copy')");
+    expect(source).toContain("t('monitor.route-state.action.retry')");
+    expect(source).toContain('data-monitor-editor-route-state-retry-owner="hertzbeat-ui-button"');
   });
 });
diff --git a/web-next/app/monitors/[monitorId]/edit/page.tsx b/web-next/app/monitors/[monitorId]/edit/page.tsx
index ad4ded7459..751d6afb7c 100644
--- a/web-next/app/monitors/[monitorId]/edit/page.tsx
+++ b/web-next/app/monitors/[monitorId]/edit/page.tsx
@@ -1,40 +1,17 @@
-'use client';
-
 import React from 'react';
-import { useCallback } from 'react';
-import { useSearchParams } from 'next/navigation';
-import { ClientWorkbench } from '@/components/workbench/client-workbench';
-import { MonitorEditorSurface } from '../../../../components/pages/monitor-editor-surface';
-import { useI18n } from '@/components/providers/i18n-provider';
-import { apiMessageGet } from '@/lib/api-client';
-import { loadMonitorEditorDraft } from '@/lib/monitor-editor/controller';
-
-export default function MonitorEditPage({ params }: { params: Promise<{ monitorId: string }> }) {
-  const { t } = useI18n();
-  const searchParams = useSearchParams();
-  const returnTo = searchParams.get('returnTo');
 
-  const load = useCallback(async () => {
-    const resolved = await params;
-    return loadMonitorEditorDraft(apiMessageGet, 'edit', { monitorId: resolved.monitorId });
-  }, [params]);
+import MonitorEditPage from './monitor-edit-page';
+import { readMonitorEditRouteState, type MonitorEditSearchParams } from '../../../../lib/monitor-editor/query-state';
 
-  return (
-    <ClientWorkbench load={load} loadingCopy={t('monitor.editor.loading.edit')}>
-      {data => (
-        <MonitorEditorSurface
-          initial={data}
-          mode="edit"
-          returnContext={{
-            labels: searchParams.get('labels'),
-            pageIndex: searchParams.get('pageIndex'),
-            pageSize: searchParams.get('pageSize'),
-            entityId: searchParams.get('entityId'),
-            entityName: searchParams.get('entityName'),
-            returnTo
-          }}
-        />
-      )}
-    </ClientWorkbench>
-  );
+export default async function MonitorEditRoutePage({
+  params,
+  searchParams
+}: {
+  params: Promise<{ monitorId: string }>;
+  searchParams?: Promise<MonitorEditSearchParams>;
+}) {
+  const { monitorId } = await params;
+  const resolvedSearchParams = await searchParams;
+  const routeState = readMonitorEditRouteState(resolvedSearchParams);
+  return <MonitorEditPage monitorId={monitorId} initialRouteState={routeState} />;
 }
diff --git a/web-next/app/monitors/new/monitor-new-page.tsx b/web-next/app/monitors/new/monitor-new-page.tsx
new file mode 100644
index 0000000000..64bad90869
--- /dev/null
+++ b/web-next/app/monitors/new/monitor-new-page.tsx
@@ -0,0 +1,71 @@
+'use client';
+
+import React, { useCallback } from 'react';
+import { ClientWorkbench } from '@/components/workbench/client-workbench';
+import { MonitorEditorSurface } from '../../../components/pages/monitor-editor-surface';
+import { useI18n } from '@/components/providers/i18n-provider';
+import { api } from '@/lib/monitor-api-facade';
+import { buildMonitorEditorCollectorsUrl, buildMonitorEditorParamDefinesUrl, loadMonitorEditorDraftFromFacade } from '@/lib/monitor-editor/controller';
+import type { MonitorNewRouteState } from '@/lib/monitor-editor/query-state';
+
+const MONITOR_NEW_SETTLED_CACHE_TTL_MS = 10_000;
+const EMPTY_MONITOR_NEW_ROUTE_STATE: MonitorNewRouteState = {
+  app: 'website',
+  returnContext: {}
+};
+
+export default function MonitorNewPage({ initialRouteState }: { initialRouteState?: MonitorNewRouteState } = {}) {
+  const { t } = useI18n();
+  const monitorNewRouteState = initialRouteState ?? EMPTY_MONITOR_NEW_ROUTE_STATE;
+  const { app, returnContext } = monitorNewRouteState;
+  const monitorNewCollectorsUrl = React.useMemo(() => buildMonitorEditorCollectorsUrl(), []);
+  const monitorNewParamsUrl = React.useMemo(() => buildMonitorEditorParamDefinesUrl(app), [app]);
+  const monitorNewCacheKey = React.useMemo(
+    () => ['monitor-editor-new', monitorNewCollectorsUrl, monitorNewParamsUrl].join(':'),
+    [monitorNewCollectorsUrl, monitorNewParamsUrl]
+  );
+
+  const load = useCallback(
+    async () =>
+      loadMonitorEditorDraftFromFacade(
+        {
+          readCollectors: api.monitors.editorCollectors,
+          readParamDefines: api.monitors.editorParamDefines
+        },
+        'new',
+        { app }
+      ),
+    [app]
+  );
+
+  return (
+    <div
+      data-monitor-editor-app-source-contract="angular-route-context-hidden-field"
+      data-monitor-editor-static-host-position-contract="angular-before-name"
+      data-monitor-editor-field-order-contract="angular-monitor-form-sequence"
+      data-monitor-editor-detect-payload-contract="angular-monitor-collector-params-no-grafana"
+      data-monitor-editor-save-payload-contract="angular-monitor-collector-params-grafana"
+      data-monitor-editor-payload-param-merge-contract="angular-params-advanced-sdparams"
+      data-monitor-editor-payload-host-instance-contract="angular-host-param-as-instance"
+      data-monitor-editor-service-discovery-params="angular-nonstatic-only"
+      data-monitor-editor-ssl-port-notice="angular-info-notification"
+      data-monitor-editor-advanced-collapse="angular-ghost-collapse-dashed-trigger"
+      data-monitor-editor-label-selector="angular-app-label-selector"
+    >
+      <ClientWorkbench
+        load={load}
+        loadingCopy={t('monitor.editor.loading.new')}
+        cacheKey={monitorNewCacheKey}
+        cacheSettledTtlMs={MONITOR_NEW_SETTLED_CACHE_TTL_MS}
+      >
+        {data => (
+          <MonitorEditorSurface
+            initial={data}
+            mode="new"
+            returnContext={returnContext}
+          />
+        )}
+      </ClientWorkbench>
+    </div>
+  );
+}
diff --git a/web-next/app/monitors/new/page.test.tsx b/web-next/app/monitors/new/page.test.tsx
index 4ca09c8e81..f90bf7fe33 100644
--- a/web-next/app/monitors/new/page.test.tsx
+++ b/web-next/app/monitors/new/page.test.tsx
@@ -1,13 +1,18 @@
 import React from 'react';
+import { readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
 import { renderToStaticMarkup } from 'react-dom/server';
 import { beforeEach, describe, expect, it, vi } from 'vitest';
-import MonitorNewPage from './page';
+import MonitorNewPage from './monitor-new-page';
 import { createTranslatorMock } from '../../../test/i18n-test-helper';
+import type { MonitorNewRouteState } from '../../../lib/monitor-editor/query-state';
 
 const mockState = vi.hoisted(() => ({
-  searchParams: new URLSearchParams(),
   lastLoad: null as null | (() => Promise<unknown>),
   routerPush: vi.fn(),
+  redirect: vi.fn((target: string) => {
+    throw new Error(`redirect:${target}`);
+  }),
   draft: {
     monitor: {
       id: 0,
@@ -33,35 +38,40 @@ const mockState = vi.hoisted(() => ({
   }
 }));
 
-const loadMonitorEditorDraft = vi.hoisted(() => vi.fn(async () => mockState.draft));
+const loadMonitorEditorDraftFromFacade = vi.hoisted(() => vi.fn(async () => mockState.draft));
 
 vi.mock('next/navigation', () => ({
+  redirect: mockState.redirect,
   useRouter: () => ({
     push: mockState.routerPush
-  }),
-  useSearchParams: () =>
-    ({
-      get: (key: string) => mockState.searchParams.get(key)
-    }) as { get(name: string): string | null }
+  })
 }));
 
 vi.mock('@/components/providers/i18n-provider', () => ({
   useI18n: () => ({
-    t: createTranslatorMock(),
-    locale: 'en-US'
+    t: createTranslatorMock({ locale: 'zh-CN' }),
+    locale: 'zh-CN'
   })
 }));
 
 vi.mock('@/components/workbench/client-workbench', () => ({
   ClientWorkbench: ({
     children,
-    load
+    load,
+    cacheKey,
+    cacheSettledTtlMs
   }: {
     children: (data: any) => React.ReactNode;
     load: () => Promise<unknown>;
+    cacheKey?: string;
+    cacheSettledTtlMs?: number;
   }) => {
     mockState.lastLoad = load;
-    return <div data-client-workbench="true">{children(mockState.draft)}</div>;
+    return (
+      <div data-client-workbench="true" data-cache-key={cacheKey} data-cache-settled-ttl={cacheSettledTtlMs}>
+        {children(mockState.draft)}
+      </div>
+    );
   }
 }));
 
@@ -118,10 +128,21 @@ vi.mock('@/lib/api-client', () => ({
   apiMessagePut: vi.fn()
 }));
 
+vi.mock('@/lib/monitor-api-facade', () => ({
+  api: {
+    monitors: {
+      editorCollectors: vi.fn(),
+      editorParamDefines: vi.fn()
+    }
+  }
+}));
+
 vi.mock('@/lib/monitor-editor/controller', () => ({
+  buildMonitorEditorCollectorsUrl: () => '/collector',
+  buildMonitorEditorParamDefinesUrl: (app: string) => `/apps/${app}/params`,
   createMonitor: vi.fn(),
   detectMonitor: vi.fn(),
-  loadMonitorEditorDraft,
+  loadMonitorEditorDraftFromFacade,
   loadMonitorScrapeDraft: vi.fn(async () => ({
     scrapeParams: [],
     scrapeParamDefines: []
@@ -136,6 +157,7 @@ vi.mock('@/lib/monitor-editor/controller', () => ({
 }));
 
 vi.mock('@/lib/monitor-editor/navigation', () => ({
+  buildMonitorEditorCancelUrl: () => '/monitors',
   buildMonitorEditorReturnUrl: () => '/monitors?app=website'
 }));
 
@@ -170,29 +192,121 @@ vi.mock('@/lib/utils', () => ({
   cn: (...values: Array<string | undefined | null | false>) => values.filter(Boolean).join(' ')
 }));
 
+function renderMonitorNewPage(
+  initialRouteState: MonitorNewRouteState = {
+    app: 'website',
+    returnContext: { returnTo: '/entities/7' }
+  }
+) {
+  return renderToStaticMarkup(<MonitorNewPage initialRouteState={initialRouteState} />);
+}
+
 describe('MonitorNewPage', () => {
   beforeEach(() => {
-    mockState.searchParams = new URLSearchParams('app=website&returnTo=%2Fentities%2F7');
     mockState.lastLoad = null;
     mockState.routerPush.mockReset();
-    loadMonitorEditorDraft.mockClear().mockResolvedValue(mockState.draft);
+    mockState.redirect.mockClear();
+    loadMonitorEditorDraftFromFacade.mockClear().mockResolvedValue(mockState.draft);
   });
 
   it('renders the HertzBeat monitor form surface for the new route', () => {
-    const html = renderToStaticMarkup(<MonitorNewPage />);
+    const html = renderMonitorNewPage();
 
     expect(html).toContain('<form');
-    expect(html).toContain('New monitor');
-    expect(html).toContain('OK');
+    expect(html).toContain('data-cache-key="monitor-editor-new:/collector:/apps/website/params"');
+    expect(html).toContain('data-cache-settled-ttl="10000"');
+    expect(html).toContain('data-monitor-editor-app-source-contract="angular-route-context-hidden-field"');
+    expect(html).toContain('data-monitor-editor-static-host-position-contract="angular-before-name"');
+    expect(html).toContain('data-monitor-editor-field-order-contract="angular-monitor-form-sequence"');
+    expect(html).toContain('data-monitor-editor-detect-payload-contract="angular-monitor-collector-params-no-grafana"');
+    expect(html).toContain('data-monitor-editor-save-payload-contract="angular-monitor-collector-params-grafana"');
+    expect(html).toContain('data-monitor-editor-payload-param-merge-contract="angular-params-advanced-sdparams"');
+    expect(html).toContain('data-monitor-editor-payload-host-instance-contract="angular-host-param-as-instance"');
+    expect(html).toContain('data-monitor-editor-service-discovery-params="angular-nonstatic-only"');
+    expect(html).toContain('data-monitor-editor-ssl-port-notice="angular-info-notification"');
+    expect(html).toContain('data-monitor-editor-advanced-collapse="angular-ghost-collapse-dashed-trigger"');
+    expect(html).toContain('data-monitor-editor-label-selector="angular-app-label-selector"');
+    expect(html).toContain('新建监控');
+    expect(html).toContain('确定');
   });
 
   it('loads the new-monitor draft with the current app context', async () => {
-    renderToStaticMarkup(<MonitorNewPage />);
+    renderMonitorNewPage();
+
+    await mockState.lastLoad?.();
+
+    expect(loadMonitorEditorDraftFromFacade).toHaveBeenCalledWith(
+      expect.objectContaining({
+        readCollectors: expect.any(Function),
+        readParamDefines: expect.any(Function)
+      }),
+      'new',
+      {
+        app: 'website'
+      }
+    );
+  });
+
+  it('uses the typed route state app when loading a new monitor from a handoff', async () => {
+    const initialRouteState: MonitorNewRouteState = {
+      app: 'prometheus',
+      returnContext: {
+        labels: 'team=platform',
+        entityId: '42',
+        entityName: 'Checkout Service',
+        timeRange: '1h',
+        returnTo: '/entities/42'
+      }
+    };
+    const html = renderMonitorNewPage(initialRouteState);
+
+    expect(html).toContain('data-cache-key="monitor-editor-new:/collector:/apps/prometheus/params"');
 
     await mockState.lastLoad?.();
 
-    expect(loadMonitorEditorDraft).toHaveBeenCalledWith(expect.any(Function), 'new', {
-      app: 'website'
-    });
+    expect(loadMonitorEditorDraftFromFacade).toHaveBeenCalledWith(
+      expect.objectContaining({
+        readCollectors: expect.any(Function),
+        readParamDefines: expect.any(Function)
+      }),
+      'new',
+      {
+        app: 'prometheus'
+      }
+    );
+  });
+
+  it('loads monitor editor draft through the monitor domain facade instead of a raw getter', () => {
+    const source = readFileSync(resolve(process.cwd(), 'app/monitors/new/monitor-new-page.tsx'), 'utf8');
+
+    expect(source).toContain('loadMonitorEditorDraftFromFacade(');
+    expect(source).toContain('readCollectors: api.monitors.editorCollectors');
+    expect(source).toContain('readParamDefines: api.monitors.editorParamDefines');
+    expect(source).not.toContain("import { apiMessageGet } from '@/lib/api-client'");
+    expect(source).not.toContain('loadMonitorEditorDraft(apiMessageGet');
+  });
+});
+
+describe('MonitorNewRoutePage', () => {
+  beforeEach(() => {
+    mockState.redirect.mockClear();
+  });
+
+  it('redirects missing app query to the Angular-style website monitor setup URL', async () => {
+    const { default: MonitorNewRoutePage } = await import('./page');
+
+    await expect(MonitorNewRoutePage({ searchParams: Promise.resolve({}) })).rejects.toThrow(
+      'redirect:/monitors/new?app=website'
+    );
+    expect(mockState.redirect).toHaveBeenCalledWith('/monitors/new?app=website');
+  });
+
+  it('does not redirect when the app query is present', async () => {
+    const { default: MonitorNewRoutePage } = await import('./page');
+    const element = await MonitorNewRoutePage({ searchParams: Promise.resolve({ app: 'prometheus' }) });
+    const html = renderToStaticMarkup(element);
+
+    expect(mockState.redirect).not.toHaveBeenCalled();
+    expect(html).toContain('data-cache-key="monitor-editor-new:/collector:/apps/prometheus/params"');
   });
 });
diff --git a/web-next/app/monitors/new/page.tsx b/web-next/app/monitors/new/page.tsx
index 2f346bead3..70d8c3dd43 100644
--- a/web-next/app/monitors/new/page.tsx
+++ b/web-next/app/monitors/new/page.tsx
@@ -1,38 +1,23 @@
-'use client';
-
 import React from 'react';
-import { useCallback } from 'react';
-import { useSearchParams } from 'next/navigation';
-import { ClientWorkbench } from '@/components/workbench/client-workbench';
-import { MonitorEditorSurface } from '../../../components/pages/monitor-editor-surface';
-import { useI18n } from '@/components/providers/i18n-provider';
-import { apiMessageGet } from '@/lib/api-client';
-import { loadMonitorEditorDraft } from '@/lib/monitor-editor/controller';
-
-export default function MonitorNewPage() {
-  const { t } = useI18n();
-  const searchParams = useSearchParams();
-  const app = searchParams.get('app') || 'website';
-  const returnTo = searchParams.get('returnTo');
+import { redirect } from 'next/navigation';
 
-  const load = useCallback(async () => loadMonitorEditorDraft(apiMessageGet, 'new', { app }), [app]);
+import MonitorNewPage from './monitor-new-page';
+import {
+  buildMonitorNewDefaultAppRedirectUrl,
+  hasMonitorNewAppParam,
+  readMonitorNewRouteState,
+  type MonitorNewSearchParams
+} from '../../../lib/monitor-editor/query-state';
 
-  return (
-    <ClientWorkbench load={load} loadingCopy={t('monitor.editor.loading.new')}>
-      {data => (
-        <MonitorEditorSurface
-          initial={data}
-          mode="new"
-          returnContext={{
-            labels: searchParams.get('labels'),
-            pageIndex: searchParams.get('pageIndex'),
-            pageSize: searchParams.get('pageSize'),
-            entityId: searchParams.get('entityId'),
-            entityName: searchParams.get('entityName'),
-            returnTo
-          }}
-        />
-      )}
-    </ClientWorkbench>
-  );
+export default async function MonitorNewRoutePage({
+  searchParams
+}: {
+  searchParams?: Promise<MonitorNewSearchParams>;
+} = {}) {
+  const resolvedSearchParams = await searchParams;
+  if (!hasMonitorNewAppParam(resolvedSearchParams)) {
+    redirect(buildMonitorNewDefaultAppRedirectUrl());
+  }
+  const routeState = readMonitorNewRouteState(resolvedSearchParams);
+  return <MonitorNewPage initialRouteState={routeState} />;
 }
diff --git a/web-next/components/pages/monitor-editor-surface.test.tsx b/web-next/components/pages/monitor-editor-surface.test.tsx
index fc2f56f3b4..9537e8f561 100644
--- a/web-next/components/pages/monitor-editor-surface.test.tsx
+++ b/web-next/components/pages/monitor-editor-surface.test.tsx
@@ -6,6 +6,10 @@ import { describe, expect, it, vi } from 'vitest';
 import { MonitorEditorSurface } from './monitor-editor-surface';
 import { createTranslatorMock } from '../../test/i18n-test-helper';
 
+const i18nMockState = vi.hoisted(() => ({
+  locale: 'en-US' as 'en-US' | 'zh-CN'
+}));
+
 vi.mock('next/navigation', () => ({
   useRouter: () => ({
     push: vi.fn()
@@ -14,11 +18,48 @@ vi.mock('next/navigation', () => ({
 
 vi.mock('@/components/providers/i18n-provider', () => ({
   useI18n: () => ({
-    t: createTranslatorMock(),
-    locale: 'en-US'
+    t: createTranslatorMock({ locale: i18nMockState.locale }),
+    locale: i18nMockState.locale
   })
 }));
 
+vi.mock('@uiw/react-codemirror', () => ({
+  default: ({ value, readOnly, extensions, height, minHeight, basicSetup, theme, onChange, ...props }: any) => (
+    <div
+      data-testid={props['data-testid']}
+      data-mocked-codemirror="true"
+      data-readonly={readOnly ? 'true' : 'false'}
+      data-height={height}
+      data-min-height={minHeight}
+      data-basic-setup={basicSetup ? 'true' : 'false'}
+      data-theme={theme ? 'custom-dark' : 'missing'}
+      data-extension-count={Array.isArray(extensions) ? String(extensions.length) : '0'}
+      onClick={() => onChange?.(`${value}\nnext`)}
+    >
+      {value}
+    </div>
+  )
+}));
+
+vi.mock('@codemirror/lang-yaml', () => ({ yaml: () => 'yaml-extension' }));
+vi.mock('@codemirror/lang-json', () => ({ json: () => 'json-extension' }));
+vi.mock('@codemirror/lang-html', () => ({ html: () => 'html-extension' }));
+vi.mock('@codemirror/lang-javascript', () => ({ javascript: () => 'javascript-extension' }));
+vi.mock('@codemirror/view', () => ({
+  EditorView: {
+    lineWrapping: 'line-wrapping-extension',
+    theme: () => 'theme-extension'
+  }
+}));
+vi.mock('@codemirror/state', () => ({
+  EditorState: {
+    readOnly: {
+      of: () => 'readonly-extension'
+    }
+  }
+}));
+vi.mock('@codemirror/theme-one-dark', () => ({ oneDark: 'one-dark-theme' }));
+
 vi.mock('@/components/ui/button', () => ({
   Button: ({ children, ...props }: any) => <button {...props}>{children}</button>,
   buttonVariants: () => ''
@@ -73,11 +114,18 @@ vi.mock('@/components/workbench/primitives', () => ({
 }));
 
 vi.mock('@/components/workbench/workbench-page', () => ({
-  WorkbenchPage: ({ kicker, title, subtitle, actions, main, side }: any) => (
+  WorkbenchPage: ({ kicker, title, subtitle, facts, actions, main, side }: any) => (
     <main>
       <div>{kicker}</div>
       <h1>{title}</h1>
       <p>{subtitle}</p>
+      <div>
+        {(facts || []).map((fact: any, index: number) => (
+          <div key={`${fact.label}-${index}`} data-monitor-editor-fact={fact.label} data-monitor-editor-fact-value={fact.value}>
+            {`${fact.label}:${fact.value}`}
+          </div>
+        ))}
+      </div>
       <div>{actions}</div>
       <div>{main}</div>
       <aside>{side}</aside>
@@ -86,7 +134,14 @@ vi.mock('@/components/workbench/workbench-page', () => ({
   RowList: ({ rows }: any) => (
     <div>
       {rows.map((row: any, index: number) => (
-        <div key={`${row.title}-${index}`}>{`${row.title}:${row.copy}`}</div>
+        <div
+          key={`${row.title}-${index}`}
+          data-monitor-editor-payload-row={row.title}
+          data-monitor-editor-payload-copy={row.copy}
+          data-monitor-editor-payload-meta={row.meta}
+        >
+          {`${row.title}:${row.copy}:${row.meta}`}
+        </div>
       ))}
     </div>
   )
@@ -98,12 +153,26 @@ vi.mock('@/lib/api-client', () => ({
   apiMessagePut: vi.fn()
 }));
 
+vi.mock('@/lib/monitor-api-facade', () => ({
+  api: {
+    monitors: {
+      create: vi.fn(),
+      detect: vi.fn(),
+      editorParamDefines: vi.fn(),
+      update: vi.fn()
+    }
+  }
+}));
+
 vi.mock('@/lib/monitor-editor/controller', () => ({
-  createMonitor: vi.fn(),
-  detectMonitor: vi.fn(),
-  loadMonitorScrapeDraft: vi.fn(),
+  applyMonitorHostNameAutofill: (draft: any) => draft,
+  createMonitorFromFacade: vi.fn(),
+  detectMonitorFromFacade: vi.fn(),
+  loadMonitorScrapeDraftFromFacade: vi.fn(async () => ({ scrapeParams: [], scrapeParamDefines: [] })),
+  shouldPreserveMonitorScrapeParamsForLoad: (previousScrape: string | null | undefined, nextScrape: string) =>
+    !previousScrape && nextScrape !== 'static',
   syncMonitorDependentDisplay: (draft: any) => draft,
-  updateMonitor: vi.fn(),
+  updateMonitorFromFacade: vi.fn(),
   updateMonitorEditorParam: (draft: any, kind: string, index: number, value: unknown) => ({
     ...draft,
     [kind]: draft[kind].map((row: any, rowIndex: number) => (rowIndex === index ? { ...row, paramValue: value } : row))
@@ -112,6 +181,7 @@ vi.mock('@/lib/monitor-editor/controller', () => ({
 }));
 
 vi.mock('@/lib/monitor-editor/navigation', () => ({
+  buildMonitorEditorCancelUrl: () => '/monitors',
   buildMonitorEditorReturnUrl: () => '/monitors?app=website'
 }));
 
@@ -180,6 +250,17 @@ describe('MonitorEditorSurface', () => {
 
     expect(html).toContain('Add label');
     expect(html).toContain('Add annotation');
+    expect(html).toContain('data-monitor-editor-label-selector="angular-app-label-selector"');
+    expect(html).toContain('data-monitor-editor-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="team:platform"');
+    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-hz-ui="key-value-editor"');
+    expect(html).toContain('data-monitor-editor-key-value-owner="hertzbeat-ui-key-value-editor"');
+    expect(html).toContain('data-hz-key-value-action="add"');
+    expect(html).toContain('data-hz-key-value-footer="action-row"');
+    expect(html).toContain('data-hz-key-value-action-layout="footer-command"');
     expect(html).toContain('<form');
     expect(html).toContain('team');
     expect(html).toContain('platform');
@@ -189,6 +270,216 @@ describe('MonitorEditorSurface', () => {
     expect(html).not.toContain('JSON object for monitor annotations.');
   });
 
+  it('renders monitor editor label and annotation actions in zh-CN', () => {
+    i18nMockState.locale = 'zh-CN';
+    try {
+      const html = renderToStaticMarkup(
+        <MonitorEditorSurface
+          initial={{
+            monitor: {
+              id: 42,
+              app: 'website',
+              name: 'checkout',
+              instance: 'example.com:80',
+              scrape: 'static',
+              scheduleType: 'interval',
+              intervals: 60,
+              status: 1,
+              labels: { team: 'platform' },
+              annotations: { owner: 'sre' }
+            } as any,
+            collector: '',
+            grafanaDashboard: { enabled: false },
+            params: [{ field: 'host', paramValue: 'example.com' }],
+            paramDefines: [{ field: 'host', type: 'text', name: 'Host', required: true }],
+            advancedParams: [],
+            advancedParamDefines: [],
+            scrapeParams: [],
+            scrapeParamDefines: [],
+            collectors: []
+          }}
+          mode="edit"
+          returnContext={{ returnTo: '/monitors?app=website' }}
+        />
+      );
+
+      expect(html).toContain('添加标签');
+      expect(html).toContain('添加注解');
+      expect(html).not.toContain('Add label');
+      expect(html).not.toContain('Add annotation');
+    } finally {
+      i18nMockState.locale = 'en-US';
+    }
+  });
+
+  it('renders the system default collector option instead of treating it as none', () => {
+    i18nMockState.locale = 'zh-CN';
+    try {
+      const html = renderToStaticMarkup(
+        <MonitorEditorSurface
+          initial={{
+            monitor: {
+              id: 42,
+              app: 'mysql',
+              name: 'mysql-prod',
+              instance: '127.0.0.1:3306',
+              scrape: 'static',
+              scheduleType: 'interval',
+              intervals: 10,
+              status: 2,
+              labels: {},
+              annotations: {}
+            } as any,
+            collector: '',
+            grafanaDashboard: { enabled: false },
+            params: [{ field: 'host', paramValue: '127.0.0.1' }],
+            paramDefines: [{ field: 'host', type: 'text', name: '目标Host', required: true }],
+            advancedParams: [],
+            advancedParamDefines: [],
+            scrapeParams: [],
+            scrapeParamDefines: [],
+            collectors: ['edge-a']
+          }}
+          mode="edit"
+          returnContext={{ returnTo: '/monitors?app=mysql' }}
+        />
+      );
+
+      expect(html).toContain('默认系统调度');
+      expect(html).toContain('data-monitor-editor-collector-selection="angular-collectors-selection-tags"');
+      expect(html).toContain('data-monitor-editor-collector-tags="angular-status-ip-mode-tags"');
+      expect(html).toContain('data-monitor-editor-collector-tag="system-default"');
+      expect(html).not.toContain('<option value="" selected="">无</option>');
+    } finally {
+      i18nMockState.locale = 'en-US';
+    }
+  });
+
+  it('preserves Angular collector option status, ip, and mode context', () => {
+    i18nMockState.locale = 'zh-CN';
+    try {
+      const html = renderToStaticMarkup(
+        <MonitorEditorSurface
+          initial={{
+            monitor: {
+              id: 42,
+              app: 'mysql',
+              name: 'mysql-prod',
+              instance: '127.0.0.1:3306',
+              scrape: 'static',
+              scheduleType: 'interval',
+              intervals: 10,
+              status: 2,
+              labels: {},
+              annotations: {}
+            } as any,
+            collector: 'edge-a',
+            grafanaDashboard: { enabled: false },
+            params: [{ field: 'host', paramValue: '127.0.0.1' }],
+            paramDefines: [{ field: 'host', type: 'text', name: '目标Host', required: true }],
+            advancedParams: [],
+            advancedParamDefines: [],
+            scrapeParams: [],
+            scrapeParamDefines: [],
+            collectors: [{ name: 'edge-a', ip: '10.0.0.12', status: 0, mode: 'private' }]
+          }}
+          mode="edit"
+          returnContext={{ returnTo: '/monitors?app=mysql' }}
+        />
+      );
+
+      expect(html).toContain('edge-a · 10.0.0.12');
+      expect(html).toContain('data-monitor-editor-collector-selection-owner="hertzbeat-ui-select"');
+      expect(html).toContain('data-monitor-editor-collector-tags="angular-status-ip-mode-tags"');
+      expect(html).toContain('data-monitor-editor-collector-tag="status"');
+      expect(html).toContain('data-monitor-editor-collector-tag="ip"');
+      expect(html).toContain('data-monitor-editor-collector-tag="mode"');
+      expect(html).toContain('10.0.0.12');
+    } finally {
+      i18nMockState.locale = 'en-US';
+    }
+  });
+
+  it('renders monitor editor as a linear Angular-style form instead of a split workbench', () => {
+    const source = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8');
+    const html = renderToStaticMarkup(
+      <MonitorEditorSurface
+        initial={{
+          monitor: {
+            id: 42,
+            app: 'prometheus',
+            name: '',
+            instance: '',
+            scrape: 'static',
+            scheduleType: 'interval',
+            intervals: 60,
+            status: 1,
+            labels: {},
+            annotations: {}
+          } as any,
+          collector: '',
+          grafanaDashboard: { enabled: false },
+          params: [{ field: 'host', paramValue: '127.0.0.1' }],
+          paramDefines: [{ field: 'host', type: 'text', name: 'Host', required: true }],
+          advancedParams: [{ field: 'headers', paramValue: '{}' }],
+          advancedParamDefines: [{ field: 'headers', type: 'key-value', name: 'Headers' }],
+          scrapeParams: [],
+          scrapeParamDefines: [],
+          collectors: []
+        }}
+        mode="new"
+        returnContext={{ returnTo: '/monitors' }}
+      />
+    );
+
+    const baseIndex = html.indexOf('Basic settings');
+    const paramsIndex = html.indexOf('Runtime parameters');
+    const advancedIndex = html.indexOf('Advanced');
+    const runtimeIndex = html.indexOf('Collection schedule');
+    const labelsIndex = html.indexOf('Labels');
+    const annotationsIndex = html.indexOf('Annotations');
+    const descriptionIndex = html.indexOf('Description');
+    const actionsIndex = html.indexOf('data-monitor-editor-action-dock="bottom"');
+
+    expect(html).toContain('data-monitor-editor-layout="linear"');
+    expect(html).toContain('data-hz-ui="monitor-editor-form"');
+    expect(html).toContain('data-monitor-editor-form-owner="hertzbeat-ui-monitor-editor-form"');
+    expect(html).toContain('data-monitor-editor-linear-shell="true"');
+    expect(html).toContain('data-hz-ui="monitor-editor-header"');
+    expect(html).toContain('data-monitor-editor-linear-header="hertzbeat-ui-monitor-editor-header"');
+    expect(html).toContain('data-hz-ui="monitor-editor-field-grid"');
+    expect(html).toContain('data-monitor-editor-field-grid-owner="hertzbeat-ui-monitor-editor-field-grid"');
+    expect((html.match(/data-hz-ui="monitor-editor-field-grid"/g) ?? []).length).toBe(3);
+    expect(html).toContain('data-monitor-editor-advanced-collapse="angular-ghost-collapse-dashed-trigger"');
+    expect(html).toContain('data-monitor-editor-advanced-visible-contract="angular-visible-param-only"');
+    expect(html).toContain('data-monitor-editor-advanced-collapse-state="collapsed"');
+    expect(html).toContain('data-monitor-editor-advanced-toggle="angular-dashed-collapse-trigger"');
+    expect(html).toContain('data-monitor-editor-detect-cron-validation="angular-detect-skips-cron-format"');
+    expect(html).toContain('data-monitor-editor-cron-required="angular-required-before-detect-save"');
+    expect(html).not.toContain('data-monitor-editor-advanced-fields="expanded"');
+    expect(baseIndex).toBeGreaterThan(-1);
+    expect(baseIndex).toBeLessThan(paramsIndex);
+    expect(paramsIndex).toBeLessThan(advancedIndex);
+    expect(advancedIndex).toBeLessThan(runtimeIndex);
+    expect(runtimeIndex).toBeLessThan(labelsIndex);
+    expect(labelsIndex).toBeLessThan(annotationsIndex);
+    expect(annotationsIndex).toBeLessThan(descriptionIndex);
+    expect(descriptionIndex).toBeLessThan(actionsIndex);
+    expect(html).not.toContain('data-monitor-editor-fact=');
+    expect(html).not.toContain('data-monitor-editor-payload-row=');
+    expect(html).not.toContain('Monitor Configuration');
+    expect(source).not.toContain('WorkbenchPage');
+    expect(source).not.toContain('RowList');
+    expect(source).toContain('HzMonitorEditorForm');
+    expect(source).toContain('HzMonitorEditorHeader');
+    expect(source).toContain('HzMonitorEditorFieldGrid');
+    expect(source).toContain('validateBeforeSubmit(nextDraft, { validateCronFormat: false })');
+    expect(source).not.toContain('className="mx-auto w-full max-w-[980px] space-y-3"');
+    expect(source).not.toContain('className="grid gap-3 md:grid-cols-2"');
+    expect(source).not.toContain('<div className="space-y-3" data-monitor-editor-linear-shell="true">');
+    expect(source).not.toContain('<header\n          className="border-b border-[var(--hz-ui-line-soft)] px-3 pb-3 pt-2"');
+  });
+
   it('keeps the embedded editor action posture inside one form', () => {
     const html = renderToStaticMarkup(
       <MonitorEditorSurface
@@ -232,7 +523,490 @@ describe('MonitorEditorSurface', () => {
     expect(html).toContain('data-monitor-editor-cancel-action="true"');
   });
 
-  it('uses the shared cold number stepper for interval schedules instead of native number input', () => {
+  it('uses the shared mutation bar for monitor editor actions and feedback instead of page-local action chrome', () => {
+    const source = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8');
+    const html = renderToStaticMarkup(
+      <MonitorEditorSurface
+        initial={{
+          monitor: {
+            id: 42,
+            app: 'website',
+            name: 'checkout',
+            instance: 'example.com:80',
+            scrape: 'static',
+            scheduleType: 'interval',
+            intervals: 60,
+            status: 1,
+            labels: {},
+            annotations: {}
+          } as any,
+          collector: '',
+          grafanaDashboard: { enabled: false },
+          params: [{ field: 'host', paramValue: 'example.com' }],
+          paramDefines: [{ field: 'host', type: 'text', name: 'Host', required: true }],
+          advancedParams: [],
+          advancedParamDefines: [],
+          scrapeParams: [],
+          scrapeParamDefines: [],
+          collectors: []
+        }}
+        mode="edit"
+        returnContext={{ returnTo: '/monitors?app=website' }}
+      />
+    );
+
+    expect(html).toContain('data-hz-ui="mutation-bar"');
+    expect(html).toContain('data-hz-ui="monitor-editor-action-bar"');
+    expect(html).toContain('data-hz-ui="monitor-editor-section"');
+    expect(html).toContain('data-monitor-editor-label-selector="angular-app-label-selector"');
+    expect(html).toContain('data-monitor-editor-label-selector-owner="cold-label-selector"');
+    expect(html).toContain('data-monitor-editor-key-value="annotations"');
+    expect(html).toContain('data-monitor-editor-section-owner="hertzbeat-ui-editor-section"');
+    expect(html).toContain('data-monitor-editor-field-owner="hertzbeat-ui-field"');
+    expect(html).toContain('data-hz-ui="field"');
+    expect(html).toContain('data-hz-ui="input"');
+    expect(html).toContain('data-monitor-editor-input-owner="hertzbeat-ui-input"');
+    expect(html).toContain('data-monitor-editor-app-source="angular-route-context-hidden-field"');
+    expect(html).toContain('data-monitor-editor-hidden-app="route-or-detail-context"');
+    expect(html).not.toContain('data-monitor-editor-input="app"');
+    expect(html).toContain('data-monitor-editor-field-order="angular-monitor-form-sequence"');
+    expect(html).toContain('data-monitor-editor-detect-payload-contract="angular-monitor-collector-params-no-grafana"');
+    expect(html).toContain('data-monitor-editor-save-payload-contract="angular-monitor-collector-params-grafana"');
+    expect(html).toContain('data-monitor-editor-payload-param-merge="angular-params-advanced-sdparams"');
+    expect(html).toContain('data-monitor-editor-payload-host-instance="angular-host-param-as-instance"');
+    expect(html).toContain('data-monitor-editor-advanced-collapse="angular-ghost-collapse-dashed-trigger"');
+    expect(html).toContain('data-monitor-editor-advanced-visible-contract="angular-visible-param-only"');
+    expect(html).toContain('data-monitor-editor-label-selector="angular-app-label-selector"');
+    expect(html).toContain('data-monitor-editor-static-host-position="angular-before-name"');
+    expect(html).toContain('data-monitor-editor-static-host-field="angular-before-name"');
+    expect(html).toContain('data-monitor-editor-input="name"');
+    expect(html).toContain('data-monitor-param-input="host"');
+    expect(html.indexOf('data-monitor-editor-static-host-field="angular-before-name"')).toBeLessThan(
+      html.indexOf('data-monitor-editor-input="name"')
+    );
+    expect(html).toContain('data-monitor-editor-host-name-autofill-contract="angular-new-host-change"');
+    expect(html).not.toContain('data-monitor-editor-host-name-autofill-target="monitor-name"');
+    expect(html).toContain('data-hz-ui="select"');
+    expect(html).toContain('data-monitor-editor-select-owner="hertzbeat-ui-select"');
+    expect(html).toContain('data-monitor-editor-select="scrape"');
+    expect(html).toContain('data-monitor-editor-select="collector"');
+    expect(html).toContain('data-monitor-editor-select="schedule-type"');
+    expect(html).toContain('data-hz-mutation-status="clean"');
+    expect(html).toContain('data-hz-mutation-issue-count="0"');
+    expect(html).toContain('data-monitor-editor-action-dock="bottom"');
+    expect(html).toContain('data-monitor-editor-mutation-bar="true"');
+    expect(html).toContain('data-monitor-editor-action-bar-owner="hertzbeat-ui-monitor-editor-action-bar"');
+    expect(html).toContain('data-monitor-editor-action-bar-layout="centered-footer"');
+    expect(html).toContain('data-monitor-editor-action="detect"');
+    expect(html).toContain('data-monitor-editor-action="submit"');
+    expect(html).toContain('data-monitor-editor-action="cancel"');
+    expect(html).toContain('data-monitor-editor-action-owner="hertzbeat-ui-button"');
+    expect(html).toContain('data-monitor-editor-detect-action="true"');
+    expect(html).toContain('data-monitor-editor-submit-action="true"');
+    expect(html).toContain('data-monitor-editor-detect-busy-label="Available Detecting"');
+    expect(html).toContain('data-monitor-editor-submit-busy-label="Loading..."');
+    expect(html).toContain('data-monitor-editor-save-return="safe-return-context-or-angular-app-list"');
+    expect(html).toContain('data-monitor-editor-save-return-target="/monitors?app=website"');
+    expect(html).toContain('data-monitor-editor-save-notification-contract="angular-success-before-return"');
+    expect(html).toContain('data-monitor-editor-cancel-action="true"');
+    expect(html).toContain('data-monitor-editor-cancel-return="safe-return-context-or-list-root"');
+    expect(html).toContain('data-monitor-editor-cancel-return-target="/monitors"');
+    expect(html.indexOf('data-monitor-editor-section-owner="hertzbeat-ui-editor-section"')).toBeLessThan(
+      html.indexOf('data-monitor-editor-action-dock="bottom"')
+    );
+    expect(source).toContain("from '@hertzbeat/ui'");
+    expect(source).toContain('HzMonitorEditorActionBar');
+    expect(source).toContain('buildMonitorEditorCancelUrl');
+    expect(source).not.toContain('HzMutationBar');
+    expect(source).not.toContain('data-monitor-editor-action-owner="hertzbeat-ui-button"');
+    expect(source).toContain('HzInlineFeedback');
+    expect(source).toContain('variant="embedded"');
+    expect(source).toContain('data-monitor-editor-feedback-owner="hertzbeat-ui-inline-feedback"');
+    expect(source).toContain("useState<'idle' | 'detecting' | 'saving'>('idle')");
+    expect(source).toContain("useState<'validation' | 'api' | null>(null)");
+    expect(source).toContain("useState<'detect' | 'save' | null>(null)");
+    expect(source).toContain("actionPhase === 'detecting' ? t('monitor.spinning-tip.detecting') : t('common.loading')");
+    expect(source).toContain('data-monitor-editor-feedback="busy"');
+    expect(source).toContain('data-monitor-editor-busy-contract="angular-spin-tip"');
+    expect(source).toContain('data-monitor-editor-busy-phase={actionPhase}');
+    expect(source).toContain('data-monitor-editor-validation-contract');
+    expect(source).toContain('data-monitor-editor-api-error-contract');
+    expect(source).toContain('data-monitor-editor-blocked-action');
+    expect(source).toContain('data-monitor-editor-blocked-before-api');
+    expect(source).toContain("setActionErrorSource('validation')");
+    expect(source).toContain("setActionErrorSource('api')");
+    expect(source).toContain("setActionErrorAction('detect')");
+    expect(source).toContain("setActionErrorAction('save')");
+    expect(source).toContain("setActionError(error instanceof Error ? error.message : t('monitor.detect.failed'))");
+    expect(source).toContain(
+      "setActionError(error instanceof Error ? error.message : mode === 'new' ? t('monitor.new.failed') : t('monitor.edit.failed'))"
+    );
+    expect(source).toContain('data-monitor-editor-detect-busy-label');
+    expect(source).toContain('data-monitor-editor-submit-busy-label');
+    expect(source).toContain('saveReturnUrl');
+    expect(source).toContain('cancelReturnUrl');
+    expect(source).toContain('data-monitor-editor-save-return');
+    expect(source).toContain('data-monitor-editor-save-return-target');
+    expect(source).toContain('data-monitor-editor-save-notification-contract');
+    expect(source).toContain('data-monitor-editor-cancel-return-target');
+    expect(source).toContain('data-monitor-editor-feedback="error"');
+    expect(source).toContain('data-monitor-editor-feedback="success"');
+    expect(source).not.toContain('className="border-0"');
+    expect(source).toContain('HzMonitorEditorSection');
+    expect(source).toContain('HzKeyValueEditor');
+    expect(source).toContain('LabelRecordInput');
+    expect(source).toContain('data-monitor-editor-label-selector="angular-app-label-selector"');
+    expect(source).toContain('data-monitor-editor-label-selector-owner="cold-label-selector"');
+    expect(source).not.toContain('data-monitor-editor-key-value="labels"');
+    expect(source).toContain('data-monitor-editor-key-value="annotations"');
+    expect(source).toContain('HzField');
+    expect(source).toContain('applyMonitorHostNameAutofill');
+    expect(source).toContain("field: defines[index]?.field");
+    expect(source).toContain('HzInput');
+    expect(source).toContain('data-monitor-editor-input-owner="hertzbeat-ui-input"');
+    expect(source).toContain('data-monitor-editor-app-source="angular-route-context-hidden-field"');
+    expect(source).toContain('data-monitor-editor-hidden-app="route-or-detail-context"');
+    expect(source).toContain('data-monitor-editor-field-order="angular-monitor-form-sequence"');
+    expect(source).toContain('data-monitor-editor-detect-payload-contract="angular-monitor-collector-params-no-grafana"');
+    expect(source).toContain('data-monitor-editor-save-payload-contract="angular-monitor-collector-params-grafana"');
+    expect(source).toContain('data-monitor-editor-payload-param-merge="angular-params-advanced-sdparams"');
+    expect(source).toContain('data-monitor-editor-payload-host-instance="angular-host-param-as-instance"');
+    expect(source).toContain('data-monitor-editor-advanced-collapse="angular-ghost-collapse-dashed-trigger"');
+    expect(source).toContain('data-monitor-editor-advanced-visible-contract="angular-visible-param-only"');
+    expect(source).toContain('data-monitor-editor-label-selector="angular-app-label-selector"');
+    expect(source).toContain('data-monitor-editor-advanced-toggle="angular-dashed-collapse-trigger"');
+    expect(source).toContain('setAdvancedOpen(open => !open)');
+    expect(source).toContain('data-monitor-editor-static-host-position="angular-before-name"');
+    expect(source).toContain('data-monitor-editor-static-host-field="angular-before-name"');
+    expect(source).toContain('data-monitor-editor-scrape-param-order="angular-after-scrape-before-name"');
+    expect(source).toContain('data-monitor-editor-runtime-order="angular-after-advanced-before-labels"');
+    expect(source).toContain("define.field === 'host'");
+    expect(source).not.toContain('data-monitor-editor-input="app"');
+    expect(source).toContain('data-monitor-editor-input="name"');
+    expect(source).toContain('data-monitor-editor-input="cron-expression"');
+    expect(source).toContain('data-monitor-editor-cron-required="angular-required-before-detect-save"');
+    expect(source).toContain('required');
+    expect(source).toContain('data-monitor-param-input={define.field}');
+    expect(source).toContain('HzSelect');
+    expect(source).toContain('data-monitor-editor-select-owner="hertzbeat-ui-select"');
+    expect(source).toContain('data-monitor-editor-select="scrape"');
+    expect(source).toContain('data-monitor-editor-select="collector"');
+    expect(source).toContain('data-monitor-editor-select="schedule-type"');
+    expect(source).toContain('data-monitor-param-radio={define.field}');
+    expect(source).toContain('data-monitor-param-radio-contract="angular-nz-radio-group-button-solid"');
+    expect(source).not.toContain('SurfaceSection');
+    expect(source).not.toContain('function KeyValueEditor');
+    expect(source).not.toContain("import { Button } from '@/components/ui/button'");
+    expect(source).not.toContain("import { Input } from '@/components/ui/input'");
+    expect(source).not.toContain("import { Select } from '@/components/ui/select'");
+    expect(source).not.toContain('const fieldLabelClassName');
+    expect(source).not.toContain('text-emerald-300');
+    expect(source).not.toContain('text-rose-300');
+  });
+
+  it('keeps scrape loading feedback owned by @hertzbeat/ui instead of page-local text chrome', () => {
+    const source = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8');
+
+    expect(source).toContain('HzLoadingState');
+    expect(source).toContain('data-monitor-editor-scrape-loading-owner="hertzbeat-ui-loading-state"');
+    expect(source).not.toContain('text-sm text-[var(--ops-text-secondary)]');
+  });
+
+  it('loads dynamic scrape params through the monitor domain facade instead of a raw getter', () => {
+    const source = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8');
+
+    expect(source).toContain('loadMonitorScrapeDraftFromFacade(api.monitors.editorParamDefines');
+    expect(source).toContain("if (scrape === 'static')");
+    expect(source).toContain('data-monitor-editor-service-discovery-params="angular-nonstatic-only"');
+    expect(source).toContain('data-monitor-editor-scrape-reload-contract="angular-reset-on-user-scrape-change"');
+    expect(source).toContain('shouldPreserveMonitorScrapeParamsForLoad(previousScrape, scrape)');
+    expect(source).toContain('shouldPreserveExistingParams ? scrapeParamsRef.current : undefined');
+    expect(source).not.toContain('loadMonitorScrapeDraft(apiMessageGet');
+    expect(source).not.toContain("import { apiMessageGet");
+  });
+
+  it('keeps Angular ssl port auto-change info notification behavior wired in the shared editor', () => {
+    const source = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8');
+    const html = renderToStaticMarkup(
+      <MonitorEditorSurface
+        initial={{
+          monitor: {
+            id: 42,
+            app: 'api',
+            name: 'checkout',
+            instance: 'example.com:80',
+            scrape: 'static',
+            scheduleType: 'interval',
+            intervals: 60,
+            status: 1,
+            labels: {},
+            annotations: {}
+          } as any,
+          collector: '',
+          grafanaDashboard: { enabled: false },
+          params: [
+            { field: 'ssl', paramValue: false },
+            { field: 'port', paramValue: 80 }
+          ],
+          paramDefines: [
+            { field: 'ssl', type: 'boolean', name: 'SSL' },
+            { field: 'port', type: 'number', name: 'Port' }
+          ],
+          advancedParams: [],
+          advancedParamDefines: [],
+          scrapeParams: [],
+          scrapeParamDefines: [],
+          collectors: []
+        }}
+        mode="new"
+      />
+    );
+
+    expect(source).toContain('resolveMonitorEditorParamChangeNotice(draft, kind, index, value)');
+    expect(source).toContain('setActionMessage(t(noticeKey))');
+    expect(html).toContain('data-monitor-editor-ssl-port-notice="angular-info-notification"');
+    expect(html).toContain('data-monitor-param-switch="ssl"');
+    expect(html).toContain('data-monitor-param-boolean-contract="angular-nz-switch"');
+    expect(html).toContain('data-monitor-param-number-stepper="port"');
+  });
+
+  it('submits monitor editor mutations through the monitor domain facade instead of raw API writers', () => {
+    const source = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8');
+
+    expect(source).toContain('detectMonitorFromFacade(api.monitors.detect');
+    expect(source).toContain('createMonitorFromFacade(api.monitors.create');
+    expect(source).toContain('updateMonitorFromFacade(api.monitors.update');
+    expect(source).not.toContain("import { apiMessagePost");
+    expect(source).not.toContain("import { apiMessagePut");
+    expect(source).not.toContain('detectMonitor(apiMessagePost');
+    expect(source).not.toContain('createMonitor(apiMessagePost');
+    expect(source).not.toContain('updateMonitor(apiMessagePut');
+  });
+
+  it('keeps Angular monitor form field order for service-discovery params and runtime controls', () => {
+    const html = renderToStaticMarkup(
+      <MonitorEditorSurface
+        initial={{
+          monitor: {
+            id: 42,
+            app: 'website',
+            name: 'checkout',
+            instance: 'example.com:80',
+            scrape: 'http_sd',
+            scheduleType: 'interval',
+            intervals: 60,
+            status: 1,
+            labels: {},
+            annotations: {}
+          } as any,
+          collector: '',
+          grafanaDashboard: { enabled: false },
+          params: [
+            { field: 'host', paramValue: 'example.com' },
+            { field: 'port', paramValue: 80 }
+          ],
+          paramDefines: [
+            { field: 'host', type: 'text', name: 'Host', required: true },
+            { field: 'port', type: 'number', name: 'Port', required: true }
+          ],
+          advancedParams: [{ field: 'timeout', paramValue: 3 }],
+          advancedParamDefines: [{ field: 'timeout', type: 'number', name: 'Timeout', required: false }],
+          scrapeParams: [{ field: 'sd_url', paramValue: 'https://sd.local/targets' }],
+          scrapeParamDefines: [{ field: 'sd_url', type: 'text', name: 'HTTP SD URL', required: true }],
+          collectors: ['collector-a']
+        }}
+        mode="edit"
+        returnContext={{ returnTo: '/monitors?app=website' }}
+      />
+    );
+
+    const scrapeIndex = html.indexOf('data-monitor-editor-select="scrape"');
+    const sdParamIndex = html.indexOf('data-monitor-editor-scrape-param-order="angular-after-scrape-before-name"');
+    const nameIndex = html.indexOf('data-monitor-editor-input="name"');
+    const mainParamIndex = html.indexOf('data-monitor-param-number-stepper="port"');
+    const advancedIndex = html.indexOf('data-monitor-editor-advanced-collapse="angular-ghost-collapse-dashed-trigger"', mainParamIndex);
+    const advancedToggleIndex = html.indexOf('data-monitor-editor-advanced-toggle="angular-dashed-collapse-trigger"', advancedIndex);
+    const runtimeIndex = html.indexOf('data-monitor-editor-runtime-order="angular-after-advanced-before-labels"');
+    const labelIndex = html.indexOf('data-monitor-editor-label-selector-owner="cold-label-selector"', runtimeIndex);
+
+    expect(html).toContain('data-monitor-editor-field-order="angular-monitor-form-sequence"');
+    expect(scrapeIndex).toBeGreaterThan(-1);
+    expect(sdParamIndex).toBeGreaterThan(scrapeIndex);
+    expect(nameIndex).toBeGreaterThan(sdParamIndex);
+    expect(mainParamIndex).toBeGreaterThan(nameIndex);
+    expect(advancedIndex).toBeGreaterThan(mainParamIndex);
+    expect(advancedToggleIndex).toBeGreaterThan(advancedIndex);
+    expect(runtimeIndex).toBeGreaterThan(advancedIndex);
+    expect(labelIndex).toBeGreaterThan(runtimeIndex);
+    expect(html).toContain('data-monitor-editor-advanced-collapse-state="collapsed"');
+    expect(html).not.toContain('data-monitor-param-number-stepper="timeout"');
+    expect(html).not.toContain('data-monitor-editor-advanced-fields="expanded"');
+  });
+
+  it('hides the Angular advanced collapse when every advanced param is dependency-hidden', () => {
+    const html = renderToStaticMarkup(
+      <MonitorEditorSurface
+        initial={{
+          monitor: {
+            id: 42,
+            app: 'website',
+            name: 'checkout',
+            instance: 'example.com:80',
+            scrape: 'static',
+            scheduleType: 'interval',
+            intervals: 60,
+            status: 1,
+            labels: {},
+            annotations: {}
+          } as any,
+          collector: '',
+          grafanaDashboard: { enabled: false },
+          params: [{ field: 'host', paramValue: 'example.com' }],
+          paramDefines: [{ field: 'host', type: 'text', name: 'Host', required: true }],
+          advancedParams: [{ field: 'token', paramValue: '', display: false }],
+          advancedParamDefines: [{ field: 'token', type: 'text', name: 'Token', required: true, depend: { ssl: [true] } }],
+          scrapeParams: [],
+          scrapeParamDefines: [],
+          collectors: []
+        }}
+        mode="edit"
+      />
+    );
+
+    expect(html).toContain('data-monitor-editor-advanced-visible-contract="angular-visible-param-only"');
+    expect(html).not.toContain('data-monitor-editor-advanced-collapse-state=');
+    expect(html).not.toContain('data-monitor-editor-advanced-toggle="angular-dashed-collapse-trigger"');
+    expect(html).not.toContain('Token');
+  });
+
+  it('hides service-discovery params for static scrape to match Angular nonstatic-only rendering', () => {
+    const html = renderToStaticMarkup(
+      <MonitorEditorSurface
+        initial={{
+          monitor: {
+            id: 42,
+            app: 'website',
+            name: 'checkout',
+            instance: 'example.com:80',
+            scrape: 'static',
+            scheduleType: 'interval',
+            intervals: 60,
+            status: 1,
+            labels: {},
+            annotations: {}
+          } as any,
+          collector: '',
+          grafanaDashboard: { enabled: false },
+          params: [{ field: 'host', paramValue: 'example.com' }],
+          paramDefines: [{ field: 'host', type: 'text', name: 'Host', required: true }],
+          advancedParams: [],
+          advancedParamDefines: [],
+          scrapeParams: [{ field: 'sd_url', paramValue: 'https://sd.local/targets' }],
+          scrapeParamDefines: [{ field: 'sd_url', type: 'text', name: 'HTTP SD URL', required: true }],
+          collectors: []
+        }}
+        mode="edit"
+      />
+    );
+
+    expect(html).toContain('data-monitor-editor-service-discovery-params="angular-nonstatic-only"');
+    expect(html).toContain('data-monitor-editor-scrape-reload-contract="angular-reset-on-user-scrape-change"');
+    expect(html).not.toContain('data-monitor-editor-scrape-param-order="angular-after-scrape-before-name"');
+    expect(html).toContain('data-monitor-editor-static-host-field="angular-before-name"');
+  });
+
+  it('centers bottom monitor editor actions without repeating the editor title or ready badge', () => {
+    const source = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8');
+    const html = renderToStaticMarkup(
+      <MonitorEditorSurface
+        initial={{
+          monitor: {
+            id: 42,
+            app: 'prometheus',
+            name: '',
+            instance: '',
+            scrape: 'static',
+            scheduleType: 'interval',
+            intervals: 60,
+            status: 1,
+            labels: {},
+            annotations: {}
+          } as any,
+          collector: '',
+          grafanaDashboard: { enabled: false },
+          params: [{ field: 'host', paramValue: '127.0.0.1' }],
+          paramDefines: [{ field: 'host', type: 'text', name: 'Host', required: true }],
+          advancedParams: [],
+          advancedParamDefines: [],
+          scrapeParams: [],
+          scrapeParamDefines: [],
+          collectors: []
+        }}
+        mode="new"
+        returnContext={{ returnTo: '/monitors' }}
+      />
+    );
+    const dockHtml = html.slice(html.indexOf('data-monitor-editor-action-dock="bottom"'));
+
+    expect(dockHtml).toContain('data-hz-mutation-summary="hidden"');
+    expect(dockHtml).toContain('data-hz-mutation-action-align="center"');
+    expect(dockHtml).toContain('data-hz-ui="monitor-editor-action-bar"');
+    expect(dockHtml).toContain('data-monitor-editor-action-bar-owner="hertzbeat-ui-monitor-editor-action-bar"');
+    expect(dockHtml).toContain('data-monitor-editor-action="detect"');
+    expect(dockHtml).toContain('data-monitor-editor-action="submit"');
+    expect(dockHtml).toContain('data-monitor-editor-action="cancel"');
+    expect(dockHtml).toContain('data-monitor-editor-detect-action="true"');
+    expect(dockHtml).toContain('data-monitor-editor-submit-action="true"');
+    expect(dockHtml).toContain('Detect');
+    expect(dockHtml).toContain('OK');
+    expect(dockHtml).toContain('Cancel');
+    expect(dockHtml).not.toContain('New monitor');
+    expect(dockHtml).not.toContain('data-hz-mutation-status-label=');
+    expect(dockHtml).not.toContain('ready');
+    expect(source).toContain('HzMonitorEditorActionBar');
+    expect(source).toContain('summaryVisible={false}');
+    expect(source).toContain('actionAlign="center"');
+  });
+
+  it('keeps missing app and name values editable without synthetic shell summaries', () => {
+    const html = renderToStaticMarkup(
+      <MonitorEditorSurface
+        initial={{
+          monitor: {
+            id: 42,
+            app: '',
+            name: '',
+            instance: '',
+            scrape: '',
+            scheduleType: 'interval',
+            intervals: 60,
+            status: 1,
+            labels: {},
+            annotations: {}
+          } as any,
+          collector: '',
+          grafanaDashboard: { enabled: false },
+          params: [{ field: 'host', paramValue: '' }],
+          paramDefines: [{ field: 'host', type: 'text', name: 'Host', required: true }],
+          advancedParams: [],
+          advancedParamDefines: [],
+          scrapeParams: [],
+          scrapeParamDefines: [],
+          collectors: []
+        }}
+        mode="edit"
+        returnContext={{ returnTo: '/monitors' }}
+      />
+    );
+
+    expect(html).toContain('data-monitor-editor-layout="linear"');
+    expect(html).toContain('value=""');
+    expect(html).not.toContain('data-monitor-editor-fact=');
+    expect(html).not.toContain('data-monitor-editor-payload-row=');
+    expect(html).not.toContain('App:-');
+  });
+
+  it('uses the shared HertzBeat UI number stepper for interval schedules instead of native number input', () => {
     const source = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8');
     const html = renderToStaticMarkup(
       <MonitorEditorSurface
@@ -264,17 +1038,67 @@ describe('MonitorEditorSurface', () => {
       />
     );
 
-    expect(html).toContain('data-monitor-interval-stepper="cold-number-stepper"');
-    expect(html).toContain('data-cold-number-stepper-owner="cold-number-stepper"');
-    expect(html).toContain('data-cold-number-stepper-input="true"');
+    expect(html).toContain('data-monitor-interval-stepper="hertzbeat-ui-number-stepper"');
+    expect(html).toContain('data-monitor-interval-stepper-contract="angular-min-step-max-by-app"');
+    expect(html).toContain('data-monitor-interval-stepper-min="10"');
+    expect(html).toContain('data-monitor-interval-stepper-max="604800"');
+    expect(html).toContain('data-monitor-interval-stepper-step="10"');
+    expect(html).toContain('min="10"');
+    expect(html).toContain('max="604800"');
+    expect(html).toContain('step="10"');
+    expect(html).toContain('data-monitor-interval-stepper-unit="common.time.unit.second"');
+    expect(html).toContain('data-monitor-editor-number-stepper-owner="hertzbeat-ui-number-stepper"');
+    expect(html).toContain('data-hz-ui="number-stepper"');
+    expect(html).toContain('data-hz-number-stepper-input="true"');
     expect(html).toContain('name="intervals"');
     expect(html).not.toContain('type="number"');
-    expect(source).toContain("from '../ui/number-stepper'");
-    expect(source).toContain('data-monitor-interval-stepper="cold-number-stepper"');
+    expect(source).toContain('HzNumberStepper');
+    expect(source).not.toContain("from '../ui/number-stepper'");
+    expect(source).toContain('data-monitor-interval-stepper="hertzbeat-ui-number-stepper"');
+    expect(source).not.toContain('data-cold-number-stepper-owner');
     expect(source).not.toContain('type="number"');
   });
 
-  it('uses the shared cold textarea for monitor descriptions instead of the old editor row', () => {
+  it('uses Angular push interval stepper bounds for push monitors', () => {
+    const html = renderToStaticMarkup(
+      <MonitorEditorSurface
+        initial={{
+          monitor: {
+            id: 42,
+            app: 'push',
+            name: 'push-input',
+            instance: 'push-input',
+            scrape: 'static',
+            scheduleType: 'interval',
+            intervals: 1,
+            status: 1,
+            labels: {},
+            annotations: {}
+          } as any,
+          collector: '',
+          grafanaDashboard: { enabled: false },
+          params: [],
+          paramDefines: [],
+          advancedParams: [],
+          advancedParamDefines: [],
+          scrapeParams: [],
+          scrapeParamDefines: [],
+          collectors: []
+        }}
+        mode="edit"
+        returnContext={{ returnTo: '/monitors?app=push' }}
+      />
+    );
+
+    expect(html).toContain('data-monitor-interval-stepper-contract="angular-min-step-max-by-app"');
+    expect(html).toContain('data-monitor-interval-stepper-min="1"');
+    expect(html).toContain('data-monitor-interval-stepper-max="604800"');
+    expect(html).toContain('data-monitor-interval-stepper-step="1"');
+    expect(html).toContain('min="1"');
+    expect(html).toContain('step="1"');
+  });
+
+  it('uses the shared HertzBeat UI textarea for monitor descriptions instead of the old editor row', () => {
     const source = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8');
     const html = renderToStaticMarkup(
       <MonitorEditorSurface
@@ -307,12 +1131,22 @@ describe('MonitorEditorSurface', () => {
       />
     );
 
-    expect(source).toContain("import { Textarea } from '@/components/ui/textarea';");
-    expect(source).toContain('data-monitor-description-textarea="cold-textarea"');
+    expect(source).toContain('HzTextarea');
+    expect(source).toContain('data-monitor-description-textarea="hertzbeat-ui-textarea"');
+    expect(source).toContain('data-monitor-editor-textarea-owner="hertzbeat-ui-textarea"');
+    expect(source).toContain('data-monitor-description-textarea-limit="angular-textarea-limit-100"');
+    expect(source).toContain('maxCharacterCount={100}');
+    expect(source).not.toContain("import { Textarea } from '@/components/ui/textarea';");
     expect(source).not.toContain("import { EditorRow } from '../observability/editor-rows';");
     expect(source).not.toContain('const textAreaClassName =');
-    expect(html).toContain('data-monitor-description-textarea="cold-textarea"');
-    expect(html).toContain('data-cold-textarea-owner="cold-textarea"');
+    expect(html).toContain('data-monitor-description-textarea="hertzbeat-ui-textarea"');
+    expect(html).toContain('data-monitor-editor-textarea-owner="hertzbeat-ui-textarea"');
+    expect(html).toContain('data-monitor-description-textarea-limit="angular-textarea-limit-100"');
+    expect(html).toContain('data-hz-ui="textarea"');
+    expect(html).toContain('data-hz-ui="textarea-count"');
+    expect(html).toContain('data-hz-textarea-count-max="100"');
+    expect(html).toContain('data-hz-textarea-count-value="24/100"');
+    expect(html).not.toContain('data-cold-textarea-owner="cold-textarea"');
     expect(html).toContain('Primary endpoint monitor');
   });
 
@@ -355,14 +1189,29 @@ describe('MonitorEditorSurface', () => {
     );
 
     expect(html).toContain('data-monitor-param-number-stepper="port"');
-    expect(html).toContain('data-cold-number-stepper-owner="cold-number-stepper"');
-    expect(html).toContain('data-cold-number-stepper-input="true"');
+    expect(html).toContain('data-monitor-param-number-contract="angular-nz-input-number--1000-65535-step-1"');
+    expect(html).toContain('data-monitor-param-number-min="-1000"');
+    expect(html).toContain('data-monitor-param-number-max="65535"');
+    expect(html).toContain('data-monitor-param-number-step="1"');
+    expect(html).toContain('data-monitor-editor-number-stepper-owner="hertzbeat-ui-number-stepper"');
+    expect(html).toContain('data-hz-ui="number-stepper"');
+    expect(html).toContain('data-hz-number-stepper-input="true"');
+    expect(html).toContain('min="-1000"');
+    expect(html).toContain('max="65535"');
+    expect(html).toContain('step="1"');
     expect(html).not.toContain('type="number"');
+    expect(source).toContain('HzNumberStepper');
     expect(source).toContain('data-monitor-param-number-stepper={define.field}');
+    expect(source).toContain('data-monitor-param-number-contract="angular-nz-input-number--1000-65535-step-1"');
+    expect(source).toContain('min={-1000}');
+    expect(source).toContain('max={65535}');
+    expect(source).toContain('step={1}');
+    expect(source).not.toContain("from '../ui/number-stepper'");
+    expect(source).not.toContain('data-cold-number-stepper-owner');
     expect(source).not.toContain("type={define.type === 'number' ? 'number' : 'text'}");
   });
 
-  it('uses the shared cold checkbox for boolean monitor parameters instead of page-local checkbox chrome', () => {
+  it('renders Angular boolean param definitions as shared switches instead of checkboxes', () => {
     const source = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8');
     const html = renderToStaticMarkup(
       <MonitorEditorSurface
@@ -394,17 +1243,25 @@ describe('MonitorEditorSurface', () => {
       />
     );
 
-    expect(html).toContain('data-monitor-param-checkbox="ssl"');
-    expect(html).toContain('data-cold-checkbox-owner="cold-checkbox"');
-    expect(html).toContain('data-cold-checkbox-control="native-hidden"');
-    expect(html).toContain('data-cold-checkbox-box="indicator"');
-    expect(html).toContain('data-cold-checkbox-label="true"');
-    expect(source).toContain("from '../ui/checkbox'");
-    expect(source).toContain('data-monitor-param-checkbox={define.field}');
+    expect(html).toContain('data-monitor-param-switch="ssl"');
+    expect(html).toContain('data-monitor-param-boolean-contract="angular-nz-switch"');
+    expect(html).toContain('data-monitor-param-field="ssl"');
+    expect(html).toContain('data-monitor-editor-switch-owner="hertzbeat-ui-switch"');
+    expect(html).toContain('data-hz-ui="switch"');
+    expect(html).toContain('data-hz-switch-control="button"');
+    expect(html).toContain('data-hz-switch-checked="true"');
+    expect(html).toContain('data-hz-switch-label="true"');
+    expect(source).toContain('HzSwitch');
+    expect(source).not.toContain("from '../ui/checkbox'");
+    expect(source).not.toContain('data-cold-checkbox-owner');
+    expect(source).toContain('data-monitor-param-switch={define.field}');
+    expect(source).toContain('data-monitor-param-boolean-contract="angular-nz-switch"');
+    expect(source).toContain('data-monitor-param-field={define.field}');
+    expect(source).not.toContain('data-monitor-param-checkbox={define.field}');
     expect(source).not.toContain('<input type="checkbox" checked={Boolean(field.paramValue)}');
   });
 
-  it('uses the shared cold checkbox for the Grafana dashboard enable switch', () => {
+  it('uses the shared HertzBeat UI checkbox for the Grafana dashboard enable switch', () => {
     const source = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8');
     const html = renderToStaticMarkup(
       <MonitorEditorSurface
@@ -436,12 +1293,16 @@ describe('MonitorEditorSurface', () => {
       />
     );
 
-    expect(html).toContain('data-monitor-grafana-enabled-checkbox="cold-checkbox"');
-    expect(html).toContain('data-cold-checkbox-owner="cold-checkbox"');
-    expect(html).toContain('data-cold-checkbox-control="native-hidden"');
-    expect(html).toContain('data-cold-checkbox-box="indicator"');
-    expect(html).toContain('data-cold-checkbox-label="true"');
-    expect(source).toContain('data-monitor-grafana-enabled-checkbox="cold-checkbox"');
+    expect(html).toContain('data-monitor-grafana-enabled-checkbox="hertzbeat-ui-checkbox"');
+    expect(html).toContain('data-monitor-editor-checkbox="grafana-dashboard-enabled"');
+    expect(html).toContain('data-monitor-editor-checkbox-owner="hertzbeat-ui-checkbox"');
+    expect(html).toContain('data-hz-ui="checkbox"');
+    expect(html).toContain('data-hz-checkbox-control="native-hidden"');
+    expect(html).toContain('data-hz-checkbox-box="indicator"');
+    expect(html).toContain('data-hz-checkbox-label="true"');
+    expect(source).toContain('data-monitor-grafana-enabled-checkbox="hertzbeat-ui-checkbox"');
+    expect(source).toContain('data-monitor-editor-checkbox="grafana-dashboard-enabled"');
+    expect(source).not.toContain('data-cold-checkbox-owner');
     expect(source).not.toContain('type="checkbox"\n                    checked={Boolean(draft.grafanaDashboard.enabled)}');
   });
 
@@ -480,17 +1341,222 @@ describe('MonitorEditorSurface', () => {
       />
     );
 
+    expect(html).toContain('data-monitor-editor-code-editor-owner="hertzbeat-ui-code-editor"');
+    expect(html).toContain('data-monitor-editor-code-editor="grafana-dashboard-template"');
+    expect(html).toContain('data-hz-ui="code-editor-frame"');
+    expect(html).toContain('data-hz-code-editor-body="true"');
+    expect(html).toContain('data-hz-code-editor-runtime="codemirror"');
+    expect(html).toContain('data-hz-code-editor-language="json"');
+    expect(html).toContain('data-hz-code-editor-license="codemirror-mit"');
     expect(html).toContain('data-monitor-grafana-code-editor="dashboard-template"');
-    expect(html).toContain('data-cold-code-editor="codemirror"');
-    expect(html).toContain('data-cold-code-editor-language="json"');
+    expect(html).toContain('data-mocked-codemirror="true"');
+    expect(html).toContain('data-min-height="120px"');
     expect(html).toContain('name="grafana_dashboard_template"');
     expect(html).toContain('{&quot;panels&quot;:[]}');
-    expect(source).toContain("from '../ui/cold-code-editor'");
+    expect(source).not.toContain("from '../ui/cold-code-editor'");
+    expect(source).toContain('HzCodeEditor');
+    expect(source).toContain('data-monitor-editor-code-editor-owner="hertzbeat-ui-code-editor"');
+    expect(source).toContain('data-monitor-editor-code-editor="grafana-dashboard-template"');
     expect(source).toContain('data-monitor-grafana-code-editor="dashboard-template"');
     expect(source).not.toContain('grafanaDashboard.enabled ? <EditorRow');
   });
 
-  it('uses the shared CodeMirror editor for structured monitor parameter values', () => {
+  it('keeps the Angular Grafana JSON template upload wired through shared file input', () => {
+    const source = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8');
+    const html = renderToStaticMarkup(
+      <MonitorEditorSurface
+        initial={{
+          monitor: {
+            id: 42,
+            app: 'prometheus',
+            name: 'prometheus',
+            instance: 'prometheus:9090',
+            scrape: 'static',
+            scheduleType: 'interval',
+            intervals: 60,
+            status: 1,
+            labels: {},
+            annotations: {}
+          } as any,
+          collector: '',
+          grafanaDashboard: {
+            enabled: true,
+            template: '{"panels":[]}'
+          },
+          params: [{ field: 'host', paramValue: 'prometheus' }],
+          paramDefines: [{ field: 'host', type: 'text', name: 'Host', required: true }],
+          advancedParams: [],
+          advancedParamDefines: [],
+          scrapeParams: [],
+          scrapeParamDefines: [],
+          collectors: []
+        }}
+        mode="edit"
+        returnContext={{ returnTo: '/monitors?app=prometheus' }}
+      />
+    );
+
+    expect(html).toContain('data-monitor-grafana-template-input-contract="angular-json-template-upload"');
+    expect(html).toContain('data-monitor-grafana-template-upload="angular-json-template-upload"');
+    expect(html).toContain('data-monitor-grafana-template-upload-owner="hertzbeat-ui-file-input"');
+    expect(html).toContain('data-monitor-grafana-template-upload-input="json-template"');
+    expect(html).toContain('data-monitor-grafana-template-upload-trigger-owner="hertzbeat-ui-button"');
+    expect(html).toContain('data-monitor-grafana-template-upload-trigger="json-template"');
+    expect(html).toContain('data-hz-ui="file-input"');
+    expect(html).toContain('accept=".json,application/json"');
+    expect(html).toContain('Upload Grafana Template');
+    expect(source).toContain('HzFileInput');
+    expect(source).toContain('async function handleGrafanaTemplateUpload');
+    expect(source).toContain('file.text()');
+    expect(source).toContain('setGrafanaTemplateText(template)');
+    expect(source).toContain('template');
+    expect(source).not.toContain('data-cold-file-input-owner');
+  });
+
+  it('renders Angular password param definitions as password inputs instead of exposing secrets as text', () => {
+    const source = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8');
+    const html = renderToStaticMarkup(
+      <MonitorEditorSurface
+        initial={{
+          monitor: {
+            id: 42,
+            app: 'mysql',
+            name: 'mysql-prod',
+            instance: '127.0.0.1:3306',
+            scheduleType: 'interval',
+            intervals: 60,
+            status: 1,
+            labels: {},
+            annotations: {}
+          } as any,
+          collector: '',
+          grafanaDashboard: {
+            enabled: false
+          },
+          params: [{ field: 'password', paramValue: 'secret' }],
+          paramDefines: [{ field: 'password', type: 'password', name: 'Password', required: true }],
+          advancedParams: [],
+          advancedParamDefines: [],
+          scrapeParams: [],
+          scrapeParamDefines: [],
+          collectors: []
+        }}
+        mode="edit"
+        returnContext={{ returnTo: '/monitors?app=mysql' }}
+      />
+    );
+
+    expect(html).toContain('data-monitor-param-password-input="password"');
+    expect(html).toContain('data-monitor-param-password-contract="angular-app-multi-func-password"');
+    expect(html).toContain('data-monitor-param-input="password"');
+    expect(html).toContain('data-monitor-param-field="password"');
+    expect(html).toContain('data-monitor-editor-input-owner="hertzbeat-ui-input"');
+    expect(html).toContain('type="password"');
+    expect(source).toContain("if (define.type === 'password')");
+    expect(source).toContain('data-monitor-param-password-contract="angular-app-multi-func-password"');
+  });
+
+  it('renders Angular textarea param definitions as multi-line shared textareas instead of single-line inputs', () => {
+    const source = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8');
+    const html = renderToStaticMarkup(
+      <MonitorEditorSurface
+        initial={{
+          monitor: {
+            id: 42,
+            app: 'website',
+            name: 'website-prod',
+            instance: 'https://example.com',
+            scheduleType: 'interval',
+            intervals: 60,
+            status: 1,
+            labels: {},
+            annotations: {}
+          } as any,
+          collector: '',
+          grafanaDashboard: {
+            enabled: false
+          },
+          params: [{ field: 'body', paramValue: 'line one\nline two' }],
+          paramDefines: [{ field: 'body', type: 'textarea', name: 'Body', required: false }],
+          advancedParams: [],
+          advancedParamDefines: [],
+          scrapeParams: [],
+          scrapeParamDefines: [],
+          collectors: []
+        }}
+        mode="edit"
+        returnContext={{ returnTo: '/monitors?app=website' }}
+      />
+    );
+
+    expect(html).toContain('data-monitor-param-textarea="body"');
+    expect(html).toContain('data-monitor-param-textarea-contract="angular-nz-input-textarea-rows-8"');
+    expect(html).toContain('data-monitor-param-field="body"');
+    expect(html).toContain('data-monitor-editor-textarea-owner="hertzbeat-ui-textarea"');
+    expect(html).toContain('data-hz-ui="textarea"');
+    expect(html).toContain('data-hz-textarea-height="tall"');
+    expect(html).not.toContain('data-monitor-param-input="body"');
+    expect(source).toContain("if (define.type === 'textarea')");
+    expect(source).toContain('data-monitor-param-textarea-contract="angular-nz-input-textarea-rows-8"');
+  });
+
+  it('renders Angular radio param definitions as shared button groups instead of select menus', () => {
+    const source = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8');
+    const html = renderToStaticMarkup(
+      <MonitorEditorSurface
+        initial={{
+          monitor: {
+            id: 42,
+            app: 'website',
+            name: 'checkout',
+            instance: 'example.com:443',
+            scrape: 'static',
+            scheduleType: 'interval',
+            intervals: 60,
+            status: 1,
+            labels: {},
+            annotations: {}
+          } as any,
+          collector: '',
+          grafanaDashboard: { enabled: false },
+          params: [{ field: 'authType', paramValue: 'basic' }],
+          paramDefines: [
+            {
+              field: 'authType',
+              type: 'radio',
+              name: 'Auth Type',
+              options: [
+                { label: 'Basic', value: 'basic' },
+                { label: 'Bearer', value: 'bearer' }
+              ]
+            }
+          ],
+          advancedParams: [],
+          advancedParamDefines: [],
+          scrapeParams: [],
+          scrapeParamDefines: [],
+          collectors: []
+        }}
+        mode="edit"
+        returnContext={{ returnTo: '/monitors?app=website' }}
+      />
+    );
+
+    expect(html).toContain('data-monitor-param-radio="authType"');
+    expect(html).toContain('data-monitor-param-radio-contract="angular-nz-radio-group-button-solid"');
+    expect(html).toContain('data-monitor-param-field="authType"');
+    expect(html).toContain('data-monitor-editor-radio-owner="hertzbeat-ui-radio-button-group"');
+    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).not.toContain('data-monitor-param-select="authType"');
+    expect(source).toContain('HzRadioButtonGroup');
+    expect(source).toContain('data-monitor-param-radio-contract="angular-nz-radio-group-button-solid"');
+    expect(source).not.toContain('data-monitor-param-select={define.field}');
+  });
+
+  it('renders monitor key-value and array params as ordinary shared text controls instead of code editors', () => {
     const source = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8');
     const html = renderToStaticMarkup(
       <MonitorEditorSurface
@@ -514,7 +1580,7 @@ describe('MonitorEditorSurface', () => {
             { field: 'targets', paramValue: '[\n  "checkout",\n  "orders"\n]' }
           ],
           paramDefines: [
-            { field: 'headers', type: 'key-value', name: 'Headers' },
+            { field: 'headers', type: 'key-value', name: 'Headers', keyAlias: 'Header Name', valueAlias: 'Header Value' },
             { field: 'targets', type: 'array', name: 'Targets' }
           ],
           advancedParams: [],
@@ -528,16 +1594,128 @@ describe('MonitorEditorSurface', () => {
       />
     );
 
-    expect(html).toContain('data-monitor-param-code-editor="key-value"');
-    expect(html).toContain('data-monitor-param-code-editor="array"');
-    expect(html).toContain('data-monitor-param-field="headers"');
-    expect(html).toContain('data-monitor-param-field="targets"');
-    expect(html).toContain('data-cold-code-editor="codemirror"');
-    expect(html).toContain('data-cold-code-editor-language="json"');
-    expect(html).toContain('&quot;Authorization&quot;');
+    expect(html).toContain('data-monitor-param-key-value-editor="headers"');
+    expect(html).toContain('data-monitor-param-array-input="targets"');
+    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('placeholder="Header Name"');
+    expect(html).toContain('placeholder="Header Value"');
+    expect(html).toContain('value="Authorization"');
+    expect(html).toContain('value="Bearer token"');
     expect(html).toContain('&quot;checkout&quot;');
-    expect(source).toContain('data-monitor-param-code-editor={define.type}');
-    expect(source).toContain('data-monitor-param-field={define.field}');
-    expect(source).toContain('onChange={nextValue => onChange(nextValue)}');
+    expect(html).not.toContain('data-monitor-param-code-editor="key-value"');
+    expect(html).not.toContain('data-monitor-param-code-editor="array"');
+    expect(html).not.toContain('data-mocked-codemirror="true"');
+    expect(source).toContain('HzCodeEditor');
+    expect(source).toContain('data-monitor-grafana-code-editor="dashboard-template"');
+    expect(source).toContain('data-monitor-param-key-value-editor={define.field}');
+    expect(source).toContain('data-monitor-param-array-input={define.field}');
+    expect(source).not.toContain('data-monitor-param-code-editor={define.type}');
+  });
+
+  it('renders Angular labels params as configurable key/value rows instead of a plain text input', () => {
+    const source = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8');
+    const html = renderToStaticMarkup(
+      <MonitorEditorSurface
+        initial={{
+          monitor: {
+            id: 42,
+            app: 'custom',
+            name: 'custom monitor',
+            instance: 'custom:8080',
+            scrape: 'static',
+            scheduleType: 'interval',
+            intervals: 60,
+            status: 1,
+            labels: {},
+            annotations: {}
+          } as any,
+          collector: '',
+          grafanaDashboard: { enabled: false },
+          params: [{ field: 'labels', paramValue: { team: 'platform', env: 'prod' } }],
+          paramDefines: [{ field: 'labels', type: 'labels', name: 'Labels' }],
+          advancedParams: [],
+          advancedParamDefines: [],
+          scrapeParams: [],
+          scrapeParamDefines: [],
+          collectors: []
+        }}
+        mode="edit"
+        returnContext={{ returnTo: '/monitors?app=custom' }}
+      />
+    );
+
+    expect(html).toContain('data-monitor-param-labels-editor="labels"');
+    expect(html).toContain('data-monitor-param-labels-contract="angular-app-configurable-field-key-value"');
+    expect(html).toContain('data-monitor-param-field="labels"');
+    expect(html).toContain('data-monitor-editor-labels-owner="hertzbeat-ui-key-value-editor"');
+    expect(html).toContain('data-monitor-param-labels-input="key"');
+    expect(html).toContain('data-monitor-param-labels-input="value"');
+    expect(html).toContain('data-hz-ui="key-value-editor"');
+    expect(html).toContain('value="team"');
+    expect(html).toContain('value="platform"');
+    expect(html).toContain('value="env"');
+    expect(html).toContain('value="prod"');
+    expect(html).not.toContain('data-monitor-param-input="labels"');
+    expect(source).toContain("if (define.type === 'labels')");
+    expect(source).toContain('data-monitor-param-labels-editor={define.field}');
+    expect(source).toContain('data-monitor-param-labels-contract="angular-app-configurable-field-key-value"');
+    expect(source).toContain("keyInputProps={{ 'data-monitor-param-labels-input': 'key' }}");
+    expect(source).not.toContain("define.type === 'labels' ? 'text'");
+  });
+
+  it('renders Angular metrics-field params as three-column configurable rows instead of a plain text input', () => {
+    const source = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8');
+    const html = renderToStaticMarkup(
+      <MonitorEditorSurface
+        initial={{
+          monitor: {
+            id: 42,
+            app: 'custom',
+            name: 'custom monitor',
+            instance: 'custom:8080',
+            scrape: 'static',
+            scheduleType: 'interval',
+            intervals: 60,
+            status: 1,
+            labels: {},
+            annotations: {}
+          } as any,
+          collector: '',
+          grafanaDashboard: { enabled: false },
+          params: [{ field: 'metrics', paramValue: [{ field: 'usage', unit: '%', type: 'number' }] }],
+          paramDefines: [{ field: 'metrics', type: 'metrics-field', name: 'Metrics' }],
+          advancedParams: [],
+          advancedParamDefines: [],
+          scrapeParams: [],
+          scrapeParamDefines: [],
+          collectors: []
+        }}
+        mode="edit"
+        returnContext={{ returnTo: '/monitors?app=custom' }}
+      />
+    );
+
+    expect(html).toContain('data-monitor-param-metrics-field-editor="metrics"');
+    expect(html).toContain('data-monitor-param-metrics-field-contract="angular-app-configurable-field-field-unit-type"');
+    expect(html).toContain('data-monitor-param-field="metrics"');
+    expect(html).toContain('data-monitor-editor-metrics-field-owner="hertzbeat-ui-configurable-field-editor"');
+    expect(html).toContain('data-monitor-param-metrics-field-input="field"');
+    expect(html).toContain('data-monitor-param-metrics-field-input="unit"');
+    expect(html).toContain('data-monitor-param-metrics-field-input="type"');
+    expect(html).toContain('data-hz-ui="configurable-field-editor"');
+    expect(html).toContain('placeholder="Field"');
+    expect(html).toContain('placeholder="Unit"');
+    expect(html).toContain('placeholder="Type"');
+    expect(html).toContain('value="usage"');
+    expect(html).toContain('value="%"');
+    expect(html).toContain('value="number"');
+    expect(html).not.toContain('data-monitor-param-input="metrics"');
+    expect(source).toContain("if (define.type === 'metrics-field')");
+    expect(source).toContain('HzConfigurableFieldEditor');
+    expect(source).toContain('data-monitor-param-metrics-field-editor={define.field}');
+    expect(source).toContain('data-monitor-param-metrics-field-contract="angular-app-configurable-field-field-unit-type"');
+    expect(source).not.toContain("define.type === 'metrics-field' ? 'text'");
   });
 });
diff --git a/web-next/components/pages/monitor-editor-surface.tsx b/web-next/components/pages/monitor-editor-surface.tsx
index 604c44646f..d966da70ef 100644
--- a/web-next/components/pages/monitor-editor-surface.tsx
+++ b/web-next/components/pages/monitor-editor-surface.tsx
@@ -2,40 +2,171 @@
 
 import React, { useEffect, useRef, useState } from 'react';
 import { useRouter } from 'next/navigation';
+import { ChevronDown } from 'lucide-react';
+import { HzButton, HzCheckbox, HzCodeEditor, HzConfigurableFieldEditor, HzField, HzFileInput, HzInlineFeedback, HzInput, HzKeyValueEditor, HzLoadingState, HzMonitorEditorActionBar, HzMonitorEditorFieldGrid, HzMonitorEditorForm, HzMonitorEditorHeader, HzMonitorEditorSection, HzNumberStepper, HzRadioButtonGroup, HzSelect, HzSwitch, HzTextarea, type HzMutationStatus } from '@hertzbeat/ui';
 import { useI18n } from '@/components/providers/i18n-provider';
-import { Button } from '@/components/ui/button';
-import { Checkbox } from '../ui/checkbox';
-import { Input } from '@/components/ui/input';
-import { Select } from '@/components/ui/select';
-import { Textarea } from '@/components/ui/textarea';
-import { ColdCodeEditor } from '../ui/cold-code-editor';
-import { NumberStepper } from '../ui/number-stepper';
-import { SurfaceSection } from '@/components/workbench/primitives';
-import { RowList, WorkbenchPage } from '@/components/workbench/workbench-page';
-import { apiMessageGet, apiMessagePost, apiMessagePut } from '@/lib/api-client';
+import { api } from '@/lib/monitor-api-facade';
 import {
-  createMonitor,
-  detectMonitor,
-  loadMonitorScrapeDraft,
+  createMonitorFromFacade,
+  detectMonitorFromFacade,
+  applyMonitorHostNameAutofill,
+  loadMonitorScrapeDraftFromFacade,
+  resolveMonitorEditorParamChangeNotice,
+  shouldPreserveMonitorScrapeParamsForLoad,
   syncMonitorDependentDisplay,
+  type MonitorCollectorOption,
   type MonitorEditorDraft,
   type MonitorEditorMode,
-  updateMonitor,
+  updateMonitorFromFacade,
   updateMonitorEditorParam,
   validateMonitorEditorDraft
 } from '@/lib/monitor-editor/controller';
-import { buildMonitorEditorReturnUrl, type MonitorEditorReturnContext } from '@/lib/monitor-editor/navigation';
+import { buildMonitorEditorCancelUrl, buildMonitorEditorReturnUrl, type MonitorEditorReturnContext } from '@/lib/monitor-editor/navigation';
 import { resolveLocalizedText } from '@/lib/monitor-editor/localized-text';
 import type { KeyValueDraft } from '@/lib/entity-editor/draft-utils';
 import { fromKeyValueDraft, toKeyValueDraft } from '@/lib/entity-editor/draft-utils';
-import { ensureKeyValueRows, removeRowAt, updateRowAt } from '@/lib/entity-editor/editor-state';
+import { LabelRecordInput } from '../ui/label-record-input';
 
-const fieldLabelClassName = 'text-[10px] uppercase tracking-[0.22em] text-[var(--ops-text-tertiary)]';
+function parseMonitorKeyValueRows(value: unknown): KeyValueDraft[] {
+  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') {
+    return toKeyValueDraft(
+      Object.fromEntries(
+        Object.entries(value as Record<string, unknown>).map(([key, entryValue]) => [key, String(entryValue ?? '')])
+      )
+    );
+  }
+
+  if (typeof value !== 'string') {
+    return [{ key: '', value: '' }];
+  }
+
+  const trimmed = value.trim();
+  if (!trimmed || trimmed === '""') {
+    return [{ key: '', value: '' }];
+  }
+
+  try {
+    const parsed = JSON.parse(trimmed);
+    if (Array.isArray(parsed)) {
+      return parseMonitorKeyValueRows(parsed);
+    }
+    if (parsed && typeof parsed === 'object') {
+      return parseMonitorKeyValueRows(parsed);
+    }
+    if (typeof parsed === 'string') {
+      return parsed.trim() ? [{ key: '', value: parsed }] : [{ key: '', value: '' }];
+    }
+  } catch {
+    // Keep non-JSON legacy values visible instead of silently dropping operator input.
+  }
+
+  return [{ key: '', value: trimmed }];
+}
+
+function stringifyMonitorKeyValueRows(rows: KeyValueDraft[]) {
+  return JSON.stringify(fromKeyValueDraft(rows));
+}
+
+function parseMonitorConfigurableRows(value: unknown, keys: string[]): Record<string, string>[] {
+  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 : [{}];
+  }
+
+  if (value && typeof value === 'object') {
+    const record = value as Record<string, unknown>;
+    return [Object.fromEntries(keys.map(key => [key, String(record[key] ?? '')]))];
+  }
+
+  return [{}];
+}
+
+function stringifyMonitorLabelSelectorValue(labels?: Record<string, unknown>) {
+  return Object.entries(labels || {})
+    .filter(([key]) => key.trim())
+    .map(([key, value]) => {
+      const normalizedKey = key.trim();
+      const normalizedValue = String(value ?? '').trim();
+      return normalizedValue ? `${normalizedKey}:${normalizedValue}` : normalizedKey;
+    })
+    .join(', ');
+}
+
+function parseMonitorLabelSelectorValue(value: string) {
+  return value
+    .split(',')
+    .map(item => item.trim())
+    .filter(Boolean)
+    .reduce<Record<string, string>>((acc, item) => {
+      const [rawKey, ...rawValueParts] = item.split(':');
+      const key = rawKey.trim();
+      if (!key) return acc;
+      const labelValue = rawValueParts.join(':').trim();
+      acc[key] = labelValue || key;
+      return acc;
+    }, {});
+}
+
+function normalizeCollectorOption(option: MonitorCollectorOption | string): MonitorCollectorOption | null {
+  if (typeof option === 'string') {
+    return option.trim() ? { name: option.trim() } : null;
+  }
+  const name = option.name?.trim();
+  return name ? { ...option, name } : null;
+}
+
+function isCollectorOnline(option: MonitorCollectorOption) {
+  if (typeof option.online === 'boolean') return option.online;
+  const status = String(option.status ?? '').toLowerCase();
+  return status === '0' || status === 'online' || status === 'true';
+}
+
+function collectorModeLabel(mode: string | null | undefined, t: (key: string) => string) {
+  return mode === 'private' ? t('collector.mode.private') : t('collector.mode.public');
+}
+
+function collectorStatusLabel(option: MonitorCollectorOption, t: (key: string) => string) {
+  return isCollectorOnline(option) ? t('monitor.collector.status.online') : t('monitor.collector.status.offline');
+}
+
+function formatCollectorOptionLabel(option: MonitorCollectorOption, t: (key: string) => string) {
+  return [option.name, option.ip, collectorStatusLabel(option, t), collectorModeLabel(option.mode, t)].filter(Boolean).join(' · ');
+}
+
+function resolveMonitorIntervalStepperBounds(app?: string | null) {
+  const fastInterval = app === 'push';
+  return {
+    min: fastInterval ? 1 : 10,
+    step: fastInterval ? 1 : 10,
+    max: 604800
+  };
+}
 
 function MonitorParamField({
   field,
   define,
   locale,
+  t,
   onChange
 }: {
   field: { paramValue?: unknown };
@@ -45,79 +176,207 @@ function MonitorParamField({
     type?: string;
     placeholder?: string | Record<string, string>;
     options?: Array<{ label?: string | Record<string, string>; value?: string }>;
+    keyAlias?: string;
+    valueAlias?: string;
   };
   locale: string;
+  t: (key: string) => string;
   onChange: (value: unknown) => void;
 }) {
   const label = resolveLocalizedText(define.name, locale, define.field);
   const placeholder = resolveLocalizedText(define.placeholder, locale, '');
   if (define.type === 'boolean') {
     return (
-      <div className="grid gap-2">
-        <span className={fieldLabelClassName}>{label}</span>
-        <Checkbox
-          data-monitor-param-checkbox={define.field}
+      <HzField as="div" label={label} data-monitor-editor-field-owner="hertzbeat-ui-field">
+        <HzSwitch
+          data-monitor-param-switch={define.field}
+          data-monitor-param-boolean-contract="angular-nz-switch"
+          data-monitor-param-field={define.field}
+          data-monitor-editor-switch-owner="hertzbeat-ui-switch"
           checked={Boolean(field.paramValue)}
-          onChange={e => onChange(e.target.checked)}
+          onCheckedChange={checked => onChange(checked)}
+          aria-label={label}
           label={label}
         />
-      </div>
+      </HzField>
     );
   }
   if (define.type === 'radio' && (define.options || []).length > 0) {
+    const radioOptions = (define.options || []).map(option => ({
+      value: option.value || '',
+      label: resolveLocalizedText(option.label, locale, option.value || '')
+    }));
+    return (
+      <HzField as="div" label={label} data-monitor-editor-field-owner="hertzbeat-ui-field">
+        <HzRadioButtonGroup
+          name={define.field}
+          value={String(field.paramValue ?? '')}
+          options={radioOptions}
+          data-monitor-param-radio={define.field}
+          data-monitor-param-radio-contract="angular-nz-radio-group-button-solid"
+          data-monitor-param-field={define.field}
+          data-monitor-editor-radio-owner="hertzbeat-ui-radio-button-group"
+          aria-label={label}
+          onChange={value => onChange(value)}
+        />
+      </HzField>
+    );
+  }
+  if (define.type === 'key-value') {
+    return (
+      <HzField as="div" label={label} data-monitor-editor-field-owner="hertzbeat-ui-field">
+        <HzKeyValueEditor
+          data-monitor-param-key-value-editor={define.field}
+          data-monitor-param-field={define.field}
+          data-monitor-editor-key-value-owner="hertzbeat-ui-key-value-editor"
+          rows={parseMonitorKeyValueRows(field.paramValue)}
+          onChange={rows => onChange(stringifyMonitorKeyValueRows(rows))}
+          addLabel={t('common.add')}
+          removeLabel={t('common.remove')}
+          keyPlaceholder={define.keyAlias || t('monitor.editor.labels.key')}
+          valuePlaceholder={define.valueAlias || t('monitor.editor.labels.value')}
+        />
+      </HzField>
+    );
+  }
+  if (define.type === 'labels') {
+    return (
+      <HzField as="div" label={label} data-monitor-editor-field-owner="hertzbeat-ui-field">
+        <HzKeyValueEditor
+          data-monitor-param-labels-editor={define.field}
+          data-monitor-param-labels-contract="angular-app-configurable-field-key-value"
+          data-monitor-param-field={define.field}
+          data-monitor-editor-labels-owner="hertzbeat-ui-key-value-editor"
+          rows={parseMonitorKeyValueRows(field.paramValue)}
+          onChange={rows => onChange(stringifyMonitorKeyValueRows(rows))}
+          addLabel={t('common.add')}
+          removeLabel={t('common.remove')}
+          keyPlaceholder={define.keyAlias || t('monitor.editor.labels.key')}
+          valuePlaceholder={define.valueAlias || t('monitor.editor.labels.value')}
+          keyInputProps={{ 'data-monitor-param-labels-input': 'key' }}
+          valueInputProps={{ 'data-monitor-param-labels-input': 'value' }}
+        />
+      </HzField>
+    );
+  }
+  if (define.type === 'metrics-field') {
+    return (
+      <HzField as="div" label={label} data-monitor-editor-field-owner="hertzbeat-ui-field">
+        <HzConfigurableFieldEditor
+          rows={parseMonitorConfigurableRows(field.paramValue, ['field', 'unit', 'type'])}
+          columns={[
+            {
+              key: 'field',
+              placeholder: 'Field',
+              inputProps: { 'data-monitor-param-metrics-field-input': 'field' } as React.ComponentProps<typeof HzConfigurableFieldEditor>['columns'][number]['inputProps']
+            },
+            {
+              key: 'unit',
+              placeholder: 'Unit',
+              className: 'minmax(50px,90px)',
+              inputProps: { 'data-monitor-param-metrics-field-input': 'unit' } as React.ComponentProps<typeof HzConfigurableFieldEditor>['columns'][number]['inputProps']
+            },
+            {
+              key: 'type',
+              placeholder: 'Type',
+              inputProps: { 'data-monitor-param-metrics-field-input': 'type' } as React.ComponentProps<typeof HzConfigurableFieldEditor>['columns'][number]['inputProps']
+            }
+          ]}
+          onChange={rows => onChange(rows)}
+          addLabel={t('common.add')}
+          removeLabel={t('common.remove')}
+          data-monitor-param-metrics-field-editor={define.field}
+          data-monitor-param-metrics-field-contract="angular-app-configurable-field-field-unit-type"
+          data-monitor-param-field={define.field}
+          data-monitor-editor-metrics-field-owner="hertzbeat-ui-configurable-field-editor"
+        />
+      </HzField>
+    );
+  }
+  if (define.type === 'array') {
+    return (
+      <HzField label={label} data-monitor-editor-field-owner="hertzbeat-ui-field">
+        <HzInput
+          type="text"
+          data-monitor-param-array-input={define.field}
+          data-monitor-param-input={define.field}
+          data-monitor-param-field={define.field}
+          data-monitor-editor-input-owner="hertzbeat-ui-input"
+          value={String(field.paramValue ?? '')}
+          placeholder={placeholder}
+          onChange={e => onChange(e.target.value)}
+        />
+      </HzField>
+    );
+  }
+  if (define.type === 'password') {
     return (
-      <label className="grid gap-2">
-        <span className={fieldLabelClassName}>{label}</span>
-        <Select value={String(field.paramValue ?? '')} onChange={e => onChange(e.target.value)}>
-          {(define.options || []).map(option => (
-            <option key={option.value} value={option.value}>
-              {resolveLocalizedText(option.label, locale, option.value || '')}
-            </option>
-          ))}
-        </Select>
-      </label>
+      <HzField label={label} data-monitor-editor-field-owner="hertzbeat-ui-field">
+        <HzInput
+          type="password"
+          data-monitor-param-password-input={define.field}
+          data-monitor-param-password-contract="angular-app-multi-func-password"
+          data-monitor-param-input={define.field}
+          data-monitor-param-field={define.field}
+          data-monitor-editor-input-owner="hertzbeat-ui-input"
+          value={String(field.paramValue ?? '')}
+          placeholder={placeholder}
+          onChange={e => onChange(e.target.value)}
+        />
+      </HzField>
     );
   }
-  if (define.type === 'key-value' || define.type === 'array') {
+  if (define.type === 'textarea') {
     return (
-      <label className="grid gap-2">
-        <span className={fieldLabelClassName}>{label}</span>
-        <ColdCodeEditor
-          data-monitor-param-code-editor={define.type}
+      <HzField label={label} data-monitor-editor-field-owner="hertzbeat-ui-field">
+        <HzTextarea
+          height="tall"
+          data-monitor-param-textarea={define.field}
+          data-monitor-param-textarea-contract="angular-nz-input-textarea-rows-8"
           data-monitor-param-field={define.field}
+          data-monitor-editor-textarea-owner="hertzbeat-ui-textarea"
           value={String(field.paramValue ?? '')}
-          language="json"
-          minHeight="120px"
           placeholder={placeholder}
-          ariaLabel={label}
-          onChange={nextValue => onChange(nextValue)}
+          onChange={e => onChange(e.target.value)}
         />
-      </label>
+      </HzField>
     );
   }
   if (define.type === 'number') {
     return (
-      <label className="grid gap-2">
-        <span className={fieldLabelClassName}>{label}</span>
-        <NumberStepper
+      <HzField as="div" label={label} data-monitor-editor-field-owner="hertzbeat-ui-field">
+        <HzNumberStepper
           data-monitor-param-number-stepper={define.field}
+          data-monitor-param-number-contract="angular-nz-input-number--1000-65535-step-1"
+          data-monitor-param-number-min="-1000"
+          data-monitor-param-number-max="65535"
+          data-monitor-param-number-step="1"
+          data-monitor-editor-number-stepper-owner="hertzbeat-ui-number-stepper"
           value={String(field.paramValue ?? '')}
+          min={-1000}
+          max={65535}
+          step={1}
           placeholder={placeholder}
+          decrementLabel={t('common.decrement')}
+          incrementLabel={t('common.increment')}
           onValueChange={value => onChange(value === '' ? null : Number(value))}
         />
-      </label>
+      </HzField>
     );
   }
   return (
-    <label className="grid gap-2">
-      <span className={fieldLabelClassName}>{label}</span>
-      <Input
+    <HzField label={label} data-monitor-editor-field-owner="hertzbeat-ui-field">
+      <HzInput
         type="text"
+        data-monitor-param-input={define.field}
+        data-monitor-param-field={define.field}
+        data-monitor-editor-input-owner="hertzbeat-ui-input"
+        data-monitor-editor-host-name-autofill-contract={define.field === 'host' ? 'angular-new-host-change' : undefined}
         value={String(field.paramValue ?? '')}
         placeholder={placeholder}
         onChange={e => onChange(e.target.value)}
       />
-    </label>
+    </HzField>
   );
 }
 
@@ -134,53 +393,6 @@ function validateMetadataRows(rows: KeyValueDraft[], sectionLabel: string) {
   return null;
 }
 
-function KeyValueEditor({
-  title,
-  rows,
-  onChange,
-  addLabel,
-  removeLabel,
-  keyPlaceholder,
-  valuePlaceholder
-}: {
-  title: string;
-  rows: KeyValueDraft[];
-  onChange: (rows: KeyValueDraft[]) => void;
-  addLabel: string;
-  removeLabel: string;
-  keyPlaceholder: string;
-  valuePlaceholder: string;
-}) {
-  const nextRows = ensureKeyValueRows(rows);
-  return (
-    <div className="grid gap-3">
-      <div className={fieldLabelClassName}>{title}</div>
-      {nextRows.map((row, index) => (
-        <div key={`${title}-${index}`} className="grid gap-2 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto]">
-          <Input
-            placeholder={keyPlaceholder}
-            value={row.key}
-            onChange={e => onChange(updateRowAt(nextRows, index, { key: e.target.value }))}
-          />
-          <Input
-            placeholder={valuePlaceholder}
-            value={row.value}
-            onChange={e => onChange(updateRowAt(nextRows, index, { value: e.target.value }))}
-          />
-          <Button type="button" size="sm" variant="subtle" onClick={() => onChange(removeRowAt(nextRows, index))}>
-            {removeLabel}
-          </Button>
-        </div>
-      ))}
-      <div className="flex flex-wrap gap-2">
-        <Button type="button" size="sm" variant="subtle" onClick={() => onChange([...nextRows, { key: '', value: '' }])}>
-          {addLabel}
-        </Button>
-      </div>
-    </div>
-  );
-}
-
 export function MonitorEditorSurface({
   initial,
   mode,
@@ -193,20 +405,55 @@ export function MonitorEditorSurface({
   const { t, locale } = useI18n();
   const router = useRouter();
   const [draft, setDraft] = useState(initial);
-  const [labelRows, setLabelRows] = useState<KeyValueDraft[]>(toKeyValueDraft(initial.monitor.labels));
+  const [labelSelectorText, setLabelSelectorText] = useState(stringifyMonitorLabelSelectorValue(initial.monitor.labels));
   const [annotationRows, setAnnotationRows] = useState<KeyValueDraft[]>(toKeyValueDraft(initial.monitor.annotations));
   const [grafanaTemplateText, setGrafanaTemplateText] = useState(initial.grafanaDashboard.template || '');
   const [actionMessage, setActionMessage] = useState<string | null>(null);
   const [actionError, setActionError] = useState<string | null>(null);
-  const [saving, setSaving] = useState(false);
+  const [actionErrorSource, setActionErrorSource] = useState<'validation' | 'api' | null>(null);
+  const [actionErrorAction, setActionErrorAction] = useState<'detect' | 'save' | null>(null);
+  const [actionPhase, setActionPhase] = useState<'idle' | 'detecting' | 'saving'>('idle');
   const [scrapeLoading, setScrapeLoading] = useState(false);
+  const [advancedOpen, setAdvancedOpen] = useState(false);
+  const grafanaTemplateInputRef = useRef<HTMLInputElement | null>(null);
   const scrapeParamsRef = useRef(draft.scrapeParams);
+  const previousScrapeRef = useRef<string | null>(null);
+  const systemDefaultCollectorLabel = t('monitor.collector.system.default');
+  const scrapeOptions = [
+    { value: 'static', label: 'static' },
+    { value: 'http_sd', label: 'http_sd' },
+    { value: 'nacos_sd', label: 'nacos_sd' },
+    { value: 'dns_sd', label: 'dns_sd' },
+    { value: 'eureka_sd', label: 'eureka_sd' },
+    { value: 'consul_sd', label: 'consul_sd' },
+    { value: 'zookeeper_sd', label: 'zookeeper_sd' }
+  ];
+  const collectorChoices = draft.collectors.map(normalizeCollectorOption).filter((option): option is MonitorCollectorOption => Boolean(option));
+  const selectedCollectorChoice = collectorChoices.find(option => option.name === draft.collector);
+  const defaultCollectorModeLabel = collectorModeLabel('public', t);
+  const intervalStepperBounds = resolveMonitorIntervalStepperBounds(draft.monitor.app);
+  const collectorOptions = [
+    { value: '', label: `${systemDefaultCollectorLabel} - ${defaultCollectorModeLabel}` },
+    ...collectorChoices.map(collector => ({ value: collector.name, label: formatCollectorOptionLabel(collector, t) }))
+  ];
+  const scheduleTypeOptions = [
+    { value: 'interval', label: t('monitor.scheduleType.interval') },
+    { value: 'cron', label: t('monitor.scheduleType.cron') }
+  ];
+  const staticHostParamIndex = draft.paramDefines.findIndex(define => define.field === 'host');
+  const shouldRenderStaticHost =
+    (draft.monitor.scrape || 'static') === 'static' &&
+    staticHostParamIndex >= 0 &&
+    draft.params[staticHostParamIndex]?.display !== false;
+  const hasVisibleAdvancedParams = draft.advancedParams.some(param => param?.display !== false);
 
   useEffect(() => {
+    previousScrapeRef.current = null;
     setDraft(syncMonitorDependentDisplay(initial));
-    setLabelRows(toKeyValueDraft(initial.monitor.labels));
+    setLabelSelectorText(stringifyMonitorLabelSelectorValue(initial.monitor.labels));
     setAnnotationRows(toKeyValueDraft(initial.monitor.annotations));
     setGrafanaTemplateText(initial.grafanaDashboard.template || '');
+    setAdvancedOpen(false);
   }, [initial]);
 
   useEffect(() => {
@@ -216,8 +463,27 @@ export function MonitorEditorSurface({
   useEffect(() => {
     const scrape = draft.monitor.scrape || 'static';
     let cancelled = false;
+    const previousScrape = previousScrapeRef.current;
+    const shouldPreserveExistingParams = shouldPreserveMonitorScrapeParamsForLoad(previousScrape, scrape);
+    previousScrapeRef.current = scrape;
+    if (scrape === 'static') {
+      setScrapeLoading(false);
+      setDraft(prev => {
+        if (prev.scrapeParams.length === 0 && prev.scrapeParamDefines.length === 0) {
+          return prev;
+        }
+        return syncMonitorDependentDisplay({
+          ...prev,
+          scrapeParams: [],
+          scrapeParamDefines: []
+        });
+      });
+      return () => {
+        cancelled = true;
+      };
+    }
     setScrapeLoading(true);
-    loadMonitorScrapeDraft(apiMessageGet, scrape, scrapeParamsRef.current)
+    loadMonitorScrapeDraftFromFacade(api.monitors.editorParamDefines, scrape, shouldPreserveExistingParams ? scrapeParamsRef.current : undefined)
       .then(result => {
         if (cancelled) return;
         setDraft(prev =>
@@ -241,7 +507,33 @@ export function MonitorEditorSurface({
   }, [draft.monitor.scrape, t]);
 
   function updateParam(kind: 'params' | 'advancedParams' | 'scrapeParams', index: number, value: unknown) {
-    setDraft(prev => updateMonitorEditorParam(prev, kind, index, value));
+    const noticeKey = resolveMonitorEditorParamChangeNotice(draft, kind, index, value);
+    setDraft(prev => {
+      const nextDraft = updateMonitorEditorParam(prev, kind, index, value);
+      const defines = kind === 'params' ? prev.paramDefines : kind === 'advancedParams' ? prev.advancedParamDefines : prev.scrapeParamDefines;
+      return applyMonitorHostNameAutofill(nextDraft, { mode, field: defines[index]?.field });
+    });
+    if (noticeKey) {
+      setActionMessage(t(noticeKey));
+      setActionError(null);
+      setActionErrorSource(null);
+      setActionErrorAction(null);
+    }
+  }
+
+  async function handleGrafanaTemplateUpload(event: React.ChangeEvent<HTMLInputElement>) {
+    const file = event.target.files?.[0];
+    event.target.value = '';
+    if (!file) return;
+    const template = await file.text();
+    setGrafanaTemplateText(template);
+    setDraft(prev => ({
+      ...prev,
+      grafanaDashboard: {
+        ...prev.grafanaDashboard,
+        template
+      }
+    }));
   }
 
   function buildDraftForSubmit() {
@@ -249,7 +541,7 @@ export function MonitorEditorSurface({
       ...draft,
       monitor: {
         ...draft.monitor,
-        labels: fromKeyValueDraft(labelRows),
+        labels: parseMonitorLabelSelectorValue(labelSelectorText),
         annotations: fromKeyValueDraft(annotationRows)
       },
       grafanaDashboard: {
@@ -259,250 +551,530 @@ export function MonitorEditorSurface({
     } satisfies MonitorEditorDraft;
   }
 
-  function validateBeforeSubmit(nextDraft: MonitorEditorDraft) {
-    const labelError = validateMetadataRows(labelRows, t('label.bind'));
+  function validateBeforeSubmit(nextDraft: MonitorEditorDraft, options: { validateCronFormat?: boolean } = {}) {
+    const labelError = validateMetadataRows(toKeyValueDraft(parseMonitorLabelSelectorValue(labelSelectorText)), t('label.bind'));
     if (labelError) return labelError;
     const annotationError = validateMetadataRows(annotationRows, t('common.annotation.bind'));
     if (annotationError) return annotationError;
-    return validateMonitorEditorDraft(nextDraft, t);
+    return validateMonitorEditorDraft(nextDraft, t, options);
   }
 
+  const actionBusy = actionPhase !== 'idle';
+  const busyActionLabel = actionPhase === 'detecting' ? t('monitor.spinning-tip.detecting') : t('common.loading');
+  const mutationStatus: HzMutationStatus = actionBusy ? 'saving' : actionError ? 'failed' : actionMessage ? 'saved' : 'clean';
+  const saveReturnUrl = buildMonitorEditorReturnUrl(draft.monitor.app, returnContext ? { ...returnContext } : undefined);
+  const cancelReturnUrl = buildMonitorEditorCancelUrl(returnContext ? { ...returnContext } : undefined);
+  const saveReturnContract = returnContext?.returnTo ? 'safe-return-context-or-angular-app-list' : 'angular-app-list';
+  const cancelReturnContract = returnContext?.returnTo ? 'safe-return-context-or-list-root' : 'legacy-list-root';
+  const mutationStatusLabel =
+    actionBusy
+      ? busyActionLabel
+      : mutationStatus === 'failed'
+        ? t('common.failed')
+        : mutationStatus === 'saved'
+          ? actionMessage || t('common.ready')
+          : t('common.ready');
+  const mutationFeedback = actionBusy ? (
+    <HzInlineFeedback
+      tone="info"
+      title={busyActionLabel}
+      variant="embedded"
+      data-monitor-editor-feedback-owner="hertzbeat-ui-inline-feedback"
+      data-monitor-editor-feedback="busy"
+      data-monitor-editor-busy-contract="angular-spin-tip"
+      data-monitor-editor-busy-phase={actionPhase}
+    />
+  ) : actionError ? (
+    <HzInlineFeedback
+      tone="critical"
+      title={t('common.failed')}
+      description={actionError}
+      variant="embedded"
+      data-monitor-editor-feedback-owner="hertzbeat-ui-inline-feedback"
+      data-monitor-editor-feedback="error"
+      data-monitor-editor-validation-contract={actionErrorSource === 'validation' ? 'angular-form-required' : undefined}
+      data-monitor-editor-api-error-contract={actionErrorSource === 'api' ? 'angular-message-msg' : undefined}
+      data-monitor-editor-blocked-action={actionErrorAction || undefined}
+      data-monitor-editor-blocked-before-api={actionErrorSource === 'validation' ? 'true' : undefined}
+    />
+  ) : actionMessage ? (
+    <HzInlineFeedback
+      tone="success"
+      title={actionMessage}
+      variant="embedded"
+      data-monitor-editor-feedback-owner="hertzbeat-ui-inline-feedback"
+      data-monitor-editor-feedback="success"
+    />
+  ) : null;
+  const mutationBar = (
+    <HzMonitorEditorActionBar
+      title={mode === 'new' ? t('monitor.new-monitor') : draft.monitor.name || t('monitor.edit-monitor')}
+      status={mutationStatus}
+      statusLabel={mutationStatusLabel}
+      summaryVisible={false}
+      actionAlign="center"
+      feedback={mutationFeedback}
+      data-monitor-editor-action-dock="bottom"
+      data-monitor-editor-mutation-bar="true"
+      actions={[
+        {
+          id: 'detect',
+          label: t('common.button.detect'),
+          intent: 'ghost',
+          disabled: actionBusy,
+          onSelect: () => void handleDetect(),
+          buttonProps: {
+            'data-monitor-editor-detect-action': 'true',
+            'data-monitor-editor-detect-busy-label': t('monitor.spinning-tip.detecting'),
+            'data-monitor-editor-detect-cron-validation': 'angular-detect-skips-cron-format'
+          }
+        },
+        {
+          id: 'submit',
+          label: t('common.button.ok'),
+          type: 'submit',
+          intent: 'primary',
+          disabled: actionBusy,
+          buttonProps: {
+            'data-monitor-editor-submit-action': 'true',
+            'data-monitor-editor-submit-busy-label': t('common.loading'),
+            'data-monitor-editor-save-return': saveReturnContract,
+            'data-monitor-editor-save-return-target': saveReturnUrl,
+            'data-monitor-editor-save-notification-contract': 'angular-success-before-return'
+          }
+        },
+        {
+          id: 'cancel',
+          label: t('common.button.cancel'),
+          intent: 'ghost',
+          onSelect: () => router.push(cancelReturnUrl),
+          buttonProps: {
+            'data-monitor-editor-cancel-action': 'true',
+            'data-monitor-editor-cancel-return': cancelReturnContract,
+            'data-monitor-editor-cancel-return-target': cancelReturnUrl
+          }
+        }
+      ]}
+    />
+  );
+
   async function handleDetect() {
-    setSaving(true);
+    setActionPhase('detecting');
+    setActionErrorSource(null);
+    setActionErrorAction(null);
     try {
       const nextDraft = buildDraftForSubmit();
-      const validationError = validateBeforeSubmit(nextDraft);
+      const validationError = validateBeforeSubmit(nextDraft, { validateCronFormat: false });
       if (validationError) {
         setActionError(validationError);
+        setActionErrorSource('validation');
+        setActionErrorAction('detect');
         setActionMessage(null);
         return;
       }
-      await detectMonitor(apiMessagePost as any, nextDraft);
+      await detectMonitorFromFacade(api.monitors.detect, nextDraft);
       setActionMessage(t('monitor.detect.success'));
       setActionError(null);
+      setActionErrorSource(null);
+      setActionErrorAction(null);
     } catch (error) {
       setActionError(error instanceof Error ? error.message : t('monitor.detect.failed'));
+      setActionErrorSource('api');
+      setActionErrorAction('detect');
     } finally {
-      setSaving(false);
+      setActionPhase('idle');
     }
   }
 
   async function handleSave() {
-    setSaving(true);
+    setActionPhase('saving');
+    setActionErrorSource(null);
+    setActionErrorAction(null);
     try {
       const nextDraft = buildDraftForSubmit();
       const validationError = validateBeforeSubmit(nextDraft);
       if (validationError) {
         setActionError(validationError);
+        setActionErrorSource('validation');
+        setActionErrorAction('save');
         setActionMessage(null);
         return;
       }
       if (mode === 'new') {
-        await createMonitor(apiMessagePost as any, nextDraft);
+        await createMonitorFromFacade(api.monitors.create, nextDraft);
         setActionMessage(t('monitor.new.success'));
       } else {
-        await updateMonitor(apiMessagePut as any, nextDraft);
+        await updateMonitorFromFacade(api.monitors.update, nextDraft);
         setActionMessage(t('monitor.edit.success'));
       }
       setActionError(null);
+      setActionErrorSource(null);
+      setActionErrorAction(null);
       router.push(buildMonitorEditorReturnUrl(nextDraft.monitor.app, returnContext ? { ...returnContext } : undefined));
     } catch (error) {
       setActionError(error instanceof Error ? error.message : mode === 'new' ? t('monitor.new.failed') : t('monitor.edit.failed'));
+      setActionErrorSource('api');
+      setActionErrorAction('save');
     } finally {
-      setSaving(false);
+      setActionPhase('idle');
     }
   }
 
   return (
-    <form
-      className="space-y-3"
+    <HzMonitorEditorForm
+      actionBar={mutationBar}
       onSubmit={event => {
         event.preventDefault();
         void handleSave();
       }}
+      data-monitor-editor-form-owner="hertzbeat-ui-monitor-editor-form"
+      data-monitor-editor-app-source="angular-route-context-hidden-field"
+      data-monitor-editor-static-host-position="angular-before-name"
+      data-monitor-editor-field-order="angular-monitor-form-sequence"
+      data-monitor-editor-detect-payload-contract="angular-monitor-collector-params-no-grafana"
+      data-monitor-editor-save-payload-contract="angular-monitor-collector-params-grafana"
+      data-monitor-editor-payload-param-merge="angular-params-advanced-sdparams"
+      data-monitor-editor-payload-host-instance="angular-host-param-as-instance"
+      data-monitor-editor-service-discovery-params="angular-nonstatic-only"
+      data-monitor-editor-scrape-reload-contract="angular-reset-on-user-scrape-change"
+      data-monitor-editor-ssl-port-notice="angular-info-notification"
+      data-monitor-editor-advanced-collapse="angular-ghost-collapse-dashed-trigger"
+      data-monitor-editor-advanced-visible-contract="angular-visible-param-only"
+      data-monitor-editor-label-selector="angular-app-label-selector"
+      data-monitor-editor-collector-selection="angular-collectors-selection-tags"
+      data-monitor-editor-detect-cron-validation="angular-detect-skips-cron-format"
+      data-monitor-editor-cron-required="angular-required-before-detect-save"
     >
-      <WorkbenchPage
-        kicker={t('monitor.form.shell.kicker')}
+      <input
+        type="hidden"
+        name="app"
+        value={draft.monitor.app || ''}
+        readOnly
+        data-monitor-editor-hidden-app="route-or-detail-context"
+      />
+      <HzMonitorEditorHeader
         title={mode === 'new' ? t('monitor.new-monitor') : draft.monitor.name || t('monitor.edit-monitor')}
-        subtitle={mode === 'new' ? t('monitor.form.shell.new.copy') : t('monitor.form.shell.edit.copy')}
-        tone="operator"
-        facts={[
-          { label: t('common.app'), value: draft.monitor.app || '-' },
-          { label: t('common.scrape'), value: draft.monitor.scrape || 'static' },
-          { label: t('common.mode'), value: mode }
-        ]}
-        actions={
-          <>
-            <Button type="button" size="sm" variant="default" onClick={() => void handleDetect()} disabled={saving}>
-              {t('common.button.detect')}
-            </Button>
-            <Button type="submit" size="sm" variant="primary" disabled={saving}>
-              {t('common.button.ok')}
-            </Button>
-            <Button
+        data-monitor-editor-linear-header="hertzbeat-ui-monitor-editor-header"
+      />
+
+        <HzMonitorEditorSection data-monitor-editor-section-owner="hertzbeat-ui-editor-section" title={t('monitor.editor.section.base')}>
+          <HzMonitorEditorFieldGrid data-monitor-editor-field-grid-owner="hertzbeat-ui-monitor-editor-field-grid">
+            <HzField label={t('monitor.scrape')} data-monitor-editor-field-owner="hertzbeat-ui-field">
+              <HzSelect
+                value={draft.monitor.scrape || 'static'}
+                aria-label={t('monitor.scrape')}
+                options={scrapeOptions}
+                data-monitor-editor-select="scrape"
+                data-monitor-editor-select-owner="hertzbeat-ui-select"
+                onChange={e => setDraft(prev => ({ ...prev, monitor: { ...prev.monitor, scrape: e.target.value } }))}
+              />
+            </HzField>
+            {draft.monitor.scrape !== 'static'
+              ? draft.scrapeParamDefines.map((define, index) => (
+                  draft.scrapeParams[index]?.display === false ? null : (
+                    <div
+                      key={`scrape-${define.field}`}
+                      data-monitor-editor-scrape-param-order="angular-after-scrape-before-name"
+                    >
+                      <MonitorParamField
+                        field={draft.scrapeParams[index] || {}}
+                        define={define}
+                        locale={locale}
+                        t={t}
+                        onChange={value => updateParam('scrapeParams', index, value)}
+                      />
+                    </div>
+                  )
+                ))
+              : null}
+            {shouldRenderStaticHost ? (
+              <div data-monitor-editor-static-host-field="angular-before-name">
+                <MonitorParamField
+                  field={draft.params[staticHostParamIndex] || {}}
+                  define={draft.paramDefines[staticHostParamIndex]}
+                  locale={locale}
+                  t={t}
+                  onChange={value => updateParam('params', staticHostParamIndex, value)}
+                />
+              </div>
+            ) : null}
+            <HzField label={t('monitor.name')} data-monitor-editor-field-owner="hertzbeat-ui-field">
+              <HzInput
+                value={draft.monitor.name || ''}
+                data-monitor-editor-input="name"
+                data-monitor-editor-input-owner="hertzbeat-ui-input"
+                data-monitor-editor-host-name-autofill-target={mode === 'new' ? 'monitor-name' : undefined}
+                onChange={e => setDraft(prev => ({ ...prev, monitor: { ...prev.monitor, name: e.target.value } }))}
+              />
+            </HzField>
+          </HzMonitorEditorFieldGrid>
+        </HzMonitorEditorSection>
+
+        <HzMonitorEditorSection data-monitor-editor-section-owner="hertzbeat-ui-editor-section" title={t('monitor.editor.section.params')}>
+          <HzMonitorEditorFieldGrid data-monitor-editor-field-grid-owner="hertzbeat-ui-monitor-editor-field-grid">
+            {draft.paramDefines.map((define, index) => (
+              draft.params[index]?.display === false || define.field === 'host' ? null : <MonitorParamField key={`param-${define.field}`} field={draft.params[index] || {}} define={define} locale={locale} t={t} onChange={value => updateParam('params', index, value)} />
+            ))}
+          </HzMonitorEditorFieldGrid>
+          {scrapeLoading ? (
+            <HzLoadingState
+              data-monitor-editor-scrape-loading-owner="hertzbeat-ui-loading-state"
+              title={t('common.loading')}
+              rows={2}
+            />
+          ) : null}
+        </HzMonitorEditorSection>
+
+        {hasVisibleAdvancedParams ? (
+          <HzMonitorEditorSection
+            data-monitor-editor-section-owner="hertzbeat-ui-editor-section"
+            data-monitor-editor-advanced-collapse="angular-ghost-collapse-dashed-trigger"
+            data-monitor-editor-advanced-collapse-state={advancedOpen ? 'expanded' : 'collapsed'}
+            title={t('monitor.advanced')}
+          >
+            <HzButton
               type="button"
               size="sm"
-              variant="subtle"
-              data-monitor-editor-cancel-action="true"
-              onClick={() => router.push(buildMonitorEditorReturnUrl(draft.monitor.app, returnContext ? { ...returnContext } : undefined))}
+              intent="secondary"
+              aria-expanded={advancedOpen}
+              aria-controls="monitor-editor-advanced-params"
+              data-monitor-editor-advanced-toggle="angular-dashed-collapse-trigger"
+              data-monitor-editor-advanced-toggle-owner="hertzbeat-ui-button"
+              onClick={() => setAdvancedOpen(open => !open)}
             >
-              {t('common.button.cancel')}
-            </Button>
-          </>
-        }
-        main={
-          <div className="space-y-4">
-            <SurfaceSection title={t('monitor.editor.section.base')} copy={t('monitor.editor.section.base.copy')}>
-              <div className="grid gap-3 md:grid-cols-2">
-                <label className="grid gap-2">
-                  <span className={fieldLabelClassName}>{t('common.app')}</span>
-                  <Input value={draft.monitor.app || ''} onChange={e => setDraft(prev => ({ ...prev, monitor: { ...prev.monitor, app: e.target.value } }))} disabled={mode === 'edit'} />
-                </label>
-                <label className="grid gap-2">
-                  <span className={fieldLabelClassName}>{t('monitor.scrape')}</span>
-                  <Select value={draft.monitor.scrape || 'static'} onChange={e => setDraft(prev => ({ ...prev, monitor: { ...prev.monitor, scrape: e.target.value } }))}>
-                    <option value="static">static</option>
-                    <option value="http_sd">http_sd</option>
-                    <option value="nacos_sd">nacos_sd</option>
-                    <option value="dns_sd">dns_sd</option>
-                    <option value="eureka_sd">eureka_sd</option>
-                    <option value="consul_sd">consul_sd</option>
-                    <option value="zookeeper_sd">zookeeper_sd</option>
-                  </Select>
-                </label>
-                <label className="grid gap-2">
-                  <span className={fieldLabelClassName}>{t('monitor.name')}</span>
-                  <Input value={draft.monitor.name || ''} onChange={e => setDraft(prev => ({ ...prev, monitor: { ...prev.monitor, name: e.target.value } }))} />
-                </label>
-                <label className="grid gap-2">
-                  <span className={fieldLabelClassName}>{t('monitor.collector')}</span>
-                  <Select value={draft.collector || ''} onChange={e => setDraft(prev => ({ ...prev, collector: e.target.value }))}>
-                    <option value="">{t('common.none')}</option>
-                    {draft.collectors.map(collector => (
-                      <option key={collector} value={collector}>
-                        {collector}
-                      </option>
-                    ))}
-                  </Select>
-                </label>
-                <label className="grid gap-2">
-                  <span className={fieldLabelClassName}>{t('monitor.scheduleType')}</span>
-                  <Select value={draft.monitor.scheduleType || 'interval'} onChange={e => setDraft(prev => ({ ...prev, monitor: { ...prev.monitor, scheduleType: e.target.value } }))}>
-                    <option value="interval">{t('monitor.scheduleType.interval')}</option>
-                    <option value="cron">{t('monitor.scheduleType.cron')}</option>
-                  </Select>
-                </label>
-                {draft.monitor.scheduleType === 'cron' ? (
-                  <label className="grid gap-2">
-                    <span className={fieldLabelClassName}>{t('monitor.cronExpression')}</span>
-                    <Input value={draft.monitor.cronExpression || ''} onChange={e => setDraft(prev => ({ ...prev, monitor: { ...prev.monitor, cronExpression: e.target.value } }))} />
-                  </label>
-                ) : (
-                  <label className="grid gap-2">
-                    <span className={fieldLabelClassName}>{t('monitor.intervals')}</span>
-                    <NumberStepper
-                      name="intervals"
-                      min="1"
-                      step="1"
-                      data-monitor-interval-stepper="cold-number-stepper"
-                      value={String(draft.monitor.intervals ?? 60)}
-                      onValueChange={value => setDraft(prev => ({ ...prev, monitor: { ...prev.monitor, intervals: value === '' ? null : Number(value) } }))}
-                    />
-                  </label>
-                )}
-              </div>
-            </SurfaceSection>
-
-            <SurfaceSection title={t('monitor.editor.section.params')} copy={t('monitor.editor.section.params.copy')}>
-              <div className="grid gap-3 md:grid-cols-2">
-                {draft.paramDefines.map((define, index) => (
-                  draft.params[index]?.display === false ? null : <MonitorParamField key={`param-${define.field}`} field={draft.params[index] || {}} define={define} locale={locale} onChange={value => updateParam('params', index, value)} />
-                ))}
-                {draft.scrapeParamDefines.map((define, index) => (
-                  draft.scrapeParams[index]?.display === false ? null : <MonitorParamField key={`scrape-${define.field}`} field={draft.scrapeParams[index] || {}} define={define} locale={locale} onChange={value => updateParam('scrapeParams', index, value)} />
+              <ChevronDown
+                aria-hidden="true"
+                className={`h-3.5 w-3.5 transition-transform ${advancedOpen ? 'rotate-180' : ''}`}
+              />
+              {t('monitor.advanced')}
+            </HzButton>
+            {advancedOpen ? (
+              <HzMonitorEditorFieldGrid
+                id="monitor-editor-advanced-params"
+                className="mt-3"
+                data-monitor-editor-field-grid-owner="hertzbeat-ui-monitor-editor-field-grid"
+                data-monitor-editor-advanced-fields="expanded"
+              >
+                {draft.advancedParamDefines.map((define, index) => (
+                  draft.advancedParams[index]?.display === false ? null : <MonitorParamField key={`advanced-${define.field}`} field={draft.advancedParams[index] || {}} define={define} locale={locale} t={t} onChange={value => updateParam('advancedParams', index, value)} />
                 ))}
-              </div>
-              {scrapeLoading ? <div className="text-sm text-[var(--ops-text-secondary)]">{t('common.loading')}</div> : null}
-            </SurfaceSection>
-
-            {draft.advancedParamDefines.length > 0 ? (
-              <SurfaceSection title={t('monitor.advanced')} copy={t('monitor.editor.section.advanced.copy')}>
-                <div className="grid gap-3 md:grid-cols-2">
-                  {draft.advancedParamDefines.map((define, index) => (
-                    draft.advancedParams[index]?.display === false ? null : <MonitorParamField key={`advanced-${define.field}`} field={draft.advancedParams[index] || {}} define={define} locale={locale} onChange={value => updateParam('advancedParams', index, value)} />
-                  ))}
-                </div>
-              </SurfaceSection>
+              </HzMonitorEditorFieldGrid>
             ) : null}
-          </div>
-        }
-        side={
-          <>
-            <SurfaceSection title={t('common.description')} copy={t('monitor.description.tip')}>
-              <Textarea
-                data-monitor-description-textarea="cold-textarea"
-                className="min-h-[120px]"
-                value={draft.monitor.description || ''}
-                onChange={e => setDraft(prev => ({ ...prev, monitor: { ...prev.monitor, description: e.target.value } }))}
-              />
-            </SurfaceSection>
-            <SurfaceSection title={t('label.bind')} copy={t('monitor.editor.labels.copy')}>
-              <KeyValueEditor
-                title={t('label.bind')}
-                rows={labelRows}
-                onChange={setLabelRows}
-                addLabel={t('monitor.editor.labels.add')}
-                removeLabel={t('monitor.editor.labels.remove')}
-                keyPlaceholder={t('monitor.editor.labels.key')}
-                valuePlaceholder={t('monitor.editor.labels.value')}
+          </HzMonitorEditorSection>
+        ) : null}
+
+        <HzMonitorEditorSection
+          data-monitor-editor-section-owner="hertzbeat-ui-editor-section"
+          data-monitor-editor-runtime-order="angular-after-advanced-before-labels"
+          title={t('monitor.editor.section.runtime')}
+        >
+          <HzMonitorEditorFieldGrid data-monitor-editor-field-grid-owner="hertzbeat-ui-monitor-editor-field-grid">
+            <HzField
+              label={t('monitor.collector')}
+              data-monitor-editor-field-owner="hertzbeat-ui-field"
+              data-monitor-editor-collector-selection="angular-collectors-selection-tags"
+            >
+              <HzSelect
+                value={draft.collector || ''}
+                aria-label={t('monitor.collector')}
+                options={collectorOptions}
+                data-monitor-editor-select="collector"
+                data-monitor-editor-select-owner="hertzbeat-ui-select"
+                data-monitor-editor-collector-selection-owner="hertzbeat-ui-select"
+                optionDataAttributes={option => ({
+                  'data-monitor-editor-collector-option': option.value || 'system-default',
+                  'data-monitor-editor-collector-option-contract': 'angular-status-ip-mode-tags'
+                })}
+                onChange={e => setDraft(prev => ({ ...prev, collector: e.target.value }))}
               />
-            </SurfaceSection>
-            <SurfaceSection title={t('common.annotation.bind')} copy={t('monitor.editor.annotations.copy')}>
-              <KeyValueEditor
-                title={t('common.annotation.bind')}
-                rows={annotationRows}
-                onChange={setAnnotationRows}
-                addLabel={t('monitor.editor.annotations.add')}
-                removeLabel={t('monitor.editor.annotations.remove')}
-                keyPlaceholder={t('monitor.editor.annotations.key')}
-                valuePlaceholder={t('monitor.editor.annotations.value')}
+              <div
+                className="mt-2 flex min-h-6 flex-wrap items-center gap-1.5"
+                data-monitor-editor-collector-tags="angular-status-ip-mode-tags"
+              >
+                {selectedCollectorChoice ? (
+                  <>
+                    <span className="border border-[#263552] bg-[#10203a] px-2 py-0.5 text-[10px] font-semibold text-[#d8e4ff]" data-monitor-editor-collector-tag="status">
+                      {collectorStatusLabel(selectedCollectorChoice, t)}
+                    </span>
+                    <span className="border border-[#263552] bg-[#10203a] px-2 py-0.5 text-[10px] font-semibold text-[#d8e4ff]" data-monitor-editor-collector-tag="name">
+                      {selectedCollectorChoice.name}
+                    </span>
+                    {selectedCollectorChoice.ip ? (
+                      <span className="border border-[#263552] bg-[#10203a] px-2 py-0.5 text-[10px] font-semibold text-[#d8e4ff]" data-monitor-editor-collector-tag="ip">
+                        {selectedCollectorChoice.ip}
+                      </span>
+                    ) : null}
+                    <span className="border border-[#263552] bg-[#10203a] px-2 py-0.5 text-[10px] font-semibold text-[#d8e4ff]" data-monitor-editor-collector-tag="mode">
+                      {collectorModeLabel(selectedCollectorChoice.mode, t)}
+                    </span>
+                  </>
+                ) : (
+                  <>
+                    <span className="border border-[#263552] bg-[#10203a] px-2 py-0.5 text-[10px] font-semibold text-[#d8e4ff]" data-monitor-editor-collector-tag="system-default">
+                      {systemDefaultCollectorLabel}
+                    </span>
+                    <span className="border border-[#1f4537] bg-[#0d2a20] px-2 py-0.5 text-[10px] font-semibold text-[#b7f7d0]" data-monitor-editor-collector-tag="mode">
+                      {defaultCollectorModeLabel}
+                    </span>
+                  </>
+                )}
+              </div>
+            </HzField>
+            <HzField label={t('monitor.scheduleType')} data-monitor-editor-field-owner="hertzbeat-ui-field">
+              <HzSelect
+                value={draft.monitor.scheduleType || 'interval'}
+                aria-label={t('monitor.scheduleType')}
+                options={scheduleTypeOptions}
+                data-monitor-editor-select="schedule-type"
+                data-monitor-editor-select-owner="hertzbeat-ui-select"
+                onChange={e => setDraft(prev => ({ ...prev, monitor: { ...prev.monitor, scheduleType: e.target.value } }))}
               />
-            </SurfaceSection>
-            {draft.monitor.app === 'prometheus' ? (
-              <SurfaceSection title={t('monitor.grafana.enabled.label')} copy={t('monitor.grafana.upload.tip')}>
-                <Checkbox
-                  containerClassName="mb-3"
-                  data-monitor-grafana-enabled-checkbox="cold-checkbox"
-                  checked={Boolean(draft.grafanaDashboard.enabled)}
-                  onChange={e => setDraft(prev => ({ ...prev, grafanaDashboard: { ...prev.grafanaDashboard, enabled: e.target.checked } }))}
-                  label={t('monitor.grafana.enabled.label')}
+            </HzField>
+            {draft.monitor.scheduleType === 'cron' ? (
+              <HzField label={t('monitor.cronExpression')} data-monitor-editor-field-owner="hertzbeat-ui-field">
+                <HzInput
+                  value={draft.monitor.cronExpression || ''}
+                  required
+                  data-monitor-editor-input="cron-expression"
+                  data-monitor-editor-input-owner="hertzbeat-ui-input"
+                  data-monitor-editor-cron-required="angular-required-before-detect-save"
+                  onChange={e => setDraft(prev => ({ ...prev, monitor: { ...prev.monitor, cronExpression: e.target.value } }))}
                 />
-                {draft.grafanaDashboard.enabled ? (
-                  <ColdCodeEditor
-                    name="grafana_dashboard_template"
-                    value={grafanaTemplateText}
-                    language="json"
-                    minHeight="120px"
-                    ariaLabel={t('monitor.grafana.enabled.label')}
-                    data-monitor-grafana-code-editor="dashboard-template"
-                    onChange={setGrafanaTemplateText}
+              </HzField>
+            ) : (
+              <HzField
+                as="div"
+                label={t('monitor.intervals')}
+                data-monitor-editor-field-owner="hertzbeat-ui-field"
+                data-monitor-interval-stepper-contract="angular-min-step-max-by-app"
+              >
+                <HzNumberStepper
+                  name="intervals"
+                  min={intervalStepperBounds.min}
+                  max={intervalStepperBounds.max}
+                  step={intervalStepperBounds.step}
+                  data-monitor-interval-stepper="hertzbeat-ui-number-stepper"
+                  data-monitor-interval-stepper-min={String(intervalStepperBounds.min)}
+                  data-monitor-interval-stepper-max={String(intervalStepperBounds.max)}
+                  data-monitor-interval-stepper-step={String(intervalStepperBounds.step)}
+                  data-monitor-editor-number-stepper-owner="hertzbeat-ui-number-stepper"
+                  value={String(draft.monitor.intervals ?? 60)}
+                  decrementLabel={t('common.decrement')}
+                  incrementLabel={t('common.increment')}
+                  onValueChange={value => setDraft(prev => ({ ...prev, monitor: { ...prev.monitor, intervals: value === '' ? null : Number(value) } }))}
+                />
+                <span
+                  className="mt-1 inline-flex text-[10px] font-semibold text-[#7d8798]"
+                  data-monitor-interval-stepper-unit="common.time.unit.second"
+                >
+                  {t('common.time.unit.second')}
+                </span>
+              </HzField>
+            )}
+          </HzMonitorEditorFieldGrid>
+        </HzMonitorEditorSection>
+
+        <HzMonitorEditorSection data-monitor-editor-section-owner="hertzbeat-ui-editor-section" title={t('label.bind')}>
+          <div
+            data-monitor-editor-label-selector="angular-app-label-selector"
+            data-monitor-editor-label-selector-owner="cold-label-selector"
+          >
+            <LabelRecordInput
+              name="labels"
+              value={labelSelectorText}
+              onValueChange={setLabelSelectorText}
+              keyPlaceholder={t('monitor.editor.labels.key')}
+              valuePlaceholder={t('monitor.editor.labels.value')}
+              addLabel={t('monitor.editor.labels.add')}
+              removeLabel={t('monitor.editor.labels.remove')}
+              containerClassName="min-w-0"
+            />
+          </div>
+        </HzMonitorEditorSection>
+
+        <HzMonitorEditorSection data-monitor-editor-section-owner="hertzbeat-ui-editor-section" title={t('common.annotation.bind')}>
+          <HzKeyValueEditor
+            data-monitor-editor-key-value="annotations"
+            data-monitor-editor-key-value-owner="hertzbeat-ui-key-value-editor"
+            title={t('common.annotation.bind')}
+            rows={annotationRows}
+            onChange={setAnnotationRows}
+            addLabel={t('monitor.editor.annotations.add')}
+            removeLabel={t('monitor.editor.annotations.remove')}
+            keyPlaceholder={t('monitor.editor.annotations.key')}
+            valuePlaceholder={t('monitor.editor.annotations.value')}
+          />
+        </HzMonitorEditorSection>
+
+        <HzMonitorEditorSection data-monitor-editor-section-owner="hertzbeat-ui-editor-section" title={t('common.description')}>
+          <HzTextarea
+            data-monitor-description-textarea="hertzbeat-ui-textarea"
+            data-monitor-editor-textarea-owner="hertzbeat-ui-textarea"
+            data-monitor-description-textarea-limit="angular-textarea-limit-100"
+            height="tall"
+            maxCharacterCount={100}
+            value={draft.monitor.description || ''}
+            onChange={e => setDraft(prev => ({ ...prev, monitor: { ...prev.monitor, description: e.target.value } }))}
+          />
+        </HzMonitorEditorSection>
+
+        {draft.monitor.app === 'prometheus' ? (
+          <HzMonitorEditorSection data-monitor-editor-section-owner="hertzbeat-ui-editor-section" title={t('monitor.grafana.enabled.label')}>
+            <HzCheckbox
+              containerClassName="mb-3"
+              data-monitor-grafana-enabled-checkbox="hertzbeat-ui-checkbox"
+              data-monitor-editor-checkbox="grafana-dashboard-enabled"
+              data-monitor-editor-checkbox-owner="hertzbeat-ui-checkbox"
+              checked={Boolean(draft.grafanaDashboard.enabled)}
+              onChange={e => setDraft(prev => ({ ...prev, grafanaDashboard: { ...prev.grafanaDashboard, enabled: e.target.checked } }))}
+              label={t('monitor.grafana.enabled.label')}
+            />
+            {draft.grafanaDashboard.enabled ? (
+              <div
+                className="space-y-3"
+                data-monitor-grafana-template-input-contract="angular-json-template-upload"
+              >
+                <div
+                  className="flex flex-wrap items-center gap-2"
+                  data-monitor-grafana-template-upload="angular-json-template-upload"
+                >
+                  <HzFileInput
+                    ref={grafanaTemplateInputRef}
+                    accept=".json,application/json"
+                    aria-label={t('monitor.grafana.upload.label')}
+                    data-monitor-grafana-template-upload-owner="hertzbeat-ui-file-input"
+                    data-monitor-grafana-template-upload-input="json-template"
+                    onChange={event => void handleGrafanaTemplateUpload(event)}
                   />
-                ) : null}
-              </SurfaceSection>
+                  <HzButton
+                    type="button"
+                    size="sm"
+                    intent="secondary"
+                    data-monitor-grafana-template-upload-trigger-owner="hertzbeat-ui-button"
+                    data-monitor-grafana-template-upload-trigger="json-template"
+                    onClick={() => grafanaTemplateInputRef.current?.click()}
+                  >
+                    {t('monitor.grafana.upload.label')}
+                  </HzButton>
+                  <span className="text-xs text-[var(--hz-ui-text-muted)]">
+                    {t('monitor.grafana.upload.tip')}
+                  </span>
+                </div>
+                <HzCodeEditor
+                  name="grafana_dashboard_template"
+                  value={grafanaTemplateText}
+                  language="json"
+                  minHeight="120px"
+                  ariaLabel={t('monitor.grafana.enabled.label')}
+                  data-monitor-editor-code-editor-owner="hertzbeat-ui-code-editor"
+                  data-monitor-editor-code-editor="grafana-dashboard-template"
+                  data-monitor-grafana-code-editor="dashboard-template"
+                  onChange={setGrafanaTemplateText}
+                />
+              </div>
             ) : null}
-            <SurfaceSection title={t('monitor.editor.section.payload')} copy={t('monitor.editor.section.payload.copy')}>
-              <RowList
-                rows={[
-                  { title: t('common.app'), copy: draft.monitor.app || '-', meta: draft.monitor.scrape || 'static' },
-                  { title: t('monitor.name'), copy: draft.monitor.name || '-', meta: draft.collector || '-' }
-                ]}
-              />
-            </SurfaceSection>
-            {actionMessage ? <div className="text-sm text-emerald-300">{actionMessage}</div> : null}
-            {actionError ? <div className="text-sm text-rose-300">{actionError}</div> : null}
-          </>
-        }
-      />
-    </form>
+          </HzMonitorEditorSection>
+        ) : null}
+    </HzMonitorEditorForm>
   );
 }
diff --git a/web-next/lib/monitor-api-facade.test.ts b/web-next/lib/monitor-api-facade.test.ts
new file mode 100644
index 0000000000..572733772c
--- /dev/null
+++ b/web-next/lib/monitor-api-facade.test.ts
@@ -0,0 +1,76 @@
+import { afterEach, describe, expect, it, vi } from 'vitest';
+
+import { api } from './monitor-api-facade';
+
+const fetchMock = vi.fn<typeof fetch>();
+
+function mockApiMessagePayload(data: unknown) {
+  fetchMock.mockResolvedValueOnce(
+    new Response(JSON.stringify({ code: 0, data }), {
+      status: 200,
+      headers: { 'Content-Type': 'application/json' }
+    })
+  );
+}
+
+describe('monitor api facade', () => {
+  afterEach(() => {
+    fetchMock.mockReset();
+    vi.unstubAllGlobals();
+  });
+
+  it('maps monitor list reads through the monitor domain URL builder', async () => {
+    vi.stubGlobal('fetch', fetchMock);
+    mockApiMessagePayload({ content: [{ id: 42 }], totalElements: 1 });
+
+    await expect(
+      api.monitors.page({
+        search: ' mysql ',
+        app: 'mysql',
+        labels: '',
+        status: '2',
+        pageIndex: '0',
+        pageSize: '8',
+        entityId: '',
+        entityName: '',
+        returnTo: ''
+      })
+    ).resolves.toEqual({ content: [{ id: 42 }], totalElements: 1 });
+
+    expect(fetchMock).toHaveBeenCalledWith(
+      '/api/monitors?pageIndex=0&pageSize=8&search=mysql&app=mysql&status=2',
+      expect.objectContaining({ credentials: 'same-origin', cache: 'no-store' })
+    );
+  });
+
+  it('maps monitor editor draft reads through the monitor domain facade', async () => {
+    vi.stubGlobal('fetch', fetchMock);
+    mockApiMessagePayload({ monitor: { id: 42, app: 'mysql' }, params: [], collector: null });
+    mockApiMessagePayload({ content: [{ collector: { name: 'edge-a' } }] });
+    mockApiMessagePayload([{ field: 'host', type: 'text' }]);
+
+    await expect(api.monitors.editorDetail(42)).resolves.toEqual({
+      monitor: { id: 42, app: 'mysql' },
+      params: [],
+      collector: null
+    });
+    await expect(api.monitors.editorCollectors()).resolves.toEqual({ content: [{ collector: { name: 'edge-a' } }] });
+    await expect(api.monitors.editorParamDefines('mysql')).resolves.toEqual([{ field: 'host', type: 'text' }]);
+
+    expect(fetchMock).toHaveBeenNthCalledWith(1, '/api/monitor/42', expect.objectContaining({ credentials: 'same-origin' }));
+    expect(fetchMock).toHaveBeenNthCalledWith(2, '/api/collector', expect.objectContaining({ credentials: 'same-origin' }));
+    expect(fetchMock).toHaveBeenNthCalledWith(3, '/api/apps/mysql/params', expect.objectContaining({ credentials: 'same-origin' }));
+  });
+
+  it('maps monitor export downloads to response readers', async () => {
+    vi.stubGlobal('fetch', fetchMock);
+    fetchMock.mockResolvedValueOnce(new Response('zip', { status: 200 }));
+
+    await expect(api.monitors.exportResponse([42, 43], 'JSON')).resolves.toBeInstanceOf(Response);
+
+    expect(fetchMock).toHaveBeenCalledWith(
+      '/api/monitors/export?ids=42&ids=43&type=JSON',
+      expect.objectContaining({ credentials: 'same-origin', cache: 'no-store' })
+    );
+  });
+});
diff --git a/web-next/lib/monitor-api-facade.ts b/web-next/lib/monitor-api-facade.ts
new file mode 100644
index 0000000000..6a8db74281
--- /dev/null
+++ b/web-next/lib/monitor-api-facade.ts
@@ -0,0 +1,84 @@
+import { apiDownload, apiMessageDelete, apiMessageGet, apiMessagePost, apiMessagePut } from './api-client';
+import {
+  buildMonitorDetailUrl,
+  buildMonitorFavoriteUrl,
+  buildMonitorGrafanaUrl,
+  buildMonitorHistoryCatalogUrl,
+  buildMonitorHistoryMetricDataUrl,
+  buildMonitorRealtimeMetricUrl,
+  type MonitorHistoryCatalogDefineResponse,
+  type MonitorHistoryMetricCatalogItem
+} from './monitor-detail/controller';
+import {
+  buildMonitorEditorCollectorsUrl,
+  buildMonitorEditorMonitorUrl,
+  buildMonitorEditorParamDefinesUrl,
+  type MonitorDetailResponse
+} from './monitor-editor/controller';
+import {
+  buildCopyMonitorUrl,
+  buildDeleteMonitorsUrl,
+  buildEnableMonitorsUrl,
+  buildExportAllMonitorsUrl,
+  buildExportMonitorsUrl,
+  buildImportMonitorsUrl,
+  buildPauseMonitorsUrl
+} from './monitor-manage/controller';
+import { buildMonitorUrl, type MonitorQueryState } from './monitor-manage/query-state';
+import type { CollectorSummary, Monitor, MonitorHistoryData, PageResult, ParamDefine } from './types';
+
+type MonitorListQuery = string | URLSearchParams | Record<string, string | number | boolean | undefined | null>;
+
+function appendQuery(path: string, query?: MonitorListQuery) {
+  if (!query) return path;
+  const params =
+    typeof query === 'string'
+      ? new URLSearchParams(query.startsWith('?') ? query.slice(1) : query)
+      : query instanceof URLSearchParams
+        ? query
+        : new URLSearchParams(
+            Object.entries(query).flatMap(([key, value]) => (value == null || value === '' ? [] : [[key, String(value)]]))
+          );
+  const queryString = params.toString();
+  return queryString ? `${path}?${queryString}` : path;
+}
+
+export const api = {
+  monitors: {
+    list: <T = PageResult<Monitor>>(query?: MonitorListQuery) => apiMessageGet<T>(appendQuery('/monitors/manage', query)),
+    page: <T = PageResult<Monitor>>(query: MonitorQueryState) => apiMessageGet<T>(buildMonitorUrl(query)),
+    detail: <T = Monitor>(monitorId: string | number) => apiMessageGet<T>(buildMonitorDetailUrl(String(monitorId))),
+    editorDetail: <T = MonitorDetailResponse>(monitorId: string | number) =>
+      apiMessageGet<T>(buildMonitorEditorMonitorUrl(String(monitorId))),
+    editorCollectors: <T = { content?: CollectorSummary[] }>() => apiMessageGet<T>(buildMonitorEditorCollectorsUrl()),
+    editorParamDefines: (app: string) => apiMessageGet<ParamDefine[]>(buildMonitorEditorParamDefinesUrl(app)),
+    favoriteMetrics: (monitorId: string | number) => apiMessageGet<string[]>(buildMonitorFavoriteUrl(String(monitorId))),
+    addFavoriteMetric: (monitorId: string | number, metricName: string) =>
+      apiMessagePost<unknown>(buildMonitorFavoriteUrl(String(monitorId), metricName), null),
+    removeFavoriteMetric: (monitorId: string | number, metricName: string) =>
+      apiMessageDelete<unknown>(buildMonitorFavoriteUrl(String(monitorId), metricName)),
+    grafanaDashboard: <T = unknown>(monitorId: string | number) => apiMessageGet<T>(buildMonitorGrafanaUrl(String(monitorId))),
+    deleteGrafanaDashboard: (monitorId: string | number) => apiMessageDelete<unknown>(buildMonitorGrafanaUrl(String(monitorId))),
+    realtimeMetric: <T = unknown>(monitorId: string | number, metricName: string) =>
+      apiMessageGet<T>(buildMonitorRealtimeMetricUrl(String(monitorId), metricName)),
+    warehouseStorageStatus: <T = unknown>() => apiMessageGet<T>('/warehouse/storage/status'),
+    historyMetricCatalogDefine: (monitor: Monitor) =>
+      apiMessageGet<MonitorHistoryCatalogDefineResponse>(buildMonitorHistoryCatalogUrl(monitor)),
+    historyMetric: (monitor: Monitor, item: MonitorHistoryMetricCatalogItem, query?: Parameters<typeof buildMonitorHistoryMetricDataUrl>[2]) =>
+      apiMessageGet<MonitorHistoryData>(buildMonitorHistoryMetricDataUrl(monitor, item, query)),
+    copy: (monitorId: string | number) => apiMessagePost<unknown>(buildCopyMonitorUrl(monitorId), null),
+    enable: (ids: Array<string | number>) => apiMessageGet<unknown>(buildEnableMonitorsUrl(ids)),
+    pause: (ids: Array<string | number>) => apiMessageDelete<unknown>(buildPauseMonitorsUrl(ids)),
+    delete: (ids: Array<string | number>) => apiMessageDelete<unknown>(buildDeleteMonitorsUrl(ids)),
+    appHierarchy: <T = unknown>(locale?: string | null) =>
+      apiMessageGet<T>(`/apps/hierarchy${locale ? `?lang=${encodeURIComponent(locale)}` : ''}`),
+    create: <T = unknown>(payload: unknown) => apiMessagePost<T>('/monitor', payload),
+    update: <T = unknown>(payload: unknown) => apiMessagePut<T>('/monitor', payload),
+    detect: <T = unknown>(payload: unknown) => apiMessagePost<T>('/monitor/detect', payload),
+    import: <T = unknown>(body: FormData) => apiMessagePost<T>(buildImportMonitorsUrl(), body),
+    export: (ids: Array<string | number>, type: 'JSON' | 'EXCEL') => apiMessageGet<Blob>(buildExportMonitorsUrl(ids, type)),
+    exportAll: (type: 'JSON' | 'EXCEL') => apiMessageGet<Blob>(buildExportAllMonitorsUrl(type)),
+    exportResponse: (ids: Array<string | number>, type: 'JSON' | 'EXCEL') => apiDownload(buildExportMonitorsUrl(ids, type)),
+    exportAllResponse: (type: 'JSON' | 'EXCEL') => apiDownload(buildExportAllMonitorsUrl(type))
+  }
+};
diff --git a/web-next/lib/monitor-editor/controller.test.ts b/web-next/lib/monitor-editor/controller.test.ts
index d26ce61851..18166cf70e 100644
--- a/web-next/lib/monitor-editor/controller.test.ts
+++ b/web-next/lib/monitor-editor/controller.test.ts
@@ -1,20 +1,40 @@
 import { describe, expect, it, vi } from 'vitest';
 import { createTranslatorMock } from '../../test/i18n-test-helper';
 import {
+  applyMonitorHostNameAutofill,
+  buildMonitorEditCacheKey,
+  buildMonitorEditorCollectorsUrl,
+  buildMonitorEditorMonitorUrl,
+  buildMonitorEditorParamDefinesUrl,
   buildMonitorDetectPayload,
   buildMonitorSavePayload,
   createMonitor,
+  createMonitorFromFacade,
   detectMonitor,
+  detectMonitorFromFacade,
+  generateMonitorEditorReadableName,
   isValidMonitorCronExpression,
   loadMonitorEditorDraft,
+  loadMonitorEditorDraftFromFacade,
   loadMonitorScrapeDraft,
+  loadMonitorScrapeDraftFromFacade,
+  resolveMonitorEditorParamChangeNotice,
+  shouldPreserveMonitorScrapeParamsForLoad,
   syncMonitorDependentDisplay,
   updateMonitor,
+  updateMonitorFromFacade,
   updateMonitorEditorParam,
   validateMonitorEditorDraft
 } from './controller';
 
 describe('monitor editor controller', () => {
+  it('exposes stable editor resource URLs for workbench cache keys', () => {
+    expect(buildMonitorEditorCollectorsUrl()).toBe('/collector');
+    expect(buildMonitorEditorMonitorUrl('42')).toBe('/monitor/42');
+    expect(buildMonitorEditorParamDefinesUrl('website')).toBe('/apps/website/params');
+    expect(buildMonitorEditCacheKey(42)).toBe('monitor-editor-edit:/monitor/42:/collector');
+  });
+
   it('loads new monitor draft from app params and collectors', async () => {
     const apiGet = vi.fn()
       .mockResolvedValueOnce({ content: [{ collector: { name: 'edge-a' } }] })
@@ -23,11 +43,86 @@ describe('monitor editor controller', () => {
 
     await expect(loadMonitorEditorDraft(apiGet as any, 'new', { app: 'website' })).resolves.toMatchObject({
       monitor: { app: 'website', scrape: 'static', scheduleType: 'interval', intervals: 60 },
-      collectors: ['edge-a'],
+      collectors: [{ name: 'edge-a' }],
       params: [{ field: 'host', paramValue: '127.0.0.1' }]
     });
   });
 
+  it('loads monitor editor drafts from facade readers without exposing raw URLs to routes', async () => {
+    const readMonitorDetail = vi.fn(async () => ({
+      monitor: {
+        id: 42,
+        app: 'mysql',
+        name: 'mysql-prod',
+        instance: '127.0.0.1:3306',
+        scrape: 'static',
+        status: 2
+      },
+      params: [{ id: 25, field: 'host', paramValue: '127.0.0.1' }],
+      collector: 'edge-a',
+      grafanaDashboard: { enabled: true }
+    }));
+    const readCollectors = vi.fn(async () => ({
+      content: [
+        { collector: { name: 'main-default-collector' } },
+        { collector: { name: 'edge-a', ip: '10.0.0.12', status: 0, mode: 'private' } }
+      ]
+    }));
+    const readParamDefines = vi.fn(async (app: string) => (app === 'mysql' ? [{ field: 'host', type: 'text', defaultValue: '' }] : []));
+
+    await expect(
+      loadMonitorEditorDraftFromFacade(
+        {
+          readMonitorDetail,
+          readCollectors,
+          readParamDefines
+        },
+        'edit',
+        { monitorId: '42' }
+      )
+    ).resolves.toMatchObject({
+      monitor: { id: 42, app: 'mysql', scrape: 'static', scheduleType: 'interval', intervals: 60 },
+      collector: 'edge-a',
+      collectors: [{ name: 'edge-a', ip: '10.0.0.12', status: 0, mode: 'private' }],
+      params: [{ id: 25, field: 'host', paramValue: '127.0.0.1' }],
+      grafanaDashboard: { enabled: true }
+    });
+
+    expect(readMonitorDetail).toHaveBeenCalledWith('42');
+    expect(readCollectors).toHaveBeenCalledTimes(1);
+    expect(readParamDefines).toHaveBeenCalledWith('mysql');
+  });
+
+  it('maps the built-in main collector to system default dispatch instead of a pinned collector', async () => {
+    const apiGet = vi.fn()
+      .mockResolvedValueOnce({
+        monitor: {
+          id: 42,
+          app: 'mysql',
+          name: 'mysql-prod',
+          instance: '127.0.0.1:3306',
+          scrape: 'static',
+          status: 2
+        },
+        collector: 'main-default-collector',
+        params: [{ id: 25, field: 'host', paramValue: '127.0.0.1' }]
+      })
+      .mockResolvedValueOnce({
+        content: [
+          { collector: { name: 'main-default-collector' } },
+          { collector: { name: 'edge-a' } }
+        ]
+      })
+      .mockResolvedValueOnce([{ field: 'host', type: 'text', defaultValue: '' }])
+      .mockResolvedValueOnce([]);
+
+    await expect(loadMonitorEditorDraft(apiGet as any, 'edit', { monitorId: '42' })).resolves.toMatchObject({
+      collector: '',
+      collectors: [{ name: 'edge-a' }],
+      params: [{ id: 25, field: 'host', paramValue: '127.0.0.1' }]
+    });
+  });
+
   it('builds save/detect payloads and posts them', async () => {
     const draft = {
       monitor: { id: 1, app: 'website', name: 'demo', instance: '127.0.0.1', scrape: 'static', status: 0 },
@@ -61,6 +156,72 @@ describe('monitor editor controller', () => {
     expect(apiPost).toHaveBeenCalledWith('/monitor/detect', buildMonitorDetectPayload(draft));
   });
 
+  it('submits save and detect payloads through facade writers', async () => {
+    const draft = {
+      monitor: { id: 1, app: 'website', name: 'demo', instance: '127.0.0.1', scrape: 'static', status: 0 },
+      collector: 'edge-a',
+      grafanaDashboard: { enabled: false },
+      params: [{ field: 'host', paramValue: '127.0.0.1' }],
+      advancedParams: [],
+      scrapeParams: []
+    } as any;
+    const writeCreate = vi.fn().mockResolvedValue(undefined);
+    const writeUpdate = vi.fn().mockResolvedValue(undefined);
+    const writeDetect = vi.fn().mockResolvedValue(undefined);
+
+    await createMonitorFromFacade(writeCreate, draft);
+    await updateMonitorFromFacade(writeUpdate, draft);
+    await detectMonitorFromFacade(writeDetect, draft);
+
+    expect(writeCreate).toHaveBeenCalledWith(buildMonitorSavePayload(draft));
+    expect(writeUpdate).toHaveBeenCalledWith(buildMonitorSavePayload(draft));
+    expect(writeDetect).toHaveBeenCalledWith(buildMonitorDetectPayload(draft));
+  });
+
+  it('matches Angular new-monitor host-change auto name behavior without touching edit mode', () => {
+    const draft = {
+      monitor: { id: 0, app: 'website', name: '', instance: '', scrape: 'static', status: 0 },
+      collector: '',
+      grafanaDashboard: { enabled: false },
+      params: [{ field: 'host', paramValue: 'example.com' }],
+      paramDefines: [{ field: 'host', type: 'text' }],
+      advancedParams: [],
+      advancedParamDefines: [],
+      scrapeParams: [],
+      scrapeParamDefines: [],
+      collectors: []
+    } as any;
+
+    expect(
+      applyMonitorHostNameAutofill(draft, {
+        mode: 'new',
+        field: 'host',
+        generateName: () => 'Bright_Probe_23AB'
+      }).monitor.name
+    ).toBe('Bright_Probe_23AB');
+
+    expect(
+      applyMonitorHostNameAutofill(
+        { ...draft, monitor: { ...draft.monitor, name: 'operator-name' } },
+        {
+          mode: 'new',
+          field: 'host',
+          generateName: () => 'Bright_Probe_23AB'
+        }
+      ).monitor.name
+    ).toBe('operator-name');
+
+    expect(
+      applyMonitorHostNameAutofill(draft, {
+        mode: 'edit',
+        field: 'host',
+        generateName: () => 'Bright_Probe_23AB'
+      }).monitor.name
+    ).toBe('');
+
+    expect(generateMonitorEditorReadableName(() => 0)).toBe('Quick_Probe_22AA');
+  });
+
   it('preserves existing param ids in update payloads', () => {
     const draft = {
       monitor: { id: 42, app: 'website', name: 'demo', instance: 'example.com:80', scrape: 'static', status: 1 },
@@ -76,7 +237,7 @@ describe('monitor editor controller', () => {
 
     expect(buildMonitorSavePayload(draft)).toEqual({
       monitor: { ...draft.monitor, instance: 'example.com' },
-      collector: '',
+      collector: null,
       params: [
         { id: 1, field: 'host', paramValue: 'example.com', type: 0 },
         { id: 2, field: 'port', paramValue: '80', type: 0 }
@@ -93,6 +254,17 @@ describe('monitor editor controller', () => {
     });
   });
 
+  it('loads dynamic scrape draft params from a facade param-define reader', async () => {
+    const readParamDefines = vi.fn(async () => [{ field: 'service', type: 'text', defaultValue: 'catalog' }]);
+
+    await expect(loadMonitorScrapeDraftFromFacade(readParamDefines, 'http_sd')).resolves.toEqual({
+      scrapeParams: [{ field: 'service', type: 1, paramValue: 'catalog' }],
+      scrapeParamDefines: [{ field: 'service', type: 'text', defaultValue: 'catalog' }]
+    });
+
+    expect(readParamDefines).toHaveBeenCalledWith('http_sd');
+  });
+
   it('preserves existing scrape param values on reload', async () => {
     const apiGet = vi.fn().mockResolvedValue([{ field: 'service', type: 'text', defaultValue: 'catalog' }]);
     await expect(loadMonitorScrapeDraft(apiGet as any, 'http_sd', [{ field: 'service', paramValue: 'inventory' } as any])).resolves.toEqual({
@@ -101,6 +273,14 @@ describe('monitor editor controller', () => {
     });
   });
 
+  it('matches Angular scrape reload lifecycle by preserving only the initial edit load', () => {
+    expect(shouldPreserveMonitorScrapeParamsForLoad(null, 'http_sd')).toBe(true);
+    expect(shouldPreserveMonitorScrapeParamsForLoad(undefined, 'nacos_sd')).toBe(true);
+    expect(shouldPreserveMonitorScrapeParamsForLoad(null, 'static')).toBe(false);
+    expect(shouldPreserveMonitorScrapeParamsForLoad('http_sd', 'dns_sd')).toBe(false);
+    expect(shouldPreserveMonitorScrapeParamsForLoad('static', 'http_sd')).toBe(false);
+  });
+
   it('syncs dependent visibility and boolean side effects', () => {
     const draft = {
       monitor: { app: 'api' },
@@ -126,6 +306,36 @@ describe('monitor editor controller', () => {
     expect(next.params[2].display).toBe(true);
   });
 
+  it('resolves Angular ssl port auto-change notification keys', () => {
+    const apiDraft = {
+      monitor: { app: 'api' },
+      params: [
+        { field: 'ssl', paramValue: false },
+        { field: 'port', paramValue: 80 }
+      ],
+      advancedParams: [],
+      scrapeParams: []
+    } as any;
+    const ftpDraft = {
+      ...apiDraft,
+      monitor: { app: 'ftp' },
+      params: [
+        { field: 'ssl', paramValue: false },
+        { field: 'port', paramValue: 21 }
+      ]
+    } as any;
+
+    expect(resolveMonitorEditorParamChangeNotice(apiDraft, 'params', 0, true)).toBe('monitor.new.notify.change-to-https');
+    expect(resolveMonitorEditorParamChangeNotice({ ...apiDraft, params: [{ field: 'ssl' }, { field: 'port', paramValue: 443 }] } as any, 'params', 0, false)).toBe(
+      'monitor.new.notify.change-to-http'
+    );
+    expect(resolveMonitorEditorParamChangeNotice(ftpDraft, 'params', 0, true)).toBe('monitor.new.notify.change-to-sftp');
+    expect(resolveMonitorEditorParamChangeNotice({ ...ftpDraft, params: [{ field: 'ssl' }, { field: 'port', paramValue: 22 }] } as any, 'params', 0, false)).toBe(
+      'monitor.new.notify.change-to-ftp'
+    );
+    expect(resolveMonitorEditorParamChangeNotice({ ...apiDraft, monitor: { app: 'website' } } as any, 'params', 0, true)).toBeNull();
+  });
+
   it('trims monitor and param strings in save payloads', () => {
     const draft = {
       monitor: { id: 42, app: 'website', name: '  demo monitor  ', instance: 'example.com:80', scrape: 'static', status: 1, description: '  desc  ' },
@@ -141,7 +351,7 @@ describe('monitor editor controller', () => {
 
     expect(buildMonitorSavePayload(draft)).toEqual({
       monitor: { ...draft.monitor, name: 'demo monitor', description: 'desc', instance: 'example.com' },
-      collector: '',
+      collector: null,
       params: [
         { id: 1, field: 'host', paramValue: 'example.com', type: 1 },
         { id: 2, field: 'uri', paramValue: '/health', type: 1 }
@@ -150,6 +360,66 @@ describe('monitor editor controller', () => {
     });
   });
 
+  it('matches Angular detect/save payload shape for merged params, host instance, and Grafana', () => {
+    const draft = {
+      monitor: {
+        id: 42,
+        app: 'prometheus',
+        name: '  prometheus monitor  ',
+        instance: 'prometheus:9090',
+        scrape: 'http_sd',
+        scheduleType: 'interval',
+        intervals: 60,
+        status: 1
+      },
+      collector: ' edge-a ',
+      grafanaDashboard: { enabled: true, template: '{"panels":[]}' },
+      params: [
+        { id: 1, field: 'host', paramValue: ' prometheus.local ', type: 1 },
+        { id: 2, field: 'port', paramValue: ' 9090 ', type: 1 }
+      ],
+      paramDefines: [
+        { field: 'host', type: 'text' },
+        { field: 'port', type: 'text' }
+      ],
+      advancedParams: [{ id: 3, field: 'timeout', paramValue: ' 3 ', type: 1 }],
+      advancedParamDefines: [{ field: 'timeout', type: 'text' }],
+      scrapeParams: [{ id: 4, field: 'sd_url', paramValue: ' https://sd.local/targets ', type: 1 }],
+      scrapeParamDefines: [{ field: 'sd_url', type: 'text' }]
+    } as any;
+
+    expect(buildMonitorDetectPayload(draft)).toEqual({
+      monitor: {
+        ...draft.monitor,
+        name: 'prometheus monitor',
+        instance: 'prometheus.local'
+      },
+      collector: 'edge-a',
+      params: [
+        { id: 1, field: 'host', paramValue: 'prometheus.local', type: 1 },
+        { id: 2, field: 'port', paramValue: '9090', type: 1 },
+        { id: 3, field: 'timeout', paramValue: '3', type: 1 },
+        { id: 4, field: 'sd_url', paramValue: 'https://sd.local/targets', type: 1 }
+      ]
+    });
+    expect(buildMonitorDetectPayload(draft)).not.toHaveProperty('grafanaDashboard');
+    expect(buildMonitorSavePayload(draft)).toEqual({
+      monitor: {
+        ...draft.monitor,
+        name: 'prometheus monitor',
+        instance: 'prometheus.local'
+      },
+      collector: 'edge-a',
+      params: [
+        { id: 1, field: 'host', paramValue: 'prometheus.local', type: 1 },
+        { id: 2, field: 'port', paramValue: '9090', type: 1 },
+        { id: 3, field: 'timeout', paramValue: '3', type: 1 },
+        { id: 4, field: 'sd_url', paramValue: 'https://sd.local/targets', type: 1 }
+      ],
+      grafanaDashboard: { enabled: true, template: '{"panels":[]}' }
+    });
+  });
+
   it('validates required fields and cron expressions before detect/save', () => {
     const t = createTranslatorMock();
     const draft = {
@@ -184,6 +454,46 @@ describe('monitor editor controller', () => {
 
     draft.params[0].paramValue = 'example.com';
     expect(validateMonitorEditorDraft(draft, t)).toBe('Cron expression is invalid');
+    expect(validateMonitorEditorDraft(draft, t, { validateCronFormat: false })).toBeNull();
+
+    draft.monitor.cronExpression = '   ';
+    expect(validateMonitorEditorDraft(draft, t, { validateCronFormat: false })).toBe('Cron expression is invalid');
+  });
+
+  it('localizes monitor editor validation copy in zh-CN', () => {
+    const t = createTranslatorMock({ locale: 'zh-CN' });
+    const draft = {
+      monitor: {
+        id: 0,
+        app: 'website',
+        name: '   ',
+        instance: '',
+        scrape: 'static',
+        status: 0,
+        scheduleType: 'cron',
+        cronExpression: 'bad cron'
+      },
+      params: [
+        { field: 'host', paramValue: '   ', display: true },
+        { field: 'port', paramValue: 80, display: true }
+      ],
+      paramDefines: [
+        { field: 'host', type: 'text', required: true, name: 'Host' },
+        { field: 'port', type: 'number', required: true, name: 'Port' }
+      ],
+      advancedParams: [],
+      advancedParamDefines: [],
+      scrapeParams: [],
+      scrapeParamDefines: []
+    } as any;
+
+    expect(validateMonitorEditorDraft(draft, t)).toBe('请输入监控名称');
+
+    draft.monitor.name = 'demo';
+    expect(validateMonitorEditorDraft(draft, t)).toBe('请填写 Host');
+
+    draft.params[0].paramValue = 'example.com';
+    expect(validateMonitorEditorDraft(draft, t)).toBe('Cron 表达式不合法');
   });
 
   it('accepts common five-field and six-field cron expressions', () => {
diff --git a/web-next/lib/monitor-editor/controller.ts b/web-next/lib/monitor-editor/controller.ts
index 782d5a7654..8d7d8f369a 100644
--- a/web-next/lib/monitor-editor/controller.ts
+++ b/web-next/lib/monitor-editor/controller.ts
@@ -16,7 +16,7 @@ export type MonitorEditorDraft = {
   advancedParamDefines: ParamDefine[];
   scrapeParams: Param[];
   scrapeParamDefines: ParamDefine[];
-  collectors: string[];
+  collectors: Array<MonitorCollectorOption | string>;
 };
 
 export type MonitorScrapeDraft = {
@@ -24,16 +24,86 @@ export type MonitorScrapeDraft = {
   scrapeParamDefines: ParamDefine[];
 };
 
+export type MonitorCollectorOption = {
+  name: string;
+  ip?: string;
+  status?: string | number;
+  online?: boolean;
+  mode?: string | null;
+};
+
 type ParamCollectionKind = 'params' | 'advancedParams' | 'scrapeParams';
 type Translator = (key: string, params?: Record<string, string | number | null | undefined>) => string;
 
-type MonitorDetailResponse = {
+export type MonitorDetailResponse = {
   monitor: Monitor;
   params?: Param[];
   collector?: string | null;
   grafanaDashboard?: GrafanaDashboard;
 };
 
+export type MonitorEditorDraftReaders = {
+  readMonitorDetail?: (monitorId: string) => Promise<MonitorDetailResponse>;
+  readCollectors: () => Promise<{ content?: CollectorSummary[] }>;
+  readParamDefines: (app: string) => Promise<ParamDefine[]>;
+};
+
+const SYSTEM_DEFAULT_COLLECTOR = 'main-default-collector';
+const MONITOR_NAME_ADJECTIVES = ['quick', 'bright', 'calm', 'brave', 'cool', 'eager', 'gentle', 'lively'];
+const MONITOR_NAME_NOUNS = ['probe', 'service', 'signal', 'watch', 'beacon', 'node', 'pulse', 'guard'];
+const MONITOR_NAME_DIGITS = '23456789';
+const MONITOR_NAME_CHARS = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz';
+
+function isSystemDefaultCollector(value?: string | null) {
+  return value === SYSTEM_DEFAULT_COLLECTOR;
+}
+
+function normalizeCollectorForDraft(value?: string | null) {
+  return isSystemDefaultCollector(value) ? '' : value || '';
+}
+
+function normalizeCollectorForPayload(value?: string | null) {
+  const normalized = value?.trim();
+  return normalized && !isSystemDefaultCollector(normalized) ? normalized : null;
+}
+
+function pickRandomToken(values: string[], random: () => number) {
+  return values[Math.floor(random() * values.length)] || values[0];
+}
+
+function capitalizeMonitorNamePart(value: string) {
+  return value.charAt(0).toUpperCase() + value.slice(1);
+}
+
+export function generateMonitorEditorReadableName(random: () => number = Math.random) {
+  const adjective = capitalizeMonitorNamePart(pickRandomToken(MONITOR_NAME_ADJECTIVES, random));
+  const noun = capitalizeMonitorNamePart(pickRandomToken(MONITOR_NAME_NOUNS, random));
+  const randomDigits = Array.from({ length: 2 }, () => MONITOR_NAME_DIGITS.charAt(Math.floor(random() * MONITOR_NAME_DIGITS.length))).join('');
+  const randomChars = Array.from({ length: 2 }, () => MONITOR_NAME_CHARS.charAt(Math.floor(random() * MONITOR_NAME_CHARS.length))).join('');
+  return `${adjective}_${noun}_${randomDigits}${randomChars}`;
+}
+
+export function applyMonitorHostNameAutofill(
+  draft: MonitorEditorDraft,
+  {
+    mode,
+    field,
+    generateName = generateMonitorEditorReadableName
+  }: { mode: MonitorEditorMode; field?: string; generateName?: () => string }
+) {
+  if (mode !== 'new' || field !== 'host' || draft.monitor.name?.trim()) {
+    return draft;
+  }
+
+  return {
+    ...draft,
+    monitor: {
+      ...draft.monitor,
+      name: generateName()
+    }
+  };
+}
+
 function paramType(type?: string) {
   if (type === 'number') return 0;
   if (type === 'key-value') return 3;
@@ -126,7 +196,8 @@ export function isValidMonitorCronExpression(cronExpression: string): boolean {
   return cronRegex.test(cronExpression);
 }
 
-export function validateMonitorEditorDraft(draft: MonitorEditorDraft, t: Translator) {
+export function validateMonitorEditorDraft(draft: MonitorEditorDraft, t: Translator, options: { validateCronFormat?: boolean } = {}) {
+  const validateCronFormat = options.validateCronFormat !== false;
   const normalizedMonitor = normalizeMonitorFields(draft.monitor);
   if (!normalizedMonitor.app?.trim()) return t('monitor.editor.validation.app');
   if (!normalizedMonitor.name?.trim()) return t('monitor.editor.validation.name');
@@ -151,7 +222,8 @@ export function validateMonitorEditorDraft(draft: MonitorEditorDraft, t: Transla
   }
 
   if (normalizedMonitor.scheduleType === 'cron') {
-    if (!isValidMonitorCronExpression(normalizedMonitor.cronExpression || '')) {
+    const cronExpression = normalizedMonitor.cronExpression || '';
+    if (!cronExpression.trim() || (validateCronFormat && !isValidMonitorCronExpression(cronExpression))) {
       return t('monitor.editor.validation.cron');
     }
   } else if (normalizedMonitor.intervals == null || !Number.isFinite(Number(normalizedMonitor.intervals)) || Number(normalizedMonitor.intervals) <= 0) {
@@ -195,23 +267,60 @@ function mergeParamDrafts(defines: ParamDefine[], paramMap: Map<string, Param>)
   };
 }
 
+export function buildMonitorEditorParamDefinesUrl(app: string) {
+  return `/apps/${app}/params`;
+}
+
+export function buildMonitorEditorCollectorsUrl() {
+  return '/collector';
+}
+
+export function buildMonitorEditCacheKey(monitorId: number | string) {
+  return ['monitor-editor-edit', buildMonitorEditorMonitorUrl(String(monitorId)), buildMonitorEditorCollectorsUrl()].join(':');
+}
+
+export function buildMonitorEditorMonitorUrl(monitorId: string) {
+  return `/monitor/${monitorId}`;
+}
+
 async function loadParamDefines(apiGet: ApiGetter, app: string) {
-  return apiGet<ParamDefine[]>(`/apps/${app}/params`);
+  return apiGet<ParamDefine[]>(buildMonitorEditorParamDefinesUrl(app));
 }
 
 async function loadCollectors(apiGet: ApiGetter) {
-  const response = await apiGet<{ content?: CollectorSummary[] }>('/collector');
-  return (response.content || []).map(item => item.collector?.name).filter((value): value is string => Boolean(value));
+  const response = await apiGet<{ content?: CollectorSummary[] }>(buildMonitorEditorCollectorsUrl());
+  return normalizeCollectorSummaries(response);
+}
+
+function normalizeCollectorSummaries(response: { content?: CollectorSummary[] }) {
+  return (response.content || [])
+    .map(item => item.collector)
+    .filter((collector): collector is NonNullable<CollectorSummary['collector']> => Boolean(collector?.name) && !isSystemDefaultCollector(collector.name))
+    .map(collector => ({
+      name: String(collector.name),
+      ip: collector.ip,
+      status: collector.status,
+      online: collector.online,
+      mode: collector.mode
+    }));
 }
 
 export async function loadMonitorScrapeDraft(apiGet: ApiGetter, scrape?: string | null, existingParams?: Param[]) {
+  return loadMonitorScrapeDraftFromFacade(app => loadParamDefines(apiGet, app), scrape, existingParams);
+}
+
+export async function loadMonitorScrapeDraftFromFacade(
+  readParamDefines: (app: string) => Promise<ParamDefine[]>,
+  scrape?: string | null,
+  existingParams?: Param[]
+) {
   if (!scrape || scrape === 'static') {
     return {
       scrapeParams: [],
       scrapeParamDefines: []
     } satisfies MonitorScrapeDraft;
   }
-  const defines = await loadParamDefines(apiGet, scrape);
+  const defines = await readParamDefines(scrape);
   const paramMap = new Map((existingParams || []).map(param => [String(param.field), param]));
   const merged = mergeParamDrafts(defines, paramMap);
   return {
@@ -220,6 +329,10 @@ export async function loadMonitorScrapeDraft(apiGet: ApiGetter, scrape?: string
   } satisfies MonitorScrapeDraft;
 }
 
+export function shouldPreserveMonitorScrapeParamsForLoad(previousScrape: string | null | undefined, nextScrape: string) {
+  return !previousScrape && nextScrape !== 'static';
+}
+
 function syncDependentCollection(defines: ParamDefine[], params: Param[], fieldValues: Map<string, unknown>) {
   return params.map((param, index) => {
     const define = defines[index];
@@ -270,7 +383,46 @@ export function updateMonitorEditorParam(draft: MonitorEditorDraft, kind: ParamC
   return syncMonitorDependentDisplay(nextDraft);
 }
 
+export function resolveMonitorEditorParamChangeNotice(draft: MonitorEditorDraft, kind: ParamCollectionKind, index: number, value: unknown) {
+  const changedField = String(draft[kind][index]?.field || '');
+  if (changedField !== 'ssl' || typeof value !== 'boolean') {
+    return null;
+  }
+
+  const portParam = draft.params.find(param => param.field === 'port');
+  if (!portParam) {
+    return null;
+  }
+
+  const current = Number(portParam.paramValue);
+  if (draft.monitor.app === 'api') {
+    if (value && (!Number.isFinite(current) || current === 80)) return 'monitor.new.notify.change-to-https';
+    if (!value && (!Number.isFinite(current) || current === 443)) return 'monitor.new.notify.change-to-http';
+  }
+  if (draft.monitor.app === 'ftp') {
+    if (value && (!Number.isFinite(current) || current === 21)) return 'monitor.new.notify.change-to-sftp';
+    if (!value && (!Number.isFinite(current) || current === 22)) return 'monitor.new.notify.change-to-ftp';
+  }
+  return null;
+}
+
 export async function loadMonitorEditorDraft(apiGet: ApiGetter, mode: MonitorEditorMode, options: { app?: string; monitorId?: string }) {
+  return loadMonitorEditorDraftFromFacade(
+    {
+      readMonitorDetail: monitorId => apiGet<MonitorDetailResponse>(buildMonitorEditorMonitorUrl(monitorId)),
+      readCollectors: () => apiGet<{ content?: CollectorSummary[] }>(buildMonitorEditorCollectorsUrl()),
+      readParamDefines: app => loadParamDefines(apiGet, app)
+    },
+    mode,
+    options
+  );
+}
+
+export async function loadMonitorEditorDraftFromFacade(
+  readers: MonitorEditorDraftReaders,
+  mode: MonitorEditorMode,
+  options: { app?: string; monitorId?: string }
+) {
   const baseMonitor: Monitor = {
     id: options.monitorId ? Number(options.monitorId) : 0,
     app: options.app || 'website',
@@ -290,7 +442,10 @@ export async function loadMonitorEditorDraft(apiGet: ApiGetter, mode: MonitorEdi
   let grafanaDashboard: GrafanaDashboard = { enabled: false };
 
   if (mode === 'edit' && options.monitorId) {
-    const detail = await apiGet<MonitorDetailResponse>(`/monitor/${options.monitorId}`);
+    if (!readers.readMonitorDetail) {
+      throw new Error('Monitor detail reader is required for edit drafts');
+    }
+    const detail = await readers.readMonitorDetail(options.monitorId);
     monitor = {
       ...baseMonitor,
       ...detail.monitor,
@@ -301,14 +456,14 @@ export async function loadMonitorEditorDraft(apiGet: ApiGetter, mode: MonitorEdi
       annotations: detail.monitor.annotations || {}
     };
     paramMap = new Map((detail.params || []).map(param => [String(param.field), param]));
-    collector = detail.collector || '';
+    collector = normalizeCollectorForDraft(detail.collector);
     grafanaDashboard = detail.grafanaDashboard || { enabled: false };
   }
 
   const [collectors, mainDefines, scrapeDefines] = await Promise.all([
-    loadCollectors(apiGet),
-    loadParamDefines(apiGet, monitor.app),
-    monitor.scrape && monitor.scrape !== 'static' ? loadParamDefines(apiGet, monitor.scrape) : Promise.resolve<ParamDefine[]>([])
+    readers.readCollectors().then(normalizeCollectorSummaries),
+    readers.readParamDefines(monitor.app),
+    monitor.scrape && monitor.scrape !== 'static' ? readers.readParamDefines(monitor.scrape) : Promise.resolve<ParamDefine[]>([])
   ]);
 
   const mergedMain = mergeParamDrafts(mainDefines, paramMap);
@@ -336,7 +491,7 @@ export function buildMonitorSavePayload(draft: MonitorEditorDraft) {
   ];
   return {
     monitor: normalizeMonitorInstanceForSave(normalizeMonitorFields(draft.monitor), params),
-    collector: draft.collector,
+    collector: normalizeCollectorForPayload(draft.collector),
     params,
     grafanaDashboard: draft.grafanaDashboard
   };
@@ -350,7 +505,7 @@ export function buildMonitorDetectPayload(draft: MonitorEditorDraft) {
   ];
   return {
     monitor: normalizeMonitorInstanceForSave(normalizeMonitorFields(draft.monitor), params),
-    collector: draft.collector,
+    collector: normalizeCollectorForPayload(draft.collector),
     params
   };
 }
@@ -359,10 +514,22 @@ export async function createMonitor(apiPost: ApiPoster, draft: MonitorEditorDraf
   return apiPost<unknown>('/monitor', buildMonitorSavePayload(draft));
 }
 
+export async function createMonitorFromFacade(writeCreateMonitor: (payload: unknown) => Promise<unknown>, draft: MonitorEditorDraft) {
+  return writeCreateMonitor(buildMonitorSavePayload(draft));
+}
+
 export async function updateMonitor(apiPut: ApiPutter, draft: MonitorEditorDraft) {
   return apiPut<unknown>('/monitor', buildMonitorSavePayload(draft));
 }
 
+export async function updateMonitorFromFacade(writeUpdateMonitor: (payload: unknown) => Promise<unknown>, draft: MonitorEditorDraft) {
+  return writeUpdateMonitor(buildMonitorSavePayload(draft));
+}
+
 export async function detectMonitor(apiPost: ApiPoster, draft: MonitorEditorDraft) {
   return apiPost<unknown>('/monitor/detect', buildMonitorDetectPayload(draft));
 }
+
+export async function detectMonitorFromFacade(writeDetectMonitor: (payload: unknown) => Promise<unknown>, draft: MonitorEditorDraft) {
+  return writeDetectMonitor(buildMonitorDetectPayload(draft));
+}
diff --git a/web-next/lib/monitor-editor/navigation.test.ts b/web-next/lib/monitor-editor/navigation.test.ts
index 129460febf..38528d8b67 100644
--- a/web-next/lib/monitor-editor/navigation.test.ts
+++ b/web-next/lib/monitor-editor/navigation.test.ts
@@ -1,5 +1,5 @@
 import { describe, expect, it } from 'vitest';
-import { buildMonitorEditorReturnUrl } from './navigation';
+import { buildMonitorEditorCancelUrl, buildMonitorEditorReturnUrl } from './navigation';
 
 describe('monitor editor navigation', () => {
   it('builds the filtered return path when app is present', () => {
@@ -26,4 +26,11 @@ describe('monitor editor navigation', () => {
       '/monitors?app=website&labels=team%3Dplatform&pageIndex=1&pageSize=20&entityId=42&entityName=Checkout+Service'
     );
   });
+
+  it('keeps Angular-style cancel navigation on the monitor list root unless a safe return target exists', () => {
+    expect(buildMonitorEditorCancelUrl()).toBe('/monitors');
+    expect(buildMonitorEditorCancelUrl({ labels: 'team=platform', pageIndex: '2' })).toBe('/monitors');
+    expect(buildMonitorEditorCancelUrl({ returnTo: '/entities/42?returnLabel=Checkout' })).toBe('/entities/42');
+    expect(buildMonitorEditorCancelUrl({ returnTo: '//evil.example', labels: 'team=platform' })).toBe('/monitors');
+  });
 });
diff --git a/web-next/lib/monitor-editor/navigation.ts b/web-next/lib/monitor-editor/navigation.ts
index 6c158b5c49..aed5932e31 100644
--- a/web-next/lib/monitor-editor/navigation.ts
+++ b/web-next/lib/monitor-editor/navigation.ts
@@ -8,3 +8,7 @@ export function buildMonitorEditorReturnUrl(app?: string | null, returnContext?:
     app
   });
 }
+
+export function buildMonitorEditorCancelUrl(returnContext?: MonitorEditorReturnContext | null) {
+  return buildMonitorListReturnHref(returnContext?.returnTo ? { returnTo: returnContext.returnTo } : undefined);
+}
diff --git a/web-next/lib/monitor-editor/query-state.test.ts b/web-next/lib/monitor-editor/query-state.test.ts
new file mode 100644
index 0000000000..63cafe58ae
--- /dev/null
+++ b/web-next/lib/monitor-editor/query-state.test.ts
@@ -0,0 +1,107 @@
+import { describe, expect, it } from 'vitest';
+import {
+  buildMonitorNewDefaultAppRedirectUrl,
+  hasMonitorNewAppParam,
+  readMonitorEditRouteState,
+  readMonitorNewRouteState
+} from './query-state';
+
+describe('monitor editor query state', () => {
+  it('normalizes multi-value URL search params into the first monitor new setup context value', () => {
+    expect(
+      readMonitorNewRouteState({
+        app: ['prometheus', 'website'],
+        labels: ['team=platform', 'team=ops'],
+        pageIndex: ['2', '0'],
+        pageSize: ['20', '8'],
+        entityId: ['42', '99'],
+        entityName: ['Checkout Service', 'Ignored Service'],
+        timeRange: ['custom', '1h'],
+        start: ['1715200000000', 'now-1h'],
+        end: ['1715203600000', 'now'],
+        refresh: ['30s', 'off'],
+        live: ['true', 'false'],
+        tz: ['UTC', 'Asia/Shanghai'],
+        returnTo: ['/entities/42?returnLabel=Checkout', '//evil.example']
+      })
+    ).toEqual({
+      app: 'prometheus',
+      returnContext: {
+        labels: 'team=platform',
+        pageIndex: '2',
+        pageSize: '20',
+        entityId: '42',
+        entityName: 'Checkout Service',
+        timeRange: 'custom',
+        start: '1715200000000',
+        end: '1715203600000',
+        refresh: '30s',
+        live: 'true',
+        tz: 'UTC',
+        returnTo: '/entities/42'
+      }
+    });
+  });
+
+  it('defaults to the website monitor type when setup route query values are absent', () => {
+    expect(readMonitorNewRouteState()).toEqual({
+      app: 'website',
+      returnContext: {
+        labels: null,
+        pageIndex: null,
+        pageSize: null,
+        entityId: null,
+        entityName: null,
+        timeRange: null,
+        start: null,
+        end: null,
+        refresh: null,
+        live: null,
+        tz: null,
+        returnTo: null
+      }
+    });
+  });
+
+  it('identifies whether the monitor new route already carries the Angular app selector', () => {
+    expect(hasMonitorNewAppParam({ app: 'website' })).toBe(true);
+    expect(hasMonitorNewAppParam({ app: ['prometheus', 'website'] })).toBe(true);
+    expect(hasMonitorNewAppParam()).toBe(false);
+    expect(hasMonitorNewAppParam({ app: '' })).toBe(false);
+    expect(buildMonitorNewDefaultAppRedirectUrl()).toBe('/monitors/new?app=website');
+  });
+
+  it('normalizes multi-value URL search params into the first monitor edit return context value', () => {
+    expect(
+      readMonitorEditRouteState({
+        labels: ['region=us-east', 'region=eu-west'],
+        pageIndex: ['3', '0'],
+        pageSize: ['50', '8'],
+        entityId: ['101', '202'],
+        entityName: ['Payment Gateway', 'Ignored Gateway'],
+        timeRange: ['custom', '6h'],
+        start: ['1715200000000', 'now-6h'],
+        end: ['1715203600000', 'now'],
+        refresh: ['60s', 'off'],
+        live: ['false', 'true'],
+        tz: ['Asia/Shanghai', 'UTC'],
+        returnTo: ['/monitors?app=website&returnLabel=Monitors', '//evil.example']
+      })
+    ).toEqual({
+      returnContext: {
+        labels: 'region=us-east',
+        pageIndex: '3',
+        pageSize: '50',
+        entityId: '101',
+        entityName: 'Payment Gateway',
+        timeRange: 'custom',
+        start: '1715200000000',
+        end: '1715203600000',
+        refresh: '60s',
+        live: 'false',
+        tz: 'Asia/Shanghai',
+        returnTo: '/monitors?app=website'
+      }
+    });
+  });
+});
diff --git a/web-next/lib/monitor-editor/query-state.ts b/web-next/lib/monitor-editor/query-state.ts
new file mode 100644
index 0000000000..643cd10276
--- /dev/null
+++ b/web-next/lib/monitor-editor/query-state.ts
@@ -0,0 +1,57 @@
+import { createCompatSearchParamReader, type SearchParamsRecord } from '../compat/search-params';
+import type { MonitorEditorReturnContext } from './navigation';
+
+export type MonitorNewSearchParams = SearchParamsRecord;
+export type MonitorEditSearchParams = SearchParamsRecord;
+
+export type MonitorNewRouteState = {
+  app: string;
+  returnContext: MonitorEditorReturnContext;
+};
+
+export type MonitorEditRouteState = {
+  returnContext: MonitorEditorReturnContext;
+};
+
+function readMonitorEditorReturnContext(searchParams: SearchParamsRecord = {}): MonitorEditorReturnContext {
+  const reader = createCompatSearchParamReader(searchParams);
+
+  return {
+    labels: reader.get('labels'),
+    pageIndex: reader.get('pageIndex'),
+    pageSize: reader.get('pageSize'),
+    entityId: reader.get('entityId'),
+    entityName: reader.get('entityName'),
+    timeRange: reader.get('timeRange'),
+    start: reader.get('start'),
+    end: reader.get('end'),
+    refresh: reader.get('refresh'),
+    live: reader.get('live'),
+    tz: reader.get('tz'),
+    returnTo: reader.get('returnTo')
+  };
+}
+
+export function readMonitorNewRouteState(searchParams: MonitorNewSearchParams = {}): MonitorNewRouteState {
+  const reader = createCompatSearchParamReader(searchParams);
+
+  return {
+    app: reader.get('app') || 'website',
+    returnContext: readMonitorEditorReturnContext(searchParams)
+  };
+}
+
+export function hasMonitorNewAppParam(searchParams: MonitorNewSearchParams = {}) {
+  const reader = createCompatSearchParamReader(searchParams);
+  return Boolean(reader.get('app'));
+}
+
+export function buildMonitorNewDefaultAppRedirectUrl() {
+  return '/monitors/new?app=website';
+}
+
+export function readMonitorEditRouteState(searchParams: MonitorEditSearchParams = {}): MonitorEditRouteState {
+  return {
+    returnContext: readMonitorEditorReturnContext(searchParams)
+  };
+}


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