index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. <template>
  2. <div>
  3. <a-card class="mb-2 card" v-for="(item, i) in serverConfigList" :key="item.key">
  4. <a-button v-if="i !== 0" @click="decrease(item.key, i)" type="link" size="small" class="error-color position-absolute" style="right: 10px; top: 10px;">{{ $t('common.delete') }}</a-button>
  5. <slot name="hypervisor" :formItemLayout="formItemLayout" :i="item.key" />
  6. <!-- <a-form-item label="CPU" v-bind="formItemLayout">
  7. <a-input-number v-decorator="decorator.vcpu_count(item.key)" :formatter="value => $t('k8s.text_119', [value])" :parser="v => parser(v, $t('k8s.text_100'))" :min="4" :max="32" />
  8. </a-form-item>
  9. <a-form-item :label="$t('k8s.text_101')" v-bind="formItemLayout">
  10. <a-input-number v-decorator="decorator.vmem_size(item.key)" :formatter="value => `${value}G`" :parser="v => parser(v, 'G')" :min="1" :max="128" />
  11. </a-form-item> -->
  12. <!-- <a-form-item :label="$t('network.text_24')" v-bind="formItemLayout">
  13. <a-select
  14. v-decorator="decorator.zone(item.key)"
  15. :placeholder="$t('network.text_287')"
  16. @change="handleZoneChanage(i, $event)">
  17. <a-select-option
  18. v-for="zone in getZones(i)"
  19. :key="zone.id"
  20. :value="zone.id">{{zone.name}}</a-select-option>
  21. </a-select>
  22. </a-form-item>-->
  23. <a-form-item :label="$t('k8s.text_121')" v-bind="formItemLayout" required>
  24. <a-row :gutter="8">
  25. <a-col :span="24">
  26. <a-form-item
  27. :wrapperCol="{ span: 24 }"
  28. style="min-width: 200px;"
  29. class="w-100 mb-0 mr-1">
  30. <base-select
  31. class="w-100"
  32. v-decorator="decorator.network(item.key)"
  33. resource="networks"
  34. remote
  35. is-default-select
  36. @change="handleNetworkChange(i, $event)"
  37. :item.sync="item.network"
  38. :need-params="true"
  39. :params="getNetworkParams(item.key)"
  40. :mapper="networkResourceMapper"
  41. :labelFormat="networkLabelFormat"
  42. :select-props="{ allowClear: true, placeholder: $t('k8s.text_122') }" />
  43. </a-form-item>
  44. </a-col>
  45. <a-col :span="10" v-if="item.ipShow">
  46. <a-form-item class="mb-0 mr-2" :wrapperCol="{ span: 24 }">
  47. <a-input
  48. :placeholder="$t('k8s.text_123')"
  49. @change="e => ipChange(e, i)"
  50. v-decorator="decorator.ip(item.key, item.network)" />
  51. </a-form-item>
  52. </a-col>
  53. <a-col :span="2">
  54. <a-button v-if="item.ipShow" type="link" class="mr-1 mt-1" @click="hideIp(item)">{{$t('k8s.text_410')}}</a-button>
  55. <a-button v-else type="link" class="mr-1 mt-1" @click="showIp(item)">{{$t('k8s.text_124')}}</a-button>
  56. </a-col>
  57. </a-row>
  58. </a-form-item>
  59. <a-form-item>
  60. <sku
  61. v-decorator="decorator.sku(item.key)"
  62. @change="handleSkuChange(i, $event)"
  63. :hypervisor="hypervisor"
  64. :sku-params="skuParam(i)" />
  65. </a-form-item>
  66. <a-form-item :label="$t('compute.text_267')" v-bind="formItemLayout" class="mb-0" v-if="useImage">
  67. <os-select
  68. :type="type"
  69. :uefi="uefi"
  70. :form="form"
  71. :hypervisor="hypervisor"
  72. :decorator="decorator.imageOS(item.key)[1]"
  73. @updateImageMsg="updateImageMsg(i, $event)"
  74. :cloudType="platform"
  75. :imageType="getImageType()"
  76. :imageParams="getImageParams()"
  77. :cacheImageParams="getCacheImageParams()" />
  78. </a-form-item>
  79. <a-form-item :label="$t('k8s.text_120')" v-bind="formItemLayout" class="mb-0">
  80. <system-disk
  81. :decorator="decorator.disk(item.key)"
  82. :type="platform"
  83. :form="form"
  84. :image="imageMock"
  85. :hypervisor="hypervisor"
  86. :capability-data="getCapability(i)"
  87. :sku="getSku(i)"
  88. :domain="userInfo.projectDomainId" />
  89. </a-form-item>
  90. <a-form-item :label="$t('dictionary.role')" v-bind="formItemLayout">
  91. <a-radio-group
  92. v-decorator="decorator.role(item.key)">
  93. <a-radio-button v-for="role in roleList" :key="role.value" :value="role.value">
  94. {{ role.label}}
  95. </a-radio-button>
  96. </a-radio-group>
  97. </a-form-item>
  98. <a-form-item v-show="!item.ipShow" :label="$t('k8s.text_125')" v-bind="formItemLayout" :extra="$t('k8s.text_126')">
  99. <a-input-number v-decorator="decorator.num(item.key)" :min="1" :max="item.ipShow ? 1 : 10" :parser="v => parser(v, '', '1')" />
  100. </a-form-item>
  101. </a-card>
  102. <div class="d-flex align-items-center" v-if="serverConfigRemaining > 0">
  103. <a-button type="primary" shape="circle" icon="plus" size="small" @click="add" />
  104. <a-button type="link" @click="add">{{$t('k8s.text_127')}}</a-button>
  105. <span class="network-count-tips">{{$t('k8s.text_128')}}<span class="remain-num">{{ serverConfigRemaining }}</span>{{$t('k8s.text_129')}}</span>
  106. </div>
  107. </div>
  108. </template>
  109. <script>
  110. import * as R from 'ramda'
  111. import { mapGetters } from 'vuex'
  112. import { NODE_ROLE_MAP } from '../../views/cluster/constants'
  113. import { IMAGES_TYPE_MAP } from '@/constants/compute'
  114. import { HYPERVISORS_MAP } from '@/constants'
  115. import { uuid } from '@/utils/utils'
  116. import SystemDisk from '@Compute/views/vminstance/create/components/SystemDisk'
  117. import Sku from '@Compute/sections/SKU'
  118. import OsSelect from '@Compute/sections/OsSelect'
  119. export default {
  120. name: 'K8SClusterServerConfig',
  121. components: {
  122. SystemDisk,
  123. Sku,
  124. OsSelect,
  125. },
  126. props: {
  127. decorator: {
  128. type: Object,
  129. required: true,
  130. },
  131. form: {
  132. type: Object,
  133. validator: val => val.fc,
  134. },
  135. cloudregionId: {
  136. type: String,
  137. required: true,
  138. },
  139. hypervisor: {
  140. type: String,
  141. required: true,
  142. },
  143. platform: {
  144. type: String,
  145. required: true,
  146. },
  147. networkParams: {
  148. type: Object,
  149. required: true,
  150. },
  151. },
  152. data () {
  153. const roleList = []
  154. R.forEachObjIndexed((value, key) => {
  155. roleList.push({ label: value, value: key })
  156. }, NODE_ROLE_MAP)
  157. return {
  158. imageMock: { min_disk: 40960 }, // 通过mock image数据设置最小磁盘大小
  159. selectedImage: {},
  160. formItemLayout: {
  161. wrapperCol: { span: 20 },
  162. labelCol: { span: 3 },
  163. },
  164. serverConfigList: [],
  165. limit: 10,
  166. roleList,
  167. }
  168. },
  169. computed: {
  170. ...mapGetters(['isAdminMode', 'scope', 'isDomainMode', 'userInfo', 'l3PermissionEnable']),
  171. useImage () {
  172. const hypervisor = this.$props.hypervisor
  173. if (hypervisor === HYPERVISORS_MAP.kvm.key || hypervisor === HYPERVISORS_MAP.cloudpods.key) {
  174. return true
  175. }
  176. return false
  177. },
  178. serverConfigRemaining () {
  179. return this.limit - this.serverConfigList.length
  180. },
  181. },
  182. watch: {
  183. hypervisor: {
  184. handler (val, oldVal) {
  185. console.log(`hypervisor change: ${val}`)
  186. },
  187. },
  188. },
  189. created () {
  190. this.add()
  191. },
  192. methods: {
  193. showIp (item) {
  194. item.ipShow = true
  195. },
  196. hideIp (item) {
  197. item.ipShow = false
  198. },
  199. ipChange (e, i) {
  200. this.serverConfigList[i].ip = e.target.value
  201. },
  202. decrease (uid, index) {
  203. this.serverConfigList.splice(index, 1)
  204. },
  205. networkLabelFormat (net) {
  206. return ( // IP子网
  207. <div>
  208. <span>{ net.name } ({ net.guest_ip_start } - { net.guest_ip_end }, zone={ net.zone })</span>
  209. </div>
  210. )
  211. },
  212. add () {
  213. const uid = uuid()
  214. const data = {
  215. ipShow: false,
  216. key: uid,
  217. }
  218. this.serverConfigList.push(data)
  219. this.$nextTick(() => {
  220. this.$emit('vmAdd', data)
  221. })
  222. },
  223. parser (val, unit, defaultValue = '4') {
  224. const value = val.replace(unit, '')
  225. const valueNum = Number(value)
  226. if (!Number.isNaN(valueNum) && R.is(Number, valueNum)) {
  227. return value
  228. }
  229. return defaultValue
  230. },
  231. networkResourceMapper (list) {
  232. return list
  233. .map(val => {
  234. const remain = val.ports - val.ports_used
  235. if (remain <= 0) {
  236. return {
  237. ...val,
  238. __disabled: true,
  239. }
  240. }
  241. return val
  242. })
  243. .sort((a, b) => (b.ports - b.ports_used) - (a.ports - a.ports_used))
  244. },
  245. getNetworkParams (key) {
  246. return {
  247. ...this.networkParams,
  248. $t: key,
  249. }
  250. },
  251. getConfig (index) {
  252. return this.serverConfigList[index]
  253. },
  254. setConfig (index, setFunc) {
  255. let conf = this.getConfig(index)
  256. conf = setFunc(conf)
  257. this.$set(this.serverConfigList, index, conf)
  258. },
  259. getCurrentNetwork (index) {
  260. return this.getConfig(index).network
  261. },
  262. getCurrentZone (index) {
  263. const curNet = this.getCurrentNetwork(index)
  264. if (curNet && curNet.zone_id) {
  265. return curNet.zone_id
  266. }
  267. return null
  268. },
  269. skuParam (index) {
  270. const filters = []
  271. filters.push('cpu_core_count.gt(1)')
  272. filters.push('memory_size_mb.gt(4095)')
  273. const orderBy = []
  274. orderBy.push('memory_size_mb')
  275. const param = {
  276. limit: 0,
  277. enabled: true,
  278. usable: true,
  279. filter: filters,
  280. order_by: orderBy,
  281. order: 'asc',
  282. cloudregion: this.cloudregionId,
  283. ...this.scopeParams,
  284. }
  285. const curZone = this.getCurrentZone(index)
  286. if (curZone) {
  287. param.zone = curZone
  288. }
  289. return param
  290. },
  291. handleNetworkChange (index, networkId) {
  292. this.fetchCapability(index)
  293. },
  294. handleSkuChange (index, sku) {
  295. this.setConfig(index, (conf) => {
  296. conf.sku = sku
  297. console.log(`set config(${index}) sku to ${sku.name}`)
  298. return conf
  299. })
  300. },
  301. updateImageMsg (index, { imageMsg }) {
  302. this.selectedImage = imageMsg
  303. this.setConfig(index, (conf) => {
  304. conf.image = imageMsg
  305. console.log(`set config(${index}) image to ${imageMsg.name}`)
  306. return conf
  307. })
  308. },
  309. getSku (index) {
  310. return this.getConfig(index).sku
  311. },
  312. fetchCapability (index) {
  313. const params = {
  314. show_emulated: true,
  315. resource_type: 'shared',
  316. scope: this.scope,
  317. }
  318. let id = this.getCurrentZone(index)
  319. let res = 'zones'
  320. if (!id) {
  321. id = this.cloudregionId
  322. res = 'cloudregions'
  323. }
  324. const capabilityParams = { id, spec: 'capability', params }
  325. this.capabilityParams = capabilityParams
  326. new this.$Manager(res).getSpecific(this.capabilityParams)
  327. .then(({ data }) => {
  328. this.setConfig(index, (conf) => {
  329. conf.capability = data
  330. /* console.log(`set config(${index}) capability to ${JSON.stringify(data)}`) */
  331. return conf
  332. })
  333. })
  334. },
  335. getCapability (index) {
  336. return this.getConfig(index).capability
  337. },
  338. isPublicCloud () {
  339. return this.platform === 'public'
  340. },
  341. isOnPremise () {
  342. return this.platform === 'idc'
  343. },
  344. getImageType () {
  345. if (this.isPublicCloud()) {
  346. return IMAGES_TYPE_MAP.public.key
  347. }
  348. if (this.isOnPremise()) {
  349. return IMAGES_TYPE_MAP.standard.key
  350. }
  351. throw Error(`Not support platform ${this.platform}`)
  352. },
  353. getImageParams () {
  354. return {
  355. distributions: ['CentOS', 'Kylin'],
  356. distribution_precise_match: true,
  357. }
  358. },
  359. getCacheImageParams () {
  360. if (!this.isPublicCloud()) {
  361. return {}
  362. }
  363. return {
  364. public_cloud: true,
  365. region: this.cloudregionId,
  366. }
  367. },
  368. /*
  369. * getImageIgnoreOptions () {
  370. * const opts = [
  371. * IMAGES_TYPE_MAP.private,
  372. * IMAGES_TYPE_MAP.customize,
  373. * IMAGES_TYPE_MAP.iso,
  374. * IMAGES_TYPE_MAP.snapshot,
  375. * IMAGES_TYPE_MAP.host,
  376. * ]
  377. * return opts.map(item => item.key)
  378. * },
  379. */
  380. },
  381. }
  382. </script>