perf: update cloud sync

This commit is contained in:
ibuler
2025-03-07 10:23:21 +08:00
parent 22170c18f8
commit 5a32d2359f
14 changed files with 153 additions and 547 deletions

View File

@@ -27,7 +27,9 @@ export default {
},
beforeSubmit: {
type: Function,
default: (val) => { return true }
default: (val) => {
return true
}
}
},
data() {
@@ -50,14 +52,16 @@ export default {
::v-deep .el-select {
width: 100%;
}
::v-deep .el-form-item__content {
width: 100% !important;
}
::v-deep .form-buttons {
margin: 0 !important;
}
.attr-input {
margin-top: -10px;
}
</style>

View File

@@ -9,7 +9,6 @@ const clouds = {
},
hidden: true,
meta: {
title: i18n.t('BaseCloudSync'),
app: 'xpack',
resource: 'account'
},
@@ -41,32 +40,10 @@ const clouds = {
hidden: true,
redirect: '/console/assets/cloud',
meta: {
title: i18n.t('CloudAccountList'),
title: i18n.t('CloudSync'),
permissions: ['xpack.view_account']
}
},
{
path: 'create',
component: () => import('@/views/assets/Cloud/Account/AccountCreateUpdate'),
name: 'AccountCreate',
hidden: true,
meta: {
title: i18n.t('CloudAccountCreate'),
action: 'create',
permissions: ['xpack.add_account']
}
},
{
path: ':id/update',
component: () => import('@/views/assets/Cloud/Account/AccountCreateUpdate'),
name: 'AccountUpdate',
hidden: true,
meta: {
title: i18n.t('CloudAccountUpdate'),
action: 'update',
permissions: ['xpack.change_account']
}
},
{
path: ':id/',
component: () => import('@/views/assets/Cloud/Account/AccountDetail/index'),

View File

@@ -1,292 +0,0 @@
<template>
<Page>
<el-row :gutter="20">
<el-col :md="20" :sm="24">
<IBox>
<GenericCreateUpdateForm
v-show="activeStep===0"
ref="baseForm"
v-bind="configSettings"
/>
<GenericCreateUpdateForm
v-if="activeStep===1"
ref="strategyForm"
v-bind="strategySettings"
/>
</IBox>
</el-col>
<el-col :md="4" :sm="24">
<IBox style="height: 490px;">
<el-steps
:active="activeStep"
direction="vertical"
space="400px"
>
<el-step :description="description" :title="$tc('Config')" />
<el-step :title="$tc('Strategy')" />
</el-steps>
</IBox>
</el-col>
</el-row>
</Page>
</template>
<script>
import { RequiredChange, specialEmojiCheck } from '@/components/Form/DataForm/rules'
import { ACCOUNT_PROVIDER_ATTRS_MAP, aliyun } from '../const'
import { Checkbox, IBox, UploadKey } from '@/components'
import { encryptPassword } from '@/utils/crypto'
import { GenericCreateUpdateForm, Page } from '@/layout/components'
import SyncInstanceTaskStrategy from './components/SyncInstanceTaskStrategy'
import { setUrlParam } from '@/utils/common'
import { crontab, interval, is_periodic } from '@/components/const'
export default {
components: {
IBox,
Page,
GenericCreateUpdateForm
},
data() {
const vm = this
const accountProvider = this.$route.query.provider || aliyun
const accountProviderAttrs = ACCOUNT_PROVIDER_ATTRS_MAP[accountProvider]
function setFieldAttrs() {
const fieldsObject = {}
const updateNotRequiredFields = [
'access_key_secret', 'client_secret', 'password', 'sc_password',
'oc_password', 'cert_file', 'key_file', 'public_key', 'private_key'
]
for (const item of accountProviderAttrs?.attrs) {
fieldsObject[item] = {
rules: updateNotRequiredFields.includes(item) && vm.$route.params.id ? [] : [RequiredChange]
}
}
return fieldsObject
}
return {
accountId: '',
taskId: '',
activeStep: 0,
description: accountProviderAttrs.title,
configSettings: {
initial: {
attrs: {
ip_group: []
},
provider: this.$route.query.provider,
port: 443
},
url: '/api/v1/xpack/cloud/accounts/',
fields: [
[this.$t('Basic'), ['name', 'provider']],
[this.$t(accountProviderAttrs.title), ['attrs']],
[this.$t('Other'), ['comment']]
],
fieldsMeta: {
name: {
rules: [RequiredChange, specialEmojiCheck]
},
attrs: {
encryptedFields: ['access_key_secret'],
fields: accountProviderAttrs.attrs,
fieldsMeta: {
// 必须放在最上面,下面特殊制定的字段才会覆盖默认
...setFieldAttrs(),
service_account_key: {
label: this.$t('ServerAccountKey'),
component: UploadKey,
el: {
toFormat: 'object'
}
},
cert_file: {
label: this.$t('Certificate'),
component: UploadKey,
el: {
toFormat: 'object'
}
},
key_file: {
label: this.$t('SecretKey'),
component: UploadKey,
el: {
toFormat: 'object'
}
},
password: {
rules: this.$route.params.id ? [] : [RequiredChange]
},
public_key: {
label: this.$t('PublicKey'),
rules: this.$route.params.id ? [] : [RequiredChange]
},
private_key: {
label: this.$t('PrivateKey'),
rules: this.$route.params.id ? [] : [RequiredChange]
},
project: {
label: this.$t('Project')
}
}
},
provider: {
rules: [RequiredChange],
el: {
disabled: true
}
}
},
onPerformSuccess: (resp) => {
let url = vm.strategySettings.fieldsMeta.regions.el.url
if (!vm.accountId) {
vm.accountId = resp?.id
url = setUrlParam(url, 'account_id', vm.accountId)
vm.strategySettings.fieldsMeta.regions.el.url = url
}
if (!vm.taskId) {
vm.taskId = resp?.task?.id
vm.strategySettings.url += `${vm.taskId}/`
}
this.activeStep = this.activeStep === 0 ? 1 : 0
},
submitBtnText: this.$t('Next'),
hasSaveContinue: false,
hasReset: false,
getUrl: () => {
let url = vm.configSettings.url
const params = vm.$route.params
const instanceId = params?.id || vm.accountId
if (instanceId) {
url = `${url}${instanceId}/`
}
return `${url}?provider=${accountProvider}`
},
submitMethod: () => {
return vm.$route.params?.id || vm.accountId ? 'put' : 'post'
},
cleanFormValue(values) {
const encryptedFields = [
'access_key_secret', 'password', 'client_secret',
'oc_password', 'sc_password'
]
const attrs = values.attrs
for (const item of encryptedFields) {
const value = attrs[item]
if (!value) {
continue
}
attrs[item] = encryptPassword(value)
}
if (Array.isArray(attrs.ip_group)) {
values.attrs.ip_group = attrs.ip_group.filter(Boolean)
}
return values
},
afterGetFormValue(formValue) {
if (!formValue.attrs) {
return formValue
}
return formValue
}
},
strategySettings: {
initial: {
is_periodic: false,
interval: 24,
hostname_strategy: 'instance_name_partial_ip',
ip_network_segment_group: ['*']
},
fields: [
[this.$t('CloudSource'), ['regions']],
[this.$t('SaveSetting'), [
'hostname_strategy', 'ip_network_segment_group', 'sync_ip_type',
'is_always_update', 'fully_synchronous'
]],
[this.$t('SyncStrategy'), ['strategy']],
[this.$t('Periodic'), ['is_periodic', 'interval', 'crontab']]
],
url: '/api/v1/xpack/cloud/sync-instance-tasks/',
fieldsMeta: {
hostname_strategy: {
rules: [RequiredChange],
helpTip: this.$t('HostnameStrategy')
},
is_always_update: {
type: 'checkbox',
label: this.$t('IsAlwaysUpdate'),
helpTip: this.$t('IsAlwaysUpdateHelpTip')
},
fully_synchronous: {
type: 'checkbox',
label: this.$t('FullySynchronous'),
helpTip: this.$t('FullySynchronousHelpTip')
},
regions: {
component: Checkbox,
el: {
url: '/api/v1/xpack/cloud/regions/',
options: [],
value: [],
noOptionTip: this.$t('CloudRegionTip'),
processResults(data) {
return data.regions?.map((item) => {
return { label: item.name, value: item.id }
})
}
}
},
is_periodic,
crontab,
interval,
strategy: {
label: this.$t('Strategy'),
component: SyncInstanceTaskStrategy,
helpTip: this.$t('StrategyHelpTip')
}
},
hasSaveContinue: false,
hasReset: false,
getUrl: () => {
return this.strategySettings.url
},
submitMethod: () => {
return this.$refs.baseForm.form.task?.id ? 'put' : 'post'
},
moreButtons: [
{
title: this.$t('Previous'),
callback: () => {
this.activeStep = this.activeStep === 1 ? 0 : 1
}
}
],
updateSuccessNextRoute: { name: 'CloudAccountList' },
createSuccessNextRoute: { name: 'CloudAccountList' },
cleanFormValue(value) {
const ipNetworkSegments = value.ip_network_segment_group
const strategy = value?.strategy || []
if (!Array.isArray(ipNetworkSegments)) {
value.ip_network_segment_group = ipNetworkSegments ? ipNetworkSegments.split(',') : []
}
value.strategy = strategy.map(item => {
return item.id
})
const accountId = this.$route.params?.id || this.accountId
if (accountId) {
value.account = { pk: accountId }
}
return value
}
}
}
},
methods: {}
}
</script>
<style lang="less" scoped>
</style>

View File

@@ -11,7 +11,7 @@
</template>
<template #right>
<QuickActions :actions="quickEditActions" type="primary" />
<QuickActions :actions="quickExecuteActions" type="primary" />
<QuickActions :actions="quickExecuteActions" :title="$t('Sync')" type="primary" />
<RelationCard
ref="StrategyRelation"
v-perms="'xpack.change_strategy'"

View File

@@ -1,64 +1,37 @@
<template>
<div>
<SmallCard ref="table" class="account-table" v-bind="table" />
<CreateDialog v-if="visible" :visible.sync="visible" v-bind="providerConfig" />
<UpdateDialog v-if="updateVisible" :object="object" :update-visible="updateVisible" />
<Dialog
:close-on-click-modal="false"
:close-on-press-escape="false"
:destroy-on-close="true"
:show-buttons="false"
:show-close="false"
:title="$tc('SyncOnline')"
<CreateDialog
v-if="visible"
:visible.sync="visible"
v-bind="providerConfig"
/>
<UpdateDialog
v-if="updateVisible"
:object="object"
:visible.sync="updateVisible"
@submitSuccess="onSubmitSuccess"
/>
<SyncDialog
v-if="onlineSyncVisible"
:visible.sync="onlineSyncVisible"
v-on="$listeners"
>
<AssetPanel :object="object" :visible.sync="onlineSyncVisible" />
</Dialog>
/>
</div>
</template>
<script type="text/jsx">
import {
aliyun,
apsara_stack,
aws_china,
aws_international,
azure,
azure_international,
baiducloud,
fc,
gcp,
huaweicloud,
huaweicloud_private,
jdcloud,
kingsoftcloud,
lan,
nutanix,
openstack,
qcloud,
qcloud_lighthouse,
qingcloud_private,
scp,
state_private,
ucloud,
vmware,
volcengine,
zstack
} from '../const'
import { lan, privateCloudProviders, publicCloudProviders } from '../const'
import CreateDialog from './components/CreateDialog.vue'
import UpdateDialog from './components/UpdateDialog.vue'
import SyncDialog from './components/SyncDialog.vue'
import SmallCard from '@/components/Table/CardTable/DataCardTable/index.vue'
import { ACCOUNT_PROVIDER_ATTRS_MAP } from '@/views/assets/Cloud/const'
import Dialog from '@/components/Dialog/index.vue'
import AssetPanel from './components/AssetPanel.vue'
import { toSafeLocalDateStr } from '@/utils/time'
export default {
name: 'CloudAccountList',
components: {
AssetPanel,
Dialog,
SyncDialog,
SmallCard,
CreateDialog,
UpdateDialog
@@ -128,13 +101,7 @@ export default {
title: this.$t('PublicCloud'),
icon: 'public-cloud',
callback: () => {
const providers = [
aliyun, qcloud, qcloud_lighthouse, huaweicloud,
baiducloud, jdcloud, kingsoftcloud, aws_china,
aws_international, azure, azure_international,
gcp, ucloud, volcengine
]
this.providerConfig.providers = providers.map(
this.providerConfig.providers = publicCloudProviders.map(
(item) => ACCOUNT_PROVIDER_ATTRS_MAP[item]
)
this.visible = true
@@ -145,11 +112,7 @@ export default {
icon: 'private-cloud',
title: this.$t('PrivateCloud'),
callback: () => {
const providers = [
vmware, qingcloud_private, huaweicloud_private, state_private,
openstack, zstack, nutanix, fc, scp, apsara_stack
]
this.providerConfig.providers = providers.map(
this.providerConfig.providers = privateCloudProviders.map(
(item) => ACCOUNT_PROVIDER_ATTRS_MAP[item]
)
this.visible = true

View File

@@ -124,7 +124,12 @@ export default {
.right-content {
background-color: #fff;
padding: 20px 10px;
::v-deep {
.el-form {
padding: 20px 20px 20px 10px
}
}
}
::v-deep .el-step {

View File

@@ -0,0 +1,48 @@
<template>
<Dialog
:close-on-click-modal="false"
:close-on-press-escape="false"
:destroy-on-close="true"
:show-buttons="false"
:show-close="false"
:title="$tc('SyncOnline')"
:visible.sync="iVisible"
v-on="$listeners"
>
<AssetPanel :object="object" :visible.sync="iVisible" />
</Dialog>
</template>
<script>
import AssetPanel from '@/views/assets/Cloud/Account/components/AssetPanel.vue'
import Dialog from '@/components/Dialog/index.vue'
export default {
name: 'SyncDialog',
components: { Dialog, AssetPanel },
props: {
object: {
type: Object,
default: () => ({})
},
visible: {
type: Boolean,
default: () => false
}
},
computed: {
iVisible: {
set(val) {
this.$emit('update:visible', val)
},
get() {
return this.visible
}
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -1,44 +1,69 @@
<template>
<Drawer
v-if="updateVisible"
:destroy-on-close="true"
:show-buttons="false"
:title="$tc('CloudAccountUpdate')"
:visible.sync="updateVisible"
:visible.sync="iVisible"
class="drawer"
v-on="$listeners"
>
<div style="background: white">
<IBox>
<AuthPanel
:object="object"
:provider="object.provider.value"
:visible.sync="updateVisible"
:visible.sync="iVisible"
origin="update"
@submitSuccess="onSubmitSuccess"
/>
</div>
</IBox>
</Drawer>
</template>
<script>
import AuthPanel from '@/views/assets/Cloud/Account/components/AuthPanel.vue'
import Drawer from '@/components/Drawer/index.vue'
import IBox from '@/components/Common/IBox/index.vue'
export default {
name: 'UpdateDialog',
components: { Drawer, AuthPanel },
components: { Drawer, AuthPanel, IBox },
props: {
object: {
type: Object,
required: true
},
updateVisible: {
visible: {
type: Boolean,
default: false
}
},
computed: {
iVisible: {
set(val) {
this.$emit('update:visible', val)
},
get() {
return this.visible
}
}
},
methods: {
onSubmitSuccess() {
this.$emit('submitSuccess')
}
}
}
</script>
<style lang="scss" scoped>
.ibox {
margin: 15px
}
.drawer ::v-deep {
.el-drawer__body {
background: #f3f3f3;
}
}
</style>

View File

@@ -1,18 +1,25 @@
<template>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<DrawerListTable
:create-drawer="createDrawer"
:detail-drawer="detailDrawer"
:header-actions="headerActions"
:table-config="tableConfig"
/>
</template>
<script type="text/jsx">
import GenericListTable from '@/layout/components/GenericListTable'
import { DrawerListTable } from '@/components'
import { DetailFormatter } from '@/components/Table/TableFormatters'
export default {
name: 'StrategyList',
components: {
GenericListTable
DrawerListTable
},
data() {
return {
createDrawer: () => import('@/views/assets/Cloud/Strategy/StrategyCreateUpdate.vue'),
detailDrawer: () => import('@/views/assets/Cloud/Strategy/StrategyDetail/index.vue'),
tableConfig: {
url: '/api/v1/xpack/cloud/strategies/',
permissions: {
@@ -31,17 +38,25 @@ export default {
}
},
strategy_rules: {
formatter: (row) => { return row.strategy_rules.length }
formatter: (row) => {
return row.strategy_rules.length
}
},
strategy_actions: {
formatter: (row) => { return row.strategy_actions.length }
formatter: (row) => {
return row.strategy_actions.length
}
},
actions: {
formatterArgs: {
updateRoute: 'CloudStrategyUpdate',
hasClone: false,
canDelete: ({ row }) => { return row.name !== 'default' },
canUpdate: ({ row }) => { return row.name !== 'default' }
canDelete: ({ row }) => {
return row.name !== 'default'
},
canUpdate: ({ row }) => {
return row.name !== 'default'
}
}
}
}
@@ -54,8 +69,7 @@ export default {
}
}
},
methods: {
}
methods: {}
}
</script>

View File

@@ -39,7 +39,8 @@ export default {
submitBtnSize: 'mini',
submitBtnText: this.$t('Add'),
hasReset: false,
onSubmit: () => {},
onSubmit: () => {
},
submitMethod: () => 'post',
getUrl: () => '',
cleanFormValue(data) {
@@ -247,7 +248,6 @@ export default {
}
.action-input {
margin-top: -10px;
}
</style>

View File

@@ -15,10 +15,10 @@
</template>
<script>
import GenericCreateUpdateForm from '@/layout/components/GenericCreateUpdateForm'
import GenericCreateUpdateForm from '@/layout/components/GenericCreateUpdateForm/index.vue'
import Dialog from '@/components/Dialog/index.vue'
import RuleInput from '@/views/assets/Cloud/Strategy/components/RuleInput'
import ActionInput from '@/views/assets/Cloud/Strategy/components/ActionInput'
import RuleInput from '@/views/assets/Cloud/Strategy/components/RuleInput.vue'
import ActionInput from '@/views/assets/Cloud/Strategy/components/ActionInput.vue'
import IBox from '@/components/Common/IBox/index.vue'
export default {

View File

@@ -1,150 +0,0 @@
<template>
<GenericCreateUpdatePage ref="createUpdatePage" v-bind="$data" />
</template>
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import { Select2 } from '@/components'
import rules from '@/components/Form/DataForm/rules'
import SyncInstanceTaskStrategy from './components/SyncInstanceTaskStrategy/index'
import { periodicMeta } from '@/components/const'
export default {
components: {
GenericCreateUpdatePage
},
data() {
const vm = this
return {
initial: {
is_periodic: true,
interval: 24,
hostname_strategy: 'instance_name_partial_ip',
ip_network_segment_group: ['*']
},
fields: [
[this.$t('common.Basic'), ['name']],
[this.$t('xpack.Cloud.CloudSource'), ['account', 'regions']],
[this.$t('xpack.Cloud.SaveSetting'), [
'hostname_strategy', 'ip_network_segment_group',
'sync_ip_type', 'is_always_update', 'fully_synchronous'
]],
[this.$t('xpack.Cloud.SyncStrategy'), ['strategy']],
[this.$t('xpack.Timer'), ['is_periodic', 'crontab', 'interval']],
[this.$t('common.Other'), ['comment']]
],
url: '/api/v1/xpack/cloud/sync-instance-tasks/',
fieldsMeta: {
...periodicMeta,
account: {
label: this.$t('xpack.Cloud.Account'),
on: {
change: ([event], updateForm) => {
vm.fieldsMeta.regions.el.ajax.url = `/api/v1/xpack/cloud/regions/?account_id=${event?.pk}`
updateForm({ regions: '' })
}
},
el: {
multiple: false,
value: [],
ajax: {
url: '/api/v1/xpack/cloud/accounts/',
transformOption: (item) => {
const label = `${item.name}(${item.provider.label})`
return { label: label, value: item.id }
}
}
}
},
hostname_strategy: {
rules: [rules.RequiredChange],
helpTips: this.$t('xpack.Cloud.HostnameStrategy')
},
is_always_update: {
type: 'checkbox',
label: this.$t('xpack.Cloud.IsAlwaysUpdate'),
helpTips: this.$t('xpack.Cloud.IsAlwaysUpdateHelpTips')
},
fully_synchronous: {
type: 'checkbox',
label: this.$t('xpack.Cloud.FullySynchronous'),
helpTips: this.$t('xpack.Cloud.FullySynchronousHelpTips')
},
regions: {
component: Select2,
el: {
multiple: true,
allowCreate: true,
value: [],
ajax: {
url: '/api/v1/xpack/cloud/regions/',
processResults(data) {
const results = data.regions?.map((item) => {
return { label: item.name, value: item.id }
})
const more = !!data.next
return { results: results, pagination: more, total: data.count }
}
}
}
},
strategy: {
label: this.$t('common.Strategy'),
component: SyncInstanceTaskStrategy,
helpTips: this.$t('xpack.Cloud.StrategyHelpTips')
}
},
updateSuccessNextRoute: { name: 'CloudCenter' },
createSuccessNextRoute: { name: 'CloudCenter' },
afterGetFormValue(formValue) {
formValue.protocols = formValue.protocols?.split(' ').map(i => {
const [name, port] = i.split('/')
return { name, port }
})
return formValue
},
cleanFormValue(value) {
const ipNetworkSegments = value.ip_network_segment_group
const strategy = value?.strategy || []
if (!Array.isArray(ipNetworkSegments)) {
value.ip_network_segment_group = ipNetworkSegments ? ipNetworkSegments.split(',') : []
}
value.strategy = strategy.map(item => {
return item.id
})
return value
},
onPerformError(error, method, vm) {
this.$emit('submitError', error)
const response = error.response
const data = response.data
if (response.status === 400) {
for (const key of Object.keys(data)) {
let value = data[key]
if (key === 'protocols') {
value = Object.values(data[key])
}
if (value instanceof Array) {
value = value.join(';')
}
this.$refs.form.setFieldError(key, value)
}
}
}
}
},
async mounted() {
const params = this.$route.params
// 更新获取链接
if (params.id) {
const form = await this.$refs.createUpdatePage.$refs.createUpdateForm.getFormValue()
this.fieldsMeta.regions.el.ajax.url = form.account?.id ? `/api/v1/xpack/cloud/regions/?account_id=${form.account.id}` : `/api/v1/xpack/cloud/regions/`
}
},
methods: {}
}
</script>
<style lang="less" scoped>
</style>

View File

@@ -29,6 +29,18 @@ export const scp = 'scp'
export const apsara_stack = 'apsara_stack'
export const lan = 'lan'
export const publicCloudProviders = [
aliyun, qcloud, qcloud_lighthouse, huaweicloud,
baiducloud, jdcloud, kingsoftcloud, aws_china,
aws_international, azure, azure_international,
gcp, ucloud, volcengine
]
export const privateCloudProviders = [
vmware, qingcloud_private, huaweicloud_private, state_private,
openstack, zstack, nutanix, fc, scp, apsara_stack
]
export const ACCOUNT_PROVIDER_ATTRS_MAP = {
[aliyun]: {
name: aliyun,