Compare commits

...

19 Commits

Author SHA1 Message Date
ibuler
56f6c17275 perf: 优先选择上个 org 切换 2024-05-16 13:53:47 +08:00
ibuler
e1bde89b29 fix: 修复切换到全局组织回不来的问题
perf: 修改组织切换
2024-05-15 19:14:58 +08:00
feng626
e9da168c9f Merge pull request #3930 from jumpserver/pr@dev@mfa
perf: User personal settings mfa new window opens
2024-05-15 16:41:15 +08:00
feng
c19ef24ec9 perf: User personal settings mfa new window opens 2024-05-15 16:40:17 +08:00
wangruidong
fb7c4a8b2a fix: 竖屏审批工单时,动作显示不出来 2024-05-15 15:33:03 +08:00
wangruidong
428ba49f9c perf: 根据type生成导出文件名 2024-05-11 10:02:53 +08:00
ibuler
7602d6e270 fix: 修复自动切换到 root org 回不来的问题 2024-05-10 19:07:46 +08:00
wangruidong
7b62ce2d33 fix: 批量传输下载结果文件名undefined 2024-05-07 14:42:06 +08:00
jiangweidong
6ed40c45b0 perf: 云同步支持同步完成后自动删除云端已经释放的资产 2024-05-07 14:40:29 +08:00
wangruidong
31238e0398 fix: 命令存储创建es后跳转路由不对 2024-04-28 10:24:44 +08:00
wangruidong
676ac2bbf6 perf: 创建、更新用户时MFA选项根据系统设置选项进行动态渲染 2024-04-26 11:35:12 +08:00
Bai
d5415b84c9 feat: Support asset tree node drag to another one 2024-04-24 18:06:12 +08:00
Bai
5e91917ba4 perf: 优化 Web 资产详情时根据 autofill 类型返回对应的 spec_info 信息 2024-04-23 13:08:59 +08:00
zhaojisen
c4361b4c17 perf: 修复默认值相关内容,优化按钮禁用条件 2024-04-23 10:17:43 +08:00
Bai
70b5ec3683 fix: Fixed User first login long wait.(SYSTEM ORG) 2024-04-22 19:17:11 +08:00
Bai
c93a061852 fix: Linux Platform id is not 1, create gateway error 2024-04-22 15:54:56 +08:00
feng626
4351d20a1e Merge pull request #3867 from jumpserver/pr@dev@asset
perf: 新建/更新资产时,新tab页面打开
2024-04-22 14:17:29 +08:00
feng
ce23d53e3c perf: 新建/更新资产时,新tab页面打开 2024-04-22 14:16:59 +08:00
wangruidong
32fc16126f perf: dashboard typo 2024-04-22 13:24:52 +08:00
21 changed files with 130 additions and 75 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -83,7 +83,7 @@ export default {
callback: () => {
this.xterm.selectAll()
const text = this.xterm.getSelection()
const filename = `shortcut_cmd_${this.$route.query?.taskId}.log`
const filename = `${this.$route.query?.type}_${this.$route.query?.taskId}.log`
downloadText(text, filename)
}
}

View File

@@ -994,14 +994,14 @@
"RealTimeData": "Real-time data",
"UserAssetActivity": "User/asset activity status",
"UserData": "User data",
"LoginUserToday": "Login account today",
"LoginUserToday": "Login accounts today",
"AssetData": "Asset data",
"LoginAssetToday": "Active asset today",
"LoginAssetToday": "Active assets today",
"WeekAdd": "New this week",
"ProportionOfAssetTypes": "Proportion of asset types",
"Proportion": "Proportion",
"LoginUserRanking": "Session user ranking",
"ActiveAssetRanking": "Session asset Ranking",
"ActiveAssetRanking": "Session asset ranking",
"AssetName": "Asset name",
"NumberOfVisits": "Number of visits",
"ranking": "Ranking",
@@ -2135,7 +2135,8 @@
"passwordWillExpiredSuffixMsg": " days.Please change your password as soon as possible.",
"dateLastLogin": "Date last login",
"AddAllMembersWarningMsg": "Are you sure you want to add all members?",
"disallowSelfUpdateFields": "Not allowed to modify the current fields oneself"
"disallowSelfUpdateFields": "Not allowed to modify the current fields oneself",
"GlobalDisableMfaMsg": "Global enforcement has been enabled"
},
"notifications": {
"MessageType": "Message Type",
@@ -2271,6 +2272,8 @@
"HostnameStrategy": "Used to produce the asset hostname. For example, 1. Instance name (instanceDemo)2. Instance name and Partial IP (instanceDemo-250.1)",
"IsAlwaysUpdate": "Keep assets up to date",
"FullySynchronous": "Assets fully synchronized",
"ReleaseAssets": "Release assets",
"ReleaseAssetsHelpTips": "Whether to automatically delete assets synchronized through this task and released on the cloud at the end of the task",
"AccountCreate": "Create account",
"AccountList": "Account list",
"AccountUpdate": "Update account",
@@ -2434,4 +2437,4 @@
"BindResource": "Bind resource",
"SelectResource": "Select resource"
}
}
}

