Merge pull request #3665 from jumpserver/dev

v3.10.1
This commit is contained in:
Bryan
2023-12-29 11:14:54 +05:00
committed by GitHub
24 changed files with 146 additions and 54 deletions

View File

@@ -58,7 +58,7 @@ export function JobUploadFile(form) {
url: '/api/v1/ops/jobs/upload/',
method: 'post',
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 10 * 60 * 1000,
timeout: 60 * 60 * 1000,
data: form
})
}

View File

@@ -392,9 +392,18 @@ export default {
can: this.$hasPerm('accounts.delete_account'),
type: 'primary',
callback: ({ row }) => {
this.$axios.delete(`/api/v1/accounts/accounts/${row.id}/`).then(() => {
this.$message.success(this.$tc('common.deleteSuccessMsg'))
this.$refs.ListTable.reloadTable()
const msg = this.$t('accounts.AccountDeleteConfirmMsg')
this.$confirm(msg, this.$tc('common.Info'), {
type: 'warning',
confirmButtonClass: 'el-button--danger',
beforeClose: async(action, instance, done) => {
if (action !== 'confirm') return done()
this.$axios.delete(`/api/v1/accounts/accounts/${row.id}/`).then(() => {
done()
this.$refs.ListTable.reloadTable()
this.$message.success(this.$tc('common.deleteSuccessMsg'))
})
}
})
}
}

View File

@@ -42,7 +42,6 @@ const {
addMessageToActiveChat,
newChatAndAddMessageById,
removeLoadingMessageInChat,
removeLoadingAndAddMessageToChat,
updateChaMessageContentById,
addTemporaryLoadingToChat
} = useChat()
@@ -120,12 +119,11 @@ export default {
}
},
onChatMessage(data) {
if (!data.message.content && data.conversation_id) {
if (data.conversation_id) {
setLoading(true)
removeLoadingAndAddMessageToChat(data)
removeLoadingMessageInChat()
this.currentConversationId = data.conversation_id
} else {
updateChaMessageContentById(data.message.id, data.message.content)
updateChaMessageContentById(data.message.id, data)
}
if (data.message?.type === 'finish') {
setLoading(false)

View File

@@ -59,13 +59,8 @@ export function useChat() {
addChatMessageById(chat)
}
const removeLoadingAndAddMessageToChat = (chat) => {
store.commit('chat/removeLoadingMessageInChat')
store.commit('chat/addMessageToActiveChat', chat)
}
const updateChaMessageContentById = (id, content) => {
store.commit('chat/updateChaMessageContentById', { id, content })
const updateChaMessageContentById = (id, data) => {
store.commit('chat/updateChaMessageContentById', { id, data })
pageScroll('scrollRef')
}
@@ -78,7 +73,6 @@ export function useChat() {
addMessageToActiveChat,
newChatAndAddMessageById,
removeLoadingMessageInChat,
removeLoadingAndAddMessageToChat,
addChatMessageById,
addTemporaryLoadingToChat,
updateChaMessageContentById

View File

@@ -124,7 +124,8 @@
"AddAccountResult": "Add account result",
"AutoPush": "Auto Push",
"GeneralAccounts": "General Accounts",
"VirtualAccounts": "Virtual Accounts"
"VirtualAccounts": "Virtual Accounts",
"AccountDeleteConfirmMsg": "Delete account, do you want to continue?"
},
"acl": {
"CommandFilterACLHelpMsg": "You can control whether commands can be executed on assets. Based on the rules, certain commands can be allowed while others are prohibited.",
@@ -1757,6 +1758,7 @@
"LDAPServerInfo": "LDAP Server",
"LDAPUser": "LDAP User",
"ChatAI": "Chat ai",
"Example": "Example: {example}",
"InsecureCommandAlert": "Insecure command alert",
"helpText": {
"TempPassword": "For a while, there is a period of 300 seconds, failure immediately after use",
@@ -2110,7 +2112,7 @@
"passwordWillExpiredPrefixMsg": "The password will expire in ",
"passwordWillExpiredSuffixMsg": " days.Please change your password as soon as possible.",
"dateLastLogin": "Date last login",
"AddAllMembersWarningMsg": "Are you sure you want to add all members"
"AddAllMembersWarningMsg": "Are you sure you want to add all members?"
},
"notifications": {
"MessageType": "Message Type",

View File

@@ -124,7 +124,8 @@
"AddAccountResult": "账号批量添加结果",
"AutoPush": "自動プッシュ",
"GeneralAccounts": "一般アカウント",
"VirtualAccounts": "仮想アカウント"
"VirtualAccounts": "仮想アカウント",
"AccountDeleteConfirmMsg": "アカウントを削除します,続行しますか?"
},
"acl": {
"CommandFilterACLHelpMsg": "コマンドフィルタリングを使用すると、コマンドがアセット上で実行されるかどうかを制御できます。ルールに基づいて、特定のコマンドは許可され、他のコマンドは禁止されることがあります。",
@@ -1763,6 +1764,7 @@
"LDAPServerInfo": "LDAPサーバー",
"LDAPUser": "LDAPユーザー",
"ChatAI": "チャットAI",
"Example": "例: {example}",
"helpText": {
"TempPassword": "一時パスワードの有効期間は300秒で、使用後すぐに失効します",
"ApiKeyList": "Api keyを使用してリクエストヘッダに署名します。リクエストのヘッダごとに異なります。使用ドキュメントを参照してください",
@@ -2099,7 +2101,7 @@
"passwordExpired": "パスワードが期限切れです",
"passwordWillExpiredPrefixMsg": "パスワードはまもなく",
"passwordWillExpiredSuffixMsg": "期限が切れた後、できるだけ早くパスワードを変更してください。",
"AddAllMembersWarningMsg": "すべてのメンバーを追加してもよろしいですか"
"AddAllMembersWarningMsg": "すべてのメンバーを追加してもよろしいですか"
},
"notifications": {
"MessageType": "メッセージタイプ",

View File

@@ -30,6 +30,7 @@
"PleaseClickLeftAssetToViewGatheredUser": "收集用户列表,点击左侧资产进行查看",
"AutoCreate": "自动创建",
"AccountExportTips": "导出信息中包含账号密文涉及敏感信息导出的格式为一个加密的zip文件若没有设置加密密码请前往个人信息中设置文件加密密码。",
"AccountDeleteConfirmMsg": "删除账号,是否继续?",
"AccountPush": {
"WindowsPushHelpText": "windows 资产暂不支持推送密钥",
"AccountPushList": "账号推送",
@@ -1752,6 +1753,7 @@
"LDAPServerInfo": "LDAP 服务器",
"LDAPUser": "LDAP 用户",
"ChatAI": "智能问答",
"Example": "例: {example}",
"helpText": {
"TempPassword": "临时密码有效期为 300 秒,使用后立刻失效",
"ApiKeyList": "使用 Api key 签名请求头进行认证,每个请求的头部是不一样的, 相对于 Token 方式,更加安全,请查阅文档使用;<br>为降低泄露风险Secret 仅在生成时可以查看, 每个用户最多支持创建 10 个",
@@ -1965,7 +1967,7 @@
"KokoSettingUpdate": "Koko 配置设置",
"UserSetting": "偏好设置",
"AllMembers": "全部成员",
"AddAllMembersWarningMsg": "你确定要添加全部成员",
"AddAllMembersWarningMsg": "你确定要添加全部成员",
"UnbindHelpText": "本地用户为此认证来源用户,无法解绑",
"SetStatus": "设置状态",
"Set": "已设置",

View File

@@ -38,10 +38,14 @@ const mutations = {
}
},
updateChaMessageContentById(state, { id, content }) {
updateChaMessageContentById(state, { id, data }) {
const chats = state.activeChat.chats || []
const filterChat = chats.filter((chat) => chat.message.id === id)?.[0] || {}
filterChat.message.content = content
if (Object.keys(filterChat).length > 0) {
filterChat.message.content = data.message.content
} else {
chats?.push(data)
}
}
}

View File

@@ -15,6 +15,17 @@ function reject(msg) {
return new Promise((resolve, reject) => reject(msg))
}
function isRenewalExpired(renewalTime) {
const currentTimeStamp = Math.floor(new Date().getTime() / 1000)
const sessionExpireTimestamp = VueCookie.get('jms_session_expire_timestamp')
if (!sessionExpireTimestamp) {
return false
}
const timeDifferenceInSeconds = currentTimeStamp - parseInt(sessionExpireTimestamp, 10)
return timeDifferenceInSeconds > renewalTime
}
async function checkLogin({ to, from, next }) {
if (whiteList.indexOf(to.path) !== -1) {
next()
@@ -28,12 +39,16 @@ async function checkLogin({ to, from, next }) {
return reject('No session mark found in cookie')
} else if (sessionExpire === 'close') {
let startTime = new Date().getTime()
setInterval(() => {
const intervalId = setInterval(() => {
const endTime = new Date().getTime()
const delta = (endTime - startTime)
startTime = endTime
Vue.$log.debug('Set session expire: ', delta)
VueCookie.set('jms_session_expire', 'close', { expires: '2m' })
if (!isRenewalExpired(120)) {
VueCookie.set('jms_session_expire', 'close', { expires: '2m' })
} else {
clearInterval(intervalId)
}
}, 10 * 1000)
} else if (sessionExpire === 'age') {
Vue.$log.debug('Session expire on age')
@@ -156,7 +171,9 @@ export async function changeCurrentViewIfNeed({ to, from, next }) {
export async function startup({ to, from, next }) {
// if (store.getters.inited) { return true }
if (store.getters.inited) { return true }
if (store.getters.inited) {
return true
}
await store.dispatch('app/init')
// set page title

View File

@@ -146,7 +146,7 @@ export default {
},
{
name: 'BulkSyncDelete',
title: this.$t('common.BulkSyncDelete'),
title: this.$t('accounts.BulkSyncDelete'),
type: 'primary',
icon: 'fa fa-exchange',
can: ({ selectedRows }) => {

View File

@@ -1,6 +1,6 @@
<template>
<el-row :gutter="10">
<el-col :span="18">
<el-row :gutter="24">
<el-col :md="24" :sm="24">
<BaseList :asset-id="object.id" :is-page="false" />
</el-col>
</el-row>

View File

@@ -1,6 +1,6 @@
<template>
<el-row :gutter="10">
<el-col :span="18">
<el-row :gutter="24">
<el-col :md="24" :sm="24">
<BaseList :url="url" :columns-show="columnsShow" />
</el-col>
</el-row>

View File

@@ -117,6 +117,7 @@ export default {
name: 'GatewayCreate',
query: {
domain: this.object.id,
platform_type: row.type.value,
clone_from: row.id
}
}

View File

@@ -119,6 +119,20 @@ export const assetFieldsMeta = (vm) => {
clearable: true
}
},
labels: {
name: 'labels',
label: vm.$t('assets.Label'),
type: 'm2m',
el: {
multiple: true,
url: '/api/v1/labels/labels/',
ajax: {
transformOption: (item) => {
return { label: `${item.name}:${item.value}`, value: `${item.name}:${item.value}` }
}
}
}
},
is_active: {
type: 'switch'
},

View File

@@ -82,16 +82,18 @@
</el-card>
</div>
<b>{{ $tc('ops.output') }}:</b>
<span v-if="executionInfo.status" style="float: right">
<span v-if="executionInfo.status && summary" style="float: right">
<span>
<span><b>{{ $tc('common.Status') }}: </b></span>
<span
:class="{
'status_success':executionInfo.status==='success',
'status_warning':executionInfo.status==='timeout',
'status_danger':executionInfo.status==='failed'
}"
>{{ $tc('ops.' + executionInfo.status) }}</span>
v-if="executionInfo.status==='timeout'"
class="status_warning"
>{{ $tc('ops.timeout') }}</span>
<span v-else>
<span class="status_success">{{ $tc('ops.success') + ': ' + summary.success }}</span>
<span class="status_warning">{{ $tc('ops.Skip') + ': ' + summary.skip }}</span>
<span class="status_danger">{{ $tc('ops.failed') + ': ' + summary.failed }}</span>
</span>
</span>
<span>
<span><b>{{ $tc('ops.timeDelta') }}: </b></span>
@@ -210,7 +212,12 @@ export default {
ShowProgress: false,
upload_interval: null,
uploadFileList: [],
SizeLimitMb: store.getters.publicSettings['FILE_UPLOAD_SIZE_LIMIT_MB']
SizeLimitMb: store.getters.publicSettings['FILE_UPLOAD_SIZE_LIMIT_MB'],
summary: {
'success': 0,
'failed': 0,
'skip': 0
}
}
},
computed: {
@@ -273,9 +280,21 @@ export default {
}
}
},
taskStatusStat(summary) {
const { ok, failures, dark, excludes, skipped } = summary
const failedKeys = Object.keys(failures)
const darkKeys = Object.keys(dark)
const excludesKeys = Object.keys(excludes)
this.summary['success'] = ok.length
this.summary['failed'] = failedKeys.length + darkKeys.length
this.summary['skip'] = excludesKeys.length + skipped.length
},
getTaskStatus() {
getTaskDetail(this.currentTaskId).then(data => {
this.executionInfo.status = data['status']
this.taskStatusStat(data['summary'])
if (this.executionInfo.status === 'success') {
this.$message.success(this.$tc('ops.runSucceed'))
clearInterval(this.upload_interval)
@@ -334,11 +353,11 @@ export default {
const filenameList = fileList.map((file) => file.name)
const filenameCount = _.countBy(filenameList)
for (const file of fileList) {
file.is_same = filenameCount[file.name] > 1
file.isSame = filenameCount[file.name] > 1
}
},
sameFileStyle(file) {
if (file.is_same) {
if (file.isSame) {
return { backgroundColor: 'var(--color-danger)' }
}
return ''
@@ -362,7 +381,7 @@ export default {
execute() {
const { hosts, nodes } = this.getSelectedNodesAndHosts()
for (const file of this.uploadFileList) {
if (file.is_same) {
if (file.isSame) {
this.$message.error(this.$tc('ops.DuplicateFileExists'))
return
}
@@ -476,7 +495,7 @@ export default {
}
.status_success {
color: var(--color-success);
color: var(--color-primary);
}
.status_warning {

View File

@@ -80,8 +80,7 @@ export default {
timeCost: 0,
cancel: 0
},
xtermConfig: {
},
xtermConfig: {},
showHelpDialog: false,
showOpenAdhocDialog: false,
showOpenAdhocSaveDialog: false,
@@ -484,7 +483,7 @@ export default {
}
.status_success {
color: var(--color-success);
color: var(--color-primary);
}
.status_warning {

View File

@@ -90,7 +90,7 @@ export default {
form,
{
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 10 * 60 * 1000,
timeout: 60 * 60 * 1000,
disableFlashErrorMsg: true,
params: { update: true }
}

View File

@@ -39,14 +39,14 @@ export default {
title: this.$t('terminal.VirtualApp'),
name: 'VirtualApp',
hidden: () => {
return !store.getters.publicSettings['VIRTUAL_APP_ENABLED']
return !store.getters.publicSettings['VIRTUAL_APP_ENABLED'] || !this.$store.getters.hasValidLicense
}
},
{
title: this.$t('terminal.AppProvider'),
name: 'AppProvider',
hidden: () => {
return !store.getters.publicSettings['VIRTUAL_APP_ENABLED']
return !store.getters.publicSettings['VIRTUAL_APP_ENABLED'] || !this.$store.getters.hasValidLicense
}
}
]

View File

@@ -51,6 +51,22 @@ export default {
]
]
],
fieldsMeta: {
GPT_BASE_URL: {
el: {
autocomplete: 'new-password'
},
helpText: this.$t('setting.Example', { example: 'https://api.openai.com/v1' })
},
GPT_API_KEY: {
el: {
autocomplete: 'new-password'
}
},
GPT_PROXY: {
helpText: this.$t('setting.Example', { example: 'http://ip:port' })
}
},
submitMethod() {
return 'patch'
}

View File

@@ -30,7 +30,7 @@ export default {
this.$t('terminal.DatabasePort'),
[
'mysql_port', 'mariadb_port', 'postgresql_port',
'redis_port', 'oracle_port_range'
'redis_port', 'sqlserver_port', 'oracle_port_range'
]
],
[this.$t('common.Other'), ['comment']]

View File

@@ -25,7 +25,7 @@ export default {
'name', 'host', 'actions',
'http_port', 'https_port', 'ssh_port', 'rdp_port',
'mysql_port', 'mariadb_port', 'postgresql_port',
'redis_port', 'oracle_port_range'
'redis_port', 'sqlserver_port', 'oracle_port_range'
]
},
columnsMeta: {

View File

@@ -46,7 +46,7 @@ export default {
},
callbacks: Object.freeze({
click: () => {
const msg = `${this.$t('users.AddAllMembersWarningMsg')} ?`
const msg = this.$t('users.AddAllMembersWarningMsg')
this.$confirm(msg, this.$tc('common.Info'), {
type: 'warning',
confirmButtonClass: 'el-button--danger',
@@ -60,7 +60,8 @@ export default {
window.location.reload()
})
}
}).catch(() => {})
}).catch(() => {
})
}
})
}
@@ -85,6 +86,9 @@ export default {
width: 150,
objects: this.object.users,
formatter: DeleteActionFormatter,
formatterArgs: {
disabled: !this.$hasPerm('users.change_usergroup')
},
onDelete: function(col, row, cellValue, reload) {
this.$axios.delete(
'/api/v1/users/users-groups-relations/', {
@@ -133,6 +137,7 @@ export default {
},
showHasObjects: false,
hasObjectsId: this.object.users,
disabled: !this.$hasPerm('users.change_usergroup'),
performAdd: (items) => {
const relationUrl = `/api/v1/users/users-groups-relations/`
const groupId = this.object.id

View File

@@ -28,6 +28,9 @@ export default {
width: '160px',
formatter: AmountFormatter,
formatterArgs: {
getItem(item) {
return item.is_service_account ? null : item.name
},
routeQuery: {
activeTab: 'GroupUser'
}

View File

@@ -174,6 +174,13 @@ export default {
return 'post'
}
},
afterGetFormValue(obj) {
if (obj?.id) {
obj.org_roles = obj.org_roles.map(({ id }) => id)
obj.system_roles = obj.system_roles.map(({ id }) => id)
}
return obj
},
cleanFormValue(value) {
const method = this.submitMethod()
if (method === 'post' && value.password_strategy === 'email') {