index.vue 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. <template>
  2. <div class="wrap">
  3. <a-popover v-model="visible" trigger="click" @visibleChange="handlePopoverVisibleChange">
  4. <template v-slot:content>
  5. <a-icon type="sync" spin v-if="loading" />
  6. <template v-else>
  7. <template v-if="error">
  8. <template v-if="error.response && error.response.status === 404">
  9. <a-icon type="exclamation-circle" theme="twoTone" twoToneColor="#faad14" />
  10. <span class="ml-2" style="color: #faad14;">{{$t('compute.text_154')}}</span>
  11. </template>
  12. <template v-else>
  13. <a-icon type="close-circle" theme="twoTone" twoToneColor="#f5222d" />
  14. <span class="ml-2" style="color: #f5222d;">{{$t('compute.text_155')}}</span>
  15. </template>
  16. </template>
  17. <template v-else>
  18. <div class="mv-2">
  19. <div class="d-flex" v-if="loginInfos.ip.value">
  20. <div><span v-html="loginInfos.ip.label" /></div>
  21. <div><span>{{ loginInfos.ip.value }}</span><copy class="ml-1" :message="loginInfos.ip.value" /></div>
  22. </div>
  23. </div>
  24. <div class="mv-2" v-if="loginInfos.mainAccount.value">
  25. <div class="d-flex">
  26. <div><span v-html="loginInfos.mainAccount.label" /></div>
  27. <div><span>{{ loginInfos.mainAccount.value }}</span><copy class="ml-1" :message="loginInfos.mainAccount.value" /></div>
  28. </div>
  29. </div>
  30. <div class="mv-2">
  31. <div class="d-flex">
  32. <div><span v-html="loginInfos.username.label" /></div>
  33. <div><span>{{ loginInfos.username.value }}</span><copy class="ml-1" :message="loginInfos.username.value" /></div>
  34. </div>
  35. </div>
  36. <div class="mv-2">
  37. <div class="d-flex">
  38. <div><span v-html="loginInfos.password.label" /></div>
  39. <template v-if="loginInfos.password.value">
  40. <div><span>{{ loginInfos.password.value }}</span><copy class="ml-1" :message="loginInfos.password.value" /></div>
  41. </template>
  42. <template v-if="!loginInfos.password.value && loginInfos.password.keypair && loginInfos.password.loginKey">
  43. <div><a @click="handleDecryptSecret">{{$t('compute.text_156')}}</a></div>
  44. </template>
  45. </div>
  46. </div>
  47. </template>
  48. </template>
  49. </template>
  50. <span @click.stop="fetchLoginInfo" v-if="!disabled">
  51. <icon class="keypair-icon" type="keypairs" fill="#555" />
  52. </span>
  53. </a-popover>
  54. <a-tooltip placement="top" v-if="promptText && disabled">
  55. <template slot="title">
  56. <span>{{promptText}}</span>
  57. </template>
  58. <span>
  59. <icon class="keypair-icon-disabled" type="keypairs" />
  60. </span>
  61. </a-tooltip>
  62. <span v-if="!promptText && disabled">
  63. <icon class="keypair-icon-disabled" type="keypairs" />
  64. </span>
  65. <a-modal
  66. :visible="dialog.visible"
  67. :closable="false"
  68. :title="$t('compute.text_157')"
  69. @cancel="handleDialogCacel">
  70. <div>
  71. <div class="mb-2">
  72. <a-alert type="warning" :message="$t('compute.text_158')" />
  73. </div>
  74. <a-row :gutter="20" class="mb-2">
  75. <a-col :span="5" class="text-right">{{$t('compute.text_159')}}</a-col>
  76. <a-col :span="19">{{ loginInfos.password.keypair }}</a-col>
  77. </a-row>
  78. <a-row :gutter="20" class="mb-2">
  79. <a-col :span="5" class="text-right">{{$t('compute.text_160')}}</a-col>
  80. <a-col :span="19">
  81. <a-textarea v-model="dialog.value" :rows="7" />
  82. </a-col>
  83. </a-row>
  84. <template v-if="dialog.password">
  85. <a-row :gutter="20">
  86. <a-col :span="5" class="text-right">{{$t('compute.text_161')}}</a-col>
  87. <a-col :span="19">
  88. <span>{{ dialog.password }}</span><copy class="ml-1" :message="dialog.password" />
  89. </a-col>
  90. </a-row>
  91. </template>
  92. </div>
  93. <template #footer>
  94. <a-button type="primary" @click="handleDialogConfirm">{{$t('compute.text_162')}}</a-button>
  95. <a-button @click="handleDialogCacel">{{$t('compute.text_135')}}</a-button>
  96. </template>
  97. </a-modal>
  98. </div>
  99. </template>
  100. <script>
  101. import * as R from 'ramda'
  102. import { Manager } from '@/utils/manager'
  103. import { passwordDecrypt } from '@/utils/common/secret'
  104. export default {
  105. name: 'PasswordFetcher',
  106. props: {
  107. serverId: {
  108. type: String,
  109. required: true,
  110. },
  111. resourceType: {
  112. type: String,
  113. required: true,
  114. validator: val => ['servers', 'baremetals', 'baremetal_ssh', 'elasticcaches', 'dbinstanceaccounts', 'elasticcacheaccounts', 'cloudusers'].includes(val),
  115. },
  116. disabled: {
  117. type: Boolean,
  118. default: false,
  119. },
  120. promptText: {
  121. type: String,
  122. },
  123. },
  124. data () {
  125. return {
  126. loading: false,
  127. error: null,
  128. visible: false,
  129. requestConfigs: {
  130. servers: {
  131. resource: 'servers',
  132. methodname: 'GetLoginInfo',
  133. },
  134. baremetals: {
  135. resource: 'hosts',
  136. methodname: 'GetIpmiInfo',
  137. },
  138. baremetal_ssh: {
  139. resource: 'hosts',
  140. methodname: 'GetLoginInfo',
  141. },
  142. elasticcaches: {
  143. resource: 'elasticcaches',
  144. methodname: 'GetLoginInfo',
  145. },
  146. dbinstanceaccounts: {
  147. resource: 'dbinstanceaccounts',
  148. methodname: 'GetLoginInfo',
  149. },
  150. elasticcacheaccounts: {
  151. resource: 'elasticcacheaccounts',
  152. methodname: 'GetLoginInfo',
  153. },
  154. cloudusers: {
  155. resource: 'cloudusers',
  156. methodname: 'GetLoginInfo',
  157. apiVersion: 'v1',
  158. },
  159. },
  160. loginInfos: {
  161. ip: {
  162. label: 'IP:',
  163. value: '',
  164. },
  165. username: {
  166. label: this.$t('compute.text_user'),
  167. value: '',
  168. },
  169. password: {
  170. label: this.$t('compute.text_164'),
  171. value: '',
  172. keypair: '',
  173. loginKey: '',
  174. },
  175. mainAccount: {
  176. label: this.$t('compute.text_1345'),
  177. value: '',
  178. },
  179. },
  180. dialog: {
  181. visible: false,
  182. value: '',
  183. password: '',
  184. },
  185. }
  186. },
  187. methods: {
  188. fetchLoginInfo () {
  189. this.$nextTick(async () => {
  190. if (this.visible === false) return
  191. const config = this.requestConfigs[this.resourceType]
  192. const manager = new Manager(config.resource, config.apiVersion || 'v2')
  193. this.loading = true
  194. try {
  195. const { data: { password, username, account, keypair, login_key: loginKey, ip } } = await manager.objectRpc({
  196. methodname: config.methodname,
  197. objId: this.serverId,
  198. })
  199. this.loginInfos.username.value = username || account
  200. if (this.resourceType === 'baremetals') {
  201. this.loginInfos.ip.value = ip
  202. }
  203. if (keypair) {
  204. this.loginInfos.password.keypair = keypair
  205. this.loginInfos.password.loginKey = loginKey
  206. } else {
  207. this.loginInfos.password.value = password
  208. }
  209. if (this.resourceType === 'cloudusers' && account) {
  210. this.loginInfos.mainAccount.value = account
  211. }
  212. this.error = null
  213. } catch (error) {
  214. this.error = {}
  215. } finally {
  216. setTimeout(() => {
  217. this.loading = false
  218. }, 100)
  219. }
  220. })
  221. },
  222. handleDecryptSecret () {
  223. this.visible = false
  224. this.dialog.visible = true
  225. this.login_key = this.loginInfos.password.loginKey
  226. },
  227. handleDialogConfirm () {
  228. if (R.isNil(this.dialog.value) || R.isEmpty(this.dialog.value)) return
  229. if (this.dialog.password) {
  230. this.handleDialogCacel()
  231. return
  232. }
  233. try {
  234. this.dialog.password = passwordDecrypt(this.login_key, this.dialog.value) || this.$t('compute.text_165')
  235. } catch (error) {
  236. this.$message.error(this.$t('compute.text_166'))
  237. throw error
  238. }
  239. },
  240. handleDialogCacel () {
  241. this.dialog.visible = false
  242. this.dialog.value = ''
  243. this.dialog.password = ''
  244. },
  245. handlePopoverVisibleChange (val) {
  246. if (!val) {
  247. setTimeout(() => {
  248. // 清空信息
  249. this.error = null
  250. this.loginInfos.ip.value = ''
  251. this.loginInfos.username.value = ''
  252. this.loginInfos.password.value = ''
  253. this.loginInfos.password.keypair = ''
  254. this.loginInfos.password.loginKey = ''
  255. }, 500)
  256. }
  257. },
  258. },
  259. }
  260. </script>
  261. <style lang="less" scoped>
  262. @import "../../../../src/styles/less/theme";
  263. .wrap {
  264. .keypair-icon {
  265. cursor: pointer;
  266. font-size: 18px;
  267. svg {
  268. &:hover {
  269. fill: @primary-color;
  270. }
  271. }
  272. }
  273. .keypair-icon-disabled {
  274. font-size: 18px;
  275. svg {
  276. fill: @disabled-color;
  277. }
  278. }
  279. }
  280. </style>