Deploy.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. <template>
  2. <base-dialog @cancel="cancelDialog">
  3. <div slot="header">{{params.title}}</div>
  4. <div slot="body">
  5. <dialog-selected-tips :name="$t('dictionary.loadbalanceragent')" :count="params.data.length" :action="$t('network.text_41')" />
  6. <dialog-table :data="params.data" :columns="params.columns.slice(0, 2)" />
  7. <a-divider orientation="left">{{$t('network.text_42')}}</a-divider>
  8. <a-form
  9. :form="form.fc">
  10. <a-form-item :label="$t('network.text_43')" v-bind="formItemLayout">
  11. <a-input v-decorator="decorators.proj" disabled :placeholder="$t('network.text_44')" />
  12. </a-form-item>
  13. <a-form-item v-bind="formItemLayout">
  14. <template slot="label">
  15. <span>{{$t('network.text_45')}}<a-tooltip placement="topLeft">
  16. <template slot="title">
  17. <div>{{$t('network.text_46')}}<br />climc user-create lbagent --password XXX --system-account
  18. <br />climc project-add-user system lbagent admin</div>
  19. </template>
  20. <a-icon type="info-circle" />
  21. </a-tooltip>
  22. </span>
  23. </template>
  24. <base-select
  25. v-decorator="decorators.user"
  26. resource="users"
  27. version="v1"
  28. :params="userParams"
  29. :mapper="userMapper"
  30. :label-format="labelFormat"
  31. idKey="name"
  32. :select-props="{ placeholder: $t('network.text_47') }"
  33. style="width: 320px" />
  34. </a-form-item>
  35. <a-form-item :label="$t('network.text_48')" v-bind="formItemLayout">
  36. <a-input v-decorator="decorators.pass" type="password" :placeholder="$t('network.text_49')" />
  37. </a-form-item>
  38. <a-divider orientation="left">{{$t('network.text_50')}}</a-divider>
  39. <a-form-item v-bind="formItemLayout">
  40. <template slot="label">
  41. <span>{{$t('network.text_51')}}<a-tooltip placement="topLeft" :overlayStyle="{'max-width': '400px'}">
  42. <div slot="title" style="width: 400px">{{$t('network.text_52')}}<br />{{$t('network.text_53')}}<br />{{$t('network.text_54')}}<br />{{$t('network.text_55')}}<div class="pl-2">{{$t('network.text_56')}}</div>
  43. <div class="pl-2">{{$t('network.text_57')}}</div>
  44. <div class="pl-2">{{$t('network.text_58')}}</div>
  45. </div>
  46. <a-icon type="info-circle" />
  47. </a-tooltip>
  48. </span>
  49. </template>
  50. <a-radio-group v-decorator="decorators.hostName" @change="hostChange">
  51. <a-radio-button v-for="(item, index) in nameServers" :value="item.value" :key="index">{{item.label}}</a-radio-button>
  52. </a-radio-group>
  53. <a-form-item v-if="this.hostName === ''">
  54. <a-input v-decorator="decorators.ip" :placeholder="$t('network.text_59')" />
  55. </a-form-item>
  56. <a-form-item class="mb-0" v-if="this.hostName === 'server'">
  57. <base-select
  58. v-decorator="decorators.server"
  59. resource="servers"
  60. style="width: 320px"
  61. :mapper="serverMapper"
  62. :params="serversParams"
  63. :label-format="labelFormat"
  64. :select-props="{ placeholder: $t('network.text_60') }"
  65. remote
  66. :remote-fn="q => ({ search: q })"
  67. @change="handleServerChange"
  68. @update:resList="serversSuccess" />
  69. <a-alert v-if="isOut" :message="$t('network.text_61')" banner />
  70. </a-form-item>
  71. <a-form-item v-if="this.hostName === 'host'">
  72. <base-select
  73. v-decorator="decorators.host"
  74. resource="hosts"
  75. :params="hostsParams"
  76. :label-format="labelFormat"
  77. remote
  78. :remote-fn="q => ({ search: q })"
  79. :select-props="{ placeholder: $t('network.text_62') }"
  80. style="width: 320px" />
  81. </a-form-item>
  82. </a-form-item>
  83. <a-form-item :label="$t('network.text_63')" v-bind="formItemLayout">
  84. <a-input v-decorator="decorators.repo_base_url" :placeholder="$t('network.text_64')" />
  85. </a-form-item>
  86. <a-form-item v-bind="tailFormItemLayout">
  87. <a-checkbox v-decorator="decorators.repo_sslverify">{{$t('network.text_65')}}</a-checkbox>
  88. </a-form-item>
  89. </a-form>
  90. <a-alert v-if="isRunning">
  91. <div slot="message">{{$t('network.text_66')}}<a @click="openAsbook(ansiblePlaybookId)">{{$t('network.text_67')}}</a>
  92. </div>
  93. </a-alert>
  94. </div>
  95. <div slot="footer">
  96. <a-button type="primary" @click="handleConfirm" :loading="loading" :disabled="isRunning">{{ $t('dialog.ok') }}</a-button>
  97. <a-button @click="cancelDialog">{{ $t('dialog.cancel') }}</a-button>
  98. </div>
  99. </base-dialog>
  100. </template>
  101. <script>
  102. import { mapGetters } from 'vuex'
  103. import Ansible from '../controls/ansible'
  104. import DialogMixin from '@/mixins/dialog'
  105. import WindowsMixin from '@/mixins/windows'
  106. import { findPlatform } from '@/utils/common/hypervisor'
  107. export default {
  108. name: 'AgentDeployDialog',
  109. mixins: [DialogMixin, WindowsMixin],
  110. data () {
  111. return {
  112. loading: false,
  113. isRunning: false,
  114. isDeleteServer: false,
  115. form: {
  116. fc: this.$form.createForm(this),
  117. },
  118. decorators: {
  119. proj: [
  120. 'proj',
  121. {
  122. validateTrigger: ['blur', 'change'],
  123. initialValue: 'system',
  124. rules: [
  125. { required: true, message: this.$t('network.text_68') },
  126. ],
  127. },
  128. ],
  129. user: [
  130. 'user',
  131. {
  132. validateTrigger: ['blur', 'change'],
  133. rules: [
  134. { required: true, message: this.$t('network.text_69') },
  135. ],
  136. },
  137. ],
  138. pass: [
  139. 'pass',
  140. {
  141. validateTrigger: ['blur', 'change'],
  142. rules: [
  143. { required: true, message: this.$t('network.text_49') },
  144. ],
  145. },
  146. ],
  147. hostName: [
  148. 'hostName',
  149. {
  150. initialValue: 'server',
  151. },
  152. ],
  153. ip: [
  154. 'ip',
  155. {
  156. validateTrigger: ['blur'],
  157. rules: [
  158. { required: true, message: this.$t('network.text_59') },
  159. ],
  160. },
  161. ],
  162. host: [
  163. 'host',
  164. {
  165. validateFirst: true,
  166. rules: [
  167. { required: true, message: this.$t('network.text_62') },
  168. ],
  169. },
  170. ],
  171. server: [
  172. 'server',
  173. {
  174. validateFirst: true,
  175. validateTrigger: ['blur', 'change'],
  176. rules: [
  177. { required: true, message: this.$t('network.text_60') },
  178. { validator: this.serverOldCheck },
  179. ],
  180. },
  181. ],
  182. deploy_method: [
  183. 'deploy_method',
  184. ],
  185. repo_base_url: [
  186. 'repo_base_url',
  187. {
  188. validateFirst: true,
  189. validateTrigger: ['blur'],
  190. // initialValue: `${location.protocol}//${location.host}/yumrepo`,
  191. rules: [
  192. { required: true, message: this.$t('network.text_64') },
  193. { validator: this.$validate('url') },
  194. ],
  195. },
  196. ],
  197. repo_sslverify: [
  198. 'repo_sslverify',
  199. {
  200. valuePropName: 'checked',
  201. },
  202. ],
  203. },
  204. formItemLayout: {
  205. wrapperCol: {
  206. span: 18,
  207. },
  208. labelCol: {
  209. span: 6,
  210. },
  211. },
  212. tailFormItemLayout: {
  213. wrapperCol: {
  214. sm: {
  215. span: 18,
  216. offset: 6,
  217. },
  218. },
  219. },
  220. userParams: {
  221. system: true,
  222. tenant: 'system',
  223. },
  224. nameServers: [
  225. { label: this.$t('dictionary.server'), value: 'server' },
  226. { label: this.$t('network.text_70'), value: 'host' },
  227. { label: this.$t('network.text_71'), value: '' },
  228. ],
  229. hostName: 'server',
  230. deploymentHost: '',
  231. deployMethod: '',
  232. ansiblePlaybookId: '',
  233. userData: [],
  234. }
  235. },
  236. computed: {
  237. ...mapGetters(['isAdminMode', 'scope', 'userInfo']),
  238. isOut () {
  239. const item = this.params.data && this.params.data.length && this.params.data[0]
  240. if (item && item.hb_last_seen) {
  241. const s = this.$moment().diff(item.hb_last_seen, 'seconds')
  242. if (s < 60) {
  243. return true
  244. }
  245. }
  246. return false
  247. },
  248. serversParams () {
  249. return {
  250. scope: this.$store.getters.scope,
  251. status: 'running',
  252. cloud_env: 'private_or_onpremise',
  253. }
  254. },
  255. hostsParams () {
  256. return {
  257. scope: this.$store.getters.scope,
  258. status: 'running',
  259. }
  260. },
  261. },
  262. created () {
  263. this.backfill()
  264. this.form.fc.getFieldDecorator('deploy_method', { preserve: true, initialValue: 'yum' })
  265. },
  266. methods: {
  267. getUpdateInfo () {
  268. new this.$Manager('updates', 'v1').list({
  269. params: {
  270. status: true,
  271. },
  272. }).then(res => {
  273. if (res.data.data && res.data.data.length) {
  274. const updateInfo = res.data.data[0]
  275. const v = updateInfo.current_version.slice(1).split('.')
  276. const majorV = `${v[0]}.${v[1]}`
  277. let baseURL = `https://iso.yunion.cn/${majorV}/rpms`
  278. if (updateInfo && updateInfo.current_version) {
  279. if (v.length >= 3 && v[0] === '3') {
  280. if (parseInt(v[1]) >= 9) {
  281. baseURL = `https://yunioniso.oss-cn-beijing.aliyuncs.com/iso/${majorV}/rpms`
  282. }
  283. }
  284. this.form.fc.setFieldsValue({
  285. repo_base_url: baseURL,
  286. })
  287. }
  288. }
  289. })
  290. },
  291. serversSuccess (list = []) {
  292. const { deployment } = this.params.data[0] || {}
  293. const { getFieldValue, validateFields } = this.form.fc
  294. if (getFieldValue('hostName') === 'server' && deployment && deployment.host) {
  295. const [, id] = deployment.host.split(':')
  296. this.isDeleteServer = !list.find(item => {
  297. return item.id === id || item.name === id
  298. })
  299. validateFields(['server'])
  300. }
  301. },
  302. handleServerChange () {
  303. this.isDeleteServer = false
  304. this.form.fc.validateFields(['server'])
  305. },
  306. serverMapper (retList) {
  307. return retList.filter(item => findPlatform(item.brand) !== 'public')
  308. },
  309. userMapper (data) {
  310. data = data.filter(item => item.is_system_account)
  311. this.userData = data
  312. return data
  313. },
  314. labelFormat (item) {
  315. if (item.ips) {
  316. return `${item.name}(${item.ips})`
  317. }
  318. return item.name
  319. },
  320. hostChange (e) {
  321. this.hostName = e.target.value
  322. },
  323. deployMethodChange (e) {
  324. this.deployMethod = e.target.value
  325. },
  326. // 更改云主机时与旧的云主机校验
  327. serverOldCheck (rule, value, callback) {
  328. if (this.isDeleteServer) {
  329. return callback(new Error(this.$t('network.text_72')))
  330. }
  331. return callback()
  332. },
  333. backfill () {
  334. const { deployment } = this.params.data[0] || {}
  335. if (deployment && deployment.ansible_playbook) {
  336. this.ansiblePlaybookId = deployment.ansible_playbook
  337. new Ansible(deployment.ansible_playbook)
  338. .get()
  339. .then(({ data }) => {
  340. if (data && data.playbook && data.playbook.inventory && data.playbook.inventory.hosts && data.playbook.inventory.hosts.length > 0) {
  341. const { vars } = data.playbook.inventory.hosts[0] || {}
  342. if (vars) {
  343. this.form.fc.setFieldsValue({
  344. user: vars.user,
  345. pass: vars.pass,
  346. deploy_method: vars.repo_base_url ? 'yum' : 'copy',
  347. })
  348. this.form.fc.getFieldDecorator('repo_base_url', {
  349. preserve: true,
  350. initialValue: vars.repo_base_url,
  351. })
  352. this.deployMethod = vars.repo_base_url ? 'yum' : 'copy'
  353. this.$nextTick(() => {
  354. this.form.fc.setFieldsValue({ repo_sslverify: !!parseInt(vars.repo_sslverify) })
  355. })
  356. }
  357. }
  358. // 是否为部署中
  359. this.isRunning = data.status === 'running'
  360. })
  361. /* 处理云主机start */
  362. const arrHost = deployment.host.split(':') || []
  363. if (arrHost && arrHost.length === 2) {
  364. const obj = {
  365. hostName: arrHost[0],
  366. }
  367. obj[arrHost[0]] = arrHost[1]
  368. this.$nextTick(() => {
  369. this.form.fc.setFieldsValue(obj)
  370. })
  371. this.deploymentHost = {
  372. hostName: arrHost[0],
  373. }
  374. this.deploymentHost[arrHost[0]] = arrHost[1]
  375. this.hostName = arrHost[0]
  376. } else {
  377. // 外部主机
  378. this.$nextTick(() => {
  379. this.form.fc.setFieldsValue({
  380. hostName: '',
  381. ip: arrHost[0],
  382. })
  383. })
  384. this.deploymentHost = {
  385. hostName: '',
  386. ip: arrHost[0],
  387. }
  388. this.hostName = ''
  389. }
  390. /* 处理云主机end */
  391. } else {
  392. this.getUpdateInfo()
  393. }
  394. },
  395. doCreate (data) {
  396. const { id } = this.params.data[0]
  397. return new this.$Manager('loadbalanceragents').performAction({
  398. id,
  399. action: 'deploy',
  400. data,
  401. })
  402. },
  403. openAsbook (ansiblePlaybookId) {
  404. this.createDialog('AnsibleplaybookDialog', {
  405. title: this.$t('network.text_73'),
  406. ansiblePlaybookId,
  407. })
  408. },
  409. async handleConfirm () {
  410. this.loading = true
  411. try {
  412. const values = await this.form.fc.validateFields()
  413. values.repo_sslverify = values.repo_sslverify ? 1 : 0
  414. const { hostName, ip, host, proj, pass, server, user } = values
  415. let name = ''
  416. if (hostName && hostName === 'host') {
  417. name = `${hostName}:${host}`
  418. } else if (hostName && hostName === 'server') {
  419. name = `${hostName}:${server}`
  420. } else {
  421. name = ip
  422. }
  423. const params = {
  424. host: {
  425. name,
  426. vars: {
  427. host: host,
  428. pass,
  429. proj,
  430. repo_base_url: values.repo_base_url,
  431. repo_sslverify: values.repo_sslverify,
  432. server,
  433. user,
  434. },
  435. },
  436. deploy_method: values.deploy_method,
  437. }
  438. const { data } = await this.doCreate(params)
  439. this.loading = false
  440. this.cancelDialog()
  441. if (data) {
  442. this.openAsbook(data.deployment.ansible_playbook || this.ansiblePlaybookId)
  443. }
  444. this.params.refresh()
  445. } catch (error) {
  446. this.loading = false
  447. }
  448. },
  449. },
  450. }
  451. </script>