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

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new 2724513d fix: avoid exposing LLM API keys (#536)
2724513d is described below

commit 2724513d3afd33b6bc67aca6efc0ddc63d746c25
Author: aias00 <[email protected]>
AuthorDate: Tue Jul 28 05:26:06 2026 -0700

    fix: avoid exposing LLM API keys (#536)
    
    * fix: align frontend API success response handling (#429)
    
    * ci: add build workflow and PR review skill (#430)
    
    * fix: connect K8s certificate page to APIs (#440)
    
    * feat: extend translation keys & add useLanguage compatible alias in 
LangContext (#433)
    
    * fix: validate audit query and cleanup parameters (#442)
    
    * chore: fix Dockerfile missing style/ copy and standardize pr-review skill 
(#444)
    
    - Add COPY style ./style to server/Dockerfile so Maven checkstyle can
      find style/rmq_checkstyle.xml during docker compose build
    - Rewrite pr-review SKILL.md with an 8-stage pipeline checklist,
      Dockerfile pre-flight fix step, and [Studio] title format spec
    
    * feat: add centralized ThemeContext and useTheme hook for dark mode 
management (#435)
    
    * fix: preserve SSE framing for AI chat (#438)
    
    * feat: add Prometheus range query adapter (#432)
    
    Add a real Prometheus /api/v1/query_range adapter as the foundation for 
observability (#431): configurable base URL, timeouts, Basic/Bearer auth, error 
mapping, and tests.
    
    * feat: implement login page, auth & AI modules, simplify theme management 
(#473)
    
    Add login page with full auth workflow, Auth/AI API modules, Zustand state, 
and simplified theme management.
    
    * feat: add Ops page (NameServer management, VIPChannel, TLS) (#474)
    
    Add Ops management page for NameServer address management, VIP channel 
toggle, and TLS switch.
    
    * feat: add Producer page (#475)
    
    Add a page to query producer client connections by Topic and Producer Group.
    
    * feat: add AlertManagement page for alert rule operations (#476)
    
    Add alert rule management page: parse/render Prometheus AlertManager YAML 
rules with search, toggle, and CRUD.
    
    * feat: add SslSettings page for SSL/TLS configuration management (#477)
    
    Add SSL/TLS configuration page: SSL toggle, TLS version, 
KeyStore/TrustStore, client auth mode.
    
    * feat: add broker cluster overview page (#479)
    
    Add Broker Cluster overview page with tab-based NameServer/Broker/Proxy 
management and cluster stats.
    
    * feat: add consumer group management page (#480)
    
    Add consumer group management page.
    
    * feat: add lite topic management page (#481)
    
    Add lite topic management page.
    
    * feat: add Proxy management page (#482)
    
    Add Proxy management page (address list, status, TPS, memory, CPU, uptime).
    
    * feat: add LLM Settings configuration page (#483)
    
    Add LLM settings configuration page (provider, apiKey, model, temperature, 
etc.).
    
    * feat: connect frontend pages to backend APIs and align contracts (#445)
    
    Consolidated API-integration work 
(#445,#446,#447,#448,#449,#450,#451,#452,#453,#454,#458,#459,#460,#461,#463).
    
    * feat: add service capabilities and interaction enhancements (#462)
    
    Consolidated service/interaction work 
(#462,#468,#469,#464,#470,#471,#472,#488).
    
    * feat: add preferences persistence, accessibility and env config (#465)
    
    Consolidated preferences/a11y/config work (#465,#466,#485,#486,#487,#484).
    
    * fix: include remaining consolidated frontend changes (#490)
    
    topicService topic API contract, authStore persisted session, vite env 
config.
    
    * fix: align login response contract (#492)
    
    Align frontend LoginResponse type with backend LoginVO contract: change 
flat username/role to nested user.username/admin structure.
    
    * fix: use runtime DNS resolver for Nginx API proxy (#494)
    
    Fix Nginx API proxy failure caused by hardcoded DNS resolver by switching 
to container runtime dynamic resolution.
    
    * feat: enhance i18n support with labelKey-based translations (#495)
    
    Migrate hardcoded Chinese UI text to i18n labelKey pattern, add ~280 
translation entries (zh+en), fix duplicate key compilation errors.
    
    * test: fix frontend baseline assertions (#498)
    
    Fix test assertions to align with backend PageResult contract and current 
i18n labels.
    
    * fix: initialize deploy helpers before validation (#499)
    
    Move logging/error helper initialization before config validation in 
deploy.sh to fix err() undefined when REMOTE_HOST is missing.
    
    * ci: build frontend Docker image (#500)
    
    Add frontend Docker image build to CI workflow to validate Dockerfile/nginx 
deployment path.
    
    * fix: load producer topics from Studio API response (#501)
    
    Fix Producer page topic list to read from Studio API new format 
(data[].name) with backward compatibility for legacy topicList format.
    
    * fix: generate data source keys on creation (#509)
    
    Fix ConcurrentHashMap NPE when creating data sources with null key by 
auto-generating UUID in service layer.
    
    * feat: add producer connection endpoint (#510)
    
    Add producer connection query endpoint with topic/producerGroup filtering, 
returning backward-compatible connectionSet format.
    
    * fix: avoid returning General Settings API key (#512)
    
    Fix security issue where General Settings endpoint returned API key in 
plaintext. Replace with apiKeyConfigured boolean flag and support explicit key 
clearing.
    
    * feat: add catalog-driven read-only tools (#514)
    
    Introduce versioned YAML tool catalog with JSON Schema validation, SHA-256 
digest, deep immutability, and two read-only L1 tool handlers (rmq.cluster.list 
/ rmq.capabilities).
    
    * feat: add LiteTopic backend endpoints (#502)
    
    Add backend stub endpoints for LiteTopic page: list, session details, TTL 
extension, quota, and capability check.
    
    * feat: add ops backend endpoints (#504)
    
    Add ops page backend endpoints: NameServer address management, VIP Channel, 
and TLS switches.
    
    * feat: add proxy address endpoints (#505)
    
    Add proxy address list and add-address endpoints supporting Studio frontend 
form submission.
    
    * feat: add LLM settings endpoints (#506)
    
    Add LLM settings backend endpoints: config read/write, connectivity test, 
and model listing.
    
    * feat: add alert rules YAML endpoint (#507)
    
    Add alert rules Prometheus YAML export endpoint with default RocketMQ alert 
template fallback.
    
    * fix: ACL page API integration (#516)
    
    Wire ACL management page to backend API, add update endpoints for ACL rules 
and users with proper validation.
    
    * fix: sort queried messages by store time (#519)
    
    Sort message query results by storeTime in descending order so latest 
messages appear first.
    
    * feat: add consumer stack diagnostics API (#521)
    
    Add consumer thread stack diagnostics endpoint (GET 
/api/groups/{name}/instances/{clientId}/stack) with Provider/Stub/Service 
layering.
    
    * fix: avoid exposing LLM API keys
    
    ---------
    
    Co-authored-by: btlqql <[email protected]>
    Co-authored-by: lizhimins <[email protected]>
    Co-authored-by: zhaohai <[email protected]>
    Co-authored-by: terrancesli <[email protected]>
    Co-authored-by: wizcraft_kris 
<[email protected]>
    Co-authored-by: Loyal-Young <[email protected]>
    Co-authored-by: majialong <[email protected]>
    Co-authored-by: yx9o <[email protected]>
    Co-authored-by: PiliLily <[email protected]>
---
 .../rocketmq/studio/ops/ai/LlmConfigService.java   | 26 +++++++++-
 .../apache/rocketmq/studio/ops/ai/LlmConfigVO.java | 10 ++++
 .../studio/ops/ai/LlmConfigServiceTest.java        | 54 +++++++++++++++++++-
 .../rocketmq/studio/ops/ai/LlmControllerTest.java  |  2 +
 web/src/api/llm.test.ts                            | 16 +++++-
 web/src/api/llm.ts                                 |  3 +-
 web/src/pages/studio/LlmSettings.tsx               | 57 ++++++++++++++--------
 7 files changed, 141 insertions(+), 27 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigService.java 
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigService.java
index 0ffc590f..240d1027 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigService.java
@@ -67,8 +67,15 @@ public class LlmConfigService {
 
     public synchronized void saveConfig(LlmConfigVO config) {
         LlmConfigVO normalized = normalize(config);
-        overrides = copy(normalized);
         GeneralSettingsVO current = settingsService.getGeneralSettings();
+        boolean sameProvider = current != null
+                && 
normalizeProvider(current.getLlmProvider()).equals(normalized.getProvider());
+        boolean apiKeyOmitted = isBlank(normalized.getApiKey());
+        LlmConfigVO effective = copy(normalized);
+        if (apiKeyOmitted && sameProvider) {
+            effective.setApiKey(defaultString(current.getApiKey(), ""));
+        }
+        overrides = effective;
         settingsService.saveGeneralSettings(GeneralSettingsVO.builder()
                 .theme(current.getTheme())
                 .compact(current.isCompact())
@@ -78,13 +85,14 @@ public class LlmConfigService {
                 .requireLogin(current.isRequireLogin())
                 .llmProvider(normalized.getProvider())
                 .apiKey(normalized.getApiKey())
+                .clearApiKey(apiKeyOmitted && !sameProvider)
                 .model(normalized.getModel())
                 .baseUrl(normalized.getApiBase())
                 .build());
     }
 
     public LlmOperationResultVO testConfig(LlmConfigVO config) {
-        LlmConfigVO normalized = normalize(config);
+        LlmConfigVO normalized = 
withStoredApiKeyIfSameProvider(normalize(config));
         String provider = normalized.getProvider();
         boolean keyRequired = !"ollama".equals(provider);
         if (keyRequired && isBlank(normalized.getApiKey())) {
@@ -136,6 +144,20 @@ public class LlmConfigService {
                 .build();
     }
 
+    private LlmConfigVO withStoredApiKeyIfSameProvider(LlmConfigVO config) {
+        if (!isBlank(config.getApiKey())) {
+            return config;
+        }
+        GeneralSettingsVO current = settingsService.getGeneralSettings();
+        if (current == null || !normalizeProvider(current.getLlmProvider())
+                .equals(config.getProvider())) {
+            return config;
+        }
+        LlmConfigVO effective = copy(config);
+        effective.setApiKey(defaultString(current.getApiKey(), ""));
+        return effective;
+    }
+
     private LlmConfigVO copy(LlmConfigVO config) {
         return normalize(config);
     }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigVO.java 
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigVO.java
index d354cde5..8981022e 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigVO.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigVO.java
@@ -17,10 +17,13 @@
 
 package org.apache.rocketmq.studio.ops.ai;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
+import lombok.ToString;
+import org.springframework.util.StringUtils;
 
 @Data
 @Builder
@@ -28,6 +31,8 @@ import lombok.NoArgsConstructor;
 @AllArgsConstructor
 public class LlmConfigVO {
     private String provider;
+    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
+    @ToString.Exclude
     private String apiKey;
     private String apiBase;
     private String model;
@@ -37,4 +42,9 @@ public class LlmConfigVO {
     private String deploymentName;
     private String apiVersion;
     private String awsRegion;
+
+    @JsonProperty(value = "apiKeyConfigured", access = 
JsonProperty.Access.READ_ONLY)
+    public boolean isApiKeyConfigured() {
+        return StringUtils.hasText(apiKey);
+    }
 }
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/LlmConfigServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/LlmConfigServiceTest.java
index 3b40024a..3d250356 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/LlmConfigServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/LlmConfigServiceTest.java
@@ -57,6 +57,7 @@ class LlmConfigServiceTest {
 
         assertThat(config.getProvider()).isEqualTo("openai");
         assertThat(config.getApiKey()).isEqualTo("sk-test");
+        assertThat(config.isApiKeyConfigured()).isTrue();
         assertThat(config.getApiBase()).isEqualTo("https://api.openai.com/v1";);
         assertThat(config.getModel()).isEqualTo("gpt-4o");
         assertThat(config.isEnabled()).isTrue();
@@ -89,17 +90,66 @@ class LlmConfigServiceTest {
     }
 
     @Test
-    void testConfigShouldRejectMissingRequiredApiKey() {
-        LlmOperationResultVO result = 
llmConfigService.testConfig(LlmConfigVO.builder()
+    void saveConfigShouldKeepStoredApiKeyWhenSameProviderRequestOmitsIt() {
+        LlmConfigVO config = LlmConfigVO.builder()
                 .provider("openai")
                 .apiKey("")
+                .apiBase("https://api.openai.com/v1";)
                 .model("gpt-4o")
+                .enabled(true)
+                .build();
+
+        llmConfigService.saveConfig(config);
+
+        
assertThat(llmConfigService.getConfig().getApiKey()).isEqualTo("sk-test");
+        ArgumentCaptor<GeneralSettingsVO> captor = 
ArgumentCaptor.forClass(GeneralSettingsVO.class);
+        verify(settingsService).saveGeneralSettings(captor.capture());
+        assertThat(captor.getValue().getApiKey()).isEmpty();
+        assertThat(captor.getValue().isClearApiKey()).isFalse();
+    }
+
+    @Test
+    void 
saveConfigShouldClearStoredApiKeyWhenProviderChangesWithoutReplacement() {
+        LlmConfigVO config = LlmConfigVO.builder()
+                .provider("deepseek")
+                .apiKey("")
+                .apiBase("https://api.deepseek.com/v1";)
+                .model("deepseek-chat")
+                .enabled(true)
+                .build();
+
+        llmConfigService.saveConfig(config);
+
+        
assertThat(llmConfigService.getConfig().isApiKeyConfigured()).isFalse();
+        ArgumentCaptor<GeneralSettingsVO> captor = 
ArgumentCaptor.forClass(GeneralSettingsVO.class);
+        verify(settingsService).saveGeneralSettings(captor.capture());
+        assertThat(captor.getValue().isClearApiKey()).isTrue();
+    }
+
+    @Test
+    void testConfigShouldRejectMissingRequiredApiKey() {
+        LlmOperationResultVO result = 
llmConfigService.testConfig(LlmConfigVO.builder()
+                .provider("deepseek")
+                .apiKey("")
+                .model("deepseek-chat")
                 .build());
 
         assertThat(result.getStatus()).isEqualTo(1);
         assertThat(result.getErrMsg()).isEqualTo("API Key is required");
     }
 
+    @Test
+    void testConfigShouldReuseStoredApiKeyForSameProvider() {
+        LlmOperationResultVO result = 
llmConfigService.testConfig(LlmConfigVO.builder()
+                .provider("openai")
+                .apiKey("")
+                .model("gpt-4o")
+                .build());
+
+        assertThat(result.getStatus()).isZero();
+        assertThat(result.getMsg()).isEqualTo("Configuration accepted");
+    }
+
     @Test
     void testConfigShouldAllowOllamaWithoutApiKey() {
         LlmOperationResultVO result = 
llmConfigService.testConfig(LlmConfigVO.builder()
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/LlmControllerTest.java 
b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/LlmControllerTest.java
index 1a957046..0b6a0a3e 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/LlmControllerTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/LlmControllerTest.java
@@ -64,6 +64,8 @@ class LlmControllerTest {
         mockMvc.perform(get("/api/llm/config"))
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.provider").value("openai"))
+                .andExpect(jsonPath("$.apiKey").doesNotExist())
+                .andExpect(jsonPath("$.apiKeyConfigured").value(true))
                 
.andExpect(jsonPath("$.apiBase").value("https://api.openai.com/v1";))
                 .andExpect(jsonPath("$.model").value("gpt-4o"))
                 .andExpect(jsonPath("$.enabled").value(true));
diff --git a/web/src/api/llm.test.ts b/web/src/api/llm.test.ts
index 63bcaf50..feb1e3c1 100644
--- a/web/src/api/llm.test.ts
+++ b/web/src/api/llm.test.ts
@@ -38,6 +38,16 @@ const sampleConfig: LlmConfig = {
   enabled: true,
 };
 
+const storedConfig: LlmConfig = {
+  provider: 'openai',
+  apiKeyConfigured: true,
+  apiBase: 'https://api.openai.com/v1',
+  model: 'gpt-4o',
+  maxTokens: 4096,
+  temperature: 0.7,
+  enabled: true,
+};
+
 describe('LLM API', () => {
   beforeEach(() => {
     mock.reset();
@@ -50,11 +60,13 @@ describe('LLM API', () => {
   });
 
   it('fetches LLM config', async () => {
-    mock.onGet('/llm/config').reply(200, sampleConfig);
+    mock.onGet('/llm/config').reply(200, storedConfig);
 
     const result = await getLlmConfig();
-    expect(result).toEqual(sampleConfig);
+    expect(result).toEqual(storedConfig);
     expect(result.provider).toBe('openai');
+    expect(result.apiKey).toBeUndefined();
+    expect(result.apiKeyConfigured).toBe(true);
     expect(result.enabled).toBe(true);
   });
 
diff --git a/web/src/api/llm.ts b/web/src/api/llm.ts
index 871366ac..5c832f72 100644
--- a/web/src/api/llm.ts
+++ b/web/src/api/llm.ts
@@ -19,7 +19,8 @@ import client from './client';
 
 export interface LlmConfig {
   provider: string;
-  apiKey: string;
+  apiKey?: string;
+  apiKeyConfigured?: boolean;
   apiBase: string;
   model: string;
   maxTokens: number;
diff --git a/web/src/pages/studio/LlmSettings.tsx 
b/web/src/pages/studio/LlmSettings.tsx
index 74f34edf..bf236175 100644
--- a/web/src/pages/studio/LlmSettings.tsx
+++ b/web/src/pages/studio/LlmSettings.tsx
@@ -153,6 +153,8 @@ interface TestResult {
   msg: string;
 }
 
+const MASKED_API_KEY = '••••••••';
+
 const LlmSettingsPage: React.FC = () => {
   const { t } = useLang();
   const { message } = App.useApp();
@@ -164,7 +166,7 @@ const LlmSettingsPage: React.FC = () => {
   const [enabled, setEnabled] = useState(false);
   const [selectedProvider, setSelectedProvider] = useState('openai');
   const [apiKeyMasked, setApiKeyMasked] = useState(true);
-  const [savedApiKey, setSavedApiKey] = useState('');
+  const [apiKeyConfigured, setApiKeyConfigured] = useState(false);
   const [modelOptions, setModelOptions] = useState<{ value: string; label: 
string }[]>([]);
   const [modelsLoading, setModelsLoading] = useState(false);
 
@@ -183,9 +185,14 @@ const LlmSettingsPage: React.FC = () => {
           const provider = config.provider || 'openai';
           setSelectedProvider(provider);
           setEnabled(config.enabled || false);
-          if (config.apiKey) {
-            setSavedApiKey(config.apiKey);
-            form.setFieldsValue({ apiKey: maskApiKey(config.apiKey) });
+          if (config.apiKeyConfigured) {
+            setApiKeyConfigured(true);
+            setApiKeyMasked(true);
+            form.setFieldsValue({ apiKey: MASKED_API_KEY });
+          } else {
+            setApiKeyConfigured(false);
+            setApiKeyMasked(false);
+            form.setFieldsValue({ apiKey: '' });
           }
           form.setFieldsValue({
             provider,
@@ -241,11 +248,6 @@ const LlmSettingsPage: React.FC = () => {
       });
   }
 
-  const maskApiKey = (key: string) => {
-    if (!key || key.length < 8) return key ? '••••••••' : '';
-    return key.slice(0, 4) + '••••••••' + key.slice(-4);
-  };
-
   const currentProvider = PROVIDERS.find((p) => p.key === selectedProvider) || 
PROVIDERS[0];
 
   const handleProviderChange = useCallback(
@@ -258,17 +260,23 @@ const LlmSettingsPage: React.FC = () => {
           apiBase: provider.defaultBaseUrl,
           model: provider.defaultModel,
         });
+        if (value !== selectedProvider) {
+          form.setFieldsValue({ apiKey: '' });
+          setApiKeyConfigured(false);
+          setApiKeyMasked(false);
+        }
         if (!provider.requireApiKey) {
           form.setFieldsValue({ apiKey: '' });
-          setSavedApiKey('');
+          setApiKeyConfigured(false);
+          setApiKeyMasked(false);
         }
       }
     },
-    [form],
+    [form, selectedProvider],
   );
 
   const handleApiKeyFocus = () => {
-    if (apiKeyMasked && savedApiKey) {
+    if (apiKeyMasked && apiKeyConfigured) {
       form.setFieldsValue({ apiKey: '' });
       setApiKeyMasked(false);
     }
@@ -276,12 +284,15 @@ const LlmSettingsPage: React.FC = () => {
 
   const handleApiKeyBlur = () => {
     const val = form.getFieldValue('apiKey');
-    if (!val && savedApiKey) {
-      form.setFieldsValue({ apiKey: maskApiKey(savedApiKey) });
+    if (!val && apiKeyConfigured) {
+      form.setFieldsValue({ apiKey: MASKED_API_KEY });
       setApiKeyMasked(true);
     }
   };
 
+  const effectiveApiKey = (value: unknown) =>
+    apiKeyMasked && apiKeyConfigured ? '' : String(value || '');
+
   const handleTestConnection = () => {
     setTestLoading(true);
     setTestResult(null);
@@ -290,7 +301,7 @@ const LlmSettingsPage: React.FC = () => {
       .then((values) => {
         const testConfig: LlmConfig = {
           ...values,
-          apiKey: apiKeyMasked && savedApiKey ? savedApiKey : values.apiKey || 
'',
+          apiKey: effectiveApiKey(values.apiKey),
           enabled: true,
         };
         testLlmConnection(testConfig)
@@ -302,8 +313,11 @@ const LlmSettingsPage: React.FC = () => {
               saveLlmConfig(testConfig)
                 .then(() => {
                   if (testConfig.apiKey) {
-                    setSavedApiKey(testConfig.apiKey);
-                    form.setFieldsValue({ apiKey: 
maskApiKey(testConfig.apiKey) });
+                    setApiKeyConfigured(true);
+                    form.setFieldsValue({ apiKey: MASKED_API_KEY });
+                    setApiKeyMasked(true);
+                  } else if (apiKeyConfigured) {
+                    form.setFieldsValue({ apiKey: MASKED_API_KEY });
                     setApiKeyMasked(true);
                   }
                 })
@@ -341,7 +355,7 @@ const LlmSettingsPage: React.FC = () => {
       .then((values) => {
         const config: LlmConfig = {
           ...values,
-          apiKey: apiKeyMasked && savedApiKey ? savedApiKey : values.apiKey || 
'',
+          apiKey: effectiveApiKey(values.apiKey),
           enabled,
         };
         saveLlmConfig(config)
@@ -349,8 +363,11 @@ const LlmSettingsPage: React.FC = () => {
             if (result && result.status === 0) {
               message.success(t('llm.saveSuccess'));
               if (config.apiKey) {
-                setSavedApiKey(config.apiKey);
-                form.setFieldsValue({ apiKey: maskApiKey(config.apiKey) });
+                setApiKeyConfigured(true);
+                form.setFieldsValue({ apiKey: MASKED_API_KEY });
+                setApiKeyMasked(true);
+              } else if (apiKeyConfigured) {
+                form.setFieldsValue({ apiKey: MASKED_API_KEY });
                 setApiKeyMasked(true);
               }
             } else {

Reply via email to