diff --git a/src/components/Apps/AccountCreateUpdateForm/OTPSecretInput.vue b/src/components/Apps/AccountCreateUpdateForm/OTPSecretInput.vue new file mode 100644 index 0000000000..7fc8871ee9 --- /dev/null +++ b/src/components/Apps/AccountCreateUpdateForm/OTPSecretInput.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/src/components/Apps/AccountCreateUpdateForm/const.js b/src/components/Apps/AccountCreateUpdateForm/const.js index f59514d8c3..49422a6c16 100644 --- a/src/components/Apps/AccountCreateUpdateForm/const.js +++ b/src/components/Apps/AccountCreateUpdateForm/const.js @@ -3,6 +3,7 @@ import Select2 from '@/components/Form/FormFields/Select2.vue' import AssetSelect from '@/components/Apps/AssetSelect/index.vue' import { Required, RequiredChange } from '@/components/Form/DataForm/rules' import AutomationParamsForm from '@/views/assets/Platform/AutomationParamsSetting.vue' +import OTPSecretInput from '@/components/Apps/AccountCreateUpdateForm/OTPSecretInput.vue' export const accountFieldsMeta = (vm) => { const defaultPrivilegedAccounts = ['root', 'administrator'] @@ -215,6 +216,17 @@ export const accountFieldsMeta = (vm) => { }, hidden: (formValue) => formValue.secret_type !== 'api_key' || vm.addTemplate }, + otp_secret_key: { + label: vm.$t('OTPSecretKey'), + component: OTPSecretInput, + helpText: vm.$t('OTPSecretKeyHelpText'), + el: { + get disabled() { + return vm.isDisabled + } + }, + hidden: () => vm.addTemplate + }, secret_type: { type: 'radio-group', options: [], diff --git a/src/components/Apps/AccountCreateUpdateForm/index.vue b/src/components/Apps/AccountCreateUpdateForm/index.vue index 12c672ecda..14023a0502 100644 --- a/src/components/Apps/AccountCreateUpdateForm/index.vue +++ b/src/components/Apps/AccountCreateUpdateForm/index.vue @@ -77,6 +77,7 @@ export default { 'access_key', 'passphrase', 'api_key', + 'otp_secret_key', 'secret_reset' ] ], @@ -160,6 +161,9 @@ export default { const secretType = form.secret_type || 'password' form.secret = form[secretType] form.secret = this.encryptPassword ? encryptPassword(form.secret) : form.secret + if (form.otp_secret_key) { + form.otp_secret_key = encryptPassword(form.otp_secret_key) + } // 如果不删除会明文显示 delete form[secretType] diff --git a/src/components/Apps/AccountListTable/AccountList.vue b/src/components/Apps/AccountListTable/AccountList.vue index 31caddfc72..b72a55d958 100644 --- a/src/components/Apps/AccountListTable/AccountList.vue +++ b/src/components/Apps/AccountListTable/AccountList.vue @@ -13,6 +13,11 @@ :url="secretUrl" :visible.sync="showViewSecretDialog" /> + + + + + {{ account.name || '-' }} + + + {{ account.username || '-' }} + + +
+
+ {{ account.asset?.name || '-' }} +
+
{{ currentOTPCode || '------' }}
+ +
+
+
+
+ + + + + diff --git a/src/components/Apps/AccountListTable/ViewSecret.vue b/src/components/Apps/AccountListTable/ViewSecret.vue index 9fde56b95f..49fdeeae85 100644 --- a/src/components/Apps/AccountListTable/ViewSecret.vue +++ b/src/components/Apps/AccountListTable/ViewSecret.vue @@ -92,7 +92,7 @@ export default { }, title: { type: String, - default: function () { + default: function() { return this.$tc('Detail') } }, diff --git a/src/components/Apps/AccountListTable/const.js b/src/components/Apps/AccountListTable/const.js index 4d71a9906f..d4c7a49020 100644 --- a/src/components/Apps/AccountListTable/const.js +++ b/src/components/Apps/AccountListTable/const.js @@ -58,6 +58,21 @@ export const accountOtherActions = vm => { }) } }, + { + name: 'OTPCode', + title: vm.$t('OTPCode'), + has: ({ row }) => !!row.has_otp_secret_key, + can: vm.$hasPerm('accounts.view_accountsecret'), + type: 'primary', + order: 2, + callback: ({ row }) => { + vm.account = row + vm.showViewOTPCodeDialog = false + setTimeout(() => { + vm.showViewOTPCodeDialog = true + }) + } + }, { name: 'Update', title: vm.$t('Edit'), diff --git a/src/i18n/langs/en.json b/src/i18n/langs/en.json index ec8bd4f25a..92745a740f 100644 --- a/src/i18n/langs/en.json +++ b/src/i18n/langs/en.json @@ -20,6 +20,11 @@ "Details": "Details", "Confirm": "Confirm", "Ignore": "Ignore", + "OTPSecretKey": "OTP Secret Key", + "OTPSecretKeyHelpText": "Optional. If the target account also requires a one-time password, set the OTP secret key here or click Generate to create a valid one.", + "CurrentOTPCode": "Current OTP Code", + "OTPCode": "OTP Code", + "Authenticator": "Authenticator", "Please enter a reason": "Please enter a reason", "PleaseEnterReason": "Please enter a reason | Please enter reasons" -} \ No newline at end of file +} diff --git a/src/i18n/langs/ja.json b/src/i18n/langs/ja.json index 2b2faa4b6c..cb3257b122 100644 --- a/src/i18n/langs/ja.json +++ b/src/i18n/langs/ja.json @@ -8,5 +8,10 @@ "notParenthesis": "不能包含括号。", "InvalidJson": "JSON 格式错误.", "requiredHasUserNameMapped": "用户名属性是必需的。", - "Diff": "" + "Diff": "", + "OTPSecretKey": "OTP Secret Key", + "OTPSecretKeyHelpText": "Optional. If the target account also requires a one-time password, set the OTP secret key here or click Generate to create a valid one.", + "CurrentOTPCode": "Current OTP Code", + "OTPCode": "OTP Code", + "Authenticator": "Authenticator" } diff --git a/src/i18n/langs/zh.json b/src/i18n/langs/zh.json index 6d1d5ad2d8..0b1bb57db7 100644 --- a/src/i18n/langs/zh.json +++ b/src/i18n/langs/zh.json @@ -19,5 +19,10 @@ "Other": "其他", "Details": "详情", "Confirm": "确认", - "Ignore": "忽略" + "Ignore": "忽略", + "OTPSecretKey": "OTP 密钥", + "OTPSecretKeyHelpText": "可选。如果目标账号登录还需要动态口令,可以填写 OTP 密钥,或者点“生成”自动生成一组可用密钥。", + "CurrentOTPCode": "当前 OTP 验证码", + "OTPCode": "OTP 验证码", + "Authenticator": "身份验证器" } diff --git a/src/i18n/langs/zh_hant.json b/src/i18n/langs/zh_hant.json index cd7765272c..9915995028 100644 --- a/src/i18n/langs/zh_hant.json +++ b/src/i18n/langs/zh_hant.json @@ -7,5 +7,10 @@ "notAlphanumericUnderscore": "仅支持字母、数字和下划线。", "notParenthesis": "不能包含括号。", "InvalidJson": "JSON 格式错误.", - "requiredHasUserNameMapped": "用户名属性是必需的。" + "requiredHasUserNameMapped": "用户名属性是必需的。", + "OTPSecretKey": "OTP 密鑰", + "OTPSecretKeyHelpText": "可選。如果目標賬號登錄還需要動態口令,可以填寫 OTP 密鑰,或者點擊「生成」自動生成一組可用密鑰。", + "CurrentOTPCode": "當前 OTP 驗證碼", + "OTPCode": "OTP 驗證碼", + "Authenticator": "身份驗證器" } diff --git a/src/views/accounts/Account/AccountDetail/Detail.vue b/src/views/accounts/Account/AccountDetail/Detail.vue index 06161ac4b1..e8059ef744 100644 --- a/src/views/accounts/Account/AccountDetail/Detail.vue +++ b/src/views/accounts/Account/AccountDetail/Detail.vue @@ -15,6 +15,11 @@ :url="secretUrl" :visible.sync="showViewSecretDialog" /> + { + vm.showViewOTPCodeDialog = false + setTimeout(() => { + vm.showViewOTPCodeDialog = true + }) + } + }) + }, { title: this.$t('ClearSecret'), attrs: { @@ -238,10 +263,24 @@ export default { } }, computed: { + secretUrl() { + return this.object?.id ? `/api/v1/accounts/account-secrets/${this.object.id}/` : '' + }, pushAccountMethod() { return this.object.asset?.auto_config?.push_account_method || '' } }, + watch: { + 'object.has_otp_secret_key': { + handler(val) { + const action = this.quickActions.find(item => item.name === 'OTPCode') + if (action) { + this.$set(action, 'has', !!val) + } + }, + immediate: true + } + }, methods: { onCanSetting(item) { this.needSetAutoPushParams = item diff --git a/src/views/accounts/AccountTemplate/Detail/Account.vue b/src/views/accounts/AccountTemplate/Detail/Account.vue index 43ae1328f2..c903b6faa5 100644 --- a/src/views/accounts/AccountTemplate/Detail/Account.vue +++ b/src/views/accounts/AccountTemplate/Detail/Account.vue @@ -22,6 +22,11 @@ :url="secretUrl" :visible.sync="showViewSecretDialog" /> + @@ -33,12 +38,14 @@ import { ActionsFormatter, DetailFormatter } from '@/components/Table/TableForma import TwoCol from '@/layout/components/Page/TwoColPage.vue' import ViewSecret from '@/components/Apps/AccountListTable/ViewSecret' +import ViewOTPCode from '@/components/Apps/AccountListTable/ViewOTPCode' export default { name: 'AccountTemplateChangeSecret', components: { TwoCol, ViewSecret, + ViewOTPCode, QuickActions, GenericListTable }, @@ -54,8 +61,10 @@ export default { return { detailDrawer: () => import('@/views/accounts/AccountDiscover/TaskDetail/index.vue'), visible: false, + account: {}, secretUrl: '', showViewSecretDialog: false, + showViewOTPCodeDialog: false, quickActions: [ { title: this.$t('SyncUpdateAccountInfo'), @@ -118,6 +127,20 @@ export default { hasClone: false, moreActionsTitle: this.$t('More'), extraActions: [ + { + name: 'OTPCode', + title: this.$t('OTPCode'), + has: ({ row }) => !!row.has_otp_secret_key, + can: this.$hasPerm('accounts.view_accountsecret'), + type: 'primary', + callback: ({ row }) => { + vm.account = row + vm.showViewOTPCodeDialog = false + setTimeout(() => { + vm.showViewOTPCodeDialog = true + }) + } + }, { name: 'View', title: this.$t('View'),