BillForm.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. <template>
  2. <div>
  3. <a-alert :showIcon="false" :message="$t('cloudenv.text_194')" banner />
  4. <a-form class="pt-3" :form="form.fc" v-bind="formLayout">
  5. <a-form-item v-if="isAzure" :label="$t('cloudenv.text_360')">
  6. <a-radio-group v-model="billType">
  7. <a-radio-button
  8. v-for="item in billTypeOptions"
  9. :key="item.value"
  10. :value="item.value">{{ item.label }}</a-radio-button>
  11. </a-radio-group>
  12. </a-form-item>
  13. <template v-if="isAzure && isEA">
  14. <a-form-item :label="$t('cloudenv.text_195')">
  15. <a-input v-decorator="decorators.enrollment_number" />
  16. <span slot="extra" v-if="showDocsLink()">
  17. {{ $t('cloudenv.text_572') }}
  18. <help-link :href="enrollmentNumberUrl">{{ $t('cloudenv.text_197') }}</help-link>
  19. </span>
  20. </a-form-item>
  21. <a-form-item :label="$t('cloudenv.text_198')">
  22. <a-input v-decorator="decorators.balance_key" type="textarea" rows="4" />
  23. </a-form-item>
  24. <a-form-item :label="$t('cloudenv.billing_scope')">
  25. <a-radio-group v-decorator="decorators.billing_scope">
  26. <a-radio-button value="managed" key="managed">{{ $t('cloudenv.billing_scope.managed') }}</a-radio-button>
  27. <a-radio-button value="all" key="all">{{ $t('cloudenv.billing_scope.all') }}</a-radio-button>
  28. </a-radio-group>
  29. <div slot="extra">
  30. <div>{{ $t('cloudenv.billing_scope.extra') }}</div>
  31. <div>{{ $t('cloudenv.billing_scope.extra_note') }}</div>
  32. </div>
  33. </a-form-item>
  34. </template>
  35. <template v-if="useBillingBucket && !isEA && !isApi">
  36. <a-divider v-if="!isHiddenDriver" orientation="left">{{ $t('cloudenv.text_199') }}</a-divider>
  37. <a-form-item :label="$t('cloudenv.text_200')">
  38. <a-radio-group v-model="cloudAccountType">
  39. <a-radio-button :value="1">{{ $t('cloudenv.text_201') }}</a-radio-button>
  40. <a-radio-button v-if="!isHuawei && !isAzure" :value="2">{{ $t('cloudenv.text_202') }}</a-radio-button>
  41. </a-radio-group>
  42. </a-form-item>
  43. <a-form-item
  44. :label="$t('cloudenv.text_201')"
  45. v-if="cloudAccountType === 2"
  46. :extra="$t('cloudenv.text_203')">
  47. <a-select
  48. :filterOption="filterOption"
  49. showSearch
  50. :loading="cloudAccountLoading"
  51. v-decorator="decorators.billing_bucket_account">
  52. <template v-for="item in cloudAccounts">
  53. <a-select-option v-if="id !== item.id" :key="item.id" :value="item.id">{{ item.name }}</a-select-option>
  54. </template>
  55. </a-select>
  56. </a-form-item>
  57. <a-form-item :label="$t('cloudenv.text_204')">
  58. <a-input v-decorator="decorators.billing_report_bucket" />
  59. <span slot="extra" v-if="bucketUrl && showDocsLink()">
  60. <!-- 请正确输入账单文件所在存储桶的URL,例如:https://bucket-name.oss-cn-beijing.aliyuncs.com <br /> -->
  61. {{ $t('cloudenv.text_196') }}
  62. <help-link :href="bucketUrl">{{ $t('cloudenv.text_205') }}</help-link>
  63. </span>
  64. </a-form-item>
  65. <a-form-item
  66. v-if="!isHuawei"
  67. :label="$t('cloudenv.text_206')"
  68. :extra="$t('cloudenv.text_207')">
  69. <a-input v-decorator="decorators.billing_file_prefix" />
  70. </a-form-item>
  71. <a-form-item :label="$t('cloudenv.billing_scope')" v-if="cloudAccountType === 1">
  72. <div slot="extra">
  73. <div>{{ $t('cloudenv.billing_scope.extra') }}</div>
  74. <div>{{ $t('cloudenv.billing_scope.extra_note') }}</div>
  75. </div>
  76. <a-radio-group v-decorator="decorators.billing_scope">
  77. <a-radio-button value="managed" key="managed">{{ $t('cloudenv.billing_scope.managed') }}</a-radio-button>
  78. <a-radio-button
  79. value="all"
  80. key="all"
  81. :disabled="billingScopeDisabled">{{ $t('cloudenv.billing_scope.all') }}</a-radio-button>
  82. </a-radio-group>
  83. </a-form-item>
  84. <!-- google -->
  85. <template v-if="isGoogle">
  86. <a-divider class="mt-5" orientation="left">{{ $t('cloudenv.text_208') }}</a-divider>
  87. <a-form-item :label="$t('cloudenv.text_204')" :extra="$t('cloudenv.text_209')">
  88. <a-input v-decorator="decorators.usage_report_bucket" />
  89. </a-form-item>
  90. <a-form-item :label="$t('cloudenv.text_206')" :extra="$t('cloudenv.text_207')">
  91. <a-input v-decorator="decorators.usage_file_prefix" />
  92. </a-form-item>
  93. </template>
  94. </template>
  95. <template v-if="isApi">
  96. <a-divider v-if="!isHiddenDriver" orientation="left">{{ $t('cloudenv.text_199') }}</a-divider>
  97. <a-form-item :label="$t('cloudenv.text_200')">
  98. <a-radio-group v-model="cloudAccountType">
  99. <a-radio-button :value="1">{{ $t('cloudenv.text_201') }}</a-radio-button>
  100. <a-radio-button v-if="!isHuawei" :value="2">{{ $t('cloudenv.text_202') }}</a-radio-button>
  101. </a-radio-group>
  102. </a-form-item>
  103. <a-form-item
  104. :label="$t('cloudenv.text_201')"
  105. v-if="cloudAccountType === 2"
  106. :extra="$t('cloudenv.text_203')">
  107. <a-select
  108. :filterOption="filterOption"
  109. showSearch
  110. :loading="cloudAccountLoading"
  111. v-decorator="decorators.billing_account">
  112. <template v-for="item in cloudAccounts">
  113. <a-select-option v-if="id !== item.id" :key="item.id" :value="item.id">{{ item.name }}</a-select-option>
  114. </template>
  115. </a-select>
  116. </a-form-item>
  117. <a-form-item :label="$t('cloudenv.billing_scope')">
  118. <div slot="extra">
  119. <div>{{ $t('cloudenv.billing_scope.extra') }}</div>
  120. <div>{{ $t('cloudenv.billing_scope.extra_note') }}</div>
  121. </div>
  122. <a-radio-group v-decorator="decorators.billing_scope">
  123. <a-radio-button value="managed" key="managed">{{ $t('cloudenv.billing_scope.managed') }}</a-radio-button>
  124. <a-radio-button
  125. value="all"
  126. key="all"
  127. :disabled="billingScopeDisabled">{{ $t('cloudenv.billing_scope.all') }}</a-radio-button>
  128. </a-radio-group>
  129. </a-form-item>
  130. </template>
  131. <a-form-item :label="$t('cloudenv.text_210')" :extra="$t('cloudenv.text_211')">
  132. <a-switch v-decorator="decorators.sync_info" />
  133. </a-form-item>
  134. <a-form-item
  135. :label="$t('cloudenv.text_212')"
  136. v-if="form.fc.getFieldValue('sync_info')"
  137. :extra="$t('cloudenv.text_213')">
  138. <a-form-item style="display: inline-block">
  139. <a-month-picker
  140. v-decorator="decorators.start_day"
  141. :disabled-date="dateDisabledStart"
  142. format="YYYY-MM" />
  143. </a-form-item>
  144. <span class="ml-2 mr-2">~</span>
  145. <a-form-item style="display: inline-block">
  146. <a-month-picker
  147. v-decorator="decorators.end_day"
  148. :disabled-date="dateDisabledEnd"
  149. format="YYYY-MM" />
  150. </a-form-item>
  151. </a-form-item>
  152. </a-form>
  153. </div>
  154. </template>
  155. <script>
  156. import * as R from 'ramda'
  157. import DialogMixin from '@/mixins/dialog'
  158. import WindowsMixin from '@/mixins/windows'
  159. import { HYPERVISORS_MAP } from '@/constants'
  160. import { DOCS_MAP, showDocsLink } from '@/constants/docs'
  161. import {
  162. keySecretFields,
  163. BILL_TYPES,
  164. BILL_TYPE_MAP,
  165. } from '../../constants'
  166. export default {
  167. name: 'BillConfig',
  168. mixins: [DialogMixin, WindowsMixin],
  169. props: {
  170. account: {
  171. type: Object,
  172. },
  173. },
  174. data () {
  175. return {
  176. showDocsLink,
  177. loading: false,
  178. cloudAccounts: [],
  179. cloudAccountLoading: false,
  180. cloudAccount: {},
  181. cloudAccountType: 1, // 1 --> 主账号 | 2 --> 关联账号
  182. billTypeOptions: BILL_TYPES,
  183. billType: BILL_TYPE_MAP.EA.value, // EA --> EA账号 | Bucket --> 存储桶
  184. form: {
  185. fc: this.$form.createForm(this),
  186. },
  187. billTasks: {
  188. '7 days': this.$t('cloudenv.text_214'),
  189. '1 months': this.$t('cloudenv.text_215'),
  190. '3 months': this.$t('cloudenv.text_216'),
  191. '6 months': this.$t('cloudenv.text_217'),
  192. },
  193. formLayout: {
  194. wrapperCol: {
  195. md: { span: 18 },
  196. xl: { span: 20 },
  197. xxl: { span: 22 },
  198. },
  199. labelCol: {
  200. md: { span: 6 },
  201. xl: { span: 3 },
  202. xxl: { span: 2 },
  203. },
  204. },
  205. offsetFormLayout: {
  206. wrapperCol: {
  207. md: { span: 18, offset: 6 },
  208. xl: { span: 20, offset: 3 },
  209. xxl: { span: 22, offset: 2 },
  210. },
  211. },
  212. }
  213. },
  214. computed: {
  215. id () {
  216. return (
  217. (this.account && this.account.id) ||
  218. (this.cloudAccount && this.cloudAccount.id)
  219. )
  220. },
  221. provider () {
  222. const { provider } = this.$route.query
  223. return provider || (this.cloudAccount && this.cloudAccount.provider)
  224. },
  225. isGoogle () {
  226. return this.provider === HYPERVISORS_MAP.google.provider
  227. },
  228. isHuawei () {
  229. return this.provider === HYPERVISORS_MAP.huawei.provider
  230. },
  231. isAzure () {
  232. return this.provider === HYPERVISORS_MAP.azure.provider
  233. },
  234. isAzurePublicCloud () {
  235. return this.cloudAccount.access_url === 'AzurePublicCloud'
  236. },
  237. isQcloud () {
  238. return this.provider === HYPERVISORS_MAP.qcloud.provider
  239. },
  240. isAws () {
  241. return this.provider === HYPERVISORS_MAP.aws.provider
  242. },
  243. isAliyun () {
  244. return this.provider === HYPERVISORS_MAP.aliyun.provider
  245. },
  246. isVolcEngine () {
  247. return this.provider === HYPERVISORS_MAP.volcengine.provider
  248. },
  249. isKsyun () {
  250. return this.provider === HYPERVISORS_MAP.ksyun.provider
  251. },
  252. useBillingBucket () {
  253. const supportProviders = [
  254. HYPERVISORS_MAP.aliyun.provider,
  255. HYPERVISORS_MAP.aws.provider,
  256. HYPERVISORS_MAP.huawei.provider,
  257. HYPERVISORS_MAP.google.provider,
  258. HYPERVISORS_MAP.volcengine.provider,
  259. HYPERVISORS_MAP.qcloud.provider,
  260. HYPERVISORS_MAP.azure.provider,
  261. HYPERVISORS_MAP.ksyun.provider,
  262. ]
  263. return supportProviders.includes(this.provider)
  264. },
  265. isApi () {
  266. return this.isAzure && this.billType === BILL_TYPE_MAP.Api.value
  267. },
  268. brandCn () {
  269. const { brand } = this.cloudAccount
  270. return brand ? keySecretFields[brand.toLowerCase()].text : ''
  271. },
  272. bucketUrl () {
  273. const { brand } = this.cloudAccount
  274. return brand
  275. ? DOCS_MAP.billBucket()[brand.toLowerCase()]
  276. : ''
  277. },
  278. enrollmentNumberUrl () {
  279. return DOCS_MAP.enrollmentNumber()
  280. },
  281. decorators () {
  282. const options = this.cloudAccount.options || {}
  283. return {
  284. billing_bucket_account: [
  285. 'billing_bucket_account',
  286. {
  287. initialValue: options.billing_bucket_account,
  288. },
  289. ],
  290. billing_account: [
  291. 'billing_account',
  292. {
  293. initialValue: options.billing_account,
  294. },
  295. ],
  296. billing_report_bucket: [
  297. 'billing_report_bucket',
  298. {
  299. initialValue: options.billing_report_bucket,
  300. rules: [{ required: true, message: this.$t('cloudenv.text_220') }],
  301. },
  302. ],
  303. billing_file_prefix: [
  304. 'billing_file_prefix',
  305. {
  306. initialValue: options.billing_file_prefix,
  307. },
  308. ],
  309. usage_report_bucket: [
  310. 'usage_report_bucket',
  311. {
  312. initialValue: options.usage_report_bucket,
  313. rules: [
  314. // { required: true, message: '请输入存储桶URL' },
  315. ],
  316. },
  317. ],
  318. usage_file_prefix: [
  319. 'usage_file_prefix',
  320. {
  321. initialValue: options.usage_file_prefix,
  322. },
  323. ],
  324. sync_info: [
  325. 'sync_info',
  326. {
  327. initialValue: false,
  328. valuePropName: 'checked',
  329. },
  330. ],
  331. enrollment_number: [
  332. 'enrollment_number',
  333. {
  334. initialValue: options.enrollment_number,
  335. rules: [{ required: true, message: this.$t('cloudenv.text_221') }],
  336. },
  337. ],
  338. balance_key: [
  339. 'balance_key',
  340. {
  341. initialValue: options.balance_key,
  342. rules: [{ required: true, message: this.$t('cloudenv.text_222') }],
  343. },
  344. ],
  345. start_day: [
  346. 'start_day',
  347. {
  348. initialValue: this.$moment().startOf('month'),
  349. rules: [
  350. {
  351. required: true,
  352. message: this.$t('common.tips.select', [
  353. this.$t('cloudenv.text_461'),
  354. ]),
  355. },
  356. ],
  357. },
  358. ],
  359. end_day: [
  360. 'end_day',
  361. {
  362. initialValue: this.$moment(),
  363. rules: [
  364. {
  365. required: true,
  366. message: this.$t('common.tips.select', [
  367. this.$t('cloudenv.text_462'),
  368. ]),
  369. },
  370. ],
  371. },
  372. ],
  373. billing_scope: [
  374. 'billing_scope',
  375. {
  376. initialValue:
  377. options.billing_scope || this.getDefaultBillingScope(),
  378. rules: [
  379. {
  380. required: true,
  381. message: this.$t('cloudenv.billing_scope.prompt'),
  382. },
  383. ],
  384. },
  385. ],
  386. }
  387. },
  388. billingScopeDisabled () {
  389. const supportProviders = [
  390. HYPERVISORS_MAP.aws.provider,
  391. HYPERVISORS_MAP.aliyun.provider,
  392. HYPERVISORS_MAP.volcengine.provider,
  393. HYPERVISORS_MAP.qcloud.provider,
  394. HYPERVISORS_MAP.azure.provider,
  395. HYPERVISORS_MAP.ksyun.provider,
  396. ]
  397. return !supportProviders.includes(this.provider)
  398. },
  399. isEA () {
  400. return this.billType === BILL_TYPE_MAP.EA.value
  401. },
  402. isHiddenDriver () {
  403. return this.isAzure
  404. },
  405. },
  406. watch: {
  407. cloudAccount (val) {
  408. const { enrollment_number, billing_report_bucket } = val.options || {}
  409. if (this.provider === HYPERVISORS_MAP.azure.provider) {
  410. if (enrollment_number) {
  411. this.billType = BILL_TYPE_MAP.EA.value
  412. } else if (billing_report_bucket) {
  413. this.billType = BILL_TYPE_MAP.Bucket.value
  414. } else {
  415. this.billType = BILL_TYPE_MAP.Api.value
  416. }
  417. } else {
  418. this.billType = BILL_TYPE_MAP.Bucket.value
  419. }
  420. },
  421. billType (val) {
  422. if (val === BILL_TYPE_MAP.Bucket.value) {
  423. this.cloudAccountType = 1
  424. }
  425. },
  426. },
  427. created () {
  428. this.manager = new this.$Manager('cloudaccounts')
  429. this.fetchs()
  430. },
  431. methods: {
  432. getDefaultBillingScope () {
  433. if (!this.billingScopeDisabled) {
  434. return 'all'
  435. } else {
  436. return 'managed'
  437. }
  438. },
  439. async fetchs () {
  440. await this.fetchCloudAccount()
  441. await this.fetchCloudAccounts()
  442. },
  443. filterOption (input, option) {
  444. return (
  445. option.componentOptions.children[0].text
  446. .toLowerCase()
  447. .indexOf(input.toLowerCase()) >= 0
  448. )
  449. },
  450. async fetchCloudAccounts () {
  451. this.cloudAccountLoading = true
  452. try {
  453. const params = {
  454. scope: this.$store.getters.scope,
  455. brand: this.provider || this.cloudAccount.brand,
  456. }
  457. const { data } = await this.manager.list({ params })
  458. this.cloudAccounts = data.data || []
  459. } catch (err) {
  460. throw err
  461. } finally {
  462. this.cloudAccountLoading = false
  463. }
  464. },
  465. async fetchCloudAccount () {
  466. const { id } = this.$route.query
  467. if (!id) {
  468. this.cloudAccount = this.account
  469. return false
  470. }
  471. try {
  472. const { data } = await this.manager.get({
  473. id,
  474. params: {
  475. details: true,
  476. },
  477. })
  478. // billing_scope没有默认选中一个,选中规则与新建相同
  479. if (!data.options || !data.options.billing_scope) {
  480. data.options = data.options || {}
  481. data.options.billing_scope =
  482. data.options.billing_scope || this.getDefaultBillingScope()
  483. }
  484. this.cloudAccount = data
  485. if (data && data.options && (data.options.billing_bucket_account || data.options.billing_account)) {
  486. this.cloudAccountType = 2
  487. }
  488. return data
  489. } catch (err) {
  490. throw err
  491. }
  492. },
  493. async postBillTasks (id, values) {
  494. const manager = new this.$Manager('billtasks/submit', 'v1')
  495. try {
  496. const { start_day, end_day } = values
  497. const data = {
  498. account_id: id,
  499. task_type: 'pull_bill',
  500. start_day: parseInt(
  501. this.$moment(start_day)
  502. .startOf('month')
  503. .format('YYYYMMDD'),
  504. ),
  505. end_day: parseInt(
  506. this.$moment(end_day)
  507. .endOf('month')
  508. .format('YYYYMMDD'),
  509. ),
  510. }
  511. await manager.create({
  512. data,
  513. })
  514. } catch (err) {
  515. throw err
  516. }
  517. },
  518. async doSubmit ({ id } = this.cloudAccount, isGoCloudaccount = true) {
  519. try {
  520. const values = await this.form.fc.validateFields()
  521. if (values.sync_info) {
  522. this.postBillTasks(id, values)
  523. }
  524. delete values.sync_info
  525. delete values.start_day
  526. delete values.end_day
  527. const params = {
  528. id,
  529. data: {
  530. options: values,
  531. },
  532. }
  533. if (this.cloudAccountType === 1) {
  534. params.data = {
  535. remove_options: [
  536. 'billing_bucket_account',
  537. 'billing_account',
  538. 'billing_bigquery_table',
  539. ],
  540. ...params.data,
  541. }
  542. }
  543. if (this.isAzure) {
  544. const { remove_options = [] } = params.data
  545. if (this.isEA) {
  546. params.data = {
  547. ...params.data,
  548. remove_options: [...remove_options, 'billing_report_bucket', 'billing_account'],
  549. }
  550. } else if (this.isApi) {
  551. params.data = {
  552. ...params.data,
  553. remove_options: [
  554. ...remove_options,
  555. // 非 API 方式独有的字段,切到 API 时需要清理
  556. 'billing_report_bucket',
  557. 'billing_file_prefix',
  558. 'billing_bucket_account',
  559. 'enrollment_number',
  560. 'balance_key',
  561. ],
  562. }
  563. } else {
  564. params.data = {
  565. ...params.data,
  566. remove_options: [
  567. ...remove_options,
  568. 'enrollment_number',
  569. 'billing_account',
  570. 'balance_key',
  571. ],
  572. }
  573. }
  574. }
  575. if (params.data?.options && R.is(Object, params.data.options)) {
  576. for (const key in params.data.options) {
  577. if (R.is(String, params.data.options[key])) {
  578. params.data.options[key] = params.data.options[key].trim()
  579. }
  580. }
  581. }
  582. await this.manager.update(params)
  583. // if (isGoCloudaccount) {
  584. // this.$router.push('/cloudaccount')
  585. // }
  586. } catch (err) {
  587. throw err
  588. }
  589. },
  590. async testPost () {
  591. const values = await this.form.fc.validateFields()
  592. values.cloudaccount_id = this.id
  593. delete values.sync_info
  594. delete values.month
  595. const params = { ...values }
  596. for (const key in params) {
  597. if (R.is(String, params[key])) {
  598. params[key] = params[key].trim()
  599. }
  600. }
  601. const res = await new this.$Manager(
  602. 'bucket_options',
  603. 'v1',
  604. ).performClassAction({
  605. action: 'verify',
  606. data: params,
  607. })
  608. if (!res || !res.data || !res.data.status) return false
  609. if (res.data.status === 'success') {
  610. this.$notification.success({
  611. message: this.$t('common_270'),
  612. description: this.$t('common_271'),
  613. })
  614. } else if (res.data.msg && res.data.msg === 'bucket file not found') {
  615. this.$notification.warning({
  616. message: this.$t('cloudenv.text_577'),
  617. description: (
  618. <div>
  619. {this.$t('cloudenv.text_578')}
  620. <br />
  621. {this.$t('cloudenv.text_579')}
  622. </div>
  623. ),
  624. })
  625. } else return false
  626. },
  627. dateDisabledStart (value) {
  628. const dateEnd = this.form.fc.getFieldValue('end_day')
  629. if (dateEnd && value > dateEnd) return true
  630. if (value > this.$moment()) return true
  631. return false
  632. },
  633. dateDisabledEnd (value) {
  634. const dateStart = this.form.fc.getFieldValue('start_day')
  635. if (dateStart && value < dateStart) return true
  636. if (value > this.$moment()) return true
  637. return false
  638. },
  639. },
  640. }
  641. </script>