Merge branch 'fix_rbac' of github.com:jumpserver/lina into fix_rbac

This commit is contained in:
ibuler
2022-02-23 17:06:16 +08:00
36 changed files with 114 additions and 449 deletions

View File

@@ -1386,6 +1386,7 @@
"Reason": "原因"
},
"Cloud": {
"CloudImport": "云导入",
"ServerAccountKey": "服务账号密钥",
"IPNetworkSegment": "IP网段",
"Aliyun": "阿里云",
@@ -1464,7 +1465,7 @@
"ImportLicenseTip": "请导入许可证",
"InterfaceSettings": "界面设置",
"License": "许可证",
"SystemMonitor": "系统监控",
"ComponentMonitor": "组件监控",
"ServiceRatio": "组件负载统计",
"LoadStatus":"组件状态",
"NormalLoad":"正常",

View File

@@ -1335,6 +1335,7 @@
"Reason": "Reason"
},
"Cloud": {
"CloudImport": "Cloud import",
"ServerAccountKey": "Server Account Key",
"IPNetworkSegment": "Ip Network Segment",
"Aliyun": "Ali Cloud",
@@ -1411,7 +1412,7 @@
"InterfaceSettings": "Interface Setting",
"License": "License",
"LicenseDetail": "License detail",
"SystemMonitor": "System Monitor",
"ComponentMonitor": "System Monitor",
"ServiceRatio": "Service ratio",
"LoadStatus":"Status",
"NormalLoad":"Normal",

View File

@@ -1,5 +1,6 @@
import i18n from '@/i18n/i18n'
import empty from '@/layout/empty'
import XPackRoutes from './xpack'
export default [
{
@@ -277,5 +278,6 @@ export default [
meta: { title: i18n.t('route.LabelUpdate') }
}
]
}
},
...XPackRoutes
]

View File

@@ -9,7 +9,6 @@ import PermsRoute from './perms'
import OpsRoutes from './ops'
import AclRoutes from './acls'
import AccountRoutes from './accounts'
import XPackRoutes from './xpack'
export default {
path: '/console',
@@ -106,14 +105,6 @@ export default {
icon: 'coffee'
},
children: OpsRoutes
},
{
path: '/console/xpack',
component: empty,
redirect: '/applications/remote-apps/',
name: 'Xpack',
meta: { title: 'X-Pack', icon: 'sitemap', licenseRequired: true },
children: XPackRoutes
}
]
}

View File

