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

lauraxia pushed a commit to branch antdUI-gravitino-base1.1.0
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/antdUI-gravitino-base1.1.0 by 
this push:
     new eb647a6542 
[#9684][#9690][#9697][#9698][#9699][#9721][#9722][#9723][#9724][#9725][#9726][#9728][#9729]
eb647a6542 is described below

commit eb647a6542a27f770e0b2a5969f3b0eff7cbe469
Author: Qian Xia <[email protected]>
AuthorDate: Thu Jan 15 18:54:19 2026 +0800

    
[#9684][#9690][#9697][#9698][#9699][#9721][#9722][#9723][#9724][#9725][#9726][#9728][#9729]
---
 web/web/src/app/access/roles/CreateRoleDialog.js   |  68 +++-
 .../app/access/userGroups/AddUserGroupDialog.js    |   1 +
 .../userGroups/GrantRolesForUserGroupDialog.js     |   2 +-
 web/web/src/app/access/users/AddUserDialog.js      |   1 +
 .../catalogs/rightContent/CreateCatalogDialog.js   |   2 +
 .../catalogs/rightContent/CreateFilesetDialog.js   |   1 +
 .../catalogs/rightContent/CreateSchemaDialog.js    |   1 +
 .../app/catalogs/rightContent/CreateTableDialog.js |  10 +-
 .../app/catalogs/rightContent/CreateTopicDialog.js |   1 +
 .../app/catalogs/rightContent/LinkVersionDialog.js |  93 ++++--
 .../catalogs/rightContent/RegisterModelDialog.js   |   1 +
 .../entitiesContent/ModelDetailsPage.js            |   2 +-
 .../app/compliance/policies/CreatePolicyDialog.js  |   1 +
 web/web/src/app/compliance/tags/CreateTagDialog.js |   1 +
 web/web/src/app/jobs/CreateJobDialog.js            | 361 ++++++++++++++++-----
 web/web/src/app/jobs/RegisterJobTemplateDialog.js  |  25 +-
 web/web/src/app/metalakes/CreateMetalakeDialog.js  |   1 +
 web/web/src/app/rootLayout/UserSetting.js          |   5 +-
 .../src/components/SecurableObjectFormFields.js    |  79 ++++-
 web/web/src/components/SetOwnerDialog.js           |   4 +-
 web/web/src/config/index.js                        |   8 +-
 21 files changed, 526 insertions(+), 142 deletions(-)

diff --git a/web/web/src/app/access/roles/CreateRoleDialog.js 
b/web/web/src/app/access/roles/CreateRoleDialog.js
index cb40eb365b..f5cc3379e9 100644
--- a/web/web/src/app/access/roles/CreateRoleDialog.js
+++ b/web/web/src/app/access/roles/CreateRoleDialog.js
@@ -47,7 +47,10 @@ export default function CreateRoleDialog({ ...props }) {
   const [confirmLoading, setConfirmLoading] = useState(false)
   const [isLoading, setIsLoading] = useState(false)
   const [privilegeErrorTips, setPrivilegeErrorTips] = useState('')
+  const [activeCollapseKeys, setActiveCollapseKeys] = useState([])
   const loadedRef = useRef(false)
+  const prevSecurableLenRef = useRef(0)
+  const editInitRef = useRef(false)
   const scrollRef = useRef(null)
   const scrolling = useScrolling(scrollRef)
   const [bottomShadow, setBottomShadow] = useState(false)
@@ -57,6 +60,14 @@ export default function CreateRoleDialog({ ...props }) {
   const [form] = Form.useForm()
   const values = Form.useWatch([], form)
 
+  const isElementVisibleInContainer = (el, container) => {
+    if (!el || !container) return true
+    const elRect = el.getBoundingClientRect()
+    const containerRect = container.getBoundingClientRect()
+
+    return elRect.top >= containerRect.top && elRect.bottom <= 
containerRect.bottom
+  }
+
   const handScroll = () => {
     if (scrollRef.current) {
       const { scrollTop, scrollHeight, clientHeight } = scrollRef.current
@@ -70,6 +81,18 @@ export default function CreateRoleDialog({ ...props }) {
         setTopShadow(false)
         setBottomShadow(false)
       }
+
+      const activeEl = document.activeElement
+
+      const isFormControl =
+        activeEl && (activeEl.getAttribute?.('role') === 'combobox' || 
['INPUT', 'TEXTAREA'].includes(activeEl.tagName))
+      if (
+        isFormControl &&
+        scrollRef.current.contains(activeEl) &&
+        !isElementVisibleInContainer(activeEl, scrollRef.current)
+      ) {
+        activeEl.blur()
+      }
     }
   }
 
@@ -85,6 +108,7 @@ export default function CreateRoleDialog({ ...props }) {
   useEffect(() => {
     if (open && editRole && !loadedRef.current) {
       loadedRef.current = true
+      editInitRef.current = true
 
       const init = async () => {
         setIsLoading(true)
@@ -119,6 +143,7 @@ export default function CreateRoleDialog({ ...props }) {
           // normal side-effects now.
           setTimeout(() => {
             form.setFieldsValue({ __init_in_progress: false })
+            editInitRef.current = false
           }, 100)
           setIsLoading(false)
         } catch (e) {
@@ -140,6 +165,33 @@ export default function CreateRoleDialog({ ...props }) {
     setPrivilegeErrorTips('')
   }, [values?.securableObjects])
 
+  useEffect(() => {
+    const currentLen = values?.securableObjects?.length || 0
+
+    if (currentLen === 0) {
+      setActiveCollapseKeys([])
+      prevSecurableLenRef.current = 0
+
+      return
+    }
+
+    if (prevSecurableLenRef.current === 0 && activeCollapseKeys.length === 0) {
+      if (editRole) {
+        setActiveCollapseKeys(['0'])
+      } else {
+        setActiveCollapseKeys(Array.from({ length: currentLen }, (_, idx) => 
String(idx)))
+      }
+    } else if (currentLen > prevSecurableLenRef.current) {
+      if (!editRole || !editInitRef.current) {
+        setActiveCollapseKeys(keys => Array.from(new Set([...(keys || []), 
String(currentLen - 1)])))
+      }
+    } else if (currentLen < prevSecurableLenRef.current) {
+      setActiveCollapseKeys(keys => (keys || []).filter(k => Number(k) < 
currentLen))
+    }
+
+    prevSecurableLenRef.current = currentLen
+  }, [values?.securableObjects?.length, editRole])
+
   const handleSubmit = e => {
     e.preventDefault()
     form
@@ -208,11 +260,9 @@ export default function CreateRoleDialog({ ...props }) {
   }
 
   const renderSecurableObjectItems = (fields, subOpt) => {
-    const activeKeys = fields.map(f => String(f.key))
-
     const items = fields.map(field => {
       const fname = field.name
-      const fkey = String(field.key)
+      const fkey = String(fname)
 
       // Lightweight internal check: avoid emitting debug logs in production
       const titleValue = form.getFieldValue(['securableObjects', fname, 
'fullName'])
@@ -247,7 +297,16 @@ export default function CreateRoleDialog({ ...props }) {
 
     return (
       <>
-        <Collapse defaultActiveKey={activeKeys} accordion={false} 
items={items} onChange={handleChangeCollapse} />
+        <Collapse
+          activeKey={activeCollapseKeys}
+          accordion={false}
+          items={items}
+          onChange={keys => {
+            const nextKeys = Array.isArray(keys) ? keys : [keys]
+            setActiveCollapseKeys(nextKeys)
+            handleChangeCollapse()
+          }}
+        />
 
         <div className='text-center mt-2'>
           <Button
@@ -301,6 +360,7 @@ export default function CreateRoleDialog({ ...props }) {
                   name='name'
                   label='Role Name'
                   rules={[{ required: true }, { type: 'string', max: 64 }, { 
pattern: new RegExp(nameRegex) }]}
+                  messageVariables={{ label: 'role name' }}
                 >
                   <Input placeholder={mismatchName} disabled={!!editRole} />
                 </Form.Item>
diff --git a/web/web/src/app/access/userGroups/AddUserGroupDialog.js 
b/web/web/src/app/access/userGroups/AddUserGroupDialog.js
index e16a311e9e..c10a2efb11 100644
--- a/web/web/src/app/access/userGroups/AddUserGroupDialog.js
+++ b/web/web/src/app/access/userGroups/AddUserGroupDialog.js
@@ -94,6 +94,7 @@ export default function AddUserGroupDialog({ ...props }) {
             name='name'
             label='User Group Name'
             rules={[{ required: true }, { type: 'string', max: 64 }, { 
pattern: new RegExp(nameRegex) }]}
+            messageVariables={{ label: 'user group name' }}
           >
             <Input placeholder={mismatchName} />
           </Form.Item>
diff --git a/web/web/src/app/access/userGroups/GrantRolesForUserGroupDialog.js 
b/web/web/src/app/access/userGroups/GrantRolesForUserGroupDialog.js
index e2ec0b45ee..70e1dc610e 100644
--- a/web/web/src/app/access/userGroups/GrantRolesForUserGroupDialog.js
+++ b/web/web/src/app/access/userGroups/GrantRolesForUserGroupDialog.js
@@ -23,7 +23,7 @@ import { validateMessages } from '@/config'
 import { useResetFormOnCloseModal } from '@/lib/hooks/use-reset'
 import { useAppSelector, useAppDispatch } from '@/lib/hooks/useStore'
 import { fetchRoles } from '@/lib/store/roles'
-import { fetchUserGroups, grantRolesForUserGroup } from 
'@/lib/store/userGroups'
+import { grantRolesForUserGroup, revokeRolesForUserGroup } from 
'@/lib/store/userGroups'
 
 const { Paragraph } = Typography
 const { Option } = Select
diff --git a/web/web/src/app/access/users/AddUserDialog.js 
b/web/web/src/app/access/users/AddUserDialog.js
index 197974a347..766410d321 100644
--- a/web/web/src/app/access/users/AddUserDialog.js
+++ b/web/web/src/app/access/users/AddUserDialog.js
@@ -99,6 +99,7 @@ export default function AddUserDialog({ ...props }) {
               { type: 'string', max: 64 },
               { pattern: new RegExp(usernameRegex), message: mismatchName }
             ]}
+            messageVariables={{ label: 'name' }}
           >
             <Input placeholder={mismatchName} />
           </Form.Item>
diff --git a/web/web/src/app/catalogs/rightContent/CreateCatalogDialog.js 
b/web/web/src/app/catalogs/rightContent/CreateCatalogDialog.js
index 2cac92bd8a..6dad082668 100644
--- a/web/web/src/app/catalogs/rightContent/CreateCatalogDialog.js
+++ b/web/web/src/app/catalogs/rightContent/CreateCatalogDialog.js
@@ -489,6 +489,7 @@ export default function CreateCatalogDialog({ ...props }) {
                       name='name'
                       label='Catalog Name'
                       rules={[{ required: true }, { type: 'string', max: 64 }, 
{ pattern: new RegExp(nameRegex) }]}
+                      messageVariables={{ label: 'catalog name' }}
                       data-refer='catalog-name-field'
                     >
                       <Input placeholder={mismatchName} disabled={!init} />
@@ -502,6 +503,7 @@ export default function CreateCatalogDialog({ ...props }) {
                             label={prop.label}
                             key={idx}
                             rules={[{ required: prop.required }]}
+                            messageVariables={{ label: 
prop.label.toLowerCase() }}
                             initialValue={prop.value}
                           >
                             {prop.select ? (
diff --git a/web/web/src/app/catalogs/rightContent/CreateFilesetDialog.js 
b/web/web/src/app/catalogs/rightContent/CreateFilesetDialog.js
index fe5a63fee6..da357f5ed3 100644
--- a/web/web/src/app/catalogs/rightContent/CreateFilesetDialog.js
+++ b/web/web/src/app/catalogs/rightContent/CreateFilesetDialog.js
@@ -246,6 +246,7 @@ export default function CreateFilesetDialog({ ...props }) {
                   name='name'
                   label='Fileset Name'
                   rules={[{ required: true }, { type: 'string', max: 64 }, { 
pattern: new RegExp(nameRegex) }]}
+                  messageVariables={{ label: 'fileset name' }}
                 >
                   <Input data-refer='fileset-name-field' 
placeholder={mismatchName} disabled={init} />
                 </Form.Item>
diff --git a/web/web/src/app/catalogs/rightContent/CreateSchemaDialog.js 
b/web/web/src/app/catalogs/rightContent/CreateSchemaDialog.js
index 2e290a5106..d9910e3b88 100644
--- a/web/web/src/app/catalogs/rightContent/CreateSchemaDialog.js
+++ b/web/web/src/app/catalogs/rightContent/CreateSchemaDialog.js
@@ -215,6 +215,7 @@ export default function CreateSchemaDialog({ ...props }) {
                   label='Schema Name'
                   data-refer='schema-name-field'
                   rules={[{ required: true }, { type: 'string', max: 64 }, { 
pattern: new RegExp(nameRegex) }]}
+                  messageVariables={{ label: 'schema name' }}
                 >
                   <Input placeholder={mismatchName} disabled={editSchema} />
                 </Form.Item>
diff --git a/web/web/src/app/catalogs/rightContent/CreateTableDialog.js 
b/web/web/src/app/catalogs/rightContent/CreateTableDialog.js
index dc34685399..26c34e8ee0 100644
--- a/web/web/src/app/catalogs/rightContent/CreateTableDialog.js
+++ b/web/web/src/app/catalogs/rightContent/CreateTableDialog.js
@@ -72,6 +72,7 @@ import { capitalizeFirstLetter, genUpdates } from 
'@/lib/utils'
 import { cn } from '@/lib/utils/tailwind'
 import { createTable, updateTable, getTableDetails } from 
'@/lib/store/metalakes'
 import { useAppDispatch } from '@/lib/hooks/useStore'
+import { includes } from 'lodash-es'
 
 const { Paragraph } = Typography
 const { TextArea } = Input
@@ -585,7 +586,7 @@ export default function CreateTableDialog({ ...props }) {
                   column['defaultValue'] = {
                     type: 'literal',
                     dataType: col.defaultValue?.dataType || 'string',
-                    value: col.defaultValue?.value
+                    value: ['', 'NULL', 
undefined].includes(col.defaultValue?.value) ? 'NULL' : col.defaultValue?.value
                   }
               }
             }
@@ -1299,11 +1300,8 @@ export default function CreateTableDialog({ ...props }) {
                 <Form.Item
                   name='name'
                   label='Table Name'
-                  rules={[
-                    { required: true, message: `Please enter the table name!` 
},
-                    { type: 'string', max: 64 },
-                    { pattern: new RegExp(nameRegex) }
-                  ]}
+                  messageVariables={{ label: 'table name' }}
+                  rules={[{ required: true }, { type: 'string', max: 64 }, { 
pattern: new RegExp(nameRegex) }]}
                 >
                   <Input data-refer='table-name-field' 
placeholder={mismatchName} disabled={init} />
                 </Form.Item>
diff --git a/web/web/src/app/catalogs/rightContent/CreateTopicDialog.js 
b/web/web/src/app/catalogs/rightContent/CreateTopicDialog.js
index 4a2646ea5c..c9c7fe962f 100644
--- a/web/web/src/app/catalogs/rightContent/CreateTopicDialog.js
+++ b/web/web/src/app/catalogs/rightContent/CreateTopicDialog.js
@@ -187,6 +187,7 @@ export default function CreateTopicDialog({ ...props }) {
                   name='name'
                   label='Topic Name'
                   rules={[{ required: true }, { type: 'string', max: 64 }, { 
pattern: new RegExp(nameRegex) }]}
+                  messageVariables={{ label: 'topic name' }}
                 >
                   <Input data-refer='topic-name-field' 
placeholder={mismatchName} disabled={!!editTopic} />
                 </Form.Item>
diff --git a/web/web/src/app/catalogs/rightContent/LinkVersionDialog.js 
b/web/web/src/app/catalogs/rightContent/LinkVersionDialog.js
index 3231fe6fe2..472444704e 100644
--- a/web/web/src/app/catalogs/rightContent/LinkVersionDialog.js
+++ b/web/web/src/app/catalogs/rightContent/LinkVersionDialog.js
@@ -55,6 +55,7 @@ export default function LinkVersionDialog({ ...props }) {
   const [form] = Form.useForm()
   const values = Form.useWatch([], form)
   const urisItems = Form.useWatch(['uris'], form)
+  const aliasesItems = Form.useWatch(['aliases'], form)
   const defaultUriName = Form.useWatch(['properties'], form)?.filter(item => 
item?.key === 'default-uri-name')[0]
   const dispatch = useAppDispatch()
 
@@ -235,7 +236,7 @@ export default function LinkVersionDialog({ ...props }) {
                       <div className='flex flex-col gap-2'>
                         {fields.map(({ key, name, ...restField }) => (
                           <Form.Item label={null} 
className='align-items-center mb-1' key={key}>
-                            <Flex gap='small' align='start'>
+                            <Flex gap='small' align='center'>
                               <Form.Item
                                 className='mb-0 w-full grow'
                                 name={[name, 'name']}
@@ -281,21 +282,36 @@ export default function LinkVersionDialog({ ...props }) {
                                   </Tooltip>
                                 </Form.Item>
                               )}
-                              {name === 0 ? (
-                                <Icons.Plus
-                                  className='size-8 cursor-pointer 
text-gray-400 hover:text-defaultPrimary'
-                                  onClick={() => {
-                                    subOpt.add({ name: '', uri: '', 
defaultUri: false })
-                                  }}
-                                />
-                              ) : (
-                                <Icons.Minus
-                                  className='size-8 cursor-pointer 
text-gray-400 hover:text-defaultPrimary'
-                                  onClick={() => {
-                                    subOpt.remove(name)
-                                  }}
-                                />
-                              )}
+                              <div className='flex w-10 shrink-0 items-center 
justify-start gap-2'>
+                                {name === 0 ? (
+                                  <>
+                                    <Icons.Plus
+                                      className='size-4 cursor-pointer 
text-gray-400 hover:text-defaultPrimary'
+                                      onClick={() => {
+                                        subOpt.add({ name: '', uri: '', 
defaultUri: false })
+                                      }}
+                                    />
+                                    {urisItems?.length > 1 && (
+                                      <Icons.Minus
+                                        className='size-4 cursor-pointer 
text-gray-400 hover:text-defaultPrimary'
+                                        onClick={() => {
+                                          subOpt.remove(name)
+                                        }}
+                                      />
+                                    )}
+                                  </>
+                                ) : (
+                                  <>
+                                    <span className='inline-block size-4' />
+                                    <Icons.Minus
+                                      className='size-4 cursor-pointer 
text-gray-400 hover:text-defaultPrimary'
+                                      onClick={() => {
+                                        subOpt.remove(name)
+                                      }}
+                                    />
+                                  </>
+                                )}
+                              </div>
                             </Flex>
                           </Form.Item>
                         ))}
@@ -309,7 +325,7 @@ export default function LinkVersionDialog({ ...props }) {
                       <div className='flex flex-col gap-2'>
                         {fields.map(({ key, name, ...restField }) => (
                           <Form.Item label={null} 
className='align-items-center mb-1' key={key}>
-                            <div className='flex items-start gap-2'>
+                            <Flex gap='small' align='center'>
                               <Form.Item
                                 {...restField}
                                 className='mb-0 w-full grow'
@@ -339,24 +355,37 @@ export default function LinkVersionDialog({ ...props }) {
                               >
                                 <Input placeholder={`Alias ${name + 1}`} />
                               </Form.Item>
-                              <Form.Item className='mb-0 grow-0'>
+                              <div className='flex w-10 shrink-0 items-center 
justify-start gap-2'>
                                 {name === 0 ? (
-                                  <Icons.Plus
-                                    className='size-4 cursor-pointer 
text-gray-400 hover:text-defaultPrimary'
-                                    onClick={() => {
-                                      subOpt.add()
-                                    }}
-                                  />
+                                  <>
+                                    <Icons.Plus
+                                      className='size-4 cursor-pointer 
text-gray-400 hover:text-defaultPrimary'
+                                      onClick={() => {
+                                        subOpt.add()
+                                      }}
+                                    />
+                                    {aliasesItems?.length > 1 && (
+                                      <Icons.Minus
+                                        className='size-4 cursor-pointer 
text-gray-400 hover:text-defaultPrimary'
+                                        onClick={() => {
+                                          subOpt.remove(name)
+                                        }}
+                                      />
+                                    )}
+                                  </>
                                 ) : (
-                                  <Icons.Minus
-                                    className='size-4 cursor-pointer 
text-gray-400 hover:text-defaultPrimary'
-                                    onClick={() => {
-                                      subOpt.remove(name)
-                                    }}
-                                  />
+                                  <>
+                                    <span className='inline-block size-4' />
+                                    <Icons.Minus
+                                      className='size-4 cursor-pointer 
text-gray-400 hover:text-defaultPrimary'
+                                      onClick={() => {
+                                        subOpt.remove(name)
+                                      }}
+                                    />
+                                  </>
                                 )}
-                              </Form.Item>
-                            </div>
+                              </div>
+                            </Flex>
                           </Form.Item>
                         ))}
                       </div>
diff --git a/web/web/src/app/catalogs/rightContent/RegisterModelDialog.js 
b/web/web/src/app/catalogs/rightContent/RegisterModelDialog.js
index 0fe81620f9..5c6994d22b 100644
--- a/web/web/src/app/catalogs/rightContent/RegisterModelDialog.js
+++ b/web/web/src/app/catalogs/rightContent/RegisterModelDialog.js
@@ -190,6 +190,7 @@ export default function RegisterModelDialog({ ...props }) {
                   name='name'
                   label='Model Name'
                   rules={[{ required: true }, { type: 'string', max: 64 }, { 
pattern: new RegExp(nameRegex) }]}
+                  messageVariables={{ label: 'model name' }}
                 >
                   <Input placeholder={mismatchName} disabled={init} />
                 </Form.Item>
diff --git 
a/web/web/src/app/catalogs/rightContent/entitiesContent/ModelDetailsPage.js 
b/web/web/src/app/catalogs/rightContent/entitiesContent/ModelDetailsPage.js
index ec777e2cad..b57351f3d1 100644
--- a/web/web/src/app/catalogs/rightContent/entitiesContent/ModelDetailsPage.js
+++ b/web/web/src/app/catalogs/rightContent/entitiesContent/ModelDetailsPage.js
@@ -275,7 +275,7 @@ export default function ModelDetailsPage({ ...props }) {
         }
       }
     ],
-    [currentMetalake]
+    [currentMetalake, catalog, schema, model]
   )
 
   const { resizableColumns, components, tableWidth } = useAntdColumnResize(() 
=> {
diff --git a/web/web/src/app/compliance/policies/CreatePolicyDialog.js 
b/web/web/src/app/compliance/policies/CreatePolicyDialog.js
index c7098aef21..5787979e20 100644
--- a/web/web/src/app/compliance/policies/CreatePolicyDialog.js
+++ b/web/web/src/app/compliance/policies/CreatePolicyDialog.js
@@ -170,6 +170,7 @@ export default function CreatePolicyDialog({ ...props }) {
             name='name'
             label='Policy Name'
             rules={[{ required: true }, { type: 'string', max: 64 }, { 
pattern: new RegExp(nameRegex) }]}
+            messageVariables={{ label: 'policy name' }}
           >
             <Input placeholder={mismatchName} />
           </Form.Item>
diff --git a/web/web/src/app/compliance/tags/CreateTagDialog.js 
b/web/web/src/app/compliance/tags/CreateTagDialog.js
index d85b8493c2..5cd1003016 100644
--- a/web/web/src/app/compliance/tags/CreateTagDialog.js
+++ b/web/web/src/app/compliance/tags/CreateTagDialog.js
@@ -153,6 +153,7 @@ export default function CreateTagDialog({ ...props }) {
             name='name'
             label='Tag Name'
             rules={[{ required: true }, { type: 'string', max: 64 }, { 
pattern: new RegExp(nameRegex) }]}
+            messageVariables={{ label: 'tag name' }}
           >
             <Input placeholder={mismatchName} />
           </Form.Item>
diff --git a/web/web/src/app/jobs/CreateJobDialog.js 
b/web/web/src/app/jobs/CreateJobDialog.js
index 313206ad1f..31b818889a 100644
--- a/web/web/src/app/jobs/CreateJobDialog.js
+++ b/web/web/src/app/jobs/CreateJobDialog.js
@@ -41,6 +41,7 @@ export default function CreateJobDialog({ ...props }) {
   const { open, setOpen, metalake, router, jobTemplateNames } = props
   const [confirmLoading, setConfirmLoading] = useState(false)
   const [isLoading, setIsLoading] = useState(false)
+  const [jobTemplateDetail, setJobTemplateDetail] = useState(null)
   const [form] = Form.useForm()
   const values = Form.useWatch([], form)
   const currentJobTemplate = Form.useWatch('jobTemplateName', form)
@@ -64,24 +65,80 @@ export default function CreateJobDialog({ ...props }) {
   }, [open, jobTemplateNames, currentJobTemplate])
 
   const updateOnChange = (index, newValue) => {
-    const changedTemplate = jobConfValues[index].template
-    if (!changedTemplate) return
+    const changedKey = jobConfValues?.[index]?.confKey
+    if (!changedKey) return
 
-    const updatedJobConf = [...jobConfValues]
+    const updatedJobConf = (jobConfValues || []).map(conf =>
+      conf?.confKey === changedKey ? { ...conf, value: newValue } : conf
+    )
 
-    jobConfValues.forEach(index => {
-      let hasChange = false
-      updatedJobConf.forEach((conf, i) => {
-        if (i !== index && conf?.template === changedTemplate) {
-          updatedJobConf[i] = { ...conf, value: newValue }
-          hasChange = true
-        }
-      })
+    form.setFieldsValue({ jobConf: updatedJobConf })
+  }
+
+  const getPlaceholderEntries = templateDetail => {
+    if (!templateDetail) return []
+
+    const jobType = String(templateDetail.jobType || '').toLowerCase()
+    const stringValues = []
+
+    const pushString = value => {
+      if (typeof value === 'string' && value) {
+        stringValues.push(value)
+      }
+    }
+
+    const pushArray = values => {
+      ;(values || []).forEach(val => pushString(val))
+    }
+
+    const pushObjectValues = obj => {
+      Object.values(obj || {}).forEach(val => pushString(val))
+    }
+
+    pushString(templateDetail.jobType)
+    pushString(templateDetail.comment)
+    pushString(templateDetail.executable)
+    pushArray(templateDetail.arguments)
+    pushObjectValues(templateDetail.environments)
+    pushObjectValues(templateDetail.customFields)
+
+    if (jobType === 'spark') {
+      pushString(templateDetail.className)
+      pushArray(templateDetail.jars)
+      pushArray(templateDetail.files)
+      pushArray(templateDetail.archives)
+      pushObjectValues(templateDetail.configs)
+    }
+
+    if (jobType === 'shell') {
+      pushArray(templateDetail.scripts)
+    }
+
+    const placeholders = stringValues.flatMap(template => 
getJobParamValues(template) || []).filter(Boolean)
+    const uniquePlaceholders = Array.from(new Set(placeholders))
+
+    return uniquePlaceholders.map(name => ({
+      key: name,
+      value: '',
+      confKey: name,
+      template: `{{${name}}}`
+    }))
+  }
 
-      if (hasChange) {
-        form.setFieldsValue({ jobConf: updatedJobConf })
+  const getPlaceholderValueMap = () =>
+    (jobConfValues || []).reduce((acc, item) => {
+      if (item?.confKey) {
+        acc[item.confKey] = item.value || ''
       }
-    })
+
+      return acc
+    }, {})
+
+  const renderTemplateValue = template => {
+    if (!template) return ''
+    const valueMap = getPlaceholderValueMap()
+
+    return template.replace(/\{\{([^}]+)\}\}/g, (_, key) => valueMap[key] || 
`{{${key}}}`)
   }
 
   useEffect(() => {
@@ -90,32 +147,9 @@ export default function CreateJobDialog({ ...props }) {
       try {
         const { payload: jobTemplate } = await dispatch(getJobTemplate({ 
metalake, jobTemplate: currentJobTemplate }))
         setIsLoading(false)
-        form.setFieldValue('jobConf', [])
-        const { arguments: args, environments, customFields } = jobTemplate
-        args.forEach((conf, index) => {
-          form.setFieldValue(['jobConf', index], {
-            key: getJobParamValues(conf)?.[0],
-            value: '',
-            confKey: getJobParamValues(conf)?.[0],
-            template: conf
-          })
-        })
-        Object.entries(environments).forEach(([key, value], index) => {
-          form.setFieldValue(['jobConf', args.length + index], {
-            key,
-            value: '',
-            confKey: getJobParamValues(value)?.[0],
-            template: value
-          })
-        })
-        Object.entries(customFields).forEach(([key, value], index) => {
-          form.setFieldValue(['jobConf', args.length + 
Object.keys(environments).length + index], {
-            key,
-            value: '',
-            confKey: getJobParamValues(value)?.[0],
-            template: value
-          })
-        })
+        setJobTemplateDetail(jobTemplate)
+        const placeholderEntries = getPlaceholderEntries(jobTemplate)
+        form.setFieldsValue({ jobConf: placeholderEntries })
       } catch (error) {
         console.log(error)
         setIsLoading(false)
@@ -166,7 +200,7 @@ export default function CreateJobDialog({ ...props }) {
         okText='Submit'
         maskClosable={false}
         keyboard={false}
-        width={800}
+        width={1000}
         confirmLoading={confirmLoading}
         onCancel={handleCancel}
       >
@@ -179,7 +213,12 @@ export default function CreateJobDialog({ ...props }) {
             name='policyForm'
             validateMessages={validateMessages}
           >
-            <Form.Item name='jobTemplateName' label='Template Name' rules={[{ 
required: true }]}>
+            <Form.Item
+              name='jobTemplateName'
+              label='Template Name'
+              rules={[{ required: true }]}
+              messageVariables={{ label: 'template name' }}
+            >
               <Select
                 popupRender={menu => (
                   <>
@@ -210,47 +249,211 @@ export default function CreateJobDialog({ ...props }) {
                 options={jobTemplateNames.map(template => ({ label: template, 
value: template }))}
               />
             </Form.Item>
-            <Form.Item label='Job Configuration'>
-              <div className='flex flex-col gap-2'>
-                <Form.List name='jobConf'>
-                  {(fields, { add, remove }) => (
+            <div className='relative rounded border border-gray-200'>
+              <div className='pointer-events-none absolute inset-y-0 left-1/2 
w-px -translate-x-1/2 bg-gray-200' />
+              <div className='pointer-events-none absolute left-1/2 top-1/2 
flex size-6 -translate-x-1/2 -translate-y-1/2 items-center justify-center 
rounded-full border border-gray-200 bg-white text-gray-500'>
+                <Icons.iconify icon='mdi:arrow-left' className='size-4' />
+              </div>
+              <div className='grid grid-cols-2 gap-6 p-4'>
+                <div className='space-y-2 max-h-[320px] overflow-y-auto pr-2'>
+                  <Paragraph className='text-sm !mb-0'>Template 
Parameters</Paragraph>
+                  <div>
+                    <Paragraph className='text-[12px] mb-1'>Basic 
Fields</Paragraph>
+                    <div className='pl-4 space-y-1'>
+                      <div className='text-[12px] text-gray-700'>
+                        <span className='text-gray-400'>Job Type:</span> 
{jobTemplateDetail?.jobType || '-'}
+                      </div>
+                      <div className='text-[12px] text-gray-700'>
+                        <span className='text-gray-400'>Comment:</span> 
{jobTemplateDetail?.comment || '-'}
+                      </div>
+                      <div className='text-[12px] text-gray-700'>
+                        <span className='text-gray-400'>Executable:</span>{' '}
+                        {renderTemplateValue(jobTemplateDetail?.executable || 
'') || '-'}
+                      </div>
+                      {jobTemplateDetail?.jobType === 'spark' && (
+                        <div className='text-[12px] text-gray-700'>
+                          <span className='text-gray-400'>Class Name:</span>{' 
'}
+                          {renderTemplateValue(jobTemplateDetail?.className || 
'') || '-'}
+                        </div>
+                      )}
+                    </div>
+                  </div>
+                  <div>
+                    <Paragraph className='text-[12px] 
mb-1'>Arguments</Paragraph>
+                    <div className='pl-4'>
+                      {(jobTemplateDetail?.arguments || []).length > 0 ? (
+                        <div className='space-y-1'>
+                          {(jobTemplateDetail?.arguments || []).map((arg, 
index) => (
+                            <div key={`arg-${index}`} className='text-sm 
text-gray-700'>
+                              {renderTemplateValue(arg)}
+                            </div>
+                          ))}
+                        </div>
+                      ) : (
+                        <div className='text-sm text-gray-400'>No 
arguments</div>
+                      )}
+                    </div>
+                  </div>
+                  <div>
+                    <Paragraph className='text-[12px] mb-1'>Environment 
Variables</Paragraph>
+                    <div className='pl-4'>
+                      {Object.keys(jobTemplateDetail?.environments || 
{}).length > 0 ? (
+                        <div className='space-y-1'>
+                          {Object.entries(jobTemplateDetail?.environments || 
{}).map(([key, value]) => (
+                            <div key={`env-${key}`} className='text-sm 
text-gray-700'>
+                              <span className='text-gray-400'>{key}:</span> 
{renderTemplateValue(value)}
+                            </div>
+                          ))}
+                        </div>
+                      ) : (
+                        <div className='text-[12px] text-gray-400'>No 
environment variables</div>
+                      )}
+                    </div>
+                  </div>
+                  <div>
+                    <Paragraph className='text-[12px] mb-1'>Custom 
Fields</Paragraph>
+                    <div className='pl-4'>
+                      {Object.keys(jobTemplateDetail?.customFields || 
{}).length > 0 ? (
+                        <div className='space-y-1'>
+                          {Object.entries(jobTemplateDetail?.customFields || 
{}).map(([key, value]) => (
+                            <div key={`custom-${key}`} className='text-sm 
text-gray-700'>
+                              <span className='text-gray-400'>{key}:</span> 
{renderTemplateValue(value)}
+                            </div>
+                          ))}
+                        </div>
+                      ) : (
+                        <div className='text-[12px] text-gray-400'>No custom 
fields</div>
+                      )}
+                    </div>
+                  </div>
+                  {jobTemplateDetail?.jobType === 'spark' && (
                     <>
-                      {fields.map(({ key, name, ...restField }) => (
-                        <Form.Item label={null} className='align-items-center 
mb-1' key={key}>
-                          <Flex gap='small' align='start' key={key}>
-                            <Form.Item
-                              {...restField}
-                              name={[name, 'key']}
-                              rules={[{ required: true, message: 'Please enter 
the job config key!' }]}
-                              className='mb-0 w-full grow'
-                            >
-                              <Input disabled placeholder='Job Config Key' />
-                            </Form.Item>
-                            <Form.Item
-                              {...restField}
-                              name={[name, 'value']}
-                              rules={[{ required: true, message: 'Please enter 
the job config value!' }]}
-                              className='mb-0 w-full grow'
-                            >
-                              <Input
-                                placeholder={form.getFieldValue(['jobConf', 
name, 'template']) || 'Job Config Value'}
-                                onChange={e => updateOnChange(name, 
e.target.value)}
-                              />
-                            </Form.Item>
-                            <Form.Item className='mb-0 grow-0'>
-                              <Icons.Minus
-                                className='size-8 cursor-pointer text-gray-400 
hover:text-defaultPrimary'
-                                onClick={() => remove(name)}
-                              />
-                            </Form.Item>
-                          </Flex>
-                        </Form.Item>
-                      ))}
+                      <div>
+                        <Paragraph className='text-[12px] 
mb-1'>Jars</Paragraph>
+                        <div className='pl-4'>
+                          {(jobTemplateDetail?.jars || []).length > 0 ? (
+                            <div className='space-y-1'>
+                              {(jobTemplateDetail?.jars || []).map((item, 
index) => (
+                                <div key={`jar-${index}`} className='text-sm 
text-gray-700'>
+                                  {renderTemplateValue(item)}
+                                </div>
+                              ))}
+                            </div>
+                          ) : (
+                            <div className='text-[12px] text-gray-400'>No 
jars</div>
+                          )}
+                        </div>
+                      </div>
+                      <div>
+                        <Paragraph className='text-[12px] 
mb-1'>Files</Paragraph>
+                        <div className='pl-4'>
+                          {(jobTemplateDetail?.files || []).length > 0 ? (
+                            <div className='space-y-1'>
+                              {(jobTemplateDetail?.files || []).map((item, 
index) => (
+                                <div key={`file-${index}`} className='text-sm 
text-gray-700'>
+                                  {renderTemplateValue(item)}
+                                </div>
+                              ))}
+                            </div>
+                          ) : (
+                            <div className='text-[12px] text-gray-400'>No 
files</div>
+                          )}
+                        </div>
+                      </div>
+                      <div>
+                        <Paragraph className='text-[12px] 
mb-1'>Archives</Paragraph>
+                        <div className='pl-4'>
+                          {(jobTemplateDetail?.archives || []).length > 0 ? (
+                            <div className='space-y-1'>
+                              {(jobTemplateDetail?.archives || []).map((item, 
index) => (
+                                <div key={`archive-${index}`} 
className='text-sm text-gray-700'>
+                                  {renderTemplateValue(item)}
+                                </div>
+                              ))}
+                            </div>
+                          ) : (
+                            <div className='text-[12px] text-gray-400'>No 
archives</div>
+                          )}
+                        </div>
+                      </div>
+                      <div>
+                        <Paragraph className='text-[12px] 
mb-1'>Configs</Paragraph>
+                        <div className='pl-4'>
+                          {Object.keys(jobTemplateDetail?.configs || 
{}).length > 0 ? (
+                            <div className='space-y-1'>
+                              {Object.entries(jobTemplateDetail?.configs || 
{}).map(([key, value]) => (
+                                <div key={`config-${key}`} 
className='text-[12px] text-gray-700'>
+                                  <span 
className='text-gray-400'>{key}:</span> {renderTemplateValue(value)}
+                                </div>
+                              ))}
+                            </div>
+                          ) : (
+                            <div className='text-[12px] text-gray-400'>No 
configs</div>
+                          )}
+                        </div>
+                      </div>
                     </>
                   )}
-                </Form.List>
+                  {jobTemplateDetail?.jobType === 'shell' && (
+                    <div>
+                      <Paragraph className='text-[12px] 
mb-1'>Scripts</Paragraph>
+                      <div className='pl-4'>
+                        {(jobTemplateDetail?.scripts || []).length > 0 ? (
+                          <div className='space-y-1'>
+                            {(jobTemplateDetail?.scripts || []).map((item, 
index) => (
+                              <div key={`script-${index}`} className='text-sm 
text-gray-700'>
+                                {renderTemplateValue(item)}
+                              </div>
+                            ))}
+                          </div>
+                        ) : (
+                          <div className='text-[12px] text-gray-400'>No 
scripts</div>
+                        )}
+                      </div>
+                    </div>
+                  )}
+                </div>
+                <div className='pl-2 max-h-[320px] overflow-y-auto pr-2'>
+                  <Form.Item label='Job Configuration'>
+                    <div className='flex flex-col gap-2'>
+                      <Form.List name='jobConf'>
+                        {fields => (
+                          <>
+                            {fields.map(({ key, name, ...restField }) => (
+                              <Form.Item label={null} 
className='align-items-center mb-0' key={key}>
+                                <Flex gap='small' align='start' key={key}>
+                                  <Form.Item
+                                    {...restField}
+                                    name={[name, 'key']}
+                                    rules={[{ required: true, message: 'Please 
enter the job config key!' }]}
+                                    className='mb-0 w-full grow'
+                                  >
+                                    <Input disabled placeholder='Job Config 
Key' />
+                                  </Form.Item>
+                                  <Form.Item
+                                    {...restField}
+                                    name={[name, 'value']}
+                                    rules={[{ required: true, message: 'Please 
enter the job config value!' }]}
+                                    className='mb-0 w-full grow'
+                                  >
+                                    <Input
+                                      placeholder={
+                                        form.getFieldValue(['jobConf', name, 
'template']) || 'Job Config Value'
+                                      }
+                                      onChange={e => updateOnChange(name, 
e.target.value)}
+                                    />
+                                  </Form.Item>
+                                </Flex>
+                              </Form.Item>
+                            ))}
+                          </>
+                        )}
+                      </Form.List>
+                    </div>
+                  </Form.Item>
+                </div>
               </div>
-            </Form.Item>
+            </div>
           </Form>
         </Spin>
         {openTemplate && (
diff --git a/web/web/src/app/jobs/RegisterJobTemplateDialog.js 
b/web/web/src/app/jobs/RegisterJobTemplateDialog.js
index 02b781cc89..9968c3af53 100644
--- a/web/web/src/app/jobs/RegisterJobTemplateDialog.js
+++ b/web/web/src/app/jobs/RegisterJobTemplateDialog.js
@@ -239,6 +239,7 @@ export default function RegisterJobTemplateDialog({ 
...props }) {
                   name='name'
                   label='Template Name'
                   rules={[{ required: true }, { type: 'string', max: 64 }, { 
pattern: new RegExp(nameRegex) }]}
+                  messageVariables={{ label: 'template name' }}
                 >
                   <Input placeholder={mismatchName} />
                 </Form.Item>
@@ -256,10 +257,10 @@ export default function RegisterJobTemplateDialog({ 
...props }) {
                   rules={[{ required: true, message: 'Please enter the 
executable!' }]}
                   label='Executable'
                 >
-                  <Input />
+                  <Input placeholder='e.g. /path/to/my_script.sh' />
                 </Form.Item>
                 <Form.Item name='arguments' label='Arguments'>
-                  <Select mode='tags' tokenSeparators={[',']} />
+                  <Select mode='tags' tokenSeparators={[',']} 
placeholder='e.g. {{arg1}},{{arg2}}' />
                 </Form.Item>
                 <Form.Item label='Environment Variables'>
                   <div className='flex flex-col gap-2'>
@@ -355,16 +356,28 @@ export default function RegisterJobTemplateDialog({ 
...props }) {
                       rules={[{ required: true, message: 'Please enter the 
class name!' }]}
                       label='Class Name'
                     >
-                      <Input />
+                      <Input placeholder='e.g. com.example.MySparkApp' />
                     </Form.Item>
                     <Form.Item name='jars' label='Jars'>
-                      <Select mode='tags' tokenSeparators={[',']} />
+                      <Select
+                        mode='tags'
+                        tokenSeparators={[',']}
+                        placeholder='e.g. 
/path/to/dependency1.jar,/path/to/dependency2.jar'
+                      />
                     </Form.Item>
                     <Form.Item name='files' label='Files'>
-                      <Select mode='tags' tokenSeparators={[',']} />
+                      <Select
+                        mode='tags'
+                        tokenSeparators={[',']}
+                        placeholder='e.g. 
/path/to/file1.txt,/path/to/file2.txt'
+                      />
                     </Form.Item>
                     <Form.Item name='archives' label='Archives'>
-                      <Select mode='tags' tokenSeparators={[',']} />
+                      <Select
+                        mode='tags'
+                        tokenSeparators={[',']}
+                        placeholder='e.g. 
/path/to/archive1.zip,/path/to/archive2.zip'
+                      />
                     </Form.Item>
                     <Form.Item label='Configs'>
                       <div className='flex flex-col gap-2'>
diff --git a/web/web/src/app/metalakes/CreateMetalakeDialog.js 
b/web/web/src/app/metalakes/CreateMetalakeDialog.js
index 2905dc6441..20c9e79339 100644
--- a/web/web/src/app/metalakes/CreateMetalakeDialog.js
+++ b/web/web/src/app/metalakes/CreateMetalakeDialog.js
@@ -144,6 +144,7 @@ export default function CreateMetalakeDialog({ ...props }) {
               label='Name'
               data-refer='metalake-name-field'
               rules={[{ required: true }, { type: 'string', max: 64 }, { 
pattern: new RegExp(nameRegex) }]}
+              messageVariables={{ label: 'name' }}
             >
               <Input placeholder={validateMessages.pattern?.mismatch} />
             </Form.Item>
diff --git a/web/web/src/app/rootLayout/UserSetting.js 
b/web/web/src/app/rootLayout/UserSetting.js
index 42aff8ce04..22b533ea73 100644
--- a/web/web/src/app/rootLayout/UserSetting.js
+++ b/web/web/src/app/rootLayout/UserSetting.js
@@ -26,7 +26,7 @@ import { usePathname, useSearchParams, useRouter } from 
'next/navigation'
 import { cn } from '@/lib/utils/tailwind'
 import { useAppSelector, useAppDispatch } from '@/lib/hooks/useStore'
 import Loading from '@/components/Loading'
-import { fetchMetalakes } from '@/lib/store/metalakes'
+import { fetchMetalakes, resetMetalakeStore } from '@/lib/store/metalakes'
 import { logoutAction } from '@/lib/store/auth'
 import { oauthProviderFactory } from '@/lib/auth/providers/factory'
 
@@ -110,6 +110,9 @@ export default function UserSetting() {
                   const params = new URLSearchParams(searchParams.toString())
                   params.set('metalake', metalake.name)
 
+                  // Reset metalake store to avoid data inconsistency
+                  dispatch(resetMetalakeStore())
+
                   // Preserve pathname and only update metalake in query string
                   router.push(`${pathname}?${params.toString()}`)
                 }}
diff --git a/web/web/src/components/SecurableObjectFormFields.js 
b/web/web/src/components/SecurableObjectFormFields.js
index 9e7dcad2ad..d754beaca5 100644
--- a/web/web/src/components/SecurableObjectFormFields.js
+++ b/web/web/src/components/SecurableObjectFormFields.js
@@ -20,7 +20,7 @@
 import React, { useEffect, useLayoutEffect, useState, useRef } from 'react'
 import { Form, Input, Select, Checkbox, Spin } from 'antd'
 import { to } from '@/lib/utils'
-import { useAppDispatch } from '@/lib/hooks/useStore'
+import { useAppDispatch, useAppSelector } from '@/lib/hooks/useStore'
 import {
   fetchCatalogs,
   fetchSchemas,
@@ -45,6 +45,7 @@ export default function SecurableObjectFormFields({ 
fieldName, fieldKey, metalak
   const currentFull = String(currentFullRaw || '')
   const displayValue = currentFull
   const dispatch = useAppDispatch()
+  const store = useAppSelector(state => state.metalakes)
 
   const parts = String(currentFull || '')
     .split('.')
@@ -70,6 +71,36 @@ export default function SecurableObjectFormFields({ 
fieldName, fieldKey, metalak
   const [localRemoteOptions, setLocalRemoteOptions] = useState({})
   const [localRemoteLoading, setLocalRemoteLoading] = useState({})
   const catalogOptions = localRemoteOptions['catalog'] || []
+  const rootRef = useRef(null)
+
+  const isElementVisibleInViewport = el => {
+    if (!el) return true
+    const rect = el.getBoundingClientRect()
+
+    return rect.top >= 0 && rect.bottom <= (window.innerHeight || 
document.documentElement.clientHeight)
+  }
+
+  useEffect(() => {
+    const handleScrollOrResize = () => {
+      const activeEl = document.activeElement
+      if (!activeEl || !rootRef.current) return
+
+      const isFormControl =
+        activeEl.getAttribute?.('role') === 'combobox' || ['INPUT', 
'TEXTAREA'].includes(activeEl.tagName)
+
+      if (isFormControl && rootRef.current.contains(activeEl) && 
!isElementVisibleInViewport(activeEl)) {
+        activeEl.blur()
+      }
+    }
+
+    window.addEventListener('scroll', handleScrollOrResize, true)
+    window.addEventListener('resize', handleScrollOrResize, true)
+
+    return () => {
+      window.removeEventListener('scroll', handleScrollOrResize, true)
+      window.removeEventListener('resize', handleScrollOrResize, true)
+    }
+  }, [])
 
   const loadOptionsForType = async type => {
     if (!metalake) return
@@ -123,6 +154,30 @@ export default function SecurableObjectFormFields({ 
fieldName, fieldKey, metalak
     }
   }
 
+  const getCatalogTypeForName = async catalogName => {
+    if (!catalogName) return null
+
+    const cachedType =
+      catalogOptions.find(c => c.value === catalogName)?.catalogType ||
+      store.catalogs.find(c => c.value === catalogName)?.catalogType
+    if (cachedType) return cachedType
+
+    const [err, res] = await to(dispatch(fetchCatalogs({ metalake })))
+    if (err || !res) return null
+
+    const catalogs = res?.payload?.catalogs || []
+    const matched = catalogs.find(c => c.name === catalogName)
+
+    if (matched?.type) {
+      setLocalRemoteOptions(prev => ({
+        ...prev,
+        catalog: catalogs.map(c => ({ label: c.name, value: c.name, 
catalogType: c.type }))
+      }))
+    }
+
+    return matched?.type || null
+  }
+
   const getPrivilegeGroupsForType = async (type, catalogName) => {
     if (!type) return []
 
@@ -147,7 +202,8 @@ export default function SecurableObjectFormFields({ 
fieldName, fieldKey, metalak
       if (schemaGroup) groups.push(schemaGroup)
 
       // If we have a cached catalog type, append the corresponding resource 
group
-      const catalogType = catalogName ? catalogOptions.filter(c => c.value === 
catalogName)[0]?.catalogType : null
+      const catalogType = await getCatalogTypeForName(catalogName)
+
       if (catalogType) {
         if (catalogType === 'relational') {
           const tableGroup = privilegeOptions.find(g => g.label === 'Table 
privileges')
@@ -174,7 +230,7 @@ export default function SecurableObjectFormFields({ 
fieldName, fieldKey, metalak
         if (useSchemaOptions.length > 0) groups.push({ label: 
schemaGroup.label, options: useSchemaOptions })
       }
 
-      const catalogType = catalogName ? catalogOptions.filter(c => c.value === 
catalogName)[0]?.catalogType : null
+      const catalogType = await getCatalogTypeForName(catalogName)
       if (catalogType) {
         if (catalogType === 'relational') {
           const tableGroup = privilegeOptions.find(g => g.label === 'Table 
privileges')
@@ -490,15 +546,26 @@ export default function SecurableObjectFormFields({ 
fieldName, fieldKey, metalak
   const denyAllOptionValues = getAllOptionValues('denyPrivileges')
 
   return (
-    <div className='flex flex-col gap-2'>
+    <div className='flex flex-col gap-2' ref={rootRef}>
       <div>
-        <Form.Item name={[fieldName, 'type']} label='Type' rules={[{ required: 
true }]} style={{ marginBottom: 8 }}>
+        <Form.Item
+          name={[fieldName, 'type']}
+          label='Type'
+          rules={[{ required: true }]}
+          messageVariables={{ label: 'type' }}
+          style={{ marginBottom: 8 }}
+        >
           <Select placeholder='Please select type' options={privilegeTypes} />
         </Form.Item>
       </div>
 
       <div>
-        <Form.Item name={[fieldName, 'fullName']} label='Full Name' rules={[{ 
required: true }]}>
+        <Form.Item
+          name={[fieldName, 'fullName']}
+          label='Full Name'
+          rules={[{ required: true }]}
+          messageVariables={{ label: 'full name' }}
+        >
           {(() => {
             if (currentType === 'metalake') {
               return <Input value={metalake} disabled />
diff --git a/web/web/src/components/SetOwnerDialog.js 
b/web/web/src/components/SetOwnerDialog.js
index 10aa704b6f..2957c256f9 100644
--- a/web/web/src/components/SetOwnerDialog.js
+++ b/web/web/src/components/SetOwnerDialog.js
@@ -76,7 +76,7 @@ export default function SetOwnerDialog({ ...props }) {
         await dispatch(setEntityOwner({ metalake, metadataObjectType, 
metadataObjectFullName, data: submitData }))
         setConfirmLoading(false)
         mutateOwner && mutateOwner()
-        setOpen(false)
+        setOpen(false, true)
       })
       .catch(info => {
         console.error(info)
@@ -85,7 +85,7 @@ export default function SetOwnerDialog({ ...props }) {
   }
 
   const handleCancel = () => {
-    setOpen(false)
+    setOpen(false, false)
   }
 
   return (
diff --git a/web/web/src/config/index.js b/web/web/src/config/index.js
index 82d9908400..eed62cf22e 100644
--- a/web/web/src/config/index.js
+++ b/web/web/src/config/index.js
@@ -30,17 +30,17 @@ export const mismatchUsername =
 export const mismatchForKey =
   'Must start with a letter/underscore(_), contain only alphanumeric 
characters (Aa-Zz,0-9) or underscores (_), hyphens(-), or dots(.)!'
 
-export const validateMessages = () => ({
-  required: `Please enter the ${label}!`,
+export const validateMessages = {
+  required: 'Please enter the ${label}!',
   string: {
-    max: `The ${label} must be less than ${max} characters!`
+    max: 'The ${label} must be less than ${max} characters!'
   },
   pattern: {
     mismatch: mismatchName,
     mismatchusername: mismatchUsername,
     mismatchforkey: mismatchForKey
   }
-})
+}
 
 export const supportedObjectTypesMap = {
   CATALOG: 'catalog',

Reply via email to