diff --git a/docs/design/json-editor-standard.md b/docs/design/json-editor-standard.md index 2aab7ae8..656ce47e 100644 --- a/docs/design/json-editor-standard.md +++ b/docs/design/json-editor-standard.md @@ -1,10 +1,12 @@ # JSON Editor Standard -The dashboard has three JSON editing contexts: +The dashboard has four named JSON editing contexts: -- Create forms: edit the payload that the visual form will submit. -- Resource RAW editors: PATCH an existing resource and verify the saved state. -- API Console: build direct Admin API requests and inspect responses. +- Payload JSON: edit the create payload that the Visual Editor will submit. +- Admin API JSON: PATCH an existing resource and verify the saved state. +- Plugin JSON: edit one plugin config inside the plugin Fields drawer. +- Request JSON and Response JSON: build direct Admin API requests and inspect + responses in the API Console. These contexts have different actions, but they share one editing standard. @@ -20,12 +22,14 @@ These contexts have different actions, but they share one editing standard. ## Contextual Actions -- Create forms submit the complete create payload through the form workflow. -- Resource RAW editors show identity fields separately as values managed by the +- Payload JSON submits the complete create payload through the form workflow. +- Admin API JSON editors show identity fields separately as values managed by the Admin API path. The editable JSON excludes read-only fields, sends changed editable fields with PATCH, and verifies the saved resource with a follow-up read. -- API Console sends the selected method and payload exactly as configured. +- Plugin JSON edits only the selected plugin config object, not the full APISIX + resource payload. +- Request JSON sends the selected method and payload exactly as configured. - Read-only response editors use the same JSON presentation without editing controls. diff --git a/docs/en/getting-started.md b/docs/en/getting-started.md index 6f5ffd78..2e6b00ba 100644 --- a/docs/en/getting-started.md +++ b/docs/en/getting-started.md @@ -85,7 +85,7 @@ From this screen you can: * See the upstream or service target. * Review applied plugins without opening every route. * Toggle route status. -* Open the raw payload for quick inspection. +* Open the Admin API payload for quick inspection. Routes are still APISIX resources. The UI does not create dashboard-only fields; it presents the Admin API payload with friendlier controls. @@ -101,11 +101,12 @@ The detail page has three working modes: * **Overview**: scan the resource without editing it. * **Configuration**: update the resource through structured form controls. -* **Raw JSON**: edit the Admin API payload directly with schema guidance. +* **Admin API JSON**: patch the saved APISIX resource directly with schema guidance. -Use the structured form for routine edits and the Raw JSON editor when you need -to paste a known APISIX payload, inspect the exact object, or work with fields -that are easier to reason about as JSON. +Use the structured form for routine edits and the Admin API JSON editor when you +need to inspect or patch the saved APISIX object directly. When creating a new +resource, the **Payload JSON** tab edits the same draft payload that the visual +editor will validate and submit. ## Understand Traffic Relationships diff --git a/e2e/tests/admin-ux-helpers.spec.ts b/e2e/tests/admin-ux-helpers.spec.ts index 4cdab6d5..551a3e39 100644 --- a/e2e/tests/admin-ux-helpers.spec.ts +++ b/e2e/tests/admin-ux-helpers.spec.ts @@ -26,7 +26,7 @@ import { getResourceIdentityPaths, } from '@/utils/resourceJsonSchema'; -test('uses one resource schema standard across raw JSON surfaces', () => { +test('uses one resource schema standard across Admin API JSON surfaces', () => { expect(getResourceIdentityPaths('/consumers/alice')).toEqual(['username']); expect(getResourceIdentityPaths('/secrets/vault/demo')).toEqual(['manager', 'id']); @@ -44,7 +44,7 @@ test('uses one resource schema standard across raw JSON surfaces', () => { .toEqual([['uri'], ['uris']]); }); -test('keeps resource identity outside editable raw JSON', () => { +test('keeps resource identity outside editable Admin API JSON', () => { const resource = { id: 'route-1', create_time: 1, diff --git a/e2e/tests/api-console.spec.ts b/e2e/tests/api-console.spec.ts index 7a8ed9d6..5c8828f9 100644 --- a/e2e/tests/api-console.spec.ts +++ b/e2e/tests/api-console.spec.ts @@ -287,7 +287,7 @@ test('keeps blocked request body errors visible with recovery actions', async ({ }); await expect(requestBodyError).toBeVisible(); await expect( - requestBodyError.getByRole('button', { name: 'Format JSON' }) + requestBodyError.getByRole('button', { name: 'Format Request JSON' }) ).toBeVisible(); await expect( requestBodyError.getByRole('button', { name: 'Reset to template' }) diff --git a/e2e/tests/consumer_groups.crud-all-fields.spec.ts b/e2e/tests/consumer_groups.crud-all-fields.spec.ts index 69e7aeec..36eb997d 100644 --- a/e2e/tests/consumer_groups.crud-all-fields.spec.ts +++ b/e2e/tests/consumer_groups.crud-all-fields.spec.ts @@ -76,7 +76,7 @@ test('should CRUD Consumer Group with all fields', async ({ page }) => { const addPluginDialog = page.getByRole('dialog', { name: 'Add Plugin: basic-auth', }); - await addPluginDialog.getByRole('tab', { name: 'JSON' }).click(); + await addPluginDialog.getByRole('tab', { name: 'Plugin JSON' }).click(); const pluginEditor = await uiGetMonacoEditor(page, addPluginDialog); await uiFillMonacoEditor( page, diff --git a/e2e/tests/consumer_groups.crud-required-fields.spec.ts b/e2e/tests/consumer_groups.crud-required-fields.spec.ts index 7478b2e4..2f5f3439 100644 --- a/e2e/tests/consumer_groups.crud-required-fields.spec.ts +++ b/e2e/tests/consumer_groups.crud-required-fields.spec.ts @@ -70,7 +70,7 @@ test('should CRUD Consumer Group with required fields', async ({ page }) => { const addPluginDialog = page.getByRole('dialog', { name: 'Add Plugin: basic-auth', }); - await addPluginDialog.getByRole('tab', { name: 'JSON' }).click(); + await addPluginDialog.getByRole('tab', { name: 'Plugin JSON' }).click(); const pluginEditor = await uiGetMonacoEditor(page, addPluginDialog); await uiFillMonacoEditor(page, pluginEditor, '{"hide_credentials": true}'); diff --git a/e2e/tests/consumers.credentials.list.spec.ts b/e2e/tests/consumers.credentials.list.spec.ts index 9049e7cc..a1b74e09 100644 --- a/e2e/tests/consumers.credentials.list.spec.ts +++ b/e2e/tests/consumers.credentials.list.spec.ts @@ -292,7 +292,7 @@ test('should create credential from the UI', async ({ page }) => { const addPluginDialog = page.getByRole('dialog', { name: 'Add Plugin: key-auth', }); - await addPluginDialog.getByRole('tab', { name: 'JSON' }).click(); + await addPluginDialog.getByRole('tab', { name: 'Plugin JSON' }).click(); const pluginEditor = await uiGetMonacoEditor(page, addPluginDialog); await uiFillMonacoEditor( page, diff --git a/e2e/tests/global_rules.crud-all-fields.spec.ts b/e2e/tests/global_rules.crud-all-fields.spec.ts index d0341fff..30738127 100644 --- a/e2e/tests/global_rules.crud-all-fields.spec.ts +++ b/e2e/tests/global_rules.crud-all-fields.spec.ts @@ -67,7 +67,7 @@ test('should CRUD global rule with multiple plugins', async ({ page }) => { name: 'Add Plugin: response-rewrite', }); await expect(pluginDialog).toBeVisible(); - await pluginDialog.getByRole('tab', { name: 'JSON' }).click(); + await pluginDialog.getByRole('tab', { name: 'Plugin JSON' }).click(); // Configure response-rewrite with custom configuration using Monaco editor const pluginEditor = await uiGetMonacoEditor(page, pluginDialog); @@ -112,7 +112,7 @@ test('should CRUD global rule with multiple plugins', async ({ page }) => { name: 'Add Plugin: cors', }); await expect(corsPluginDialog).toBeVisible(); - await corsPluginDialog.getByRole('tab', { name: 'JSON' }).click(); + await corsPluginDialog.getByRole('tab', { name: 'Plugin JSON' }).click(); // Submit with simple configuration for cors const corsEditor = await uiGetMonacoEditor(page, corsPluginDialog); @@ -149,7 +149,7 @@ test('should CRUD global rule with multiple plugins', async ({ page }) => { const editPluginDialog = page.getByRole('dialog', { name: 'Edit Plugin: response-rewrite', }); - await editPluginDialog.getByRole('tab', { name: 'JSON' }).click(); + await editPluginDialog.getByRole('tab', { name: 'Plugin JSON' }).click(); const pluginEditor = await uiGetMonacoEditor(page, editPluginDialog); await uiFillMonacoEditor( page, diff --git a/e2e/tests/global_rules.crud-required-fields.spec.ts b/e2e/tests/global_rules.crud-required-fields.spec.ts index 0fda7205..1920549d 100644 --- a/e2e/tests/global_rules.crud-required-fields.spec.ts +++ b/e2e/tests/global_rules.crud-required-fields.spec.ts @@ -69,7 +69,7 @@ test('should CRUD global rule with required fields only', async ({ page }) => { name: 'Add Plugin: response-rewrite', }); await expect(pluginDialog).toBeVisible(); - await pluginDialog.getByRole('tab', { name: 'JSON' }).click(); + await pluginDialog.getByRole('tab', { name: 'Plugin JSON' }).click(); // Add minimal plugin configuration using Monaco editor const pluginEditor = await uiGetMonacoEditor(page, pluginDialog); diff --git a/e2e/tests/hot-path.upstream-service-route.spec.ts b/e2e/tests/hot-path.upstream-service-route.spec.ts index 43aa3480..bbcb4d78 100644 --- a/e2e/tests/hot-path.upstream-service-route.spec.ts +++ b/e2e/tests/hot-path.upstream-service-route.spec.ts @@ -215,7 +215,7 @@ test('can create upstream -> service -> route', async ({ page }) => { const addPluginDialog = page.getByRole('dialog', { name: `Add Plugin: ${servicePluginName}`, }); - await addPluginDialog.getByRole('tab', { name: 'JSON' }).click(); + await addPluginDialog.getByRole('tab', { name: 'Plugin JSON' }).click(); const pluginEditor = await uiGetMonacoEditor(page, addPluginDialog); // Add plugin configuration @@ -343,7 +343,7 @@ test('can create upstream -> service -> route', async ({ page }) => { const addPluginDialog = page.getByRole('dialog', { name: `Add Plugin: ${routePluginName}`, }); - await addPluginDialog.getByRole('tab', { name: 'JSON' }).click(); + await addPluginDialog.getByRole('tab', { name: 'Plugin JSON' }).click(); const pluginEditor = await uiGetMonacoEditor(page, addPluginDialog); // Add plugin configuration diff --git a/e2e/tests/plugin_configs.crud-all-fields.spec.ts b/e2e/tests/plugin_configs.crud-all-fields.spec.ts index 32825cd0..b7ef0bd9 100644 --- a/e2e/tests/plugin_configs.crud-all-fields.spec.ts +++ b/e2e/tests/plugin_configs.crud-all-fields.spec.ts @@ -94,7 +94,7 @@ test('should CRUD plugin config with all fields', async ({ page }) => { const addPluginDialog = page.getByRole('dialog', { name: 'Add Plugin: response-rewrite', }); - await addPluginDialog.getByRole('tab', { name: 'JSON' }).click(); + await addPluginDialog.getByRole('tab', { name: 'Plugin JSON' }).click(); const pluginEditor = await uiGetMonacoEditor(page, addPluginDialog); await uiFillMonacoEditor( page, @@ -196,7 +196,7 @@ test('should CRUD plugin config with all fields', async ({ page }) => { const editPluginDialog = page.getByRole('dialog', { name: 'Edit Plugin: response-rewrite', }); - await editPluginDialog.getByRole('tab', { name: 'JSON' }).click(); + await editPluginDialog.getByRole('tab', { name: 'Plugin JSON' }).click(); const pluginEditor = await uiGetMonacoEditor(page, editPluginDialog); await uiFillMonacoEditor( page, diff --git a/e2e/tests/plugin_configs.crud-required-fields.spec.ts b/e2e/tests/plugin_configs.crud-required-fields.spec.ts index bfcdebd4..3e16e87e 100644 --- a/e2e/tests/plugin_configs.crud-required-fields.spec.ts +++ b/e2e/tests/plugin_configs.crud-required-fields.spec.ts @@ -90,7 +90,7 @@ test('should CRUD plugin config with required fields', async ({ page }) => { const addPluginDialog = page.getByRole('dialog', { name: 'Add Plugin: response-rewrite', }); - await addPluginDialog.getByRole('tab', { name: 'JSON' }).click(); + await addPluginDialog.getByRole('tab', { name: 'Plugin JSON' }).click(); const pluginEditor = await uiGetMonacoEditor(page, addPluginDialog); await uiFillMonacoEditor(page, pluginEditor, '{"body": "test response"}'); // add plugin @@ -159,7 +159,7 @@ test('should CRUD plugin config with required fields', async ({ page }) => { const editPluginDialog = page.getByRole('dialog', { name: 'Edit Plugin: response-rewrite', }); - await editPluginDialog.getByRole('tab', { name: 'JSON' }).click(); + await editPluginDialog.getByRole('tab', { name: 'Plugin JSON' }).click(); const pluginEditor = await uiGetMonacoEditor(page, editPluginDialog); await uiFillMonacoEditor( page, diff --git a/e2e/tests/plugin_metadata.crud-all-fields.spec.ts b/e2e/tests/plugin_metadata.crud-all-fields.spec.ts index 6c4c0726..71769d77 100644 --- a/e2e/tests/plugin_metadata.crud-all-fields.spec.ts +++ b/e2e/tests/plugin_metadata.crud-all-fields.spec.ts @@ -73,7 +73,7 @@ test('should CRUD plugin metadata with all fields', async ({ page }) => { name: 'Add Plugin: http-logger', }); await expect(addPluginDialog).toBeVisible(); - await addPluginDialog.getByRole('tab', { name: 'JSON' }).click(); + await addPluginDialog.getByRole('tab', { name: 'Plugin JSON' }).click(); const pluginEditor = await uiGetMonacoEditor(page, addPluginDialog); await page.evaluate(() => { @@ -85,7 +85,7 @@ test('should CRUD plugin metadata with all fields', async ({ page }) => { await pluginEditor.blur(); await expect(pluginEditor.getByText('{')).toBeVisible(); await expect( - addPluginDialog.getByText('Fix JSON syntax before saving.') + addPluginDialog.getByText('Fix Plugin JSON syntax before saving.') ).toBeVisible(); await addPluginDialog.getByRole('button', { name: 'Add Plugin' }).click(); const jsonErrorAlert = addPluginDialog.getByRole('alert').filter({ @@ -93,10 +93,10 @@ test('should CRUD plugin metadata with all fields', async ({ page }) => { }); await expect(jsonErrorAlert).toBeVisible(); await expect( - addPluginDialog.getByRole('button', { name: 'Format JSON after error' }) + addPluginDialog.getByRole('button', { name: 'Format Plugin JSON after error' }) ).toBeVisible(); await addPluginDialog - .getByRole('button', { name: 'Reset JSON after error' }) + .getByRole('button', { name: 'Reset Plugin JSON after error' }) .click(); await expect(jsonErrorAlert).toBeHidden(); @@ -118,16 +118,16 @@ test('should CRUD plugin metadata with all fields', async ({ page }) => { }) ); await expect( - addPluginDialog.getByRole('button', { name: 'Format plugin JSON' }) + addPluginDialog.getByRole('button', { name: 'Format Plugin JSON' }) ).toBeVisible(); await expect( - addPluginDialog.getByRole('button', { name: 'Copy plugin JSON' }) + addPluginDialog.getByRole('button', { name: 'Copy Plugin JSON' }) ).toBeVisible(); await expect( - addPluginDialog.getByRole('button', { name: 'Reset plugin JSON' }) + addPluginDialog.getByRole('button', { name: 'Reset Plugin JSON' }) ).toBeVisible(); await addPluginDialog - .getByRole('button', { name: 'Format plugin JSON' }) + .getByRole('button', { name: 'Format Plugin JSON' }) .click(); await expect(pluginEditor.getByText('"log_format": {')).toBeVisible(); @@ -185,7 +185,7 @@ test('should CRUD plugin metadata with all fields', async ({ page }) => { name: 'Edit Plugin: http-logger', }); await expect(editPluginDialog).toBeVisible(); - await editPluginDialog.getByRole('tab', { name: 'JSON' }).click(); + await editPluginDialog.getByRole('tab', { name: 'Plugin JSON' }).click(); const pluginEditor = await uiGetMonacoEditor(page, editPluginDialog, false); @@ -207,7 +207,7 @@ test('should CRUD plugin metadata with all fields', async ({ page }) => { name: 'Edit Plugin: http-logger', }); await expect(editPluginDialog).toBeVisible(); - await editPluginDialog.getByRole('tab', { name: 'JSON' }).click(); + await editPluginDialog.getByRole('tab', { name: 'Plugin JSON' }).click(); const pluginEditor = await uiGetMonacoEditor(page, editPluginDialog); await uiFillMonacoEditor( diff --git a/e2e/tests/plugin_metadata.crud-required-fields.spec.ts b/e2e/tests/plugin_metadata.crud-required-fields.spec.ts index 29c0f400..b1cb9a5b 100644 --- a/e2e/tests/plugin_metadata.crud-required-fields.spec.ts +++ b/e2e/tests/plugin_metadata.crud-required-fields.spec.ts @@ -73,7 +73,7 @@ test('should CRUD plugin metadata with required fields only', async ({ name: 'Add Plugin: syslog', }); await expect(addPluginDialog).toBeVisible(); - await addPluginDialog.getByRole('tab', { name: 'JSON' }).click(); + await addPluginDialog.getByRole('tab', { name: 'Plugin JSON' }).click(); // Fill in minimal required configuration const pluginEditor = await uiGetMonacoEditor(page, addPluginDialog); @@ -124,7 +124,7 @@ test('should CRUD plugin metadata with required fields only', async ({ name: 'Edit Plugin: syslog', }); await expect(editPluginDialog).toBeVisible(); - await editPluginDialog.getByRole('tab', { name: 'JSON' }).click(); + await editPluginDialog.getByRole('tab', { name: 'Plugin JSON' }).click(); // Verify existing configuration is shown await expect(editPluginDialog.getByText('host')).toBeVisible(); diff --git a/e2e/tests/resource-required-templates.spec.ts b/e2e/tests/resource-required-templates.spec.ts index 50d39acb..3ea578d3 100644 --- a/e2e/tests/resource-required-templates.spec.ts +++ b/e2e/tests/resource-required-templates.spec.ts @@ -42,7 +42,7 @@ test('create forms show conditional required fields and minimal JSON templates', expect.arrayContaining(['uri', 'uris']) ); - await page.getByRole('tab', { name: 'Raw JSON' }).click(); + await page.getByRole('tab', { name: 'Payload JSON' }).click(); const routeJsonEditor = page.locator( '.ant-tabs-tabpane-active .monaco-editor' ); @@ -56,7 +56,7 @@ test('create forms show conditional required fields and minimal JSON templates', await expect.poll(() => requiredFields(page)).not.toContain('uris'); await page.goto('/ui/services/add'); - await page.getByRole('tab', { name: 'Raw JSON' }).click(); + await page.getByRole('tab', { name: 'Payload JSON' }).click(); await expect .poll(() => readMonacoValue( @@ -68,7 +68,7 @@ test('create forms show conditional required fields and minimal JSON templates', await page.goto('/ui/upstreams/add'); await expect.poll(() => requiredFields(page)).toContain('nodes'); - await page.getByRole('tab', { name: 'Raw JSON' }).click(); + await page.getByRole('tab', { name: 'Payload JSON' }).click(); await expect .poll(() => readMonacoValue( @@ -155,7 +155,7 @@ test('plugin add JSON prefills required fields from APISIX schema', async ({ const addPluginDialog = page.getByRole('dialog', { name: 'Add Plugin: limit-count', }); - await addPluginDialog.getByRole('tab', { name: 'JSON' }).click(); + await addPluginDialog.getByRole('tab', { name: 'Plugin JSON' }).click(); const pluginEditor = await uiGetMonacoEditor(page, addPluginDialog, false); await expect diff --git a/e2e/tests/routes.edit-payload-preservation.spec.ts b/e2e/tests/routes.edit-payload-preservation.spec.ts index 6db78e95..16d2adf9 100644 --- a/e2e/tests/routes.edit-payload-preservation.spec.ts +++ b/e2e/tests/routes.edit-payload-preservation.spec.ts @@ -25,7 +25,7 @@ import { API_ROUTES } from '@/config/constant'; const routeId = randomId('route-payload-preserve'); const routeUri = '/route-payload-preserve'; -const updatedDesc = 'updated through form while preserving raw payload'; +const updatedDesc = 'updated through form while preserving Admin API payload'; const rawDraftDesc = 'raw json draft that will fail once'; const rawLatestDesc = 'latest server value after failed raw save'; @@ -53,7 +53,7 @@ test.afterAll(async () => { await deleteAllRoutes(e2eReq); }); -test('route form save preserves raw payload and strips readonly fields', async ({ +test('route form save preserves Admin API payload and strips readonly fields', async ({ page, }) => { await uiGoto(page, '/routes/detail/$id', { id: routeId }); @@ -113,13 +113,13 @@ test('route form save preserves raw payload and strips readonly fields', async ( }); }); -test('raw JSON save failure offers reset and reload recovery actions', async ({ +test('Admin API JSON save failure offers reset and reload recovery actions', async ({ page, }) => { await uiGoto(page, '/routes/detail/$id', { id: routeId }); - await page.getByRole('tab', { name: 'Raw JSON' }).click(); + await page.getByRole('tab', { name: 'Admin API JSON' }).click(); - const rawJsonPanel = page.getByRole('tabpanel', { name: 'Raw JSON' }); + const rawJsonPanel = page.getByRole('tabpanel', { name: 'Admin API JSON' }); const editor = rawJsonPanel.locator('.monaco-editor').first(); await expect(editor).toBeVisible(); await uiFillMonacoEditor( diff --git a/src/components/form-slice/FormItemPlugins/PluginEditorDrawer.tsx b/src/components/form-slice/FormItemPlugins/PluginEditorDrawer.tsx index 907326e2..d812428f 100644 --- a/src/components/form-slice/FormItemPlugins/PluginEditorDrawer.tsx +++ b/src/components/form-slice/FormItemPlugins/PluginEditorDrawer.tsx @@ -128,7 +128,7 @@ export const PluginEditorDrawer = (props: PluginEditorDrawerProps) => { const parsed = JSON.parse(methods.getValues('config') || '{}') as Record; setFormValue(parsed); } catch { - setSaveError('Fix the JSON syntax error before switching to the fields view.'); + setSaveError('Fix the Plugin JSON syntax error before switching to Fields.'); return; } } @@ -176,7 +176,7 @@ export const PluginEditorDrawer = (props: PluginEditorDrawerProps) => { : []), { key: 'json', - label: 'JSON', + label: 'Plugin JSON', children: ( { await navigator.clipboard.writeText(methods.getValues('config') || '{}'); message.success('Plugin JSON copied'); } catch { - message.error('Failed to copy plugin JSON'); + message.error('Failed to copy Plugin JSON'); } }, [methods]); @@ -245,7 +245,7 @@ export const PluginEditorDrawer = (props: PluginEditorDrawerProps) => { parsed = JSON.parse(jsonConfigText || '{}'); } catch (error) { return { - message: 'Fix JSON syntax before saving.', + message: 'Fix Plugin JSON syntax before saving.', issues: [error instanceof Error ? error.message : String(error)], }; } @@ -276,17 +276,17 @@ export const PluginEditorDrawer = (props: PluginEditorDrawerProps) => { size="small" icon={} onClick={formatJsonConfig} - aria-label="Format JSON after error" + aria-label="Format Plugin JSON after error" > - Format JSON + Format Plugin JSON ) : undefined; @@ -412,28 +412,28 @@ export const PluginEditorDrawer = (props: PluginEditorDrawerProps) => { ))} {activeTab === 'json' && mode !== 'view' && ( - + - + } styles={{ body: { flex: 1, padding: 0, overflow: 'hidden' } }} @@ -1054,7 +1053,7 @@ function RawApiPage() { action={