@@ -7,16 +7,18 @@ export default [
component: empty,
redirect: '',
meta: {
title: i18n.t('xpack.Cloud.Cloud'),
title: i18n.t('xpack.Cloud.CloudImport'),
permissions: ['xpack.view_account']
},
children: [
{
path: '',
component: () => import('@/views/xpack/Cloud/index.vue'),
component: () => import('@/views/assets/Cloud'),
name: 'CloudCenter',
hidden: true,
meta: {
title: i18n.t('xpack.Cloud.CloudCenter'),
title: i18n.t('xpack.Cloud.CloudImport'),
activeMenu: '/console/assets/assets',
permissions: ['xpack.view_account']
}
},
@@ -32,7 +34,7 @@ export default [
children: [
{
path: '',
component: () => import('@/views/xpack/Cloud/Account/AccountList'),
component: () => import('@/views/assets/Cloud/Account/AccountList'),
name: 'AccountList',
hidden: true,
meta: {
@@ -42,7 +44,7 @@ export default [
},
{
path: 'create',
component: () => import('@/views/xpack/Cloud/Account/AccountCreateUpdate'),
component: () => import('@/views/assets/Cloud/Account/AccountCreateUpdate'),
name: 'AccountCreate',
hidden: true,
meta: {
@@ -53,7 +55,7 @@ export default [
},
{
path: ':id/update',
component: () => import('@/views/xpack/Cloud/Account/AccountCreateUpdate'),
component: () => import('@/views/assets/Cloud/Account/AccountCreateUpdate'),
name: 'AccountUpdate',
hidden: true,
meta: {
@@ -64,7 +66,7 @@ export default [
},
{
path: ':id/',
component: () => import('@/views/xpack/Cloud/Account/AccountDetail/index'),
component: () => import('@/views/assets/Cloud/Account/AccountDetail/index'),
name: 'AccountDetail',
hidden: true,
meta: {
@@ -85,7 +87,7 @@ export default [
children: [
{
path: '',
component: () => import('@/views/xpack/Cloud/SyncInstanceTask/SyncInstanceTaskList'),
component: () => import('@/views/assets/Cloud/SyncInstanceTask/SyncInstanceTaskList'),
name: 'SyncInstanceTaskList',
hidden: true,
meta: {
@@ -95,7 +97,7 @@ export default [
},
{
path: 'create',
component: () => import('@/views/xpack/Cloud/SyncInstanceTask/SyncInstanceTaskCreateUpdate'),
component: () => import('@/views/assets/Cloud/SyncInstanceTask/SyncInstanceTaskCreateUpdate'),
name: 'SyncInstanceTaskCreate',
hidden: true,
meta: {
@@ -105,7 +107,7 @@ export default [
},
{
path: ':id/update',
component: () => import('@/views/xpack/Cloud/SyncInstanceTask/SyncInstanceTaskCreateUpdate'),
component: () => import('@/views/assets/Cloud/SyncInstanceTask/SyncInstanceTaskCreateUpdate'),
name: 'SyncInstanceTaskUpdate',
hidden: true,
meta: {
@@ -115,7 +117,7 @@ export default [
},
{
path: ':id',
component: () => import('@/views/xpack/Cloud/SyncInstanceTask/SyncInstanceTaskDetail/index'),
component: () => import('@/views/assets/Cloud/SyncInstanceTask/SyncInstanceTaskDetail/index'),
name: 'SyncInstanceTaskDetail',
hidden: true,
meta: {
@@ -125,69 +127,5 @@ export default [
]
}
]
},
{
path: 'interface-setting',
component: () => import('@/views/xpack/InterfaceSettings.vue'),
name: 'InterfaceSetting',
meta: {
title: i18n.t('xpack.InterfaceSettings'),
permissions: ['xpack.view_interface']
}
},
{
path: 'orgs',
component: empty,
redirect: '',
meta: { permissions: ['orgs.view_organization'] },
children: [
{
path: '',
component: () => import('@/views/xpack/Org/OrganizationList'),
name: 'OrganizationList',
meta: {
title: i18n.t('xpack.Organization.OrganizationList'),
permissions: ['orgs.view_organization']
}
},
{
path: 'create',
component: () => import('@/views/xpack/Org/OrganizationCreateUpdate'),
name: 'OrganizationCreate',
hidden: true,
meta: {
title: i18n.t('xpack.Organization.OrganizationCreate'),
action: 'create',
permissions: ['orgs.add_organization']
}
},
{
path: ':id/update',
component: () => import('@/views/xpack/Org/OrganizationCreateUpdate'),
name: 'OrganizationUpdate',
hidden: true,
meta: {
title: i18n.t('xpack.Organization.OrganizationUpdate'),
action: 'update',
permissions: ['orgs.change_organization']
}
},
{
path: ':id',
component: () => import('@/views/xpack/Org/OrganizationDetail/index'),
name: 'OrganizationDetail',
hidden: true,
meta: {
title: i18n.t('xpack.Organization.OrganizationDetail'),
permissions: ['orgs.view_organization']
}
}
]
},
{
path: 'system-monitor',
component: () => import('@/views/xpack/SystemMonitor/index.vue'),
name: 'SystemMonitor',
meta: { title: i18n.t('xpack.SystemMonitor'), permissions: ['terminal.view_terminal'] }
}
]

View File

@@ -202,6 +202,66 @@ export default {
permissions: ['settings.view_setting']
}
},
{
path: '/settings/interface',
name: 'Interface',
component: () => import('@/views/settings/Interface'),
meta: {
title: i18n.t('xpack.InterfaceSettings'),
icon: 'laptop',
permissions: ['xpack.view_interface']
}
},
{
path: '/settings/orgs',
component: empty,
redirect: '',
meta: { permissions: ['orgs.view_organization'] },
children: [
{
path: '',
component: () => import('@/views/settings/Org/OrganizationList'),
name: 'OrganizationList',
meta: {
title: i18n.t('xpack.Organization.OrganizationList'),
icon: 'sitemap',
permissions: ['orgs.view_organization']
}
},
{
path: 'create',
component: () => import('@/views/settings/Org/OrganizationCreateUpdate'),
name: 'OrganizationCreate',
hidden: true,
meta: {
title: i18n.t('xpack.Organization.OrganizationCreate'),
action: 'create',
permissions: ['orgs.add_organization']
}
},
{
path: ':id/update',
component: () => import('@/views/settings/Org/OrganizationCreateUpdate'),
name: 'OrganizationUpdate',
hidden: true,
meta: {
title: i18n.t('xpack.Organization.OrganizationUpdate'),
action: 'update',
permissions: ['orgs.change_organization']
}
},
{
path: ':id',
component: () => import('@/views/settings/Org/OrganizationDetail/index'),
name: 'OrganizationDetail',
hidden: true,
meta: {
title: i18n.t('xpack.Organization.OrganizationDetail'),
permissions: ['orgs.view_organization']
}
}
]
},
{
path: '/settings/other',
name: 'Other',

View File

@@ -171,6 +171,13 @@ export default {
{ label: this.$t('assets.Label'), value: 'label' }
]
},
extraActions: [
{
name: this.$t('xpack.Cloud.CloudImport'),
title: this.$t('xpack.Cloud.CloudImport'),
callback: () => this.$router.push({ name: 'CloudCenter' })
}
],
extraMoreActions: [
{
name: 'DeactiveSelected',

View File

@@ -1,17 +1,20 @@
<template>
<IBox>
<GenericCreateUpdateForm v-bind="$data" />
</IBox>
<Page>
<IBox>
<GenericCreateUpdateForm v-bind="$data" />
</IBox>
</Page>
</template>
<script>
import { IBox, CronTab } from '@/components'
import GenericCreateUpdateForm from '@/layout/components/GenericCreateUpdateForm'
import { Page, GenericCreateUpdateForm } from '@/layout/components'
export default {
name: 'Senior',
components: {
IBox,
Page,
GenericCreateUpdateForm
},
data() {

View File

@@ -19,7 +19,7 @@
import { Page } from '@/layout/components'
import { IBox, UploadField } from '@/components'
import GenericCreateUpdateForm from '@/layout/components/GenericCreateUpdateForm'
import { getInterfaceInfo, postInterface, restoreInterface } from '@/views/xpack/api'
import { getInterfaceInfo, postInterface, restoreInterface } from '@/api/interface'
export default {
name: 'InterfaceSettings',

View File

@@ -1,17 +1,20 @@
<template>
<IBox>
<GenericCreateUpdateForm v-bind="$data" />
</IBox>
<Page>
<IBox>
<GenericCreateUpdateForm v-bind="$data" />
</IBox>
</Page>
</template>
<script>
import { IBox, CronTab } from '@/components'
import GenericCreateUpdateForm from '@/layout/components/GenericCreateUpdateForm'
import { Page, GenericCreateUpdateForm } from '@/layout/components'
export default {
name: 'Senior',
components: {
IBox,
Page,
GenericCreateUpdateForm
},
data() {

View File

@@ -1,21 +1,17 @@
<template>
<Page>
<el-row v-if="loaded" :gutter="40">
<el-col v-for="metric of metricsData" :key="metric.type" :lg="12" :md="24">
<MonitorCard :type="metric.type" :component-metric="metric" class="monitorCard" />
</el-col>
</el-row>
</Page>
<el-row v-if="loaded" :gutter="40">
<el-col v-for="metric of metricsData" :key="metric.type" :lg="12" :md="24">
<MonitorCard :type="metric.type" :component-metric="metric" class="monitorCard" />
</el-col>
</el-row>
</template>
<script>
import Page from '@/layout/components/Page/index'
import MonitorCard from '@/views/xpack/SystemMonitor/component/MonitorCard'
import MonitorCard from './component/MonitorCard'
export default {
name: 'SystemMonitor',
components: {
Page,
MonitorCard
},
data() {

View File

@@ -12,11 +12,13 @@ import Basic from './Base'
import TerminalList from './TerminalList'
import ReplayStorage from './Storage/ReplayStorage'
import CommandStorage from './Storage/CommandStorage'
import Monitor from './Monitor'
export default {
components: {
TabPage,
Basic,
Monitor,
TerminalList,
ReplayStorage,
CommandStorage
@@ -41,6 +43,10 @@ export default {
{
title: this.$t('sessions.commandStorage'),
name: 'CommandStorage'
},
{
title: this.$t('xpack.ComponentMonitor'),
name: 'Monitor'
}
]
}

View File

@@ -1,163 +0,0 @@
<template>
<div>
<GenericDetailPage :object.sync="licenseData" v-bind="config">
<div>
<el-alert v-if="!hasValidLicense" type="success">
{{ this.$t('xpack.ImportLicenseTip') }}
</el-alert>
<el-row :gutter="20">
<el-col :span="14">
<DetailCard :title="cardTitle" :items="detailItems" />
</el-col>
<el-col :span="10">
<QuickActions type="primary" :actions="quickActions" />
</el-col>
</el-row>
</div>
</GenericDetailPage>
<el-dialog :visible.sync="dialogLicenseImport" center>
<div slot="title">
<h4>{{ this.$t('xpack.ImportLicense') }}</h4>
</div>
{{ this.$t('xpack.LicenseFile') }}
<br>
<input type="file" @change="fileChange">
<div slot="footer">
<el-button @click="dialogLicenseImport = false">{{ $t('common.Cancel') }}</el-button>
<el-button type="primary" @click="importLicense">{{ $t('common.Confirm') }}</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { GenericDetailPage } from '@/layout/components'
import { QuickActions } from '@/components'
import DetailCard from '@/components/DetailCard/index'
import { importLicense } from '@/views/xpack/api'
import { mapGetters } from 'vuex'
export default {
name: 'License',
components: {
GenericDetailPage,
DetailCard,
QuickActions
},
data() {
return {
dialogLicenseImport: false,
licenseData: {},
licenseFile: {},
config: {
activeMenu: 'detail',
submenu: [
{
title: this.$t('xpack.LicenseDetail'),
name: 'detail'
}
],
hasRightSide: false,
title: this.$t('xpack.LicenseDetail'),
actions: {
detailApiUrl: '/api/v1/xpack/license/detail'
}
},
quickActions: [
{
title: this.$t('xpack.ImportLicense'),
attrs: {
type: 'primary',
label: this.$t('xpack.Import')
},
callbacks: {
click: this.importAction
}
},
{
title: this.$t('xpack.technologyConsult'),
attrs: {
type: 'primary',
label: this.$t('xpack.consult')
},
callbacks: {
click: this.consultAction
}
}
]
}
},
computed: {
...mapGetters([
'publicSettings', 'hasValidLicense'
]),
cardTitle() {
return ''
},
detailItems() {
if (!this.publicSettings.XPACK_LICENSE_IS_VALID) {
return [
{
key: this.$t('xpack.License'),
value: this.$t('xpack.NoLicense')
}
]
}
return [
{
key: this.$t('xpack.SubscriptionID'),
value: this.licenseData.subscription_id
},
{
key: this.$t('xpack.Corporation'),
value: this.licenseData.corporation
},
{
key: this.$t('xpack.Expired'),
value: this.licenseData.expired
},
{
key: this.$t('xpack.AssetCount'),
value: this.licenseData.asset_count !== null ? this.licenseData.asset_count + '' : ''
},
{
key: this.$t('xpack.Edition'),
value: this.licenseData.edition
}
]
}
},
methods: {
importAction: function() {
this.dialogLicenseImport = true
},
consultAction: function() {
const url = 'http://www.jumpserver.org/support/'
window.open(url)
},
importLicense() {
if (this.licenseFile['file'] === undefined) {
return
}
const formData = new FormData()
formData.append('file', this.licenseFile['file'])
importLicense(formData).then(res => {
if (res.status) {
this.$message.success(res.msg)
setTimeout(() => location.reload(), 500)
} else {
this.$message.error(res.msg)
}
})
},
fileChange(e) {
this.licenseFile['file'] = e.target.files[0]
}
}
}
</script>
<style scoped>
</style>

View File

@@ -1 +0,0 @@
# js-xpack-web

View File

@@ -1,179 +0,0 @@
<template>
<div>
<GenericTreeListPage ref="TreeTablePage" :tree-setting="treeSetting">
<template #table>
<AssetUserTable ref="table" v-bind="assetUserConfig" />
</template>
</GenericTreeListPage>
<Dialog width="50" :title="this.$t('common.MFAConfirm')" :visible.sync="showMFADialog" :show-confirm="false" :show-cancel="false" :destroy-on-close="true">
<el-row :gutter="20">
<el-col :span="4">
<div style="line-height: 34px;text-align: center">MFA</div>
</el-col>
<el-col :span="14">
<el-input v-model="MFAInput" />
<span class="help-tips help-block">{{ $t('common.MFARequireForSecurity') }}</span>
</el-col>
<el-col :span="4">
<el-button size="mini" type="primary" style="line-height:20px " @click="MFAConfirm">{{ this.$t('common.Confirm') }}</el-button>
</el-col>
</el-row>
</Dialog>
</div>
</template>
<script>
import GenericTreeListPage from '@/layout/components/GenericTreeListPage'
import { AssetUserTable } from '@/components'
import Dialog from '@/components/Dialog'
import { setUrlParam } from '@/utils/common'
import { mapGetters } from 'vuex'
export default {
name: 'VaultList',
components: {
GenericTreeListPage,
AssetUserTable,
Dialog
},
data() {
const vm = this
return {
errorMsg: '',
exportOption: '1',
meta: {},
MfaExpired: 0,
showMFADialog: false,
MFAInput: '',
selectedRows: '',
assetUserConfig: {
hasLeftActions: true,
hasCreate: true,
hasClone: false,
url: '/api/v1/assets/accounts/'
},
treeSetting: {
showMenu: false,
showRefresh: false,
showAssets: true,
url: '/api/v1/assets/accounts/',
treeUrl: '/api/v1/assets/nodes/children/tree/?assets=1',
callback: {
onSelected: function(event, treeNode) {
let url = vm.assetUserConfig.url
if (treeNode.meta.type === 'node') {
const nodeId = treeNode.meta.data.id
url = setUrlParam(url, 'asset_id', '')
url = setUrlParam(url, 'node_id', nodeId)
} else if (treeNode.meta.type === 'asset') {
const assetId = treeNode.meta.data.id
url = setUrlParam(url, 'node_id', '')
url = setUrlParam(url, 'asset_id', assetId)
}
setTimeout(() => {
vm.assetUserConfig.url = url
}, 100)
}
}
}
}
},
computed: {
...mapGetters([
'MFA_TTl',
'MFAVerifyAt',
'publicSettings'
]),
needMFAVerify() {
if (!this.publicSettings.SECURITY_VIEW_AUTH_NEED_MFA) {
return false
}
const ttl = this.publicSettings.SECURITY_MFA_VERIFY_TTL
const now = new Date()
return !(this.MFAVerifyAt && (now - this.MFAVerifyAt) < ttl * 1000)
},
hasSelected() {
return this.selectedRows.length > 0
},
...mapGetters([
'MFAVerifyAt',
'MFA_TTl'
])
},
methods: {
performUpdate(item) {
this.$axios.put(
`/api/v1/assets/accounts/`,
item.file,
{ headers: { 'Content-Type': 'text/csv' }, disableFlashErrorMsg: true }
).then((data) => {
const msg = this.$t('common.imExport.updateSuccessMsg', { count: data.length })
this.onSuccess(msg)
}).catch(error => {
this.catchError(error)
})
},
performCreate(item) {
this.$axios.post(
`/api/v1/assets/accounts/`,
item.file,
{ headers: { 'Content-Type': 'text/csv' }, disableFlashErrorMsg: true }
).then((data) => {
const msg = this.$t('common.imExport.createSuccessMsg', { count: data.length })
this.onSuccess(msg)
}).catch(error => {
this.$message.error(this.$t('common.updateErrorMsg') + ' ' + error)
this.catchError(error)
})
},
catchError(error) {
this.$refs.upload.clearFiles()
if (error.response && error.response.status === 400) {
const errorData = error.response.data
const totalErrorMsg = []
errorData.forEach((value, index) => {
if (typeof value === 'string') {
totalErrorMsg.push(`line ${index}. ${value}`)
} else {
const errorMsg = [`line ${index}. `]
for (const [k, v] of Object.entries(value)) {
if (v) {
errorMsg.push(`${k}: ${v}`)
}
}
if (errorMsg.length > 1) {
totalErrorMsg.push(errorMsg.join(' '))
}
}
})
this.errorMsg = totalErrorMsg
}
},
MFAConfirm() {
if (this.MFAInput.length !== 6) {
return this.$message.error(this.$t('common.MFAErrorMsg'))
}
this.$axios.post(
`/api/v1/authentication/otp/verify/`, {
code: this.MFAInput
}
).then(
res => {
this.$store.dispatch('users/setMFAVerify')
if (this.dialogStatus === 'import') {
this.showMFADialog = false
this.showImportDialog = true
} else {
this.showMFADialog = false
this.showExportDialog = true
}
}
)
}
}
}
</script>
<style scoped>
</style>