Compare commits

...

18 Commits

Author SHA1 Message Date
zhaojisen
3db0ed756e fixed: Fixed the issue that parameter push parameters could not be saved 2024-07-17 10:51:15 +08:00
feng626
453c4b1e4e Merge pull request #4222 from jumpserver/pr@dev@download
perf: Downloading files does not trigger the beforeunload event
2024-07-16 12:35:32 +08:00
feng
bf4d8ce7a6 perf: Downloading files does not trigger the beforeunload event 2024-07-16 12:34:18 +08:00
wangruidong
34effdbe15 perf: 社区版移除magnus 2024-07-15 19:27:16 +08:00
zhaojisen
274db466f2 fixed:Fixed an issue where the user was unable to enter non-MD content 2024-07-15 19:09:05 +08:00
ZhaoJiSen
cbe697f9dc Merge pull request #4218 from jumpserver/pr@dev@fix_job_tooltip
fixed: Fixed the tooltip shortcut command interface issue
2024-07-15 18:31:28 +08:00
zhaojisen
fb7acb100e fixed: Fixed the tooltip shortcut command interface issue 2024-07-15 18:28:15 +08:00
feng626
ebb36847df Merge pull request #4213 from jumpserver/pr@dev@cloud_sync_install
perf: Cloud sync instance execution add trigger
2024-07-15 18:02:46 +08:00
feng
beba4f1994 perf: Cloud sync instance execution add trigger 2024-07-11 17:15:58 +08:00
ZhaoJiSen
452796e3f5 Merge pull request #4212 from jumpserver/pr@dev@fix_params_remain
fixed: Fixed an issue where push parameters could not be saved when adding an asset account
2024-07-11 15:34:42 +08:00
zhaojisen
02a7969d90 fixed: Fixed an issue where push parameters could not be saved when adding an asset account 2024-07-11 15:32:19 +08:00
zhaojisen
798dbec151 fixed :Event bus shutdown 2024-07-11 15:20:51 +08:00
ibuler
0e11a56e37 perf: table search two times, one init one search 2024-07-11 15:20:01 +08:00
ibuler
be5344344c perf: view cache 2024-07-11 15:18:41 +08:00
ibuler
e75d711e0a perf: nest field change may be lead blink 2024-07-11 15:17:07 +08:00
wangruidong
46ee116f3e fix: role display error when update user 2024-07-11 15:07:29 +08:00
ZhaoJiSen
f4b304338f Merge pull request #4207 from jumpserver/pr@dev@fix_switch_showTooltip
fixed: Fixed tooltip issue with switch button
2024-07-11 10:48:46 +08:00
zhaojisen
e93e78307c fixed: Fixed tooltip issue with switch button 2024-07-11 10:47:33 +08:00
19 changed files with 230 additions and 132 deletions

View File

@@ -35,6 +35,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.15.14",
"eslint-plugin-html": "^6.0.0",

View File

