mirror of
https://github.com/jumpserver/lina.git
synced 2026-01-29 21:28:52 +00:00
feat: 云同步增加同步策略 (#3264)
* feat: 云同步逻辑更新 * feat: 云同步增加同步策略 * perf: 优化 * perf: 优化提交逻辑,平台和网域只允许设置一次 * perf: 策略抽离,有列表、详情、同步任务可便捷添加策略 * fix: 修改组件层级关系 * perf: 去掉不需要的 * perf: 优化变量名称、策略添加优先级
This commit is contained in:
@@ -38,11 +38,11 @@
|
||||
v-if="defaultButton"
|
||||
:disabled="!canSubmit"
|
||||
:loading="isSubmitting"
|
||||
size="small"
|
||||
:size="submitBtnSize"
|
||||
type="primary"
|
||||
@click="submitForm('form')"
|
||||
>
|
||||
{{ $t('common.Submit') }}
|
||||
{{ submitBtnText }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</ElFormRender>
|
||||
@@ -73,6 +73,16 @@ export default {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
submitBtnSize: {
|
||||
type: String,
|
||||
default: 'small'
|
||||
},
|
||||
submitBtnText: {
|
||||
type: String,
|
||||
default() {
|
||||
return this.$t('common.Submit')
|
||||
}
|
||||
},
|
||||
hasSaveContinue: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
|
||||
62
src/components/Form/FormFields/AttrInput.vue
Normal file
62
src/components/Form/FormFields/AttrInput.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div>
|
||||
<GenericCreateUpdateForm
|
||||
class="attr-form"
|
||||
v-bind="formConfig"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
<DataTable :config="tableConfig" class="attr-list" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GenericCreateUpdateForm from '@/layout/components/GenericCreateUpdateForm'
|
||||
import DataTable from '@/components/Table/DataTable/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'AttrInput',
|
||||
components: { DataTable, GenericCreateUpdateForm },
|
||||
props: {
|
||||
formConfig: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
tableConfig: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
beforeSubmit: {
|
||||
type: Function,
|
||||
default: (val) => { return true }
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
onSubmit(value) {
|
||||
if (this.beforeSubmit(value)) {
|
||||
const clonedValue = JSON.parse(JSON.stringify(value))
|
||||
this.tableConfig.totalData.push(clonedValue)
|
||||
this.$emit('submit', this.tableConfig.totalData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.attr-form {
|
||||
>>> .el-select {
|
||||
width: 100%;
|
||||
}
|
||||
>>> .el-form-item__content {
|
||||
width: 100%;
|
||||
}
|
||||
>>> .form-buttons {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
import DataForm from '@/components/Form/DataForm/index.vue'
|
||||
import Dialog from '@/components/Dialog/index.vue'
|
||||
import ValueField from '@/components/Form/FormFields/JSONManyToManySelect/ValueField.vue'
|
||||
import { attrMatchOptions, typeMatchMapper } from './const'
|
||||
import { attrMatchOptions, typeMatchMapper } from '@/components/const'
|
||||
|
||||
export default {
|
||||
name: 'AttrFormDialog',
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import i18n from '@/i18n/i18n'
|
||||
|
||||
export const strMatchValues = ['exact', 'not', 'in', 'contains', 'startswith', 'endswith', 'regex']
|
||||
export const typeMatchMapper = {
|
||||
str: strMatchValues,
|
||||
bool: ['exact', 'not'],
|
||||
m2m: ['m2m'],
|
||||
ip: [...strMatchValues, 'ip_in'],
|
||||
int: [...strMatchValues, 'gte', 'lte'],
|
||||
select: ['in']
|
||||
}
|
||||
|
||||
export const attrMatchOptions = [
|
||||
{ label: i18n.t('common.Equal'), value: 'exact' },
|
||||
{ label: i18n.t('common.NotEqual'), value: 'not' },
|
||||
{ label: i18n.t('common.MatchIn'), value: 'in' },
|
||||
{ label: i18n.t('common.Contains'), value: 'contains' },
|
||||
{ label: i18n.t('common.Startswith'), value: 'startswith' },
|
||||
{ label: i18n.t('common.Endswith'), value: 'endswith' },
|
||||
{ label: i18n.t('common.Regex'), value: 'regex' },
|
||||
{ label: i18n.t('common.BelongTo'), value: 'm2m' },
|
||||
{ label: i18n.t('common.IPMatch'), value: 'ip_in' },
|
||||
{ label: i18n.t('common.GreatEqualThan'), value: 'gte' },
|
||||
{ label: i18n.t('common.LessEqualThan'), value: 'lte' }
|
||||
]
|
||||
|
||||
@@ -43,7 +43,7 @@ import ValueFormatter from './ValueFormatter.vue'
|
||||
import AttrFormDialog from './AttrFormDialog.vue'
|
||||
import AttrMatchResultDialog from './AttrMatchResultDialog.vue'
|
||||
import { setUrlParam } from '@/utils/common'
|
||||
import { attrMatchOptions } from './const'
|
||||
import { attrMatchOptions } from '@/components/const'
|
||||
import { toM2MJsonParams } from '@/utils/jms'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -305,7 +305,7 @@ export default {
|
||||
items = protocols.filter(item => allProtocolNames.indexOf(item.name) !== -1)
|
||||
} else {
|
||||
const defaults = choices.filter(item => (item.required || item.primary || item.default))
|
||||
if (defaults.length === 0) {
|
||||
if (defaults.length === 0 && choices.length !== 0) {
|
||||
defaults.push(choices[0])
|
||||
}
|
||||
items = defaults
|
||||
|
||||
@@ -3,6 +3,7 @@ import Text from './Text.vue'
|
||||
import Select2 from './Select2.vue'
|
||||
import TagInput from './TagInput.vue'
|
||||
import Switcher from './Switcher.vue'
|
||||
import AttrInput from './AttrInput.vue'
|
||||
import UploadKey from './UploadKey.vue'
|
||||
import JsonEditor from './JsonEditor.vue'
|
||||
import PhoneInput from './PhoneInput.vue'
|
||||
@@ -23,6 +24,7 @@ export default {
|
||||
Switcher,
|
||||
Select2,
|
||||
TagInput,
|
||||
AttrInput,
|
||||
UploadKey,
|
||||
JsonEditor,
|
||||
UpdateToken,
|
||||
@@ -44,6 +46,7 @@ export {
|
||||
Switcher,
|
||||
Select2,
|
||||
TagInput,
|
||||
AttrInput,
|
||||
UploadKey,
|
||||
JsonEditor,
|
||||
UpdateToken,
|
||||
|
||||
25
src/components/const.js
Normal file
25
src/components/const.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import i18n from '@/i18n/i18n'
|
||||
|
||||
export const strMatchValues = ['exact', 'not', 'in', 'contains', 'startswith', 'endswith', 'regex']
|
||||
export const typeMatchMapper = {
|
||||
str: strMatchValues,
|
||||
bool: ['exact', 'not'],
|
||||
m2m: ['m2m'],
|
||||
ip: [...strMatchValues, 'ip_in'],
|
||||
int: [...strMatchValues, 'gte', 'lte'],
|
||||
select: ['in']
|
||||
}
|
||||
|
||||
export const attrMatchOptions = [
|
||||
{ label: i18n.t('common.Equal'), value: 'exact' },
|
||||
{ label: i18n.t('common.NotEqual'), value: 'not' },
|
||||
{ label: i18n.t('common.MatchIn'), value: 'in' },
|
||||
{ label: i18n.t('common.Contains'), value: 'contains' },
|
||||
{ label: i18n.t('common.Startswith'), value: 'startswith' },
|
||||
{ label: i18n.t('common.Endswith'), value: 'endswith' },
|
||||
{ label: i18n.t('common.Regex'), value: 'regex' },
|
||||
{ label: i18n.t('common.BelongTo'), value: 'm2m' },
|
||||
{ label: i18n.t('common.IPMatch'), value: 'ip_in' },
|
||||
{ label: i18n.t('common.GreatEqualThan'), value: 'gte' },
|
||||
{ label: i18n.t('common.LessEqualThan'), value: 'lte' }
|
||||
]
|
||||
@@ -486,6 +486,20 @@
|
||||
"ReLoginErr": "Login time has exceeded 5 minutes, please login again"
|
||||
},
|
||||
"common": {
|
||||
"SyncTask": "Synchronization task",
|
||||
"New": "New",
|
||||
"Strategy": "Strategy",
|
||||
"StrategyList": "Strategy list",
|
||||
"StrategyCreate": "Create strategy",
|
||||
"StrategyUpdate": "Update strategy",
|
||||
"StrategyDetail": "Strategy detail",
|
||||
"Rule": "Rule",
|
||||
"RuleCount": "Number of conditions",
|
||||
"ActionCount": "Number of actions",
|
||||
"PolicyName": "Policy name",
|
||||
"BasicSetting": "Basic setting",
|
||||
"RuleSetting": "Rule setting",
|
||||
"ActionSetting": "Action setting",
|
||||
"Proxy": "Proxy",
|
||||
"PublicKey": "Public key",
|
||||
"PrivateKey": "Private key",
|
||||
@@ -595,6 +609,7 @@
|
||||
"CrontabHelpTips": "eg: Every Sunday 03:05 run <5 3 * * 0> <br>Tips:Using 5 digits linux crontab expressions<min hour day month week> (<a href='https://tool.lu/crontab/' target='_blank'>Online tools</a>) <br>Note:If both Regularly perform and Cycle perform are set, give priority to Regularly perform",
|
||||
"DateEnd": "End date",
|
||||
"Resource": "Resource",
|
||||
"ResourceType": "Resource type",
|
||||
"DateLast24Hours": "Last 24 hours",
|
||||
"DateLast3Months": "Last 3 months",
|
||||
"DateLastHarfYear": "Last half year",
|
||||
@@ -2056,6 +2071,11 @@
|
||||
"Reason": "Reason"
|
||||
},
|
||||
"Cloud": {
|
||||
"UniqueError": "Only one of the following properties can be set",
|
||||
"ExistError": "This element already exists",
|
||||
"InstanceName": "Instance name",
|
||||
"InstancePlatformName": "Instance platform name",
|
||||
"InstanceAddress": "Instance address",
|
||||
"CloudSync": "Cloud sync",
|
||||
"ServerAccountKey": "Server Account Key",
|
||||
"IPNetworkSegment": "Ip Network Segment",
|
||||
@@ -2084,11 +2104,12 @@
|
||||
"CloudCenter": "Cloud Center",
|
||||
"Provider": "Provider",
|
||||
"Validity": "Validity",
|
||||
"SyncStrategy": "Synchronisation strategy",
|
||||
"IsAlwaysUpdateHelpTips": "Whether the asset information, including Hostname, IP, Platform, and AdminUser, is updated synchronously each time a synchronization task is performed",
|
||||
"SyncInstanceTaskCreate": "Create sync instance task",
|
||||
"SyncInstanceTaskList": "Sync instance task list",
|
||||
"SyncInstanceTaskDetail": "Sync instance task detail",
|
||||
"SyncInstanceTaskUpdate": "Update sync instance task",
|
||||
"SyncInstanceTaskCreate": "Create sync task",
|
||||
"SyncInstanceTaskList": "Sync task list",
|
||||
"SyncInstanceTaskDetail": "Sync task detail",
|
||||
"SyncInstanceTaskUpdate": "Update sync task",
|
||||
"SyncInstanceTaskHistoryList": "Sync task history",
|
||||
"SyncInstanceTaskHistoryAssetList": "Sync instance list",
|
||||
"CloudSource": "Cloud source",
|
||||
|
||||
@@ -486,6 +486,20 @@
|
||||
"ReLoginErr": "ログイン時間が 5 分を超えました。もう一度ログインしてください"
|
||||
},
|
||||
"common": {
|
||||
"SyncTask": "同期任務です",
|
||||
"New": "新筑",
|
||||
"Strategy": "せんりゃく",
|
||||
"StrategyList": "ポリシーリストです",
|
||||
"StrategyCreate": "戦略を作ります",
|
||||
"StrategyUpdate": "ポリシーを更新します",
|
||||
"StrategyDetail": "戦略の詳細です",
|
||||
"Rule": "ルール",
|
||||
"RuleCount": "条件数です",
|
||||
"ActionCount": "アクション数",
|
||||
"PolicyName": "ポリシーの名前です",
|
||||
"BasicSetting": "基本設定",
|
||||
"RuleSetting": "条件設定です",
|
||||
"ActionSetting": "動作設定です",
|
||||
"Proxy": "プロキシ",
|
||||
"PublicKey": "公開鍵です",
|
||||
"PrivateKey": "秘密鍵です",
|
||||
@@ -594,6 +608,7 @@
|
||||
"CrontabHelpTips": "Eg: 毎週日曜日03:05に <5 3 * * 0> <br> ヒント: 5ビットLinux crontab式 <時分割日月曜日> (<a href = 'https:// tool.lu/crontab/' target = '_ blank'> オンラインツール </a>) <br>注意: 定期実行とサイクル実行の両方が設定されている場合は、定期実行を優先します",
|
||||
"DateEnd": "終了日",
|
||||
"Resource": "リソース",
|
||||
"ResourceType": "リソースタイプです",
|
||||
"DateLast24Hours": "最近の日",
|
||||
"DateLast3Months": "直近3月",
|
||||
"DateLastHarfYear": "ここ半年です",
|
||||
@@ -2049,6 +2064,11 @@
|
||||
"Reason": "原因"
|
||||
},
|
||||
"Cloud": {
|
||||
"UniqueError": "以下の属性は1つしか設定できません",
|
||||
"ExistError": "この元素は既に存在します",
|
||||
"InstanceName": "インスタンス名です",
|
||||
"InstancePlatformName": "インスタンスのプラットフォーム名です",
|
||||
"InstanceAddress": "インスタンスアドレスです",
|
||||
"CloudSync": "クラウド同期",
|
||||
"ServerAccountKey": "サービスアカウントキー",
|
||||
"IPNetworkSegment": "IPネットワークセグメント",
|
||||
@@ -2081,11 +2101,12 @@
|
||||
"CloudCenter": "クラウド管理センター",
|
||||
"Provider": "クラウドサービス業者",
|
||||
"Validity": "有効",
|
||||
"SyncStrategy": "同調戦略です",
|
||||
"IsAlwaysUpdateHelpTips": "同期タスクを実行するたびに、ホスト名、IP、システムプラットフォーム、管理ユーザーなど、アセットの情報を同期更新しますか?",
|
||||
"SyncInstanceTaskCreate": "同期インスタンスタスクの作成",
|
||||
"SyncInstanceTaskList": "インスタンスのタスクリストの同期",
|
||||
"SyncInstanceTaskDetail": "インスタンスタスクの詳細の同期",
|
||||
"SyncInstanceTaskUpdate": "同期インスタンスタスクの更新",
|
||||
"SyncInstanceTaskCreate": "同期タスクを作成します",
|
||||
"SyncInstanceTaskList": "同期タスクリストです",
|
||||
"SyncInstanceTaskDetail": "同期任務詳細です",
|
||||
"SyncInstanceTaskUpdate": "同期タスクの更新です",
|
||||
"SyncInstanceTaskHistoryList": "履歴リストの同期",
|
||||
"SyncInstanceTaskHistoryAssetList": "インスタンスリストの同期",
|
||||
"CloudSource": "同期ソース",
|
||||
|
||||
@@ -466,6 +466,20 @@
|
||||
"ReLoginErr": "登录时长已超过 5 分钟,请重新登录"
|
||||
},
|
||||
"common": {
|
||||
"SyncTask": "同步任务",
|
||||
"New": "新建",
|
||||
"Strategy": "策略",
|
||||
"StrategyList": "策略列表",
|
||||
"StrategyCreate": "创建策略",
|
||||
"StrategyUpdate": "更新策略",
|
||||
"StrategyDetail": "策略详情",
|
||||
"Rule": "条件",
|
||||
"RuleCount": "条件数量",
|
||||
"ActionCount": "动作数量",
|
||||
"PolicyName": "策略名称",
|
||||
"BasicSetting": "基本设置",
|
||||
"RuleSetting": "条件设置",
|
||||
"ActionSetting": "动作设置",
|
||||
"Proxy": "代理",
|
||||
"PublicKey": "公钥",
|
||||
"PrivateKey": "私钥",
|
||||
@@ -623,6 +637,7 @@
|
||||
"CrontabHelpTips": "eg:每周日 03:05 执行 <5 3 * * 0> <br> 提示: 使用5位 Linux crontab 表达式 <分 时 日 月 星期> (<a href='https://tool.lu/crontab/' target='_blank'>在线工具</a>) <br>注意: 如果同时设置了定期执行和周期执行,优先使用定期执行",
|
||||
"DateEnd": "结束日期",
|
||||
"Resource": "资源",
|
||||
"ResourceType": "资源类型",
|
||||
"DateLast24Hours": "最近一天",
|
||||
"DateLast3Months": "最近三月",
|
||||
"DateLastHarfYear": "最近半年",
|
||||
@@ -1970,6 +1985,11 @@
|
||||
"AssetCount": "资产数量",
|
||||
"Auditor": "审计员",
|
||||
"Cloud": {
|
||||
"UniqueError": "以下属性只能设置一个",
|
||||
"ExistError": "这个元素已经存在",
|
||||
"InstanceName": "实例名称",
|
||||
"InstancePlatformName": "实例平台名称",
|
||||
"InstanceAddress": "实例地址",
|
||||
"LAN": "局域网",
|
||||
"CloudSync": "云同步",
|
||||
"ServerAccountKey": "服务账号密钥",
|
||||
@@ -2002,11 +2022,12 @@
|
||||
"CloudCenter": "云管中心",
|
||||
"Provider": "云服务商",
|
||||
"Validity": "有效",
|
||||
"SyncStrategy": "同步策略",
|
||||
"IsAlwaysUpdateHelpTips": "每次执行同步任务时,是否同步更新资产的信息,包括主机名、IP、系统平台、管理用户",
|
||||
"SyncInstanceTaskCreate": "创建同步实例任务",
|
||||
"SyncInstanceTaskList": "同步实例任务列表",
|
||||
"SyncInstanceTaskDetail": "同步实例任务详情",
|
||||
"SyncInstanceTaskUpdate": "更新同步实例任务",
|
||||
"SyncInstanceTaskCreate": "创建同步任务",
|
||||
"SyncInstanceTaskList": "同步任务列表",
|
||||
"SyncInstanceTaskDetail": "同步任务详情",
|
||||
"SyncInstanceTaskUpdate": "更新同步任务",
|
||||
"SyncInstanceTaskHistoryList": "同步历史列表",
|
||||
"SyncInstanceTaskHistoryAssetList": "同步实例列表",
|
||||
"CloudSource": "同步源",
|
||||
|
||||
@@ -129,6 +129,58 @@ export default [
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'strategy',
|
||||
component: empty,
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('xpack.Cloud.Strategy'),
|
||||
permissions: ['xpack.view_strategy']
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'CloudStrategyList',
|
||||
hidden: true,
|
||||
component: () => import('@/views/assets/Cloud/'),
|
||||
meta: {
|
||||
title: i18n.t('xpack.Cloud.StrategyList'),
|
||||
permissions: ['xpack.view_strategy']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: () => import('@/views/assets/Cloud/Strategy/StrategyCreateUpdate'),
|
||||
name: 'CloudStrategyCreate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('common.StrategyCreate'),
|
||||
action: 'create',
|
||||
permissions: ['xpack.add_strategy']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id/update',
|
||||
component: () => import('@/views/assets/Cloud/Strategy/StrategyCreateUpdate'),
|
||||
name: 'CloudStrategyUpdate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('common.StrategyUpdate'),
|
||||
permissions: ['xpack.change_strategy']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id/',
|
||||
component: () => import('@/views/assets/Cloud/Strategy/StrategyDetail/index'),
|
||||
name: 'CloudStrategyDetail',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('common.StrategyDetail'),
|
||||
permissions: ['xpack.view_strategy']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -80,17 +80,6 @@ export default {
|
||||
password: {
|
||||
rules: this.$route.params.id ? [] : [RequiredChange]
|
||||
},
|
||||
platform: {
|
||||
el: {
|
||||
multiple: false,
|
||||
ajax: {
|
||||
url: `/api/v1/assets/platforms/`,
|
||||
transformOption: (item) => {
|
||||
return { label: item.name, value: item.id }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
public_key: {
|
||||
label: this.$t('common.PublicKey'),
|
||||
rules: this.$route.params.id ? [] : [RequiredChange]
|
||||
|
||||
50
src/views/assets/Cloud/Strategy/StrategyCreateUpdate.vue
Normal file
50
src/views/assets/Cloud/Strategy/StrategyCreateUpdate.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<GenericCreateUpdatePage
|
||||
v-bind="$data"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { GenericCreateUpdatePage } from '@/layout/components'
|
||||
import { RequiredChange, specialEmojiCheck } from '@/components/Form/DataForm/rules'
|
||||
import RuleInput from './components/RuleInput'
|
||||
import ActionInput from './components/ActionInput'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GenericCreateUpdatePage
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
url: '/api/v1/xpack/cloud/strategies/',
|
||||
fields: [
|
||||
[this.$t('common.Basic'), ['name', 'priority']],
|
||||
[this.$t('common.Strategy'), ['strategy_rules', 'strategy_actions']],
|
||||
[this.$t('common.Other'), ['comment']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
name: {
|
||||
rules: [RequiredChange, specialEmojiCheck]
|
||||
},
|
||||
strategy_rules: {
|
||||
component: RuleInput
|
||||
},
|
||||
strategy_actions: {
|
||||
component: ActionInput
|
||||
}
|
||||
},
|
||||
updateSuccessNextRoute: { name: 'CloudCenter', params: { activeMenu: 'StrategyList' }},
|
||||
createSuccessNextRoute: { name: 'CloudCenter', params: { activeMenu: 'StrategyList' }},
|
||||
getUrl() {
|
||||
const id = this.$route.params?.id
|
||||
return id ? `${this.url}${id}/` : this.url
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :md="14" :sm="24">
|
||||
<AutoDetailCard :fields="detailFields" :object="object" :url="url" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AutoDetailCard from '@/components/Cards/DetailCard/auto'
|
||||
|
||||
export default {
|
||||
name: 'StrategyDetail',
|
||||
components: {
|
||||
AutoDetailCard
|
||||
},
|
||||
props: {
|
||||
object: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
url: `/api/v1/xpack/cloud/strategies/${this.object.id}/`,
|
||||
detailFields: [
|
||||
'name', 'priority',
|
||||
{
|
||||
key: this.$t('common.Rule'),
|
||||
formatter: () => {
|
||||
const newArr = this.object.strategy_rules || []
|
||||
return (
|
||||
<ul>
|
||||
{
|
||||
newArr.map((r, index) => {
|
||||
return <li key={index}>{`${r.attr.label} ${r.match.label} ${r.value}`} </li>
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
key: this.$t('common.Action'),
|
||||
formatter: () => {
|
||||
const newArr = this.object.strategy_actions || []
|
||||
return (
|
||||
<ul>
|
||||
{
|
||||
newArr.map((a, index) => {
|
||||
return <li key={index}>{`${a.attr.label}: ${a.value.label}`} </li>
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
},
|
||||
'comment', 'org_name'
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cardTitle() {
|
||||
return this.object.name
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
ul {
|
||||
counter-reset: my-counter;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
counter-increment: my-counter;
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
li:before {
|
||||
content: counter(my-counter);
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 32%;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
line-height: 12px;
|
||||
text-align: center;
|
||||
border: 1px solid;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
53
src/views/assets/Cloud/Strategy/StrategyDetail/index.vue
Normal file
53
src/views/assets/Cloud/Strategy/StrategyDetail/index.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<GenericDetailPage :object.sync="Account" :active-menu.sync="config.activeMenu" v-bind="config" v-on="$listeners">
|
||||
<keep-alive>
|
||||
<component :is="config.activeMenu" :object="Account" />
|
||||
</keep-alive>
|
||||
</GenericDetailPage>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { GenericDetailPage, TabPage } from '@/layout/components'
|
||||
import StrategyDetail from './StrategyDetail'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GenericDetailPage,
|
||||
StrategyDetail,
|
||||
TabPage
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
Account: {
|
||||
name: '', strategy_rules: [], strategy_actions: [], comment: ''
|
||||
},
|
||||
config: {
|
||||
url: `/api/v1/xpack/cloud/strategies`,
|
||||
activeMenu: 'StrategyDetail',
|
||||
submenu: [
|
||||
{
|
||||
title: this.$t('common.Strategy'),
|
||||
name: 'StrategyDetail'
|
||||
}
|
||||
],
|
||||
actions: {
|
||||
deleteSuccessRoute: 'CloudCenter',
|
||||
updateCallback: () => {
|
||||
const id = this.$route.params.id
|
||||
const routeName = 'CloudStrategyUpdate'
|
||||
this.$router.push({
|
||||
name: routeName,
|
||||
params: { id: id }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
</style>
|
||||
|
||||
59
src/views/assets/Cloud/Strategy/StrategyList.vue
Normal file
59
src/views/assets/Cloud/Strategy/StrategyList.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<GenericListTable :table-config="tableConfig" :header-actions="headerActions" />
|
||||
</template>
|
||||
|
||||
<script type="text/jsx">
|
||||
import GenericListTable from '@/layout/components/GenericListTable'
|
||||
import { DetailFormatter } from '@/components/Table/TableFormatters'
|
||||
|
||||
export default {
|
||||
name: 'StrategyList',
|
||||
components: {
|
||||
GenericListTable
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableConfig: {
|
||||
url: '/api/v1/xpack/cloud/strategies/',
|
||||
permissions: {
|
||||
app: 'xpack',
|
||||
resource: 'strategy'
|
||||
},
|
||||
columns: ['name', 'priority', 'strategy_rules', 'strategy_actions', 'actions', 'user_actions'],
|
||||
columnsMeta: {
|
||||
name: {
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
route: 'CloudStrategyDetail'
|
||||
}
|
||||
},
|
||||
strategy_rules: {
|
||||
formatter: (row) => { return row.strategy_rules.length }
|
||||
},
|
||||
strategy_actions: {
|
||||
formatter: (row) => { return row.strategy_actions.length }
|
||||
},
|
||||
actions: {
|
||||
formatterArgs: {
|
||||
updateRoute: 'CloudStrategyUpdate',
|
||||
hasClone: false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasImport: false,
|
||||
hasMoreActions: false,
|
||||
createRoute: 'CloudStrategyCreate'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
205
src/views/assets/Cloud/Strategy/components/ActionInput.vue
Normal file
205
src/views/assets/Cloud/Strategy/components/ActionInput.vue
Normal file
@@ -0,0 +1,205 @@
|
||||
<template>
|
||||
<AttrInput
|
||||
:form-config="formConfig"
|
||||
:table-config="tableConfig"
|
||||
:before-submit="beforeSubmit"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { AttrInput, Select2 } from '@/components/Form/FormFields'
|
||||
import { Required } from '@/components/Form/DataForm/rules'
|
||||
import ProtocolSelector from '@/components/Form/FormFields/ProtocolSelector'
|
||||
import { resourceTypeOptions, tableFormatter } from './const'
|
||||
|
||||
export default {
|
||||
name: 'ActionInput',
|
||||
components: { AttrInput },
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
resourceType: '',
|
||||
globalResource: {},
|
||||
globalProtocols: {},
|
||||
formConfig: {
|
||||
initial: { attr: '', value: '' },
|
||||
inline: true,
|
||||
hasSaveContinue: false,
|
||||
submitBtnSize: 'mini',
|
||||
submitBtnText: this.$t('common.Add'),
|
||||
hasReset: false,
|
||||
onSubmit: () => {},
|
||||
submitMethod: () => 'post',
|
||||
getUrl: () => '',
|
||||
cleanFormValue(data) {
|
||||
if (data?.attr !== 'platform') {
|
||||
delete data.protocols
|
||||
}
|
||||
return data
|
||||
},
|
||||
fields: [['', ['attr', 'value', 'protocols']]],
|
||||
fieldsMeta: {
|
||||
attr: {
|
||||
label: '',
|
||||
component: Select2,
|
||||
rules: [Required],
|
||||
el: {
|
||||
url: '',
|
||||
clearable: false,
|
||||
multiple: false,
|
||||
options: resourceTypeOptions
|
||||
},
|
||||
on: {
|
||||
change: ([val], updateForm) => {
|
||||
updateForm({ value: '' })
|
||||
let url
|
||||
switch (val) {
|
||||
case 'platform':
|
||||
url = '/api/v1/assets/platforms/?category=host'
|
||||
break
|
||||
case 'node':
|
||||
url = '/api/v1/assets/nodes/'
|
||||
break
|
||||
case 'domain':
|
||||
url = '/api/v1/assets/domains/'
|
||||
break
|
||||
case 'account_template':
|
||||
url = '/api/v1/accounts/account-templates/'
|
||||
break
|
||||
}
|
||||
if (val !== 'platform') {
|
||||
this.formConfig.fieldsMeta.protocols.el.hidden = true
|
||||
}
|
||||
this.resourceType = val
|
||||
this.formConfig.fieldsMeta.value.el.ajax.url = url
|
||||
}
|
||||
}
|
||||
},
|
||||
value: {
|
||||
label: '',
|
||||
component: Select2,
|
||||
rules: [Required],
|
||||
el: {
|
||||
value: [],
|
||||
ajax: {
|
||||
url: '',
|
||||
clearable: false,
|
||||
transformOption: (item) => {
|
||||
let label
|
||||
switch (this.resourceType) {
|
||||
case 'platform':
|
||||
label = item?.name
|
||||
this.globalProtocols[item.id] = item.protocols
|
||||
this.globalResource[item.id] = label
|
||||
break
|
||||
case 'account_template':
|
||||
label = `${item.name}(${item.username})`
|
||||
this.globalResource[item.id] = label
|
||||
break
|
||||
case 'node':
|
||||
label = item?.full_value
|
||||
this.globalResource[item.id] = label
|
||||
break
|
||||
default:
|
||||
label = item?.name
|
||||
this.globalResource[item.id] = label
|
||||
}
|
||||
return { label: label, value: item.id }
|
||||
}
|
||||
},
|
||||
multiple: false
|
||||
},
|
||||
on: {
|
||||
change: ([val], updateForm) => {
|
||||
if (this.resourceType === 'platform') {
|
||||
this.formConfig.fieldsMeta.protocols.el.choices = this.globalProtocols[val] || []
|
||||
this.formConfig.fieldsMeta.protocols.el.hidden = false
|
||||
} else {
|
||||
this.formConfig.fieldsMeta.protocols.el.hidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
protocols: {
|
||||
label: '',
|
||||
component: ProtocolSelector,
|
||||
el: {
|
||||
hidden: true,
|
||||
settingReadonly: true,
|
||||
choices: []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
tableConfig: {
|
||||
columns: [
|
||||
{ prop: 'attr', label: this.$t('common.ResourceType'), formatter: tableFormatter('resource_type') },
|
||||
{ prop: 'value', label: this.$t('common.Resource'), formatter: tableFormatter('resource', () => { return this.globalResource }) },
|
||||
{ prop: 'protocols', label: this.$t('common.Other'), formatter: tableFormatter('protocols') },
|
||||
{ prop: 'action', label: this.$t('common.Action'), align: 'center', width: '100px', formatter: (row, col, cellValue, index) => {
|
||||
return (
|
||||
<div className='input-button'>
|
||||
<el-button
|
||||
icon='el-icon-minus'
|
||||
size='mini'
|
||||
style={{ 'flexShrink': 0 }}
|
||||
type='danger'
|
||||
onClick={ this.handleDelete(index) }
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
} }
|
||||
],
|
||||
totalData: this.value || [],
|
||||
hasPagination: false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSubmit() {
|
||||
this.$emit('input', this.tableConfig.totalData)
|
||||
},
|
||||
beforeSubmit(data) {
|
||||
let status = true
|
||||
const labelMap = {
|
||||
platform: this.$tc('assets.Platform'), domain: this.$tc('assets.Domain')
|
||||
}
|
||||
this.tableConfig.totalData.map(item => {
|
||||
const iValue = item.value?.id || item.value
|
||||
const iAttr = item.attr?.value || item.attr
|
||||
if (iValue === data.value) {
|
||||
status = false
|
||||
this.$message.error(`${this.$tc('xpack.Cloud.ExistError')}`)
|
||||
} else if (Object.keys(labelMap).indexOf(data?.attr) !== -1 && iAttr === data.attr) {
|
||||
status = false
|
||||
this.$message.error(`${this.$tc('xpack.Cloud.UniqueError')}: ${labelMap[data.attr]}`)
|
||||
}
|
||||
})
|
||||
return status
|
||||
},
|
||||
handleDelete(index) {
|
||||
return () => {
|
||||
const item = this.tableConfig.totalData.splice(index, 1)
|
||||
this.$axios.delete(`/api/v1/xpack/cloud/strategy-actions/${item[0]?.id}/`)
|
||||
this.$message.success(this.$tc('common.deleteSuccessMsg'))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
>>> .el-form-item:nth-child(-n+3) {
|
||||
width: 43.5%;
|
||||
}
|
||||
>>> .el-form-item:last-child {
|
||||
width: 6%;
|
||||
}
|
||||
</style>
|
||||
|
||||
117
src/views/assets/Cloud/Strategy/components/RuleInput.vue
Normal file
117
src/views/assets/Cloud/Strategy/components/RuleInput.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<AttrInput
|
||||
:form-config="formConfig"
|
||||
:table-config="tableConfig"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { AttrInput, Select2 } from '@/components/Form/FormFields'
|
||||
import { Required } from '@/components/Form/DataForm/rules'
|
||||
import { instanceAttrOptions, tableFormatter } from './const'
|
||||
import { attrMatchOptions, strMatchValues } from '@/components/const'
|
||||
|
||||
export default {
|
||||
name: 'RuleInput',
|
||||
components: { AttrInput },
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formConfig: {
|
||||
initial: { attr: '', match: '', value: '' },
|
||||
inline: true,
|
||||
hasSaveContinue: false,
|
||||
submitBtnSize: 'mini',
|
||||
submitBtnText: this.$t('common.Add'),
|
||||
hasReset: false,
|
||||
onSubmit: () => {},
|
||||
submitMethod: () => 'post',
|
||||
getUrl: () => '',
|
||||
fields: [['', ['attr', 'match', 'value']]],
|
||||
fieldsMeta: {
|
||||
attr: {
|
||||
label: '',
|
||||
component: Select2,
|
||||
rules: [Required],
|
||||
el: {
|
||||
value: [],
|
||||
multiple: false,
|
||||
clearable: false,
|
||||
options: instanceAttrOptions
|
||||
}
|
||||
},
|
||||
match: {
|
||||
label: '',
|
||||
component: Select2,
|
||||
rules: [Required],
|
||||
el: {
|
||||
value: [],
|
||||
multiple: false,
|
||||
clearable: false,
|
||||
options: attrMatchOptions.filter((option) => {
|
||||
if (strMatchValues.indexOf(option.value) !== -1 && option.value !== 'in') {
|
||||
return option
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
value: {
|
||||
component: 'el-input',
|
||||
rules: [Required]
|
||||
}
|
||||
}
|
||||
},
|
||||
tableConfig: {
|
||||
columns: [
|
||||
{ prop: 'attr', label: this.$t('common.AttrName'), formatter: tableFormatter('attr') },
|
||||
{ prop: 'match', label: this.$t('common.Match'), formatter: tableFormatter('match') },
|
||||
{ prop: 'value', label: this.$t('common.AttrValue'), formatter: tableFormatter('value') },
|
||||
{ prop: 'action', label: this.$t('common.Action'), align: 'center', width: '100px', formatter: (row, col, cellValue, index) => {
|
||||
return (
|
||||
<div className='input-button'>
|
||||
<el-button
|
||||
icon='el-icon-minus'
|
||||
size='mini'
|
||||
style={{ 'flexShrink': 0 }}
|
||||
type='danger'
|
||||
onClick={ this.handleDelete(index) }
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
} }
|
||||
],
|
||||
totalData: this.value || [],
|
||||
hasPagination: false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSubmit() {
|
||||
this.$emit('input', this.tableConfig.totalData)
|
||||
},
|
||||
handleDelete(index) {
|
||||
return () => {
|
||||
const item = this.tableConfig.totalData.splice(index, 1)
|
||||
this.$axios.delete(`/api/v1/xpack/cloud/strategy-rules/${item[0]?.id}/`)
|
||||
this.$message.success(this.$tc('common.deleteSuccessMsg'))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
>>> .el-form-item:nth-child(-n+4) {
|
||||
width: 29%;
|
||||
}
|
||||
>>> .el-form-item:last-child {
|
||||
width: 6%;
|
||||
}
|
||||
</style>
|
||||
|
||||
44
src/views/assets/Cloud/Strategy/components/const.js
Normal file
44
src/views/assets/Cloud/Strategy/components/const.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import i18n from '@/i18n/i18n'
|
||||
import { attrMatchOptions } from '@/components/const'
|
||||
|
||||
export const resourceTypeOptions = [
|
||||
{ label: i18n.t('assets.Platform'), value: 'platform' },
|
||||
{ label: i18n.t('assets.Node'), value: 'node' },
|
||||
{ label: i18n.t('assets.Domain'), value: 'domain' },
|
||||
{ label: i18n.t('accounts.AccountTemplate'), value: 'account_template' }
|
||||
]
|
||||
|
||||
export const instanceAttrOptions = [
|
||||
{ label: i18n.t('xpack.Cloud.InstanceName'), value: 'instance_name' },
|
||||
{ label: i18n.t('xpack.Cloud.InstancePlatformName'), value: 'instance_platform' },
|
||||
{ label: i18n.t('xpack.Cloud.InstanceAddress'), value: 'instance_address' }
|
||||
]
|
||||
|
||||
export const tableFormatter = (colName, getResourceLabel) => {
|
||||
return (row, col, cellValue) => {
|
||||
const globalResource = {}
|
||||
const value = cellValue
|
||||
if (value?.label) { return value.label }
|
||||
switch (colName) {
|
||||
case 'attr':
|
||||
return instanceAttrOptions.find(attr => attr.value === value)?.label || value
|
||||
case 'resource_type':
|
||||
return resourceTypeOptions.find(attr => attr.value === value)?.label || value
|
||||
case 'match':
|
||||
return attrMatchOptions.find(opt => opt.value === value).label || value
|
||||
case 'value':
|
||||
return Array.isArray(value) ? value.join(', ') : value
|
||||
case 'resource':
|
||||
if (typeof getResourceLabel === 'function') {
|
||||
Object.assign(globalResource, getResourceLabel())
|
||||
}
|
||||
return globalResource[value] || value
|
||||
case 'protocols':
|
||||
return Array.isArray(value) ? value.map(p => { return `${p.name}/${p.port}` }).join(', ') : ''
|
||||
case 'count':
|
||||
return value?.length || 0
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
import { GenericCreateUpdatePage } from '@/layout/components'
|
||||
import { CronTab, Select2 } from '@/components'
|
||||
import rules from '@/components/Form/DataForm/rules'
|
||||
import ProtocolSelector from '@/components/Form/FormFields/ProtocolSelector'
|
||||
import SyncInstanceTaskStrategy from './components/SyncInstanceTaskStrategy/index'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -24,7 +24,8 @@ export default {
|
||||
fields: [
|
||||
[this.$t('common.Basic'), ['name']],
|
||||
[this.$t('xpack.Cloud.CloudSource'), ['account', 'regions']],
|
||||
[this.$t('xpack.Cloud.SaveSetting'), ['hostname_strategy', 'node', 'protocols', 'ip_network_segment_group', 'sync_ip_type', 'is_always_update']],
|
||||
[this.$t('xpack.Cloud.SaveSetting'), ['hostname_strategy', 'ip_network_segment_group', 'sync_ip_type', 'is_always_update']],
|
||||
[this.$t('common.Strategy'), ['strategy']],
|
||||
[this.$t('xpack.Timer'), ['is_periodic', 'crontab', 'interval']],
|
||||
[this.$t('common.Other'), ['comment']]
|
||||
],
|
||||
@@ -49,31 +50,6 @@ export default {
|
||||
rules: [rules.RequiredChange],
|
||||
helpText: this.$t('xpack.Cloud.HostnameStrategy')
|
||||
},
|
||||
node: {
|
||||
rules: [rules.RequiredChange],
|
||||
el: {
|
||||
multiple: false,
|
||||
value: [],
|
||||
ajax: {
|
||||
url: '/api/v1/assets/nodes/',
|
||||
transformOption: (item) => {
|
||||
return { label: item.full_value, value: item.id }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
protocols: {
|
||||
component: ProtocolSelector,
|
||||
el: {
|
||||
showSetting: () => { return false },
|
||||
choices: [
|
||||
{ 'name': 'ssh', 'port': 22, 'primary': true, 'default': false, 'required': false },
|
||||
{ 'name': 'telnet', 'port': 23, 'primary': false, 'default': false, 'required': false },
|
||||
{ 'name': 'vnc', 'port': 5900, 'primary': false, 'default': false, 'required': false },
|
||||
{ 'name': 'rdp', 'port': 3389, 'primary': false, 'default': false, 'required': false }
|
||||
]
|
||||
}
|
||||
},
|
||||
is_always_update: {
|
||||
type: 'switch',
|
||||
label: this.$t('xpack.Cloud.IsAlwaysUpdate'),
|
||||
@@ -88,7 +64,7 @@ export default {
|
||||
ajax: {
|
||||
url: '/api/v1/xpack/cloud/regions/',
|
||||
processResults(data) {
|
||||
const results = data.regions.map((item) => {
|
||||
const results = data.regions?.map((item) => {
|
||||
return { label: item.name, value: item.id }
|
||||
})
|
||||
const more = !!data.next
|
||||
@@ -114,6 +90,10 @@ export default {
|
||||
return formValue.is_periodic === false
|
||||
},
|
||||
helpText: this.$t('xpack.HelpText.IntervalOfCreateUpdatePage')
|
||||
},
|
||||
strategy: {
|
||||
label: this.$t('common.Strategy'),
|
||||
component: SyncInstanceTaskStrategy
|
||||
}
|
||||
},
|
||||
updateSuccessNextRoute: { name: 'CloudCenter' },
|
||||
@@ -123,20 +103,15 @@ export default {
|
||||
const [name, port] = i.split('/')
|
||||
return { name, port }
|
||||
})
|
||||
|
||||
return formValue
|
||||
},
|
||||
cleanFormValue(value) {
|
||||
let protocols = ''
|
||||
const ipNetworkSegments = value.ip_network_segment_group
|
||||
const strategy = value?.strategy || []
|
||||
if (!Array.isArray(ipNetworkSegments)) {
|
||||
value.ip_network_segment_group = ipNetworkSegments ? ipNetworkSegments.split(',') : []
|
||||
}
|
||||
if (value.protocols.length > 0) {
|
||||
protocols = value.protocols.map(i => (i.name + '/' + i.port)).join(' ')
|
||||
}
|
||||
value.protocols = protocols
|
||||
|
||||
value.strategy = strategy.map(item => { return item.id })
|
||||
return value
|
||||
},
|
||||
onPerformError(error, method, vm) {
|
||||
|
||||
@@ -5,12 +5,20 @@
|
||||
</el-col>
|
||||
<el-col :md="10" :sm="24">
|
||||
<QuickActions :actions="quickActions" type="primary" />
|
||||
<RelationCard
|
||||
ref="StrategyRelation"
|
||||
v-perms="'xpack.change_strategy'"
|
||||
style="margin-top: 15px"
|
||||
type="info"
|
||||
v-bind="strategyRelationConfig"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AutoDetailCard from '@/components/Cards/DetailCard/auto'
|
||||
import RelationCard from '@/components/Cards/RelationCard'
|
||||
import QuickActions from '@/components/QuickActions'
|
||||
import { toSafeLocalDateStr } from '@/utils/common'
|
||||
import { openTaskPage } from '@/utils/jms'
|
||||
@@ -19,7 +27,8 @@ export default {
|
||||
name: 'Detail',
|
||||
components: {
|
||||
AutoDetailCard,
|
||||
QuickActions
|
||||
QuickActions,
|
||||
RelationCard
|
||||
},
|
||||
props: {
|
||||
object: {
|
||||
@@ -47,14 +56,57 @@ export default {
|
||||
)
|
||||
}.bind(this)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: this.$t('common.Strategy'),
|
||||
attrs: {
|
||||
type: 'primary',
|
||||
label: this.$t('xpack.Execute'),
|
||||
disabled: !this.$hasPerm('xpack.add_syncinstancetaskexecution')
|
||||
}
|
||||
}
|
||||
],
|
||||
strategyRelationConfig: {
|
||||
icon: 'fa-info',
|
||||
title: this.$t('common.Strategy'),
|
||||
objectsAjax: {
|
||||
url: '/api/v1/xpack/cloud/strategies/',
|
||||
transformOption: (item) => {
|
||||
return { label: item.name, value: item.id }
|
||||
}
|
||||
},
|
||||
hasObjectsId: this.object.strategy?.map(i => i.id) || [],
|
||||
performAdd: (items) => {
|
||||
const newData = []
|
||||
const value = this.$refs.StrategyRelation.iHasObjects
|
||||
value.map(v => {
|
||||
newData.push(v.value)
|
||||
})
|
||||
const relationUrl = `/api/v1/xpack/cloud/sync-instance-tasks/${this.object.id}/`
|
||||
items.map(v => {
|
||||
newData.push(v.value)
|
||||
})
|
||||
return this.$axios.patch(relationUrl, { strategy: newData })
|
||||
},
|
||||
performDelete: (item) => {
|
||||
const itemId = item.value
|
||||
const newData = []
|
||||
const value = this.$refs.StrategyRelation.iHasObjects
|
||||
value.map(v => {
|
||||
if (v.value !== itemId) {
|
||||
newData.push(v.value)
|
||||
}
|
||||
})
|
||||
const relationUrl = `/api/v1/xpack/cloud/sync-instance-tasks/${this.object.id}/`
|
||||
return this.$axios.patch(relationUrl, { strategy: newData })
|
||||
}
|
||||
},
|
||||
url: `/api/v1/xpack/cloud/accounts/${this.object.id}`,
|
||||
detailFields: [
|
||||
'name', 'account_display', 'node_display',
|
||||
{
|
||||
key: this.$t('assets.Protocols'),
|
||||
value: this.object.protocols
|
||||
key: this.$t('common.Strategy'),
|
||||
value: this.object.strategy?.map(item => item.name).join(', ')
|
||||
},
|
||||
{
|
||||
key: this.$t('xpack.Cloud.IPNetworkSegment'),
|
||||
@@ -87,7 +139,6 @@ export default {
|
||||
computed: {
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:destroy-on-close="true"
|
||||
:show-buttons="false"
|
||||
:close-on-click-modal="false"
|
||||
:title="$tc('common.Strategy')"
|
||||
width="80%"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<GenericCreateUpdateForm v-bind="$data" />
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GenericCreateUpdateForm from '@/layout/components/GenericCreateUpdateForm'
|
||||
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'
|
||||
|
||||
export default {
|
||||
name: 'AttrDialog',
|
||||
components: { Dialog, GenericCreateUpdateForm },
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => ({ name: '', strategy_rules: [], strategy_actions: [] })
|
||||
},
|
||||
tableConfig: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
object: this.getObject(),
|
||||
fields: [
|
||||
[this.$t('common.BasicSetting'), ['name', 'priority']],
|
||||
[this.$t('common.RuleSetting'), ['strategy_rules']],
|
||||
[this.$t('common.ActionSetting'), ['strategy_actions']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
strategy_rules: {
|
||||
label: this.$t('common.Rule'),
|
||||
component: RuleInput
|
||||
},
|
||||
strategy_actions: {
|
||||
label: this.$t('common.Action'),
|
||||
component: ActionInput
|
||||
}
|
||||
},
|
||||
hasSaveContinue: false,
|
||||
onPerformSuccess: (instance) => {
|
||||
const index = this.tableConfig.totalData.findIndex(x => x.id === instance.id)
|
||||
if (index !== -1) {
|
||||
this.tableConfig.totalData.splice(index, 1, instance)
|
||||
} else {
|
||||
this.tableConfig.totalData.push(instance)
|
||||
}
|
||||
this.$emit('confirm')
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
getUrl: () => {
|
||||
const url = '/api/v1/xpack/cloud/strategies/'
|
||||
return this.object?.id ? `${url}${this.object.id}/` : url
|
||||
},
|
||||
submitMethod: () => {
|
||||
return this.object?.id ? 'put' : 'post'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getObject() {
|
||||
if (this.value?.id) {
|
||||
return {
|
||||
id: this.value.id, name: this.value.name,
|
||||
strategy_rules: this.value.strategy_rules, strategy_actions: this.value.strategy_actions
|
||||
}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
handleAttrDelete(type, index) {
|
||||
return () => {
|
||||
const item = this.fieldsMeta[type].el.tableConfig.totalData.splice(index, 1)
|
||||
if (item[0]?.id) {
|
||||
this.$axios.delete(`/api/v1/xpack/cloud/${type}/${item[0]?.id}/`)
|
||||
}
|
||||
this.$message.success(this.$tc('common.deleteSuccessMsg'))
|
||||
this.object[type] = this.fieldsMeta[type].el.tableConfig.totalData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-form ::v-deep .el-form {
|
||||
margin-top: -15px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div>
|
||||
<Select2 v-bind="select2" @change="handleInput" />
|
||||
<DataTable :config="tableConfig" />
|
||||
<AttrDialog
|
||||
v-if="visible"
|
||||
:value="attrValue"
|
||||
:visible.sync="visible"
|
||||
:table-config="tableConfig"
|
||||
@confirm="onAttrDialogConfirm"
|
||||
/>
|
||||
<el-button type="primary" size="mini" @click="handleCreate">{{ this.$t('common.New') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DataTable from '@/components/Table/DataTable/index.vue'
|
||||
import Select2 from '@/components/Form/FormFields/Select2.vue'
|
||||
import { tableFormatter } from '@/views/assets/Cloud/Strategy/components/const'
|
||||
import AttrDialog from './AttrDialog.vue'
|
||||
|
||||
export default {
|
||||
name: 'SyncInstanceTaskStrategy',
|
||||
components: { DataTable, AttrDialog, Select2 },
|
||||
props: {
|
||||
totalData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
attrValue: { name: '', priority: 50, strategy_rules: [], strategy_actions: [] },
|
||||
strategy: {},
|
||||
visible: false,
|
||||
tableConfig: {
|
||||
columns: [
|
||||
{ prop: 'name', label: this.$t('common.PolicyName') },
|
||||
{ prop: 'priority', label: this.$t('acl.priority') },
|
||||
{ prop: 'strategy_rules', label: this.$t('common.RuleCount'), formatter: tableFormatter('count') },
|
||||
{ prop: 'strategy_actions', label: this.$t('common.ActionCount'), formatter: tableFormatter('count') },
|
||||
{ prop: 'action', label: this.$t('common.Action'), align: 'center', width: '100px', formatter: (row, col, cellValue, index) => {
|
||||
return (
|
||||
<div className='input-button'>
|
||||
<el-button
|
||||
icon='el-icon-edit'
|
||||
size='mini'
|
||||
style={{ 'flexShrink': 0 }}
|
||||
type='primary'
|
||||
onClick={this.handleAttrEdit({ row, col, cellValue, index })}
|
||||
/>
|
||||
<el-button
|
||||
icon='el-icon-minus'
|
||||
size='mini'
|
||||
style={{ 'flexShrink': 0 }}
|
||||
type='danger'
|
||||
onClick={this.handleAttrDelete({ row, col, cellValue, index })}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
} }
|
||||
],
|
||||
totalData: this.value,
|
||||
hasPagination: false
|
||||
},
|
||||
select2: {
|
||||
url: '/api/v1/xpack/cloud/strategies/',
|
||||
multiple: false,
|
||||
ajax: {
|
||||
transformOption: (item) => {
|
||||
this.strategy[item.id] = {
|
||||
name: item.name, priority: item.priority, strategy_rules: item.strategy_rules, strategy_actions: item.strategy_actions
|
||||
}
|
||||
return { label: item.name, value: item.id }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleInput(value) {
|
||||
let status = true
|
||||
const totalData = this.tableConfig.totalData
|
||||
const data = this.strategy[value]
|
||||
for (let i = 0; i < totalData.length; i++) {
|
||||
if (totalData[i].id === value) {
|
||||
status = false
|
||||
this.$message.error(`${this.$tc('xpack.Cloud.ExistError')}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (status) {
|
||||
data['id'] = value
|
||||
this.tableConfig.totalData.push(data)
|
||||
}
|
||||
},
|
||||
handleCreate() {
|
||||
this.attrValue = { name: '', priority: 50, strategy_rules: [], strategy_actions: [] }
|
||||
this.visible = true
|
||||
},
|
||||
onAttrDialogConfirm() {
|
||||
this.$emit('input', this.tableConfig.totalData)
|
||||
},
|
||||
handleAttrDelete({ index }) {
|
||||
return () => {
|
||||
this.tableConfig.totalData.splice(index, 1)
|
||||
}
|
||||
},
|
||||
handleAttrEdit({ row, index }) {
|
||||
return () => {
|
||||
this.$axios.get(`/api/v1/xpack/cloud/strategies/${row?.id}/`).then((data) => {
|
||||
this.attrValue = data
|
||||
this.visible = true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -127,6 +127,6 @@ export const ACCOUNT_PROVIDER_ATTRS_MAP = {
|
||||
[lan]: {
|
||||
name: lan,
|
||||
title: i18n.t('xpack.Cloud.LAN'),
|
||||
attrs: ['ip_group', 'test_port', 'test_timeout', 'platform', 'hostname_prefix']
|
||||
attrs: ['ip_group', 'test_port', 'test_timeout', 'hostname_prefix']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,17 @@ export default {
|
||||
activeMenu: 'SyncInstanceTaskList',
|
||||
submenu: [
|
||||
{
|
||||
title: this.$t('xpack.Cloud.SyncInstanceTaskList'),
|
||||
title: this.$t('common.SyncTask'),
|
||||
name: 'SyncInstanceTaskList',
|
||||
hidden: () => !this.$hasPerm('xpack.view_syncinstancetask'),
|
||||
component: () => import('@/views/assets/Cloud/SyncInstanceTask/SyncInstanceTaskList.vue')
|
||||
},
|
||||
{
|
||||
title: this.$t('xpack.Cloud.SyncStrategy'),
|
||||
name: 'StrategyList',
|
||||
hidden: () => !this.$hasPerm('xpack.view_strategy'),
|
||||
component: () => import('@/views/assets/Cloud/Strategy/StrategyList.vue')
|
||||
},
|
||||
{
|
||||
title: this.$t('xpack.Cloud.AccountList'),
|
||||
name: 'AccountList',
|
||||
|
||||
Reference in New Issue
Block a user