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

jshao 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 70494b5e1a [#9807][#9976][#9978] web(UI): support details drawer 
componet for tag/policy/job template basic info and associated info (#9931)
70494b5e1a is described below

commit 70494b5e1a82c513205fedbeafea253a573dff57
Author: Qian Xia <[email protected]>
AuthorDate: Thu Feb 26 14:01:37 2026 +0800

    [#9807][#9976][#9978] web(UI): support details drawer componet for 
tag/policy/job template basic info and associated info (#9931)
    
    ### What changes were proposed in this pull request?
    <img width="2890" height="1862" alt="image"
    
src="https://github.com/user-attachments/assets/8d3ee1fb-6da6-4cf4-b435-5f394c0d7b19";
    />
    <img width="2896" height="1862" alt="image"
    
src="https://github.com/user-attachments/assets/31f3ab59-b916-465b-bb19-2c339929a8d3";
    />
    <img width="2896" height="1878" alt="image"
    
src="https://github.com/user-attachments/assets/56f5289e-d1e1-47b7-a452-9fe34b522a5a";
    />
    <img width="3168" height="1862" alt="image"
    
src="https://github.com/user-attachments/assets/9cd315e5-44ed-4d4e-9f17-2b38e6af60c3";
    />
    
    
    ### Why are the changes needed?
    N/A
    
    Fix: #9807 , #9976, #9978
    
    ### Does this PR introduce _any_ user-facing change?
    N/A
    
    ### How was this patch tested?
    manually
    
    ---------
    
    Co-authored-by: Jerry Shao <[email protected]>
---
 .github/workflows/frontend-integration-test.yml    |   2 +-
 .../test/web/ui/CatalogsPageDorisTest.java         |   2 +-
 .../test/web/ui/pages/CatalogsPage.java            |  37 ++
 web-v2/web/src/app/catalogs/page.js                |  50 ++-
 .../catalogs/rightContent/CreateCatalogDialog.js   |   4 +-
 .../catalogs/rightContent/CreateSchemaDialog.js    |  20 +-
 .../app/catalogs/rightContent/CreateTableDialog.js |   1 +
 .../entitiesContent/CatalogDetailsPage.js          |   3 +-
 .../entitiesContent/FilesetDetailsPage.js          |   4 +-
 .../entitiesContent/ModelDetailsPage.js            | 108 +++---
 .../entitiesContent/SchemaDetailsPage.js           |  10 +-
 .../entitiesContent/TableDetailsPage.js            |   4 +-
 .../entitiesContent/TopicDetailsPage.js            |   2 +-
 web-v2/web/src/app/compliance/policies/page.js     | 191 +++++-----
 web-v2/web/src/app/compliance/tags/page.js         |  61 +++-
 web-v2/web/src/app/jobTemplates/page.js            | 394 ++++++++++++---------
 web-v2/web/src/app/jobs/page.js                    |  53 ++-
 .../web/src/components/EntityPropertiesFormItem.js | 294 +++++++--------
 18 files changed, 708 insertions(+), 532 deletions(-)

diff --git a/.github/workflows/frontend-integration-test.yml 
b/.github/workflows/frontend-integration-test.yml
index a1e7ce5125..d9f4232bc1 100644
--- a/.github/workflows/frontend-integration-test.yml
+++ b/.github/workflows/frontend-integration-test.yml
@@ -37,7 +37,7 @@ jobs:
               - scripts/**
               - server/**
               - server-common/**
-              - web/**
+              - web-v2/**
               - build.gradle.kts
               - gradle.properties
               - gradlew
diff --git 
a/web-v2/integration-test/src/test/java/org/apache/gravitino/integration/test/web/ui/CatalogsPageDorisTest.java
 
b/web-v2/integration-test/src/test/java/org/apache/gravitino/integration/test/web/ui/CatalogsPageDorisTest.java
index 1bea908d2c..2fe2f937be 100644
--- 
a/web-v2/integration-test/src/test/java/org/apache/gravitino/integration/test/web/ui/CatalogsPageDorisTest.java
+++ 
b/web-v2/integration-test/src/test/java/org/apache/gravitino/integration/test/web/ui/CatalogsPageDorisTest.java
@@ -262,7 +262,7 @@ public class CatalogsPageDorisTest extends BaseWebIT {
   @Test
   @Order(7)
   public void testBackHomePage() throws InterruptedException {
-    clickAndWait(catalogsPage.backHomeBtn);
+    catalogsPage.clickBackHomeBtn();
     Assertions.assertTrue(catalogsPage.verifyBackHomePage());
   }
 }
diff --git 
a/web-v2/integration-test/src/test/java/org/apache/gravitino/integration/test/web/ui/pages/CatalogsPage.java
 
b/web-v2/integration-test/src/test/java/org/apache/gravitino/integration/test/web/ui/pages/CatalogsPage.java
index e1ac9c5c3a..5ea601cfd6 100644
--- 
a/web-v2/integration-test/src/test/java/org/apache/gravitino/integration/test/web/ui/pages/CatalogsPage.java
+++ 
b/web-v2/integration-test/src/test/java/org/apache/gravitino/integration/test/web/ui/pages/CatalogsPage.java
@@ -598,6 +598,43 @@ public class CatalogsPage extends BaseWebIT {
     }
   }
 
+  public void clickBackHomeBtn() {
+    try {
+      String urlBefore = driver.getCurrentUrl();
+      WebDriverWait shortWait = new WebDriverWait(driver, 
Duration.ofSeconds(10));
+      WebDriverWait wait = new WebDriverWait(driver, 
Duration.ofSeconds(MAX_TIMEOUT));
+
+      // Strategy 1: Normal click
+      wait.until(ExpectedConditions.elementToBeClickable(backHomeBtn));
+      clickAndWait(backHomeBtn);
+      if (isUrlChanged(urlBefore, shortWait)) {
+        LOG.info("Back home navigation succeeded via normal click");
+        return;
+      }
+      LOG.warn("Normal back home click did not navigate, trying JavaScript 
click...");
+
+      // Strategy 2: JavaScript click
+      ((JavascriptExecutor) driver).executeScript("arguments[0].click();", 
backHomeBtn);
+      Thread.sleep(ACTION_SLEEP * 1000);
+      if (isUrlChanged(urlBefore, shortWait)) {
+        LOG.info("Back home navigation succeeded via JavaScript click");
+        return;
+      }
+      LOG.warn("JavaScript back home click did not navigate, trying direct URL 
navigation...");
+
+      // Strategy 3: Extract href and navigate directly
+      String href = backHomeBtn.getAttribute("href");
+      if (href != null && !href.isEmpty()) {
+        driver.get(href);
+        LOG.info("Back home navigation succeeded via direct URL: {}", href);
+        return;
+      }
+      LOG.error("Failed to navigate back home: href was empty");
+    } catch (Exception e) {
+      LOG.error(e.getMessage(), e);
+    }
+  }
+
   private boolean isElementPresent(String xpath, WebDriverWait wait) {
     try {
       wait.until(ExpectedConditions.presenceOfElementLocated(By.xpath(xpath)));
diff --git a/web-v2/web/src/app/catalogs/page.js 
b/web-v2/web/src/app/catalogs/page.js
index 2356e9a469..2231c9d6eb 100644
--- a/web-v2/web/src/app/catalogs/page.js
+++ b/web-v2/web/src/app/catalogs/page.js
@@ -87,7 +87,6 @@ const CatalogsListPage = () => {
     async function fetchDependsData() {
       if ([...searchParams.keys()].length) {
         const { metalake, catalog, catalogType, schema, table, fileset, topic, 
model, version } = routeParams
-        await dispatch(setActivatedDetailsLoading(true))
 
         if (metalake) {
           dispatch(fetchTags({ metalake, details: true }))
@@ -103,7 +102,9 @@ const CatalogsListPage = () => {
           if (!store.catalogs.length) {
             await dispatch(fetchCatalogs({ metalake }))
           }
-          dispatch(getCatalogDetails({ init: true, metalake, catalog }))
+          await dispatch(setActivatedDetailsLoading(true))
+          await dispatch(getCatalogDetails({ init: true, metalake, catalog }))
+          await dispatch(setActivatedDetailsLoading(false))
           await dispatch(fetchSchemas({ init: true, page: 'catalogs', 
metalake, catalog, catalogType }))
           dispatch(
             getCurrentEntityTags({
@@ -147,7 +148,9 @@ const CatalogsListPage = () => {
             default:
               break
           }
-          dispatch(getSchemaDetails({ init: true, metalake, catalog, schema }))
+          await dispatch(setActivatedDetailsLoading(true))
+          await dispatch(getSchemaDetails({ init: true, metalake, catalog, 
schema }))
+          await dispatch(setActivatedDetailsLoading(false))
           dispatch(
             getCurrentEntityTags({
               init: true,
@@ -175,10 +178,13 @@ const CatalogsListPage = () => {
           }
           switch (catalogType) {
             case 'relational':
-              await dispatch(fetchTables({ init: true, page: 'schemas', 
metalake, catalog, schema }))
+              store.tables.length === 0 &&
+                (await dispatch(fetchTables({ init: true, page: 'schemas', 
metalake, catalog, schema })))
               await dispatch(resetActivatedDetails())
+              await dispatch(setActivatedDetailsLoading(true))
               await dispatch(getTableDetails({ init: true, metalake, catalog, 
schema, table }))
-              await dispatch(
+              await dispatch(setActivatedDetailsLoading(false))
+              dispatch(
                 getCurrentEntityTags({
                   init: true,
                   metalake,
@@ -186,7 +192,7 @@ const CatalogsListPage = () => {
                   metadataObjectFullName: `${catalog}.${schema}.${table}`
                 })
               )
-              await dispatch(
+              dispatch(
                 getCurrentEntityPolicies({
                   init: true,
                   metalake,
@@ -197,9 +203,12 @@ const CatalogsListPage = () => {
               )
               break
             case 'fileset':
-              await dispatch(fetchFilesets({ init: true, page: 'schemas', 
metalake, catalog, schema }))
+              store.filesets.length === 0 &&
+                (await dispatch(fetchFilesets({ init: true, page: 'schemas', 
metalake, catalog, schema })))
+              await dispatch(setActivatedDetailsLoading(true))
               await dispatch(getFilesetDetails({ init: true, metalake, 
catalog, schema, fileset }))
-              await dispatch(
+              await dispatch(setActivatedDetailsLoading(false))
+              dispatch(
                 getCurrentEntityTags({
                   init: true,
                   metalake,
@@ -208,7 +217,7 @@ const CatalogsListPage = () => {
                   details: true
                 })
               )
-              await dispatch(
+              dispatch(
                 getCurrentEntityPolicies({
                   init: true,
                   metalake,
@@ -219,9 +228,12 @@ const CatalogsListPage = () => {
               )
               break
             case 'messaging':
-              await dispatch(fetchTopics({ init: true, page: 'schemas', 
metalake, catalog, schema }))
+              store.topics.length === 0 &&
+                (await dispatch(fetchTopics({ init: true, page: 'schemas', 
metalake, catalog, schema })))
+              await dispatch(setActivatedDetailsLoading(true))
               await dispatch(getTopicDetails({ init: true, metalake, catalog, 
schema, topic }))
-              await dispatch(
+              await dispatch(setActivatedDetailsLoading(false))
+              dispatch(
                 getCurrentEntityTags({
                   init: true,
                   metalake,
@@ -230,7 +242,7 @@ const CatalogsListPage = () => {
                   details: true
                 })
               )
-              await dispatch(
+              dispatch(
                 getCurrentEntityPolicies({
                   init: true,
                   metalake,
@@ -241,10 +253,13 @@ const CatalogsListPage = () => {
               )
               break
             case 'model':
-              await dispatch(fetchModels({ init: true, page: 'schemas', 
metalake, catalog, schema }))
+              store.models.length === 0 &&
+                (await dispatch(fetchModels({ init: true, page: 'schemas', 
metalake, catalog, schema })))
               await dispatch(fetchModelVersions({ init: true, metalake, 
catalog, schema, model }))
+              await dispatch(setActivatedDetailsLoading(true))
               await dispatch(getModelDetails({ init: true, metalake, catalog, 
schema, model }))
-              await dispatch(
+              await dispatch(setActivatedDetailsLoading(false))
+              dispatch(
                 getCurrentEntityTags({
                   init: true,
                   metalake,
@@ -253,7 +268,7 @@ const CatalogsListPage = () => {
                   details: true
                 })
               )
-              await dispatch(
+              dispatch(
                 getCurrentEntityPolicies({
                   init: true,
                   metalake,
@@ -273,9 +288,10 @@ const CatalogsListPage = () => {
             await dispatch(fetchSchemas({ metalake, catalog, catalogType }))
             await dispatch(fetchModels({ init: true, page: 'schemas', 
metalake, catalog, schema }))
           }
-          dispatch(getVersionDetails({ init: true, metalake, catalog, schema, 
model, version }))
+          await dispatch(setActivatedDetailsLoading(true))
+          await dispatch(getVersionDetails({ init: true, metalake, catalog, 
schema, model, version }))
+          await dispatch(setActivatedDetailsLoading(false))
         }
-        await dispatch(setActivatedDetailsLoading(false))
       }
     }
     fetchDependsData()
diff --git a/web-v2/web/src/app/catalogs/rightContent/CreateCatalogDialog.js 
b/web-v2/web/src/app/catalogs/rightContent/CreateCatalogDialog.js
index d0cff6543e..c7eeafba5a 100644
--- a/web-v2/web/src/app/catalogs/rightContent/CreateCatalogDialog.js
+++ b/web-v2/web/src/app/catalogs/rightContent/CreateCatalogDialog.js
@@ -385,9 +385,9 @@ export default function CreateCatalogDialog({ ...props }) {
           </div>
         </div>
         {provider.value === currentProvider ? (
-          <Icons.CircleCheckBig className='provider-radio size-4 text-white 
default-theme-radio' />
+          <Icons.CircleCheckBig className='provider-radio size-4 shrink-0 
text-white default-theme-radio' />
         ) : (
-          <Icons.Circle className='provider-radio size-4 text-gray-400 
default-theme-radio' />
+          <Icons.Circle className='provider-radio size-4 shrink-0 
text-gray-400 default-theme-radio' />
         )}
       </div>
     )
diff --git a/web-v2/web/src/app/catalogs/rightContent/CreateSchemaDialog.js 
b/web-v2/web/src/app/catalogs/rightContent/CreateSchemaDialog.js
index d9910e3b88..31f7065f2c 100644
--- a/web-v2/web/src/app/catalogs/rightContent/CreateSchemaDialog.js
+++ b/web-v2/web/src/app/catalogs/rightContent/CreateSchemaDialog.js
@@ -45,7 +45,18 @@ const defaultValues = {
 }
 
 export default function CreateSchemaDialog({ ...props }) {
-  const { open, setOpen, metalake, catalog, catalogType, provider, 
locationProviders, editSchema, init } = props
+  const {
+    open,
+    setOpen,
+    metalake,
+    catalog,
+    catalogType,
+    provider,
+    locationProviders,
+    catalogBackend,
+    editSchema,
+    init
+  } = props
   const [confirmLoading, setConfirmLoading] = useState(false)
   const [isLoading, setIsLoading] = useState(false)
   const [cacheData, setCacheData] = useState()
@@ -59,6 +70,8 @@ export default function CreateSchemaDialog({ ...props }) {
   const selectBefore = [...new Set(['file:/', 'hdfs://', 
...currentSelectBefore])]
   const dispatch = useAppDispatch()
 
+  const paimonCatalogBackend = provider === 'lakehouse-paimon' && ['hive', 
'jdbc'].includes(catalogBackend)
+
   const [form] = Form.useForm()
   const values = Form.useWatch([], form)
 
@@ -219,12 +232,13 @@ export default function CreateSchemaDialog({ ...props }) {
                 >
                   <Input placeholder={mismatchName} disabled={editSchema} />
                 </Form.Item>
-                {!['jdbc-mysql', 'lakehouse-paimon'].includes(provider) && (
+                {(!['jdbc-mysql', 'lakehouse-paimon', 
'jdbc-oceanbase'].includes(provider) || paimonCatalogBackend) && (
                   <Form.Item name='comment' label='Comment' 
data-refer='schema-comment-field'>
                     <TextArea disabled={editSchema} />
                   </Form.Item>
                 )}
-                {!['jdbc-postgresql', 'lakehouse-paimon', 'kafka', 
'jdbc-mysql'].includes(provider) && (
+                {(!['jdbc-postgresql', 'lakehouse-paimon', 'kafka', 
'jdbc-mysql'].includes(provider) ||
+                  paimonCatalogBackend) && (
                   <Form.Item label='Properties'>
                     <Form.List name='properties'>
                       {(fields, subOpt) => (
diff --git a/web-v2/web/src/app/catalogs/rightContent/CreateTableDialog.js 
b/web-v2/web/src/app/catalogs/rightContent/CreateTableDialog.js
index 4a68258b77..f528acd635 100644
--- a/web-v2/web/src/app/catalogs/rightContent/CreateTableDialog.js
+++ b/web-v2/web/src/app/catalogs/rightContent/CreateTableDialog.js
@@ -438,6 +438,7 @@ export default function CreateTableDialog({ ...props }) {
               Object.entries(table.properties).forEach(([key, value]) => {
                 form.setFieldValue(['properties', idxProperty, 'key'], key)
                 form.setFieldValue(['properties', idxProperty, 'value'], value)
+                form.setFieldValue(['properties', idxProperty, 'isEdit'], true)
                 idxProperty++
               })
             }
diff --git 
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/CatalogDetailsPage.js
 
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/CatalogDetailsPage.js
index 62add30ac8..f59bf91fa8 100644
--- 
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/CatalogDetailsPage.js
+++ 
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/CatalogDetailsPage.js
@@ -174,7 +174,7 @@ export default function CatalogDetailsPage() {
   const tabOptions = anthEnable
     ? [
         { label: 'Schemas', key: 'Schemas' },
-        { label: 'Associated roles', key: 'Associated roles' }
+        { label: 'Associated Roles', key: 'Associated Roles' }
       ]
     : [{ label: 'Schemas', key: 'Schemas' }]
 
@@ -500,6 +500,7 @@ export default function CatalogDetailsPage() {
               catalogType={catalogType}
               provider={store.activatedDetails?.provider}
               
locationProviders={store.activatedDetails?.properties?.['filesystem-providers']?.split(',')
 || []}
+              
catalogBackend={store.activatedDetails?.properties?.['catalog-backend']}
               editSchema={editSchema}
             />
           )}
diff --git 
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/FilesetDetailsPage.js
 
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/FilesetDetailsPage.js
index 8214d49908..ad34e2136c 100644
--- 
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/FilesetDetailsPage.js
+++ 
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/FilesetDetailsPage.js
@@ -158,7 +158,7 @@ export default function FilesetDetailsPage({ ...props }) {
       key: 'files',
       disabled: disableFilesystemOps
     },
-    ...(anthEnable ? [{ label: 'Associated roles', key: 'Associated roles' }] 
: [])
+    ...(anthEnable ? [{ label: 'Associated Roles', key: 'Associated Roles' }] 
: [])
   ]
 
   const handleEditTable = () => {
@@ -277,7 +277,7 @@ export default function FilesetDetailsPage({ ...props }) {
           />
         </div>
       )}
-      {anthEnable && activeTab === 'Associated roles' && fileset && (
+      {anthEnable && activeTab === 'Associated Roles' && fileset && (
         <AssociatedTable
           metalake={currentMetalake}
           metadataObjectType={'fileset'}
diff --git 
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/ModelDetailsPage.js 
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/ModelDetailsPage.js
index c6a5d95ed7..2b4e7148e6 100644
--- 
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/ModelDetailsPage.js
+++ 
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/ModelDetailsPage.js
@@ -24,6 +24,7 @@ import dynamic from 'next/dynamic'
 import { ExclamationCircleFilled, PlusOutlined } from '@ant-design/icons'
 import {
   Button,
+  Descriptions,
   Divider,
   Drawer,
   Empty,
@@ -134,7 +135,7 @@ export default function ModelDetailsPage({ ...props }) {
 
   const tabOptions = [
     { label: 'Versions', key: 'Versions' },
-    ...(anthEnable ? [{ label: 'Associated roles', key: 'Associated roles' }] 
: [])
+    ...(anthEnable ? [{ label: 'Associated Roles', key: 'Associated Roles' }] 
: [])
   ]
 
   const onChangeTab = key => {
@@ -368,90 +369,77 @@ export default function ModelDetailsPage({ ...props }) {
             />
           )}
           {openVersion && (
-            <Drawer title={`Version ${currentVersion?.version} Details`} 
onClose={onClose} open={openVersion}>
-              <>
-                {currentVersion?.uri && (
-                  <div className='my-4'>
-                    <div className='text-sm text-slate-400'>URI</div>
-                    <span className='break-words 
text-base'>{currentVersion?.uri}</span>
-                  </div>
-                )}
+            <Drawer
+              title={`Version ${currentVersion?.version} Details`}
+              onClose={onClose}
+              open={openVersion}
+              width={560}
+            >
+              <Title level={5} className='mb-2'>
+                Basic Information
+              </Title>
+              <Descriptions column={1} bordered size='small'>
+                {currentVersion?.uri && <Descriptions.Item 
label='URI'>{currentVersion?.uri}</Descriptions.Item>}
                 {currentVersion?.uris && (
-                  <div className='my-4'>
-                    <div className='mb-1 text-sm text-slate-400'>URI(s)</div>
+                  <Descriptions.Item label='URI(s)'>
                     <Space.Compact className='max-h-80 w-full overflow-auto'>
                       <Space.Compact direction='vertical' className='w-1/2 
divide-y border-gray-100'>
                         <span className='bg-gray-100 p-1'>URI Name</span>
-                        {currentVersion?.uris
-                          ? Object.keys(currentVersion?.uris).map(name => (
-                              <span key={name} className='truncate p-1' 
title={name}>
-                                {name}
-                              </span>
-                            ))
-                          : null}
+                        {Object.keys(currentVersion.uris).map(name => (
+                          <span key={name} className='truncate p-1' 
title={name}>
+                            {name}
+                          </span>
+                        ))}
                       </Space.Compact>
                       <Space.Compact direction='vertical' className='w-1/2 
divide-y border-gray-100'>
                         <span className='bg-gray-100 p-1'>URI</span>
-                        {currentVersion?.uris
-                          ? Object.values(currentVersion?.uris).map(uri => (
-                              <span key={uri} className='truncate p-1' 
title={uri}>
-                                {uri || '-'}
-                              </span>
-                            ))
-                          : null}
+                        {Object.values(currentVersion.uris).map((uri, idx) => (
+                          <span key={idx} className='truncate p-1' title={uri}>
+                            {uri || '-'}
+                          </span>
+                        ))}
                       </Space.Compact>
                     </Space.Compact>
-                  </div>
+                  </Descriptions.Item>
                 )}
-                <div className='my-4'>
-                  <div className='text-sm text-slate-400'>Aliases</div>
-                  <span className='break-words text-base'>
-                    {currentVersion?.aliases.length === 1
-                      ? currentVersion?.aliases[0]
-                      : currentVersion?.aliases.length
-                        ? currentVersion?.aliases.join(', ')
-                        : '-'}
-                  </span>
-                </div>
-                <div className='my-4'>
-                  <div className='text-sm text-slate-400'>Comment</div>
-                  <span className='break-words 
text-base'>{currentVersion?.comment || '-'}</span>
-                </div>
-                <div className='my-4'>
-                  <div className='mb-1 text-sm text-slate-400'>Properties</div>
-                  {currentVersion?.properties && 
Object.keys(currentVersion?.properties).length > 0 ? (
+                <Descriptions.Item label='Aliases'>
+                  {currentVersion?.aliases?.length === 1
+                    ? currentVersion?.aliases[0]
+                    : currentVersion?.aliases?.length
+                      ? currentVersion?.aliases.join(', ')
+                      : '-'}
+                </Descriptions.Item>
+                <Descriptions.Item label='Comment'>{currentVersion?.comment || 
'-'}</Descriptions.Item>
+                <Descriptions.Item label='Properties'>
+                  {currentVersion?.properties && 
Object.keys(currentVersion.properties).length > 0 ? (
                     <Space.Compact className='max-h-80 w-full overflow-auto'>
                       <Space.Compact direction='vertical' className='w-1/2 
divide-y border-gray-100'>
                         <span className='bg-gray-100 p-1'>Key</span>
-                        {currentVersion?.properties
-                          ? Object.keys(currentVersion?.properties).map(key => 
(
-                              <span key={key} className='truncate p-1' 
title={key}>
-                                {key}
-                              </span>
-                            ))
-                          : null}
+                        {Object.keys(currentVersion.properties).map(key => (
+                          <span key={key} className='truncate p-1' title={key}>
+                            {key}
+                          </span>
+                        ))}
                       </Space.Compact>
                       <Space.Compact direction='vertical' className='w-1/2 
divide-y border-gray-100'>
                         <span className='bg-gray-100 p-1'>Value</span>
-                        {currentVersion?.properties
-                          ? 
Object.values(currentVersion?.properties).map(value => (
-                              <span key={value} className='truncate p-1' 
title={value}>
-                                {value || '-'}
-                              </span>
-                            ))
-                          : null}
+                        {Object.values(currentVersion.properties).map((value, 
idx) => (
+                          <span key={idx} className='truncate p-1' 
title={value}>
+                            {value || '-'}
+                          </span>
+                        ))}
                       </Space.Compact>
                     </Space.Compact>
                   ) : (
                     <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
                   )}
-                </div>
-              </>
+                </Descriptions.Item>
+              </Descriptions>
             </Drawer>
           )}
         </>
       )}
-      {anthEnable && activeTab === 'Associated roles' && model && (
+      {anthEnable && activeTab === 'Associated Roles' && model && (
         <AssociatedTable
           metalake={currentMetalake}
           metadataObjectType={'model'}
diff --git 
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/SchemaDetailsPage.js 
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/SchemaDetailsPage.js
index f1f96bf72c..38555281d6 100644
--- 
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/SchemaDetailsPage.js
+++ 
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/SchemaDetailsPage.js
@@ -127,7 +127,7 @@ export default function SchemaDetailsPage() {
             ? [
                 { label: 'Tables', key: 'Tables' },
                 { label: 'Functions', key: 'Functions' },
-                { label: 'Associated roles', key: 'Associated roles' }
+                { label: 'Associated Roles', key: 'Associated Roles' }
               ]
             : [
                 { label: 'Tables', key: 'Tables' },
@@ -145,7 +145,7 @@ export default function SchemaDetailsPage() {
             ? [
                 { label: 'Topics', key: 'Topics' },
                 { label: 'Functions', key: 'Functions' },
-                { label: 'Associated roles', key: 'Associated roles' }
+                { label: 'Associated Roles', key: 'Associated Roles' }
               ]
             : [
                 { label: 'Topics', key: 'Topics' },
@@ -163,7 +163,7 @@ export default function SchemaDetailsPage() {
             ? [
                 { label: 'Filesets', key: 'Filesets' },
                 { label: 'Functions', key: 'Functions' },
-                { label: 'Associated roles', key: 'Associated roles' }
+                { label: 'Associated Roles', key: 'Associated Roles' }
               ]
             : [
                 { label: 'Filesets', key: 'Filesets' },
@@ -181,7 +181,7 @@ export default function SchemaDetailsPage() {
             ? [
                 { label: 'Models', key: 'Models' },
                 { label: 'Functions', key: 'Functions' },
-                { label: 'Associated roles', key: 'Associated roles' }
+                { label: 'Associated Roles', key: 'Associated Roles' }
               ]
             : [
                 { label: 'Models', key: 'Models' },
@@ -536,7 +536,7 @@ export default function SchemaDetailsPage() {
         </Space>
       </Spin>
       <Tabs data-refer='details-tabs' defaultActiveKey={tabKey} 
onChange={onChangeTab} items={tabOptions} />
-      {tabKey === 'Associated roles' ? (
+      {tabKey === 'Associated Roles' ? (
         <AssociatedTable
           metalake={currentMetalake}
           metadataObjectType={'schema'}
diff --git 
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/TableDetailsPage.js 
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/TableDetailsPage.js
index 988392c9a6..3567f0cc64 100644
--- 
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/TableDetailsPage.js
+++ 
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/TableDetailsPage.js
@@ -345,7 +345,7 @@ export default function TableDetailsPage({ ...props }) {
 
   const tabOptions = [
     { label: 'Columns', key: 'Columns' },
-    ...(anthEnable ? [{ label: 'Associated roles', key: 'Associated roles' }] 
: [])
+    ...(anthEnable ? [{ label: 'Associated Roles', key: 'Associated Roles' }] 
: [])
   ]
 
   const onChangeTab = key => {
@@ -613,7 +613,7 @@ export default function TableDetailsPage({ ...props }) {
           </Spin>
         </>
       )}
-      {tabKey === 'Associated roles' && anthEnable && (
+      {tabKey === 'Associated Roles' && anthEnable && (
         <AssociatedTable
           metalake={currentMetalake}
           metadataObjectType={'table'}
diff --git 
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/TopicDetailsPage.js 
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/TopicDetailsPage.js
index c2d08e4eaa..eb97f93729 100644
--- 
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/TopicDetailsPage.js
+++ 
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/TopicDetailsPage.js
@@ -92,7 +92,7 @@ export default function TopicDetailsPage({ ...props }) {
   const propertyContent = (
     <PropertiesContent properties={properties} dataReferPrefix='props' 
contentDataRefer='properties-popover-content' />
   )
-  const tabOptions = [{ label: 'Associated roles', key: 'Associated roles' }]
+  const tabOptions = [{ label: 'Associated Roles', key: 'Associated Roles' }]
 
   const handleEditTable = () => {
     setOpen(true)
diff --git a/web-v2/web/src/app/compliance/policies/page.js 
b/web-v2/web/src/app/compliance/policies/page.js
index e64538ba77..ed8dda2f1a 100644
--- a/web-v2/web/src/app/compliance/policies/page.js
+++ b/web-v2/web/src/app/compliance/policies/page.js
@@ -24,6 +24,7 @@ import { useRouter, useSearchParams } from 'next/navigation'
 import { ExclamationCircleFilled, PlusOutlined, StopOutlined } from 
'@ant-design/icons'
 import {
   Button,
+  Descriptions,
   Drawer,
   Dropdown,
   Empty,
@@ -43,6 +44,7 @@ import { useAntdColumnResize } from 'react-antd-column-resize'
 import ConfirmInput from '@/components/ConfirmInput'
 import Icons from '@/components/Icons'
 import SectionContainer from '@/components/SectionContainer'
+import AssociatedTable from '@/components/AssociatedTable'
 import CreatePolicyDialog from './CreatePolicyDialog'
 import { formatToDateTime } from '@/lib/utils'
 import { useAppSelector, useAppDispatch } from '@/lib/hooks/useStore'
@@ -66,6 +68,8 @@ export default function PoliciesPage() {
   const searchParams = useSearchParams()
   const currentMetalake = searchParams.get('metalake')
   const [detailsLoading, setDetailsLoading] = useState(false)
+  const auth = useAppSelector(state => state.auth)
+  const { anthEnable } = auth
 
   useEffect(() => {
     currentMetalake && dispatch(fetchPolicies({ metalake: currentMetalake, 
details: true }))
@@ -277,95 +281,112 @@ export default function PoliciesPage() {
       </Spin>
       {openPolicy && (
         <Drawer
-          title={`View ${currentPolicy?.name} details`}
+          title={`View Policy "${currentPolicy?.name}" Details`}
           loading={detailsLoading}
           onClose={onClose}
           open={openPolicy}
+          width={'40%'}
         >
-          <>
-            <div className='my-4'>
-              <div className='text-sm text-slate-400'>Policy Name</div>
-              <span className='break-words 
text-base'>{currentPolicy?.name}</span>
-            </div>
-            <div className='my-4'>
-              <div className='text-sm text-slate-400'>Enabled</div>
-              <span className='break-words text-base'>
-                <Switch checked={currentPolicy?.enabled} disabled size='small' 
/>
-              </span>
-            </div>
-            <div className='my-4'>
-              <div className='text-sm text-slate-400'>Policy Type</div>
-              <span className='break-words 
text-base'>{currentPolicy?.policyType}</span>
-            </div>
-            <div className='my-4'>
-              <div className='text-sm text-slate-400'>Supported Object 
Types</div>
-              <span className='break-words text-base'>
-                {currentPolicy?.content?.supportedObjectTypes.length === 1
-                  ? currentPolicy?.content?.supportedObjectTypes[0]
-                  : currentPolicy?.content?.supportedObjectTypes.length
-                    ? currentPolicy?.content?.supportedObjectTypes.join(', ')
-                    : '-'}
-              </span>
-            </div>
-            <div className='my-4'>
-              <div className='mb-1 text-sm text-slate-400'>Rule(s)</div>
-              <Space.Compact className='max-h-80 w-full overflow-auto'>
-                <Space.Compact direction='vertical' className='w-1/2 divide-y 
border-gray-100'>
-                  <span className='bg-gray-100 p-1'>Rule Name</span>
-                  {currentPolicy?.content?.customRules
-                    ? 
Object.keys(currentPolicy?.content?.customRules).map(rule => (
-                        <span key={rule} className='truncate p-1' title={rule}>
-                          {rule}
-                        </span>
-                      ))
-                    : null}
-                </Space.Compact>
-                <Space.Compact direction='vertical' className='w-1/2 divide-y 
border-gray-100'>
-                  <span className='bg-gray-100 p-1'>Rule Content</span>
-                  {currentPolicy?.content?.customRules
-                    ? 
Object.values(currentPolicy?.content?.customRules).map(ruleContent => (
-                        <span key={ruleContent} className='truncate p-1' 
title={ruleContent}>
-                          {ruleContent || '-'}
-                        </span>
-                      ))
-                    : null}
-                </Space.Compact>
-              </Space.Compact>
-            </div>
-            <div className='my-4'>
-              <div className='text-sm text-slate-400'>Comment</div>
-              <span className='break-words text-base'>{currentPolicy?.comment 
|| '-'}</span>
-            </div>
-            <div className='my-4'>
-              <div className='mb-1 text-sm text-slate-400'>Properties</div>
-              {currentPolicy?.content?.properties && 
Object.keys(currentPolicy?.content?.properties).length > 0 ? (
-                <Space.Compact className='max-h-80 w-full overflow-auto'>
-                  <Space.Compact direction='vertical' className='w-1/2 
divide-y border-gray-100'>
-                    <span className='bg-gray-100 p-1'>Key</span>
-                    {currentPolicy?.content?.properties
-                      ? 
Object.keys(currentPolicy?.content?.properties).map(key => (
-                          <span key={key} className='truncate p-1' title={key}>
-                            {key}
-                          </span>
-                        ))
-                      : null}
-                  </Space.Compact>
-                  <Space.Compact direction='vertical' className='w-1/2 
divide-y border-gray-100'>
-                    <span className='bg-gray-100 p-1'>Value</span>
-                    {currentPolicy?.content?.properties
-                      ? 
Object.values(currentPolicy?.content?.properties).map(value => (
-                          <span key={value} className='truncate p-1' 
title={value}>
-                            {value || '-'}
-                          </span>
-                        ))
-                      : null}
-                  </Space.Compact>
-                </Space.Compact>
-              ) : (
-                <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
-              )}
-            </div>
-          </>
+          <Title level={5} className='mb-2'>
+            Basic Information
+          </Title>
+          <Descriptions column={1} bordered size='small'>
+            <Descriptions.Item label='Policy 
Name'>{currentPolicy?.name}</Descriptions.Item>
+            <Descriptions.Item label='Enabled'>
+              <Switch checked={currentPolicy?.enabled} disabled size='small' />
+            </Descriptions.Item>
+            <Descriptions.Item label='Policy 
Type'>{currentPolicy?.policyType}</Descriptions.Item>
+            <Descriptions.Item label='Supported Object Types'>
+              {currentPolicy?.content?.supportedObjectTypes?.length === 1
+                ? currentPolicy?.content?.supportedObjectTypes[0]
+                : currentPolicy?.content?.supportedObjectTypes?.length
+                  ? currentPolicy?.content?.supportedObjectTypes.join(', ')
+                  : '-'}
+            </Descriptions.Item>
+            <Descriptions.Item label='Comment'>{currentPolicy?.comment || 
'-'}</Descriptions.Item>
+            <Descriptions.Item label='Creator'>{currentPolicy?.audit?.creator 
|| '-'}</Descriptions.Item>
+            <Descriptions.Item label='Created At'>
+              {currentPolicy?.audit?.createTime ? 
formatToDateTime(currentPolicy.audit.createTime) : '-'}
+            </Descriptions.Item>
+            <Descriptions.Item label='Last Modified At'>
+              {currentPolicy?.audit?.lastModifiedTime ? 
formatToDateTime(currentPolicy.audit.lastModifiedTime) : '-'}
+            </Descriptions.Item>
+            <Descriptions.Item label='Last 
Modifier'>{currentPolicy?.audit?.lastModifier || '-'}</Descriptions.Item>
+          </Descriptions>
+          <Title level={5} className='mt-4 mb-2'>
+            Rule(s)
+          </Title>
+          <Table
+            size='small'
+            pagination={false}
+            rowKey='name'
+            dataSource={
+              currentPolicy?.content?.customRules && 
Object.keys(currentPolicy.content.customRules).length > 0
+                ? 
Object.entries(currentPolicy.content.customRules).map(([name, content]) => ({
+                    name,
+                    content
+                  }))
+                : []
+            }
+            columns={[
+              {
+                title: 'Rule Name',
+                dataIndex: 'name',
+                key: 'name',
+                ellipsis: true
+              },
+              {
+                title: 'Rule Content',
+                dataIndex: 'content',
+                key: 'content',
+                ellipsis: true,
+                render: value => value || '-'
+              }
+            ]}
+          />
+          <Title level={5} className='mt-4 mb-2'>
+            Properties
+          </Title>
+          <Table
+            size='small'
+            pagination={false}
+            rowKey='key'
+            dataSource={
+              currentPolicy?.content?.properties && 
Object.keys(currentPolicy.content.properties).length > 0
+                ? Object.entries(currentPolicy.content.properties).map(([key, 
value]) => ({
+                    key,
+                    value
+                  }))
+                : []
+            }
+            columns={[
+              {
+                title: 'Key',
+                dataIndex: 'key',
+                key: 'key',
+                ellipsis: true
+              },
+              {
+                title: 'Value',
+                dataIndex: 'value',
+                key: 'value',
+                ellipsis: true,
+                render: value => value || '-'
+              }
+            ]}
+          />
+          {anthEnable && (
+            <>
+              <Title level={5} className='mt-4 mb-2'>
+                Associated Roles
+              </Title>
+              <AssociatedTable
+                metalake={currentMetalake}
+                metadataObjectType={'policy'}
+                metadataObjectFullName={currentPolicy?.name}
+              />
+            </>
+          )}
         </Drawer>
       )}
       <CreatePolicyDialog open={open} setOpen={setOpen} 
metalake={currentMetalake} editPolicy={editPolicy} />
diff --git a/web-v2/web/src/app/compliance/tags/page.js 
b/web-v2/web/src/app/compliance/tags/page.js
index 6599ff3c66..74a27f0644 100644
--- a/web-v2/web/src/app/compliance/tags/page.js
+++ b/web-v2/web/src/app/compliance/tags/page.js
@@ -22,11 +22,12 @@
 import { createContext, useMemo, useState, useEffect } from 'react'
 import { useRouter, useSearchParams } from 'next/navigation'
 import { ExclamationCircleFilled, PlusOutlined } from '@ant-design/icons'
-import { Button, Flex, Input, Modal, Spin, Table, Tag, Tooltip, Typography } 
from 'antd'
+import { Button, Descriptions, Drawer, Flex, Input, Modal, Spin, Table, Tag, 
Tooltip, Typography } from 'antd'
 import { useAntdColumnResize } from 'react-antd-column-resize'
 import ConfirmInput from '@/components/ConfirmInput'
 import Icons from '@/components/Icons'
 import SectionContainer from '@/components/SectionContainer'
+import AssociatedTable from '@/components/AssociatedTable'
 import CreateTagDialog from './CreateTagDialog'
 import { formatToDateTime } from '@/lib/utils'
 import { useAppSelector, useAppDispatch } from '@/lib/hooks/useStore'
@@ -40,12 +41,16 @@ export default function TagsPage() {
   const [open, setOpen] = useState(false)
   const [editTag, setEditTag] = useState('')
   const [search, setSearch] = useState('')
+  const [drawerOpen, setDrawerOpen] = useState(false)
+  const [selectedTag, setSelectedTag] = useState(null)
   const [modal, contextHolder] = Modal.useModal()
   const router = useRouter()
   const dispatch = useAppDispatch()
   const store = useAppSelector(state => state.tags)
   const searchParams = useSearchParams()
   const currentMetalake = searchParams.get('metalake')
+  const auth = useAppSelector(state => state.auth)
+  const { anthEnable } = auth
 
   useEffect(() => {
     currentMetalake && dispatch(fetchTags({ metalake: currentMetalake, 
details: true }))
@@ -118,6 +123,16 @@ export default function TagsPage() {
     
router.push(`/metadataObjectsForTag?tag=${name}&metalake=${currentMetalake}`)
   }
 
+  const handleViewTag = record => {
+    setSelectedTag(record)
+    setDrawerOpen(true)
+  }
+
+  const handleCloseDrawer = () => {
+    setDrawerOpen(false)
+    setSelectedTag(null)
+  }
+
   const columns = useMemo(
     () => [
       {
@@ -159,7 +174,7 @@ export default function TagsPage() {
       {
         title: 'Actions',
         key: 'action',
-        width: 100,
+        width: 140,
         render: (_, record) => {
           const NameContext = createContext(record.name)
 
@@ -171,6 +186,11 @@ export default function TagsPage() {
                   <Icons.Pencil className='size-4' onClick={() => 
handleEditTag(record.name)} />
                 </Tooltip>
               </a>
+              <a>
+                <Tooltip title='View'>
+                  <Icons.Eye className='size-4' onClick={() => 
handleViewTag(record)} />
+                </Tooltip>
+              </a>
               <a>
                 <Tooltip title='Delete'>
                   <Icons.Trash2Icon className='size-4' onClick={() => 
showDeleteConfirm(NameContext, record.name)} />
@@ -211,6 +231,43 @@ export default function TagsPage() {
         />
       </Spin>
       <CreateTagDialog open={open} setOpen={setOpen} 
metalake={currentMetalake} editTag={editTag} />
+      <Drawer
+        title={`View Tag "${selectedTag?.name || ''}" Details`}
+        placement='right'
+        width={'40%'}
+        onClose={handleCloseDrawer}
+        open={drawerOpen}
+      >
+        {selectedTag && (
+          <>
+            <Title level={5} className='mb-2'>
+              Basic Information
+            </Title>
+            <Descriptions column={1} bordered size='small'>
+              <Descriptions.Item label='Tag Name'>
+                <Tag 
color={selectedTag.properties?.color}>{selectedTag.name}</Tag>
+              </Descriptions.Item>
+              <Descriptions.Item label='Comment'>{selectedTag.comment || 
'-'}</Descriptions.Item>
+              <Descriptions.Item label='Created At'>
+                {selectedTag.createTime ? 
formatToDateTime(selectedTag.createTime) : '-'}
+              </Descriptions.Item>
+              <Descriptions.Item label='Creator'>{selectedTag.audit?.creator 
|| '-'}</Descriptions.Item>
+            </Descriptions>
+            {anthEnable && (
+              <>
+                <Title level={5} className='mt-4 mb-2'>
+                  Associated Roles
+                </Title>
+                <AssociatedTable
+                  metalake={currentMetalake}
+                  metadataObjectType={'tag'}
+                  metadataObjectFullName={selectedTag?.name}
+                />
+              </>
+            )}
+          </>
+        )}
+      </Drawer>
     </SectionContainer>
   )
 }
diff --git a/web-v2/web/src/app/jobTemplates/page.js 
b/web-v2/web/src/app/jobTemplates/page.js
index f5b38dbdf2..5da6404b08 100644
--- a/web-v2/web/src/app/jobTemplates/page.js
+++ b/web-v2/web/src/app/jobTemplates/page.js
@@ -23,11 +23,12 @@ import { createContext, useContext, useEffect, useMemo, 
useState } from 'react'
 import dynamic from 'next/dynamic'
 import { useRouter, useSearchParams } from 'next/navigation'
 import { ExclamationCircleFilled, PlusOutlined } from '@ant-design/icons'
-import { Button, Drawer, Flex, Input, Modal, Space, Spin, Table, Tooltip, 
Typography } from 'antd'
+import { Button, Descriptions, Drawer, Flex, Input, Modal, Spin, Table, Tag, 
Tooltip, Typography } from 'antd'
 import { useAntdColumnResize } from 'react-antd-column-resize'
 import ConfirmInput from '@/components/ConfirmInput'
 import Icons from '@/components/Icons'
 import SectionContainer from '@/components/SectionContainer'
+import AssociatedTable from '@/components/AssociatedTable'
 import { formatToDateTime } from '@/lib/utils'
 import Loading from '@/components/Loading'
 import { useAppSelector, useAppDispatch } from '@/lib/hooks/useStore'
@@ -55,6 +56,8 @@ export default function JobTemplatesPage() {
   const dispatch = useAppDispatch()
   const store = useAppSelector(state => state.jobs)
   const [isLoadingDetails, setIsLoadingDetails] = useState(false)
+  const auth = useAppSelector(state => state.auth)
+  const { anthEnable } = auth
 
   useEffect(() => {
     currentMetalake && dispatch(fetchJobTemplates({ metalake: currentMetalake, 
details: true }))
@@ -251,187 +254,232 @@ export default function JobTemplatesPage() {
         </Spin>
         {openDetailJobTemplate && (
           <Drawer
-            title={`View Details - ${currentJobTemplate?.name || ''}`}
+            title={`View Job Template "${currentJobTemplate?.name || ''}" 
Details`}
             loading={isLoadingDetails}
             onClose={onClose}
             open={openDetailJobTemplate}
+            width={'40%'}
           >
-            <>
-              <div className='my-4'>
-                <div className='text-sm text-slate-400'>Template Name</div>
-                <span className='break-words 
text-base'>{currentJobTemplate?.name}</span>
-              </div>
-              <div className='my-4'>
-                <div className='text-sm text-slate-400'>Job Type</div>
-                <span className='break-words 
text-base'>{currentJobTemplate?.jobType}</span>
-              </div>
-              <div className='my-4'>
-                <div className='text-sm text-slate-400'>Executable</div>
-                <span className='break-words 
text-base'>{currentJobTemplate?.executable}</span>
-              </div>
-              <div className='my-4'>
-                <div className='text-sm text-slate-400'>Comment</div>
-                <span className='break-words 
text-base'>{currentJobTemplate?.comment || '-'}</span>
-              </div>
-              <div className='my-4'>
-                <div className='text-sm text-slate-400'>Arguments</div>
-                <span className='break-words text-base'>
-                  {currentJobTemplate?.arguments.length === 1
-                    ? currentJobTemplate?.arguments[0]
-                    : currentJobTemplate?.arguments.length
-                      ? currentJobTemplate?.arguments.join(', ')
-                      : '-'}
-                </span>
-              </div>
-              <div className='my-4'>
-                <div className='mb-1 text-sm text-slate-400'>Environment 
Variable(s)</div>
-                <Space.Compact className='max-h-80 w-full overflow-auto'>
-                  <Space.Compact direction='vertical' className='w-1/2 
divide-y border-gray-100'>
-                    <span className='bg-gray-100 p-1'>Env Var Name</span>
-                    {currentJobTemplate?.environments
-                      ? 
Object.keys(currentJobTemplate?.environments).map(envName => (
-                          <span key={envName} className='truncate p-1' 
title={envName}>
-                            {envName}
-                          </span>
-                        ))
-                      : null}
-                  </Space.Compact>
-                  <Space.Compact direction='vertical' className='w-1/2 
divide-y border-gray-100'>
-                    <span className='bg-gray-100 p-1'>Env Var Value</span>
-                    {currentJobTemplate?.environments
-                      ? 
Object.values(currentJobTemplate?.environments).map(envValue => (
-                          <span key={envValue} className='truncate p-1' 
title={envValue}>
-                            {envValue || '-'}
-                          </span>
-                        ))
-                      : null}
-                  </Space.Compact>
-                </Space.Compact>
-              </div>
-              <div className='my-4'>
-                <div className='mb-1 text-sm text-slate-400'>Custom 
Fields</div>
-                <Space.Compact className='max-h-80 w-full overflow-auto'>
-                  <Space.Compact direction='vertical' className='w-1/2 
divide-y border-gray-100'>
-                    <span className='bg-gray-100 p-1'>Key</span>
-                    {currentJobTemplate?.customFields
-                      ? 
Object.keys(currentJobTemplate?.customFields).map(field => (
-                          <span key={field} className='truncate p-1' 
title={field}>
-                            {field}
-                          </span>
-                        ))
-                      : null}
-                  </Space.Compact>
-                  <Space.Compact direction='vertical' className='w-1/2 
divide-y border-gray-100'>
-                    <span className='bg-gray-100 p-1'>Value</span>
-                    {currentJobTemplate?.customFields
-                      ? 
Object.values(currentJobTemplate?.customFields).map(value => (
-                          <span key={value} className='truncate p-1' 
title={value}>
-                            {value || '-'}
-                          </span>
-                        ))
-                      : null}
-                  </Space.Compact>
-                </Space.Compact>
-              </div>
-              {currentJobTemplate?.jobType === 'shell' && (
-                <div className='my-4'>
-                  <div className='text-sm text-slate-400'>Script(s)</div>
-                  {currentJobTemplate?.scripts && 
currentJobTemplate.scripts.length > 0 ? (
-                    currentJobTemplate.scripts.map((script, index) => (
-                      <span key={index} className='mb-2 block break-words 
last:mb-0'>
-                        {script}
-                      </span>
-                    ))
-                  ) : (
-                    <span>-</span>
-                  )}
-                </div>
-              )}
+            <Title level={5} className='mb-2'>
+              Basic Information
+            </Title>
+            <Descriptions column={1} bordered size='small'>
+              <Descriptions.Item label='Template 
Name'>{currentJobTemplate?.name}</Descriptions.Item>
+              <Descriptions.Item label='Job 
Type'>{currentJobTemplate?.jobType}</Descriptions.Item>
+              <Descriptions.Item 
label='Executable'>{currentJobTemplate?.executable}</Descriptions.Item>
+              <Descriptions.Item label='Comment'>{currentJobTemplate?.comment 
|| '-'}</Descriptions.Item>
               {currentJobTemplate?.jobType === 'spark' && (
-                <>
-                  <div className='my-4'>
-                    <div className='text-sm text-slate-400'>Class Name</div>
-                    <span className='break-words 
text-base'>{currentJobTemplate?.className}</span>
-                  </div>
-                  <div className='my-4'>
-                    <div className='text-sm text-slate-400'>Jars</div>
-                    {currentJobTemplate?.jars && 
currentJobTemplate.jars.length > 0 ? (
-                      currentJobTemplate.jars.map((jar, index) => (
-                        <span key={index} className='mb-2 block break-words 
last:mb-0'>
-                          {jar}
-                        </span>
-                      ))
-                    ) : (
-                      <span>-</span>
-                    )}
-                  </div>
-                  <div className='my-4'>
-                    <div className='text-sm text-slate-400'>Files</div>
-                    {currentJobTemplate?.files && 
currentJobTemplate.files.length > 0 ? (
-                      currentJobTemplate.files.map((file, index) => (
-                        <span key={index} className='mb-2 block break-words 
last:mb-0'>
-                          {file}
-                        </span>
-                      ))
-                    ) : (
-                      <span>-</span>
-                    )}
-                  </div>
-                  <div className='my-4'>
-                    <div className='text-sm text-slate-400'>Archives</div>
-                    {currentJobTemplate?.archives && 
currentJobTemplate.archives.length > 0 ? (
-                      currentJobTemplate.archives.map((archive, index) => (
+                <Descriptions.Item label='Class 
Name'>{currentJobTemplate?.className || '-'}</Descriptions.Item>
+              )}
+              <Descriptions.Item label='Arguments'>
+                {currentJobTemplate?.arguments?.length
+                  ? currentJobTemplate.arguments.map((argument, index) => (
+                      <Tag key={`${argument}-${index}`} className='mb-1 mr-1'>
+                        {argument}
+                      </Tag>
+                    ))
+                  : '-'}
+              </Descriptions.Item>
+              {currentJobTemplate?.jobType === 'shell' && (
+                <Descriptions.Item label='Script(s)'>
+                  {currentJobTemplate?.scripts && 
currentJobTemplate.scripts.length > 0
+                    ? currentJobTemplate.scripts.map((script, index) => (
                         <span key={index} className='mb-2 block break-words 
last:mb-0'>
-                          {archive}
+                          {script}
                         </span>
                       ))
-                    ) : (
-                      <span>-</span>
-                    )}
-                  </div>
-                  <div className='my-4'>
-                    <div className='mb-1 text-sm 
text-slate-400'>Config(s)</div>
-                    <Space.Compact className='max-h-80 w-full overflow-auto'>
-                      <Space.Compact direction='vertical' className='w-1/2 
divide-y border-gray-100'>
-                        <span className='bg-gray-100 p-1'>Config Name</span>
-                        {currentJobTemplate?.configs
-                          ? 
Object.keys(currentJobTemplate?.configs).map(configName => (
-                              <span key={configName} className='truncate p-1' 
title={configName}>
-                                {configName}
-                              </span>
-                            ))
-                          : null}
-                      </Space.Compact>
-                      <Space.Compact direction='vertical' className='w-1/2 
divide-y border-gray-100'>
-                        <span className='bg-gray-100 p-1'>Config Value</span>
-                        {currentJobTemplate?.configs
-                          ? 
Object.values(currentJobTemplate?.configs).map(configValue => (
-                              <span key={configValue} className='truncate p-1' 
title={configValue}>
-                                {configValue || '-'}
-                              </span>
-                            ))
-                          : null}
-                      </Space.Compact>
-                    </Space.Compact>
-                  </div>
-                </>
+                    : '-'}
+                </Descriptions.Item>
               )}
-              <div className='my-4'>
-                <div className='text-sm text-slate-400'>Details</div>
-                <div className='flex justify-between'>
-                  <span className='text-sm'>Creator: </span>
-                  <span 
className='text-sm'>{currentJobTemplate?.audit?.creator}</span>
-                </div>
-                <div className='flex justify-between'>
-                  <span className='text-sm'>Created At: </span>
-                  <span 
className='text-sm'>{formatToDateTime(currentJobTemplate?.audit?.createTime)}</span>
-                </div>
-                <div className='flex justify-between'>
-                  <span className='text-sm'>Updated At: </span>
-                  <span 
className='text-sm'>{formatToDateTime(currentJobTemplate?.audit?.lastModifiedTime)}</span>
-                </div>
-              </div>
-            </>
+            </Descriptions>
+            <Title level={5} className='mt-4 mb-2'>
+              Environment Variable(s)
+            </Title>
+            <Table
+              size='small'
+              pagination={false}
+              rowKey='key'
+              dataSource={
+                currentJobTemplate?.environments && 
Object.keys(currentJobTemplate.environments).length > 0
+                  ? Object.entries(currentJobTemplate.environments).map(([key, 
value]) => ({
+                      key,
+                      value
+                    }))
+                  : []
+              }
+              columns={[
+                {
+                  title: 'Env Var Name',
+                  dataIndex: 'key',
+                  key: 'key',
+                  ellipsis: true
+                },
+                {
+                  title: 'Env Var Value',
+                  dataIndex: 'value',
+                  key: 'value',
+                  ellipsis: true,
+                  render: value => value || '-'
+                }
+              ]}
+            />
+            <Title level={5} className='mt-4 mb-2'>
+              Custom Fields
+            </Title>
+            <Table
+              size='small'
+              pagination={false}
+              rowKey='key'
+              dataSource={
+                currentJobTemplate?.customFields && 
Object.keys(currentJobTemplate.customFields).length > 0
+                  ? Object.entries(currentJobTemplate.customFields).map(([key, 
value]) => ({
+                      key,
+                      value
+                    }))
+                  : []
+              }
+              columns={[
+                {
+                  title: 'Key',
+                  dataIndex: 'key',
+                  key: 'key',
+                  ellipsis: true
+                },
+                {
+                  title: 'Value',
+                  dataIndex: 'value',
+                  key: 'value',
+                  ellipsis: true,
+                  render: value => value || '-'
+                }
+              ]}
+            />
+            {currentJobTemplate?.jobType === 'spark' && (
+              <>
+                <Title level={5} className='mt-4 mb-2'>
+                  Jars
+                </Title>
+                <Table
+                  size='small'
+                  pagination={false}
+                  rowKey='value'
+                  dataSource={
+                    currentJobTemplate?.jars && currentJobTemplate.jars.length 
> 0
+                      ? currentJobTemplate.jars.map(value => ({ value }))
+                      : []
+                  }
+                  columns={[
+                    {
+                      title: 'Jar',
+                      dataIndex: 'value',
+                      key: 'value',
+                      ellipsis: true
+                    }
+                  ]}
+                />
+                <Title level={5} className='mt-4 mb-2'>
+                  Files
+                </Title>
+                <Table
+                  size='small'
+                  pagination={false}
+                  rowKey='value'
+                  dataSource={
+                    currentJobTemplate?.files && 
currentJobTemplate.files.length > 0
+                      ? currentJobTemplate.files.map(value => ({ value }))
+                      : []
+                  }
+                  columns={[
+                    {
+                      title: 'File',
+                      dataIndex: 'value',
+                      key: 'value',
+                      ellipsis: true
+                    }
+                  ]}
+                />
+                <Title level={5} className='mt-4 mb-2'>
+                  Archives
+                </Title>
+                <Table
+                  size='small'
+                  pagination={false}
+                  rowKey='value'
+                  dataSource={
+                    currentJobTemplate?.archives && 
currentJobTemplate.archives.length > 0
+                      ? currentJobTemplate.archives.map(value => ({ value }))
+                      : []
+                  }
+                  columns={[
+                    {
+                      title: 'Archive',
+                      dataIndex: 'value',
+                      key: 'value',
+                      ellipsis: true
+                    }
+                  ]}
+                />
+                <Title level={5} className='mt-4 mb-2'>
+                  Config(s)
+                </Title>
+                <Table
+                  size='small'
+                  pagination={false}
+                  rowKey='key'
+                  dataSource={
+                    currentJobTemplate?.configs && 
Object.keys(currentJobTemplate.configs).length > 0
+                      ? Object.entries(currentJobTemplate.configs).map(([key, 
value]) => ({
+                          key,
+                          value
+                        }))
+                      : []
+                  }
+                  columns={[
+                    {
+                      title: 'Config Name',
+                      dataIndex: 'key',
+                      key: 'key',
+                      ellipsis: true
+                    },
+                    {
+                      title: 'Config Value',
+                      dataIndex: 'value',
+                      key: 'value',
+                      ellipsis: true,
+                      render: value => value || '-'
+                    }
+                  ]}
+                />
+              </>
+            )}
+            <div className='my-4'>
+              <Typography.Title level={5} className='mb-2'>
+                Details
+              </Typography.Title>
+              <Descriptions size='small' bordered column={1} labelStyle={{ 
width: 180 }}>
+                <Descriptions.Item 
label='Creator'>{currentJobTemplate?.audit?.creator || '-'}</Descriptions.Item>
+                <Descriptions.Item label='Created At'>
+                  {currentJobTemplate?.audit?.createTime ? 
formatToDateTime(currentJobTemplate.audit.createTime) : '-'}
+                </Descriptions.Item>
+                <Descriptions.Item label='Updated At'>
+                  {currentJobTemplate?.audit?.lastModifiedTime
+                    ? 
formatToDateTime(currentJobTemplate.audit.lastModifiedTime)
+                    : '-'}
+                </Descriptions.Item>
+              </Descriptions>
+            </div>
+            {anthEnable && (
+              <>
+                <Title level={5} className='mt-4 mb-2'>
+                  Associated Roles
+                </Title>
+                <AssociatedTable
+                  metalake={currentMetalake}
+                  metadataObjectType={'job_template'}
+                  metadataObjectFullName={currentJobTemplate?.name}
+                />
+              </>
+            )}
           </Drawer>
         )}
         {open && (
diff --git a/web-v2/web/src/app/jobs/page.js b/web-v2/web/src/app/jobs/page.js
index edd34cf211..bd27ace228 100644
--- a/web-v2/web/src/app/jobs/page.js
+++ b/web-v2/web/src/app/jobs/page.js
@@ -22,7 +22,7 @@ import { createContext, useContext, useMemo, useState, 
useEffect } from 'react'
 import dynamic from 'next/dynamic'
 import { useRouter, useSearchParams } from 'next/navigation'
 import { ArrowRightOutlined, ExclamationCircleFilled, PlusOutlined, 
RedoOutlined } from '@ant-design/icons'
-import { Button, Drawer, Flex, Input, Modal, Spin, Table, Tag, Tooltip, 
Typography, theme } from 'antd'
+import { Button, Descriptions, Drawer, Flex, Input, Modal, Spin, Table, Tag, 
Tooltip, Typography, theme } from 'antd'
 import { useAntdColumnResize } from 'react-antd-column-resize'
 import ConfirmInput from '@/components/ConfirmInput'
 import Icons from '@/components/Icons'
@@ -322,42 +322,29 @@ export default function JobsPage() {
         </Spin>
         {openDetailJob && (
           <Drawer
-            title={`View Job ${currentJob?.jobId} Details`}
+            title={`View Job "${currentJob?.jobId}" Details`}
             loading={jobDetailLoading}
             onClose={onClose}
             open={openDetailJob}
+            width={'40%'}
           >
-            <>
-              <div className='my-4'>
-                <div className='text-sm text-slate-400'>Job ID</div>
-                <span className='break-words 
text-base'>{currentJob?.jobId}</span>
-              </div>
-              <div className='my-4'>
-                <div className='text-sm text-slate-400'>Template Name</div>
-                <span className='break-words 
text-base'>{currentJob?.jobTemplateName}</span>
-              </div>
-              <div className='my-4'>
-                <div className='text-sm text-slate-400'>Status</div>
-                <span className='break-words text-base'>
-                  {<Tag color={getStatusColor(currentJob?.status || 
'')}>{currentJob?.status}</Tag>}
-                </span>
-              </div>
-              <div className='my-4'>
-                <div className='text-sm text-slate-400'>Details</div>
-                <div className='flex justify-between'>
-                  <span className='text-sm'>Creator: </span>
-                  <span className='text-sm'>{currentJob?.audit?.creator}</span>
-                </div>
-                <div className='flex justify-between'>
-                  <span className='text-sm'>Created At: </span>
-                  <span 
className='text-sm'>{formatToDateTime(currentJob?.audit?.createTime)}</span>
-                </div>
-                <div className='flex justify-between'>
-                  <span className='text-sm'>Updated At: </span>
-                  <span 
className='text-sm'>{formatToDateTime(currentJob?.audit?.lastModifiedTime)}</span>
-                </div>
-              </div>
-            </>
+            <Title level={5} className='mb-2'>
+              Basic Information
+            </Title>
+            <Descriptions column={1} bordered size='small'>
+              <Descriptions.Item label='Job 
ID'>{currentJob?.jobId}</Descriptions.Item>
+              <Descriptions.Item label='Template 
Name'>{currentJob?.jobTemplateName}</Descriptions.Item>
+              <Descriptions.Item label='Status'>
+                <Tag color={getStatusColor(currentJob?.status || 
'')}>{currentJob?.status}</Tag>
+              </Descriptions.Item>
+              <Descriptions.Item label='Creator'>{currentJob?.audit?.creator 
|| '-'}</Descriptions.Item>
+              <Descriptions.Item label='Created At'>
+                {currentJob?.audit?.createTime ? 
formatToDateTime(currentJob?.audit?.createTime) : '-'}
+              </Descriptions.Item>
+              <Descriptions.Item label='Updated At'>
+                {currentJob?.audit?.lastModifiedTime ? 
formatToDateTime(currentJob?.audit?.lastModifiedTime) : '-'}
+              </Descriptions.Item>
+            </Descriptions>
           </Drawer>
         )}
         {open && (
diff --git a/web-v2/web/src/components/EntityPropertiesFormItem.js 
b/web-v2/web/src/components/EntityPropertiesFormItem.js
index a2fcf16826..8fec52025a 100644
--- a/web-v2/web/src/components/EntityPropertiesFormItem.js
+++ b/web-v2/web/src/components/EntityPropertiesFormItem.js
@@ -34,6 +34,7 @@ export default function RenderPropertiesFormItem({ ...props 
}) {
   const { fields, subOpt, form, isEdit, isDisable, provider, selectBefore } = 
props
   const disableTableLevelPro = (provider && getPropInfo(provider).immutable) 
|| []
   const reservedPro = provider ? getPropInfo(provider).reserved : []
+  const allowDeletePro = provider ? getPropInfo(provider).allowDelete : true
   const auth = useAppSelector(state => state.auth)
   const { systemConfig } = auth
 
@@ -106,7 +107,8 @@ export default function RenderPropertiesFormItem({ ...props 
}) {
   }
 
   const handlePropertiesKey = (e, index) => {
-    if (isEdit) return
+    const isFieldEdit = !!form.getFieldValue(['properties', index, 'isEdit'])
+    if (isFieldEdit) return
     const key = e.target.value
 
     const locationProviders =
@@ -131,155 +133,159 @@ export default function RenderPropertiesFormItem({ 
...props }) {
 
   return (
     <div className='flex flex-col gap-2'>
-      {fields.map(subField => (
-        <Form.Item label={null} className='align-items-center mb-1' 
key={subField.key}>
-          <Flex gap='small' align='start'>
-            <Form.Item
-              name={[subField.name, 'key']}
-              className='mb-0 w-full grow'
-              label=''
-              data-refer={`props-key-${subField.name}`}
-              data-testid={`props-key-${subField.name}`}
-              rules={[
-                {
-                  pattern: new RegExp(keyRegex),
-                  message: mismatchForKey
-                },
-                ({ getFieldValue }) => ({
-                  validator(_, key) {
-                    if (getFieldValue('properties').length) {
-                      if (key) {
-                        let keys = getFieldValue('properties').map(p => p?.key)
-                        keys.splice(subField.name, 1)
-                        if (keys.includes(key)) {
-                          return Promise.reject(new Error('The key already 
exists!'))
-                        }
-                        if (reservedPro.includes(key) && !isEdit) {
-                          return Promise.reject(new Error('The key is 
reserved!'))
-                        }
-                        if (defaultPro[key]) {
-                          const { value, select, disabled, description } = 
defaultPro[key]
-                          form.setFieldValue(['properties', subField.name, 
'select'], select)
-                          form.setFieldValue(['properties', subField.name, 
'description'], description)
-                          !form.getFieldValue(['properties', subField.name, 
'value']) &&
-                            value &&
-                            form.setFieldValue(['properties', subField.name, 
'value'], value)
-                          form.setFieldValue(['properties', subField.name, 
'disabled'], disabled)
-                        } else {
-                          form.setFieldValue(['properties', subField.name, 
'select'], undefined)
-                          form.setFieldValue(['properties', subField.name, 
'description'], '')
-                          form.setFieldValue(['properties', subField.name, 
'disabled'], false)
-                        }
+      {fields.map(subField => {
+        const isFieldEdit = !!form.getFieldValue(['properties', subField.name, 
'isEdit'])
 
-                        return Promise.resolve()
-                      } else {
-                        return Promise.reject(new Error('The key is 
required!'))
-                      }
-                    }
-                  }
-                })
-              ]}
-            >
-              <Input
-                placeholder={'Key'}
-                disabled={
-                  ([...reservedPro, ...disableTableLevelPro].includes(
-                    form.getFieldValue(['properties', subField.name, 'key'])
-                  ) &&
-                    isEdit) ||
-                  isDisable
-                }
-                onChange={e => handlePropertiesKey(e, subField.name)}
-              />
-            </Form.Item>
-            <span className='w-full'>
+        return (
+          <Form.Item label={null} className='align-items-center mb-1' 
key={subField.key}>
+            <Flex gap='small' align='start'>
               <Form.Item
-                name={[subField.name, 'value']}
+                name={[subField.name, 'key']}
                 className='mb-0 w-full grow'
                 label=''
-                data-refer={`props-value-${subField.name}`}
-                data-testid={`props-value-${subField.name}`}
-              >
-                {form.getFieldValue(['properties', subField.name, 'select']) ? 
(
-                  <Select
-                    className='flex-none'
-                    mode={form.getFieldValue(['properties', subField.name, 
'multiple']) ? 'multiple' : undefined}
-                    disabled={form.getFieldValue(['properties', subField.name, 
'disabled'])}
-                    placeholder={
-                      form.getFieldValue(['properties', subField.name, 
'description'])
-                        ? `${form.getFieldValue(['properties', subField.name, 
'description'])}`
-                        : 'Value'
-                    }
-                  >
-                    {form.getFieldValue(['properties', subField.name, 
'select']).map(item => (
-                      <Select.Option value={item} key={item}>
-                        {item}
-                      </Select.Option>
-                    ))}
-                  </Select>
-                ) : (
-                  <Input
-                    placeholder={
-                      form.getFieldValue(['properties', subField.name, 
'description'])
-                        ? `${form.getFieldValue(['properties', subField.name, 
'description'])}`
-                        : 'Value'
-                    }
-                    disabled={
-                      ([...reservedPro, ...disableTableLevelPro].includes(
-                        form.getFieldValue(['properties', subField.name, 
'key'])
-                      ) &&
-                        isEdit) ||
-                      isDisable ||
-                      form.getFieldValue(['properties', subField.name, 
'disabled'])
-                    }
-                    type={
-                      form.getFieldValue(['properties', subField.name, 
'key'])?.includes('password')
-                        ? 'password'
-                        : 'text'
-                    }
-                    addonBefore={
-                      form.getFieldValue(['properties', subField.name, 
'selectBefore']) ? (
-                        <Select
-                          value={form.getFieldValue(['properties', 
subField.name, 'prefix'])}
-                          key={form.getFieldValue(['properties', 
subField.name, 'selectBefore']).join(',')}
-                          onChange={value => form.setFieldValue(['properties', 
subField.name, 'prefix'], value)}
-                        >
-                          {form.getFieldValue(['properties', subField.name, 
'selectBefore']).map(item => (
-                            <Select.Option key={item} value={item}>
-                              {item}
-                            </Select.Option>
-                          ))}
-                        </Select>
-                      ) : null
+                data-refer={`props-key-${subField.name}`}
+                data-testid={`props-key-${subField.name}`}
+                rules={[
+                  {
+                    pattern: new RegExp(keyRegex),
+                    message: mismatchForKey
+                  },
+                  ({ getFieldValue }) => ({
+                    validator(_, key) {
+                      if (getFieldValue('properties').length) {
+                        if (key) {
+                          let keys = getFieldValue('properties').map(p => 
p?.key)
+                          keys.splice(subField.name, 1)
+                          if (keys.includes(key)) {
+                            return Promise.reject(new Error('The key already 
exists!'))
+                          }
+                          if (reservedPro.includes(key) && !isFieldEdit) {
+                            return Promise.reject(new Error('The key is 
reserved!'))
+                          }
+                          if (defaultPro[key]) {
+                            const { value, select, disabled, description } = 
defaultPro[key]
+                            form.setFieldValue(['properties', subField.name, 
'select'], select)
+                            form.setFieldValue(['properties', subField.name, 
'description'], description)
+                            !form.getFieldValue(['properties', subField.name, 
'value']) &&
+                              value &&
+                              form.setFieldValue(['properties', subField.name, 
'value'], value)
+                            form.setFieldValue(['properties', subField.name, 
'disabled'], disabled)
+                          } else {
+                            form.setFieldValue(['properties', subField.name, 
'select'], undefined)
+                            form.setFieldValue(['properties', subField.name, 
'description'], '')
+                            form.setFieldValue(['properties', subField.name, 
'disabled'], false)
+                          }
+
+                          return Promise.resolve()
+                        } else {
+                          return Promise.reject(new Error('The key is 
required!'))
+                        }
+                      }
                     }
-                  />
-                )}
+                  })
+                ]}
+              >
+                <Input
+                  placeholder={'Key'}
+                  disabled={
+                    ([...reservedPro, ...disableTableLevelPro].includes(
+                      form.getFieldValue(['properties', subField.name, 'key'])
+                    ) &&
+                      isFieldEdit) ||
+                    isDisable
+                  }
+                  onChange={e => handlePropertiesKey(e, subField.name)}
+                />
               </Form.Item>
-            </span>
-            <Icons.Minus
-              className={cn('size-8 cursor-pointer text-gray-400 
hover:text-defaultPrimary', {
-                'text-gray-100 hover:text-gray-200 cursor-not-allowed':
-                  ([...reservedPro, ...disableTableLevelPro].includes(
-                    form.getFieldValue(['properties', subField.name, 'key'])
-                  ) &&
-                    isEdit) ||
-                  isDisable
-              })}
-              onClick={() => {
-                if (
-                  ([...reservedPro, ...disableTableLevelPro].includes(
-                    form.getFieldValue(['properties', subField.name, 'key'])
-                  ) &&
-                    isEdit) ||
-                  isDisable
-                )
-                  return
-                subOpt.remove(subField.name)
-              }}
-            />
-          </Flex>
-        </Form.Item>
-      ))}
+              <span className='w-full'>
+                <Form.Item
+                  name={[subField.name, 'value']}
+                  className='mb-0 w-full grow'
+                  label=''
+                  data-refer={`props-value-${subField.name}`}
+                  data-testid={`props-value-${subField.name}`}
+                >
+                  {form.getFieldValue(['properties', subField.name, 'select']) 
? (
+                    <Select
+                      className='flex-none'
+                      mode={form.getFieldValue(['properties', subField.name, 
'multiple']) ? 'multiple' : undefined}
+                      disabled={form.getFieldValue(['properties', 
subField.name, 'disabled'])}
+                      placeholder={
+                        form.getFieldValue(['properties', subField.name, 
'description'])
+                          ? `${form.getFieldValue(['properties', 
subField.name, 'description'])}`
+                          : 'Value'
+                      }
+                    >
+                      {form.getFieldValue(['properties', subField.name, 
'select']).map(item => (
+                        <Select.Option value={item} key={item}>
+                          {item}
+                        </Select.Option>
+                      ))}
+                    </Select>
+                  ) : (
+                    <Input
+                      placeholder={
+                        form.getFieldValue(['properties', subField.name, 
'description'])
+                          ? `${form.getFieldValue(['properties', 
subField.name, 'description'])}`
+                          : 'Value'
+                      }
+                      disabled={
+                        ([...reservedPro, ...disableTableLevelPro].includes(
+                          form.getFieldValue(['properties', subField.name, 
'key'])
+                        ) &&
+                          isFieldEdit) ||
+                        isDisable ||
+                        form.getFieldValue(['properties', subField.name, 
'disabled'])
+                      }
+                      type={
+                        form.getFieldValue(['properties', subField.name, 
'key'])?.includes('password')
+                          ? 'password'
+                          : 'text'
+                      }
+                      addonBefore={
+                        form.getFieldValue(['properties', subField.name, 
'selectBefore']) ? (
+                          <Select
+                            value={form.getFieldValue(['properties', 
subField.name, 'prefix'])}
+                            key={form.getFieldValue(['properties', 
subField.name, 'selectBefore']).join(',')}
+                            onChange={value => 
form.setFieldValue(['properties', subField.name, 'prefix'], value)}
+                          >
+                            {form.getFieldValue(['properties', subField.name, 
'selectBefore']).map(item => (
+                              <Select.Option key={item} value={item}>
+                                {item}
+                              </Select.Option>
+                            ))}
+                          </Select>
+                        ) : null
+                      }
+                    />
+                  )}
+                </Form.Item>
+              </span>
+              <Icons.Minus
+                className={cn('size-8 cursor-pointer text-gray-400 
hover:text-defaultPrimary', {
+                  'text-gray-100 hover:text-gray-200 cursor-not-allowed':
+                    ([...reservedPro, ...disableTableLevelPro].includes(
+                      form.getFieldValue(['properties', subField.name, 'key'])
+                    ) &&
+                      isFieldEdit) ||
+                    isDisable
+                })}
+                onClick={() => {
+                  if (
+                    ([...reservedPro, ...disableTableLevelPro].includes(
+                      form.getFieldValue(['properties', subField.name, 'key'])
+                    ) &&
+                      isFieldEdit) ||
+                    isDisable
+                  )
+                    return
+                  subOpt.remove(subField.name)
+                }}
+              />
+            </Flex>
+          </Form.Item>
+        )
+      })}
       {rangerAuthType && !isEdit ? (
         <Dropdown.Button
           data-refer='add-props'

Reply via email to