@@ -85,6 +85,7 @@ export default {
// 如果不想等,证明是 value 自己变化导致的, 需要重新渲染
if (valJson !== this.formJson) {
this.iValue = val
this.$log.debug('Sub form value changed, rerender form: ', this.formJson, valJson)
this.loading = true
setTimeout(() => {
this.loading = false
@@ -95,11 +96,12 @@ export default {
}
},
methods: {
outputValue: _.debounce(function(val) {
this.$emit('input', val)
}),
updateValue(val) {
this.iValue = val
setTimeout(() => {
this.$emit('input', val)
}, 100)
this.outputValue(val)
},
objectToString(obj) {
let data = ''

View File

@@ -9,98 +9,115 @@
:prop="item.name"
>
<template v-if="item.type === 'button' && !item.isVisible">
<el-button
:type="item.el && item.el.type"
class="start-stop-btn"
size="mini"
@click="item.callback()"
>
<i :class="item.icon" />{{ item.name }}
</el-button>
<el-tooltip :disabled="!item.tip" :content="item.tip">
<el-button
:type="item.el && item.el.type"
class="start-stop-btn"
size="mini"
@click="item.callback()"
>
<i :class="item.icon" />
{{ item.name }}
</el-button>
</el-tooltip>
</template>
<template v-if="item.type === 'input' && item.el && item.el.autoComplete">
<el-autocomplete
v-model="formModel[item.name]"
:fetch-suggestions="item.el.query"
:placeholder="item.placeholder"
class="inline-input"
size="mini"
@change="handleInputChange(item)"
@select="handleInputChange(item)"
/>
<el-tooltip :disabled="!item.tip" :content="item.tip">
<el-autocomplete
v-model="formModel[item.name]"
:fetch-suggestions="item.el.query"
:placeholder="item.placeholder"
class="inline-input"
size="mini"
@change="handleInputChange(item)"
@select="handleInputChange(item)"
/>
</el-tooltip>
</template>
<template v-else-if="item.type === 'input'">
<el-input
v-model="formModel[item.name]"
:class="!isFold ? 'special-style' : ''"
:placeholder="item.placeholder"
class="inline-input"
size="mini"
@change="item.callback(formModel[item.name])"
/>
<el-tooltip :disabled="!item.tip" :content="item.tip">
<el-input
v-model="formModel[item.name]"
:class="!isFold ? 'special-style' : ''"
:placeholder="item.placeholder"
class="inline-input"
size="mini"
@change="item.callback(formModel[item.name])"
/>
</el-tooltip>
</template>
<template v-if="item.type === 'select' && item.el && item.el.create">
<span class="filter-label">{{ item.name }}:</span>
<el-select
v-if="item.type === 'select' && item.el && item.el.create"
:key="index"
v-model="formModel[item.name]"
:allow-create="item.el.create || false"
:filterable="item.el.create || false"
:multiple="item.el.multiple"
:placeholder="item.name"
class="autoWidth-select"
default-first-option
size="mini"
@change="item.callback(item.value)"
>
<template slot="prefix">{{ item.label + ':' + item.value }}</template>
<el-option
v-for="(option, id) in item.options"
:key="id"
:label="option.label"
:title="option.value"
:value="option.value"
/>
</el-select>
<el-tooltip :disabled="!item.tip" :content="item.tip">
<span class="filter-label">{{ item.name }}:</span>
<el-select
v-if="item.type === 'select' && item.el && item.el.create"
:key="index"
v-model="formModel[item.name]"
:allow-create="item.el.create || false"
:filterable="item.el.create || false"
:multiple="item.el.multiple"
:placeholder="item.name"
class="autoWidth-select"
default-first-option
size="mini"
@change="item.callback(item.value)"
>
<template slot="prefix">{{ item.label + ':' + item.value }}</template>
<el-option
v-for="(option, id) in item.options"
:key="id"
:label="option.label"
:title="option.value"
:value="option.value"
/>
</el-select>
</el-tooltip>
</template>
<template v-if="item.type === 'select' && (!item.el || !item.el.create)">
<el-dropdown
class="select-dropdown"
trigger="click"
@command="(command) => {
item.value = command
item.callback(command)
}"
>
<el-button size="mini" type="primary">
<div class="text-content">
<span class="content">
{{ getLabel(item.value, item.options) }}
<i class="el-icon-arrow-down el-icon--right" />
</span>
</div>
</el-button>
<el-dropdown-menu v-slot="dropdown">
<el-dropdown-item
v-for="(option, i) in item.options"
:key="i"
:command="option.value"
>
{{ option.label }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-tooltip :disabled="!item.tip" :content="item.tip">
<el-dropdown
class="select-dropdown"
trigger="click"
@command="(command) => {
item.value = command
item.callback(command)
}"
>
<el-button size="mini" type="primary">
<div class="text-content">
<span class="content">
{{ getLabel(item.value, item.options) }}
<i class="el-icon-arrow-down el-icon--right" />
</span>
</div>
</el-button>
<el-dropdown-menu v-slot="dropdown">
<el-dropdown-item
v-for="(option, i) in item.options"
:key="i"
:command="option.value"
>
{{ option.label }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-tooltip>
</template>
<template v-if="item.type === 'switch'">
<el-switch
v-model="formModel[item.name]"
:active-text="item.name"
:disabled="item.disabled"
@change="item.callback(formModel[item.name])"
/>
<el-tooltip :disabled="!item.tip" :content="item.tip">
<el-switch
v-model="formModel[item.name]"
:active-text="item.name"
:disabled="item.disabled"
@change="item.callback(formModel[item.name])"
/>
</el-tooltip>
</template>
</el-form-item>
<div

View File

@@ -90,8 +90,14 @@ export default {
}
}
},
beforeDestroy() {
this.$eventBus.$off('showColumnSettingPopover', this.showColumnSettingPopoverHandler)
},
mounted() {
this.$eventBus.$on('showColumnSettingPopover', ({ url }) => {
this.$eventBus.$on('showColumnSettingPopover', this.showColumnSettingPopoverHandler)
},
methods: {
showColumnSettingPopoverHandler({ url }) {
if (url === this.url) {
this.checkAll = false
this.showColumnSettingPopover = true
@@ -105,9 +111,7 @@ export default {
this.checkAll = false
this.isIndeterminate = true
}
})
},
methods: {
},
handleColumnConfirm() {
this.showColumnSettingPopover = false
this.$emit('columnsUpdate', { columns: this.iCurrentColumns, url: this.url })

View File

@@ -49,6 +49,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',
@@ -170,14 +171,18 @@ export default {
]
}
},
beforeDestroy() {
this.$eventBus.$off('showExportDialog', this.showExportDialogHandler)
},
mounted() {
this.$eventBus.$on('showExportDialog', ({ selectedRows, url, name }) => {
this.$eventBus.$on('showExportDialog', this.showExportDialogHandler)
},
methods: {
showExportDialogHandler({ selectedRows, url, name }) {
if (url === this.url || url.indexOf(this.url) > -1) {
this.showExportDialog()
}
})
},
methods: {
},
showExportDialog() {
if (!this.mfaVerifyRequired) {
this.exportDialogShow = true
@@ -197,10 +202,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 {
@@ -142,14 +142,18 @@ export default {
this.showTable = false
}
},
beforeDestroy() {
this.$eventBus.$off('showImportDialog', this.showImportEventHandler)
},
mounted() {
this.$eventBus.$on('showImportDialog', ({ url }) => {
this.$eventBus.$on('showImportDialog', this.showImportEventHandler)
},
methods: {
showImportEventHandler({ url }) {
if (url === this.url) {
this.showImportDialog = true
}
})
},
methods: {
},
closeDialog() {
this.showImportDialog = false
this.$emit('importDialogClose')
@@ -226,10 +230,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

@@ -71,7 +71,16 @@ export default {
this.listenViewPort()
},
mounted() {
this.$eventBus.$on('labelSearch', label => {
this.$eventBus.$on('labelSearch', this.labelSearchHandler)
},
beforeDestroy(label) {
this.$eventBus.$off('labelSearch', this.labelSearchHandler)
},
methods: {
handleCascaderFocus() {
this.setSearchFocus()
},
labelSearchHandler(label) {
if (!label) {
this.labelValue = []
this.showLabelSearch = true
@@ -82,14 +91,6 @@ export default {
setTimeout(() => {
this.showLabelSearch = true
}, 500)
})
},
destroyed() {
this.$eventBus.$off('labelSearch')
},
methods: {
handleCascaderFocus() {
this.setSearchFocus()
},
handleCascaderVisibleChange(visible) {
const input = this.$refs.labelCascader.$el

View File

@@ -124,6 +124,9 @@ export default {
return this.iHasLeftActions ? 'right' : 'left'
}
},
created() {
this.$emit('done')
},
methods: {
handleTagSearch(val) {
this.searchTable(val)

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,9 +75,11 @@ export default {
return {
selectedRows: [],
init: false,
extraQuery: extraQuery,
urlUpdated: {},
isDeactivated: false
isDeactivated: false,
extraQuery: extraQuery,
actionInit: this.headerActions.has === false,
initQuery: {}
}
},
computed: {
@@ -203,13 +207,35 @@ export default {
}, 500)
},
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) {
const init = this.updateInitQuery(attrs)
if (init) {
return
}
this.$log.debug('ListTable: search table', attrs)
this.$emit('TagSearch', attrs)
this.$refs.dataTable?.$refs.dataTable?.search(attrs, true)

View File

@@ -4,28 +4,29 @@
<div class="action-bar">
<div class="action">
<span>
<i :class="[!isShow ? 'fa-eye' : 'fa-eye-slash']" class="fa" @click="onView" />
<i class="fa" :class="[!isShow ? 'fa-eye' : 'fa-eye-slash']" @click="onView" />
</span>
</div>
</div>
<el-col :span="span" :style="{'height': height + 'px' }">
<el-input
v-model="iValue"
:rows="rows"
autosize
:rows="rows"
type="textarea"
@change="onChange"
/>
</el-col>
<el-col v-show="isShow" :span="span">
<VueMarkdown :html="false" :show="true" :source="iValue" class="result-html" />
<VueMarkdown class="result-html" :source="sanitizedValue" :html="false" :show="true" />
</el-col>
</el-row>
<VueMarkdown v-else :html="false" :source="iValue" class="source" />
<VueMarkdown v-else class="source" :html="false" :source="sanitizedValue" />
</div>
</template>
<script>
import DOMPurify from 'dompurify'
import VueMarkdown from 'vue-markdown'
export default {
@@ -55,6 +56,17 @@ export default {
iValue: this.value
}
},
computed: {
sanitizedValue() {
// 转义特殊字符
let content = this.iValue.replace(/\\/g, '\\\\').replace(/\$/g, '\\$')
// 使用 DOMPurify 进行 XSS 过滤
content = DOMPurify.sanitize(content)
return content
}
},
mounted() {
this.$nextTick(() => {
this.resizeObserver = new ResizeObserver(entries => {

View File

@@ -23,8 +23,18 @@ export default {
'publicSettings'
]),
key() {
// 想让创建后回来 List 页面不刷新,但是完全不刷新 table 会不对,所以创建完成后,会更新 order 和 updated
// query 去掉这两个,如果变了再刷新
const query = {}
for (const [k, v] of Object.entries(this.$route.query)) {
if (k.includes('updated') || k.includes('order')) {
continue
}
query[k] = v
}
if (this.$route.name.toLowerCase().includes('list')) {
return _.trimEnd(this.$route.path, '/')
return _.trimEnd(this.$route.path, '/') + '?' + new URLSearchParams(query).toString()
} else {
return new Date().getTime()
}

View File

@@ -217,13 +217,19 @@ export function downloadText(content, filename) {
}
export function download(downloadUrl, filename) {
const iframe = document.createElement('iframe')
iframe.style.display = 'none'
document.body.appendChild(iframe)
const a = document.createElement('a')
a.href = downloadUrl
if (filename) {
a.download = filename
}
iframe.contentWindow.document.body.appendChild(a)
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) {

View File

@@ -26,7 +26,8 @@ Object.assign(Table.components.TableBody.methods, {
const range = document.createRange()
range.setStart(cellChild, 0)
range.setEnd(cellChild, cellChild.childNodes.length)
const rangeWidth = range.getBoundingClientRect().width
// rangeWidth 有可能是小数,因此就会导致原本 rangeWidth + padding = cellChild.offsetWidth 的大于了 cellChild.offsetWidth
const rangeWidth = Math.floor(range.getBoundingClientRect().width)
const padding = (parseInt(getStyle(cellChild, 'paddingLeft'), 10) || 0) +
(parseInt(getStyle(cellChild, 'paddingRight'), 10) || 0)
if (

View File

@@ -64,8 +64,11 @@ export default {
formatter: DateFormatter
},
{
prop: 'summary.triggerMode',
label: this.$t('TriggerMode')
prop: 'trigger',
label: this.$t('TriggerMode'),
formatter: row => {
return row.trigger.label
}
},
{
prop: 'actions',

View File

@@ -10,8 +10,6 @@
@click="onSetting"
/>
<Dialog
v-if="isVisible"
:destroy-on-close="true"
:show-cancel="false"
:show-confirm="false"
:title="title"

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',
@@ -134,10 +135,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

@@ -52,6 +52,14 @@ export default {
el: {
hiddenGroup: true
}
},
TERMINAL_MAGNUS_ENABLED: {
hidden: () => {
return !this.$store.getters.hasValidLicense
},
el: {
hiddenGroup: true
}
}
},
getUrl: () => '/api/v1/settings/setting/?category=terminal',

View File

@@ -174,8 +174,8 @@ export default {
},
afterGetFormValue(obj) {
if (obj?.id) {
obj.org_roles = obj.org_roles.map(({ id }) => id)
obj.system_roles = obj.system_roles.map(({ id }) => id)
obj.org_roles = obj.org_roles?.map(({ id }) => id)
obj.system_roles = obj.system_roles?.map(({ id }) => id)
}
return obj
},

View File

@@ -4292,6 +4292,11 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.2.2, domhandler@^4.3.1:
dependencies:
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:
version "1.0.8"
resolved "https://registry.npmmirror.com/domready/-/domready-1.0.8.tgz"