From a45d86c568a21358efeb93f8f1e5cd452e56e325 Mon Sep 17 00:00:00 2001
From: ibuler <ibuler@qq.com>
Date: Wed, 2 Apr 2025 19:14:40 +0800
Subject: [PATCH 1/5] feat: ad domain as asset

---
 src/App.vue                                   |  3 +--
 .../Apps/AccountCreateUpdateForm/const.js     | 17 +++++++-------
 .../Apps/AccountListTable/AccountList.vue     | 15 ++++++++++--
 .../TableFormatters/AccountInfoFormatter.vue  | 14 +++++++++--
 src/store/modules/assets.js                   |  3 +++
 .../AssetCreateUpdate/ADCreateUpdate.vue      | 23 +++++++++++++++++++
 .../assets/Asset/AssetDetail/Account.vue      |  4 ++--
 src/views/assets/Asset/AssetList/ADList.vue   | 21 +++++++++++++++++
 .../Asset/AssetList/components/BaseList.vue   |  3 ++-
 .../AssetList/components/PlatformDialog.vue   |  6 ++---
 src/views/assets/Asset/AssetList/index.vue    |  6 +++++
 .../assets/Platform/PlatformCreateUpdate.vue  |  9 ++++++--
 src/views/assets/Platform/PlatformList.vue    | 20 +++++++++-------
 src/views/assets/Platform/const.js            | 13 +++++++++++
 14 files changed, 126 insertions(+), 31 deletions(-)
 create mode 100644 src/views/assets/Asset/AssetCreateUpdate/ADCreateUpdate.vue
 create mode 100644 src/views/assets/Asset/AssetList/ADList.vue

