Compare commits

...

32 Commits

Author SHA1 Message Date
Jiangjie Bai
a1d76b8ff0 Merge pull request #5314 from jumpserver/dev
v4.10.15
2026-01-22 16:34:12 +08:00
w940853815
1c527e9d77 perf: Translate ticket status 2026-01-22 14:31:25 +08:00
w940853815
eb9c9344eb perf: Translate quick filter 2026-01-22 11:22:15 +08:00
feng
6d8a931132 perf: Ticket action 2026-01-21 16:10:14 +08:00
w940853815
8cd3c5fa47 fix: Don't repeat the error message. 2026-01-21 15:51:17 +08:00
zhaojisen
ed770bd629 fix: Fix the icon display issue of the cloud synchronization card 2026-01-21 11:28:47 +08:00
feng
bdeded024d perf: Change secret execution detail remove recipients 2026-01-21 11:28:28 +08:00
w940853815
771ee96a1a fix: MFA display problem on user detail page 2026-01-20 16:48:59 +08:00
w940853815
dd9b40de20 fix: Ensure password is converted to string before encryption 2026-01-20 16:48:23 +08:00
zhaojisen
d360cadadb fix: Fix the issue where the current node asset cannot be displayed independently 2026-01-20 16:19:57 +08:00
fit2bot
86814693af fix: Fix the issue of detail navigation for account template settings (#5302) 2026-01-19 14:43:53 +08:00
Chenyang Shen
6ad0a49392 Merge pull request #5300 from jumpserver/pr@dev@feat_update_facelive_check
feat: update facelive license check
2026-01-14 16:21:23 +08:00
Aaron3S
465eb45ff9 feat: update facelive license check 2026-01-14 16:16:20 +08:00
feng
f258b47ba6 perf: Move custom ai model 2026-01-05 16:48:31 +08:00
feng
0a8ed3c4cc perf: Ticket quick filter 2025-12-24 15:51:34 +08:00
zhaojisen
1cd1437357 Fixed: panel title show 2025-12-23 12:14:22 +08:00
zhaojisen
ef2c72b80e perf: optimism panel style 2025-12-23 12:02:45 +08:00
zhaojisen
cf3d2fff03 Fix the problem of overly long names 2025-12-23 11:44:37 +08:00
w940853815
3221e44e69 fix: Ticket remove import and add export support 2025-12-22 15:26:48 +08:00
Jiangjie Bai
0fbdb3d94e Merge pull request #5293 from jumpserver/dev
v4.10.14
2025-12-18 17:23:42 +08:00
w940853815
65706509b1 fix:Disabled assets have no audit records 2025-12-18 11:48:17 +08:00
feng
3d668502e1 perf: chat ai custom model 2025-12-12 15:18:16 +08:00
w940853815
56e07c8568 fix: Remove 'Account' option from const.js 2025-12-11 17:01:08 +08:00
w940853815
c2f7a9e8e2 feat: Implement secret readability control based on system settings 2025-12-11 16:42:43 +08:00
w940853815
f0949f0e54 feat: Add permission check for reading account secrets based on system settings 2025-12-11 16:42:43 +08:00
w940853815
a165e45937 perf: Include 'id' in search and filter fields for AutomationExecution 2025-12-11 14:47:58 +08:00
zhaojisen
6350bdb42d Fixed: Fix the issue where the remote application details page is lost and the account needs to be regenerated 2025-12-10 17:31:49 +08:00
w940853815
4ecaee36f9 fix: Remove unused relevant_system_user option from BaseTicketList 2025-12-09 14:41:52 +08:00
w940853815
43e7e5cd74 fix: Remove unused CAS_CREATE_USER attribute from options 2025-12-04 17:40:54 +08:00
Bai
4332dbcc1c feat: add access token page 2025-12-01 17:54:55 +08:00
jiangweidong
a755b2ffa0 perf: increase joint camp areas 2025-12-01 10:56:32 +08:00
jiangweidong
d391592778 feat: Host cloud sync supports state cloud 2025-12-01 10:56:32 +08:00
29 changed files with 426 additions and 178 deletions

View File

@@ -140,5 +140,6 @@
"src/**/*.{js,vue}": [
"eslint --fix"
]
}
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View File

