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

roryqi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new 53fcaa993d [#10535] fix(server,web-v2): Expose serviceAdmins in 
/configs and handle array format in UI (#10523)
53fcaa993d is described below

commit 53fcaa993d2de78407142e7d5363fc179263499c
Author: Bharath Krishna <[email protected]>
AuthorDate: Wed Mar 25 04:38:49 2026 -0700

    [#10535] fix(server,web-v2): Expose serviceAdmins in /configs and handle 
array format in UI (#10523)
    
    ### What changes were proposed in this pull request?
    - Add Configs.SERVICE_ADMINS to ConfigServlet basicConfigEntries so
    gravitino.authorization.serviceAdmins is returned by GET /configs
    - Update TestConfigServlet to assert the new field is present
    - Fix web-v2 UI to handle serviceAdmins as JSON array (List<String>)
    rather than a comma-separated string
    
    It is accessed in front-end here :
    
https://github.com/apache/gravitino/blob/main/web-v2/web/src/lib/store/auth/index.js#L48
    
    ### Why are the changes needed?
    ConfigServlet only exposed AUTHENTICATORS and ENABLE_AUTHORIZATION via
    the /configs endpoint. Without serviceAdmins, the web UI cannot
    determine if the logged-in user is a service admin, causing the 'Create
    Metalake' button to be hidden even for valid service admins.
    
    Fix: #10535
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes, it will correctly make the "CREATE METALAKE" button appear
    
    ### How was this patch tested?
    
    Testing on Web UI
    <img width="468" height="233" alt="Screenshot 2026-03-23 at 9 57 07 PM"
    
src="https://github.com/user-attachments/assets/7ed96155-7812-4b32-bed6-5409f621bd16";
    />
---
 .../apache/gravitino/server/web/ConfigServlet.java |  9 ++++++
 .../gravitino/server/web/TestConfigServlet.java    | 35 ++++++++++++++++++++++
 web-v2/web/src/app/metalakes/page.js               |  6 ++--
 web-v2/web/src/app/rootLayout/UserSetting.js       |  4 ++-
 4 files changed, 51 insertions(+), 3 deletions(-)

diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/ConfigServlet.java 
b/server/src/main/java/org/apache/gravitino/server/web/ConfigServlet.java
index a1f8d84698..3b48cc4295 100644
--- a/server/src/main/java/org/apache/gravitino/server/web/ConfigServlet.java
+++ b/server/src/main/java/org/apache/gravitino/server/web/ConfigServlet.java
@@ -58,6 +58,15 @@ public class ConfigServlet extends HttpServlet {
       configs.put(key.getKey(), serverConfig.get(key));
     }
 
+    if (serverConfig.get(Configs.ENABLE_AUTHORIZATION)) {
+      // Expose serviceAdmins when authorization is enabled so the web UI can 
determine whether the
+      // logged-in user has service-admin privileges (e.g. to show the "Create 
Metalake" button).
+      String serviceAdminsRaw = 
serverConfig.getRawString(Configs.SERVICE_ADMINS.getKey());
+      if (serviceAdminsRaw != null) {
+        configs.put(Configs.SERVICE_ADMINS.getKey(), 
serverConfig.get(Configs.SERVICE_ADMINS));
+      }
+    }
+
     if (serverConfig
         .get(Configs.AUTHENTICATORS)
         .contains(AuthenticatorType.OAUTH.name().toLowerCase())) {
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/TestConfigServlet.java 
b/server/src/test/java/org/apache/gravitino/server/web/TestConfigServlet.java
index aab753fac2..d165dc2b42 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/TestConfigServlet.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/TestConfigServlet.java
@@ -52,6 +52,41 @@ public class TestConfigServlet {
     configServlet.destroy();
   }
 
+  @Test
+  public void testConfigServletWithAuthEnabledButNoServiceAdmins() throws 
Exception {
+    // When authorization is enabled but serviceAdmins is not configured, the 
key should be
+    // absent from the response (no crash) rather than null or empty.
+    ServerConfig serverConfig = new ServerConfig();
+    serverConfig.set(Configs.ENABLE_AUTHORIZATION, true);
+    ConfigServlet configServlet = new ConfigServlet(serverConfig);
+    configServlet.init();
+    HttpServletResponse res = mock(HttpServletResponse.class);
+    PrintWriter writer = mock(PrintWriter.class);
+    when(res.getWriter()).thenReturn(writer);
+    configServlet.doGet(null, res);
+    verify(writer)
+        .write(
+            
"{\"gravitino.authorization.enable\":true,\"gravitino.authenticators\":[\"simple\"]}");
+    configServlet.destroy();
+  }
+
+  @Test
+  public void testConfigServletWithAuthEnabledAndServiceAdmins() throws 
Exception {
+    ServerConfig serverConfig = new ServerConfig();
+    serverConfig.set(Configs.ENABLE_AUTHORIZATION, true);
+    serverConfig.set(Configs.SERVICE_ADMINS, Lists.newArrayList("admin1", 
"admin2"));
+    ConfigServlet configServlet = new ConfigServlet(serverConfig);
+    configServlet.init();
+    HttpServletResponse res = mock(HttpServletResponse.class);
+    PrintWriter writer = mock(PrintWriter.class);
+    when(res.getWriter()).thenReturn(writer);
+    configServlet.doGet(null, res);
+    verify(writer)
+        .write(
+            
"{\"gravitino.authorization.enable\":true,\"gravitino.authenticators\":[\"simple\"],\"gravitino.authorization.serviceAdmins\":[\"admin1\",\"admin2\"]}");
+    configServlet.destroy();
+  }
+
   @Test
   public void testConfigServletWithVisibleConfigs() throws Exception {
     ServerConfig serverConfig = new ServerConfig();
diff --git a/web-v2/web/src/app/metalakes/page.js 
b/web-v2/web/src/app/metalakes/page.js
index 8b8fcb17b5..183b04a329 100644
--- a/web-v2/web/src/app/metalakes/page.js
+++ b/web-v2/web/src/app/metalakes/page.js
@@ -70,6 +70,8 @@ const MetalakeList = () => {
   const [ownerRefreshKey, setOwnerRefreshKey] = useState(0)
   const auth = useAppSelector(state => state.auth)
   const { serviceAdmins, authUser, anthEnable } = auth
+  const admins = Array.isArray(serviceAdmins) ? serviceAdmins : (serviceAdmins 
|| '').split(',')
+  const isServiceAdmin = admins.includes(authUser?.name)
   const dispatch = useAppDispatch()
   const store = useAppSelector(state => state.metalakes)
   const [tableData, setTableData] = useState([])
@@ -273,7 +275,7 @@ const MetalakeList = () => {
           return (
             <div className='flex gap-2' key={record.name}>
               <NameContext.Provider 
value={record.name}>{contextHolder}</NameContext.Provider>
-              {([...(serviceAdmins || '').split(',')].includes(authUser?.name) 
|| !authUser) && (
+              {(isServiceAdmin || !authUser) && (
                 <a data-refer={`edit-metalake-${record.name}`}>
                   <Tooltip title='Edit'>
                     <Icons.Pencil className='size-4' onClick={() => 
handleEditMetalake(record.name)} />
@@ -361,7 +363,7 @@ const MetalakeList = () => {
               placeholder='Search...'
               onChange={onSearchTable}
             />
-            {([...(serviceAdmins || '').split(',')].includes(authUser?.name) 
|| !anthEnable) && (
+            {(isServiceAdmin || !anthEnable) && (
               <Button
                 data-refer='create-metalake-btn'
                 type='primary'
diff --git a/web-v2/web/src/app/rootLayout/UserSetting.js 
b/web-v2/web/src/app/rootLayout/UserSetting.js
index dbbe97e962..d3041ae2a8 100644
--- a/web-v2/web/src/app/rootLayout/UserSetting.js
+++ b/web-v2/web/src/app/rootLayout/UserSetting.js
@@ -41,6 +41,8 @@ export default function UserSetting() {
   const [showLogoutButton, setShowLogoutButton] = useState(false)
   const auth = useAppSelector(state => state.auth)
   const { serviceAdmins, authUser, anthEnable } = auth
+  const admins = Array.isArray(serviceAdmins) ? serviceAdmins : (serviceAdmins 
|| '').split(',')
+  const isServiceAdmin = admins.includes(authUser?.name)
   const [session, setSession] = useState({})
   const router = useRouter()
   const pathname = usePathname()
@@ -85,7 +87,7 @@ export default function UserSetting() {
         label: (
           <div className='flex w-[208px] justify-between'>
             <span>Metalakes</span>
-            {[...(serviceAdmins || '').split(',')].includes(authUser?.name) && 
(
+            {isServiceAdmin && (
               <Tooltip title='Create Metalake'>
                 <PlusOutlined className='cursor-pointer text-black' 
onClick={handleCreateMetalake} />
               </Tooltip>

Reply via email to