Compare commits

...

64 Commits

Author SHA1 Message Date
zhaojisen
3e009c17fd fixed: Fixed an issue where multiple selections across pages could not be reversed 2024-12-04 15:14:44 +08:00
feng
66532f4d4b fix: ticket duplicate submission 2024-11-26 16:27:27 +08:00
wangruidong
42f27eb30f perf: users use Select2 2024-10-17 10:34:54 +08:00
wangruidong
57920bf771 fix: user lack permission to view the type tree 2024-10-10 17:11:40 +08:00
ZhaoJiSen
290772f44e Merge pull request #4384 from jumpserver/pr@v3@fix_special_chara
fixed: Fixed + and - not being special characters
2024-10-09 18:37:03 +08:00
zhaojisen
f140f2f59e fixed: Fixed + and - not being special characters 2024-10-09 18:35:40 +08:00
zhaojisen
7b1883e012 fixed: Fixed + and - not being special characters 2024-10-09 18:28:00 +08:00
wisonic
352ac7e828 fix: assets proportion tooltip cause page bounced at first time 2024-09-27 15:16:26 +08:00
ZhaoJiSen
1cbd58664c Merge pull request #4375 from jumpserver/pr@v3@perf_import_style
perf: Import UI style optimizations
2024-09-26 17:35:26 +08:00
zhaojisen
e48da6be9b perf: Import UI style optimizations 2024-09-26 17:30:14 +08:00
wangruidong
fa31b36550 fix: xterm output truncate 2024-09-26 17:16:22 +08:00
ibuler
6b93a6563d perf: revert pre org if logout 2024-08-30 17:02:49 +08:00
feng
d561701049 perf: Ticket flow rule user display name(username) 2024-08-19 16:06:55 +08:00
Ewall555
edbf477c1e perf: Translate ticket cancel button 2024-08-15 15:49:49 +08:00
ZhaoJiSen
6a2578b339 Merge pull request #4292 from jumpserver/pr@v3@fix_loading
perf: add button loading status
2024-08-08 15:01:35 +08:00
zhaojisen
2cb7569cb0 perf: add button loading status 2024-08-08 15:00:11 +08:00
fit2bot
9e0c623b9a perf: add button loading status (#4287)
* perf: add button loading status

* perf: add button loading status

---------

Co-authored-by: zhaojisen <1301338853@qq.com>
2024-08-08 11:38:04 +08:00
feng
40bf040501 perf: Asset authorization: The number of accounts displayed is incorrect ignore @SPEC 2024-08-07 17:51:21 +08:00
halo
efee7c7bbf perf: Email service authentication username is optional 2024-07-26 14:20:20 +08:00
ZhaoJiSen
5daecb84ae Merge pull request #4251 from jumpserver/pr@v3@fix_data_refresh
fixed: Fixed an issue where the validation date does not refresh
2024-07-23 10:09:16 +08:00
feng
3be325214d perf: You can modify sudo permissions multiple times 2024-07-22 18:18:11 +08:00
zhaojisen
581509f42f fixed: Fixed an issue where the validation date does not refresh 2024-07-22 14:52:45 +08:00
zhaojisen
654b36b064 fixed: Fixed the issue that parameter push parameters could not be saved 2024-07-17 10:49:02 +08:00
feng626
dcec73ae67 Merge pull request #4224 from jumpserver/pr@v3@download
perf: Downloading files does not trigger the beforeunload event
2024-07-16 12:42:48 +08:00
feng
00bafa8164 perf: Downloading files does not trigger the beforeunload event 2024-07-16 12:40:31 +08:00
zhaojisen
da09af79a7 fixed:Fixed an issue where the user was unable to enter non-MD content 2024-07-15 19:08:46 +08:00
zhaojisen
b596815ea5 fixed:Fixed an issue where the user was unable to enter non-MD content 2024-07-15 19:08:46 +08:00
ibuler
cb37273e80 perf: table search two times, one init one search 2024-07-12 10:20:55 +08:00
zhaojisen
c5bf7d0ad2 fixed: Fixed an issue where push parameters could not be saved when adding an asset account 2024-07-11 15:18:11 +08:00
wangruidong
c31195a67a perf: profile improvement mfa disabled 2024-07-10 14:21:27 +08:00
wangruidong
1eb59b379a perf: profile improvement mfa cancel hide 2024-07-09 16:14:43 +08:00
ZhaoJiSen
b7cee17156 Merge pull request #4188 from jumpserver/pr@v3@fix_unuse_item
fixed: Remove unused fields
2024-07-08 19:01:07 +08:00
zhaojisen
f1c8874010 fixed: Remove unused fields 2024-07-08 18:57:37 +08:00
Gerry.tan
5ccaa3b77d perf: 优化用户校验页面对密码进行加密传输 2024-06-24 10:38:00 +08:00
w940853815
27d3637330 Revert "fix: 待我审批列表页面默认过滤出还未审批的工单"
This reverts commit 91c44d0500.
2024-06-19 19:33:40 +08:00
wangruidong
9c8ceb04f0 perf: Disable editing labels for the root organization 2024-06-19 15:46:46 +08:00
wangruidong
ccd7b319c8 fix: 审计台仪表盘今日数据有问题 2024-06-18 17:59:18 +08:00
feng626
55637c7fa1 Merge pull request #4063 from jumpserver/pr@dev@account_template
fix: Account tempalte update secret failed
2024-06-18 14:54:14 +08:00
feng
4e95c88318 fix: Account tempalte update secret failed 2024-06-18 14:52:50 +08:00
wangruidong
1ff49ca16d perf: 系统设置-组织管理:最新创建成功后返回列表未按创建时间排序 2024-06-17 17:30:34 +08:00
wangruidong
91c44d0500 fix: 待我审批列表页面默认过滤出还未审批的工单 2024-06-13 16:15:35 +08:00
wangruidong
0b3a9844f7 fix: Crontab - Failed to set minute range 2024-06-13 10:40:22 +08:00
feng
95b58f3c96 perf: Complete asset hardware information 2024-06-11 18:49:03 +08:00
wangruidong
128b9c79ba fix: 工单-待我审批默认筛选打开的工单 2024-06-11 17:46:09 +08:00
halo
4eda83f83d perf: 优化图标不对齐的问题 2024-06-07 15:55:20 +08:00
halo
4cd0071054 feat: 支持批量测试资产可连接性 2024-06-07 11:18:35 +08:00
wangruidong
67a2a9be6a If non-existent values are entered into the select component, won't trigger a search request. 2024-06-05 16:01:46 +08:00
ibuler
f927a2a3cc perf: action 添加 token 2024-06-04 10:32:48 +08:00
Gerry.tan
ca40cb34da feat: 新增 dameng 图标 2024-05-31 11:11:06 +08:00
feng
d725e5497d perf: console dashboard api 2024-05-21 16:26:34 +08:00
ibuler
56f6c17275 perf: 优先选择上个 org 切换 2024-05-16 13:53:47 +08:00
ibuler
e1bde89b29 fix: 修复切换到全局组织回不来的问题
perf: 修改组织切换
2024-05-15 19:14:58 +08:00
feng626
e9da168c9f Merge pull request #3930 from jumpserver/pr@dev@mfa
perf: User personal settings mfa new window opens
2024-05-15 16:41:15 +08:00
feng
c19ef24ec9 perf: User personal settings mfa new window opens 2024-05-15 16:40:17 +08:00
wangruidong
fb7c4a8b2a fix: 竖屏审批工单时,动作显示不出来 2024-05-15 15:33:03 +08:00
wangruidong
428ba49f9c perf: 根据type生成导出文件名 2024-05-11 10:02:53 +08:00
ibuler
7602d6e270 fix: 修复自动切换到 root org 回不来的问题 2024-05-10 19:07:46 +08:00
wangruidong
7b62ce2d33 fix: 批量传输下载结果文件名undefined 2024-05-07 14:42:06 +08:00
jiangweidong
6ed40c45b0 perf: 云同步支持同步完成后自动删除云端已经释放的资产 2024-05-07 14:40:29 +08:00
wangruidong
31238e0398 fix: 命令存储创建es后跳转路由不对 2024-04-28 10:24:44 +08:00
wangruidong
676ac2bbf6 perf: 创建、更新用户时MFA选项根据系统设置选项进行动态渲染 2024-04-26 11:35:12 +08:00
Bai
d5415b84c9 feat: Support asset tree node drag to another one 2024-04-24 18:06:12 +08:00
Bai
5e91917ba4 perf: 优化 Web 资产详情时根据 autofill 类型返回对应的 spec_info 信息 2024-04-23 13:08:59 +08:00
zhaojisen
c4361b4c17 perf: 修复默认值相关内容,优化按钮禁用条件 2024-04-23 10:17:43 +08:00
68 changed files with 556 additions and 282 deletions

View File

@@ -10,3 +10,4 @@ jobs:
- uses: jumpserver/action-generic-handler@master - uses: jumpserver/action-generic-handler@master
env: env:
GITHUB_TOKEN: ${{ secrets.PRIVATE_TOKEN }} GITHUB_TOKEN: ${{ secrets.PRIVATE_TOKEN }}
I18N_TOKEN: ${{ secrets.I18N_TOKEN }}

View File

@@ -34,6 +34,7 @@
"css-color-function": "^1.3.3", "css-color-function": "^1.3.3",
"decimal.js": "^10.4.3", "decimal.js": "^10.4.3",
"deepmerge": "^4.2.2", "deepmerge": "^4.2.2",
"dompurify": "^3.1.6",
"echarts": "4.7.0", "echarts": "4.7.0",
"element-ui": "2.13.2", "element-ui": "2.13.2",
"eslint-plugin-html": "^6.0.0", "eslint-plugin-html": "^6.0.0",

View File

@@ -151,6 +151,3 @@ export default {
} }
} }
</script> </script>
<style scoped>
</style>

View File