@@ -62,6 +62,7 @@ import Dialog from '@/components/Dialog/index.vue'
import PasswordHistoryDialog from './PasswordHistoryDialog.vue'
import { SecretViewerFormatter } from '@/components/Table/TableFormatters'
import { encryptPassword } from '@/utils/secure'
import { mapGetters } from 'vuex'
export default {
name: 'ShowSecretInfo',
@@ -111,6 +112,9 @@ export default {
}
},
computed: {
...mapGetters({
publicSettings: 'publicSettings'
}),
secretTypeLabel() {
return this.account['secret_type'].label || 'Password'
},
@@ -146,7 +150,11 @@ export default {
})
},
showSecretDialog() {
return this.$axios.get(this.url, { disableFlashErrorMsg: true }).then((res) => {
if (!this.publicSettings.SECURITY_ACCOUNT_SECRET_READ) {
this.$message.warning(this.$tc('AccountSecretReadDisabled'))
return
}
return this.$axios.get(this.url).then((res) => {
this.secretInfo = res
this.sshKeyFingerprint = res?.spec_info?.ssh_key_fingerprint || '-'
this.showSecret = true
@@ -167,54 +175,54 @@ export default {
</script>
<style lang="scss" scoped>
.item-textarea ::v-deep .el-textarea__inner {
height: 110px;
.item-textarea ::v-deep .el-textarea__inner {
height: 110px;
}
.el-form-item {
border-bottom: 1px solid #EBEEF5;
padding: 5px 0;
margin-bottom: 0;
&:last-child {
border-bottom: none;
}
.el-form-item {
border-bottom: 1px solid #EBEEF5;
padding: 5px 0;
margin-bottom: 0;
&:last-child {
border-bottom: none;
}
::v-deep .el-form-item__label {
display: flex;
align-items: center;
justify-content: flex-start;
padding-right: 20px;
line-height: 30px;
word-break: keep-all;
overflow-wrap: break-word;
white-space: normal;
}
::v-deep .el-form-item__content {
line-height: 30px;
pre {
margin: 0;
}
}
::v-deep .el-form-item__label {
display: flex;
align-items: center;
justify-content: flex-start;
padding-right: 20px;
line-height: 30px;
word-break: keep-all;
overflow-wrap: break-word;
white-space: normal;
}
ul {
margin: 0;
}
::v-deep .el-form-item__content {
line-height: 30px;
li {
display: block;
font-size: 13px;
margin-bottom: 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.title {
color: #303133;
font-weight: 500;
pre {
margin: 0;
}
}
}
ul {
margin: 0;
}
li {
display: block;
font-size: 13px;
margin-bottom: 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.title {
color: #303133;
font-weight: 500;
}
}
</style>

View File

@@ -20,7 +20,7 @@
<script>
import TreeTable from '../../Table/TreeTable/index.vue'
import { setRouterQuery, setUrlParam } from '@/utils/common/index'
import { getShowCurrentAssetValue, setRouterQuery, setUrlParam } from '@/utils/common/index'
import $ from '@/utils/jquery-vendor'
export default {
@@ -160,7 +160,7 @@ export default {
return str
},
decorateRMenu() {
const show_current_asset = this.$cookie.get('show_current_asset') || '0'
const show_current_asset = getShowCurrentAssetValue(this.$cookie)
if (show_current_asset === '1') {
$('#m_show_asset_all_children_node').css('color', '#606266')
$('#m_show_asset_only_current_node').css('color', 'green')
@@ -172,6 +172,7 @@ export default {
getAssetsUrl(treeNode) {
let url = this.treeSetting?.url || this.url
const showCurrentAsset = getShowCurrentAssetValue(this.$cookie)
const setParam = (param, value, delay) => {
setTimeout(() => {
@@ -183,10 +184,12 @@ export default {
const nodeId = treeNode.meta.data.id
setParam('node_id', nodeId)
setParam('asset_id', '')
setParam('show_current_asset', showCurrentAsset)
} else if (treeNode.meta.type === 'asset') {
const assetId = treeNode.meta.data?.id || treeNode.id
setParam('node_id', '')
setParam('asset_id', assetId)
setParam('show_current_asset', showCurrentAsset)
} else if (treeNode.meta.type === 'category') {
setParam('category', treeNode.meta.category)
} else if (treeNode.meta.type === 'type') {

View File

@@ -4,15 +4,11 @@
<div class="panel-title">
<el-avatar :src="imageUrl" shape="square" />
<div class="title-display">
<span class="name">{{ object.name }}</span>
<p class="name" :title="object.name">{{ object.name }}</p>
<span class="comment">{{ object.provider.label }}</span>
</div>
</div>
<div
v-if="iActions.length !== 0"
class="panel-actions"
@click="handleClick($event)"
>
<div v-if="iActions.length !== 0" class="panel-actions" @click="handleClick($event)">
<el-dropdown>
<el-button size="mini">
<i class="el-icon-more el-icon--right" />
@@ -64,21 +60,19 @@ export default {
},
getImage: {
type: Function,
default: (obj) => ''
default: obj => ''
},
getInfos: {
type: Function,
default: (obj) => []
default: obj => []
},
handleUpdate: {
type: Function,
default: () => {
}
default: () => {}
},
onView: {
type: Function,
default: () => {
}
default: () => {}
}
},
data() {
@@ -152,13 +146,13 @@ export default {
</script>
<style lang="scss" scoped>
div.info-panel {
display: flex;
flex-direction: column;
padding: 10px;
gap: 10px;
gap: unset;
cursor: pointer;
height: initial !important;
.panel-header {
padding: 10px 20px;
@@ -175,12 +169,23 @@ div.info-panel {
.title-display {
display: flex;
flex-basis: 225px;
flex-direction: column;
text-align: left;
justify-content: center;
align-items: start;
max-width: 225px;
min-width: 0;
overflow-x: hidden;
.name {
font-size: 1.1em;
color: #555555;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
margin: unset;
text-align: start;
}
.comment {
@@ -193,6 +198,7 @@ div.info-panel {
::v-deep {
.el-avatar {
background: #fff;
flex-shrink: 0;
}
}
}

View File

@@ -111,7 +111,7 @@ export default {
{
name: 'actionFilter',
icon: 'filter',
tip: this.$t('Filter'),
tip: this.$t('QuickFilter'),
has: this.hasQuickFilter,
callback: this.handleFilterClick.bind(this)
},

View File

@@ -38,6 +38,7 @@
<script>
import { copy, downloadText } from '@/utils/common/index'
import BaseFormatter from '@/components/Table/TableFormatters/base.vue'
import { mapGetters } from 'vuex'
export default {
name: 'SecretViewerFormatter',
@@ -69,6 +70,9 @@ export default {
}
},
computed: {
...mapGetters({
publicSettings: 'publicSettings'
}),
hasShow: function() {
return this.formatterArgs.hasShow
},
@@ -140,6 +144,10 @@ export default {
},
methods: {
async getAccountSecret() {
if (!this.publicSettings.SECURITY_ACCOUNT_SECRET_READ) {
this.$message.warning(this.$tc('AccountSecretReadDisabled'))
return
}
if (this.formatterArgs.secretFrom === 'cellValue' || this.getIt) {
return
}
@@ -178,48 +186,48 @@ export default {
}
</script>
<style lang="scss" scoped>
.content {
display: inline-block;
width: 100%;
.content {
display: inline-block;
width: 100%;
overflow: hidden;
//white-space: nowrap;
text-overflow: ellipsis;
font-size: 13px;
.text {
flex: 1;
display: inline;
margin: 0;
padding: 0;
overflow: hidden;
//white-space: nowrap;
white-space: nowrap;
text-overflow: ellipsis;
}
.action {
font-size: 13px;
cursor: pointer;
margin-left: 1px;
display: inline;
.text {
flex: 1;
display: inline;
margin: 0;
padding: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
&.right {
float: right;
}
.action {
font-size: 13px;
cursor: pointer;
margin-left: 1px;
display: inline;
.fa {
margin-right: 5px;
&.right {
float: right;
}
.fa {
margin-right: 5px;
&:hover {
color: var(--color-primary);
}
&:hover {
color: var(--color-primary);
}
}
}
}
.edit-input ::v-deep input {
border-left: none;
border-right: none;
border-top: none;
height: 30px;
}
.edit-input ::v-deep input {
border-left: none;
border-right: none;
border-top: none;
height: 30px;
}
</style>

View File

@@ -26,6 +26,7 @@
import DataZTree from '../DataZTree/index.vue'
import Icon from '@/components/Widgets/Icon'
import $ from '@/utils/jquery-vendor'
import { getShowCurrentAssetValue } from '@/utils/common/index'
import { mapGetters } from 'vuex'
export default {
@@ -199,7 +200,7 @@ export default {
},
// Request URL: http://localhost/api/v1/assets/assets/?node_id=ID&show_current_asset=null&draw=2&limit=15&offset=0&_=1587022917769
onSelected: function(event, treeNode) {
const show_current_asset = this.$cookie.get('show_current_asset') || '0'
const show_current_asset = getShowCurrentAssetValue(this.$cookie)
if (!this.setting.url) {
return
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free v5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M512 176.001C512 273.203 433.202 352 336 352c-11.22 0-22.19-1.062-32.827-3.069l-24.012 27.014A23.999 23.999 0 0 1 261.223 384H224v40c0 13.255-10.745 24-24 24h-40v40c0 13.255-10.745 24-24 24H24c-13.255 0-24-10.745-24-24v-78.059c0-6.365 2.529-12.47 7.029-16.971l161.802-161.802C163.108 213.814 160 195.271 160 176 160 78.798 238.797.001 335.999 0 433.488-.001 512 78.511 512 176.001zM336 128c0 26.51 21.49 48 48 48s48-21.49 48-48-21.49-48-48-48-48 21.49-48 48z"/></svg>

After

Width:  |  Height:  |  Size: 691 B

View File

@@ -123,6 +123,16 @@ export default {
permissions: ['authentication.view_connectiontoken']
}
},
{
path: '/profile/access-token',
component: () => import('@/views/profile/AccessToken'),
name: 'AccessToken',
meta: {
title: i18n.t('AccessToken'),
icon: 'access-token',
permissions: ['oauth2_provider.view_accesstoken']
}
},
{
path: '/profile/preferences',
name: 'Preferences',

View File

@@ -150,10 +150,11 @@ export function getErrorResponseMsg(error) {
} else if (typeof data === 'string') {
return data
} else if (_.isPlainObject(data)) {
return Object.values(data)
const msg = Object.values(data)
.map(item => getErrorResponseMsg(item))
.filter(i => i)
.join('; ')
// 错误信息不要重复提示
return [...new Set(msg)].join('; ')
} else {
msg = error.toString()
}
@@ -423,6 +424,28 @@ export function getDrawerWidth() {
return '90%'
}
export function getShowCurrentAssetValue(cookie, defaultValue = '0') {
const stored = typeof window !== 'undefined'
? window.localStorage.getItem('show_current_asset')
: null
if (stored === '0' || stored === '1') {
return stored
}
if (cookie && typeof cookie.get === 'function') {
return cookie.get('show_current_asset') || defaultValue
}
return defaultValue
}
export function setShowCurrentAssetValue(cookie, value) {
if (typeof window !== 'undefined') {
window.localStorage.setItem('show_current_asset', String(value))
}
if (cookie && typeof cookie.set === 'function') {
cookie.set('show_current_asset', value, 1)
}
}
export class ObjectLocalStorage {
constructor(key, attr) {
this.key = key

View File

@@ -92,7 +92,7 @@ export function encryptPassword(password) {
rsaPublicKeyText = rsaPublicKeyText.replaceAll('"', '')
const rsaPublicKey = atob(rsaPublicKeyText)
const keyCipher = rsaEncrypt(aesKey, rsaPublicKey)
const passwordCipher = aesEncrypt(password, aesKey)
const passwordCipher = aesEncrypt(String(password), aesKey)
return `${keyCipher}:${passwordCipher}`
}

View File

@@ -38,11 +38,6 @@ export default {
value: this.object.snapshot.node_amount
},
'trigger_display', 'date_start', 'date_finished',
{
key: this.$t('MailRecipient'),
value: this.object.recipients ? this.object.recipients.map(
i => `${i[0]}` + `${i[1] ? ': ' + this.$t('ContainAttachment') : ''}`).join(', ') : ''
},
{
key: this.$t('Comment'),
value: this.object.snapshot.common

View File

@@ -5,7 +5,12 @@
</el-alert>
<TwoCol>
<template>
<GenericListTable ref="listTable" :header-actions="headerActions" :table-config="tableConfig" />
<GenericListTable
ref="listTable"
:detail-drawer="detailDrawer"
:header-actions="headerActions"
:table-config="tableConfig"
/>
</template>
<template #right>
<QuickActions :actions="quickActions" type="primary" />
@@ -21,12 +26,13 @@
</template>
<script>
import GenericListTable from '@/layout/components/GenericListTable'
import { QuickActions } from '@/components'
import { ActionsFormatter, DetailFormatter } from '@/components/Table/TableFormatters'
import ViewSecret from '@/components/Apps/AccountListTable/ViewSecret'
import { openTaskPage } from '@/utils/jms/index'
import { GenericListTable } from '@/layout/components'
import { ActionsFormatter, DetailFormatter } from '@/components/Table/TableFormatters'
import TwoCol from '@/layout/components/Page/TwoColPage.vue'
import ViewSecret from '@/components/Apps/AccountListTable/ViewSecret'
export default {
name: 'AccountTemplateChangeSecret',
@@ -46,6 +52,7 @@ export default {
data() {
const vm = this
return {
detailDrawer: () => import('@/views/accounts/AccountDiscover/TaskDetail/index.vue'),
visible: false,
secretUrl: '',
showViewSecretDialog: false,
@@ -58,20 +65,20 @@ export default {
},
callbacks: Object.freeze({
click: () => {
this.$axios.patch(
`/api/v1/accounts/account-templates/${this.object.id}/sync-related-accounts/`
).then(res => {
openTaskPage(res['task'])
})
this.$axios
.patch(
`/api/v1/accounts/account-templates/${this.object.id}/sync-related-accounts/`
)
.then(res => {
openTaskPage(res['task'])
})
}
})
}
],
tableConfig: {
url: `/api/v1/accounts/accounts/?source_id=${this.object.id}`,
columns: [
'name', 'asset', 'secret_type', 'is_active', 'date_created'
],
columns: ['name', 'asset', 'secret_type', 'is_active', 'date_created'],
columnsMeta: {
name: {
formatter: DetailFormatter,
@@ -79,8 +86,9 @@ export default {
drawer: true,
can: vm.$hasPerm('accounts.view_account'),
getRoute: ({ row }) => {
this.detailDrawer = () => import('@/views/accounts/Account/AccountDetail/index.vue')
return {
name: 'AssetAccountDetail',
name: 'AccountDetail',
params: { id: row.id }
}
}
@@ -94,6 +102,7 @@ export default {
can: vm.$hasPerm('assets.view_asset'),
getTitle: ({ row }) => row.asset.name,
getRoute: ({ row }) => {
this.detailDrawer = () => import('@/views/assets/Asset/AssetDetail')
return {
name: 'AssetDetail',
params: { id: row.asset.id }

View File

@@ -137,8 +137,8 @@ export default {
getUrlQuery: true,
options: [
{
label: this.$t('TaskID'),
value: 'automation_id'
label: this.$t('ID'),
value: 'id'
},
{
label: this.$t('DisplayName'),

View File

@@ -61,8 +61,11 @@ export default {
},
callbacks: {
change: function(val) {
const category = this.object.category.value
const normalizedCategory =
category === 'ds' ? 'directorie' : category
this.$axios.patch(
`/api/v1/assets/assets/${this.object.id}/`,
`/api/v1/assets/${normalizedCategory}s/${this.object.id}/`,
{ is_active: val }
).then(res => {
this.$message.success(this.$tc('UpdateSuccessMsg'))

View File

@@ -28,7 +28,12 @@ import { mapGetters } from 'vuex'
import TreeMenu from './components/TreeMenu'
import BaseList from './components/BaseList'
import $ from '@/utils/jquery-vendor'
import { setRouterQuery, setUrlParam } from '@/utils/common/index'
import {
getShowCurrentAssetValue,
setShowCurrentAssetValue,
setRouterQuery,
setUrlParam
} from '@/utils/common/index'
export default {
components: {
@@ -73,7 +78,7 @@ export default {
},
methods: {
decorateRMenu() {
const show_current_asset = this.$cookie.get('show_current_asset') || '0'
const show_current_asset = getShowCurrentAssetValue(this.$cookie)
if (show_current_asset === '1') {
$('#m_show_asset_all_children_node').css('color', '#606266')
$('#m_show_asset_only_current_node').css('color', 'green')
@@ -83,7 +88,7 @@ export default {
}
},
showAll({ node, showCurrentAsset }) {
this.$cookie.set('show_current_asset', showCurrentAsset, 1)
setShowCurrentAssetValue(this.$cookie, showCurrentAsset)
this.decorateRMenu()
const url = `${this.treeSetting.url}?node_id=${node.meta.data.id}&show_current_asset=${showCurrentAsset}`
this.$refs.AssetTreeTable.$refs.TreeList.handleUrlChange(url)

View File

@@ -77,7 +77,7 @@ export default {
[this.$tc('Platform'), 'platform'],
[this.$tc('Node'), 'node'],
[this.$tc('Protocol'), 'protocols'],
[this.$tc('Region'), 'region_id']
[this.$tc('Region'), 'region_name']
],
data: []
},
@@ -124,7 +124,7 @@ export default {
this.ws.onmessage = e => {
const data = JSON.parse(e.data)
if (data.action === 'sync_region') {
this.addRegion(data.region_id)
this.addRegion(data.id, data.name)
} else if (data.action === 'import') {
data['@status'] = 'pending'
this.$refs.importTable.addTableItem(data)
@@ -140,10 +140,10 @@ export default {
}
}
},
addRegion(region) {
if (!this.alreadySync.includes(region)) {
this.alreadySync.push(region)
this.tip = `${this.$t('SyncRegion')}: ${this.alreadySync.at(-1)}`
addRegion(regionId, regionName) {
if (!this.alreadySync.includes(regionId)) {
this.alreadySync.push(regionId)
this.tip = `${this.$t('SyncRegion')}: ${regionName}`
}
},
showResult() {

View File

@@ -17,6 +17,8 @@ export const ucloud = 'ucloud'
export const volcengine = 'volcengine'
export const ctyun = 'ctyun'
export const qingcloud_private = 'qingcloud_private'
export const huaweicloud_private = 'huaweicloud_private'
export const ctyun_private = 'ctyun_private'
@@ -46,7 +48,8 @@ export const publicHostProviders = [
gcp,
ucloud,
volcengine,
smartx
smartx,
ctyun
]
export const publicDBProviders = [aliyun]
@@ -141,6 +144,12 @@ export const ACCOUNT_PROVIDER_ATTRS_MAP = {
attrs: ['access_key_id', 'access_key_secret'],
image: require('@/assets/img/cloud/volcengine.svg')
},
[ctyun]: {
name: ctyun,
title: i18n.t('CTYun'),
attrs: ['access_key_id', 'access_key_secret', 'project_id'],
image: require('@/assets/img/cloud/state.svg')
},
[vmware]: {
name: vmware,
title: 'VMware',

View File

@@ -8,7 +8,6 @@ export const UserAssetPermissionListPageSearchConfigOptions = [
{ label: i18n.t('UserGroups'), value: 'user_group' },
{ label: i18n.t('AssetName'), value: 'asset_name' },
{ label: i18n.t('AssetAddress'), value: 'address' },
{ label: i18n.t('Account'), value: 'accounts' },
{
label: i18n.t('Valid'),
value: 'is_valid',

View File

@@ -0,0 +1,77 @@
<template>
<GenericListPage
ref="GenericListTable"
:header-actions="headerActions"
:help-tip="helpMessage"
:table-config="tableConfig"
/>
</template>
<script>
import { GenericListPage } from '@/layout/components'
export default {
components: {
GenericListPage
},
data() {
const ajaxUrl = '/api/v1/authentication/access-tokens/'
return {
helpMessage: this.$t('AccessTokenTip'),
tableConfig: {
hasSelection: false,
url: ajaxUrl,
columns: [
'token_preview', 'scope', 'is_valid', 'expires', 'updated', 'created', 'actions'
],
columnsMeta: {
actions: {
prop: '',
formatterArgs: {
hasUpdate: false,
hasClone: false,
hasDelete: false,
extraActions: [
{
name: 'Revoke',
title: this.$t('Revoke'),
can: ({ row }) => this.$hasPerm('oauth2_provider.delete_accesstoken'),
type: 'info',
callback: function({ row }) {
this.$axios.delete(`${ajaxUrl}${row.id}/revoke/`,
).then(res => {
this.reloadTable()
this.$message.success(this.$tc('UpdateSuccessMsg'))
}).catch(error => {
this.$message.error(this.$tc('UpdateErrorMsg' + ' ' + error))
})
}.bind(this)
}
]
}
}
}
},
headerActions: {
hasLeftActions: false,
hasSearch: false,
hasRightActions: true,
hasRefresh: true,
hasExport: false,
hasImport: false,
hasBulkDelete: false,
hasCreate: false,
extraActions: []
}
}
},
methods: {
reloadTable() {
this.$refs.GenericListTable.reloadTable()
}
}
}
</script>
<style scoped>
</style>

View File

@@ -96,7 +96,6 @@ export default {
{
title: this.$t('FacialFeatures'),
has: this.$store.getters.publicSettings.FACE_RECOGNITION_ENABLED &&
this.$store.getters.publicSettings.XPACK_LICENSE_EDITION_ULTIMATE &&
!store.getters.publicSettings['PRIVACY_MODE'],
attrs: {
type: 'primary',

View File

@@ -1,36 +1,41 @@
<template>
<Account
:url="url"
:columns-meta="columnsMeta"
:extra-quick-actions="quickActions"
:object.sync="object"
/>
<TwoCol>
<template>
<Account :url="url" :columns-meta="columnsMeta" :object.sync="object" />
</template>
<template #right>
<QuickActions :actions="quickActions" type="primary" />
</template>
</TwoCol>
</template>
<script>
import { QuickActions } from '@/components'
import Account from '@/views/assets/Asset/AssetDetail/Account'
import TwoCol from '@/layout/components/Page/TwoColPage.vue'
export default {
name: 'Accounts',
components: {
Account
TwoCol,
Account,
QuickActions
},
props: {
object: {
type: Object,
default: () => {
}
default: () => {}
}
},
data() {
return {
columnsMeta: {
name: {
formatter: (row) => <span>{row.name}</span>
formatter: row => <span>{row.name}</span>
},
asset: {
label: this.$t('Asset'),
formatter: (row) => <span>{row.asset.name}</span>
formatter: row => <span>{row.asset.name}</span>
}
},
quickActions: [

View File

@@ -29,7 +29,7 @@ export default {
'CAS_RENAME_ATTRIBUTES'
]],
[this.$t('Other'), [
'CAS_ORG_IDS', 'CAS_CREATE_USER', 'CAS_LOGOUT_COMPLETELY'
'CAS_ORG_IDS', 'CAS_LOGOUT_COMPLETELY'
]]
],
fieldsMeta: {

View File

@@ -53,7 +53,9 @@ export default {
'GPT_BASE_URL',
'GPT_API_KEY',
'GPT_PROXY',
'GPT_MODEL'
'GPT_MODEL',
'CUSTOM_GPT_MODEL',
'CUSTOM_DEEPSEEK_MODEL'
],
fieldsMeta: {
CHAT_AI_TYPE: {
@@ -115,6 +117,16 @@ export default {
},
CHAT_AI_EMBED_URL: {
hidden: (formValue) => formValue.CHAT_AI_METHOD !== 'embed'
},
CUSTOM_GPT_MODEL: {
hidden: (formValue) => {
return formValue.CHAT_AI_METHOD !== 'api' || formValue.CHAT_AI_TYPE !== 'gpt' || formValue.GPT_MODEL !== 'custom'
}
},
CUSTOM_DEEPSEEK_MODEL: {
hidden: (formValue) => {
return formValue.CHAT_AI_METHOD !== 'api' || formValue.CHAT_AI_TYPE !== 'deep-seek' || formValue.DEEPSEEK_MODEL !== 'custom'
}
}
},
submitMethod() {

View File

@@ -6,6 +6,7 @@
:create-drawer="createDrawer"
:detail-drawer="detailDrawer"
:header-actions="iTicketAction"
:quick-filters="quickFilters"
:table-config="ticketTableConfig"
/>
</template>
@@ -41,11 +42,77 @@ export default {
loading: true,
getDrawerTitle: () => ' ',
createDrawer: () => import('@/views/tickets/RequestAssetPerm/CreateUpdate'),
quickFilters: [
{
label: this.$t('Type'),
options: [
{
label: this.$t('ApplyAsset'),
filter: {
type: 'apply_asset'
}
},
{
label: this.$t('LoginConfirm'),
filter: {
type: 'login_confirm'
}
},
{
label: this.$t('CommandConfirm'),
filter: {
type: 'command_confirm'
}
},
{
label: this.$t('LoginAssetConfirm'),
filter: {
type: 'login_asset_confirm'
}
}
]
},
{
label: this.$t('State'),
options: [
{
label: this.$t('All'),
filter: {
state: 'all'
}
},
{
label: this.$t('Open'),
filter: {
state: 'pending'
}
},
{
label: this.$t('Cancel'),
filter: {
state: 'closed'
}
},
{
label: this.$t('Approved'),
filter: {
state: 'approved'
}
},
{
label: this.$t('Rejected'),
filter: {
state: 'rejected'
}
}
]
}
],
detailDrawer: null,
ticketTableConfig: {
url: this.url,
extraQuery: this.extraQuery,
columnsExclude: ['process_map', 'rel_snapshot'],
columnsExclude: ['process_map', 'rel_snapshot', 'status'],
columnsShow: {
min: ['title', 'serial_num', 'type', 'state', 'date_created'],
default: ['title', 'serial_num', 'type', 'state', 'date_created']
@@ -94,25 +161,8 @@ export default {
return row.type.label
}
},
status: {
align: 'center',
sortable: 'custom',
formatter: TagChoicesFormatter,
formatterArgs: {
getTagLabel({ row }) {
return row.status.label
},
getTagType({ row }) {
if (row.status.value === 'open') {
return 'primary'
} else {
return 'danger'
}
}
}
},
state: {
label: this.$t('Action'),
label: this.$t('State'),
align: 'center',
sortable: 'custom',
formatter: TagChoicesFormatter,
@@ -142,7 +192,7 @@ export default {
}
},
defaultTicketActions: {
hasExport: false,
hasImport: false,
hasMoreActions: false,
hasLeftActions: true,
canCreate: this.$hasPerm('tickets.view_ticket'),
@@ -189,10 +239,6 @@ export default {
value: 'relevant_asset',
label: this.$t('RelevantAsset')
},
{
value: 'relevant_system_user',
label: this.$t('RelevantCommand')
},
{
value: 'relevant_command',
label: this.$t('ApplyRunCommand')

View File

@@ -217,12 +217,14 @@ export default {
const accounts = this.requestForm.accounts
if (this.object.approval_step.value === this.object.process_map.length) {
if (assets.length === 0 && nodes.length === 0) {
return this.$message.error(this.$tc('SelectAtLeastOneAssetOrNodeErrMsg'))
this.$message.error(this.$tc('SelectAtLeastOneAssetOrNodeErrMsg'))
return false
} else if (accounts.length === 0) {
return this.$message.error(this.$tc('RequiredSystemUserErrMsg'))
this.$message.error(this.$tc('RequiredSystemUserErrMsg'))
return false
}
}
this.$axios.patch(`/api/v1/tickets/apply-asset-tickets/${this.object.id}/approve/`, {
return this.$axios.patch(`/api/v1/tickets/apply-asset-tickets/${this.object.id}/approve/`, {
apply_nodes: nodes || [],
apply_assets: assets || [],
apply_accounts: accounts || [],
@@ -239,11 +241,11 @@ export default {
},
handleClose() {
const url = `/api/v1/tickets/apply-asset-tickets/${this.object.id}/close/`
this.$axios.put(url).then(res => this.reloadPage()).catch(err => this.$message.error(err))
return this.$axios.put(url).then(res => this.reloadPage()).catch(err => this.$message.error(err))
},
handleReject() {
const url = `/api/v1/tickets/apply-asset-tickets/${this.object.id}/reject/`
this.$axios.put(url).then(res => this.reloadPage()).catch(err => this.$message.error(err))
return this.$axios.put(url).then(res => this.reloadPage()).catch(err => this.$message.error(err))
}
}
}

View File

@@ -231,7 +231,16 @@ export default {
}
if (handler) {
handler()
const result = handler()
if (result === false) {
this.isDisabled = false
return
}
if (result && typeof result.finally === 'function') {
result.finally(() => {
this.isDisabled = false
})
}
} else {
this.$message.error('No handler for action')
}

View File

@@ -264,7 +264,7 @@ export default {
let mfa_level = null
// SECURITY_MFA_AUTH 0 不开启 1 全局开启 2 管理员开启
const securityMFAAuth = store.getters.publicSettings['SECURITY_MFA_AUTH']
const adminUserIsNeed = (user?.is_superuser || user?.is_org_admin) && this.$route.meta.action === 'update' &&
const adminUserIsNeed = (user?.is_superuser || user?.is_org_admin) && this.$route.params.action === 'update' &&
securityMFAAuth === MFASystemSetting.onlyAdminUsers
if (securityMFAAuth === MFASystemSetting.allUsers) {
options = [{ 'value': MFALevel.allUsers, 'label': this.$t('MFAAllUsers') }]

View File

@@ -20,6 +20,8 @@ import { QuickActions } from '@/components'
import RelationCard from '@/components/Cards/RelationCard'
import AutoDetailCard from '@/components/Cards/DetailCard/auto'
import TwoCol from '@/layout/components/Page/TwoColPage.vue'
import store from '@/store'
import { MFASystemSetting } from '@/views/users/const'
export default {
name: 'UserInfo',
@@ -226,7 +228,22 @@ export default {
return <div>{doms}</div>
}
},
'wecom_id', 'dingtalk_id', 'feishu_id', 'mfa_level',
'wecom_id', 'dingtalk_id', 'feishu_id',
{
key: this.$t('MFA'),
formatter: (item, val) => {
const user = vm.object
const securityMFAAuth = store.getters.publicSettings['SECURITY_MFA_AUTH']
const adminUserIsNeed = (user?.is_superuser || user?.is_org_admin) && securityMFAAuth === MFASystemSetting.onlyAdminUsers
if (securityMFAAuth === MFASystemSetting.allUsers) {
return this.$t('MFAAllUsers')
}
if (securityMFAAuth === MFASystemSetting.onlyAdminUsers && adminUserIsNeed) {
return this.$t('MFAOnlyAdminUsers')
}
return user?.mfa_level.label
}
},
'source', 'labels',
'created_by', 'date_joined', 'date_expired',
'date_password_last_updated', 'last_login', 'comment'