Merge pull request #4931 from jumpserver/pr@dev@feat_ad_domain

feat: ad domain as asset
This commit is contained in:
老广 2025-04-08 19:30:13 +08:00 committed by GitHub
commit a3f17ea4d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 222 additions and 123 deletions

View File

@ -52,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()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -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: () => {

View File

@ -182,13 +182,21 @@ export default {
},
columnsMeta: {
name: {
width: '120px',
minWidth: '60px',
formatterArgs: {
can: () => vm.$hasPerm('accounts.view_account'),
getRoute: ({ row }) => ({
name: 'AccountDetail',
params: { id: row.id }
}),
getTitle: ({ row }) => {
let title = row.name
if (row.ds && 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}`
}
@ -208,15 +216,18 @@ 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
})
can: ({ row }) => {
return this.currentUserIsSuperAdmin
}
}
},
ds: {
width: '100px',
formatter: (row) => {
if (row.ds && row.ds['domain_name']) {
return row.ds['domain_name']
} else {
return ''
}
}
},
@ -229,12 +240,20 @@ export default {
}
},
asset: {
minWidth: '100px',
formatter: function(row) {
return row.asset.name
}
},
username: {
width: '120px'
minWidth: '60px',
formatter: function(row) {
if (row.ds && row.ds['domain_name']) {
return `${row.username}@${row.ds['domain_name']}`
} else {
return row.username
}
}
},
secret_type: {
formatter: function(row) {

View File

@ -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({ row: this.row, cellValue: 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.filter(protocol => protocol.name !== 'sftp')[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.filter(protocol => protocol.name !== 'sftp')
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);

View File

@ -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(() => {

View File

@ -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
}
}

View File

@ -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)

View File

@ -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) {

View File

@ -0,0 +1,23 @@
<template>
<BaseAssetCreateUpdate v-bind="$data" />
</template>
<script>
import BaseAssetCreateUpdate from './BaseAssetCreateUpdate'
export default {
name: 'DSCreateUpdate',
components: { BaseAssetCreateUpdate },
data() {
return {
url: '/api/v1/assets/directories/',
addFields: [
[this.$t('IdentityDomain'), ['domain_name'], 1]
]
}
}
}
</script>
<style>
</style>

View File

@ -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: {

View File

@ -0,0 +1,21 @@
<template>
<BaseList v-bind="tableConfig" />
</template>
<script>
import BaseList from './components/BaseList'
export default {
components: {
BaseList
},
data() {
return {
tableConfig: {
category: 'ds',
url: '/api/v1/assets/directories/'
}
}
}
}
</script>

View File

@ -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'),
'ds': () => import('@/views/assets/Asset/AssetCreateUpdate/DSCreateUpdate.vue')
},
createProps: {},
showPlatform: false,

View File

@ -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,
@ -76,7 +77,7 @@ export default {
platforms: [],
recentPlatformIds: [],
loading: true,
activeType: 'host',
activeType: [],
recentUsedLabel: this.$t('RecentlyUsed'),
typeIconMapper: {
linux: 'fa-linux',
@ -130,9 +131,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: {

View File

@ -56,6 +56,12 @@ export default {
hidden: true,
component: () => import('@/views/assets/Asset/AssetList/WebList.vue')
},
{
icon: 'fa-id-card-o',
name: 'ds',
hidden: true,
component: () => import('@/views/assets/Asset/AssetList/DSList.vue')
},
{
icon: 'fa-comment',
name: 'gpt',

View File

@ -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', 'ds_enabled', 'ds',
'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,15 +155,20 @@ export default {
})
this.fieldsMeta.protocols.el.choices = protocols
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)
}

View File

@ -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 }
})
}
}
}
@ -149,14 +153,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 +170,15 @@ 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 && !item.group.includes(this.$t('Type'))) {
item.group += this.$t('WordSep') + this.$t('Type')
}
return item
})
this.headerActions.moreCreates.dropdown = types
},
async setCategoriesTab() {
const categoryIcon = {
@ -185,6 +188,7 @@ export default {
cloud: 'fa-cloud',
web: 'fa-globe',
gpt: 'fa-comment',
ds: 'fa-id-card-o',
custom: 'fa-cube'
}
const state = await this.$store.dispatch('assets/getAssetCategories')

View File

@ -81,6 +81,19 @@ export const platformFieldsMeta = (vm) => {
disabled: false
}
},
ds_enabled: {
el: {
disabled: false
}
},
ds: {
el: {
multiple: false,
url: '/api/v1/assets/directories/',
disabled: false
},
hidden: (formValue) => !formValue['ds_enabled']
},
protocols: {
label: i18n.t('SupportedProtocol'),
...assetMeta.protocols,

View File

@ -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 }
}