View File

@@ -2124,7 +2124,8 @@
"passwordWillExpiredPrefixMsg": "パスワードはまもなく",
"passwordWillExpiredSuffixMsg": "期限が切れた後、できるだけ早くパスワードを変更してください。",
"AddAllMembersWarningMsg": "すべてのメンバーを追加してもよろしいですか?",
"disallowSelfUpdateFields": "現在のフィールドを自分で変更することは許可されていません"
"disallowSelfUpdateFields": "現在のフィールドを自分で変更することは許可されていません",
"GlobalDisableMfaMsg": "グローバルでの強制が有効になっています"
},
"notifications": {
"MessageType": "メッセージタイプ",
@@ -2267,6 +2268,8 @@
"HostnameStrategy": "資産を生成するためにホスト名。例: 1. インスタンス名 (instanceDemo) 2.インスタンス名と一部IP (下位2桁) (instanceDemo-250.1)",
"IsAlwaysUpdate": "資産は常に最新です",
"FullySynchronous": "資産完全にシンクロします",
"ReleaseAssets": "資産の同期解放",
"ReleaseAssetsHelpTips": "タスクの終了時に、このタスクを介して同期され、クラウド上で解放された資産を自動的に削除するかどうか",
"AccountCreate": "アカウントの作成",
"AccountList": "アカウントリスト",
"AccountUpdate": "アカウントの更新",
@@ -2425,4 +2428,4 @@
"BindResource": "リソースを関連付ける",
"SelectResource": "リソースを選択"
}
}
}

View File

@@ -2115,7 +2115,8 @@
"passwordExpired": "密码过期了",
"passwordWillExpiredPrefixMsg": "密码即将在 ",
"passwordWillExpiredSuffixMsg": "天 后过期,请尽快修改您的密码。",
"disallowSelfUpdateFields": "不允许自己修改当前字段"
"disallowSelfUpdateFields": "不允许自己修改当前字段",
"GlobalDisableMfaMsg": "全局已强制开启"
},
"notifications": {
"MessageType": "消息类型",
@@ -2186,6 +2187,8 @@
"HostnameStrategy": "用于生成资产主机名。例如1. 实例名称 (instanceDemo)2. 实例名称和部分IP(后两位) (instanceDemo-250.1)",
"IsAlwaysUpdate": "资产保持最新",
"FullySynchronous": "资产完全同步",
"ReleaseAssets": "同步释放资产",
"ReleaseAssetsHelpTips": "是否在任务结束时,自动删除通过此任务同步下来且已经在云上释放的资产",
"AccountCreate": "创建账户",
"AccountList": "云账号",
"AccountUpdate": "更新账户",

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ 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']
function reject(msg) {
return new Promise((resolve, reject) => reject(msg))
@@ -41,7 +42,11 @@ async function getPublicSetting({ to, from, next }, isOpen) {
}
async function refreshCurrentOrg() {
orgs.getCurrentOrg().then(org => {
return orgs.getCurrentOrg().then(org => {
// Root 就不刷新本地的了, 会影响 autoEnter
if (autoEnterOrgs.indexOf(org.id) !== -1) {
return
}
store.dispatch('users/setCurrentOrg', org)
})
}
@@ -60,9 +65,16 @@ async function changeCurrentOrgIfNeed({ to, from, next }) {
Vue.$log.error('Current org is null or not a object: ', currentOrg)
await orgUtil.change2PropOrg({ to, from, next })
}
if (currentOrg.name === 'SystemSetting') {
const preOrg = store.getters.preOrg
await orgUtil.changeOrg(preOrg)
const globalOrgPath = [
'/console/perms/login-acls/', '/console/users/roles/',
'/console/perms/connect-method-acls/', '/settings/'
]
if (autoEnterOrgs.indexOf(currentOrg.id) !== -1 && currentOrg.autoEnter) {
const delta = new Date().getTime() - currentOrg.autoEnter
const notNeedChange = globalOrgPath.find(path => to.path.indexOf(path) === 0)
if (!notNeedChange && delta > 3000) {
await orgUtil.change2PropOrg({ to, from, next })
}
return
}
if (!orgUtil.hasCurrentOrgPermission()) {

View File

@@ -131,7 +131,7 @@ export default {
const { defaultConfig } = this
const { node, platform } = this.$route?.query || {}
const nodesInitial = node ? [node] : []
const platformId = platform || 1
const platformId = platform || 'Linux'
const url = `/api/v1/assets/platforms/${platformId}/`
this.platform = await this.$axios.get(url)
const initial = {

View File

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

View File

@@ -101,7 +101,7 @@ export default {
route.query.type = row.type.value
route.query.category = row.type.category
}
const createInNewPage = this.$route.query.node_id && routeAction === 'Create'
const createInNewPage = this.$route.query.node_id
if (createInNewPage) {
const { href } = vm.$router.resolve(route)
window.open(href, '_blank')

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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