Compare commits

...

58 Commits

Author SHA1 Message Date
fit2bot
db9f8eb72f feat: Update v3.10.20 2025-10-21 15:08:20 +08:00
feng
81b3c79ac1 perf: Account remove 2025-08-26 17:28:05 +08:00
feng
710fa9c109 perf: Automatically re-login after 1 second after csrftoken expires 2025-08-26 16:36:09 +08:00
ewall555
b3a3ca13a7 feat: Remove vue-moment dependency, use moment library directly 2025-06-17 10:15:26 +08:00
Jackbewater
e04abd8b79 feat: Add batch activation and disable options in account push tasks 2025-06-17 10:14:06 +08:00
Jackbewater
e3a207f7b6 feat: Add options for batch activation and disabling account password change tasks 2025-06-16 10:59:40 +08:00
ewall555
40379ac761 feat: Remove the expired date field from the initial value
- jumpserver/pull/15373
2025-05-13 10:19:20 +08:00
ewall555
33862c716e Fix: Remove conditional rendering to ensure action buttons are always visible 2025-05-09 14:14:37 +08:00
八千流
4162bdb74d Merge pull request #4917 from jumpserver/pr@v3@fix_special_drag
Fixed: Special Drag
2025-03-27 09:27:56 +08:00
ZhaoJiSen
d231bd503c Fixed: Special Drag 2025-03-26 23:28:10 +08:00
zhaojisen
b42c5f9170 Fixed: Export Items 2025-03-26 20:56:28 +08:00
zhaojisen
f823257515 Fixed: Tree Drag 2025-03-26 20:55:51 +08:00
zhaojisen
d3f4b2b2e8 Fixed: XSS 2025-03-26 20:55:24 +08:00
w940853815
5eba19946c fix: open monitor link in a new tab 2025-03-24 17:39:47 +08:00
feng
b4e889316e perf: User system_roles required 2025-03-24 16:02:05 +08:00
zhaojisen
4a72e5aa2b Fixed: Fixed: Fixed Fix account template redirection error in remote applications 2025-02-10 16:16:32 +08:00
jiangweidong
6ea86c7efe feat: VMware automatically syncs folders to node 2025-01-03 18:52:06 +08:00
zhaojisen
dc0a0ae868 Fixed: Change Tree Icon Position 2025-01-02 14:58:43 +08:00
Bai
5b3b8f72cd fix: system org 2024-12-26 14:44:05 +08:00
feng
df26679166 perf: Firefox unable to download file 2024-12-26 13:59:08 +08:00
zhaojisen
9efccb8ada fixed: Fixed tissue switching issues 2024-12-25 16:22:56 +08:00
zhaojisen
d784530539 Fixed: Fixed an issue with the number of cross-pages in Select All reverse 2024-12-25 15:24:51 +08:00
zhaojisen
afcc60f29c fixed: Optimize select all and deselect all functionality 2024-12-24 17:00:01 +08:00
zhaojisen
f8d581e455 Fixed: Optimize select all and deselect all functionality 2024-12-24 17:00:01 +08:00
zhaojisen
dba1540953 fixed: Fixed remarks text wrapping issue 2024-12-24 16:07:21 +08:00
Bai
456227abcf fix: ticket approve 2024-12-18 16:35:58 +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
67 changed files with 2494 additions and 2065 deletions

1
GITSHA Normal file
View File

@@ -0,0 +1 @@
81b3c79ac15dd02e31090e2bee138fbeca56799d

View File

@@ -34,6 +34,7 @@
"css-color-function": "^1.3.3",
"decimal.js": "^10.4.3",
"deepmerge": "^4.2.2",
"dompurify": "^3.1.6",
"echarts": "4.7.0",
"element-ui": "2.13.2",
"eslint-plugin-html": "^6.0.0",
@@ -71,7 +72,6 @@
"vue-i18n": "^8.15.5",
"vue-json-editor": "^1.4.3",
"vue-markdown": "^2.2.4",
"vue-moment": "^4.1.0",
"vue-password-strength-meter": "^1.7.2",
"vue-router": "3.0.6",
"vue-select": "^3.9.5",

View File

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

View File

