This commit is contained in:
ibuler
2022-04-29 18:29:50 +08:00
parent 0a5bfa5a55
commit 9630d8200c
11 changed files with 589 additions and 79 deletions

View File

@@ -0,0 +1,164 @@
<template>
<div>
<div v-for="(item,index) in items" :key="index" style="display: flex;margin-top: 8px;">
<el-input v-model="item.value" class="input-with-select" v-bind="$attrs">
<el-select slot="prepend" v-model="item.select" @change="handleProtocolChange">
<el-option v-for="p of remainProtocols" :key="p.name" :label="p.name" :value="p.name" />
</el-select>
</el-input>
<div style="display: flex; margin-left: 20px" class="input-button">
<el-button type="danger" icon="el-icon-minus" style="flex-shrink: 0;" size="mini" :disabled="items.length===1" @click="handleDelete(index)" />
<el-button type="primary" icon="el-icon-plus" style="flex-shrink: 0;" size="mini" @click="handleAdd(index)" />
</div>
</div>
</div>
</template>
<script>
export default {
props: {
value: {
type: [Array],
default: () => []
},
title: {
type: String,
default: ''
}
},
data() {
return {
select: '',
items: [
{
value: '',
select: ''
}
],
protocols: [
{
name: 'ssh',
port: 22
},
{
name: 'rdp',
port: 3389
},
{
name: 'telnet',
port: 23
},
{
name: 'vnc',
port: 5901
}
]
}
},
computed: {
values() {
const data = []
this.items.map(i => {
data.push(`${i.select}/${i.value}`)
})
return data
},
itemsMap() {
const mapper = {}
for (const item of this.items) {
mapper[item.select] = item
}
return mapper
},
remainProtocols() {
const remain = []
for (const item of this.protocols) {
if (!this.itemsMap[item.name]) {
remain.push(item)
}
}
return remain
}
},
watch: {
values: {
handler(value) {
this.$emit('input', value)
},
immediate: true,
deep: true
}
},
mounted() {
if (this.value.length !== 0) {
this.items = []
this.value.forEach(v => {
const data = v.split('/')
this.items.push({
value: data[1],
select: data[0]
})
})
}
},
methods: {
onInput(val) {
this.$emit('input', 'my-input: ' + val)
},
handleDelete(index) {
this.items = this.items.filter((value, i) => {
return i !== index
})
},
handleAdd(index) {
this.items.push(
{
value: '',
select: ''
}
)
},
handleProtocolChange(val) {
let port = 22
switch (val) {
case 'rdp':
port = 3389
break
case 'telnet':
port = 23
break
case 'vnc':
port = 5901
break
}
this.itemsMap[val].value = port
}
}
}
</script>
<style lang="less" scoped>
.el-select .el-input {
width: 130px;
}
.input-with-select {
flex-shrink: 1;
width: 80% !important;
}
.input-with-select .el-input-group__prepend {
background-color: #fff;
}
.el-select ::v-deep .el-input__inner {
width: 100px;
}
.input-button {
margin-top: 4px;
}
.input-button ::v-deep .el-button.el-button--mini {
height: 25px;
padding: 5px;
}
</style>

View File

