mirror of
https://github.com/jumpserver/lina.git
synced 2025-07-14 23:44:16 +00:00
commit
00cf2c34b9
@ -46,6 +46,7 @@
|
||||
"lodash.set": "^4.3.2",
|
||||
"lodash.topairs": "^4.3.0",
|
||||
"lodash.values": "^4.3.0",
|
||||
"moment": "^2.29.1",
|
||||
"moment-parseformat": "^3.0.0",
|
||||
"normalize.css": "7.0.0",
|
||||
"npm": "^7.8.0",
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 3.2 KiB |
@ -8,7 +8,6 @@
|
||||
<meta http-equiv="Cache-control" content="no-cache">
|
||||
<meta http-equiv="Cache" content="no-cache">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= webpackConfig.name %></title>
|
||||
</head>
|
||||
<body>
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 7.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 16 KiB |
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div>
|
||||
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
|
||||
<ListTable ref="ListTable" :table-config="iTableConfig" :header-actions="headerActions" />
|
||||
<Dialog v-if="showMFADialog" width="50" :title="this.$t('common.MFAConfirm')" :visible.sync="showMFADialog" :show-confirm="false" :show-cancel="false" :destroy-on-close="true">
|
||||
<div v-if="MFAConfirmed">
|
||||
<el-form label-position="right" label-width="80px" :model="MFAInfo">
|
||||
@ -93,6 +93,10 @@ export default {
|
||||
hasClone: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
tableConfig: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@ -114,36 +118,31 @@ export default {
|
||||
password: '',
|
||||
private_key: ''
|
||||
},
|
||||
tableConfig: {
|
||||
defaultTableConfig: {
|
||||
url: this.url,
|
||||
columns: [
|
||||
{
|
||||
prop: 'hostname',
|
||||
columns: ['hostname', 'ip', 'username', 'version', 'date_created', 'actions'],
|
||||
columnsMeta: {
|
||||
'hostname': {
|
||||
label: this.$t('assets.Hostname'),
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'ip',
|
||||
'ip': {
|
||||
label: this.$t('assets.ip'),
|
||||
width: '120px'
|
||||
},
|
||||
{
|
||||
prop: 'username',
|
||||
'username': {
|
||||
label: this.$t('assets.Username'),
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'version',
|
||||
'version': {
|
||||
label: this.$t('assets.Version'),
|
||||
width: '70px'
|
||||
},
|
||||
{
|
||||
prop: 'date_created',
|
||||
'date_created': {
|
||||
label: this.$t('assets.date_joined'),
|
||||
formatter: DateFormatter
|
||||
},
|
||||
{
|
||||
prop: 'id',
|
||||
'actions': {
|
||||
label: this.$t('common.Action'),
|
||||
align: 'center',
|
||||
width: 150,
|
||||
@ -211,7 +210,7 @@ export default {
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
extraQuery: {
|
||||
latest: 1
|
||||
}
|
||||
@ -256,16 +255,22 @@ export default {
|
||||
const ttl = this.publicSettings.SECURITY_MFA_VERIFY_TTL
|
||||
const now = new Date()
|
||||
return !(this.MFAVerifyAt && (now - this.MFAVerifyAt) < ttl * 1000)
|
||||
},
|
||||
iTableConfig() {
|
||||
const columnsMeta = Object.assign({}, this.defaultTableConfig.columnsMeta, this.tableConfig.columnsMeta || {})
|
||||
const config = Object.assign(this.defaultTableConfig, this.tableConfig)
|
||||
config.columnsMeta = columnsMeta
|
||||
return config
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
url(iNew) {
|
||||
this.$set(this.tableConfig, 'url', iNew)
|
||||
this.$set(this.iTableConfig, 'url', iNew)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.otherActions) {
|
||||
const actionColumn = this.tableConfig.columns[this.tableConfig.columns.length - 1]
|
||||
const actionColumn = this.iTableConfig.columns[this.iTableConfig.columns.length - 1]
|
||||
for (const item of this.otherActions) {
|
||||
actionColumn.formatterArgs.extraActions.push(item)
|
||||
}
|
||||
|
@ -37,12 +37,11 @@
|
||||
class="action-item"
|
||||
@click="handleClick(action)"
|
||||
>
|
||||
<el-tooltip v-if="action.tip" effect="dark" :content="action.tip" placement="top">
|
||||
<i v-if="action.fa" :class="'fa ' + action.fa" />{{ action.title }}
|
||||
<el-tooltip :disabled="!action.tip" :content="action.tip" placement="top">
|
||||
<span>
|
||||
<i v-if="action.fa" :class="'fa ' + action.fa" />{{ action.title }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>
|
||||
<i v-if="action.fa" :class="'fa ' + action.fa" />{{ action.title }}
|
||||
</span>
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
|
@ -74,7 +74,7 @@ export default {
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
padding-right: 50px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@ -207,7 +207,7 @@ export default {
|
||||
if (!d) {
|
||||
return 0
|
||||
}
|
||||
if (!itemColData || !itemColData.length) {
|
||||
if (typeof itemColData !== 'number' && (!itemColData || !itemColData.length)) {
|
||||
return 0
|
||||
}
|
||||
return itemColData.length
|
||||
|
@ -144,10 +144,11 @@ export default {
|
||||
let actions = [...this.defaultActions, ...this.extraActions]
|
||||
actions = _.cloneDeep(actions)
|
||||
actions = actions.map((v) => {
|
||||
v.has = this.cleanBoolean(v, 'has')
|
||||
v.can = this.cleanBoolean(v, 'can')
|
||||
v.callback = this.cleanCallback(v)
|
||||
v.has = this.cleanBoolean(v, 'has', true)
|
||||
v.can = this.cleanBoolean(v, 'can', true)
|
||||
v.callback = this.cleanCallback(v, 'callback')
|
||||
v.order = v.order || 100
|
||||
v.tip = this.cleanValue(v, 'tip')
|
||||
return v
|
||||
})
|
||||
actions = actions.filter((v) => v.has)
|
||||
@ -168,15 +169,15 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
cleanBoolean(item, attr) {
|
||||
cleanBoolean(item, attr, defaults) {
|
||||
const ok = item[attr]
|
||||
if (typeof ok !== 'function') {
|
||||
return ok === undefined ? true : ok
|
||||
return ok === undefined ? defaults : ok
|
||||
}
|
||||
return ok(this.row, this.cellValue)
|
||||
return this.cleanValue(item, attr)
|
||||
},
|
||||
cleanCallback(item) {
|
||||
const callback = item.callback
|
||||
cleanCallback(item, attr) {
|
||||
const callback = item[attr]
|
||||
const attrs = {
|
||||
reload: this.reload,
|
||||
row: this.row,
|
||||
@ -185,6 +186,20 @@ export default {
|
||||
tableData: this.tableData
|
||||
}
|
||||
return () => { return callback.bind(this)(attrs) }
|
||||
},
|
||||
cleanValue(item, attr) {
|
||||
const value = item[attr]
|
||||
if (!value || typeof value !== 'function') {
|
||||
return value
|
||||
}
|
||||
const attrs = {
|
||||
reload: this.reload,
|
||||
row: this.row,
|
||||
col: this.col,
|
||||
cellValue: this.cellValue,
|
||||
tableData: this.tableData
|
||||
}
|
||||
return value(attrs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -522,6 +522,9 @@
|
||||
},
|
||||
"route": {
|
||||
"": "",
|
||||
"Accounts": "账号管理",
|
||||
"AssetAccount": "资产账号",
|
||||
"ApplicationAccount": "应用账号",
|
||||
"Ticket":"工单",
|
||||
"CommandConfirm": "命令复核",
|
||||
"AdminUserCreate": "创建管理用户",
|
||||
@ -646,7 +649,8 @@
|
||||
"Users": "用户管理",
|
||||
"WebFTP": "文件管理",
|
||||
"WebTerminal": "Web终端",
|
||||
"Notifications": "通知"
|
||||
"Notifications": "通知",
|
||||
"SiteMessageList": "站内信"
|
||||
},
|
||||
"sessions": {
|
||||
"StorageConfiguration": "存储配置",
|
||||
@ -709,6 +713,7 @@
|
||||
"common": "普通"
|
||||
},
|
||||
"Monitor": "监控",
|
||||
"XRDPNotSupport": "RDP 客户端会话, 暂不支持监控",
|
||||
"sessionMonitor": "监控",
|
||||
"TerminateTaskSendSuccessMsg": "终断任务已下发,请稍后刷新查看",
|
||||
"helpText": {
|
||||
@ -1031,7 +1036,15 @@
|
||||
"MessageType": "消息类型",
|
||||
"Receivers": "接收人",
|
||||
"Subscription": "消息订阅",
|
||||
"ChangeReceiver": "修改消息接收人"
|
||||
"ChangeReceiver": "修改消息接收人",
|
||||
"Subject": "主题",
|
||||
"Message": "消息",
|
||||
"DeliveryTime": "发送时间",
|
||||
"HasRead": "是否已读",
|
||||
"Sender": "发送人",
|
||||
"MarkAsRead": "标记已读",
|
||||
"NoUnreadMsg": "暂无未读消息",
|
||||
"SiteMessage": "站内信"
|
||||
},
|
||||
"xpack": {
|
||||
"Admin": "管理员",
|
||||
|
@ -520,6 +520,9 @@
|
||||
},
|
||||
"route": {
|
||||
"": "",
|
||||
"Accounts": "Accounts",
|
||||
"AssetAccount": "Asset account",
|
||||
"ApplicationAccount": "Application account",
|
||||
"Ticket": "Tickets",
|
||||
"CommandConfirm": "Command confirm",
|
||||
"AdminUserCreate": "Admin user create",
|
||||
@ -644,7 +647,8 @@
|
||||
"Users": "Users",
|
||||
"WebFTP": "WebFTP",
|
||||
"WebTerminal": "Web Terminal",
|
||||
"Notifications": "Notifications"
|
||||
"Notifications": "Notifications",
|
||||
"SiteMessageList": "Site message"
|
||||
},
|
||||
"sessions": {
|
||||
"StorageConfiguration": "Storage configuration",
|
||||
@ -708,6 +712,7 @@
|
||||
},
|
||||
"Monitor": "Monitor",
|
||||
"sessionMonitor": "Session Monitor",
|
||||
"XRDPNotSupport": "RDP Client session not support now",
|
||||
"TerminateTaskSendSuccessMsg": "Terminate task has been send, Please check later",
|
||||
"helpText": {
|
||||
"esUrl": "Tip: If you have multiple hosts, use comma (,) to split (eg: http://www.jumpserver.a.com,http://www.jumpserver.b.com)",
|
||||
@ -1025,7 +1030,15 @@
|
||||
"MessageType": "Message Type",
|
||||
"Receivers": "Receivers",
|
||||
"Subscription": "Subscription",
|
||||
"ChangeReceiver": "Change Receivers"
|
||||
"ChangeReceiver": "Change Receivers",
|
||||
"Subject": "Subject",
|
||||
"Message": "Message",
|
||||
"DeliveryTime": "Delivery time",
|
||||
"HasRead": "Has read",
|
||||
"Sender": "Sender",
|
||||
"MarkAsRead": "Mark as read",
|
||||
"NoUnreadMsg": "No unread messages",
|
||||
"SiteMessage": "Site messages"
|
||||
},
|
||||
"xpack": {
|
||||
"Admin": "Admin",
|
||||
|
@ -2,13 +2,10 @@
|
||||
<div class="sidebar-logo-container" :class="{'collapse':collapse}">
|
||||
<transition name="sidebarLogoFade">
|
||||
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
|
||||
<img v-if="logo" :src="logo" class="sidebar-logo">
|
||||
<h1 v-else class="sidebar-title">{{ title }}</h1>
|
||||
<img :src="logoSrc" class="sidebar-logo">
|
||||
</router-link>
|
||||
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
|
||||
<img :src="logoSrc" class="sidebar-logo-text">
|
||||
<!-- <img v-else-if="logoText" :src="logoText" class="sidebar-logo-text">-->
|
||||
<!-- <h1 class="sidebar-title">{{ title }}</h1>-->
|
||||
<img :src="logoTextSrc" class="sidebar-logo-text">
|
||||
</router-link>
|
||||
</transition>
|
||||
</div>
|
||||
@ -26,10 +23,6 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
title: 'JumpServer',
|
||||
logoText: require('@/assets/img/logo-text.png'),
|
||||
logo: require('@/assets/img/logo.png'),
|
||||
xpackData: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -37,16 +30,14 @@ export default {
|
||||
'publicSettings'
|
||||
]),
|
||||
// eslint-disable-next-line vue/return-in-computed-property
|
||||
logoTextSrc() {
|
||||
return this.publicSettings.LOGO_URLS.logo_index
|
||||
},
|
||||
logoSrc() {
|
||||
if (this.publicSettings.LOGO_URLS.logo_index !== '/static/img/logo_text.png') {
|
||||
return this.publicSettings.LOGO_URLS.logo_index
|
||||
} else {
|
||||
return this.logoText
|
||||
}
|
||||
return this.publicSettings.LOGO_URLS.logo_logout
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -10,7 +10,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Language',
|
||||
data() {
|
||||
@ -47,11 +46,22 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.currentLang.code !== this.$i18n.locale) {
|
||||
this.changeLangTo(this.currentLang)
|
||||
}
|
||||
this.changeLang()
|
||||
this.changeMomentLang()
|
||||
},
|
||||
methods: {
|
||||
changeLang() {
|
||||
if (this.currentLang.code !== this.$i18n.locale) {
|
||||
this.changeLangTo(this.currentLang)
|
||||
}
|
||||
},
|
||||
changeMomentLang() {
|
||||
if (this.currentLang.code.indexOf('en') > -1) {
|
||||
this.$moment.locale('en')
|
||||
} else {
|
||||
this.$moment.locale('zh-cn')
|
||||
}
|
||||
},
|
||||
changeLangTo(item) {
|
||||
this.$i18n.locale = item.code
|
||||
localStorage.setItem('lang', item.code)
|
||||
|
262
src/layout/components/NavHeader/SiteMessages.vue
Normal file
262
src/layout/components/NavHeader/SiteMessages.vue
Normal file
@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-badge :value="unreadMsgCount" :hidden="unreadMsgCount === 0" :max="99" size="mini" type="primary">
|
||||
<a style="color: #606266 !important; width: 30px" @click="toggleDrawer">
|
||||
<i class="el-icon-message" style="font-size: 18px" />
|
||||
</a>
|
||||
</el-badge>
|
||||
<el-drawer
|
||||
:visible.sync="show"
|
||||
:before-close="handleClose"
|
||||
:modal="false"
|
||||
:title="$t('notifications.SiteMessage')"
|
||||
custom-class="site-msg"
|
||||
size="25%"
|
||||
@open="getMessages"
|
||||
>
|
||||
<div v-if="unreadMsgCount !== 0" class="msg-list">
|
||||
<div
|
||||
v-for="msg of messages"
|
||||
:key="msg.id"
|
||||
class="msg-item"
|
||||
:class="msg.has_read ? 'msg-read' : 'msg-unread'"
|
||||
@mouseover="hoverMsgId = msg.id"
|
||||
@mouseleave="hoverMsgId = ''"
|
||||
@click="showMsgDetail(msg)"
|
||||
>
|
||||
<div class="msg-item-head">
|
||||
<span class="msg-item-head-type">
|
||||
<i :class="msg.has_read ? 'fa-envelope-open-o' : 'fa-envelope'" class="fa msg-icon" />
|
||||
{{ msg.subject }}
|
||||
</span>
|
||||
<span v-if="hoverMsgId !== msg.id || msg.has_read" class="msg-item-head-time">
|
||||
{{ formatDate(msg.date_created) }}
|
||||
</span>
|
||||
<div v-else class="msg-item-read-btn" @click.stop="markAsRead(msg)">
|
||||
<a>{{ $t('notifications.MarkAsRead') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="msg-item-txt">
|
||||
<span v-html="msg.message" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-msg">
|
||||
{{ $t('notifications.NoUnreadMsg') }}
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
<Dialog
|
||||
v-if="msgDetailVisible"
|
||||
:visible.sync="msgDetailVisible"
|
||||
:title="''"
|
||||
:close-on-click-modal="false"
|
||||
:confirm-title="$t('notifications.MarkAsRead')"
|
||||
@confirm="markAsRead(currentMsg)"
|
||||
@cancel="cancelRead"
|
||||
>
|
||||
<div class="msg-detail">
|
||||
<div class="msg-detail-head">
|
||||
<h3>{{ currentMsg.subject }}</h3>
|
||||
<h5>
|
||||
<span class="msg-detail-time">{{ formatDate(currentMsg.date_created) }}</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="msg-detail-txt">
|
||||
<span v-html="currentMsg.message" />
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { toSafeLocalDateStr } from '@/utils/common'
|
||||
import Dialog from '@/components/Dialog'
|
||||
|
||||
export default {
|
||||
name: 'SiteMessages',
|
||||
components: { Dialog },
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
messages: [],
|
||||
hoverMsgId: '',
|
||||
msgDetailVisible: false,
|
||||
currentMsg: null,
|
||||
unreadMsgCount: 0
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.enablePullMsgCount()
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.show = false
|
||||
},
|
||||
toggleDrawer() {
|
||||
this.show = !this.show
|
||||
},
|
||||
showMsgDetail(msg) {
|
||||
this.currentMsg = msg
|
||||
this.msgDetailVisible = true
|
||||
},
|
||||
getMessages() {
|
||||
const url = '/api/v1/notifications/site-message/?offset=0&limit=15&has_read=false'
|
||||
this.$axios.get(url).then(resp => {
|
||||
this.messages = [...resp.results]
|
||||
this.unreadMsgCount = resp.count
|
||||
})
|
||||
},
|
||||
formatDate(s) {
|
||||
if (!s) {
|
||||
return ''
|
||||
}
|
||||
const d = new Date(s)
|
||||
const now = new Date()
|
||||
if (now.getTime() - d.getTime() > (3600 * 24 * 7) * 1000) {
|
||||
return toSafeLocalDateStr(s)
|
||||
} else {
|
||||
return this.$moment(d).fromNow()
|
||||
}
|
||||
},
|
||||
markAsRead(msg) {
|
||||
console.log(`${msg}`)
|
||||
const url = `/api/v1/notifications/site-message/mark-as-read/`
|
||||
this.$axios.patch(url, { ids: [msg.id] }).then(res => {
|
||||
this.msgDetailVisible = false
|
||||
this.getMessages()
|
||||
}).catch(err => {
|
||||
this.$message(err.detail)
|
||||
})
|
||||
},
|
||||
cancelRead() {
|
||||
this.msgDetailVisible = false
|
||||
},
|
||||
pullMsgCount() {
|
||||
const url = '/api/v1/notifications/site-message/unread-total/'
|
||||
this.$axios.get(url).then(res => {
|
||||
this.unreadMsgCount = res.total
|
||||
}).catch(err => {
|
||||
this.$message(err.detail)
|
||||
})
|
||||
},
|
||||
enablePullMsgCount() {
|
||||
this.pullMsgCount()
|
||||
setInterval(() => {
|
||||
this.pullMsgCount()
|
||||
}, 10000)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-badge ::v-deep .el-badge__content.is-fixed{
|
||||
top:10px;
|
||||
}
|
||||
|
||||
.msg-list {
|
||||
padding: 0 25px 20px;
|
||||
}
|
||||
|
||||
>>> .site-msg {
|
||||
.el-drawer__header {
|
||||
border-bottom: solid 1px rgb(231, 234, 239);
|
||||
margin-bottom: 0;
|
||||
padding-top: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.el-drawer__body {
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.msg-item {
|
||||
border-bottom: solid 1px rgb(231, 234, 239);
|
||||
padding: 15px 0 10px;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #ddd;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #f2f2f2;
|
||||
padding: 15px 20px 10px;
|
||||
margin: 0 -20px;
|
||||
border-bottom: 1px solid #fff;
|
||||
}
|
||||
|
||||
.msg-icon {
|
||||
font-size: 13px;
|
||||
line-height: 13px;
|
||||
}
|
||||
|
||||
&.msg-unread {
|
||||
.msg-item-txt {
|
||||
font-weight: bolder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.msg-item-head {
|
||||
line-height: 20px;
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
&:after {
|
||||
clear: both;
|
||||
content: ".";
|
||||
display: block;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.msg-item-head-type {
|
||||
float: left;
|
||||
width: 120px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.msg-item-head-time {
|
||||
float: right;
|
||||
}
|
||||
.msg-item-read-btn {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.msg-item-txt {
|
||||
overflow: hidden;
|
||||
color: #000;
|
||||
padding: 4px 0 0;
|
||||
line-height: 21px;
|
||||
max-height: 21px;
|
||||
display: -webkit-box;
|
||||
font-size: 12px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.msg-detail {
|
||||
padding-left: 20px;
|
||||
|
||||
.msg-detail-time {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.msg-detail-txt {
|
||||
margin-bottom: 20px;
|
||||
line-height: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.no-msg {
|
||||
padding-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
>>> :focus{ outline:0; }
|
||||
</style>
|
@ -3,30 +3,26 @@
|
||||
<div class="navbar-header">
|
||||
<hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
|
||||
</div>
|
||||
<div class="navbar-right">
|
||||
<div class="header-item">
|
||||
<ul class="navbar-right">
|
||||
<li class="header-item header-icon">
|
||||
<SiteMessages />
|
||||
</li>
|
||||
<li class="header-item" style="margin-left: 10px">
|
||||
<Help />
|
||||
</div>
|
||||
<div class="header-item">
|
||||
</li>
|
||||
<li class="header-item">
|
||||
<Language />
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
publicSettings.TICKETS_ENABLED
|
||||
&& publicSettings.XPACK_LICENSE_IS_VALID
|
||||
&& !isOrgAuditor
|
||||
"
|
||||
class="header-item"
|
||||
>
|
||||
</li>
|
||||
<li v-if="showTickets" class="header-item">
|
||||
<Tickets />
|
||||
</div>
|
||||
<div class="header-item">
|
||||
</li>
|
||||
<li class="header-item">
|
||||
<WebTerminal />
|
||||
</div>
|
||||
<div class="header-item header-profile">
|
||||
</li>
|
||||
<li class="header-item header-profile">
|
||||
<AccountDropdown />
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -34,6 +30,7 @@
|
||||
import { mapGetters } from 'vuex'
|
||||
import Hamburger from '@/components/Hamburger'
|
||||
import AccountDropdown from './AccountDropdown'
|
||||
import SiteMessages from './SiteMessages'
|
||||
import Help from './Help'
|
||||
import Language from './Language'
|
||||
import WebTerminal from './WebTerminal'
|
||||
@ -48,7 +45,8 @@ export default {
|
||||
Language,
|
||||
Help,
|
||||
Tickets,
|
||||
WebTerminal
|
||||
WebTerminal,
|
||||
SiteMessages
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -60,13 +58,17 @@ export default {
|
||||
]),
|
||||
isOrgAuditor() {
|
||||
return rolc.getRolesDisplay(this.currentOrgRoles).includes('OrgAuditor') || rolc.getRolesDisplay(this.currentOrgRoles).includes('Auditor')
|
||||
},
|
||||
showTickets() {
|
||||
return this.publicSettings.TICKETS_ENABLED &&
|
||||
this.publicSettings.XPACK_LICENSE_IS_VALID &&
|
||||
!this.isOrgAuditor
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleSideBar() {
|
||||
this.$store.dispatch('app/toggleSideBar')
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -91,12 +93,19 @@ export default {
|
||||
.navbar-right {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.header-item {
|
||||
line-height: 50px;
|
||||
display: inline-block;
|
||||
padding-right: 20px;
|
||||
.header-item {
|
||||
line-height: 50px;
|
||||
display: inline-block;
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
&:hover {
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb-container {
|
||||
@ -108,5 +117,9 @@ export default {
|
||||
.el-header {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
@ -40,8 +40,13 @@ Vue.config.productionTip = false
|
||||
import VueCookie from 'vue-cookie'
|
||||
Vue.use(VueCookie)
|
||||
window.$cookie = VueCookie
|
||||
import VueMoment from 'vue-moment'
|
||||
Vue.use(VueMoment)
|
||||
|
||||
const moment = require('moment')
|
||||
require('moment/locale/zh-cn')
|
||||
Vue.use(require('vue-moment'), {
|
||||
moment
|
||||
})
|
||||
|
||||
// logger
|
||||
import VueLogger from 'vuejs-logger'
|
||||
import loggerOptions from './utils/logger'
|
||||
|
120
src/router/accounts.js
Normal file
120
src/router/accounts.js
Normal file
@ -0,0 +1,120 @@
|
||||
import i18n from '@/i18n/i18n'
|
||||
import empty from '@/layout/empty'
|
||||
export default [
|
||||
{
|
||||
path: 'asset-accounts',
|
||||
component: empty,
|
||||
meta: { title: i18n.t('route.AssetAccount') },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'AssetAccountList',
|
||||
component: () => import('@/views/accounts/AssetAccount/AssetAccountList'),
|
||||
meta: { title: i18n.t('route.AssetAccount') }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'application-accounts',
|
||||
component: empty,
|
||||
meta: { title: i18n.t('route.AssetAccount') },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'ApplicationAccountList',
|
||||
component: () => import('@/views/accounts/ApplicationAccount/ApplicationAccountList'),
|
||||
meta: { title: i18n.t('route.ApplicationAccount') }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'gathered-user',
|
||||
component: empty,
|
||||
redirect: '',
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUserList') },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/accounts/GatheredUser/index'),
|
||||
name: 'GatherUserListIndex',
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUser'), activeMenu: '/accounts/gathered-user' }
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/accounts/GatheredUser/GatheredUserList'),
|
||||
name: 'GatherUserList',
|
||||
hidden: true,
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUserList'), activeMenu: '/accounts/gathered-user' }
|
||||
},
|
||||
{
|
||||
path: 'tasks',
|
||||
component: () => import('@/views/accounts/GatheredUser/TaskList'),
|
||||
name: 'GatherUserTaskList',
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUserTaskList'), activeMenu: '/accounts/gathered-user' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'tasks/:id',
|
||||
component: () => import('@/views/accounts/GatheredUser/TaskDetail/index'),
|
||||
name: 'GatherUserTaskDetail',
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUserTaskDetail'), activeMenu: '/accounts/gathered-user' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'tasks/create',
|
||||
component: () => import('@/views/accounts/GatheredUser/TaskCreateUpdate'),
|
||||
name: 'GatherUserTaskCreate',
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUserTaskCreate'), activeMenu: '/accounts/gathered-user' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'tasks/:id/update',
|
||||
component: () => import('@/views/accounts/GatheredUser/TaskCreateUpdate'),
|
||||
name: 'GatherUserTaskUpdate',
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUserTaskUpdate'), action: 'update', activeMenu: '/accounts/gathered-user' },
|
||||
hidden: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'change-auth-plan',
|
||||
component: empty,
|
||||
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlan'), activeMenu: '/accounts/change-auth-plan/plan' },
|
||||
children: [
|
||||
{
|
||||
path: 'plan',
|
||||
component: () => import('@/views/accounts/ChangeAuthPlan/ChangeAuthPlanList.vue'),
|
||||
name: 'ChangeAuthPlanList',
|
||||
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlan'), activeMenu: '/accounts/change-auth-plan/plan' }
|
||||
},
|
||||
{
|
||||
path: 'plan/create',
|
||||
component: () => import('@/views/accounts/ChangeAuthPlan/ChangeAuthPlanCreateUpdate.vue'),
|
||||
name: 'ChangeAuthPlanCreate',
|
||||
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlanCreate'), activeMenu: '/accounts/change-auth-plan/plan', action: 'create' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'plan/:id/update',
|
||||
component: () => import('@/views/accounts/ChangeAuthPlan/ChangeAuthPlanCreateUpdate.vue'),
|
||||
name: 'ChangeAuthPlanUpdate',
|
||||
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlanUpdate'), activeMenu: '/accounts/change-auth-plan/plan', action: 'update' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'plan/:id',
|
||||
component: () => import('@/views/accounts/ChangeAuthPlan/ChangeAuthPlanDetail/index.vue'),
|
||||
name: 'ChangeAuthPlanDetail',
|
||||
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlan'), activeMenu: '/accounts/change-auth-plan/plan' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'plan-execution/:id',
|
||||
component: () => import('@/views/accounts/ChangeAuthPlan/ChangeAuthPlanDetail/ChangeAuthPlanExecution/ChangeAuthPlanExecutionDetail/index.vue'),
|
||||
name: 'ChangeAuthPlanExecutionDetail',
|
||||
meta: { title: i18n.t('xpack.ChangeAuthPlan.ExecutionDetail'), activeMenu: '/accounts/change-auth-plan/plan' },
|
||||
hidden: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -37,6 +37,7 @@ import TicketsRoutes from './tickets'
|
||||
import AuditsRoutes from './audits'
|
||||
import commonRoutes from './common'
|
||||
import aclRoutes from './acl'
|
||||
import AccountRoutes from './accounts'
|
||||
|
||||
/**
|
||||
* constantRoutes
|
||||
@ -110,6 +111,18 @@ export const allRoleRoutes = [
|
||||
meta: { title: i18n.t('route.Applications'), icon: 'th' },
|
||||
children: ApplicationsRoute
|
||||
},
|
||||
{
|
||||
path: '/accounts',
|
||||
component: Layout,
|
||||
redirect: '/accounts/asset-accounts/',
|
||||
name: 'Accounts',
|
||||
meta: {
|
||||
licenseRequired: true,
|
||||
title: i18n.t('route.Accounts'),
|
||||
icon: 'address-book'
|
||||
},
|
||||
children: AccountRoutes
|
||||
},
|
||||
{
|
||||
path: '/perms/',
|
||||
component: Layout,
|
||||
|
@ -1,6 +1,5 @@
|
||||
module.exports = {
|
||||
|
||||
title: 'JumpServer',
|
||||
title: '.',
|
||||
|
||||
/**
|
||||
* @type {boolean} true | false
|
||||
|
@ -35,15 +35,18 @@ const actions = {
|
||||
getPublicSettings({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getPublicSettings().then(response => {
|
||||
const link = document.querySelector("link[rel*='icon']") || document.createElement('link')
|
||||
link.type = 'image/x-icon'
|
||||
link.rel = 'shortcut icon'
|
||||
link.href = response.data.LOGO_URLS.favicon
|
||||
document.getElementsByTagName('head')[0].appendChild(link)
|
||||
const faviconURL = response.data.LOGO_URLS.favicon
|
||||
let link = document.querySelector("link[rel*='icon']")
|
||||
if (!link) {
|
||||
link = document.createElement('link')
|
||||
link.type = 'image/x-icon'
|
||||
link.rel = 'shortcut icon'
|
||||
document.getElementsByTagName('head')[0].appendChild(link)
|
||||
}
|
||||
link.href = faviconURL
|
||||
|
||||
// 动态修改Title
|
||||
if (response.data.LOGIN_TITLE) { document.title = response.data.LOGIN_TITLE }
|
||||
|
||||
document.title = response.data.LOGIN_TITLE
|
||||
commit('SET_PUBLIC_SETTINGS', response.data)
|
||||
resolve(response)
|
||||
}).catch(error => {
|
||||
|
@ -117,7 +117,7 @@ export default {
|
||||
name: 'connect',
|
||||
fa: 'fa-terminal',
|
||||
type: 'primary',
|
||||
can: (row, cellValue) => {
|
||||
can: ({ row, cellValue }) => {
|
||||
return row.is_active
|
||||
},
|
||||
callback: function({ row, col, cellValue, reload }) {
|
||||
|
@ -88,11 +88,14 @@ export function toSafeLocalDateStr(d) {
|
||||
}
|
||||
|
||||
export function getApiPath(that) {
|
||||
const pagePath = that.$route.path
|
||||
const isOrgPath = pagePath.split('/').indexOf('orgs') !== -1
|
||||
if (isOrgPath) {
|
||||
return `/api/v1/orgs/orgs/${pagePath.split('/').pop()}/`
|
||||
let pagePath = that.$route.path
|
||||
const pagePathArray = pagePath.split('/')
|
||||
if (pagePathArray.indexOf('orgs') !== -1) {
|
||||
pagePathArray[pagePathArray.indexOf('xpack')] = 'orgs'
|
||||
} else if (pagePathArray.indexOf('gathered-user') !== -1 || pagePathArray.indexOf('change-auth-plan') !== -1) {
|
||||
pagePathArray[pagePathArray.indexOf('accounts')] = 'xpack'
|
||||
}
|
||||
pagePath = pagePathArray.join('/')
|
||||
return `/api/v1${pagePath}/`
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import getPageTitle from '@/utils/get-page-title'
|
||||
// import getPageTitle from '@/utils/get-page-title'
|
||||
import store from '@/store'
|
||||
import router from '@/router'
|
||||
import { Message } from 'element-ui'
|
||||
@ -15,9 +15,9 @@ function reject(msg) {
|
||||
return new Promise((resolve, reject) => reject(msg))
|
||||
}
|
||||
|
||||
function setHeadTitle({ to, from, next }) {
|
||||
document.title = getPageTitle(to.meta.title)
|
||||
}
|
||||
// function setHeadTitle({ to, from, next }) {
|
||||
// document.title = getPageTitle(to.meta.title)
|
||||
// }
|
||||
|
||||
async function checkLogin({ to, from, next }) {
|
||||
if (whiteList.indexOf(to.path) !== -1) {
|
||||
@ -145,7 +145,7 @@ export async function startup({ to, from, next }) {
|
||||
|
||||
// set page title
|
||||
await getPublicSetting({ to, from, next })
|
||||
await setHeadTitle({ to, from, next })
|
||||
// await setHeadTitle({ to, from, next })
|
||||
await checkLogin({ to, from, next })
|
||||
await changeCurrentOrgIfNeed({ to, from, next })
|
||||
await changeCurrentRoleIfNeed({ to, from, next })
|
||||
|
247
src/views/accounts/ApplicationAccount/ApplicationAccountList.vue
Normal file
247
src/views/accounts/ApplicationAccount/ApplicationAccountList.vue
Normal file
@ -0,0 +1,247 @@
|
||||
<template>
|
||||
<Page>
|
||||
<el-row>
|
||||
<el-col :span="11">
|
||||
<GenericListTable
|
||||
ref="LeftTable"
|
||||
class="application-table"
|
||||
:header-actions="leftTable.headerActions"
|
||||
:table-config="leftTable.tableConfig"
|
||||
@row-click="leftTable.tableConfig.rowClick"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="13">
|
||||
<GenericListTable
|
||||
ref="RightTable"
|
||||
class="application-user-table"
|
||||
:header-actions="rightTable.headerActions"
|
||||
:table-config="rightTable.tableConfig"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<Dialog v-if="showMFADialog" width="50" :title="this.$t('common.MFAConfirm')" :visible.sync="showMFADialog" :show-confirm="false" :show-cancel="false" :destroy-on-close="true">
|
||||
<div v-if="MFAConfirmed">
|
||||
<el-form label-position="right" label-width="80px" :model="MFAInfo">
|
||||
<el-form-item :label="this.$t('assets.Applications')">
|
||||
<el-input v-model="MFAInfo.application" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item :label="this.$t('assets.Username')">
|
||||
<el-input v-model="MFAInfo.username" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item :label="this.$t('assets.Password')">
|
||||
<el-input v-model="MFAInfo.password" type="password" show-password />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<el-row v-else :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>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Page from '@/layout/components/Page'
|
||||
import GenericListTable from '@/layout/components/GenericListTable'
|
||||
import { ActionsFormatter, DetailFormatter } from '@/components/TableFormatters'
|
||||
import Dialog from '@/components/Dialog'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'AssetAccountList',
|
||||
components: {
|
||||
GenericListTable, Page, Dialog
|
||||
},
|
||||
data() {
|
||||
const vm = this
|
||||
return {
|
||||
showMFADialog: false,
|
||||
MFAConfirmed: false,
|
||||
MFAInput: '',
|
||||
MFAInfo: {
|
||||
systemUser: '',
|
||||
application: '',
|
||||
username: '',
|
||||
password: ''
|
||||
},
|
||||
clickedRow: {},
|
||||
leftTable: {
|
||||
tableConfig: {
|
||||
url: '/api/v1/applications/applications/',
|
||||
columns: [
|
||||
'name', 'category_display', 'type_display', 'comment'
|
||||
],
|
||||
columnsShow: {
|
||||
min: ['name'],
|
||||
default: ['name', 'category_display', 'type_display']
|
||||
},
|
||||
columnsMeta: {
|
||||
name: {
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
getRoute({ row, col, cellValue }) {
|
||||
return {
|
||||
'db': 'DatabaseAppDetail', 'remote_app': 'RemoteAppDetail', 'cloud': 'KubernetesAppDetail'
|
||||
}[row.category]
|
||||
}
|
||||
},
|
||||
showOverflowTooltip: true,
|
||||
sortable: false
|
||||
}
|
||||
},
|
||||
tableAttrs: {
|
||||
stripe: false, // 斑马纹表格
|
||||
border: true, // 表格边框
|
||||
fit: true, // 宽度自适应,
|
||||
tooltipEffect: 'dark',
|
||||
rowClassName({ row, rowIndex }) {
|
||||
if (row === vm.clickedRow) {
|
||||
return 'row-clicked'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
},
|
||||
rowClick: function(row, column, event) {
|
||||
vm.rightTable.tableConfig.url = `/api/v1/applications/application-users/?application_id=${row.id}`
|
||||
vm.clickedRow = row
|
||||
vm.MFAInfo.application = row.name
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasLeftActions: false,
|
||||
hasCreate: false,
|
||||
hasExport: false,
|
||||
hasImport: false,
|
||||
hasBulkDelete: false,
|
||||
hasBulkUpdate: false
|
||||
}
|
||||
},
|
||||
rightTable: {
|
||||
tableConfig: {
|
||||
url: `/api/v1/applications/application-users/?application_id=`,
|
||||
columns: [
|
||||
'name', 'username', 'username_same_with_user', 'protocol', 'login_mode', 'priority', 'comment', 'actions'
|
||||
],
|
||||
columnsShow: {
|
||||
min: ['name', 'username', 'actions'],
|
||||
default: ['name', 'username', 'date_created', 'actions']
|
||||
},
|
||||
columnsMeta: {
|
||||
name: {
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
route: 'SystemUserDetail'
|
||||
},
|
||||
showOverflowTooltip: true,
|
||||
sortable: false
|
||||
},
|
||||
protocol: {
|
||||
sortable: false
|
||||
},
|
||||
login_mode: {
|
||||
sortable: false
|
||||
},
|
||||
actions: {
|
||||
label: this.$t('common.Action'),
|
||||
align: 'center',
|
||||
width: 150,
|
||||
formatter: ActionsFormatter,
|
||||
formatterArgs: {
|
||||
hasUpdate: false, // can set function(row, value)
|
||||
hasDelete: false, // can set function(row, value)
|
||||
hasClone: false,
|
||||
moreActionsTitle: this.$t('common.More'),
|
||||
extraActions: [
|
||||
{
|
||||
name: 'View',
|
||||
title: this.$t('common.View'),
|
||||
type: 'primary',
|
||||
callback: function(val) {
|
||||
this.MFAInfo.systemUser = val.row
|
||||
if (!this.needMFAVerify) {
|
||||
this.showMFADialog = true
|
||||
this.MFAConfirmed = true
|
||||
this.$axios.get(`/api/v1/assets/system-users/${this.MFAInfo.systemUser.id}/auth-info/`).then(res => {
|
||||
this.MFAConfirmed = true
|
||||
this.MFAInfo.username = res.username
|
||||
this.MFAInfo.password = res.password
|
||||
})
|
||||
} else {
|
||||
this.showMFADialog = true
|
||||
}
|
||||
}.bind(this)
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
tableAttrs: {
|
||||
stripe: false, // 斑马纹表格
|
||||
border: true, // 表格边框
|
||||
fit: true, // 宽度自适应,
|
||||
tooltipEffect: 'dark',
|
||||
rowClassName({ row, rowIndex }) {
|
||||
return 'row-background-color'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasLeftActions: false,
|
||||
hasImport: false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
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)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
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')
|
||||
this.$axios.get(`/api/v1/assets/system-users/${this.MFAInfo.systemUser.id}/auth-info/`).then(res => {
|
||||
this.MFAConfirmed = true
|
||||
this.MFAInfo.username = res.username
|
||||
this.MFAInfo.password = res.password
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.application-table ::v-deep .row-clicked, .application-user-table ::v-deep .row-background-color {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
</style>
|
176
src/views/accounts/AssetAccount/AssetAccountList.vue
Normal file
176
src/views/accounts/AssetAccount/AssetAccountList.vue
Normal file
@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<Page>
|
||||
<el-row>
|
||||
<el-col v-show="iShowTree" :span="iShowTree?4:0">
|
||||
<AutoDataZTree
|
||||
ref="AUtoDataZTree"
|
||||
:setting="treeSetting"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="iShowTree?9:11">
|
||||
<div class="mini">
|
||||
<div style="display:block" class="mini-button" @click="iShowTree=!iShowTree">
|
||||
<i v-show="iShowTree" class="fa fa-angle-left fa-x" />
|
||||
<i v-show="!iShowTree" class="fa fa-angle-right fa-x" />
|
||||
</div>
|
||||
</div>
|
||||
<GenericListTable
|
||||
ref="LeftTable"
|
||||
class="asset-table"
|
||||
:header-actions="leftTable.headerActions"
|
||||
:table-config="leftTable.tableConfig"
|
||||
@row-click="leftTable.tableConfig.rowClick"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="iShowTree?11:13">
|
||||
<AssetUserTable
|
||||
ref="RightTable"
|
||||
class="asset-user-table"
|
||||
:url="rightTable.url"
|
||||
:has-left-actions="rightTable.hasLeftActions"
|
||||
:table-config="rightTable.tableConfig"
|
||||
:has-clone="false"
|
||||
:has-import="false"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Page from '@/layout/components/Page'
|
||||
import GenericListTable from '@/layout/components/GenericListTable'
|
||||
import AutoDataZTree from '@/components/AutoDataZTree/index'
|
||||
import { AssetUserTable } from '@/components'
|
||||
import { DetailFormatter } from '@/components/TableFormatters'
|
||||
|
||||
export default {
|
||||
name: 'AssetAccountList',
|
||||
components: {
|
||||
AutoDataZTree, GenericListTable, Page, AssetUserTable
|
||||
},
|
||||
data() {
|
||||
const vm = this
|
||||
return {
|
||||
clickedRow: {},
|
||||
iShowTree: true,
|
||||
treeSetting: {
|
||||
showMenu: false,
|
||||
showRefresh: false,
|
||||
showAssets: false,
|
||||
url: '',
|
||||
treeUrl: '/api/v1/assets/nodes/children/tree/',
|
||||
callback: {
|
||||
onSelected: function(event, treeNode) {
|
||||
vm.leftTable.tableConfig.url = `/api/v1/assets/assets/?node_id=${treeNode.meta.node.id}`
|
||||
}
|
||||
}
|
||||
},
|
||||
leftTable: {
|
||||
tableConfig: {
|
||||
url: '/api/v1/assets/assets/',
|
||||
columns: [
|
||||
'hostname', 'ip', 'protocols', 'platform', 'comment'
|
||||
],
|
||||
columnsShow: {
|
||||
min: ['hostname', 'ip'],
|
||||
default: ['hostname', 'ip', 'platform']
|
||||
},
|
||||
columnsMeta: {
|
||||
hostname: {
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
route: 'AssetDetail',
|
||||
routeQuery: {
|
||||
activeTab: 'Detail'
|
||||
}
|
||||
},
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
ip: {
|
||||
showOverflowTooltip: true
|
||||
}
|
||||
},
|
||||
tableAttrs: {
|
||||
stripe: false, // 斑马纹表格
|
||||
border: true, // 表格边框
|
||||
fit: true, // 宽度自适应,
|
||||
tooltipEffect: 'dark',
|
||||
rowClassName({ row, rowIndex }) {
|
||||
if (row === vm.clickedRow) {
|
||||
return 'row-clicked'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
},
|
||||
rowClick: function(row, column, event) {
|
||||
vm.rightTable.url = `/api/v1/assets/asset-users/?asset_id=${row.id}&latest=1`
|
||||
vm.clickedRow = row
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasLeftActions: false,
|
||||
hasCreate: false,
|
||||
hasExport: false,
|
||||
hasImport: false,
|
||||
hasBulkDelete: false,
|
||||
hasColumnSetting: true,
|
||||
hasRefresh: true,
|
||||
hasBulkUpdate: false
|
||||
}
|
||||
},
|
||||
rightTable: {
|
||||
url: `/api/v1/assets/asset-users/?hostname=ShowFirstAssetRelated&latest=1`,
|
||||
tableConfig: {
|
||||
columns: ['name', 'username', 'version', 'backend_display', 'date_created', 'actions'],
|
||||
columnsShow: {
|
||||
min: ['username', 'actions'],
|
||||
default: ['name', 'username', 'version', 'backend_display', 'date_created', 'actions']
|
||||
},
|
||||
columnsMeta: {
|
||||
name: {
|
||||
formatter: null,
|
||||
showOverflowTooltip: true,
|
||||
sortable: false
|
||||
}
|
||||
},
|
||||
tableAttrs: {
|
||||
stripe: true, // 斑马纹表格
|
||||
border: true, // 表格边框
|
||||
fit: true, // 宽度自适应,
|
||||
tooltipEffect: 'dark',
|
||||
rowClassName({ row, rowIndex }) {
|
||||
return 'row-background-color'
|
||||
}
|
||||
}
|
||||
},
|
||||
hasLeftActions: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.asset-table ::v-deep .row-clicked, .asset-user-table ::v-deep .row-background-color {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
.asset-table {
|
||||
& >>> .table-content {
|
||||
margin-left: 21px;
|
||||
}
|
||||
}
|
||||
.mini-button{
|
||||
width: 12px;
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
text-align: center;
|
||||
padding: 9px 0;
|
||||
background-color: #1ab394;
|
||||
border-color: #1ab394;
|
||||
color: #FFFFFF;
|
||||
border-radius: 5px;
|
||||
line-height: 1.428;
|
||||
cursor:pointer;
|
||||
}
|
||||
</style>
|
203
src/views/accounts/GatheredUser/GatheredUserList.vue
Normal file
203
src/views/accounts/GatheredUser/GatheredUserList.vue
Normal file
@ -0,0 +1,203 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col v-show="iShowTree" :span="iShowTree?4:0">
|
||||
<AutoDataZTree
|
||||
ref="AUtoDataZTree"
|
||||
:setting="treeSetting"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="iShowTree?9:11">
|
||||
<div class="mini">
|
||||
<div style="display:block" class="mini-button" @click="iShowTree=!iShowTree">
|
||||
<i v-show="iShowTree" class="fa fa-angle-left fa-x" />
|
||||
<i v-show="!iShowTree" class="fa fa-angle-right fa-x" />
|
||||
</div>
|
||||
</div>
|
||||
<GenericListTable
|
||||
ref="LeftTable"
|
||||
class="asset-table"
|
||||
:header-actions="leftTable.headerActions"
|
||||
:table-config="leftTable.tableConfig"
|
||||
@row-click="leftTable.tableConfig.rowClick"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="iShowTree?11:13">
|
||||
<GenericListTable
|
||||
ref="RightTable"
|
||||
class="asset-user-table"
|
||||
:header-actions="rightTable.headerActions"
|
||||
:table-config="rightTable.tableConfig"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GenericListTable from '@/layout/components/GenericListTable'
|
||||
import AutoDataZTree from '@/components/AutoDataZTree/index'
|
||||
import { ChoicesFormatter, DetailFormatter } from '@/components/TableFormatters'
|
||||
|
||||
export default {
|
||||
name: 'AssetAccountList',
|
||||
components: {
|
||||
AutoDataZTree, GenericListTable
|
||||
},
|
||||
data() {
|
||||
const vm = this
|
||||
return {
|
||||
clickedRow: {},
|
||||
iShowTree: true,
|
||||
treeSetting: {
|
||||
showMenu: false,
|
||||
showRefresh: false,
|
||||
showAssets: false,
|
||||
url: '',
|
||||
treeUrl: '/api/v1/assets/nodes/children/tree/',
|
||||
callback: {
|
||||
onSelected: function(event, treeNode) {
|
||||
vm.leftTable.tableConfig.url = `/api/v1/assets/assets/?node_id=${treeNode.meta.node.id}`
|
||||
}
|
||||
}
|
||||
},
|
||||
leftTable: {
|
||||
tableConfig: {
|
||||
url: '/api/v1/assets/assets/',
|
||||
columns: [
|
||||
'hostname', 'ip', 'public_ip', 'admin_user_display',
|
||||
'protocols', 'platform', 'connectivity',
|
||||
'created_by', 'date_created', 'comment', 'org_name'
|
||||
],
|
||||
columnsShow: {
|
||||
min: ['hostname', 'ip', 'platform'],
|
||||
default: ['hostname', 'ip', 'connectivity', 'platform']
|
||||
},
|
||||
columnsMeta: {
|
||||
hostname: {
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
route: 'AssetDetail',
|
||||
routeQuery: {
|
||||
activeTab: 'Detail'
|
||||
}
|
||||
},
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
connectivity: {
|
||||
label: this.$t('assets.Reachable'),
|
||||
formatter: ChoicesFormatter,
|
||||
formatterArgs: {
|
||||
iconChoices: {
|
||||
0: 'fa-times text-danger',
|
||||
1: 'fa-check text-primary',
|
||||
2: 'fa-circle text-warning'
|
||||
},
|
||||
typeChange: function(val) {
|
||||
if (!val) {
|
||||
return 2
|
||||
}
|
||||
return val.status
|
||||
},
|
||||
hasTips: true
|
||||
},
|
||||
width: '90px',
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
tableAttrs: {
|
||||
stripe: false, // 斑马纹表格
|
||||
border: true, // 表格边框
|
||||
fit: true, // 宽度自适应,
|
||||
tooltipEffect: 'dark',
|
||||
rowClassName({ row, rowIndex }) {
|
||||
if (row === vm.clickedRow) {
|
||||
return 'row-clicked'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
},
|
||||
rowClick: function(row, column, event) {
|
||||
vm.rightTable.tableConfig.url = `/api/v1/assets/gathered-users/?asset_id=${row.id}`
|
||||
vm.clickedRow = row
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasLeftActions: false,
|
||||
hasCreate: false,
|
||||
hasExport: false,
|
||||
hasImport: false,
|
||||
hasBulkDelete: false,
|
||||
hasBulkUpdate: false
|
||||
}
|
||||
},
|
||||
rightTable: {
|
||||
tableConfig: {
|
||||
url: `/api/v1/assets/gathered-users/?asset__hostname=ShowFirstAssetRelated`,
|
||||
columns: [
|
||||
'username', 'date_last_login', 'present', 'ip_last_login', 'date_updated'
|
||||
],
|
||||
columnsShow: {
|
||||
min: ['username'],
|
||||
default: [
|
||||
'username', 'date_last_login', 'present', 'ip_last_login', 'date_updated'
|
||||
]
|
||||
},
|
||||
columnsMeta: {
|
||||
username: {
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
present: {
|
||||
width: 80
|
||||
},
|
||||
ip_last_login: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
tableAttrs: {
|
||||
stripe: true, // 斑马纹表格
|
||||
border: true, // 表格边框
|
||||
fit: true, // 宽度自适应,
|
||||
tooltipEffect: 'dark',
|
||||
rowClassName({ row, rowIndex }) {
|
||||
return 'row-background-color'
|
||||
}
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasLeftActions: false,
|
||||
hasCreate: false,
|
||||
hasExport: true,
|
||||
hasImport: false,
|
||||
hasBulkDelete: false,
|
||||
hasBulkUpdate: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.asset-table ::v-deep .row-clicked, .asset-user-table ::v-deep .row-background-color {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
.asset-table {
|
||||
& >>> .table-content {
|
||||
margin-left: 21px;
|
||||
}
|
||||
}
|
||||
.mini-button{
|
||||
width: 12px;
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
text-align: center;
|
||||
padding: 9px 0;
|
||||
background-color: #1ab394;
|
||||
border-color: #1ab394;
|
||||
color: #FFFFFF;
|
||||
border-radius: 5px;
|
||||
line-height: 1.428;
|
||||
cursor:pointer;
|
||||
}
|
||||
</style>
|
@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<GenericTreeListPage
|
||||
ref="GenericTreeListPage"
|
||||
:table-config="tableConfig"
|
||||
:header-actions="headerActions"
|
||||
:tree-setting="treeSetting"
|
||||
@TreeInitFinish="checkFirstNode"
|
||||
@TagSearch="handleTagChange"
|
||||
@TagFilter="handleFilterChange"
|
||||
@TagDateChange="handleDateChange"
|
||||
/>
|
||||
</div>
|
||||
<GenericTreeListPage
|
||||
ref="GenericTreeListPage"
|
||||
v-loading="loading"
|
||||
:table-config="tableConfig"
|
||||
:header-actions="headerActions"
|
||||
:tree-setting="treeSetting"
|
||||
class="command-list-table"
|
||||
@TreeInitFinish="checkFirstNode"
|
||||
@TagSearch="handleTagChange"
|
||||
@TagFilter="handleFilterChange"
|
||||
@TagDateChange="handleDateChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -39,6 +39,14 @@ export default {
|
||||
loading: true,
|
||||
tableConfig: {
|
||||
url: '',
|
||||
tableAttrs: {
|
||||
rowClassName: ({ row }) => {
|
||||
if (row.risk_level === 5) {
|
||||
return 'risk-command'
|
||||
}
|
||||
return 'command'
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
'expandCol', 'input', 'risk_level', 'user',
|
||||
'asset', 'system_user', 'session', 'timestamp'
|
||||
@ -51,7 +59,7 @@ export default {
|
||||
expandCol: {
|
||||
type: 'expand',
|
||||
prop: 'output',
|
||||
label: '>',
|
||||
label: '',
|
||||
formatter: OutputExpandFormatter
|
||||
},
|
||||
risk_level: {
|
||||
@ -151,7 +159,6 @@ export default {
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
},
|
||||
methods: {
|
||||
checkFirstNode(obj) {
|
||||
@ -203,6 +210,13 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style lang="scss" scoped>
|
||||
.command-list-table >>> .risk-command {
|
||||
background-color: oldlace;
|
||||
|
||||
tr {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@ -17,7 +17,7 @@ export default {
|
||||
name: 'replay',
|
||||
title: this.$t('sessions.replay'),
|
||||
type: 'warning',
|
||||
can: (row, cellValue) => {
|
||||
can: ({ row, cellValue }) => {
|
||||
return row.can_replay
|
||||
},
|
||||
callback: function({ row, tableData }) {
|
||||
@ -30,7 +30,7 @@ export default {
|
||||
name: 'download',
|
||||
title: this.$t('sessions.download'),
|
||||
type: 'primary',
|
||||
can: (row, cellValue) => {
|
||||
can: ({ row, cellValue }) => {
|
||||
return row.can_replay
|
||||
},
|
||||
callback: function({ row, tableData }) {
|
||||
|
@ -19,7 +19,7 @@ export default {
|
||||
name: 'terminate',
|
||||
title: this.$t('sessions.terminate'),
|
||||
type: 'danger',
|
||||
can: (row, cellValue) => {
|
||||
can: ({ row, cellValue }) => {
|
||||
return row.can_terminate
|
||||
},
|
||||
callback: function({ reload, row }) {
|
||||
@ -39,13 +39,15 @@ export default {
|
||||
name: 'join',
|
||||
title: this.$t('sessions.Monitor'),
|
||||
type: 'primary',
|
||||
can: (row, cellValue) => {
|
||||
if (row.protocol === 'rdp' ||
|
||||
row.protocol === 'vnc') {
|
||||
return true
|
||||
}
|
||||
can: ({ row, cellValue }) => {
|
||||
return row.can_join
|
||||
},
|
||||
tip: ({ row }) => {
|
||||
if (row.login_from === 'RT') {
|
||||
return this.$t('sessions.XRDPNotSupport')
|
||||
}
|
||||
return ''
|
||||
},
|
||||
callback: function({ row, tableData }) {
|
||||
// 跳转到luna页面
|
||||
if (row.protocol === 'rdp' || row.protocol === 'vnc') {
|
||||
|
@ -27,7 +27,8 @@ export default {
|
||||
fields: [
|
||||
[
|
||||
this.$t('common.BasicInfo'), [
|
||||
'SITE_URL', 'USER_GUIDE_URL', 'FORGOT_PASSWORD_URL', globalOrgName
|
||||
'SITE_URL', 'USER_GUIDE_URL',
|
||||
'FORGOT_PASSWORD_URL', globalOrgName
|
||||
]
|
||||
]
|
||||
],
|
||||
|
@ -64,7 +64,7 @@ export default {
|
||||
const selectedUsers = this.selectedUsers.map(item => {
|
||||
return {
|
||||
id: item.id,
|
||||
label: `${item.name}(${item.username})`
|
||||
label: item.name
|
||||
}
|
||||
})
|
||||
setTimeout(() => {
|
||||
|
@ -67,7 +67,7 @@ export default {
|
||||
}
|
||||
|
||||
this.$axios.patch(
|
||||
`/api/v1/notifications/system/subscriptions/${sub.id}/`,
|
||||
`/api/v1/notifications/system-msg-subscription/${sub.id}/`,
|
||||
{ receive_backends: backends }
|
||||
).catch(err => {
|
||||
this.$log.error(err)
|
||||
@ -78,11 +78,10 @@ export default {
|
||||
onDialogSelectSubmit(userIds) {
|
||||
this.dialogVisible = false
|
||||
this.$axios.patch(
|
||||
`/api/v1/notifications/system/subscriptions/${this.currentEditSub.id}/`,
|
||||
`/api/v1/notifications/system-msg-subscription/${this.currentEditSub.id}/`,
|
||||
{ users: userIds }
|
||||
).then(newSub => {
|
||||
const msgType = this.idMessageTypeMapper[newSub.id]
|
||||
msgType.users = newSub.users
|
||||
const msgType = this.idMessageTypeMapper[newSub.message_type]
|
||||
msgType.receivers = newSub.receivers
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
@ -97,40 +96,37 @@ export default {
|
||||
this.receiveBackends = await this.$axios.get('/api/v1/notifications/backends/')
|
||||
},
|
||||
async initSubscriptions() {
|
||||
const subscriptions = await this.$axios.get('/api/v1/notifications/system/subscriptions/')
|
||||
const subscriptions = await this.$axios.get('/api/v1/notifications/system-msg-subscription/')
|
||||
|
||||
// 根据 app 分组
|
||||
const appMessageTypesMapper = {}
|
||||
subscriptions.forEach(sub => {
|
||||
if (!(sub.message_category in appMessageTypesMapper)) {
|
||||
appMessageTypesMapper[sub.message_category] = {
|
||||
id: sub.message_category,
|
||||
value: sub.message_category_label,
|
||||
children: [sub]
|
||||
}
|
||||
} else {
|
||||
appMessageTypesMapper[sub.message_category].children.push(sub)
|
||||
}
|
||||
})
|
||||
const trans_subscriptions = []
|
||||
|
||||
// sub 改成需要的数据结构
|
||||
for (const app of Object.values(appMessageTypesMapper)) {
|
||||
app.children = app.children.map(sub => {
|
||||
for (const category of subscriptions) {
|
||||
const children = []
|
||||
trans_subscriptions.push({
|
||||
id: category.category,
|
||||
value: category.category_label,
|
||||
children: children
|
||||
})
|
||||
|
||||
for (const sub of category.children) {
|
||||
const backendsChecked = {}
|
||||
this.receiveBackends.forEach(backend => {
|
||||
backendsChecked[backend.name] = sub.receive_backends.indexOf(backend.name) > -1
|
||||
})
|
||||
const subObj = {
|
||||
id: sub.id,
|
||||
value: sub.message_label,
|
||||
|
||||
const trans_sub = {
|
||||
id: sub.message_type,
|
||||
value: sub.message_type_label,
|
||||
receivers: sub.receivers,
|
||||
receive_backends: backendsChecked
|
||||
}
|
||||
this.idMessageTypeMapper[sub.id] = subObj
|
||||
return subObj
|
||||
})
|
||||
children.push(trans_sub)
|
||||
|
||||
this.idMessageTypeMapper[trans_sub.id] = trans_sub
|
||||
}
|
||||
}
|
||||
this.tableData = Object.values(appMessageTypesMapper)
|
||||
|
||||
this.tableData = trans_subscriptions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,11 +43,22 @@ export default {
|
||||
url: '/api/v1/settings/setting/?category=terminal'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.$store.getters.hasValidLicense) {
|
||||
const xRDPFields = [
|
||||
'XRDP', [
|
||||
'TERMINAL_RDP_ADDR'
|
||||
]
|
||||
]
|
||||
this.selectFields.splice(1, 0, xRDPFields)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getMethod() {
|
||||
return 'put'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -69,6 +69,10 @@ export default {
|
||||
title: this.$t('setting.DingTalk'),
|
||||
name: 'DingTalk'
|
||||
},
|
||||
{
|
||||
title: this.$t('setting.SystemMessageSubscription'),
|
||||
name: 'SystemMessageSubscription'
|
||||
},
|
||||
{
|
||||
title: this.$t('setting.Terminal'),
|
||||
name: 'Terminal'
|
||||
@ -119,6 +123,9 @@ export default {
|
||||
case 'License':
|
||||
this.activeMenu = 'License'
|
||||
break
|
||||
case 'SystemMessageSubscription':
|
||||
this.activeMenu = 'SystemMessageSubscription'
|
||||
break
|
||||
default:
|
||||
this.activeMenu = 'Basic'
|
||||
break
|
||||
|
@ -59,7 +59,7 @@ export default {
|
||||
password: {
|
||||
component: UserPassword,
|
||||
hidden: (formValue) => {
|
||||
if (formValue.password_strategy) {
|
||||
if (formValue.password_strategy === 'custom') {
|
||||
return false
|
||||
}
|
||||
return !formValue.update_password
|
||||
@ -81,7 +81,7 @@ export default {
|
||||
}
|
||||
],
|
||||
hidden: (formValue) => {
|
||||
if (formValue.password_strategy) {
|
||||
if (formValue.password_strategy === 'custom') {
|
||||
return false
|
||||
}
|
||||
return !formValue.update_password || !this.user.can_public_key_auth
|
||||
@ -148,7 +148,7 @@ export default {
|
||||
methods: {
|
||||
cleanFormValue(value) {
|
||||
const method = this.getMethod()
|
||||
if (method === 'post' && !value.password_strategy) {
|
||||
if (method === 'post' && value.password_strategy === 'email') {
|
||||
delete value['password']
|
||||
if (this.currentOrgIsRoot) {
|
||||
delete value['groups']
|
||||
|
@ -92,7 +92,7 @@ export default {
|
||||
name: 'remove',
|
||||
type: 'warning',
|
||||
has: hasRemove,
|
||||
can: function(row, cellValue) {
|
||||
can: ({ row }) => {
|
||||
return row.can_delete
|
||||
},
|
||||
callback: this.removeUserFromOrg
|
||||
|
@ -1,78 +0,0 @@
|
||||
<template>
|
||||
<TreeTable :table-config="tableConfig" :tree-setting="treeSetting" :header-actions="headerActions" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TreeTable from '@/components/TreeTable'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TreeTable
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
treeSetting: {
|
||||
showMenu: false,
|
||||
showRefresh: true,
|
||||
showAssets: true,
|
||||
url: '/api/v1/assets/gathered-users/',
|
||||
nodeUrl: '/api/v1/assets/nodes/',
|
||||
// ?assets=0不显示资产. =1显示资产
|
||||
treeUrl: '/api/v1/assets/nodes/children/tree/?assets=1'
|
||||
},
|
||||
tableConfig: {
|
||||
url: '/api/v1/assets/gathered-users/',
|
||||
hasTree: true,
|
||||
columns: [
|
||||
'hostname', 'ip', 'username', 'date_last_login', 'present',
|
||||
'ip_last_login', 'date_updated'
|
||||
],
|
||||
columnsMeta: {
|
||||
hostname: {
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
ip: {
|
||||
width: 120
|
||||
},
|
||||
username: {
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
present: {
|
||||
width: 80
|
||||
},
|
||||
ip_last_login: {
|
||||
width: 120
|
||||
}
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasCreate: false,
|
||||
hasLeftActions: false,
|
||||
hasImport: false,
|
||||
searchConfig: {
|
||||
exclude: ['asset'],
|
||||
options: [
|
||||
{
|
||||
label: this.$t('assets.Hostname'),
|
||||
value: 'asset__hostname'
|
||||
},
|
||||
{
|
||||
label: 'IP',
|
||||
value: 'asset__ip'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onGatherUserTasks() {
|
||||
this.$router.push({ name: 'GatherUserTaskList' })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
@ -85,9 +85,10 @@ export default {
|
||||
componentName() {
|
||||
const nameMapper = {
|
||||
koko: 'KoKo',
|
||||
guacamole: 'Guacamole',
|
||||
omnidb: 'OmniDB',
|
||||
lion: 'Lion'
|
||||
guacamole: 'Guacamole',
|
||||
lion: 'Lion',
|
||||
xrdp: 'XRDP'
|
||||
}
|
||||
return nameMapper[this.componentMetric.type]
|
||||
}
|
||||
|
@ -1,60 +0,0 @@
|
||||
<template>
|
||||
<GenericCreateUpdatePage :fields="fields" :initial="initial" :fields-meta="fieldsMeta" :url="url" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
|
||||
import { UploadKey, Select2 } from '@/components'
|
||||
export default {
|
||||
name: 'VaultCreate',
|
||||
components: {
|
||||
GenericCreateUpdatePage
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
url: '/api/v1/assets/asset-users/',
|
||||
initial: {
|
||||
|
||||
},
|
||||
fields: [
|
||||
[this.$t('common.Basic'), ['asset', 'username', 'password', 'private_key', 'comment']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
asset: {
|
||||
label: this.$t('perms.Asset'),
|
||||
component: Select2,
|
||||
el: {
|
||||
multiple: false,
|
||||
ajax: {
|
||||
url: '/api/v1/assets/assets/',
|
||||
transformOption: (item) => {
|
||||
return { label: `${item.hostname}(${item.ip})`, value: item.id }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
name: {
|
||||
el: {
|
||||
placeholder: this.$t('common.Name')
|
||||
}
|
||||
},
|
||||
username: {
|
||||
el: {
|
||||
placeholder: this.$t('common.Username')
|
||||
}
|
||||
},
|
||||
password: {
|
||||
helpText: this.$t('common.passwordOrPassphrase')
|
||||
},
|
||||
private_key: {
|
||||
component: UploadKey
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
@ -1,340 +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>
|
||||
<Dialog :title="$t('common.Export')" :visible.sync="showExportDialog" @confirm="handleExportConfirm()" @cancel="handleExportCancel()">
|
||||
<el-form label-position="left" style="padding-left: 50px">
|
||||
<el-form-item :label="this.$t('common.imExport.ExportRange')" :label-width="'100px'">
|
||||
<el-radio v-model="exportOption" class="export-item" label="1">{{ this.$t('common.imExport.ExportAll') }}</el-radio>
|
||||
<br>
|
||||
<el-radio v-model="exportOption" :disabled="selectedRows.length===0" class="export-item" label="2">{{ this.$t('common.imExport.ExportOnlySelectedItems') }}</el-radio>
|
||||
<br>
|
||||
<!-- 去掉导出搜索项-->
|
||||
<!-- <el-radio v-model="exportOption" disabled class="export-item" label="3">{{ this.$t('common.imExport.ExportOnlyFiltered') }}</el-radio>-->
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</Dialog>
|
||||
<Dialog :title="$t('common.Import')" :visible.sync="showImportDialog" @confirm="handleImportConfirm()" @cancel="handleImportCancel()">
|
||||
<el-form label-position="left" style="padding-left: 50px">
|
||||
<el-form-item :label="$t('common.Import' )" :label-width="'100px'">
|
||||
<el-radio v-model="importOption" class="export-item" label="1">{{ this.$t('common.Create') }}</el-radio>
|
||||
<el-radio v-model="importOption" disabled class="export-item" label="2">{{ this.$t('common.Update') }}</el-radio>
|
||||
<div style="line-height: 1.5">
|
||||
<span v-if="importOption==='1'" class="el-upload__tip">
|
||||
{{ this.$t('common.imExport.downloadImportTemplateMsg') }}
|
||||
<el-link type="success" :underline="false" :href="downloadImportTempUrl">{{ this.$t('common.Download') }}</el-link>
|
||||
</span>
|
||||
<span v-else class="el-upload__tip">`
|
||||
{{ this.$t('common.imExport.downloadUpdateTemplateMsg') }}
|
||||
<el-link type="success" :underline="false" @click="downloadUpdateTempUrl">{{ this.$t('common.Download') }}</el-link>
|
||||
</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('common.Upload' )" :label-width="'100px'">
|
||||
<el-upload
|
||||
ref="upload"
|
||||
action="string"
|
||||
:http-request="handleImport"
|
||||
list-type="text/csv"
|
||||
:limit="1"
|
||||
:auto-upload="false"
|
||||
:before-upload="beforeUpload"
|
||||
>
|
||||
<el-button size="mini" type="default">{{ this.$t('common.SelectFile') }}</el-button>
|
||||
<div slot="tip" :class="uploadHelpTextClass" style="line-height: 1.5">{{ this.$t('common.imExport.onlyCSVFilesTips') }}</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div v-if="errorMsg" class="error-msg error-results">
|
||||
<ul v-if="typeof errorMsg === 'object'">
|
||||
<li v-for="(item, index) in errorMsg" :key="item + '-' + index"> {{ item }}</li>
|
||||
</ul>
|
||||
<span v-else>{{ errorMsg }}</span>
|
||||
</div>
|
||||
</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 { createSourceIdCache } from '@/api/common'
|
||||
import * as queryUtil from '@/components/DataTable/compenents/el-data-table/utils/query'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'VaultList',
|
||||
components: {
|
||||
GenericTreeListPage,
|
||||
AssetUserTable,
|
||||
Dialog
|
||||
},
|
||||
data() {
|
||||
const vm = this
|
||||
return {
|
||||
showImportDialog: false,
|
||||
importOption: '1',
|
||||
isCsv: true,
|
||||
errorMsg: '',
|
||||
showExportDialog: false,
|
||||
exportOption: '1',
|
||||
meta: {},
|
||||
MfaExpired: 0,
|
||||
showMFADialog: false,
|
||||
MFAInput: '',
|
||||
selectedRows: '',
|
||||
assetUserConfig: {
|
||||
hasLeftActions: true,
|
||||
hasCreate: true,
|
||||
hasClone: false,
|
||||
url: '/api/v1/assets/asset-users/',
|
||||
handleImport: function({ selectedRows }) {
|
||||
this.selectedRows = selectedRows
|
||||
this.dialogStatus = 'import'
|
||||
if (!this.needMFAVerify) {
|
||||
this.showMFADialog = false
|
||||
this.showImportDialog = true
|
||||
} else {
|
||||
this.showMFADialog = true
|
||||
}
|
||||
}.bind(this),
|
||||
handleExport: function({ selectedRows }) {
|
||||
this.selectedRows = selectedRows
|
||||
this.dialogStatus = 'export'
|
||||
if (!this.needMFAVerify) {
|
||||
this.showMFADialog = false
|
||||
this.showExportDialog = true
|
||||
} else {
|
||||
this.showMFADialog = true
|
||||
}
|
||||
}.bind(this)
|
||||
},
|
||||
treeSetting: {
|
||||
showMenu: false,
|
||||
showRefresh: false,
|
||||
showAssets: true,
|
||||
url: '/api/v1/assets/asset-users/',
|
||||
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.node.id
|
||||
url = setUrlParam(url, 'asset_id', '')
|
||||
url = setUrlParam(url, 'node_id', nodeId)
|
||||
} else if (treeNode.meta.type === 'asset') {
|
||||
const assetId = treeNode.meta.asset.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
|
||||
},
|
||||
upLoadUrl() {
|
||||
return this.url
|
||||
},
|
||||
downloadImportTempUrl() {
|
||||
const baseUrl = `/api/v1/assets/asset-user-auth-infos/`
|
||||
return baseUrl + '?format=csv&template=import&limit=1'
|
||||
},
|
||||
uploadHelpTextClass() {
|
||||
const cls = ['el-upload__tip']
|
||||
if (!this.isCsv) {
|
||||
cls.push('error-msg')
|
||||
}
|
||||
return cls
|
||||
},
|
||||
...mapGetters([
|
||||
'MFAVerifyAt',
|
||||
'MFA_TTl'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
performUpdate(item) {
|
||||
this.$axios.put(
|
||||
`/api/v1/assets/asset-users/`,
|
||||
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/asset-users/`,
|
||||
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
|
||||
}
|
||||
},
|
||||
onSuccess(msg) {
|
||||
this.errorMsg = ''
|
||||
this.$message.success(msg)
|
||||
},
|
||||
handleImport(item) {
|
||||
if (this.importOption === '1') {
|
||||
this.performCreate(item)
|
||||
} else {
|
||||
this.performUpdate(item)
|
||||
}
|
||||
},
|
||||
async downloadUpdateTempUrl() {
|
||||
var resources = []
|
||||
const data = this.selectedRows
|
||||
for (let index = 0; index < data.length; index++) {
|
||||
resources.push(data[index].id)
|
||||
}
|
||||
const spm = await createSourceIdCache(resources)
|
||||
const baseUrl = `/api/v1/assets/asset-user-auth-infos/`
|
||||
const url = `${baseUrl}?format=csv&template=update&spm=` + spm.spm
|
||||
return this.downloadCsv(url)
|
||||
},
|
||||
async handleImportConfirm() {
|
||||
this.$refs.upload.submit()
|
||||
this.showImportDialog = false
|
||||
},
|
||||
handleImportCancel() {
|
||||
this.showImportDialog = false
|
||||
},
|
||||
beforeUpload(file) {
|
||||
this.isCsv = _.endsWith(file.name, 'csv')
|
||||
return this.isCsv
|
||||
},
|
||||
downloadCsv(url) {
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
},
|
||||
async handleExport() {
|
||||
const url = `/api/v1/assets/asset-user-auth-infos/`
|
||||
let query = {}
|
||||
if (this.exportOption === '2') {
|
||||
const resources = []
|
||||
const data = this.selectedRows
|
||||
for (let index = 0; index < data.length; index++) {
|
||||
resources.push(data[index].id)
|
||||
}
|
||||
const spm = await createSourceIdCache(resources)
|
||||
query['spm'] = spm.spm
|
||||
} else if (this.exportOption === '3') {
|
||||
const listTableRef = this.$parent.$parent.$parent.$parent
|
||||
// console.log(listTableRef)
|
||||
// console.log(listTableRef.dataTable)
|
||||
query = listTableRef.dataTable.getQuery()
|
||||
delete query['limit']
|
||||
delete query['offset']
|
||||
}
|
||||
query['format'] = 'csv'
|
||||
const queryStr =
|
||||
(url.indexOf('?') > -1 ? '&' : '?') +
|
||||
queryUtil.stringify(query, '=', '&')
|
||||
return this.downloadCsv(url + queryStr)
|
||||
},
|
||||
async handleExportConfirm() {
|
||||
await this.handleExport()
|
||||
this.showExportDialog = false
|
||||
},
|
||||
handleExportCancel() {
|
||||
this.showExportDialog = false
|
||||
},
|
||||
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>
|
@ -10,47 +10,6 @@ export default {
|
||||
name: 'Xpack',
|
||||
meta: { title: 'X-Pack', icon: 'sitemap', licenseRequired: true },
|
||||
children: [
|
||||
{
|
||||
path: 'change-auth-plan',
|
||||
component: empty,
|
||||
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlan'), activeMenu: '/xpack/change-auth-plan/plan' },
|
||||
children: [
|
||||
{
|
||||
path: 'plan',
|
||||
component: () => import('@/views/xpack/ChangeAuthPlan/ChangeAuthPlanList.vue'),
|
||||
name: 'ChangeAuthPlanList',
|
||||
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlan'), activeMenu: '/xpack/change-auth-plan/plan' }
|
||||
},
|
||||
{
|
||||
path: 'plan/create',
|
||||
component: () => import('@/views/xpack/ChangeAuthPlan/ChangeAuthPlanCreateUpdate.vue'),
|
||||
name: 'ChangeAuthPlanCreate',
|
||||
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlanCreate'), activeMenu: '/xpack/change-auth-plan/plan', action: 'create' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'plan/:id/update',
|
||||
component: () => import('@/views/xpack/ChangeAuthPlan/ChangeAuthPlanCreateUpdate.vue'),
|
||||
name: 'ChangeAuthPlanUpdate',
|
||||
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlanUpdate'), activeMenu: '/xpack/change-auth-plan/plan', action: 'update' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'plan/:id',
|
||||
component: () => import('@/views/xpack/ChangeAuthPlan/ChangeAuthPlanDetail/index.vue'),
|
||||
name: 'ChangeAuthPlanDetail',
|
||||
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlan'), activeMenu: '/xpack/change-auth-plan/plan' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'plan-execution/:id',
|
||||
component: () => import('@/views/xpack/ChangeAuthPlan/ChangeAuthPlanDetail/ChangeAuthPlanExecution/ChangeAuthPlanExecutionDetail/index.vue'),
|
||||
name: 'ChangeAuthPlanExecutionDetail',
|
||||
meta: { title: i18n.t('xpack.ChangeAuthPlan.ExecutionDetail'), activeMenu: '/xpack/change-auth-plan/plan' },
|
||||
hidden: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'cloud',
|
||||
component: empty,
|
||||
@ -127,55 +86,6 @@ export default {
|
||||
name: 'InterfaceSetting',
|
||||
meta: { title: i18n.t('xpack.InterfaceSettings'), permissions: [rolec.PERM_SUPER] }
|
||||
},
|
||||
{
|
||||
path: 'gathered-user',
|
||||
component: empty,
|
||||
redirect: '',
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUserList') },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/xpack/GatheredUser/index'),
|
||||
name: 'GatherUserListIndex',
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUser'), activeMenu: '/xpack/gathered-user' }
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/xpack/GatheredUser/GatheredUserList'),
|
||||
name: 'GatherUserList',
|
||||
hidden: true,
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUserList'), activeMenu: '/xpack/gathered-user' }
|
||||
},
|
||||
{
|
||||
path: 'tasks',
|
||||
component: () => import('@/views/xpack/GatheredUser/TaskList'),
|
||||
name: 'GatherUserTaskList',
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUserTaskList'), activeMenu: '/xpack/gathered-user' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'tasks/:id',
|
||||
component: () => import('@/views/xpack/GatheredUser/TaskDetail/index'),
|
||||
name: 'GatherUserTaskDetail',
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUserTaskDetail'), activeMenu: '/xpack/gathered-user' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'tasks/create',
|
||||
component: () => import('@/views/xpack/GatheredUser/TaskCreateUpdate'),
|
||||
name: 'GatherUserTaskCreate',
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUserTaskCreate'), activeMenu: '/xpack/gathered-user' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'tasks/:id/update',
|
||||
component: () => import('@/views/xpack/GatheredUser/TaskCreateUpdate'),
|
||||
name: 'GatherUserTaskUpdate',
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUserTaskUpdate'), action: 'update', activeMenu: '/xpack/gathered-user' },
|
||||
hidden: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'orgs',
|
||||
component: empty,
|
||||
@ -211,27 +121,6 @@ export default {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'vault',
|
||||
component: empty,
|
||||
redirect: '',
|
||||
meta: { },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/xpack/Vault/VaultList.vue'),
|
||||
name: 'VaultList',
|
||||
meta: { title: i18n.t('xpack.Vault.Vault'), activeMenu: '/xpack/vault' }
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: () => import('@/views/xpack/Vault/VaultCreate'),
|
||||
name: 'VaultCreate',
|
||||
meta: { title: i18n.t('xpack.Vault.Create'), activeMenu: '/xpack/vault' },
|
||||
hidden: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'system-monitor',
|
||||
component: () => import('@/views/xpack/SystemMonitor/index.vue'),
|
||||
|
@ -4,12 +4,11 @@ const defaultSettings = require('./src/settings.js')
|
||||
const CompressionWebpackPlugin = require('compression-webpack-plugin')
|
||||
const productionGzipExtensions = /\.(js|css|json|txt|ico|svg)(\?.*)?$/i
|
||||
|
||||
|
||||
function resolve(dir) {
|
||||
return path.join(__dirname, dir)
|
||||
}
|
||||
|
||||
const name = defaultSettings.title || 'JumpServer' // page title
|
||||
const name = '' // page title
|
||||
|
||||
// If your port is set to 80,
|
||||
// use administrator privileges to execute the command line.
|
||||
|
68
yarn.lock
68
yarn.lock
@ -2357,13 +2357,15 @@ browserify-zlib@^0.2.0:
|
||||
pako "~1.0.5"
|
||||
|
||||
browserslist@^4.0.0, browserslist@^4.3.4, browserslist@^4.5.4, browserslist@^4.8.3:
|
||||
version "4.10.0"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.10.0.tgz#f179737913eaf0d2b98e4926ac1ca6a15cbcc6a9"
|
||||
version "4.16.6"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2"
|
||||
integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==
|
||||
dependencies:
|
||||
caniuse-lite "^1.0.30001035"
|
||||
electron-to-chromium "^1.3.378"
|
||||
node-releases "^1.1.52"
|
||||
pkg-up "^3.1.0"
|
||||
caniuse-lite "^1.0.30001219"
|
||||
colorette "^1.2.2"
|
||||
electron-to-chromium "^1.3.723"
|
||||
escalade "^3.1.1"
|
||||
node-releases "^1.1.71"
|
||||
|
||||
bser@2.1.1:
|
||||
version "2.1.1"
|
||||
@ -2556,9 +2558,10 @@ caniuse-api@^3.0.0:
|
||||
lodash.memoize "^4.1.2"
|
||||
lodash.uniq "^4.5.0"
|
||||
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001035:
|
||||
version "1.0.30001035"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001035.tgz#2bb53b8aa4716b2ed08e088d4dc816a5fe089a1e"
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001219:
|
||||
version "1.0.30001230"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz#8135c57459854b2240b57a4a6786044bdc5a9f71"
|
||||
integrity sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ==
|
||||
|
||||
capture-exit@^1.2.0:
|
||||
version "1.2.0"
|
||||
@ -2874,6 +2877,11 @@ color@^3.0.0:
|
||||
color-convert "^1.9.1"
|
||||
color-string "^1.5.2"
|
||||
|
||||
colorette@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
|
||||
integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
|
||||
|
||||
colors@^1.1.2:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npm.taobao.org/colors/download/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
|
||||
@ -3605,8 +3613,9 @@ dns-equal@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
|
||||
|
||||
dns-packet@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a"
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.4.tgz#e3455065824a2507ba886c55a89963bb107dec6f"
|
||||
integrity sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==
|
||||
dependencies:
|
||||
ip "^1.1.0"
|
||||
safe-buffer "^5.0.1"
|
||||
@ -3758,9 +3767,10 @@ ejs@^2.6.1:
|
||||
version "2.7.4"
|
||||
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba"
|
||||
|
||||
electron-to-chromium@^1.3.378:
|
||||
version "1.3.379"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.379.tgz#81dc5e82a3e72bbb830d93e15bc35eda2bbc910e"
|
||||
electron-to-chromium@^1.3.723:
|
||||
version "1.3.739"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.739.tgz#f07756aa92cabd5a6eec6f491525a64fe62f98b9"
|
||||
integrity sha512-+LPJVRsN7hGZ9EIUUiWCpO7l4E3qBYHNadazlucBfsXBbccDFNKUBAgzE68FnkWGJPwD/AfKhSzL+G+Iqb8A4A==
|
||||
|
||||
elegant-spinner@^1.0.1:
|
||||
version "1.0.1"
|
||||
@ -3903,6 +3913,11 @@ es6-object-assign@1.1.0:
|
||||
resolved "https://registry.npm.taobao.org/es6-object-assign/download/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c"
|
||||
integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=
|
||||
|
||||
escalade@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
|
||||
|
||||
escape-html@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||
@ -7324,6 +7339,11 @@ moment@^2.19.2:
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.26.0.tgz#5e1f82c6bafca6e83e808b30c8705eed0dcbd39a"
|
||||
integrity sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw==
|
||||
|
||||
moment@^2.29.1:
|
||||
version "2.29.1"
|
||||
resolved "https://registry.npm.taobao.org/moment/download/moment-2.29.1.tgz?cache=0&sync_timestamp=1601983320283&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmoment%2Fdownload%2Fmoment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
||||
integrity sha1-sr52n6MZQL6e7qZGnAdeNQBvo9M=
|
||||
|
||||
move-concurrently@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
|
||||
@ -7495,11 +7515,10 @@ node-notifier@^5.2.1:
|
||||
shellwords "^0.1.1"
|
||||
which "^1.3.0"
|
||||
|
||||
node-releases@^1.1.52:
|
||||
version "1.1.52"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.52.tgz#bcffee3e0a758e92e44ecfaecd0a47554b0bcba9"
|
||||
dependencies:
|
||||
semver "^6.3.0"
|
||||
node-releases@^1.1.71:
|
||||
version "1.1.72"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe"
|
||||
integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==
|
||||
|
||||
nopt@^5.0.0:
|
||||
version "5.0.0"
|
||||
@ -8307,12 +8326,6 @@ pkg-up@^2.0.0:
|
||||
dependencies:
|
||||
find-up "^2.1.0"
|
||||
|
||||
pkg-up@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5"
|
||||
dependencies:
|
||||
find-up "^3.0.0"
|
||||
|
||||
please-upgrade-node@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
|
||||
@ -9372,8 +9385,9 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
|
||||
safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
||||
safe-regex@^1.1.0:
|
||||
version "1.1.0"
|
||||
|
Loading…
Reference in New Issue
Block a user