@@ -3,7 +3,7 @@
<table style="width: 100%">
<tr>
<td colspan="2">
<AssetSelect ref="assetSelect" :can-select="canSelect" :disabled="disabled" />
<AssetSelect ref="assetSelect" :can-select="canSelect" :disabled="disabled" :tree-setting="treeSetting" />
</td>
</tr>
<tr>
@@ -59,6 +59,10 @@ export default {
default(row, index) {
return true
}
},
treeSetting: {
type: Object,
default: () => {}
}
},
data() {

View File

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

View File

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

View File

@@ -31,6 +31,10 @@ export default {
type: String,
default: '/api/v1/assets/assets/'
},
typeUrl: {
type: String,
default: '/api/v1/assets/nodes/category/tree/'
},
nodeUrl: {
type: String,
default: '/api/v1/assets/nodes/'
@@ -101,9 +105,14 @@ export default {
showAssets: false,
showSearch: false,
customTreeHeaderName: this.$t('assets.BuiltinTree'),
url: '/api/v1/assets/nodes/category/tree/',
url: this.typeUrl,
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'}`,
edit: {
drag: {
isMove: false
}
},
callback: {
onSelected: (event, treeNode) => this.getAssetsUrl(treeNode)
}

View File

@@ -51,6 +51,11 @@ export default {
url: this.tableUrl,
// ?assets=0不显示资产. =1显示资产
treeUrl: this.treeUrl,
edit: {
drag: {
isMove: false
}
},
callback: {
onSelected: (event, node) => vm.onSelected(node, vm),
refresh: vm.refreshObjectAssetPermission

View File

@@ -78,7 +78,7 @@ export default {
formatterData = data
}
return (
<span>{formatterData}</span>
<span style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word', lineHeight: '1.2' }}>{formatterData}</span>
)
}
if (this.value instanceof Array) {

View File

@@ -9,11 +9,13 @@
v-bind="$attrs"
v-on="$listeners"
>
<slot />
<div v-loading="loadingStatus">
<slot />
</div>
<div slot="footer" class="dialog-footer">
<slot name="footer">
<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 }}
</el-button>
</slot>
@@ -71,13 +73,16 @@ export default {
}
},
data() {
return {}
return {
}
},
computed: {
iWidth() {
return this.$store.getters.isMobile ? '1000px' : this.width
}
},
mounted() {
},
methods: {
onCancel() {
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 { assignIfNot } from '@/utils/common'
import TagInput from '@/components/Form/FormFields/TagInput.vue'
import TransferSelect from '@/components/Form/FormFields/TransferSelect.vue'
export class FormFieldGenerator {
constructor(emit) {
@@ -134,9 +133,6 @@ export class FormFieldGenerator {
case 'comment':
field.el.type = 'textarea'
break
case 'users':
field.component = TransferSelect
field.el.label = field.label
}
return field
}

View File

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

View File

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

View File

@@ -13,15 +13,28 @@ class StrategyAbstract {
this.onSelect = this.onSelect.bind(this)
this.onSelectAll = this.onSelectAll.bind(this)
}
get elTable() {
return this.elDataTable.$refs.table
}
onSelectionChange() {}
onSelect() {}
onSelectAll() {}
toggleRowSelection() {}
clearSelection() {}
updateElTableSelection() {}
onSelectionChange() {
}
onSelect() {
}
onSelectAll() {
}
toggleRowSelection() {
}
clearSelection() {
}
updateElTableSelection() {
}
}
/**
@@ -34,14 +47,16 @@ class StrategyNormal extends StrategyAbstract {
onSelectionChange(val) {
this.elDataTable.selected = val
}
/**
* toggleRowSelection和clearSelection的表现与el-table一致
*/
toggleRowSelection(...args) {
return this.elTable.toggleRowSelection(...args)
}
clearSelection() {
return this.elTable.clearSelection()
return this.elTable?.clearSelection()
}
}
@@ -65,17 +80,55 @@ class StrategyPersistSelection extends StrategyAbstract {
const isChosen = selection.indexOf(row) > -1
this.toggleRowSelection(row, isChosen)
}
/**
* 用户切换当前页的多选
*/
onSelectAll(selection, selectable = () => true) {
const isSelected = !!selection.length
this.elDataTable.data.forEach(r => {
if (selectable(r)) {
this.toggleRowSelection(r, isSelected)
}
})
const { id, selected, data } = this.elDataTable
const selectableRows = data.filter(selectable)
// const isSelected = !!selection.length
// 创建已选择项的 id 集合,用于快速查找
const selectedIds = new Set(selected.map(r => r[id]))
const currentPageIds = new Set(selectableRows.map(row => row[id]))
// 前页面的选中状态
const currentPageSelectedCount = selectableRows.filter(row =>
selectedIds.has(row[id])
).length
// 判断是全选还是取消全选
const shouldSelectAll = currentPageSelectedCount < selectableRows.length
this.elTable?.clearSelection()
if (shouldSelectAll) {
selectableRows.forEach(row => {
if (!selectedIds.has(row[id])) selected.push(row)
this.elTable.toggleRowSelection(row, true)
// ! 这里需要触发事件,否则在 el-table 中无法触发 selection-change 事件
this.elDataTable.$emit('toggle-row-selection', true, row)
})
} else {
const newSelected = []
selected.forEach(row => {
if (!currentPageIds.has(row[id])) {
newSelected.push(row)
} else {
this.elDataTable.$emit('toggle-row-selection', false, row)
}
})
this.elDataTable.selected = newSelected
}
this.elDataTable.$emit('selection-change', this.elDataTable.selected)
}
/**
* toggleRowSelection和clearSelection管理elDataTable的selected数组
* 记得最后要将状态同步到el-table中
@@ -83,34 +136,42 @@ class StrategyPersistSelection extends StrategyAbstract {
toggleRowSelection(row, isSelected) {
const { id, selected } = this.elDataTable
const foundIndex = selected.findIndex(r => r[id] === row[id])
if (typeof isSelected === 'undefined') {
isSelected = foundIndex <= -1
}
if (isSelected && foundIndex === -1) {
selected.push(row)
} else if (!isSelected && foundIndex > -1) {
selected.splice(foundIndex, 1)
}
this.elDataTable.$emit('toggle-row-selection', isSelected, row)
this.updateElTableSelection()
}
clearSelection() {
this.elDataTable.selected = []
this.updateElTableSelection()
}
/**
* 将selected状态同步到el-table中
*/
updateElTableSelection() {
const { data, id, selected } = this.elDataTable
// 历史勾选的行已经不在当前页了所以要将当前页的行数据和selected合并
const mergeData = _.uniqWith([...data, ...selected], _.isEqual)
mergeData.forEach(r => {
const isSelected = !!selected.find(r2 => r[id] === r2[id])
if (!this.elTable) {
return
const selectedIds = new Set(selected.map(r => r[id]))
this.elTable?.clearSelection()
data.forEach(row => {
const shouldBeSelected = selectedIds.has(row[id])
if (!this.elTable) return
if (shouldBeSelected) {
this.elTable.toggleRowSelection(row, true)
}
this.elTable.toggleRowSelection(r, isSelected)
})
}
}

View File

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

View File

@@ -48,6 +48,7 @@
import Dialog from '@/components/Dialog/index.vue'
import { createSourceIdCache } from '@/api/common'
import * as queryUtil from '@/components/Table/DataTable/compenents/el-data-table/utils/query'
import { download } from '@/utils/common'
export default {
name: 'ExportDialog',
@@ -187,10 +188,7 @@ export default {
})
},
downloadCsv(url) {
const a = document.createElement('a')
a.href = url
a.click()
window.URL.revokeObjectURL(url)
download(url)
},
async defaultPerformExport(selectRows, exportOption, q, exportTypeOption) {
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>
import Dialog from '@/components/Dialog/index.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'
export default {
@@ -221,10 +221,7 @@ export default {
this.$message.success(msg)
},
downloadCsv(url) {
const a = document.createElement('a')
a.href = url
a.click()
window.URL.revokeObjectURL(url)
download(url)
},
async handleImportConfirm() {
await this.$refs['importTable'].performUpload()

View File

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

View File

@@ -122,8 +122,13 @@ export default {
})
},
iExportOptions() {
const options = assignIfNot(this.exportOptions, { url: this.tableUrl })
return options
// const options = assignIfNot(this.exportOptions, { url: this.tableUrl })
// return options
return {
url: this.tableUrl,
...this.exportOptions
}
}
},
methods: {

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,7 @@
class="text edit-input"
@blur="onEditBlur"
/>
<span v-if="realValue" class="action">
<span class="action">
<template v-for="(item, index) in iActions">
<el-tooltip
v-if="item.has"

View File

@@ -157,7 +157,7 @@ export default {
</a>`
const treeActions = `${showSearch ? searchIcon : ''}${showRefresh ? refreshIcon : ''}`
const icons = `
<span style="float: right; margin-right: 10px">
<span>
${treeActions}
</span>`
if (rootNode) {

View File

@@ -39,7 +39,7 @@ export default {
showRenameBtn: false,
drag: {
isCopy: false,
isMove: true
isMove: !this.$store.getters.currentOrgIsRoot
}
},
callback: {

View File

@@ -18,14 +18,15 @@
/>
</el-col>
<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-row>
<VueMarkdown v-else class="source" :source="iValue" :html="false" />
<VueMarkdown v-else class="source" :html="false" :source="sanitizedValue" />
</div>
</template>
<script>
import DOMPurify from 'dompurify'
import VueMarkdown from 'vue-markdown'
import 'github-markdown-css/github-markdown-light.css'
@@ -53,13 +54,24 @@ export default {
resizeObserver: null,
span: 12,
isShow: true,
iValue: this.value
iValue: this.sanitizeContent(this.value)
}
},
computed: {
sanitizedValue() {
const content = this.iValue.replace(/\\/g, '\\\\').replace(/\$/g, '\\$')
return this.sanitizeContent(content)
}
},
watch: {
value(newVal) {
this.iValue = this.sanitizeContent(newVal)
}
},
mounted() {
this.$nextTick(() => {
this.resizeObserver = new ResizeObserver(entries => {
// 监听高度变化
const height = entries[0].target.offsetHeight
if (height) {
this.height = height
@@ -79,8 +91,19 @@ export default {
this.resizeObserver = null
},
methods: {
sanitizeContent(content) {
if (!content) return ''
return DOMPurify.sanitize(content, {
ALLOWED_TAGS: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'strong', 'em', 'code', 'pre', 'blockquote', 'a'],
FORBID_TAGS: ['script', 'style', 'iframe', 'frame', 'object', 'embed'],
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover']
})
},
onChange() {
this.$emit('change', this.iValue)
const sanitizedValue = this.sanitizeContent(this.iValue)
this.iValue = sanitizedValue
this.$emit('change', sanitizedValue)
},
onView() {
this.isShow = !this.isShow

View File

@@ -48,6 +48,7 @@ export default {
fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
lineHeight: 1.2,
fontSize: 13,
scrollback: 9999999,
rightClickSelectsWord: true,
theme: {
background: '#fff',

View File

@@ -1931,6 +1931,7 @@
"CheckViewAcceptor": "View more acceptor",
"Assignees": "Assignees",
"Close": "Close",
"CancelTicket": "Cancel Ticket",
"OpenStatus": "Open",
"CloseStatus": "Close",
"Comment": "Comment",

View File

@@ -1927,6 +1927,7 @@
"Assignee": "処理者",
"Assignees": "処理待ち",
"Close": "閉じる",
"CancelTicket": "作業指示をキャンセルする",
"OpenStatus": "オン",
"CloseStatus": "閉じる",
"Comment": "備考",

View File

@@ -1913,6 +1913,7 @@
"Assignee": "处理人",
"Assignees": "待处理人",
"Close": "关闭",
"CancelTicket": "取消工单",
"OpenStatus": "审批中",
"CloseStatus": "已完成",
"Comment": "备注",

View File

@@ -1913,6 +1913,7 @@
"Assignee": "處理人",
"Assignees": "待處理人",
"Close": "關閉",
"CancelTicket": "取消工單",
"OpenStatus": "審批中",
"CloseStatus": "已完成",
"Comment": "備註",

View File

@@ -81,7 +81,7 @@ export default {
},
{
label: this.$t('common.Version'),
value: 'version-dev'
value: 'v3.10.20'
},
{
label: this.$t('common.PermissionCompany'),

View File

@@ -61,7 +61,6 @@ export default {
break
case 'logout':
this.logout()
window.location.href = `${process.env.VUE_APP_LOGOUT_PATH}?next=${this.$route.fullPath}`
break
case 'apiKey':
this.$router.push('/profile/api-keys')
@@ -73,7 +72,14 @@ export default {
this.$router.push('/profile/user/setting')
}
},
logout() {
async logout() {
const currentOrg = this.$store.getters.currentOrg
if (currentOrg.autoEnter || currentOrg.is_system) {
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

@@ -24,6 +24,8 @@ import { message } from '@/utils/message'
import xss from '@/utils/xss'
import request from '@/utils/request'
import ElTableTooltipPatch from '@/utils/elTableTooltipPatch.js'
import moment from 'moment'
moment.locale('zh-cn')
/**
* If you don't want to use mock-server
@@ -50,11 +52,7 @@ Vue.config.productionTip = false
Vue.use(VueCookie)
window.$cookie = VueCookie
const moment = require('moment')
require('moment/locale/zh-cn')
Vue.use(require('vue-moment'), {
moment
})
Vue.prototype.$moment = moment
Vue.use(VueLogger, loggerOptions)

View File

@@ -71,10 +71,12 @@ const mutations = {
state.consoleOrgs = state.consoleOrgs.filter(i => i.id !== org.id)
},
SET_CURRENT_ORG(state, org) {
// 系统组织和全局组织不设置成 Pre org
if (!state.currentOrg?.autoEnter && !state.currentOrg?.is_root) {
state.preOrg = state.currentOrg
setPreOrgLocal(state.username, state.currentOrg)
// 系统组织不设置成 Pre org
const currentOrg = state.currentOrg
if (currentOrg && !currentOrg.autoEnter && !currentOrg.is_system) {
state.preOrg = currentOrg
setPreOrgLocal(state.username, currentOrg)
}
state.currentOrg = org
saveCurrentOrgLocal(state.username, org)
@@ -144,6 +146,7 @@ const actions = {
const systemOrg = {
id: orgUtil.SYSTEM_ORG_ID,
name: 'SystemSetting',
is_system: true,
autoEnter: new Date().getTime()
}
commit('SET_CURRENT_ORG', systemOrg)

View File

@@ -42,8 +42,14 @@ export function getCurrentOrgLocal(username) {
export function saveCurrentOrgLocal(username, org) {
const key = CURRENT_ORG_KEY + '_' + username
localStorage.setItem(key, JSON.stringify(org))
VueCookie.set('X-JMS-ORG', org.id)
if (org) {
localStorage.setItem(key, JSON.stringify(org))
VueCookie.set('X-JMS-ORG', org.id)
} else {
localStorage.removeItem(key)
VueCookie.delete('X-JMS-ORG')
}
}
export function setPreOrgLocal(username, org) {

View File

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

View File

@@ -17,7 +17,7 @@ function getPropOrg() {
if (defaultOrg) {
return defaultOrg
}
return orgs.filter(item => !item['is_root'] && item.id !== SYSTEM_ORG_ID)[0]
return orgs.filter(item => !item['is_root'] && !item['is_system'])[0]
}
async function change2PropOrg() {

View File

@@ -90,10 +90,20 @@ function ifBadRequest({ response, error }) {
}
}
export function logout() {
window.location.href = `${process.env.VUE_APP_LOGOUT_PATH}?next=${location.pathname}`
}
export function flashErrorMsg({ response, error }) {
if (!response.config.disableFlashErrorMsg) {
const responseErrorMsg = getErrorResponseMsg(error)
const msg = responseErrorMsg || error.message
if (response.status === 403 && msg === 'CSRF Failed: CSRF token missing.') {
setTimeout(() => {
logout()
}, 1000)
}
message({
message: msg,
type: 'error',

View File

@@ -8,7 +8,10 @@ import orgs from '@/api/orgs'
import { getPropView, isViewHasOrgs } from '@/utils/jms'
const whiteList = ['/login', process.env.VUE_APP_LOGIN_PATH] // no redirect whitelist
const autoEnterOrgs = ['00000000-0000-0000-0000-000000000001', '00000000-0000-0000-0000-000000000000']
const autoEnterOrgs = [
'00000000-0000-0000-0000-000000000001',
'00000000-0000-0000-0000-000000000000'
]
function reject(msg) {
return new Promise((resolve, reject) => reject(msg))
@@ -23,7 +26,23 @@ async function checkLogin({ to, from, next }) {
return await store.dispatch('users/getProfile')
} catch (e) {
Vue.$log.error(e)
// remove currentOrg: System org item
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i)
if (!key.startsWith('jms_current_org_')) {
continue
}
let value = localStorage.getItem(key)
value = JSON.parse(value)
if (!value.is_system) {
continue
}
localStorage.removeItem(key)
}
const status = e.response.status
if (store.getters.currentOrg.autoEnter) {
await store.dispatch('users/setCurrentOrg', store.getters.preOrg)
}
if (status === 401 || status === 403) {
setTimeout(() => {
window.location = process.env.VUE_APP_LOGIN_PATH
@@ -151,18 +170,24 @@ export async function startup({ to, from, next }) {
if (store.getters.inited) {
return true
}
await store.dispatch('app/init')
// set page title
// await getOpenPublicSetting({ to, from, next })
await getPublicSetting({ to, from, next }, true)
await checkLogin({ to, from, next })
await getPublicSetting({ to, from, next }, false)
await changeCurrentViewIfNeed({ to, from, next })
await changeCurrentOrgIfNeed({ to, from, next })
await generatePageRoutes({ to, from, next })
await checkUserFirstLogin({ to, from, next })
await store.dispatch('assets/getAssetCategories')
try {
await store.dispatch('app/init')
// set page title
// await getOpenPublicSetting({ to, from, next })
await getPublicSetting({ to, from, next }, true)
await checkLogin({ to, from, next })
await getPublicSetting({ to, from, next }, false)
await changeCurrentViewIfNeed({ to, from, next })
await changeCurrentOrgIfNeed({ to, from, next })
await generatePageRoutes({ to, from, next })
await checkUserFirstLogin({ to, from, next })
await store.dispatch('assets/getAssetCategories')
} catch (e) {
Vue.$log.error('Startup error: ', e)
}
return true
}

View File

@@ -30,7 +30,12 @@ export default {
showMenu: false,
showAssets: true,
url: '/api/v1/accounts/accounts/',
countResource: 'account'
countResource: 'account',
edit: {
drag: {
isMove: false
}
}
}
}
}

View File

@@ -122,9 +122,53 @@ export default {
return {
name: 'AccountChangeSecretCreate'
}
}
},
extraMoreActions: [
{
name: 'BatchDisable',
title: this.$t('common.BatchDisable'),
icon: 'fa fa-ban',
can: ({ selectedRows }) => selectedRows.length > 0 && this.$hasPerm('accounts.change_changesecretautomation'),
callback: ({ selectedRows, reloadTable }) => this.bulkDisableCallback(selectedRows, reloadTable)
},
{
name: 'BatchActivate',
title: this.$t('common.BatchActivate'),
icon: 'fa fa-check-circle-o',
can: ({ selectedRows }) => selectedRows.length > 0 && this.$hasPerm('accounts.change_changesecretautomation'),
callback: ({ selectedRows, reloadTable }) => this.bulkActivateCallback(selectedRows, reloadTable)
}
]
}
}
},
methods: {
bulkDisableCallback(selectedRows, reloadTable) {
const url = '/api/v1/accounts/change-secret-automations/'
const data = selectedRows.map(row => {
return { id: row.id, is_active: false }
})
if (data.length === 0) return
this.$axios.patch(url, data).then(() => {
reloadTable()
this.$message.success(this.$t('common.disableSuccessMsg'))
}).catch(error => {
this.$message.error(this.$t('common.updateError') + ' ' + error)
})
},
bulkActivateCallback(selectedRows, reloadTable) {
const url = '/api/v1/accounts/change-secret-automations/'
const data = selectedRows.map(row => {
return { id: row.id, is_active: true }
})
if (data.length === 0) return
this.$axios.patch(url, data).then(() => {
reloadTable()
this.$message.success(this.$t('common.activateSuccessMsg'))
}).catch(error => {
this.$message.error(this.$t('common.updateError') + ' ' + error)
})
}
}
}
</script>

View File

@@ -127,9 +127,58 @@ export default {
headerActions: {
hasRefresh: true,
hasExport: false,
hasImport: false
hasImport: false,
createRoute: () => {
return {
name: 'AccountPushCreate'
}
},
extraMoreActions: [
{
name: 'BatchDisable',
title: this.$t('common.BatchDisable'),
icon: 'fa fa-ban',
can: ({ selectedRows }) => selectedRows.length > 0 && this.$hasPerm('accounts.change_pushaccountautomation'),
callback: ({ selectedRows, reloadTable }) => this.bulkDisableCallback(selectedRows, reloadTable)
},
{
name: 'BatchActivate',
title: this.$t('common.BatchActivate'),
icon: 'fa fa-check-circle-o',
can: ({ selectedRows }) => selectedRows.length > 0 && this.$hasPerm('accounts.change_pushaccountautomation'),
callback: ({ selectedRows, reloadTable }) => this.bulkActivateCallback(selectedRows, reloadTable)
}
]
}
}
},
methods: {
bulkDisableCallback(selectedRows, reloadTable) {
const url = '/api/v1/accounts/push-account-automations/'
const data = selectedRows.map(row => {
return { id: row.id, is_active: false }
})
if (data.length === 0) return
this.$axios.patch(url, data).then(() => {
reloadTable()
this.$message.success(this.$t('common.disableSuccessMsg'))
}).catch(error => {
this.$message.error(this.$t('common.updateError') + ' ' + error)
})
},
bulkActivateCallback(selectedRows, reloadTable) {
const url = '/api/v1/accounts/push-account-automations/'
const data = selectedRows.map(row => {
return { id: row.id, is_active: true }
})
if (data.length === 0) return
this.$axios.patch(url, data).then(() => {
reloadTable()
this.$message.success(this.$t('common.activateSuccessMsg'))
}).catch(error => {
this.$message.error(this.$t('common.updateError') + ' ' + error)
})
}
}
}
</script>

View File

@@ -32,7 +32,7 @@ export default {
tableConfig: {
url: '/api/v1/accounts/account-templates/',
columns: null,
columnsExclude: ['spec_info'],
columnsExclude: ['spec_info', 'password_rules', 'push_params'],
columnsShow: {
min: ['name', 'actions'],
default: ['name', 'username', 'secret_type', 'has_secret', 'privileged', 'actions']

View File

@@ -65,9 +65,11 @@ export default {
columns: ['name', 'username', 'secret_type', 'privileged'],
columnsMeta: {
name: {
formatterArgs: {
route: 'AccountTemplateDetail'
}
formatter: (row) => <span>{row.name}</span>
// 暂禁用远程应用中账号模板的详情跳转
// formatterArgs: {
// route: 'AccountTemplateDetail'
// }
},
privileged: {
width: '100px'

View File

@@ -102,7 +102,7 @@ export const ACCOUNT_PROVIDER_ATTRS_MAP = {
[vmware]: {
name: vmware,
title: 'VMware',
attrs: ['host', 'port', 'username', 'password']
attrs: ['host', 'port', 'username', 'password', 'auto_sync_node']
},
[nutanix]: {
name: nutanix,

View File

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

View File

@@ -20,7 +20,8 @@ export const platformFieldsMeta = (vm) => {
'change_secret_enabled', 'change_secret_method', 'change_secret_params',
'push_account_enabled', 'push_account_method', 'push_account_params',
'verify_account_enabled', 'verify_account_method', 'verify_account_params',
'gather_accounts_enabled', 'gather_accounts_method', 'gather_accounts_params'
'gather_accounts_enabled', 'gather_accounts_method', 'gather_accounts_params',
'remove_account_enabled', 'remove_account_method', 'remove_account_params'
],
fieldsMeta: {
ansible_config: {
@@ -28,12 +29,15 @@ export const platformFieldsMeta = (vm) => {
hidden: (formValue) => !formValue['ansible_enabled']
},
gather_facts_enabled: {},
remove_account_enabled: {},
ping_method: {},
ping_params: {
label: ''
},
gather_facts_method: {},
push_account_method: {},
remove_account_method: {},
remove_account_params: {},
push_account_params: {
label: ''
},

View File

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

View File

@@ -36,6 +36,11 @@ export default {
this.tableConfig.url = `/api/v1/perms/users/self/nodes/${currentNodeId}/assets/?cache_policy=1`
}
}.bind(this)
},
edit: {
drag: {
isMove: false
}
}
},
tableConfig: {

View File

@@ -205,6 +205,11 @@ export default {
view: {
dblClickExpand: false,
showLine: true
},
edit: {
drag: {
isMove: false
}
}
},
iShowTree: true,

View File

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

View File

@@ -290,6 +290,11 @@ export default {
view: {
dblClickExpand: false,
showLine: true
},
edit: {
drag: {
isMove: false
}
}
},
iShowTree: true

View File

@@ -11,7 +11,6 @@
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import AssetSelect from '@/components/Apps/AssetSelect'
import { getDayFuture } from '@/utils/common'
import AccountFormatter from './components/AccountFormatter'
import { AllAccount } from '../const'
import ProtocolsSelect from '@/components/Form/FormFields/AllOrSpec.vue'
@@ -34,7 +33,6 @@ export default {
initial: {
is_active: true,
date_start: new Date().toISOString(),
date_expired: getDayFuture(25550, new Date()).toISOString(),
nodes: nodesInitial,
assets: assetsInitial,
accounts: [AllAccount]

View File

@@ -107,6 +107,13 @@ export default {
this.$log.debug('AssetSelect value', that.assets)
this.$message.success(this.$tc('common.updateSuccessMsg'))
this.$store.commit('common/reload')
},
treeSetting: {
edit: {
drag: {
isMove: false
}
}
}
},
nodeRelationConfig: {

View File

@@ -39,7 +39,12 @@ export default {
notShowBuiltinTree: true,
url: '/api/v1/perms/asset-permissions/',
nodeUrl: '/api/v1/perms/asset-permissions/',
treeUrl: '/api/v1/assets/nodes/children/tree/?assets=1'
treeUrl: '/api/v1/assets/nodes/children/tree/?assets=1',
edit: {
drag: {
isMove: false
}
}
},
tableConfig: {
url: '/api/v1/perms/asset-permissions/',
@@ -105,6 +110,7 @@ export default {
formatter: AmountFormatter,
formatterArgs: {
async: true,
cellValueToRemove: ['@SPEC'],
routeQuery: {
activeTab: 'AssetPermissionUser'
}

View File

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

View File

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

View File

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

View File

@@ -24,6 +24,7 @@
v-model="requestForm.accounts"
:nodes="requestForm.nodes"
:assets="requestForm.assets"
:oid="requestForm.oid"
:show-add-template="false"
style="width: 50% !important"
/>
@@ -83,6 +84,7 @@ export default {
assets: this.object.apply_assets?.map(i => i.id),
accounts: this.object.apply_accounts,
actions: this.object.apply_actions,
oid: this.object.org_id,
apply_date_expired: this.object.apply_date_expired,
apply_date_start: this.object.apply_date_start
},

View File

@@ -48,7 +48,12 @@ export default {
}
],
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: [
]

View File

@@ -27,7 +27,7 @@
<el-form-item style="float: right">
<template v-if="hasActionPerm">
<el-button
:disabled="object.status.value === 'closed'"
:disabled="isDisabled || object.status.value === 'closed'"
size="small"
type="primary"
@click="handleApprove"
@@ -35,7 +35,7 @@
<i class="fa fa-check" /> {{ $t('tickets.Accept') }}
</el-button>
<el-button
:disabled="object.status.value === 'closed'"
:disabled="isDisabled || object.status.value === 'closed'"
size="small"
type="warning"
@click="handleReject"
@@ -45,12 +45,12 @@
</template>
<el-button
v-if="isSelfTicket"
:disabled="object.status.value === 'closed'"
:disabled="isDisabled || object.status.value === 'closed'"
size="small"
type="danger"
@click="handleClose"
>
<i class="fa fa-times" /> {{ $t('tickets.Close') }}
<i class="fa fa-times" /> {{ $t('tickets.CancelTicket') }}
</el-button>
<el-button
:disabled="object.status.value === 'closed'"
@@ -94,6 +94,7 @@ export default {
},
data() {
return {
isDisabled: false,
comments: '',
type_api: '',
imageUrl: require('@/assets/img/avatar.png'),
@@ -156,17 +157,35 @@ export default {
this.createComment(function() {
})
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() {
this.createComment(function() {
})
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() {
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) {
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() {
const handler = this.approve || this.defaultApprove
handler()
this.handleAction('approve')
},
handleReject() {
const handler = this.reject || this.defaultReject
handler()
this.handleAction('reject')
},
handleClose() {
const handler = this.close || this.defaultClose
handler()
this.handleAction('close')
},
handleComment() {
this.createComment(

View File

@@ -134,7 +134,7 @@ export default {
},
onMonitor() {
const joinUrl = `/luna/monitor/${this.session.id}?ticket_id=${this.object.id}`
window.open(joinUrl, 'height=600, width=850, top=400, left=400, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')
window.open(joinUrl, '_blank', 'height=600, width=850, top=400, left=400, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')
},
onToggleLock() {
const url = '/api/v1/terminal/tasks/toggle-lock-session-for-ticket/'

View File

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

View File

@@ -54,6 +54,11 @@ export default {
showRefresh: true,
showSearch: false,
treeUrl: '',
edit: {
drag: {
isMove: false
}
},
check: {
enable: true
},

View File

@@ -121,6 +121,9 @@ export default {
system_roles: {
component: Select2,
label: this.$t('users.SystemRoles'),
rules: [
rules.Required
],
el: {
multiple: true,
ajax: {

3666
yarn.lock

File diff suppressed because it is too large Load Diff