@@ -45,6 +45,66 @@ export default [
}
]
},
{
path: 'hosts',
component: empty,
redirect: '',
hidden: true,
meta: { title: i18n.t('route.HostList'), permissions: ['assets.view_asset'] },
children: [
{
path: '',
name: 'HostList',
component: () => import('@/views/assets/Host/HostList.vue'),
hidden: true,
meta: { title: i18n.t('route.HostList'), activeMenu: '/console/assets/assets' }
},
{
path: 'create',
name: 'HostCreate',
component: () => import('@/views/assets/Host/HostCreateUpdate.vue'),
hidden: true,
meta: { title: i18n.t('route.AssetCreate'), activeMenu: '/console/assets/assets' }
},
{
path: ':id/update',
name: 'HostUpdate',
component: () => import('@/views/assets/Host/HostCreateUpdate.vue'),
hidden: true,
meta: { title: i18n.t('route.AssetUpdate'), activeMenu: '/console/assets/assets' }
}
]
},
{
path: 'databases',
component: empty,
redirect: '',
hidden: true,
meta: { title: i18n.t('route.Databases'), permissions: ['assets.view_asset'] },
children: [
{
path: '',
name: 'DatabaseList',
component: () => import('@/views/assets/Database/HostList.vue'),
hidden: true,
meta: { title: i18n.t('route.HostList'), activeMenu: '/console/assets/assets' }
},
{
path: 'create',
name: 'DatabaseCreate',
component: () => import('@/views/assets/Database/DatabaseCreateUpdate.vue'),
hidden: true,
meta: { title: i18n.t('route.AssetCreate'), activeMenu: '/console/assets/assets' }
},
{
path: ':id/update',
name: 'DatabaseUpdate',
component: () => import('@/views/assets/Database/DatabaseCreateUpdate.vue'),
hidden: true,
meta: { title: i18n.t('route.AssetUpdate'), activeMenu: '/console/assets/assets' }
}
]
},
{
path: 'domains',
component: empty,

View File

@@ -4,8 +4,7 @@
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import Protocols from './components/Protocols'
import rules from '@/components/DataForm/rules'
import { assetFieldsMeta } from '@/views/assets/const'
export default {
name: 'AssetCreateUpdate',
@@ -32,71 +31,7 @@ export default {
[this.$t('assets.Label'), ['labels']],
[this.$t('common.Other'), ['is_active', 'comment']]
],
fieldsMeta: {
ip: {
label: this.$t('assets.ipDomain')
},
protocols: {
component: Protocols
},
platform: {
el: {
multiple: false,
ajax: {
url: '/api/v1/assets/platforms/',
transformOption: (item) => {
return { label: `${item.name}`, value: item.name }
}
}
}
},
domain: {
el: {
multiple: false,
clearable: true,
ajax: {
url: '/api/v1/assets/domains/'
}
}
},
admin_user: {
el: {
multiple: false,
ajax: {
url: '/api/v1/assets/system-users/?type=admin',
transformOption: (item) => {
const username = item.username || '*'
return { label: item.name + '(' + username + ')', value: item.id }
}
}
}
},
nodes: {
rules: [rules.RequiredChange],
el: {
ajax: {
url: '/api/v1/assets/nodes/',
transformOption: (item) => {
return { label: `${item.full_value}`, value: item.id }
}
},
clearable: true
}
},
labels: {
el: {
ajax: {
url: '/api/v1/assets/labels/',
transformOption: (item) => {
return { label: `${item.name}:${item.value}`, value: item.id }
}
}
}
},
is_active: {
type: 'switch'
}
},
fieldsMeta: assetFieldsMeta,
url: '/api/v1/assets/assets/',
createSuccessNextRoute: { name: 'AssetDetail' },
hasDetailInMsg: false

View File

@@ -23,9 +23,10 @@
:span="6"
>
<el-card
:style="{ borderBottomColor: randomBottomColor(index) }"
:style="{ borderBottomColor: randomBorderColor(index) }"
class="platform-item"
shadow="hover"
@click.native="createAsset(platform)"
>
{{ platform.name }}
</el-card>
@@ -93,13 +94,21 @@ export default {
},
methods: {
onConfirm() {
this.iVisible = false
},
randomBottomColor(i) {
randomBorderColor(i) {
const length = this.bottomColors.length
const colorIndex = i % length
const color = this.bottomColors[colorIndex]
console.log('Color: ', color)
return color
},
createAsset(platform) {
const mapper = {
host: 'HostCreate'
}
const route = mapper[platform.category] || 'HostCreate'
this.$router.push({ name: route, query: { platform: platform.id }})
this.iVisible = false
}
}
}
@@ -114,4 +123,8 @@ export default {
border-bottom: solid 4px;
}
.platform-item:hover {
cursor: pointer;
}
</style>

View File

@@ -0,0 +1,47 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import { assetFieldsMeta } from '@/views/assets/const'
export default {
name: 'DatabaseCreateUpdate',
components: {
GenericCreateUpdatePage
},
data() {
const nodesInitial = []
if (this.$route.query['node']) {
nodesInitial.push(this.$route.query.node)
}
const platformId = this.$route.query['platform'] || 1
return {
initial: {
is_active: true,
platform: parseInt(platformId),
protocols: ['mysql/22'],
nodes: nodesInitial
},
url: '/api/v1/assets/databases/',
createSuccessNextRoute: { name: 'AssetDetail' },
hasDetailInMsg: false,
fields: [
[this.$t('common.Basic'), ['platform', 'hostname', 'ip', 'db_name']],
[this.$t('assets.Protocols'), ['protocols']],
[this.$t('assets.Domain'), ['domain']],
[this.$t('assets.Auth'), ['admin_user']],
[this.$t('assets.Node'), ['nodes']],
[this.$t('assets.Label'), ['labels']],
[this.$t('common.Other'), ['is_active', 'comment']]
],
fieldsMeta: assetFieldsMeta
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,107 @@
<template>
<GenericListPage
:table-config="tableConfig"
:header-actions="headerActions"
:help-message="notice"
/>
</template>
<script>
import { GenericListPage } from '@/layout/components'
import { ActionsFormatter, DetailFormatter, TagsFormatter } from '@/components/TableFormatters'
import { connectivityMeta } from '@/components/AccountListTable/const'
export default {
components: {
GenericListPage
},
data() {
const vm = this
return {
tableConfig: {
url: '/api/v1/assets/hosts/',
columns: [
'hostname', 'ip', 'public_ip', 'admin_user_display',
'protocols', 'category', 'type', 'platform', 'sn',
'is_active', 'connectivity', 'labels_display',
'created_by', 'date_created', 'comment', 'org_name', 'actions'
],
columnsShow: {
min: ['hostname', 'ip', 'actions'],
default: [
'hostname', 'ip', 'platform', 'category', 'type',
'connectivity', 'actions'
]
},
columnsMeta: {
hostname: {
formatter: DetailFormatter,
formatterArgs: {
route: 'AssetDetail'
},
showOverflowTooltip: true,
sortable: true
},
platform: {
sortable: true
},
protocols: {
formatter: function(row) {
return <span> {row.protocols.toString()} </span>
}
},
ip: {
sortable: 'custom',
width: '140px'
},
hardware_info: {
showOverflowTooltip: true
},
cpu_model: {
showOverflowTooltip: true
},
sn: {
showOverflowTooltip: true
},
comment: {
showOverflowTooltip: true
},
connectivity: connectivityMeta,
labels_display: {
formatter: TagsFormatter
},
actions: {
formatter: ActionsFormatter,
formatterArgs: {
performDelete: ({ row, col }) => {
const id = row.id
const url = `/api/v1/assets/assets/${id}/`
return this.$axios.delete(url)
},
extraActions: [
{
name: 'View',
title: this.$t(`common.UpdateAssetDetail`),
type: 'primary',
can: vm.$hasPerm('assets.refresh_assethardwareinfo'),
callback: function({ cellValue, tableData, row }) {
return this.$router.push({ name: 'AssetMoreInformationEdit', params: { id: row.id }})
}
}
]
}
}
}
},
headerActions: {
hasMoreActions: false,
createRoute: 'HostCreate'
}
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,46 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import { assetFieldsMeta } from '@/views/assets/const'
export default {
name: 'HostCreateUpdate',
components: {
GenericCreateUpdatePage
},
data() {
const nodesInitial = []
if (this.$route.query['node']) {
nodesInitial.push(this.$route.query.node)
}
const platformId = this.$route.query['platform'] || 1
return {
initial: {
is_active: true,
platform: parseInt(platformId),
protocols: ['ssh/22'],
nodes: nodesInitial
},
fields: [
[this.$t('common.Basic'), ['hostname', 'ip', 'platform', 'public_ip', 'domain']],
[this.$t('assets.Protocols'), ['protocols']],
[this.$t('assets.Auth'), ['admin_user']],
[this.$t('assets.Node'), ['nodes']],
[this.$t('assets.Label'), ['labels']],
[this.$t('common.Other'), ['is_active', 'comment']]
],
fieldsMeta: assetFieldsMeta,
url: '/api/v1/assets/assets/',
createSuccessNextRoute: { name: 'AssetDetail' },
hasDetailInMsg: false
}
}
}
</script>
<style>
</style>

View File

@@ -1,23 +1,36 @@
<template>
<GenericCreateUpdatePage :fields="fields" :initial="initial" :fields-meta="fieldsMeta" :url="url" />
<div>
<GenericCreateUpdatePage
:url="url"
:fields="fields"
:initial="initial"
:fields-meta="fieldsMeta"
:clean-form-value="cleanFormValue"
:after-get-form-value="afterGetFormValue"
/>
</div>
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import rules from '@/components/DataForm/rules'
export default {
name: 'PlatformCreateUpdate',
components: { GenericCreateUpdatePage },
data() {
return {
loading: true,
initial: {
base: 'Linux',
console: 'true',
security: 'RDP',
comment: '',
charset: 'utf8'
comment: 'Hello world',
charset: 'utf8',
category_type: ['host', 'linux']
},
fields: [
[this.$t('common.Basic'), ['name', 'base', 'charset', 'meta', 'comment']]
[this.$t('common.Basic'), ['name', 'category_type', 'charset', 'meta']],
[this.$t('common.Other'), ['comment']]
],
fieldsMeta: {
meta: {
@@ -57,10 +70,51 @@ export default {
}
},
hidden: form => form.base !== 'Windows'
},
category_type: {
type: 'cascader',
label: this.$t('assets.Type'),
rules: [
rules.Required
],
el: {
multiple: false,
options: []
}
}
},
url: '/api/v1/assets/platforms/'
url: '/api/v1/assets/platforms/',
cleanFormValue: (values) => {
const category_type = values['category_type']
values['category'] = category_type[0]
values['type'] = category_type[1]
// delete values['category_type']
return values
},
afterGetFormValue: (obj) => {
obj['category_type'] = [obj['category'], obj['type']]
return obj
}
}
},
mounted() {
this.$axios.get('/api/v1/assets/platforms/categories/').then(data => {
const options = data.map((item) => {
const children = item.children.map(this.toOption)
const option = this.toOption(item)
option.children = children
return option
})
this.fieldsMeta.category_type.el.options = options
this.loading = false
})
},
methods: {
toOption(choice) {
return {
label: choice['display_name'],
value: choice['value']
}
}
}
}

View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"sourceMap": true
},
"exclude": [
"node_modules"
]
}

View File

@@ -1,5 +1,7 @@
import i18n from '@/i18n/i18n'
import { groupedDropdownToCascader } from '@/utils/common'
import ProtocolSelector from '@/components/FormFields/ProtocolSelector'
import rules from '@/components/DataForm/rules'
export const AssetProtocols = [
{
@@ -31,3 +33,67 @@ export const AssetProtocols = [
export const AssetCascader = groupedDropdownToCascader(AssetProtocols)
export const assetFieldsMeta = {
protocols: {
component: ProtocolSelector
},
platform: {
el: {
multiple: false,
disabled: true,
ajax: {
url: '/api/v1/assets/platforms/',
transformOption: (item) => {
return { label: `${item.name}`, value: item.id }
}
}
}
},
domain: {
el: {
multiple: false,
clearable: true,
ajax: {
url: '/api/v1/assets/domains/'
}
}
},
admin_user: {
el: {
multiple: false,
ajax: {
url: '/api/v1/assets/system-users/?type=admin',
transformOption: (item) => {
const username = item.username || '*'
return { label: item.name + '(' + username + ')', value: item.id }
}
}
}
},
nodes: {
rules: [rules.RequiredChange],
el: {
ajax: {
url: '/api/v1/assets/nodes/',
transformOption: (item) => {
return { label: `${item.full_value}`, value: item.id }
}
},
clearable: true
}
},
labels: {
el: {
ajax: {
url: '/api/v1/assets/labels/',
transformOption: (item) => {
return { label: `${item.name}:${item.value}`, value: item.id }
}
}
}
},
is_active: {
type: 'switch'
}
}

View File

@@ -119,8 +119,16 @@ export default {
apply_applications: [],
apply_system_users: []
})
this.fieldsMeta.meta.fieldsMeta.apply_applications.el.ajax.url = `/api/v1/applications/applications/suggestion/?oid=${vm.org_id}&category=${event[0]}&type=${event[1]}`
this.fieldsMeta.meta.fieldsMeta.apply_system_users.el.ajax.url = event[0] === 'remote_app' ? `/api/v1/assets/system-users/suggestion/?oid=${vm.org_id}&protocol=rdp` : `/api/v1/assets/system-users/suggestion/?oid=${vm.org_id}&protocol=${event[1]}`
const fieldsMeta = this.fieldsMeta.meta.fieldsMeta
const appUrl = `/api/v1/applications/applications/suggestion/?oid=${vm.org_id}&category=${event[0]}&type=${event[1]}`
fieldsMeta.apply_applications.el.ajax.url = appUrl
let protocol = event[1]
if (event[0] === 'remote_app') {
protocol = 'rdp'
}
const sysUserUrl = `/api/v1/assets/system-users/suggestion/?oid=${vm.org_id}&protocol=${protocol}`
fieldsMeta.apply_system_users.el.ajax.url = sysUserUrl
}
}
}