@@ -353,7 +353,7 @@ export default {
name: 'BulkVerify', name: 'BulkVerify',
title: this.$t('accounts.BulkVerify'), title: this.$t('accounts.BulkVerify'),
type: 'primary', type: 'primary',
fa: 'fa-handshake-o', fa: 'fa-link',
can: ({ selectedRows }) => { can: ({ selectedRows }) => {
return selectedRows.length > 0 && return selectedRows.length > 0 &&
['clickhouse', 'redis', 'website', 'chatgpt'].indexOf(selectedRows[0].asset.type.value) === -1 && ['clickhouse', 'redis', 'website', 'chatgpt'].indexOf(selectedRows[0].asset.type.value) === -1 &&

View File

@@ -83,6 +83,10 @@ export default {
type: String, type: String,
default: '' default: ''
}, },
type: {
type: String,
default: 'account'
},
title: { title: {
type: String, type: String,
default: function() { default: function() {
@@ -136,7 +140,8 @@ export default {
name: this.secretInfo.name, name: this.secretInfo.name,
secret: encryptPassword(this.modifiedSecret) secret: encryptPassword(this.modifiedSecret)
} }
this.$axios.patch(`/api/v1/accounts/accounts/${this.account.id}/`, params).then(() => { const url = this.type === 'account' ? `/api/v1/accounts/accounts` : `/api/v1/accounts/account-templates`
this.$axios.patch(`${url}/${this.account.id}/`, params).then(() => {
this.$message.success(this.$tc('common.updateSuccessMsg')) this.$message.success(this.$tc('common.updateSuccessMsg'))
}) })
}, },

View File

@@ -1,6 +1,7 @@
<template> <template>
<Dialog <Dialog
:close-on-click-modal="false" :close-on-click-modal="false"
:loading-status="!isLoaded"
:title="$tc('assets.Assets')" :title="$tc('assets.Assets')"
custom-class="asset-select-dialog" custom-class="asset-select-dialog"
top="2vh" top="2vh"
@@ -21,6 +22,7 @@
:tree-setting="treeSetting" :tree-setting="treeSetting"
class="tree-table" class="tree-table"
v-bind="$attrs" v-bind="$attrs"
@loaded="handleTableLoaded"
/> />
</Dialog> </Dialog>
</template> </template>
@@ -63,6 +65,7 @@ export default {
data() { data() {
const vm = this const vm = this
return { return {
isLoaded: false,
dialogVisible: false, dialogVisible: false,
rowSelected: _.cloneDeep(this.value) || [], rowSelected: _.cloneDeep(this.value) || [],
rowsAdd: [], rowsAdd: [],
@@ -143,6 +146,9 @@ export default {
if (selectValueIndex > -1) { if (selectValueIndex > -1) {
this.rowSelected.splice(selectValueIndex, 1) this.rowSelected.splice(selectValueIndex, 1)
} }
},
handleTableLoaded() {
this.isLoaded = true
} }
} }
} }

View File

@@ -28,7 +28,6 @@
<script> <script>
import Select2 from '@/components/Form/FormFields/Select2.vue' import Select2 from '@/components/Form/FormFields/Select2.vue'
import AssetSelectDialog from './dialog.vue' import AssetSelectDialog from './dialog.vue'
import { b } from 'css-color-function/lib/adjusters'
export default { export default {
componentName: 'AssetSelect', componentName: 'AssetSelect',
@@ -81,7 +80,6 @@ export default {
} }
}, },
methods: { methods: {
b,
handleFocus() { handleFocus() {
this.$refs.select2.selectRef.blur() this.$refs.select2.selectRef.blur()
this.dialogVisible = true this.dialogVisible = true

View File

@@ -31,6 +31,10 @@ export default {
type: String, type: String,
default: '/api/v1/assets/assets/' default: '/api/v1/assets/assets/'
}, },
typeUrl: {
type: String,
default: '/api/v1/assets/nodes/category/tree/'
},
nodeUrl: { nodeUrl: {
type: String, type: String,
default: '/api/v1/assets/nodes/' default: '/api/v1/assets/nodes/'
@@ -101,9 +105,9 @@ export default {
showAssets: false, showAssets: false,
showSearch: false, showSearch: false,
customTreeHeaderName: this.$t('assets.BuiltinTree'), customTreeHeaderName: this.$t('assets.BuiltinTree'),
url: '/api/v1/assets/nodes/category/tree/', url: this.typeUrl,
nodeUrl: this.treeSetting?.nodeUrl || this.nodeUrl, nodeUrl: this.treeSetting?.nodeUrl || this.nodeUrl,
treeUrl: `/api/v1/assets/nodes/category/tree/?assets=${showAssets ? '1' : '0'}&count_resource=${this.treeSetting.countResource || 'asset'}`, treeUrl: `${this.typeUrl}?assets=${showAssets ? '1' : '0'}&count_resource=${this.treeSetting.countResource || 'asset'}`,
callback: { callback: {
onSelected: (event, treeNode) => this.getAssetsUrl(treeNode) onSelected: (event, treeNode) => this.getAssetsUrl(treeNode)
} }

View File

@@ -82,6 +82,7 @@
</template> </template>
<script> <script>
import Dialog from '@/components/Dialog/index.vue' import Dialog from '@/components/Dialog/index.vue'
import { encryptPassword } from '@/utils/crypto'
export default { export default {
name: 'UserConfirmDialog', name: 'UserConfirmDialog',
@@ -199,7 +200,7 @@ export default {
const data = { const data = {
confirm_type: this.confirmTypeRequired, confirm_type: this.confirmTypeRequired,
mfa_type: this.confirmTypeRequired === 'mfa' ? this.subTypeSelected : '', mfa_type: this.confirmTypeRequired === 'mfa' ? this.subTypeSelected : '',
secret_key: this.secretValue secret_key: this.confirmTypeRequired === 'password' ? encryptPassword(this.secretValue) : this.secretValue
} }
this.$axios.post(`/api/v1/authentication/confirm/`, data).then(res => { this.$axios.post(`/api/v1/authentication/confirm/`, data).then(res => {
this.callback() this.callback()

View File

@@ -106,16 +106,28 @@ export default {
if (Array.isArray(value)) { if (Array.isArray(value)) {
if (typeof value[0] === 'object') { if (typeof value[0] === 'object') {
value.forEach(item => { const firstValue = value[0]
const fieldName = `${name}.${item.name}` if (firstValue.hasOwnProperty('name')) {
if (excludes.includes(fieldName)) { value.forEach(item => {
return const fieldName = `${name}.${item.name}`
} if (excludes.includes(fieldName)) {
this.items.push({ return
key: item.label, }
value: item.value this.items.push({
key: item.label,
value: item.value
})
}) })
}) } else {
value.forEach((item, index) => {
const v = Object.entries(item).map(([key, value]) => `${key}:${value}`).join(', ')
const data = { value: v }
if (index === 0) {
data['key'] = label
}
this.items.push(data)
})
}
} else if (typeof value[0] === 'string') { } else if (typeof value[0] === 'string') {
value.forEach((item, index) => { value.forEach((item, index) => {
let data = {} let data = {}

View File

@@ -9,11 +9,13 @@
v-bind="$attrs" v-bind="$attrs"
v-on="$listeners" v-on="$listeners"
> >
<slot /> <div v-loading="loadingStatus">
<slot />
</div>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<slot name="footer"> <slot name="footer">
<el-button v-if="showCancel && showButtons" @click="onCancel">{{ cancelTitle }}</el-button> <el-button v-if="showCancel && showButtons" @click="onCancel">{{ cancelTitle }}</el-button>
<el-button v-if="showConfirm && showButtons" :loading="loadingStatus" type="primary" @click="onConfirm"> <el-button v-if="showConfirm && showButtons" :disabled="loadingStatus" type="primary" @click="onConfirm">
{{ confirmTitle }} {{ confirmTitle }}
</el-button> </el-button>
</slot> </slot>
@@ -71,13 +73,16 @@ export default {
} }
}, },
data() { data() {
return {} return {
}
}, },
computed: { computed: {
iWidth() { iWidth() {
return this.$store.getters.isMobile ? '1000px' : this.width return this.$store.getters.isMobile ? '1000px' : this.width
} }
}, },
mounted() {
},
methods: { methods: {
onCancel() { onCancel() {
this.$emit('cancel') this.$emit('cancel')

View File

@@ -7,7 +7,6 @@ import BasicTree from '@/components/Form/FormFields/BasicTree.vue'
import JsonEditor from '@/components/Form/FormFields/JsonEditor.vue' import JsonEditor from '@/components/Form/FormFields/JsonEditor.vue'
import { assignIfNot } from '@/utils/common' import { assignIfNot } from '@/utils/common'
import TagInput from '@/components/Form/FormFields/TagInput.vue' import TagInput from '@/components/Form/FormFields/TagInput.vue'
import TransferSelect from '@/components/Form/FormFields/TransferSelect.vue'
export class FormFieldGenerator { export class FormFieldGenerator {
constructor(emit) { constructor(emit) {
@@ -134,9 +133,6 @@ export class FormFieldGenerator {
case 'comment': case 'comment':
field.el.type = 'textarea' field.el.type = 'textarea'
break break
case 'users':
field.component = TransferSelect
field.el.label = field.label
} }
return field return field
} }

View File

@@ -7,18 +7,11 @@
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="2">
{{ this.$t('common.CronTab.from') }}
<el-input-number v-model="cycle01" :max="60" :min="0" size="mini" /> -
<el-input-number v-model="cycle02" :max="60" :min="0" size="mini" /> {{ this.$t('common.CronTab.min') }}
</el-radio>
</el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model="radioValue" :label="3"> <el-radio v-model="radioValue" :label="3">
{{ this.$t('common.CronTab.from') }} {{ this.$t('common.CronTab.from') }}
<el-input-number v-model="average02" :max="60" :min="1" size="mini" /> {{ this.$t('common.CronTab.min') }}{{ this.$t('common.CronTab.executeOnce') }} <el-input-number v-model="average02" :max="60" :min="1" size="mini" />
{{ this.$t('common.CronTab.min') }}{{ this.$t('common.CronTab.executeOnce') }}
</el-radio> </el-radio>
</el-form-item> </el-form-item>
@@ -33,7 +26,7 @@
size="small" size="small"
style="width:100%" style="width:100%"
> >
<el-option v-for="item in 60" :key="item" :value="item-1">{{ item-1 }}</el-option> <el-option v-for="item in 60" :key="item" :value="item-1">{{ item - 1 }}</el-option>
</el-select> </el-select>
</el-radio> </el-radio>
</el-form-item> </el-form-item>
@@ -158,7 +151,7 @@ export default {
</script> </script>
<style scoped> <style scoped>
.el-form-item--small.el-form-item { .el-form-item--small.el-form-item {
margin-bottom: 10px; margin-bottom: 10px;
} }
</style> </style>

View File

@@ -225,9 +225,6 @@ export default {
handler(newValue, oldValue) { handler(newValue, oldValue) {
}, },
deep: true deep: true
},
iOptions(val) {
this.remote = val.length !== 0
} }
}, },
async mounted() { async mounted() {

View File

@@ -11,6 +11,7 @@
/> />
<Dialog <Dialog
v-if="showTransfer" v-if="showTransfer"
:loading-status="!isLoaded"
:close-on-click-modal="false" :close-on-click-modal="false"
:title="label" :title="label"
:visible.sync="showTransfer" :visible.sync="showTransfer"
@@ -18,6 +19,7 @@
width="730px" width="730px"
@cancel="handleTransCancel" @cancel="handleTransCancel"
@confirm="handleTransConfirm" @confirm="handleTransConfirm"
v-on="$listeners"
> >
<krryPaging v-if="selectInitialized" ref="pageTransfer" class="transfer" v-bind="pagingTransfer" /> <krryPaging v-if="selectInitialized" ref="pageTransfer" class="transfer" v-bind="pagingTransfer" />
</Dialog> </Dialog>
@@ -77,13 +79,16 @@ export default {
if (keyword) { if (keyword) {
params['search'] = keyword params['search'] = keyword
} }
this.isLoaded = false
const data = await this.$axios.get(url, { params }) const data = await this.$axios.get(url, { params })
this.isLoaded = true
return data['results'].map(item => { return data['results'].map(item => {
const n = transformOption(item) const n = transformOption(item)
return { id: n.value, label: n.label } return { id: n.value, label: n.label }
}) })
} }
return { return {
isLoaded: false,
showTransfer: false, showTransfer: false,
selectInitialized: false, selectInitialized: false,
select2: { select2: {
@@ -166,7 +171,3 @@ export default {
} }
} }
</script> </script>
<style scoped>
</style>

View File

@@ -42,7 +42,7 @@ export default {
patterns.push([/\d/, i18n.t('common.password.NUMBER_REQUIRED')]) patterns.push([/\d/, i18n.t('common.password.NUMBER_REQUIRED')])
} }
if (passwordRule['SECURITY_PASSWORD_SPECIAL_CHAR']) { if (passwordRule['SECURITY_PASSWORD_SPECIAL_CHAR']) {
const pattern = new RegExp("[`~!@#$^&*()=|{}':;',\\[\\].<>/?~@#¥……&*()——|{}【】‘;:”“'。,、?]") const pattern = new RegExp("[`~!@#$^&*()=|{}':;',\\[\\].<>/?~@#¥……&*()——|{}【】‘;:”“'。,、?_+-]")
patterns.push([pattern, i18n.t('common.password.SPECIAL_CHAR_REQUIRED')]) patterns.push([pattern, i18n.t('common.password.SPECIAL_CHAR_REQUIRED')])
} }
for (const [pattern, msg] of patterns) { for (const [pattern, msg] of patterns) {

View File

@@ -53,27 +53,34 @@ export default {
}, },
props: { props: {
boxTitle: { boxTitle: {
type: Array type: Array,
default: () => []
}, },
boxOperation: { boxOperation: {
type: Array type: Array,
default: () => []
}, },
// 地域数据 // 地域数据
dataObj: { dataObj: {
type: Object type: Object,
default: () => {}
}, },
// 已选数据 // 已选数据
selectedData: { selectedData: {
type: Array type: Array,
default: () => []
}, },
onChangeSelected: { onChangeSelected: {
type: Function type: Function,
default: () => () => {}
}, },
filterable: { filterable: {
type: Boolean type: Boolean,
default: () => false
}, },
filterPlaceholder: { filterPlaceholder: {
type: String type: String,
default: () => ''
} }
}, },
data() { data() {

View File

@@ -46,7 +46,7 @@
<div class="vip-footer"> <div class="vip-footer">
<el-button <el-button
type="text" type="text"
:disabled="selectedDistrict.length > 0 ? false : true" :disabled="selectedDistrict.length<=0"
size="small" size="small"
round round
@click="checkedSelected" @click="checkedSelected"
@@ -62,23 +62,29 @@ export default {
components: {}, components: {},
props: { props: {
title: { title: {
type: String type: String,
default: () => ''
}, },
operation: { operation: {
type: String type: String,
default: () => ''
}, },
operateId: { operateId: {
type: Number type: Number,
default: () => 0
}, },
// 区域数据 // 区域数据
districtList: { districtList: {
type: Array type: Array,
default: () => []
}, },
filterable: { filterable: {
type: Boolean type: Boolean,
default: () => false
}, },
filterPlaceholder: { filterPlaceholder: {
type: String type: String,
default: () => ''
} }
}, },
data() { data() {

View File

@@ -34,12 +34,14 @@ class StrategyNormal extends StrategyAbstract {
onSelectionChange(val) { onSelectionChange(val) {
this.elDataTable.selected = val this.elDataTable.selected = val
} }
/** /**
* toggleRowSelection和clearSelection的表现与el-table一致 * toggleRowSelection和clearSelection的表现与el-table一致
*/ */
toggleRowSelection(...args) { toggleRowSelection(...args) {
return this.elTable.toggleRowSelection(...args) return this.elTable.toggleRowSelection(...args)
} }
clearSelection() { clearSelection() {
return this.elTable.clearSelection() return this.elTable.clearSelection()
} }
@@ -50,12 +52,12 @@ class StrategyNormal extends StrategyAbstract {
*/ */
class StrategyPersistSelection extends StrategyAbstract { class StrategyPersistSelection extends StrategyAbstract {
/** /**
* el-tableselection-change事件不适用于开启跨页保存的情况。 * el-tableselection-change 事件不适用于开启跨页保存的情况。
* 比如当开启persistSelection时发生以下两个场景 * 比如,当开启 persistSelection时发生以下两个场景
* 1. 用户点击翻页 * 1. 用户点击翻页
* 2. 用户点击行首的切换全选项按钮,清空当前页多选项数据 * 2. 用户点击行首的切换全选项按钮,清空当前页多选项数据
* 其中场景1应该保持selected不变而场景2只应该从selected移除当前页所有行保留其他页面的多选状态。 * 其中场景 1 应该保持 selected 不变;而场景 2 只应该从 selected 移除当前页所有行,保留其他页面的多选状态。
* 但el-tableselection-change事件在两个场景中无差别发生所以这里不处理这个事件 * 但 el-tableselection-change 事件在两个场景中无差别发生,所以这里不处理这个事件
*/ */
/** /**
@@ -63,13 +65,19 @@ class StrategyPersistSelection extends StrategyAbstract {
*/ */
onSelect(selection, row) { onSelect(selection, row) {
const isChosen = selection.indexOf(row) > -1 const isChosen = selection.indexOf(row) > -1
this.toggleRowSelection(row, isChosen) this.toggleRowSelection(row, isChosen)
} }
/** /**
* 用户切换当前页的多选 * 用户切换当前页的多选
*/ */
onSelectAll(selection, selectable = () => true) { onSelectAll(selection, selectable = () => true) {
const isSelected = !!selection.length // 获取当前所有已选择的项
const selectedRows = this.elDataTable.data.filter(r => selection.includes(r))
// 判断是否已全选
const isSelected = this.elDataTable.data.every(r => selectable(r) && selectedRows.includes(r))
this.elDataTable.data.forEach(r => { this.elDataTable.data.forEach(r => {
if (selectable(r)) { if (selectable(r)) {
this.toggleRowSelection(r, isSelected) this.toggleRowSelection(r, isSelected)
@@ -83,33 +91,42 @@ class StrategyPersistSelection extends StrategyAbstract {
toggleRowSelection(row, isSelected) { toggleRowSelection(row, isSelected) {
const { id, selected } = this.elDataTable const { id, selected } = this.elDataTable
const foundIndex = selected.findIndex(r => r[id] === row[id]) const foundIndex = selected.findIndex(r => r[id] === row[id])
if (typeof isSelected === 'undefined') { if (typeof isSelected === 'undefined') {
isSelected = foundIndex <= -1 isSelected = foundIndex <= -1
} }
if (isSelected && foundIndex === -1) { if (isSelected && foundIndex === -1) {
selected.push(row) selected.push(row)
} else if (!isSelected && foundIndex > -1) { } else if (!isSelected && foundIndex > -1) {
selected.splice(foundIndex, 1) selected.splice(foundIndex, 1)
} }
this.elDataTable.$emit('toggle-row-selection', isSelected, row) this.elDataTable.$emit('toggle-row-selection', isSelected, row)
this.updateElTableSelection() this.updateElTableSelection()
} }
clearSelection() { clearSelection() {
this.elDataTable.selected = [] this.elDataTable.selected = []
this.updateElTableSelection() this.updateElTableSelection()
} }
/** /**
* 将selected状态同步到el-table中 * 将selected状态同步到el-table中
*/ */
updateElTableSelection() { updateElTableSelection() {
const { data, id, selected } = this.elDataTable const { data, id, selected } = this.elDataTable
// 历史勾选的行已经不在当前页了所以要将当前页的行数据和selected合并 // 历史勾选的行已经不在当前页了所以要将当前页的行数据和selected合并
const mergeData = _.uniqWith([...data, ...selected], _.isEqual) const mergeData = _.uniqWith([...data, ...selected], _.isEqual)
mergeData.forEach(r => { mergeData.forEach(r => {
const isSelected = !!selected.find(r2 => r[id] === r2[id]) const isSelected = !!selected.find(r2 => r[id] === r2[id])
if (!this.elTable) { if (!this.elTable) {
return return
} }
this.elTable.toggleRowSelection(r, isSelected) this.elTable.toggleRowSelection(r, isSelected)
}) })
} }

View File

@@ -153,6 +153,8 @@ export default {
this.toggleRowSelection(row, true) this.toggleRowSelection(row, true)
} }
} }
this.$emit('loaded')
}, },
handleSizeChange(val) { handleSizeChange(val) {
localStorage.setItem('paginationSize', val) localStorage.setItem('paginationSize', val)

View File

@@ -48,6 +48,7 @@
import Dialog from '@/components/Dialog/index.vue' import Dialog from '@/components/Dialog/index.vue'
import { createSourceIdCache } from '@/api/common' import { createSourceIdCache } from '@/api/common'
import * as queryUtil from '@/components/Table/DataTable/compenents/el-data-table/utils/query' import * as queryUtil from '@/components/Table/DataTable/compenents/el-data-table/utils/query'
import { download } from '@/utils/common'
export default { export default {
name: 'ExportDialog', name: 'ExportDialog',
@@ -187,10 +188,7 @@ export default {
}) })
}, },
downloadCsv(url) { downloadCsv(url) {
const a = document.createElement('a') download(url)
a.href = url
a.click()
window.URL.revokeObjectURL(url)
}, },
async defaultPerformExport(selectRows, exportOption, q, exportTypeOption) { async defaultPerformExport(selectRows, exportOption, q, exportTypeOption) {
const url = (process.env.VUE_APP_ENV === 'production') ? (`${this.url}`) : (`${process.env.VUE_APP_BASE_API}${this.url}`) const url = (process.env.VUE_APP_ENV === 'production') ? (`${this.url}`) : (`${process.env.VUE_APP_BASE_API}${this.url}`)

View File

@@ -68,7 +68,7 @@
<script> <script>
import Dialog from '@/components/Dialog/index.vue' import Dialog from '@/components/Dialog/index.vue'
import ImportTable from '@/components/Table/ListTable/TableAction/ImportTable.vue' import ImportTable from '@/components/Table/ListTable/TableAction/ImportTable.vue'
import { getErrorResponseMsg } from '@/utils/common' import { download, getErrorResponseMsg } from '@/utils/common'
import { createSourceIdCache } from '@/api/common' import { createSourceIdCache } from '@/api/common'
export default { export default {
@@ -221,10 +221,7 @@ export default {
this.$message.success(msg) this.$message.success(msg)
}, },
downloadCsv(url) { downloadCsv(url) {
const a = document.createElement('a') download(url)
a.href = url
a.click()
window.URL.revokeObjectURL(url)
}, },
async handleImportConfirm() { async handleImportConfirm() {
await this.$refs['importTable'].performUpload() await this.$refs['importTable'].performUpload()

View File

@@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<el-row> <el-row type="flex" align="center">
<el-col :md="8" :sm="24"> <el-col :md="8" :sm="24">
<div class="tableFilter"> <div class="tableFilter">
<el-radio-group v-model="importStatusFilter" size="small"> <el-radio-group v-model="importStatusFilter" size="small">
@@ -11,7 +11,7 @@
</el-radio-group> </el-radio-group>
</div> </div>
</el-col> </el-col>
<el-col :md="8" :sm="24" style="text-align: center"> <el-col :md="16" :sm="24" style="text-align: center; display: flex; align-items: center">
<span class="summary-item summary-total"> {{ $t('common.Total') }}: {{ totalCount }}</span> <span class="summary-item summary-total"> {{ $t('common.Total') }}: {{ totalCount }}</span>
<span class="summary-item summary-success"> {{ $t('common.Success') }}: {{ successCount }}</span> <span class="summary-item summary-success"> {{ $t('common.Success') }}: {{ successCount }}</span>
<span class="summary-item summary-failed"> {{ $t('common.Failed') }}: {{ failedCount }}</span> <span class="summary-item summary-failed"> {{ $t('common.Failed') }}: {{ failedCount }}</span>

View File

@@ -122,6 +122,9 @@ export default {
return this.hasLeftActions ? 'right' : 'left' return this.hasLeftActions ? 'right' : 'left'
} }
}, },
created() {
this.$emit('done')
},
methods: { methods: {
handleTagSearch(val) { handleTagSearch(val) {
this.searchTable(val) this.searchTable(val)
@@ -144,119 +147,121 @@ export default {
</script> </script>
<style lang='scss' scoped> <style lang='scss' scoped>
.table-header { .table-header {
/*display: flex;*/ /*display: flex;*/
/*flex-direction: row;*/ /*flex-direction: row;*/
/*justify-content: space-between;*/ /*justify-content: space-between;*/
} }
.right-side-item { .right-side-item {
} }
.right-side-actions >>> .el-button { .right-side-actions > > > .el-button {
border: none; border: none;
padding: 5px; padding: 5px;
font-size: 14px; font-size: 14px;
width: 26px; width: 26px;
height: 26px; height: 26px;
color: #888; color: #888;
background-color: transparent; background-color: transparent;
} }
.right-side-actions >>> .fa { .right-side-actions > > > .fa {
height: 16px; height: 16px;
width: 16px; width: 16px;
} }
.right-side-actions >>> .el-button:hover { .right-side-actions > > > .el-button:hover {
background-color: rgb(0, 0, 0, 0.05); background-color: rgb(0, 0, 0, 0.05);
} }
.action-search >>> .el-input__suffix i { .action-search > > > .el-input__suffix i {
font-weight: 500; font-weight: 500;
color: #888; color: #888;
} }
.action-search >>> .el-cascader { .action-search > > > .el-cascader {
line-height: 32px !important; line-height: 32px !important;
} }
.right-side-actions { .right-side-actions {
display: flex; display: flex;
padding-left: 10px; padding-left: 10px;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.table-action-right-side { .table-action-right-side {
display: flex; display: flex;
justify-content: center; justify-content: center;
} }
.export-item { .export-item {
display: block; display: block;
padding: 5px 20px; padding: 5px 20px;
} }
.datepicker { .datepicker {
margin-left: 10px; margin-left: 10px;
} }
.table-header { .table-header {
line-height: 32px; line-height: 32px;
} }
.left-side { .left-side {
float: left; float: left;
display: block; display: block;
&>>> .action-item.el-dropdown {
height: 33px; & > > > .action-item.el-dropdown {
&> .el-button { height: 33px;
height: 100%;
} & > .el-button {
height: 100%;
} }
} }
}
.right-side { .right-side {
float: right; float: right;
} }
.search { .search {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
} }
.mobile .search { .mobile .search {
display: inherit; display: inherit;
} }
.mobile .search .datepicker { .mobile .search .datepicker {
margin-left: 0; margin-left: 0;
} }
.search.left { .search.left {
float: left; float: left;
padding: 0 !important; padding: 0 !important;
} }
.search.right { .search.right {
float: right; float: right;
} }
.mobile .search.right { .mobile .search.right {
float: none; float: none;
} }
.mobile .search.right .action-search { .mobile .search.right .action-search {
width: 100%; width: 100%;
} }
.mobile .right-side { .mobile .right-side {
padding-top: 5px; padding-top: 5px;
} }
.filter-field.right-side-item.action-search { .filter-field.right-side-item.action-search {
height: 34px; height: 34px;
} }
</style> </style>

View File

@@ -8,9 +8,11 @@
:selected-rows="selectedRows" :selected-rows="selectedRows"
:table-url="tableUrl" :table-url="tableUrl"
v-bind="iHeaderActions" v-bind="iHeaderActions"
@done="handleActionInitialDone"
/> />
<IBox class="table-content"> <IBox class="table-content">
<AutoDataTable <AutoDataTable
v-if="actionInit"
ref="dataTable" ref="dataTable"
:config="iTableConfig" :config="iTableConfig"
:filter-table="filter" :filter-table="filter"
@@ -73,7 +75,10 @@ export default {
return { return {
selectedRows: [], selectedRows: [],
init: false, init: false,
extraQuery: extraQuery isDeactivated: false,
extraQuery: extraQuery,
actionInit: this.headerActions.has === false,
initQuery: {}
} }
}, },
computed: { computed: {
@@ -166,14 +171,36 @@ export default {
} }
}, },
methods: { methods: {
handleActionInitialDone() {
setTimeout(() => {
this.actionInit = true
}, 100)
},
handleSelectionChange(val) { handleSelectionChange(val) {
this.selectedRows = val this.selectedRows = val
}, },
reloadTable() { reloadTable() {
this.dataTable.getList() this.dataTable.getList()
}, },
updateInitQuery(attrs) {
if (!this.actionInit) {
this.initQuery = attrs
for (const key in attrs) {
this.$set(this.extraQuery, key, attrs[key])
}
return true
}
const removeKeys = Object.keys(this.initQuery).filter(key => !attrs[key])
for (const key of removeKeys) {
this.$delete(this.extraQuery, key)
}
},
search(attrs) { search(attrs) {
this.$log.debug('ListTable: search table', attrs) this.$log.debug('ListTable: search table', attrs)
const init = this.updateInitQuery(attrs)
if (init) {
return
}
this.$emit('TagSearch', attrs) this.$emit('TagSearch', attrs)
this.$refs.dataTable?.$refs.dataTable?.search(attrs, true) this.$refs.dataTable?.$refs.dataTable?.search(attrs, true)
}, },

View File

@@ -1,5 +1,5 @@
<template> <template>
<span>{{ value }}</span> <span class="date">{{ dateValue }}</span>
</template> </template>
<script> <script>
@@ -10,24 +10,31 @@ export default {
name: 'DateFormatter', name: 'DateFormatter',
extends: BaseFormatter, extends: BaseFormatter,
data() { data() {
let value // let value
if (this.cellValue) { // if (this.cellValue) {
value = toSafeLocalDateStr(this.cellValue) // value = toSafeLocalDateStr(this.cellValue)
} else { // } else {
value = '-' // value = '-'
} // }
// const locale = this.$i18n.locale // const locale = this.$i18n.locale
// const value = dt.toLocaleString(locale, { hourCycle: 'h23' }) // const value = dt.toLocaleString(locale, { hourCycle: 'h23' })
// debug(this.$i18n.locale) // debug(this.$i18n.locale)
return { // return {
value: value // value: value
} // }
// return { // return {
// value: `${year}-${month}-${date} ${hour}:${minutes}:${seconds}` // value: `${year}-${month}-${date} ${hour}:${minutes}:${seconds}`
// } // }
return {}
},
computed: {
dateValue() {
if (this.cellValue) {
return toSafeLocalDateStr(this.cellValue)
} else {
return '-'
}
}
} }
} }
</script> </script>
<style scoped>
</style>

View File

@@ -64,7 +64,7 @@ export default {
} }
return text return text
} }
return '-' return this.items?.distribution || '-'
} }
} }
} }

View File

@@ -27,7 +27,7 @@
</a> </a>
<a <a
v-if="formatterArgs.showEditBtn" v-if="formatterArgs.showEditBtn"
class="edit-btn" :class="[{ 'disabled-link': this.$store.getters.currentOrgIsRoot },'edit-btn']"
style="padding-left: 5px" style="padding-left: 5px"
@click="showDialog = true" @click="showDialog = true"
> >
@@ -269,4 +269,11 @@ export default {
.tag-tip { .tag-tip {
margin-top: 10px; margin-top: 10px;
} }
.disabled-link {
pointer-events: none;
color: grey;
cursor: default;
text-decoration: none;
}
</style> </style>

View File

@@ -39,7 +39,7 @@ export default {
showRenameBtn: false, showRenameBtn: false,
drag: { drag: {
isCopy: false, isCopy: false,
isMove: false isMove: true
} }
}, },
callback: { callback: {

View File

@@ -18,14 +18,15 @@
/> />
</el-col> </el-col>
<el-col v-show="isShow" :span="span"> <el-col v-show="isShow" :span="span">
<VueMarkdown class="result-html" :source="iValue" :show="true" :html="false" /> <VueMarkdown class="result-html" :source="sanitizedValue" :html="false" :show="true" />
</el-col> </el-col>
</el-row> </el-row>
<VueMarkdown v-else class="source" :source="iValue" :html="false" /> <VueMarkdown v-else class="source" :html="false" :source="sanitizedValue" />
</div> </div>
</template> </template>
<script> <script>
import DOMPurify from 'dompurify'
import VueMarkdown from 'vue-markdown' import VueMarkdown from 'vue-markdown'
import 'github-markdown-css/github-markdown-light.css' import 'github-markdown-css/github-markdown-light.css'
@@ -56,6 +57,17 @@ export default {
iValue: this.value iValue: this.value
} }
}, },
computed: {
sanitizedValue() {
// 转义特殊字符
let content = this.iValue.replace(/\\/g, '\\\\').replace(/\$/g, '\\$')
// 使用 DOMPurify 进行 XSS 过滤
content = DOMPurify.sanitize(content)
return content
}
},
mounted() { mounted() {
this.$nextTick(() => { this.$nextTick(() => {
this.resizeObserver = new ResizeObserver(entries => { this.resizeObserver = new ResizeObserver(entries => {

View File

@@ -48,6 +48,7 @@ export default {
fontFamily: 'monaco, Consolas, "Lucida Console", monospace', fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
lineHeight: 1.2, lineHeight: 1.2,
fontSize: 13, fontSize: 13,
scrollback: 9999999,
rightClickSelectsWord: true, rightClickSelectsWord: true,
theme: { theme: {
background: '#fff', background: '#fff',
@@ -83,7 +84,7 @@ export default {
callback: () => { callback: () => {
this.xterm.selectAll() this.xterm.selectAll()
const text = this.xterm.getSelection() const text = this.xterm.getSelection()
const filename = `shortcut_cmd_${this.$route.query?.taskId}.log` const filename = `${this.$route.query?.type}_${this.$route.query?.taskId}.log`
downloadText(text, filename) downloadText(text, filename)
} }
} }

View File

@@ -721,6 +721,7 @@
"BatchActivate": "Batch activate", "BatchActivate": "Batch activate",
"SyncSelected": "Sync selected", "SyncSelected": "Sync selected",
"bulkDeploy": "Bulk deploy", "bulkDeploy": "Bulk deploy",
"BulkVerify": "Bulk verify",
"bulkDeleteErrorMsg": "Bulk delete failed: ", "bulkDeleteErrorMsg": "Bulk delete failed: ",
"bulkDeleteSuccessMsg": "Bulk delete success", "bulkDeleteSuccessMsg": "Bulk delete success",
"bulkRemoveErrorMsg": "Bulk remove failed: ", "bulkRemoveErrorMsg": "Bulk remove failed: ",
@@ -1930,6 +1931,7 @@
"CheckViewAcceptor": "View more acceptor", "CheckViewAcceptor": "View more acceptor",
"Assignees": "Assignees", "Assignees": "Assignees",
"Close": "Close", "Close": "Close",
"CancelTicket": "Cancel Ticket",
"OpenStatus": "Open", "OpenStatus": "Open",
"CloseStatus": "Close", "CloseStatus": "Close",
"Comment": "Comment", "Comment": "Comment",
@@ -2135,7 +2137,8 @@
"passwordWillExpiredSuffixMsg": " days.Please change your password as soon as possible.", "passwordWillExpiredSuffixMsg": " days.Please change your password as soon as possible.",
"dateLastLogin": "Date last login", "dateLastLogin": "Date last login",
"AddAllMembersWarningMsg": "Are you sure you want to add all members?", "AddAllMembersWarningMsg": "Are you sure you want to add all members?",
"disallowSelfUpdateFields": "Not allowed to modify the current fields oneself" "disallowSelfUpdateFields": "Not allowed to modify the current fields oneself",
"GlobalDisableMfaMsg": "Global enforcement has been enabled"
}, },
"notifications": { "notifications": {
"MessageType": "Message Type", "MessageType": "Message Type",
@@ -2271,6 +2274,8 @@
"HostnameStrategy": "Used to produce the asset hostname. For example, 1. Instance name (instanceDemo)2. Instance name and Partial IP (instanceDemo-250.1)", "HostnameStrategy": "Used to produce the asset hostname. For example, 1. Instance name (instanceDemo)2. Instance name and Partial IP (instanceDemo-250.1)",
"IsAlwaysUpdate": "Keep assets up to date", "IsAlwaysUpdate": "Keep assets up to date",
"FullySynchronous": "Assets fully synchronized", "FullySynchronous": "Assets fully synchronized",
"ReleaseAssets": "Release assets",
"ReleaseAssetsHelpTips": "Whether to automatically delete assets synchronized through this task and released on the cloud at the end of the task",
"AccountCreate": "Create account", "AccountCreate": "Create account",
"AccountList": "Account list", "AccountList": "Account list",
"AccountUpdate": "Update account", "AccountUpdate": "Update account",

View File

@@ -717,6 +717,7 @@
"SyncSuccessMsg": "同期に成功しました", "SyncSuccessMsg": "同期に成功しました",
"SyncSelected": "選択した同期", "SyncSelected": "選択した同期",
"bulkDeploy": "一括デプロイ", "bulkDeploy": "一括デプロイ",
"BulkVerify": "一括テスト",
"bulkSyncErrorMsg": "一括同期に失敗しました:", "bulkSyncErrorMsg": "一括同期に失敗しました:",
"bulkDeleteErrorMsg": "一括削除に失敗しました:", "bulkDeleteErrorMsg": "一括削除に失敗しました:",
"bulkDeleteSuccessMsg": "一括削除に成功しました", "bulkDeleteSuccessMsg": "一括削除に成功しました",
@@ -1926,6 +1927,7 @@
"Assignee": "処理者", "Assignee": "処理者",
"Assignees": "処理待ち", "Assignees": "処理待ち",
"Close": "閉じる", "Close": "閉じる",
"CancelTicket": "作業指示をキャンセルする",
"OpenStatus": "オン", "OpenStatus": "オン",
"CloseStatus": "閉じる", "CloseStatus": "閉じる",
"Comment": "備考", "Comment": "備考",
@@ -2124,7 +2126,8 @@
"passwordWillExpiredPrefixMsg": "パスワードはまもなく", "passwordWillExpiredPrefixMsg": "パスワードはまもなく",
"passwordWillExpiredSuffixMsg": "期限が切れた後、できるだけ早くパスワードを変更してください。", "passwordWillExpiredSuffixMsg": "期限が切れた後、できるだけ早くパスワードを変更してください。",
"AddAllMembersWarningMsg": "すべてのメンバーを追加してもよろしいですか?", "AddAllMembersWarningMsg": "すべてのメンバーを追加してもよろしいですか?",
"disallowSelfUpdateFields": "現在のフィールドを自分で変更することは許可されていません" "disallowSelfUpdateFields": "現在のフィールドを自分で変更することは許可されていません",
"GlobalDisableMfaMsg": "グローバルでの強制が有効になっています"
}, },
"notifications": { "notifications": {
"MessageType": "メッセージタイプ", "MessageType": "メッセージタイプ",
@@ -2267,6 +2270,8 @@
"HostnameStrategy": "資産を生成するためにホスト名。例: 1. インスタンス名 (instanceDemo) 2.インスタンス名と一部IP (下位2桁) (instanceDemo-250.1)", "HostnameStrategy": "資産を生成するためにホスト名。例: 1. インスタンス名 (instanceDemo) 2.インスタンス名と一部IP (下位2桁) (instanceDemo-250.1)",
"IsAlwaysUpdate": "資産は常に最新です", "IsAlwaysUpdate": "資産は常に最新です",
"FullySynchronous": "資産完全にシンクロします", "FullySynchronous": "資産完全にシンクロします",
"ReleaseAssets": "資産の同期解放",
"ReleaseAssetsHelpTips": "タスクの終了時に、このタスクを介して同期され、クラウド上で解放された資産を自動的に削除するかどうか",
"AccountCreate": "アカウントの作成", "AccountCreate": "アカウントの作成",
"AccountList": "アカウントリスト", "AccountList": "アカウントリスト",
"AccountUpdate": "アカウントの更新", "AccountUpdate": "アカウントの更新",
@@ -2425,4 +2430,4 @@
"BindResource": "リソースを関連付ける", "BindResource": "リソースを関連付ける",
"SelectResource": "リソースを選択" "SelectResource": "リソースを選択"
} }
} }

View File

@@ -771,6 +771,7 @@
"BatchActivate": "批量激活", "BatchActivate": "批量激活",
"SyncSelected": "同步所选", "SyncSelected": "同步所选",
"bulkDeploy": "批量部署", "bulkDeploy": "批量部署",
"BulkVerify": "批量测试",
"bulkDeleteErrorMsg": "批量删除失败: ", "bulkDeleteErrorMsg": "批量删除失败: ",
"bulkDeleteSuccessMsg": "批量删除成功", "bulkDeleteSuccessMsg": "批量删除成功",
"bulkRemoveErrorMsg": "批量移除失败: ", "bulkRemoveErrorMsg": "批量移除失败: ",
@@ -1912,6 +1913,7 @@
"Assignee": "处理人", "Assignee": "处理人",
"Assignees": "待处理人", "Assignees": "待处理人",
"Close": "关闭", "Close": "关闭",
"CancelTicket": "取消工单",
"OpenStatus": "审批中", "OpenStatus": "审批中",
"CloseStatus": "已完成", "CloseStatus": "已完成",
"Comment": "备注", "Comment": "备注",
@@ -2115,7 +2117,8 @@
"passwordExpired": "密码过期了", "passwordExpired": "密码过期了",
"passwordWillExpiredPrefixMsg": "密码即将在 ", "passwordWillExpiredPrefixMsg": "密码即将在 ",
"passwordWillExpiredSuffixMsg": "天 后过期,请尽快修改您的密码。", "passwordWillExpiredSuffixMsg": "天 后过期,请尽快修改您的密码。",
"disallowSelfUpdateFields": "不允许自己修改当前字段" "disallowSelfUpdateFields": "不允许自己修改当前字段",
"GlobalDisableMfaMsg": "全局已强制开启"
}, },
"notifications": { "notifications": {
"MessageType": "消息类型", "MessageType": "消息类型",
@@ -2186,6 +2189,8 @@
"HostnameStrategy": "用于生成资产主机名。例如1. 实例名称 (instanceDemo)2. 实例名称和部分IP(后两位) (instanceDemo-250.1)", "HostnameStrategy": "用于生成资产主机名。例如1. 实例名称 (instanceDemo)2. 实例名称和部分IP(后两位) (instanceDemo-250.1)",
"IsAlwaysUpdate": "资产保持最新", "IsAlwaysUpdate": "资产保持最新",
"FullySynchronous": "资产完全同步", "FullySynchronous": "资产完全同步",
"ReleaseAssets": "同步释放资产",
"ReleaseAssetsHelpTips": "是否在任务结束时,自动删除通过此任务同步下来且已经在云上释放的资产",
"AccountCreate": "创建账户", "AccountCreate": "创建账户",
"AccountList": "云账号", "AccountList": "云账号",
"AccountUpdate": "更新账户", "AccountUpdate": "更新账户",

View File

@@ -771,6 +771,7 @@
"BatchActivate": "批次啟用", "BatchActivate": "批次啟用",
"SyncSelected": "同步所選", "SyncSelected": "同步所選",
"bulkDeploy": "批次部署", "bulkDeploy": "批次部署",
"BulkVerify": "批次測試",
"bulkDeleteErrorMsg": "批次刪除失敗: ", "bulkDeleteErrorMsg": "批次刪除失敗: ",
"bulkDeleteSuccessMsg": "批次刪除成功", "bulkDeleteSuccessMsg": "批次刪除成功",
"bulkRemoveErrorMsg": "批次移除失敗: ", "bulkRemoveErrorMsg": "批次移除失敗: ",
@@ -1912,6 +1913,7 @@
"Assignee": "處理人", "Assignee": "處理人",
"Assignees": "待處理人", "Assignees": "待處理人",
"Close": "關閉", "Close": "關閉",
"CancelTicket": "取消工單",
"OpenStatus": "審批中", "OpenStatus": "審批中",
"CloseStatus": "已完成", "CloseStatus": "已完成",
"Comment": "備註", "Comment": "備註",

View File

@@ -61,7 +61,6 @@ export default {
break break
case 'logout': case 'logout':
this.logout() this.logout()
window.location.href = `${process.env.VUE_APP_LOGOUT_PATH}?next=${this.$route.fullPath}`
break break
case 'apiKey': case 'apiKey':
this.$router.push('/profile/api-keys') this.$router.push('/profile/api-keys')
@@ -73,7 +72,12 @@ export default {
this.$router.push('/profile/user/setting') this.$router.push('/profile/user/setting')
} }
}, },
logout() { async logout() {
const currentOrg = this.$store.getters.currentOrg
if (currentOrg.autoEnter) {
await this.$store.dispatch('users/setCurrentOrg', this.$store.getters.preOrg)
}
window.location.href = `${process.env.VUE_APP_LOGOUT_PATH}?next=${this.$route.fullPath}`
} }
} }
} }

View File

@@ -1,5 +1,7 @@
import { getProfile as apiGetProfile, logout } from '@/api/users' import { getProfile as apiGetProfile, logout } from '@/api/users'
import { getCurrentOrgLocal, getPreOrgLocal, getTokenFromCookie, saveCurrentOrgLocal, setPreOrgLocal } from '@/utils/auth' import {
getCurrentOrgLocal, getPreOrgLocal, getTokenFromCookie, saveCurrentOrgLocal, setPreOrgLocal
} from '@/utils/auth'
import orgUtil from '@/utils/org' import orgUtil from '@/utils/org'
import { resetRouter } from '@/router' import { resetRouter } from '@/router'
import Vue from 'vue' import Vue from 'vue'
@@ -70,7 +72,7 @@ const mutations = {
}, },
SET_CURRENT_ORG(state, org) { SET_CURRENT_ORG(state, org) {
// 系统组织和全局组织不设置成 Pre org // 系统组织和全局组织不设置成 Pre org
if (!state.currentOrg?.autoEnter) { if (!state.currentOrg?.autoEnter && !state.currentOrg?.is_root) {
state.preOrg = state.currentOrg state.preOrg = state.currentOrg
setPreOrgLocal(state.username, state.currentOrg) setPreOrgLocal(state.username, state.currentOrg)
} }
@@ -142,7 +144,7 @@ const actions = {
const systemOrg = { const systemOrg = {
id: orgUtil.SYSTEM_ORG_ID, id: orgUtil.SYSTEM_ORG_ID,
name: 'SystemSetting', name: 'SystemSetting',
autoEnter: true autoEnter: new Date().getTime()
} }
commit('SET_CURRENT_ORG', systemOrg) commit('SET_CURRENT_ORG', systemOrg)
}, },
@@ -157,7 +159,8 @@ const actions = {
const globalOrg = { const globalOrg = {
id: orgUtil.GLOBAL_ORG_ID, id: orgUtil.GLOBAL_ORG_ID,
name: 'Global', name: 'Global',
autoEnter: true is_root: true,
autoEnter: new Date().getTime()
} }
commit('SET_CURRENT_ORG', globalOrg) commit('SET_CURRENT_ORG', globalOrg)
}, },

BIN
src/styles/icons/dameng.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -92,6 +92,10 @@
background: url('./icons/db2.png') no-repeat center left transparent; background: url('./icons/db2.png') no-repeat center left transparent;
} }
&.dameng_ico_docu {
background: url('./icons/dameng.png') no-repeat center left transparent;
}
&.private_ico_docu { &.private_ico_docu {
background: url('./icons/private.png') no-repeat center left transparent; background: url('./icons/private.png') no-repeat center left transparent;
} }

View File

@@ -363,13 +363,19 @@ export function downloadText(content, filename) {
} }
export function download(downloadUrl, filename) { export function download(downloadUrl, filename) {
const iframe = document.createElement('iframe')
iframe.style.display = 'none'
document.body.appendChild(iframe)
const a = document.createElement('a') const a = document.createElement('a')
a.href = downloadUrl a.href = downloadUrl
if (filename) { if (filename) {
a.download = filename a.download = filename
} }
iframe.contentWindow.document.body.appendChild(a)
a.click() a.click()
window.URL.revokeObjectURL(downloadUrl) setTimeout(() => {
document.body.removeChild(iframe)
}, 1000 * 60 * 30) // If you can't download it in half an hour, don't download it.
} }
export function diffObject(object, base) { export function diffObject(object, base) {

View File

@@ -8,6 +8,11 @@ export const GLOBAL_ORG_ID = '00000000-0000-0000-0000-000000000000'
function getPropOrg() { function getPropOrg() {
const orgs = store.getters.usingOrgs const orgs = store.getters.usingOrgs
const preOrg = store.getters.preOrg || {}
const preFound = orgs.find((item) => item.id === preOrg.id)
if (preFound) {
return preFound
}
const defaultOrg = orgs.find((item) => item.is_default) const defaultOrg = orgs.find((item) => item.is_default)
if (defaultOrg) { if (defaultOrg) {
return defaultOrg return defaultOrg
@@ -62,7 +67,7 @@ async function changeOrg(org, reload = true, vm = null) {
} }
} }
location.hash = '#' + path location.hash = '#' + path
setTimeout(() => location.reload(), 400) setTimeout(() => location.reload(), 500)
} }
function hasCurrentOrgPermission() { function hasCurrentOrgPermission() {

View File

@@ -8,6 +8,7 @@ import orgs from '@/api/orgs'
import { getPropView, isViewHasOrgs } from '@/utils/jms' import { getPropView, isViewHasOrgs } from '@/utils/jms'
const whiteList = ['/login', process.env.VUE_APP_LOGIN_PATH] // no redirect whitelist const whiteList = ['/login', process.env.VUE_APP_LOGIN_PATH] // no redirect whitelist
const autoEnterOrgs = ['00000000-0000-0000-0000-000000000001', '00000000-0000-0000-0000-000000000000']
function reject(msg) { function reject(msg) {
return new Promise((resolve, reject) => reject(msg)) return new Promise((resolve, reject) => reject(msg))
@@ -23,6 +24,9 @@ async function checkLogin({ to, from, next }) {
} catch (e) { } catch (e) {
Vue.$log.error(e) Vue.$log.error(e)
const status = e.response.status const status = e.response.status
if (store.getters.currentOrg.autoEnter) {
await store.dispatch('users/setCurrentOrg', store.getters.preOrg)
}
if (status === 401 || status === 403) { if (status === 401 || status === 403) {
setTimeout(() => { setTimeout(() => {
window.location = process.env.VUE_APP_LOGIN_PATH window.location = process.env.VUE_APP_LOGIN_PATH
@@ -42,6 +46,10 @@ async function getPublicSetting({ to, from, next }, isOpen) {
async function refreshCurrentOrg() { async function refreshCurrentOrg() {
return orgs.getCurrentOrg().then(org => { return orgs.getCurrentOrg().then(org => {
// Root 就不刷新本地的了, 会影响 autoEnter
if (autoEnterOrgs.indexOf(org.id) !== -1) {
return
}
store.dispatch('users/setCurrentOrg', org) store.dispatch('users/setCurrentOrg', org)
}) })
} }
@@ -60,9 +68,16 @@ async function changeCurrentOrgIfNeed({ to, from, next }) {
Vue.$log.error('Current org is null or not a object: ', currentOrg) Vue.$log.error('Current org is null or not a object: ', currentOrg)
await orgUtil.change2PropOrg({ to, from, next }) await orgUtil.change2PropOrg({ to, from, next })
} }
if (currentOrg.name === 'SystemSetting') { const globalOrgPath = [
const preOrg = store.getters.preOrg '/console/perms/login-acls/', '/console/users/roles/',
await orgUtil.changeOrg(preOrg) '/console/perms/connect-method-acls/', '/settings/'
]
if (autoEnterOrgs.indexOf(currentOrg.id) !== -1 && currentOrg.autoEnter) {
const delta = new Date().getTime() - currentOrg.autoEnter
const notNeedChange = globalOrgPath.find(path => to.path.indexOf(path) === 0)
if (!notNeedChange && delta > 3000) {
await orgUtil.change2PropOrg({ to, from, next })
}
return return
} }
if (!orgUtil.hasCurrentOrgPermission()) { if (!orgUtil.hasCurrentOrgPermission()) {

View File

@@ -6,6 +6,7 @@
:account="account" :account="account"
:show-password-record="false" :show-password-record="false"
:url="secretUrl" :url="secretUrl"
type="template"
:visible.sync="showViewSecretDialog" :visible.sync="showViewSecretDialog"
/> />
</div> </div>
@@ -31,7 +32,7 @@ export default {
tableConfig: { tableConfig: {
url: '/api/v1/accounts/account-templates/', url: '/api/v1/accounts/account-templates/',
columns: null, columns: null,
columnsExclude: ['spec_info'], columnsExclude: ['spec_info', 'password_rules', 'push_params'],
columnsShow: { columnsShow: {
min: ['name', 'actions'], min: ['name', 'actions'],
default: ['name', 'username', 'secret_type', 'has_secret', 'privileged', 'actions'] default: ['name', 'username', 'secret_type', 'has_secret', 'privileged', 'actions']

View File

@@ -221,8 +221,7 @@ export default {
url: `/api/v1/assets/assets/${this.object.id}/`, url: `/api/v1/assets/assets/${this.object.id}/`,
object: this.object, object: this.object,
nested: 'spec_info', nested: 'spec_info',
showUndefine: true, showUndefine: true
excludes: ['script']
}, },
customInfoConfig: { customInfoConfig: {
title: this.$t('common.CustomInfo'), title: this.$t('common.CustomInfo'),

View File

@@ -222,6 +222,30 @@ export default {
getUrlQuery: false getUrlQuery: false
}, },
extraMoreActions: [ extraMoreActions: [
{
name: 'BulkVerify',
title: this.$t('common.BulkVerify'),
type: 'primary',
icon: 'fa fa-link',
can: ({ selectedRows }) =>
this.$hasPerm('assets.test_assetconnectivity') &&
!this.$store.getters.currentOrgIsRoot &&
selectedRows.length > 0 &&
selectedRows[0].auto_config?.ansible_enabled &&
selectedRows[0].auto_config?.ping_enabled,
callback: function({ selectedRows }) {
const ids = selectedRows.map(v => {
return v.id
})
this.$axios.post(
'/api/v1/assets/assets/tasks/',
{ action: 'test', assets: ids }).then(res => {
openTaskPage(res['task'])
}).catch(err => {
this.$message.error(this.$tc('common.bulkVerifyErrorMsg' + ' ' + err))
})
}.bind(this)
},
{ {
name: 'DeactiveSelected', name: 'DeactiveSelected',
title: this.$t('common.BatchDisable'), title: this.$t('common.BatchDisable'),

View File

@@ -26,7 +26,7 @@ export default {
[this.$t('xpack.Cloud.CloudSource'), ['account', 'regions']], [this.$t('xpack.Cloud.CloudSource'), ['account', 'regions']],
[this.$t('xpack.Cloud.SaveSetting'), [ [this.$t('xpack.Cloud.SaveSetting'), [
'hostname_strategy', 'ip_network_segment_group', 'hostname_strategy', 'ip_network_segment_group',
'sync_ip_type', 'is_always_update', 'fully_synchronous' 'sync_ip_type', 'is_always_update', 'fully_synchronous', 'release_assets'
]], ]],
[this.$t('xpack.Cloud.SyncStrategy'), ['strategy']], [this.$t('xpack.Cloud.SyncStrategy'), ['strategy']],
[this.$t('xpack.Timer'), ['is_periodic', 'crontab', 'interval']], [this.$t('xpack.Timer'), ['is_periodic', 'crontab', 'interval']],
@@ -68,6 +68,11 @@ export default {
label: this.$t('xpack.Cloud.FullySynchronous'), label: this.$t('xpack.Cloud.FullySynchronous'),
helpTips: this.$t('xpack.Cloud.FullySynchronousHelpTips') helpTips: this.$t('xpack.Cloud.FullySynchronousHelpTips')
}, },
release_assets: {
type: 'switch',
label: this.$t('xpack.Cloud.ReleaseAssets'),
helpTips: this.$t('xpack.Cloud.ReleaseAssetsHelpTips')
},
regions: { regions: {
component: Select2, component: Select2,
el: { el: {

View File

@@ -10,13 +10,11 @@
@click="onSetting" @click="onSetting"
/> />
<Dialog <Dialog
v-if="isVisible"
width="60%" width="60%"
:visible.sync="isVisible" :visible.sync="isVisible"
:title="title" :title="title"
:show-cancel="false" :show-cancel="false"
:show-confirm="false" :show-confirm="false"
:destroy-on-close="true"
@close="onDialogClose" @close="onDialogClose"
> >
<AutoDataForm <AutoDataForm
@@ -34,7 +32,7 @@
<script> <script>
import Dialog from '../../../components/Dialog' import Dialog from '../../../components/Dialog'
import AutoDataForm from '../../../components/Form/AutoDataForm' import AutoDataForm from '../../../components/Form/AutoDataForm'
import { DynamicInput } from '../../../components/Form/FormFields' import { DynamicInput, Switcher } from '../../../components/Form/FormFields'
export default { export default {
components: { components: {
@@ -147,6 +145,9 @@ export default {
case 'list': case 'list':
component = DynamicInput component = DynamicInput
break break
case 'boolean':
component = Switcher
break
} }
if (param) { if (param) {

View File

@@ -100,7 +100,7 @@ export default {
this.data.total_count_job_logs = data?.total_count_job_logs this.data.total_count_job_logs = data?.total_count_job_logs
this.data.total_count_job_logs_running = data?.total_count_job_logs_running this.data.total_count_job_logs_running = data?.total_count_job_logs_running
this.data.total_count_job_logs_failed = data?.total_count_job_logs_failed this.data.total_count_job_logs_failed = data?.total_count_job_logs_failed
if (totalCountSession.length > 1) { if (totalCountSession.length > 0) {
this.chartConfig.secondaryData = totalCountSession this.chartConfig.secondaryData = totalCountSession
} }
} }

View File

@@ -16,8 +16,7 @@ export default {
Title, Title,
LineChart LineChart
}, },
props: { props: {},
},
data() { data() {
return { return {
loading: false, loading: false,
@@ -48,10 +47,10 @@ export default {
const activeUsers = data?.dates_metrics_total_count_active_users const activeUsers = data?.dates_metrics_total_count_active_users
const activeAssets = data?.dates_metrics_total_count_active_assets const activeAssets = data?.dates_metrics_total_count_active_assets
this.lineChartConfig.datesMetrics = data.dates_metrics_date this.lineChartConfig.datesMetrics = data.dates_metrics_date
if (activeUsers.length > 1) { if (activeUsers.length > 0) {
this.lineChartConfig.primaryData = activeUsers this.lineChartConfig.primaryData = activeUsers
} }
if (activeAssets.length > 1) { if (activeAssets.length > 0) {
this.lineChartConfig.secondaryData = activeAssets this.lineChartConfig.secondaryData = activeAssets
} }
} }
@@ -64,6 +63,7 @@ export default {
margin-top: 16px; margin-top: 16px;
padding: 20px; padding: 20px;
background: #fff; background: #fff;
.head { .head {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;

View File

@@ -128,7 +128,8 @@ export default {
tip += current.label + '' + current.total + '<br/>' tip += current.label + '' + current.total + '<br/>'
} }
return tip return tip
} },
appendToBody: true
}, },
grid: { grid: {
top: '60%', top: '60%',

View File

@@ -66,7 +66,16 @@ export default {
}, },
methods: { methods: {
async getResourcesCount() { async getResourcesCount() {
return this.$axios.get('/api/v1/index/?total_count=1') return this.$axios.get(
'/api/v1/index/',
{
params: {
total_count_online_sessions: 1,
total_count_online_users: 1,
total_count_today_failed_sessions: 1
}
}
)
} }
} }
} }

View File

@@ -119,7 +119,7 @@
import { TreeTable } from '@/components' import { TreeTable } from '@/components'
import Term from '@/components/Widgets/Term' import Term from '@/components/Widgets/Term'
import Page from '@/layout/components/Page' import Page from '@/layout/components/Page'
import { createJob, getJob, getTaskDetail, JobUploadFile } from '@/api/ops' import { createJob, getTaskDetail, JobUploadFile } from '@/api/ops'
import { formatFileSize } from '@/utils/common' import { formatFileSize } from '@/utils/common'
import store from '@/store' import store from '@/store'
@@ -230,28 +230,9 @@ export default {
}, },
mounted() { mounted() {
this.enableWS() this.enableWS()
this.initData()
}, },
methods: { methods: {
formatFileSize, formatFileSize,
async initData() {
this.recoverStatus()
},
recoverStatus() {
if (this.$route.query.taskId) {
this.currentTaskId = this.$route.query.taskId
getTaskDetail(this.currentTaskId).then(data => {
getJob(data.job_id).then(res => {
this.runAsInput.value = res.runas
this.runAsInput.callback(res.runas)
this.executionInfo.status = data['status']
this.executionInfo.timeCost = data['time_cost']
this.setCostTimeInterval()
this.writeExecutionOutput()
})
})
}
},
enableWS() { enableWS() {
const scheme = document.location.protocol === 'https:' ? 'wss' : 'ws' const scheme = document.location.protocol === 'https:' ? 'wss' : 'ws'
const port = document.location.port ? ':' + document.location.port : '' const port = document.location.port ? ':' + document.location.port : ''
@@ -281,7 +262,7 @@ export default {
} }
}, },
taskStatusStat(summary) { taskStatusStat(summary) {
const { ok, failures, dark, excludes, skipped } = summary const { ok = [], failures = [], dark = [], excludes = [], skipped = [] } = summary
const failedKeys = Object.keys(failures) const failedKeys = Object.keys(failures)
const darkKeys = Object.keys(dark) const darkKeys = Object.keys(dark)
@@ -439,6 +420,7 @@ export default {
this.executionInfo.timeCost = 0 this.executionInfo.timeCost = 0
this.executionInfo.status = 'running' this.executionInfo.status = 'running'
this.currentTaskId = res.task_id this.currentTaskId = res.task_id
this.$router.replace({ query: { taskId: this.currentTaskId, type: 'file_upload' }})
this.setCostTimeInterval() this.setCostTimeInterval()
this.writeExecutionOutput() this.writeExecutionOutput()
}).catch(() => { }).catch(() => {

View File

@@ -98,6 +98,7 @@ export default {
el: { el: {
baseUrl: '/api/v1/perms/users/self/assets/', baseUrl: '/api/v1/perms/users/self/assets/',
baseNodeUrl: '/api/v1/perms/users/self/nodes/', baseNodeUrl: '/api/v1/perms/users/self/nodes/',
typeUrl: '/api/v1/perms/users/self/nodes/children-with-assets/category/tree',
value: [] value: []
} }
}, },

View File

@@ -446,7 +446,7 @@ export default {
this.executionInfo.timeCost = 0 this.executionInfo.timeCost = 0
this.executionInfo.status = 'running' this.executionInfo.status = 'running'
this.currentTaskId = res.task_id this.currentTaskId = res.task_id
this.$router.replace({ query: { taskId: this.currentTaskId }}) this.$router.replace({ query: { taskId: this.currentTaskId, type: 'shortcut_cmd' }})
this.setCostTimeInterval() this.setCostTimeInterval()
this.writeExecutionOutput() this.writeExecutionOutput()
this.setBtn() this.setBtn()
@@ -455,7 +455,7 @@ export default {
stop() { stop() {
StopJob({ task_id: this.currentTaskId }).then(() => { StopJob({ task_id: this.currentTaskId }).then(() => {
this.xterm.write('\x1b[31m' + this.xterm.write('\x1b[31m' +
this.$tc('ops.StopLogOutput').replace('currentTaskId', this.currentTaskId) + '\x1b[0m') this.$tc('ops.StopLogOutput').replace('currentTaskId', this.currentTaskId) + '\x1b[0m')
this.xterm.write(this.wrapperError('')) this.xterm.write(this.wrapperError(''))
this.getTaskStatus() this.getTaskStatus()
}).catch((e) => { }).catch((e) => {

View File

@@ -105,6 +105,7 @@ export default {
formatter: AmountFormatter, formatter: AmountFormatter,
formatterArgs: { formatterArgs: {
async: true, async: true,
cellValueToRemove: ['@SPEC'],
routeQuery: { routeQuery: {
activeTab: 'AssetPermissionUser' activeTab: 'AssetPermissionUser'
} }

View File

@@ -41,7 +41,7 @@ export default {
component: PhoneInput component: PhoneInput
}, },
mfa_level: { mfa_level: {
hidden: (formValue) => { disabled: (formValue) => {
return formValue.mfa_level === 2 return formValue.mfa_level === 2
}, },
helpText: this.$t('users.HelpText.MFAOfUserFirstLoginPersonalInformationImprovementPage') helpText: this.$t('users.HelpText.MFAOfUserFirstLoginPersonalInformationImprovementPage')
@@ -90,8 +90,7 @@ export default {
} }
} }
}, },
methods: { methods: {}
}
} }
</script> </script>

View File

@@ -143,7 +143,7 @@ export default {
}, },
callbacks: { callbacks: {
click: function() { click: function() {
window.location.href = `/core/auth/profile/mfa/` window.open('/core/auth/profile/mfa/', '_blank')
} }
} }
}, },

View File

@@ -22,6 +22,7 @@ import isFalsey from '@/components/Table/DataTable/compenents/el-data-table/util
import deepmerge from 'deepmerge' import deepmerge from 'deepmerge'
import * as queryUtil from '@/components/Table/DataTable/compenents/el-data-table/utils/query' import * as queryUtil from '@/components/Table/DataTable/compenents/el-data-table/utils/query'
import { createSourceIdCache } from '@/api/common' import { createSourceIdCache } from '@/api/common'
import { download } from '@/utils/common'
export default { export default {
name: 'CommandList', name: 'CommandList',
@@ -144,10 +145,7 @@ export default {
queryUtil.stringify(query, '=', '&') queryUtil.stringify(query, '=', '&')
url = url + queryStr url = url + queryStr
this.$log.debug('Export url: ', this.url, '=>', url) this.$log.debug('Export url: ', this.url, '=>', url)
const a = document.createElement('a') download(url + queryStr)
a.href = url
a.click()
window.URL.revokeObjectURL(url + queryStr)
} }
} }
}, },

View File

@@ -55,8 +55,7 @@ export default {
fieldsMeta: { fieldsMeta: {
EMAIL_HOST_USER: { EMAIL_HOST_USER: {
rules: [ rules: [
rules.EmailCheck, rules.EmailCheck
rules.Required
] ]
}, },
EMAIL_FROM: { EMAIL_FROM: {

View File

@@ -12,25 +12,24 @@ export default {
data() { data() {
return { return {
config: { config: {
initial: { initial: {},
},
url: '/api/v1/orgs/orgs/', url: '/api/v1/orgs/orgs/',
fields: [ fields: [
['', ['name', 'comment']] ['', ['name', 'comment']]
], ],
hasSaveContinue: false, hasSaveContinue: false,
fieldsMeta: { fieldsMeta: {},
},
onPerformSuccess(res, method) { onPerformSuccess(res, method) {
const order_params = { params: { order: '-date_created' }}
switch (method) { switch (method) {
case 'post': case 'post':
this.$store.dispatch('users/addAdminOrg', { id: res.id, name: res.name }) this.$store.dispatch('users/addAdminOrg', { id: res.id, name: res.name })
this.$message.success(this.$tc('common.createSuccessMsg')) this.$message.success(this.$tc('common.createSuccessMsg'))
return this.$router.push({ name: 'OrganizationList' }) return this.$router.push({ name: 'OrganizationList', ...order_params })
case 'put': case 'put':
this.$store.dispatch('users/modifyOrg', { id: res.id, name: res.name }) this.$store.dispatch('users/modifyOrg', { id: res.id, name: res.name })
this.$message.success(this.$tc('common.updateSuccessMsg')) this.$message.success(this.$tc('common.updateSuccessMsg'))
return this.$router.push({ name: 'OrganizationList' }) return this.$router.push({ name: 'OrganizationList', ...order_params })
} }
} }
} }

View File

@@ -19,7 +19,7 @@ export default {
data() { data() {
const commandType = this.$route.query.type || 'es' const commandType = this.$route.query.type || 'es'
return { return {
successUrl: { name: 'TerminalSetting', params: { activeMenu: 'CommandStorage' }}, successUrl: { name: 'Storage', params: { activeMenu: 'CommandStorage' }},
initial: { initial: {
type: commandType, type: commandType,
doc_type: 'command', doc_type: 'command',

View File

@@ -137,6 +137,14 @@ export default {
canCreate: this.$hasPerm('tickets.view_ticket'), canCreate: this.$hasPerm('tickets.view_ticket'),
hasBulkDelete: false, hasBulkDelete: false,
searchConfig: { searchConfig: {
default: {
state: {
key: 'state',
label: this.$t('tickets.action'),
value: 'pending',
valueLabel: this.$t('common.Open')
}
},
exclude: ['id', 'title', 'type', 'applicant'], exclude: ['id', 'title', 'type', 'applicant'],
options: [ options: [
{ {
@@ -180,7 +188,7 @@ export default {
}, },
{ {
value: 'relevant_command', value: 'relevant_command',
label: this.$t('tickets.RelevantSystemUser') label: this.$t('tickets.ApplyRunCommand')
} }
] ]
}, },

View File

@@ -44,7 +44,7 @@
<BasicTree <BasicTree
v-model="requestForm.actions" v-model="requestForm.actions"
:tree="treeNodes" :tree="treeNodes"
style="width: 30% !important" style="width: 100%"
/> />
</el-form-item> </el-form-item>

View File

@@ -48,7 +48,12 @@ export default {
} }
], ],
select2Option: { select2Option: {
url: '/api/v1/users/users/?oid=root' url: '/api/v1/users/users/?oid=root',
ajax: {
transformOption: (item) => {
return { label: item.name + '(' + item.username + ')', value: item.id }
}
}
}, },
fields: [ fields: [
] ]

View File

@@ -27,7 +27,7 @@
<el-form-item style="float: right"> <el-form-item style="float: right">
<template v-if="hasActionPerm"> <template v-if="hasActionPerm">
<el-button <el-button
:disabled="object.status.value === 'closed'" :disabled="isDisabled || object.status.value === 'closed'"
size="small" size="small"
type="primary" type="primary"
@click="handleApprove" @click="handleApprove"
@@ -35,7 +35,7 @@
<i class="fa fa-check" /> {{ $t('tickets.Accept') }} <i class="fa fa-check" /> {{ $t('tickets.Accept') }}
</el-button> </el-button>
<el-button <el-button
:disabled="object.status.value === 'closed'" :disabled="isDisabled || object.status.value === 'closed'"
size="small" size="small"
type="warning" type="warning"
@click="handleReject" @click="handleReject"
@@ -45,12 +45,12 @@
</template> </template>
<el-button <el-button
v-if="isSelfTicket" v-if="isSelfTicket"
:disabled="object.status.value === 'closed'" :disabled="isDisabled || object.status.value === 'closed'"
size="small" size="small"
type="danger" type="danger"
@click="handleClose" @click="handleClose"
> >
<i class="fa fa-times" /> {{ $t('tickets.Close') }} <i class="fa fa-times" /> {{ $t('tickets.CancelTicket') }}
</el-button> </el-button>
<el-button <el-button
:disabled="object.status.value === 'closed'" :disabled="object.status.value === 'closed'"
@@ -94,6 +94,7 @@ export default {
}, },
data() { data() {
return { return {
isDisabled: false,
comments: '', comments: '',
type_api: '', type_api: '',
imageUrl: require('@/assets/img/avatar.png'), imageUrl: require('@/assets/img/avatar.png'),
@@ -156,17 +157,35 @@ export default {
this.createComment(function() { this.createComment(function() {
}) })
const url = `/api/v1/tickets/${this.type_api}/${this.object.id}/approve/` const url = `/api/v1/tickets/${this.type_api}/${this.object.id}/approve/`
this.$axios.put(url).then(res => this.reloadPage()).catch(err => this.$message.error(err)) this.$axios.put(url).then(res => {
this.reloadPage()
}).catch(err => {
this.$message.error(err)
}).finally(() => {
this.isDisabled = false
})
}, },
defaultReject() { defaultReject() {
this.createComment(function() { this.createComment(function() {
}) })
const url = `/api/v1/tickets/${this.type_api}/${this.object.id}/reject/` const url = `/api/v1/tickets/${this.type_api}/${this.object.id}/reject/`
this.$axios.put(url).then(res => this.reloadPage()).catch(err => this.$message.error(err)) this.$axios.put(url).then(res => {
this.reloadPage()
}).catch(err => {
this.$message.error(err)
}).finally(() => {
this.isDisabled = false
})
}, },
defaultClose() { defaultClose() {
const url = `/api/v1/tickets/${this.type_api}/${this.object.id}/close/` const url = `/api/v1/tickets/${this.type_api}/${this.object.id}/close/`
this.$axios.put(url).then(res => this.reloadPage()).catch(err => this.$message.error(err)) this.$axios.put(url).then(res => {
this.reloadPage()
}).catch(err => {
this.$message.error(err)
}).finally(() => {
this.isDisabled = false
})
}, },
createComment(successCallback) { createComment(successCallback) {
const commentText = this.form.comments const commentText = this.form.comments
@@ -187,17 +206,42 @@ export default {
} }
}) })
}, },
handleAction(actionType) {
if (this.isDisabled) {
return
}
this.isDisabled = true
let handler
switch (actionType) {
case 'approve':
handler = this.approve || this.defaultApprove
break
case 'reject':
handler = this.reject || this.defaultReject
break
case 'close':
handler = this.close || this.defaultClose
break
default:
handler = null
break
}
if (handler) {
handler()
} else {
this.$message.error('No handler for action')
}
},
handleApprove() { handleApprove() {
const handler = this.approve || this.defaultApprove this.handleAction('approve')
handler()
}, },
handleReject() { handleReject() {
const handler = this.reject || this.defaultReject this.handleAction('reject')
handler()
}, },
handleClose() { handleClose() {
const handler = this.close || this.defaultClose this.handleAction('close')
handler()
}, },
handleComment() { handleComment() {
this.createComment( this.createComment(

View File

@@ -4,7 +4,6 @@
<script> <script>
import { GenericCreateUpdatePage } from '@/layout/components' import { GenericCreateUpdatePage } from '@/layout/components'
import TransSelect from '@/components/Form/FormFields/TransferSelect.vue'
export default { export default {
components: { components: {
@@ -23,7 +22,6 @@ export default {
], ],
fieldsMeta: { fieldsMeta: {
users: { users: {
component: TransSelect,
el: { el: {
url: '/api/v1/users/users/?fields_size=mini&order=name', url: '/api/v1/users/users/?fields_size=mini&order=name',
ajax: { ajax: {
@@ -38,8 +36,7 @@ export default {
} }
} }
}, },
methods: { methods: {}
}
} }
</script> </script>

View File

@@ -8,6 +8,7 @@ import { PhoneInput, UserPassword } from '@/components/Form/FormFields'
import rules from '@/components/Form/DataForm/rules' import rules from '@/components/Form/DataForm/rules'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import { Select2 } from '@/components' import { Select2 } from '@/components'
import store from '@/store'
export default { export default {
components: { components: {
@@ -40,6 +41,9 @@ export default {
return this.$route.params.id || formValue.source !== 'local' return this.$route.params.id || formValue.source !== 'local'
} }
}, },
mfa_level: {
disabled: false
},
email: { email: {
rules: [ rules: [
rules.EmailCheck, rules.EmailCheck,
@@ -148,8 +152,8 @@ export default {
}, },
hidden: () => { hidden: () => {
return !this.$store.getters.hasValidLicense || return !this.$store.getters.hasValidLicense ||
!this.$hasPerm('rbac.add_orgrolebinding') || !this.$hasPerm('rbac.add_orgrolebinding') ||
this.$store.getters.currentOrgIsRoot this.$store.getters.currentOrgIsRoot
}, },
helpText: this.$t('users.HelpText.OrgRoleHelpText') helpText: this.$t('users.HelpText.OrgRoleHelpText')
}, },
@@ -212,6 +216,7 @@ export default {
this.fieldsMeta.groups.el.disabled = true this.fieldsMeta.groups.el.disabled = true
} }
await this.setDefaultRoles() await this.setDefaultRoles()
this.disableMFAFieldIfNeed(null)
this.loading = false this.loading = false
}, },
methods: { methods: {
@@ -229,11 +234,21 @@ export default {
if (this.$route.query.clone_from) { if (this.$route.query.clone_from) {
this.user.groups = [] this.user.groups = []
} }
this.disableMFAFieldIfNeed(user)
}, },
async setDefaultRoles() { async setDefaultRoles() {
const roles = await this.$axios.get('/api/v1/rbac/roles/') const roles = await this.$axios.get('/api/v1/rbac/roles/')
this.initial.system_roles = roles.filter(role => role.name === 'User').map(role => role.id) this.initial.system_roles = roles.filter(role => role.name === 'User').map(role => role.id)
this.initial.org_roles = roles.filter(role => role.name === 'OrgUser').map(role => role.id) this.initial.org_roles = roles.filter(role => role.name === 'OrgUser').map(role => role.id)
},
disableMFAFieldIfNeed(user) {
// SECURITY_MFA_AUTH 0 不开启 1 全局开启 2 管理员开启
const adminUserIsNeed = (user?.is_superuser || user?.is_org_admin) && this.$route.meta.action === 'update' &&
store.getters.publicSettings['SECURITY_MFA_AUTH'] === 2
if (store.getters.publicSettings['SECURITY_MFA_AUTH'] === 1 || adminUserIsNeed) {
this.fieldsMeta['mfa_level'].disabled = true
this.fieldsMeta['mfa_level'].helpText = this.$t('users.GlobalDisableMfaMsg')
}
} }
} }
} }

View File

@@ -4463,6 +4463,11 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.2.2, domhandler@^4.3.1:
dependencies: dependencies:
domelementtype "^2.2.0" domelementtype "^2.2.0"
dompurify@^3.1.6:
version "3.1.6"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.1.6.tgz#43c714a94c6a7b8801850f82e756685300a027e2"
integrity sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==
domready@1.0.8: domready@1.0.8:
version "1.0.8" version "1.0.8"
resolved "https://registry.npmmirror.com/domready/-/domready-1.0.8.tgz#91f252e597b65af77e745ae24dd0185d5e26d58c" resolved "https://registry.npmmirror.com/domready/-/domready-1.0.8.tgz#91f252e597b65af77e745ae24dd0185d5e26d58c"
@@ -12079,7 +12084,7 @@ string-length@^2.0.0:
astral-regex "^1.0.0" astral-regex "^1.0.0"
strip-ansi "^4.0.0" strip-ansi "^4.0.0"
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: "string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3" version "4.2.3"
resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -12097,6 +12102,15 @@ string-width@^1.0.1:
is-fullwidth-code-point "^1.0.0" is-fullwidth-code-point "^1.0.0"
strip-ansi "^3.0.0" strip-ansi "^3.0.0"
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.npmmirror.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" resolved "https://registry.npmmirror.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
@@ -12196,7 +12210,7 @@ stringify-package@^1.0.1:
resolved "https://registry.npmmirror.com/stringify-package/-/stringify-package-1.0.1.tgz#e5aa3643e7f74d0f28628b72f3dad5cecfc3ba85" resolved "https://registry.npmmirror.com/stringify-package/-/stringify-package-1.0.1.tgz#e5aa3643e7f74d0f28628b72f3dad5cecfc3ba85"
integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg== integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: "strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1" version "6.0.1"
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -12224,6 +12238,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
dependencies: dependencies:
ansi-regex "^4.1.0" ansi-regex "^4.1.0"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^7.0.1, strip-ansi@^7.1.0: strip-ansi@^7.0.1, strip-ansi@^7.1.0:
version "7.1.0" version "7.1.0"
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -13616,8 +13637,7 @@ worker-farm@^1.7.0:
dependencies: dependencies:
errno "~0.1.7" errno "~0.1.7"
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
name wrap-ansi-cjs
version "7.0.0" version "7.0.0"
resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -13652,6 +13672,15 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0" string-width "^4.1.0"
strip-ansi "^6.0.0" strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.1.0: wrap-ansi@^8.1.0:
version "8.1.0" version "8.1.0"
resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"