diff --git a/src/App.vue b/src/App.vue
index f558a2ee0..6777688f2 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -45,7 +45,6 @@ export default {
   },
   methods: {
     createWatermark() {
-      console.log('currentUser', this.currentUser)
       if (this.currentUser?.username && this.publicSettings?.SECURITY_WATERMARK_ENABLED) {
         this.watermark = new Watermark({
           content: `${this.currentUser.username}(${this.currentUser.name})`,
@@ -53,7 +52,7 @@ export default {
           height: 200,
           rotate: 45,
           fontWeight: 'normal',
-          fontColor: 'rgba(128, 128, 128, 0.3)'
+          fontColor: 'rgba(128, 128, 128, 0.2)'
         })
 
         this.watermark.create()
diff --git a/src/components/Apps/AccountCreateUpdateForm/const.js b/src/components/Apps/AccountCreateUpdateForm/const.js
index e320c9c06..3affbde3e 100644
--- a/src/components/Apps/AccountCreateUpdateForm/const.js
+++ b/src/components/Apps/AccountCreateUpdateForm/const.js
@@ -6,6 +6,13 @@ import AutomationParamsForm from '@/views/assets/Platform/AutomationParamsSettin
 export const accountFieldsMeta = (vm) => {
   const defaultPrivilegedAccounts = ['root', 'administrator']
 
+  function onPrivilegedUser(value, updateForm) {
+    const maybePrivileged = defaultPrivilegedAccounts.includes(value)
+    if (maybePrivileged) {
+      updateForm({ privileged: true, secret_reset: false, push_now: false })
+    }
+  }
+
   return {
     assets: {
       component: Select2,
@@ -70,11 +77,8 @@ export const accountFieldsMeta = (vm) => {
             if (!vm.account?.name) {
               updateForm({ username: value })
             }
-            const maybePrivileged = defaultPrivilegedAccounts.includes(value)
-            if (maybePrivileged) {
-              updateForm({ privileged: true })
-            }
           }
+          onPrivilegedUser(value, updateForm)
         }
       },
       hidden: () => {
@@ -92,10 +96,7 @@ export const accountFieldsMeta = (vm) => {
           vm.usernameChanged = true
         },
         change: ([value], updateForm) => {
-          const maybePrivileged = defaultPrivilegedAccounts.includes(value)
-          if (maybePrivileged) {
-            updateForm({ privileged: true })
-          }
+          onPrivilegedUser(value, updateForm)
         }
       },
       hidden: () => {
diff --git a/src/components/Apps/AccountListTable/AccountList.vue b/src/components/Apps/AccountListTable/AccountList.vue
index 191b777d4..1220d33f6 100644
--- a/src/components/Apps/AccountListTable/AccountList.vue
+++ b/src/components/Apps/AccountListTable/AccountList.vue
@@ -147,6 +147,10 @@ export default {
     showActions: {
       type: Boolean,
       default: true
+    },
+    target: {
+      type: Object,
+      default: null
     }
   },
   data() {
@@ -182,7 +186,7 @@ export default {
         },
         columnsMeta: {
           name: {
-            width: '120px',
+            minWidth: '120px',
             formatterArgs: {
               can: () => vm.$hasPerm('accounts.view_account'),
               getRoute: ({ row }) => ({
@@ -234,7 +238,14 @@ export default {
             }
           },
           username: {
-            width: '120px'
+            minWidth: '120px',
+            formatter: function(row) {
+              if (row.ad_domain) {
+                return `${row.username}@${row.ad_domain}`
+              } else {
+                return row.username
+              }
+            }
           },
           secret_type: {
             formatter: function(row) {
diff --git a/src/components/Table/TableFormatters/AccountInfoFormatter.vue b/src/components/Table/TableFormatters/AccountInfoFormatter.vue
index f1204c926..ad0dad1af 100644
--- a/src/components/Table/TableFormatters/AccountInfoFormatter.vue
+++ b/src/components/Table/TableFormatters/AccountInfoFormatter.vue
@@ -10,7 +10,7 @@
         <span>{{ $t('No accounts') }}</span>
       </div>
       <div v-for="account of accountData" :key="account.id" class="detail-item">
-        <span>{{ account.name }}({{ account.username }})</span>
+        <span>{{ getDisplay(account) }}</span>
       </div>
     </div>
     <el-button slot="reference" class="link-btn" plain size="mini" type="primary">
@@ -39,10 +39,20 @@ export default {
     }
   },
   methods: {
+    getDisplay(account) {
+      const { username, name } = account
+      if (username.startsWith('@')) {
+        return name
+      } else if (name === username) {
+        return username
+      } else {
+        return `${name}(${username})`
+      }
+    },
     async getAsyncItems() {
       this.loading = true
       const userId = this.$route.params.id || 'self'
-      const url = `/api/v1/perms/users/${userId}/assets/${this.row.id}`
+      const url = `/api/v1/perms/users/${userId}/assets/${this.row.id}/`
       this.$axios.get(url).then(res => {
         this.accountData = res?.permed_accounts || []
       }).finally(() => {
diff --git a/src/store/modules/assets.js b/src/store/modules/assets.js
index 360cff4fe..0e772705f 100644
--- a/src/store/modules/assets.js
+++ b/src/store/modules/assets.js
@@ -89,6 +89,9 @@ const actions = {
         })
     })
   },
+  cleanPlatforms({ commit, dispatch, state }) {
+    state.platforms = []
+  },
   addToRecentPlatforms({ commit, display, state }, platform) {
     const recentPlatformIds = state.recentPlatformIds.filter(i => i !== platform.id)
     recentPlatformIds.unshift(platform.id)
diff --git a/src/views/assets/Asset/AssetCreateUpdate/ADCreateUpdate.vue b/src/views/assets/Asset/AssetCreateUpdate/ADCreateUpdate.vue
new file mode 100644
index 000000000..cfb370955
--- /dev/null
+++ b/src/views/assets/Asset/AssetCreateUpdate/ADCreateUpdate.vue
@@ -0,0 +1,23 @@
+<template>
+  <BaseAssetCreateUpdate v-bind="$data" />
+</template>
+
+<script>
+import BaseAssetCreateUpdate from './BaseAssetCreateUpdate'
+
+export default {
+  name: 'ADCreateUpdate',
+  components: { BaseAssetCreateUpdate },
+  data() {
+    return {
+      url: '/api/v1/assets/directories/',
+      addFields: [
+        [this.$t('Domain name'), ['domain_name'], 1]
+      ]
+    }
+  }
+}
+</script>
+
+<style>
+</style>
diff --git a/src/views/assets/Asset/AssetDetail/Account.vue b/src/views/assets/Asset/AssetDetail/Account.vue
index 6a61b6164..82260865e 100644
--- a/src/views/assets/Asset/AssetDetail/Account.vue
+++ b/src/views/assets/Asset/AssetDetail/Account.vue
@@ -59,7 +59,7 @@ export default {
     return {
       title: this.$t('Test'),
       templateDialogVisible: false,
-      columnsDefault: ['name', 'username', 'asset', 'connect'],
+      columnsDefault: ['name', 'username', 'connect'],
       headerExtraActions: [
         {
           name: this.$t('AccountTemplate'),
@@ -96,7 +96,7 @@ export default {
   },
   computed: {
     iUrl() {
-      return this.url || `/api/v1/accounts/accounts/?asset=${this.object.id}`
+      return this.url || `/api/v1/assets/assets/${this.object.id}/accounts/`
     }
   },
   methods: {
diff --git a/src/views/assets/Asset/AssetList/ADList.vue b/src/views/assets/Asset/AssetList/ADList.vue
new file mode 100644
index 000000000..c9365bae1
--- /dev/null
+++ b/src/views/assets/Asset/AssetList/ADList.vue
@@ -0,0 +1,21 @@
+<template>
+  <BaseList v-bind="tableConfig" />
+</template>
+
+<script>
+import BaseList from './components/BaseList'
+
+export default {
+  components: {
+    BaseList
+  },
+  data() {
+    return {
+      tableConfig: {
+        category: 'ad',
+        url: '/api/v1/assets/directories/'
+      }
+    }
+  }
+}
+</script>
diff --git a/src/views/assets/Asset/AssetList/components/BaseList.vue b/src/views/assets/Asset/AssetList/components/BaseList.vue
index 6be5f7876..f5aab55fa 100644
--- a/src/views/assets/Asset/AssetList/components/BaseList.vue
+++ b/src/views/assets/Asset/AssetList/components/BaseList.vue
@@ -143,7 +143,8 @@ export default {
         'custom': () => import('@/views/assets/Asset/AssetCreateUpdate/CustomCreateUpdate.vue'),
         'cloud': () => import('@/views/assets/Asset/AssetCreateUpdate/CloudCreateUpdate.vue'),
         'device': () => import('@/views/assets/Asset/AssetCreateUpdate/DeviceCreateUpdate.vue'),
-        'database': () => import('@/views/assets/Asset/AssetCreateUpdate/DatabaseCreateUpdate.vue')
+        'database': () => import('@/views/assets/Asset/AssetCreateUpdate/DatabaseCreateUpdate.vue'),
+        'ad': () => import('@/views/assets/Asset/AssetCreateUpdate/ADCreateUpdate.vue')
       },
       createProps: {},
       showPlatform: false,
diff --git a/src/views/assets/Asset/AssetList/components/PlatformDialog.vue b/src/views/assets/Asset/AssetList/components/PlatformDialog.vue
index bad249702..9c01c3dfc 100644
--- a/src/views/assets/Asset/AssetList/components/PlatformDialog.vue
+++ b/src/views/assets/Asset/AssetList/components/PlatformDialog.vue
@@ -76,7 +76,7 @@ export default {
       platforms: [],
       recentPlatformIds: [],
       loading: true,
-      activeType: 'host',
+      activeType: [],
       recentUsedLabel: this.$t('RecentlyUsed'),
       typeIconMapper: {
         linux: 'fa-linux',
@@ -130,9 +130,7 @@ export default {
   async created() {
     this.platforms = await this.$store.dispatch('assets/getPlatforms')
     this.allRecentPlatforms = await this.$store.dispatch('assets/getRecentPlatforms')
-    if (this.allRecentPlatforms.length > 0) {
-      this.activeType = this.recentUsedLabel
-    }
+    this.activeType = Object.keys(this.iPlatforms)[0]
     this.loading = false
   },
   methods: {
diff --git a/src/views/assets/Asset/AssetList/index.vue b/src/views/assets/Asset/AssetList/index.vue
index c8ccada5d..ee3dbb001 100644
--- a/src/views/assets/Asset/AssetList/index.vue
+++ b/src/views/assets/Asset/AssetList/index.vue
@@ -56,6 +56,12 @@ export default {
             hidden: true,
             component: () => import('@/views/assets/Asset/AssetList/WebList.vue')
           },
+          {
+            icon: 'fa-comment',
+            name: 'ad',
+            hidden: true,
+            component: () => import('@/views/assets/Asset/AssetList/ADList.vue')
+          },
           {
             icon: 'fa-comment',
             name: 'gpt',
diff --git a/src/views/assets/Platform/PlatformCreateUpdate.vue b/src/views/assets/Platform/PlatformCreateUpdate.vue
index 03fe74e6f..008b3363c 100644
--- a/src/views/assets/Platform/PlatformCreateUpdate.vue
+++ b/src/views/assets/Platform/PlatformCreateUpdate.vue
@@ -11,6 +11,7 @@
       :has-reset="false"
       :initial="initial"
       :url="url"
+      @submitSuccess="onSubmitSuccess"
     />
   </div>
 </template>
@@ -49,7 +50,8 @@ export default {
         ]],
         [this.$t('Config'), [
           'protocols', 'su_enabled', 'su_method',
-          'domain_enabled', 'charset'
+          'domain_enabled', 'ad_enabled', 'ad',
+          'charset'
         ]],
         [this.$t('Automations'), ['automation']],
         [this.$t('Other'), ['comment']]
@@ -105,6 +107,9 @@ export default {
     }
   },
   methods: {
+    onSubmitSuccess() {
+      this.$store.dispatch('assets/cleanPlatforms')
+    },
     updateSuMethodOptions() {
       const options = this.suMethods.filter(i => {
         return this.suMethodLimits.includes(i.value)
@@ -141,7 +146,6 @@ export default {
       const constraints = await this.$axios.get(url)
       this.defaultOptions = constraints
 
-      const fieldsCheck = ['domain_enabled', 'su_enabled']
       let protocols = constraints?.protocols || []
       protocols = protocols?.map(i => {
         if (i.name === 'http') {
@@ -151,6 +155,7 @@ export default {
       })
       this.fieldsMeta.protocols.el.choices = protocols
 
+      const fieldsCheck = ['domain_enabled', 'su_enabled', 'ad_enabled']
       for (const field of fieldsCheck) {
         const disabled = constraints[field] === false
         this.initial[field] = !disabled
diff --git a/src/views/assets/Platform/PlatformList.vue b/src/views/assets/Platform/PlatformList.vue
index b4e168a7f..7c68de1cd 100644
--- a/src/views/assets/Platform/PlatformList.vue
+++ b/src/views/assets/Platform/PlatformList.vue
@@ -149,14 +149,7 @@ export default {
       return `/api/v1/assets/platforms/?category=${this.tab.activeMenu}`
     }
   },
-  deactivated() {
-    window.localStorage.setItem('lastTab', this.tab.activeMenu)
-  },
   activated() {
-    setTimeout(() => {
-      this.tab.activeMenu = window.localStorage.getItem('lastTab') || 'host'
-      this.$refs.genericListTable?.reloadTable()
-    }, 300)
   },
   async mounted() {
     try {
@@ -173,9 +166,19 @@ export default {
       this.tableConfig.url = this.url
       this.headerActions.importOptions.url = this.url
       this.headerActions.exportOptions.url = this.url
-      this.headerActions.moreCreates.dropdown = this.$store.state.assets.assetCategoriesDropdown.filter(item => {
+      const types = this.$store.state.assets.assetCategoriesDropdown.filter(item => {
         return item.category === this.tab.activeMenu
+      }).map(item => {
+        if (!item.group) {
+          return item
+        } else {
+          return {
+            ...item,
+            group: item.group + this.$t('WordSep') + this.$t('Type')
+          }
+        }
       })
+      this.headerActions.moreCreates.dropdown = types
     },
     async setCategoriesTab() {
       const categoryIcon = {
@@ -185,6 +188,7 @@ export default {
         cloud: 'fa-cloud',
         web: 'fa-globe',
         gpt: 'fa-comment',
+        ad: 'fa-comment',
         custom: 'fa-cube'
       }
       const state = await this.$store.dispatch('assets/getAssetCategories')
diff --git a/src/views/assets/Platform/const.js b/src/views/assets/Platform/const.js
index f71cc94ff..b1f0250b8 100644
--- a/src/views/assets/Platform/const.js
+++ b/src/views/assets/Platform/const.js
@@ -81,6 +81,19 @@ export const platformFieldsMeta = (vm) => {
         disabled: false
       }
     },
+    ad_enabled: {
+      el: {
+        disabled: false
+      }
+    },
+    ad: {
+      el: {
+        multiple: false,
+        url: '/api/v1/assets/directories/',
+        disabled: false
+      },
+      hidden: (formValue) => !formValue['ad_enabled']
+    },
     protocols: {
       label: i18n.t('SupportedProtocol'),
       ...assetMeta.protocols,

From 0a7704c06ce823870ce16bb91c0ca2f582c30f90 Mon Sep 17 00:00:00 2001
From: ibuler <ibuler@qq.com>
Date: Mon, 7 Apr 2025 19:10:33 +0800
Subject: [PATCH 2/5] perf: rename ad to ds

---
 .../Apps/AccountListTable/AccountList.vue     |  15 +-
 .../AccountConnectFormatter.vue               | 135 ++++++++----------
 .../AssetCreateUpdate/ADCreateUpdate.vue      |   2 +-
 .../BaseAssetCreateUpdate.vue                 |   2 +-
 .../AssetList/{ADList.vue => DSList.vue}      |   2 +-
 .../Asset/AssetList/components/BaseList.vue   |   2 +-
 .../AssetList/components/PlatformDialog.vue   |   3 +-
 src/views/assets/Asset/AssetList/index.vue    |   6 +-
 .../assets/Platform/PlatformCreateUpdate.vue  |  12 +-
 src/views/assets/Platform/PlatformList.vue    |  18 +--
 src/views/assets/Platform/const.js            |   6 +-
 src/views/assets/const.js                     |   6 +-
 12 files changed, 97 insertions(+), 112 deletions(-)
 rename src/views/assets/Asset/AssetList/{ADList.vue => DSList.vue} (92%)

diff --git a/src/components/Apps/AccountListTable/AccountList.vue b/src/components/Apps/AccountListTable/AccountList.vue
index 1220d33f6..4b8863030 100644
--- a/src/components/Apps/AccountListTable/AccountList.vue
+++ b/src/components/Apps/AccountListTable/AccountList.vue
@@ -148,6 +148,7 @@ export default {
       type: Boolean,
       default: true
     },
+    // target for ad connect btn, if not has, ad account should be select one
     target: {
       type: Object,
       default: null
@@ -186,7 +187,7 @@ export default {
         },
         columnsMeta: {
           name: {
-            minWidth: '120px',
+            minWidth: '60px',
             formatterArgs: {
               can: () => vm.$hasPerm('accounts.view_account'),
               getRoute: ({ row }) => ({
@@ -212,15 +213,9 @@ export default {
             width: '80px',
             formatter: AccountConnectFormatter,
             formatterArgs: {
-              buttonIcon: 'fa fa-desktop',
-              url: '/api/v1/assets/assets/{id}',
               can: () => this.currentUserIsSuperAdmin,
-              connectUrlTemplate: (row) => `/luna/direct_connect/${row.id}/${row.username}/${row.asset.id}/${row.asset.name}/`,
-              setMapItem: (id, protocol) => {
-                this.$store.commit('table/SET_PROTOCOL_MAP_ITEM', {
-                  key: id,
-                  value: protocol
-                })
+              connectUrlTemplate: (row) => {
+
               }
             }
           },
@@ -238,7 +233,7 @@ export default {
             }
           },
           username: {
-            minWidth: '120px',
+            minWidth: '60px',
             formatter: function(row) {
               if (row.ad_domain) {
                 return `${row.username}@${row.ad_domain}`
diff --git a/src/components/Table/TableFormatters/AccountConnectFormatter.vue b/src/components/Table/TableFormatters/AccountConnectFormatter.vue
index 6a2493fe1..2fde206d3 100644
--- a/src/components/Table/TableFormatters/AccountConnectFormatter.vue
+++ b/src/components/Table/TableFormatters/AccountConnectFormatter.vue
@@ -1,24 +1,32 @@
 <template>
   <div>
     <el-dropdown
-      v-if="hasPerm"
+      :disabled="!hasPerm"
+      :show-timeout="500"
+      class="action-connect"
       size="small"
       trigger="hover"
-      :show-timeout="500"
-      @command="handleCommand"
+      type="primary"
+      @command="handleProtocolConnect"
       @visible-change="visibleChange"
     >
       <el-button
         plain
         size="mini"
         type="primary"
-        @click="handlePamConnect"
+        @click="handleBtnConnect"
       >
-        <i :class="IButtonIcon" />
+        <i :class="iButtonIcon" />
       </el-button>
-      <el-dropdown-menu slot="dropdown">
-        <el-dropdown-item command="Title" disabled>
-          {{ ITitleText }}
+
+      <el-dropdown-menu v-if="!isClick" slot="dropdown">
+        <el-dropdown-item command="title" disabled>
+          <div v-if="getProtocolsLoading">
+            {{ $t('Loading') }}
+          </div>
+          <div v-else>
+            {{ dropdownTitle }}
+          </div>
         </el-dropdown-item>
         <el-dropdown-item divided />
         <el-dropdown-item
@@ -30,16 +38,6 @@
         </el-dropdown-item>
       </el-dropdown-menu>
     </el-dropdown>
-
-    <el-button
-      v-else
-      plain
-      size="mini"
-      type="primary"
-      :disabled="!hasPerm"
-    >
-      <i :class="IButtonIcon" style="color: #fff" />
-    </el-button>
   </div>
 </template>
 
@@ -50,85 +48,70 @@ export default {
   name: 'AccountConnectFormatter',
   extends: BaseFormatter,
   props: {
-    buttonIcon: {
-      type: String,
-      default: 'fa fa-desktop'
-    },
-    titleText: {
-      type: String,
-      default: ''
-    },
-    url: {
-      type: String,
-      default: ''
+    formatterArgsDefault: {
+      type: Object,
+      default() {
+        return {
+          can: () => true,
+          getConnectUrl: (row, protocol) => {
+            return `/luna/admin-connect/?
+              asset=${row.asset.id}
+              &account=${row.id}
+              &protocol=${protocol}
+            `.replace(/\s+/g, '')
+          },
+          assetUrl: '/api/v1/assets/assets/{id}/',
+          buttonIcon: 'fa fa-desktop'
+        }
+      }
     }
   },
   data() {
     return {
-      hasPerm: false,
-      protocols: []
+      formatterArgs: Object.assign(this.formatterArgsDefault, this.col.formatterArgs),
+      protocols: [],
+      isClick: false,
+      getProtocolsLoading: false,
+      dropdownTitle: this.$t('Protocols')
     }
   },
   computed: {
-    IButtonIcon() {
-      return this.buttonIcon
+    iButtonIcon() {
+      return this.formatterArgs.buttonIcon
     },
-    ITitleText() {
-      return this.titleText || this.$t('SelectProtocol')
+    hasPerm() {
+      return this.formatterArgs.can(this.row, this.cellValue)
     }
   },
-  mounted() {
-    this.hasPerm = this.formatterArgs.can()
-  },
   methods: {
-    handleCommand(protocol) {
-      if (protocol === 'Title') return
-
-      this.formatterArgs.setMapItem(this.row.id, protocol)
-      this.handleWindowOpen(this.row, protocol)
+    handleProtocolConnect(protocol) {
+      const url = this.formatterArgs.getConnectUrl(this.row, protocol)
+      window.open(url, '_blank')
     },
     visibleChange(visible) {
       if (visible) {
         this.getProtocols(this.row.asset.id)
       }
     },
-    handleWindowOpen(row, protocol) {
-      const url = this.formatterArgs.connectUrlTemplate(row) + `${protocol}`
-
-      this.$nextTick(() => {
-        window.open(url, '_blank')
-      })
-    },
-    async handlePamConnect() {
-      const protocolMap = this.$store.getters.protocolMap
-
-      if (protocolMap.has(this.row.id)) {
-        // 直连
-        const protocol = protocolMap.get(this.row.id)
-        this.handleWindowOpen(this.row, protocol)
-      } else {
-        try {
-          const url = this.formatterArgs.url.replace('{id}', this.row.asset.id)
-          const res = await this.$axios.get(url)
-
-          if (res && res.protocols.length > 0) {
-            const protocol = res.protocols[0]
-
-            this.formatterArgs.setMapItem(this.row.id, protocol.name)
-            this.handleWindowOpen(this.row, protocol.name)
-          }
-        } catch (e) {
-          throw new Error(`Error getting protocols: ${e}`)
-        }
+    async handleBtnConnect() {
+      this.isClick = true
+      if (this.protocols === 0) {
+        await this.getProtocols(this.row.asset.id)
       }
+
+      if (this.protocols.length > 0) {
+        this.handleProtocolConnect(this.protocols[0].name)
+      }
+      setTimeout(() => {
+        this.isClick = false
+      }, 1000)
     },
     async getProtocols(assetId) {
+      if (this.protocols.length > 0) return
       try {
-        const url = this.formatterArgs.url.replace('{id}', assetId)
+        const url = this.formatterArgs.assetUrl.replace('{id}', assetId)
         const res = await this.$axios.get(url)
-
-        // 暂将 SFTP 过滤
-        if (res) this.protocols = res.protocols
+        this.protocols = res.protocols || []
       } catch (e) {
         throw new Error(`Error getting protocols: ${e}`)
       }
@@ -137,7 +120,7 @@ export default {
 }
 </script>
 
-<style scoped lang="scss">
+<style lang="scss" scoped>
 .el-dropdown-menu__item.is-disabled {
   font-weight: 500;
   color: var(--el-text-color-secondary);
diff --git a/src/views/assets/Asset/AssetCreateUpdate/ADCreateUpdate.vue b/src/views/assets/Asset/AssetCreateUpdate/ADCreateUpdate.vue
index cfb370955..5a6c0556c 100644
--- a/src/views/assets/Asset/AssetCreateUpdate/ADCreateUpdate.vue
+++ b/src/views/assets/Asset/AssetCreateUpdate/ADCreateUpdate.vue
@@ -12,7 +12,7 @@ export default {
     return {
       url: '/api/v1/assets/directories/',
       addFields: [
-        [this.$t('Domain name'), ['domain_name'], 1]
+        [this.$t('DomainName'), ['domain_name'], 1]
       ]
     }
   }
diff --git a/src/views/assets/Asset/AssetCreateUpdate/BaseAssetCreateUpdate.vue b/src/views/assets/Asset/AssetCreateUpdate/BaseAssetCreateUpdate.vue
index 8775244ac..c08e94f15 100644
--- a/src/views/assets/Asset/AssetCreateUpdate/BaseAssetCreateUpdate.vue
+++ b/src/views/assets/Asset/AssetCreateUpdate/BaseAssetCreateUpdate.vue
@@ -114,7 +114,7 @@ export default {
     },
     async genConfig() {
       const { addFields, addFieldsMeta, defaultConfig } = this
-      defaultConfig.fieldsMeta = assetFieldsMeta(this, this.$route.query.type)
+      defaultConfig.fieldsMeta = assetFieldsMeta(this)
       let url = this.url
       const id = this.$route.params.id
       if (!id) {
diff --git a/src/views/assets/Asset/AssetList/ADList.vue b/src/views/assets/Asset/AssetList/DSList.vue
similarity index 92%
rename from src/views/assets/Asset/AssetList/ADList.vue
rename to src/views/assets/Asset/AssetList/DSList.vue
index c9365bae1..fbbd420eb 100644
--- a/src/views/assets/Asset/AssetList/ADList.vue
+++ b/src/views/assets/Asset/AssetList/DSList.vue
@@ -12,7 +12,7 @@ export default {
   data() {
     return {
       tableConfig: {
-        category: 'ad',
+        category: 'ds',
         url: '/api/v1/assets/directories/'
       }
     }
diff --git a/src/views/assets/Asset/AssetList/components/BaseList.vue b/src/views/assets/Asset/AssetList/components/BaseList.vue
index f5aab55fa..d26a4b8e5 100644
--- a/src/views/assets/Asset/AssetList/components/BaseList.vue
+++ b/src/views/assets/Asset/AssetList/components/BaseList.vue
@@ -144,7 +144,7 @@ export default {
         'cloud': () => import('@/views/assets/Asset/AssetCreateUpdate/CloudCreateUpdate.vue'),
         'device': () => import('@/views/assets/Asset/AssetCreateUpdate/DeviceCreateUpdate.vue'),
         'database': () => import('@/views/assets/Asset/AssetCreateUpdate/DatabaseCreateUpdate.vue'),
-        'ad': () => import('@/views/assets/Asset/AssetCreateUpdate/ADCreateUpdate.vue')
+        'ds': () => import('@/views/assets/Asset/AssetCreateUpdate/ADCreateUpdate.vue')
       },
       createProps: {},
       showPlatform: false,
diff --git a/src/views/assets/Asset/AssetList/components/PlatformDialog.vue b/src/views/assets/Asset/AssetList/components/PlatformDialog.vue
index 9c01c3dfc..4fad5858e 100644
--- a/src/views/assets/Asset/AssetList/components/PlatformDialog.vue
+++ b/src/views/assets/Asset/AssetList/components/PlatformDialog.vue
@@ -5,7 +5,7 @@
     :show-confirm="false"
     :title="$tc('SelectPlatform')"
     :visible.sync="iVisible"
-    size="600px"
+    size="700px"
     top="1vh"
   >
     <template #title>
@@ -61,6 +61,7 @@ import { loadPlatformIcon } from '@/utils/jms'
 
 export default {
   name: 'PlatformDrawer',
+  components: {},
   props: {
     visible: {
       type: Boolean,
diff --git a/src/views/assets/Asset/AssetList/index.vue b/src/views/assets/Asset/AssetList/index.vue
index ee3dbb001..01ef6ad68 100644
--- a/src/views/assets/Asset/AssetList/index.vue
+++ b/src/views/assets/Asset/AssetList/index.vue
@@ -57,10 +57,10 @@ export default {
             component: () => import('@/views/assets/Asset/AssetList/WebList.vue')
           },
           {
-            icon: 'fa-comment',
-            name: 'ad',
+            icon: 'fa-vcard-o',
+            name: 'ds',
             hidden: true,
-            component: () => import('@/views/assets/Asset/AssetList/ADList.vue')
+            component: () => import('@/views/assets/Asset/AssetList/DSList.vue')
           },
           {
             icon: 'fa-comment',
diff --git a/src/views/assets/Platform/PlatformCreateUpdate.vue b/src/views/assets/Platform/PlatformCreateUpdate.vue
index 008b3363c..88e3ff967 100644
--- a/src/views/assets/Platform/PlatformCreateUpdate.vue
+++ b/src/views/assets/Platform/PlatformCreateUpdate.vue
@@ -50,7 +50,7 @@ export default {
         ]],
         [this.$t('Config'), [
           'protocols', 'su_enabled', 'su_method',
-          'domain_enabled', 'ad_enabled', 'ad',
+          'domain_enabled', 'ds_enabled', 'ds',
           'charset'
         ]],
         [this.$t('Automations'), ['automation']],
@@ -155,16 +155,20 @@ export default {
       })
       this.fieldsMeta.protocols.el.choices = protocols
 
-      const fieldsCheck = ['domain_enabled', 'su_enabled', 'ad_enabled']
+      const fieldsCheck = ['domain_enabled', 'su_enabled']
       for (const field of fieldsCheck) {
         const disabled = constraints[field] === false
         this.initial[field] = !disabled
         _.set(this.fieldsMeta, `${field}.el.disabled`, disabled)
       }
 
-      if (constraints['charset_enabled'] === false) {
-        this.fieldsMeta.charset.hidden = () => true
+      const fieldsHidden = ['charset_enabled', 'ds_enabled']
+      for (const field of fieldsHidden) {
+        if (constraints[field] === false) {
+          this.fieldsMeta[field].hidden = () => true
+        }
       }
+
       await setAutomations(this)
       await this.updateSuMethods(constraints)
     }
diff --git a/src/views/assets/Platform/PlatformList.vue b/src/views/assets/Platform/PlatformList.vue
index 7c68de1cd..3796816a8 100644
--- a/src/views/assets/Platform/PlatformList.vue
+++ b/src/views/assets/Platform/PlatformList.vue
@@ -115,7 +115,11 @@ export default {
               canUpdate: ({ row }) => !row.internal && vm.$hasPerm('assets.change_platform'),
               canDelete: ({ row }) => !row.internal && vm.$hasPerm('assets.delete_platform'),
               onUpdate({ row, col }) {
-                vm.$refs.genericListTable.onUpdate({ row, col, query: { type: row.type.value, category: row.category.value }})
+                vm.$refs.genericListTable.onUpdate({
+                  row,
+                  col,
+                  query: { type: row.type.value, category: row.category.value }
+                })
               }
             }
           }
@@ -169,14 +173,10 @@ export default {
       const types = this.$store.state.assets.assetCategoriesDropdown.filter(item => {
         return item.category === this.tab.activeMenu
       }).map(item => {
-        if (!item.group) {
-          return item
-        } else {
-          return {
-            ...item,
-            group: item.group + this.$t('WordSep') + this.$t('Type')
-          }
+        if (item.group && !item.group.includes(this.$t('Type'))) {
+          item.group += this.$t('WordSep') + this.$t('Type')
         }
+        return item
       })
       this.headerActions.moreCreates.dropdown = types
     },
@@ -188,7 +188,7 @@ export default {
         cloud: 'fa-cloud',
         web: 'fa-globe',
         gpt: 'fa-comment',
-        ad: 'fa-comment',
+        ds: 'fa-id-card-o',
         custom: 'fa-cube'
       }
       const state = await this.$store.dispatch('assets/getAssetCategories')
diff --git a/src/views/assets/Platform/const.js b/src/views/assets/Platform/const.js
index b1f0250b8..2d65b8a40 100644
--- a/src/views/assets/Platform/const.js
+++ b/src/views/assets/Platform/const.js
@@ -81,18 +81,18 @@ export const platformFieldsMeta = (vm) => {
         disabled: false
       }
     },
-    ad_enabled: {
+    ds_enabled: {
       el: {
         disabled: false
       }
     },
-    ad: {
+    ds: {
       el: {
         multiple: false,
         url: '/api/v1/assets/directories/',
         disabled: false
       },
-      hidden: (formValue) => !formValue['ad_enabled']
+      hidden: (formValue) => !formValue['ds_enabled']
     },
     protocols: {
       label: i18n.t('SupportedProtocol'),
diff --git a/src/views/assets/const.js b/src/views/assets/const.js
index d4274bef1..a3c45eb44 100644
--- a/src/views/assets/const.js
+++ b/src/views/assets/const.js
@@ -48,7 +48,9 @@ function updatePlatformProtocols(vm, platformType, updateForm, platformChanged =
   }), 100)
 }
 
-export const assetFieldsMeta = (vm, platformType) => {
+export const assetFieldsMeta = (vm, category, type) => {
+  const platformCategory = category || vm.$route.query.category
+  const platformType = type || vm.$route.query.type
   const platformProtocols = []
   const secretTypes = []
   const asset = { address: 'https://jumpserver:330' }
@@ -92,7 +94,7 @@ export const assetFieldsMeta = (vm, platformType) => {
       el: {
         multiple: false,
         ajax: {
-          url: `/api/v1/assets/platforms/?type=${platformType}`,
+          url: `/api/v1/assets/platforms/?category=${platformCategory}&type=${platformType}`,
           transformOption: (item) => {
             return { label: item.name, value: item.id }
           }

From 08c26a461e497afbaf4ed87d4a3b09e731eb53e0 Mon Sep 17 00:00:00 2001
From: ibuler <ibuler@qq.com>
Date: Tue, 8 Apr 2025 14:03:36 +0800
Subject: [PATCH 3/5] perf: connect ad account

---
 src/assets/img/icons/general.png              | Bin 2499 -> 2884 bytes
 src/assets/img/icons/other.png                | Bin 2064 -> 2741 bytes
 src/assets/img/icons/windows_ad.png           | Bin 0 -> 2376 bytes
 .../Apps/AccountListTable/AccountList.vue     |  26 +++++++++++-------
 .../AccountConnectFormatter.vue               |   2 +-
 .../TableFormatters/PlatformFormatter.vue     |   7 ++++-
 .../assets/Asset/AssetDetail/Account.vue      |   1 +
 src/views/assets/Asset/AssetList/index.vue    |   2 +-
 8 files changed, 25 insertions(+), 13 deletions(-)
 create mode 100644 src/assets/img/icons/windows_ad.png

diff --git a/src/assets/img/icons/general.png b/src/assets/img/icons/general.png
index 3760e8075374f99ede513573020dac492b40d945..58787c23280fda44de0f03151b5313c29168c243 100644
GIT binary patch
literal 2884
zcmbW(do)yQ8vyVzZD%gTG+0NP*v7bw`?W@e85w3$WaJ)lcU%r~Dn)LChFqp8D!G)D
zLKBjN2s0cB-Q*U{xRsL#A=kd$cm6r6^?iSQ-yeIwYrXHYpZ9szUhi+csfTT>wxZ?G
zFc@qroo2y+#y@{N$X}rMJ!ftKG>AkptjuBDZrl_MhTf!GP>w`-e9qs>xn_w;p6kfV
zd%o1+U8orm_O$cB`HzLWk#7_n?h+h^pT-P_jfi6hb)*mgSV0$vg@CYxP&hJaCjRLb
zXjqDe!EYcywo*KEy!JwG5)A$uBQWKvQat(iyFY`>VF`vr!`A29$e{7>zDA1i<Xq`o
zuSq1x#_g`k#gM^6=q}wz07J{%;c^I?RY~^0Vhl$T+B3>kfNqf8q}{gqfj_gnbW*re
zP4E)#lC}`q*j^?RObN!X7=6#$SazXqnT?k<6*D;iI;u(W9YlrA>tm;0oU#>&=(VjB
zZs9JJTjSbRkqOSPH8bZluUok<SG1VOvQcB%szr-0&Mn9j*%I8!-jY_a<&I>}VWWbU
zGndGF9=F+}Pes~A*}i7ef4fB9GzmHsIsZwVC^y<)zWV+zgo+Z-60%eoJ6|+hjkx%7
zFD5!Su)*(~E65saG@LKGr!6uYJ;La76iXVIUU+QnTD^j0`3$N0a_>naMWaKetBSfV
zd+85OYDI=-lj}^q)5}j}Yq$7T+j{6L-UZB?m&8=A^-kJ$)W^uy+Ab|10gm!AZ(nZ`
zZ$7Ar#X->>Dr`K%`gOX#bt%CI?zjB?iT^hAP=N6#pHMr$Eoz0wHof0z#6`aG%-o>y
zEq5kNM|$9&NxgCbyfY%-o>G51amNNg4>@TD+$!9)wq_*#IA*};(jQXm`f#FTvAG`G
z&HX?D6--Oji`SAV{r2(tJ16wv5?NX)Fh>Be0XbbHs;45`c?CmDcT$#1DF2v!uY!?E
z)`p{=s;A&F04GS1ui{>LoHiT-q7pzhxxV2X9%EKB|I9+~VxIX_tp6=qj<a&ijdA<0
zGH_IHh&ia|ksS!MU^RNpG%MHpoxN9F==cZ>%Ce6hj=}MI)gDnuEQK8sqXEjPJPJY9
zUA37Kj&Zsv0Eq{~=p<T!ySfyD?q#hk1WB~gh<@g`N+B5Q$f4mZ5?`Lc;s8X15jvVJ
zt+E834p}*fBlKwJrZNTK%YxJA10+Y=+vIsY2e7G3nr@v<A~esn^pfPX@Z<?uT7{|1
z@d2tq1O`VKG?=C&^7@%Tf<tQrTb&YTDv`+iR_`a%VXtYn3-@yhA<+>nWm`GlKY#T{
zg@#gyW=fGst9?1Bw**p-^@ih$54<SMg0|cI9g0npk>IrC*tSsSr;DzVkP_I>+o3RT
z5)I>QghSlLe*G{8l2}b{{mf!#AxI2aA^f8aFG(yR<>$hZO=$lmsm>VK9O1cZy?u7?
zcaMf>zL-7Vt@hw55%8`qwa)6q=#1Pk2h(m@MaW*J>wHgbo=5o~c^=eQK<o*8ZK#M*
z_*A%1X!eB~W}(Q&HilQF?aq9X<sp1~vGmyUpf%Bp&jU$IrXP*r3AJ|lN{1P`z^L|Z
zZ)1%}%&x!IPS~S?_K^=-E<5Qq`tLQUL)y6vT&F{n?r~VyE%Nk9;@}6WeWVAs#ebvV
z(I5J9cBHhW%D`8N!(X>>&+1@)v$eKuV|?PeD)E~c!4=W{Q&vb`cd%#4x0<G`Gb=2+
zn6PHy;A=x)eL0t>-FUm$Nnt*9&w@NY^MXW`cF?01dO=gPLxfac#=}DvDiUkUY@cH0
zA5t9#M}5kOdysxtg3E8nE3+qZ9c%9|{5q4Owkf!%ZP53^Mbr59MDXJMwza-#+18_D
zSK*KEE!MF*OiBB?c*oimMX{OP@~9!RlUD+{;hNr2a<eygj-#nUdBrd1ASSniUw&w5
z0>+a+iXg|4g8u_W4DF)1!E>Alz!|f2l!7CR={L6fXTsne5lUq!5#Uwv3vmN0!Bx16
z$4F(I=ai_tQmBr#`&=@yepzE<F#U<HpVeZP#KarDF9C6;^*1ImYMf$3zuu+3yVE%5
z!K*lEIb~@~FFJHUCASrbyu|#X1aCj-e6BG5Y^p-2vwx-2)#iPnu{5E4ZA$9RPQAQE
z8e&Qy=axZg2roQZ%M8YKM?-p^_i^Vz|6oUQq5P8<wpsYh<t3BR_Hn^Rx7%h}j*>sg
z|3TJ5eM4Z`Yo*LJ>gDQ^TdLO-n%t+1*X*vj7=4&%yqZ3vp>`tFPk5!PW<o`4FlKvH
z*h0?Z8V2~>KWl^LXqY$Xx6T>K$@AUp>a*#2?=Dk^V$>mLDm_Iyl8UiN(IK5H3^3np
zp<i_)4uH>b_SoeGnR^@8dme$-)nyF^W1EjWkc%65+rD}P?q#_kbI5B=GvI8wR%r-q
zLaKtY>=Yb(j%UFz#;^)KPMhyJPky##&On3en9uN9OPeS!no(wF`tli7f2+vaQi}%?
z)YMkHBhnhp!A5T_J0zj444XlA{QAt%`K|@{YXWdgX8$%Moc<6K{ewYXC$>{^2p_?g
zy??+EBBXc~ftd4YrL30|E|6Hj%})gA_>Tr%8Uk^=tgVQ-2ONu05Sz}uD1ZPcvkPZS
z-+uJ(!=IhXEUvi4frupZlBOx^{oxoh4LU&r-~4e83QAln2N8glie~StiBXZ@1pL~?
zqlB{qdm8u;pbdQ_f9A9TGSpj=)1K4A`$-`PzNe)^5-t0>nF7Sv`fyd+I5Foxi2Oxb
zBRRFi`6p!|-q^CyY_KATB*9Sr*wYT_9rp-|<)fi3J5`0?>60%HLQJPFi9=y}I0CX<
zE|Yl#wYmVo%X_aiO<|Nneb>Zb|ItPT0)=h;xv&;_!vB)gqm2@K@by=NWI;b6dide-
ze%X70+56|7g)YjgFVA)vAyy{0j<BM)CrBF~RT)-EoaNU)Tn`$P4ioB6m0(yeOSxS-
z`x~Neq7&5KPV&8usytbD%Ij3UVn+=5uKz}Ccc~zQ93y&IGeDKvJbiK->wcj62!vx;
zvRfz6QeNY!+mW8)Gs`Sc!mxJq8pD-EubpeLJlk}8Vr!kdx!;QBgC()sz8OAdlUmo(
z)dpjIH1NKnZo}f*RqoqeJUeZY7Q-BalSnz|=fcP3>n}ZW1vMYei0w+8J1&#ss&Lwx
ze`ADsxVrhlK#Eo6n==Td!t`-f-@A(?uJZ%a&Hl&>2VY}GZUR+rs)B`tELo;4qOPi@
z=W@!~ffLo_^0s_*$-1uYoh&n9ew1WHhS4`A;1&N+fl$s3f9Avm@e>&VC>}T1)_q!G
zZzFee?_M`kjj+?F`2M<kyH~Zv8qV)LUd#*`{tJI)^st1rS($7DOz>G*d9#!*s7f*y
z$N}8~un*+B*AU{#t~vRXXcUOiA&$CY$)Hwe_w^?LhILswLJI}5bNm8cc)*c7>$KV&
z=*rkxWe}(%0*oqCk6J?x`VDNaz6^toI-aWxIaoC<K4=X~h@x-n<3M*(my^GIR*ok(
e6tjZq0<(}9`z2o^<E_xO7)GbsSa8jcCH@QKa2NCd

literal 2499
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
Q?tEn!rrBxw6B7^QKl_9{R{#J2

diff --git a/src/assets/img/icons/other.png b/src/assets/img/icons/other.png
index 85a4db8fbb110b08703fa150dd9ccac6416dfbec..a25cb939eda287d5fbd5d8c980ac55c260f0d89e 100644
GIT binary patch
literal 2741
zcmcguYg7~077h~37$TV<ASi?huM$C_R4qax3<)Jsv_L^3i6F05tf&QTLZlFeAOT7l
zc|%2c#76-UP<hlK2v{Q^Lc}WCrhpBK3WSG6MYPGi?fre%z3bj}*6eR)uXDb$*SGfm
z)@1nY^jV2tgU4VnE7>e&fClB-2UxC&7dUmNG@uh7;Nyj<>o=dkVDN|7OgK;wI$7o)
z_sA90E6KCnvU^MuhU@%Hti7AW3nDX}@{l0z;wWWB+b63`(;C|k$XFuml$|>5fEGUx
zL45{>0u+Nd(^(j}Sl4Et5aYn&9yEVxwE~i7iN<4_Y(-FP>sLcrHYioz9Dm%yAYXNQ
z?%3<LSnlgXXI9hJ?DNJ4U|zVqa9(r#@({&4cTef*yliu_j%OwI_0b=zu4Ni6L?pW}
z?KNSKG{jP9LOp7Lk*RFM3b81LU_`b|eLgm54e)<hywsw<t;$@@8W~(CPtFFzChLDr
z{PpBWh3)5a^+ea{OF8qm@=ap7K`~?P%@*q{gWiw@`@R~c@nBI!N=w~3B<dR*LQWL9
z4suj&MdQTZUlJM%&a;=eAIzcp!y#8rxLQU#O?KYn(8cIfzMK{aiv|rMJzU~y^m0C!
z@kHO1-}^|;Uq31ati*xT6N2-I+H5gTBq>TsHyuiC^n>>;Urb?cey$c65~KA-XVOef
zRgR=@0NN=zgCt&>T9pD6A?aW)C!j5yA}NmYE4e&<fu|26dr!XCSs2YT$b?8ME8@yp
zn#=fSm2JslNZq2|@ub3+C!&!gBbzlmy0F+RAt*QLrs45a2(a?a`_L9}v0FG6yyMAr
z{Fu(CI4eTBvDlbdY!|r=jPjkTh2m97{EdLi>hnj)2O4N26Q1JH`Wq2hwtpSvH*w-s
z{U-Y4=@s=iW<Ou%ASnv|`luTbI|H88B^_rFy@-F`!WW{gap!O;I6AA>e6L;%d;N6r
z5%N4lmK}jQ4?l&U!r~a0vsn12-**ySf~V#obvZ7kM7I`N|AWdRuRoRComynTG&P_D
zbcyqhpYkDj@h=nbcuyG_t6^CmFEW<eUXevA5(ZKQYDEgrRe`I(k<Q*KHpH6cxzpvm
z*1M@4sbp!iK|OL=TFCcxBML!cs2dpWIuFf5og2+QL!d}ag(VJrAt)F~z1*j>3oRU)
z{f_+vCXQX-t`&zYCjU#Ed5t(biOYyWv~WuYUXdH3{BVNy6s8HZA`PStqyl%Nd{-df
z-YO$U)oDr{fr$zi90v{w=ykU%!_g%;Cku(QxeyeB;icbFVe~^yg?$C?bU?!rGOn4D
z?fVmMO6SUPGb3mIYcT}}iH$7fL|3Bwp3yI!1X(q4${4PFqe0|t#M#(KP0P}Pla~cS
zowg$su}xuM0;FxQ;f2^oN&Hj#u$!*TP1eI8Nl#vE-jaQ6`<JKLx3B)OVbp&+YcZj~
z=gLT;#UE`UBRSPQJ^oW1l9OQmoc&)NAGmP!Sw=rSe0cxN{!cx}D)61xnk~AMq*$hm
zK<h3i=d(3~(sPva^P>uubKl*rF2ajy-uNisbxCQ^eS3GC;q4zwhuV+25L?xWNrhiq
zN)nrAKW)3-MZ7&WitpUjs~d4$y>qF#^g<!|<n1bNuqnh1{W<ZagDkl5p2F}_!UG&H
zyNdSNJY((s-XQkr2Hd%2Ig2Tq=bn<J1f0EPMWAIEVDk9Q5j8MA=pf%}-XZgdG=Ax%
zYBRWQE%C{R7WW6dd;fCYQ(5!YVfx)jc+5MYrPqssu|HldH3$VqY}Qiclg^noSMBu_
z{-NH-XG?cTLawRL+s@y*^O0VRsX;p0!P6KWk7jy1N`q0Jrv3lF?<jm@^6qD>_6MVP
zO2S?@4xM-MEjWZHPw4NnF0iLrzJse+a3*+_aB;ri(VkLeyZM=&MoGlN(!jR5H^;D*
zSe=JW!|nOC{nK~xjRy%+d0h&<(crKoUpm(ayPFAm{)#K0u4r|Ffc9qrsYX}Q+ODlZ
z_6<7(QMryu)Dpv`V>^sRN!9HIfC7A2#;8vG#oava)#1uyV=D5BFl9m70B~zIyG~_4
z9Sj1c+1J1DNyB$O8#FkJ?{wXSn1%;!tKki~uZ^2$WIWtYVR>XN5dg!Y=!KGM=WYi`
zVw&%_E4s1{@+2vy_VnM_-r+^qX0N|WJkDcr?fnn2HcogI>)28dwJRHwF-Ns}1MWgy
z$rNOu{H6|!wm4e%iy;zq`Kc;Av&F<gqIFhP#fuDUk^OzrZ+8b8m7%rxsHaySk69Y}
z*=<005e#&79stuufHBpT#7&3*Aa(bC`Fg*`?5re3_h;_`V-zOnbWv49>Sjow(9qss
zYe_l?%VdQYKLLppM3AsPEE^(wVP|GTuC0M-;{TM0H|y5(n<(K`ItpT88CF@7GkXkQ
z9k!PN9un3gkUZV2>tnpr2?)@<r+Vwbnq8pu$7w09HB++Qgj>sv)MU1{Wi@K7{Dw)E
zHbvLjI)0%)Eg8>&|GBF9E49`nL~*5B9soJ8md-l*F-)9gGi#y0m-0<RY+~k(bDV#K
zdyC7tj!m2c4eLtPX+Ok!(z1@0P!BXzXZ1GVUm5j2&iG%7@^7nq$1v)3Iil=%G(q!l
zzhgYJbh4jfKCVVR%H#7d2KRd!!=INQHq%A=ZbU97y_t(9zPCH&9J&jn*fiKM(YOoB
zwq{plR}q!_Fa!VswzJ+R6hoeajP~H&X_U}qCJ}yKy9+XeKDwFc*Jo@h-`f|7ytS<x
zjtrb%V8G}*Le(A64&c{q-RK|JQIXR%Iaq-V)HZkdH#hbeKZu}GV>XQ<_4gD=dtD}v
z&Vk>)wbYJ^h~_yMhqf0bUvSgf+?5AT9yz~+Onwoab>>M}jqfTcuSMf?RhyC3O9`7^
zea8c(*0S#zH7AABhgq|vUEPmua?WB9M;rOpKxRVnPu21hOI~P;xTa4Sj6ScJ%K0jp
zPDlCq?H2xw1`h$JbVivDetEwV=f6tv{M0)6WWCnqyi2QOnFQ5KN6Nu?hmR|*Ra@dh
z-)>n@@Na^x$hV1Ayzj9AhMs6XXUl}Y2CLBDzmeLAAZkOLqDq54lv|4|HU<ueP)U6>
p_?=ho!iiiABJJ*B=j3+0Z#(|{iL0vVrRF~r!}i|Etn=E7{0R)5%c=kX

literal 2064
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

diff --git a/src/assets/img/icons/windows_ad.png b/src/assets/img/icons/windows_ad.png
new file mode 100644
index 0000000000000000000000000000000000000000..d436f43afbfa7ba17902efe5acf0618ed630360f
GIT binary patch
literal 2376
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
Rjuz-P5CVeyulSL3{{(<jE|LHM

literal 0
HcmV?d00001

diff --git a/src/components/Apps/AccountListTable/AccountList.vue b/src/components/Apps/AccountListTable/AccountList.vue
index 4b8863030..dc9b2b4b5 100644
--- a/src/components/Apps/AccountListTable/AccountList.vue
+++ b/src/components/Apps/AccountListTable/AccountList.vue
@@ -147,11 +147,6 @@ export default {
     showActions: {
       type: Boolean,
       default: true
-    },
-    // target for ad connect btn, if not has, ad account should be select one
-    target: {
-      type: Object,
-      default: null
     }
   },
   data() {
@@ -194,6 +189,14 @@ export default {
                 name: 'AccountDetail',
                 params: { id: row.id }
               }),
+              getTitle: ({ row }) => {
+                let title = row.name
+                if (row.ds_id && this.asset && this.asset.id !== row.asset.id) {
+                  const dsID = row.ds_id.split('-')[0]
+                  title = `${row.name}@${dsID}`
+                }
+                return title
+              },
               getDrawerTitle({ row }) {
                 return `${row.username}@${row.asset.name}`
               }
@@ -213,12 +216,14 @@ export default {
             width: '80px',
             formatter: AccountConnectFormatter,
             formatterArgs: {
-              can: () => this.currentUserIsSuperAdmin,
-              connectUrlTemplate: (row) => {
-
+              can: ({ row }) => {
+                return this.currentUserIsSuperAdmin
               }
             }
           },
+          ds_domain: {
+            width: '100px'
+          },
           platform: {
             label: this.$t('Platform'),
             width: '150px',
@@ -228,6 +233,7 @@ export default {
             }
           },
           asset: {
+            minWidth: '100px',
             formatter: function(row) {
               return row.asset.name
             }
@@ -235,8 +241,8 @@ export default {
           username: {
             minWidth: '60px',
             formatter: function(row) {
-              if (row.ad_domain) {
-                return `${row.username}@${row.ad_domain}`
+              if (row.ds_domain) {
+                return `${row.username}@${row.ds_domain}`
               } else {
                 return row.username
               }
diff --git a/src/components/Table/TableFormatters/AccountConnectFormatter.vue b/src/components/Table/TableFormatters/AccountConnectFormatter.vue
index 2fde206d3..411310780 100644
--- a/src/components/Table/TableFormatters/AccountConnectFormatter.vue
+++ b/src/components/Table/TableFormatters/AccountConnectFormatter.vue
@@ -80,7 +80,7 @@ export default {
       return this.formatterArgs.buttonIcon
     },
     hasPerm() {
-      return this.formatterArgs.can(this.row, this.cellValue)
+      return this.formatterArgs.can({ row: this.row, cellValue: this.cellValue })
     }
   },
   methods: {
diff --git a/src/components/Table/TableFormatters/PlatformFormatter.vue b/src/components/Table/TableFormatters/PlatformFormatter.vue
index 26a5214b5..12f1c78f5 100644
--- a/src/components/Table/TableFormatters/PlatformFormatter.vue
+++ b/src/components/Table/TableFormatters/PlatformFormatter.vue
@@ -45,7 +45,7 @@ export default {
 }
 </script>
 
-<style scoped lang="scss">
+<style lang="scss" scoped>
 .platform-td {
   display: flex;
   flex-wrap: nowrap;
@@ -55,6 +55,7 @@ export default {
   .icon-zone {
     width: 1.5em;
     height: 1.5em;
+    flex-shrink: 0;
 
     .asset-icon {
       height: 100%;
@@ -66,6 +67,10 @@ export default {
 
   .platform-name {
     flex: 1;
+    min-width: 0;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis
   }
 }
 
diff --git a/src/views/assets/Asset/AssetDetail/Account.vue b/src/views/assets/Asset/AssetDetail/Account.vue
index 82260865e..eee5d0896 100644
--- a/src/views/assets/Asset/AssetDetail/Account.vue
+++ b/src/views/assets/Asset/AssetDetail/Account.vue
@@ -10,6 +10,7 @@
           :has-import="false"
           :has-left-actions="true"
           :header-extra-actions="headerExtraActions"
+          :target="object"
           :url="iUrl"
           v-bind="$attrs"
         />
diff --git a/src/views/assets/Asset/AssetList/index.vue b/src/views/assets/Asset/AssetList/index.vue
index 01ef6ad68..3f0f51785 100644
--- a/src/views/assets/Asset/AssetList/index.vue
+++ b/src/views/assets/Asset/AssetList/index.vue
@@ -57,7 +57,7 @@ export default {
             component: () => import('@/views/assets/Asset/AssetList/WebList.vue')
           },
           {
-            icon: 'fa-vcard-o',
+            icon: 'fa-id-card-o',
             name: 'ds',
             hidden: true,
             component: () => import('@/views/assets/Asset/AssetList/DSList.vue')

From 4e72e955edd69ac67678e2b5bfc49fcdf509aa2d Mon Sep 17 00:00:00 2001
From: ibuler <ibuler@qq.com>
Date: Tue, 8 Apr 2025 18:04:22 +0800
Subject: [PATCH 4/5] perf: ds var

---
 .../Apps/AccountListTable/AccountList.vue     | 19 +++++++++++++------
 .../AssetCreateUpdate/ADCreateUpdate.vue      |  2 +-
 2 files changed, 14 insertions(+), 7 deletions(-)

diff --git a/src/components/Apps/AccountListTable/AccountList.vue b/src/components/Apps/AccountListTable/AccountList.vue
index dc9b2b4b5..be0cb463d 100644
--- a/src/components/Apps/AccountListTable/AccountList.vue
+++ b/src/components/Apps/AccountListTable/AccountList.vue
@@ -191,8 +191,8 @@ export default {
               }),
               getTitle: ({ row }) => {
                 let title = row.name
-                if (row.ds_id && this.asset && this.asset.id !== row.asset.id) {
-                  const dsID = row.ds_id.split('-')[0]
+                if (row.ds && this.asset && this.asset.id !== row.asset.id) {
+                  const dsID = row.ds.id.split('-')[0]
                   title = `${row.name}@${dsID}`
                 }
                 return title
@@ -221,8 +221,15 @@ export default {
               }
             }
           },
-          ds_domain: {
-            width: '100px'
+          ds: {
+            width: '100px',
+            formatter: (row) => {
+              if (row.ds && row.ds['domain_name']) {
+                return row.ds['domain_name']
+              } else {
+                return ''
+              }
+            }
           },
           platform: {
             label: this.$t('Platform'),
@@ -241,8 +248,8 @@ export default {
           username: {
             minWidth: '60px',
             formatter: function(row) {
-              if (row.ds_domain) {
-                return `${row.username}@${row.ds_domain}`
+              if (row.ds && row.ds['domain_name']) {
+                return `${row.username}@${row.ds['domain_name']}`
               } else {
                 return row.username
               }
diff --git a/src/views/assets/Asset/AssetCreateUpdate/ADCreateUpdate.vue b/src/views/assets/Asset/AssetCreateUpdate/ADCreateUpdate.vue
index 5a6c0556c..0f0f65724 100644
--- a/src/views/assets/Asset/AssetCreateUpdate/ADCreateUpdate.vue
+++ b/src/views/assets/Asset/AssetCreateUpdate/ADCreateUpdate.vue
@@ -12,7 +12,7 @@ export default {
     return {
       url: '/api/v1/assets/directories/',
       addFields: [
-        [this.$t('DomainName'), ['domain_name'], 1]
+        [this.$t('IdentityDomain'), ['domain_name'], 1]
       ]
     }
   }

From 0777ecd818c47a5c529f95b2bae4d78312cdbdc3 Mon Sep 17 00:00:00 2001
From: ibuler <ibuler@qq.com>
Date: Tue, 8 Apr 2025 19:27:01 +0800
Subject: [PATCH 5/5] perf: rename to dc

---
 .../{ADCreateUpdate.vue => DSCreateUpdate.vue}                  | 2 +-
 src/views/assets/Asset/AssetDetail/Account.vue                  | 1 -
 src/views/assets/Asset/AssetList/components/BaseList.vue        | 2 +-
 3 files changed, 2 insertions(+), 3 deletions(-)
 rename src/views/assets/Asset/AssetCreateUpdate/{ADCreateUpdate.vue => DSCreateUpdate.vue} (93%)

diff --git a/src/views/assets/Asset/AssetCreateUpdate/ADCreateUpdate.vue b/src/views/assets/Asset/AssetCreateUpdate/DSCreateUpdate.vue
similarity index 93%
rename from src/views/assets/Asset/AssetCreateUpdate/ADCreateUpdate.vue
rename to src/views/assets/Asset/AssetCreateUpdate/DSCreateUpdate.vue
index 0f0f65724..8af6ba9e8 100644
--- a/src/views/assets/Asset/AssetCreateUpdate/ADCreateUpdate.vue
+++ b/src/views/assets/Asset/AssetCreateUpdate/DSCreateUpdate.vue
@@ -6,7 +6,7 @@
 import BaseAssetCreateUpdate from './BaseAssetCreateUpdate'
 
 export default {
-  name: 'ADCreateUpdate',
+  name: 'DSCreateUpdate',
   components: { BaseAssetCreateUpdate },
   data() {
     return {
diff --git a/src/views/assets/Asset/AssetDetail/Account.vue b/src/views/assets/Asset/AssetDetail/Account.vue
index eee5d0896..82260865e 100644
--- a/src/views/assets/Asset/AssetDetail/Account.vue
+++ b/src/views/assets/Asset/AssetDetail/Account.vue
@@ -10,7 +10,6 @@
           :has-import="false"
           :has-left-actions="true"
           :header-extra-actions="headerExtraActions"
-          :target="object"
           :url="iUrl"
           v-bind="$attrs"
         />
diff --git a/src/views/assets/Asset/AssetList/components/BaseList.vue b/src/views/assets/Asset/AssetList/components/BaseList.vue
index d26a4b8e5..8266ce8bd 100644
--- a/src/views/assets/Asset/AssetList/components/BaseList.vue
+++ b/src/views/assets/Asset/AssetList/components/BaseList.vue
@@ -144,7 +144,7 @@ export default {
         'cloud': () => import('@/views/assets/Asset/AssetCreateUpdate/CloudCreateUpdate.vue'),
         'device': () => import('@/views/assets/Asset/AssetCreateUpdate/DeviceCreateUpdate.vue'),
         'database': () => import('@/views/assets/Asset/AssetCreateUpdate/DatabaseCreateUpdate.vue'),
-        'ds': () => import('@/views/assets/Asset/AssetCreateUpdate/ADCreateUpdate.vue')
+        'ds': () => import('@/views/assets/Asset/AssetCreateUpdate/DSCreateUpdate.vue')
       },
       createProps: {},
       showPlatform: false,