Compare commits

..

114 Commits

Author SHA1 Message Date
“huailei000”
ee55cebf0e fix: 修复临时密码提示翻译 2022-04-29 10:53:55 +08:00
ibuler
2e5096f386 perf: 修改 build 2022-04-29 10:53:55 +08:00
feng626
bf57ee8910 fix: 开放platform 权限 2022-04-29 10:38:44 +08:00
feng626
f86f08f2b6 Merge pull request #1713 from jumpserver/pr@v2.21@workbench_orgs
fix: myorgs -> workbench_orgs
2022-04-25 11:36:57 +08:00
feng626
6eb429823d fix: myorgs -> workbench_orgs 2022-04-25 11:34:37 +08:00
feng626
d6be84026c Merge pull request #1711 from jumpserver/pr@v2.21@fix_temporary_password_text
fix: 修改临时密码提示文案
2022-04-25 11:05:26 +08:00
“huailei000”
4a7680ec7a fix: 修改临时密码提示文案 2022-04-25 03:00:15 +00:00
Jiangjie.Bai
4fd8956b0d Merge pull request #1707 from jumpserver/dev
v2.21.0
2022-04-21 18:10:49 +08:00
ibuler
9d75e2e6fd perf: 修改 组织 2022-04-21 17:38:17 +08:00
ibuler
6317400da8 perf: 去掉 reload 2022-04-21 17:38:17 +08:00
fit2bot
7f963e9991 perf: 修改 view 切换 (#1705)
* perf: 修改 view 切换

* fix: 修改 view 跳转 bug

* perf:去掉 reload

Co-authored-by: ibuler <ibuler@qq.com>
2022-04-21 17:23:58 +08:00
“huailei000”
8a731937cf fix: 修复用户列表批量更新弹窗组件不重新渲染问题 2022-04-21 17:01:57 +08:00
fit2bot
a593da7fea perf: 修改 cookie get (#1703)
Co-authored-by: ibuler <ibuler@qq.com>
2022-04-21 15:52:39 +08:00
fit2bot
b9547214b7 perf: view switch 默认值 (#1702)
* perf: view switch 默认值

* perf: 去掉冗余

Co-authored-by: ibuler <ibuler@qq.com>
2022-04-21 13:59:58 +08:00
fit2bot
0e7b60a001 fix: 切换视图的问题,修复组织切换的问题 (#1701)
* fix: 切换视图的问题,修复组织切换的问题

* perf: 优化写法

Co-authored-by: ibuler <ibuler@qq.com>
2022-04-21 11:07:56 +08:00
Jiangjie.Bai
20f84db466 Merge pull request #1700 from jumpserver/dev
v2.21.0-rc6
2022-04-20 20:26:24 +08:00
“huailei000”
c5495d9846 fix: 修复会话录像时间1秒显示问题 2022-04-20 20:22:18 +08:00
ibuler
332cd2c073 perf: 修改 org 2022-04-20 19:54:52 +08:00
“huailei000”
2b4d827789 fix: dashboard页面在线用户文案修改为 连接用户 2022-04-20 19:54:29 +08:00
“huailei000”
92144e4f69 fix: 修复移动端搜索框不对齐问题 2022-04-20 17:58:43 +08:00
“huailei000”
41d42c04a6 fix: 修复用户列表批量移除权限位 2022-04-20 17:58:11 +08:00
“huailei000”
f4f42639aa fix: 导入弹框权限根据路由自动判断 2022-04-20 17:57:45 +08:00
“huailei000”
a37af9ed7d fix: 修复命令记录跳转去权限 2022-04-20 16:49:30 +08:00
Jiangjie.Bai
1f0fe18533 fix: 修改系统设置-终端设置创建权限控制 2022-04-20 16:49:01 +08:00
feng626
bc21d7ac17 Merge pull request #1694 from jumpserver/pr@dev@add_orgrolebinding
fix: 修复角色添加用户权限问题
2022-04-20 15:48:35 +08:00
feng626
922ecbf096 fix: 修复角色添加用户权限问题 2022-04-20 15:47:23 +08:00
fit2bot
a162a206bc perf: 修改 tickets orgs (#1691)
* perf: 修改 tickets orgs

* perf: 修改 orgs 切换

Co-authored-by: ibuler <ibuler@qq.com>
2022-04-20 15:27:04 +08:00
fit2bot
12a85da6f7 fix: 修复点击资产授权树勾选框报错的问题 (#1687)
* fix: 修复点击资产授权树勾选狂报错的问题

* fix: 修复点击资产授权树勾选狂报错的问题

Co-authored-by: Jiangjie.Bai <bugatti_it@163.com>
2022-04-20 11:32:25 +08:00
Jiangjie.Bai
585f5e04d6 Merge pull request #1686 from jumpserver/dev
v2.21.0-rc5
2022-04-19 21:59:27 +08:00
feng626
6db90fce09 perf: add org remind 2022-04-19 21:56:18 +08:00
Jiangjie.Bai
69e392b069 fix: 请求设置默认org_id 2022-04-19 20:23:57 +08:00
“huailei000”
e5b2bd7008 fix: 修复会话详情页button 按钮权限位控制 2022-04-19 20:19:00 +08:00
Jiangjie.Bai
c6aaaaf1fd perf: 优化临时密码route控制 2022-04-19 20:18:37 +08:00
“huailei000”
1ab6ed6109 fix: 修复用户详情页按钮权限 2022-04-19 20:18:08 +08:00
“huailei000”
c17d983b2f fix: 修复应用改密计划详情页执行列表操作权限 2022-04-19 20:17:38 +08:00
Jiangjie.Bai
484fb63577 fix: 修复ldap同步interval必填 2022-04-19 18:29:28 +08:00
Jiangjie.Bai
2c8d8c6e66 fix: 修复ldap同步interval必填 2022-04-19 18:29:28 +08:00
Jiangjie.Bai
5577051416 fix: 修改日期搜索默认8天内 2022-04-19 17:45:13 +08:00
“huailei000”
cd51596d45 fix: 恢复资产列表展示字段 2022-04-19 16:18:15 +08:00
Jiangjie.Bai
dd5b805da9 feat: 添加配置项 KoKo SSH Client 方式 2022-04-19 16:17:09 +08:00
ibuler
54097f07be perf: 修改 view orgs 2022-04-19 13:21:22 +08:00
老广
ef5c1b2e23 Merge pull request #1674 from jumpserver/dev
v2.21.0-rc5
2022-04-19 13:15:38 +08:00
ibuler
f104376e92 perf: 优化一些写法 2022-04-19 10:20:41 +08:00
fit2bot
62ed11e7ec perf: 修改用户所属的 org (#1662)
* perf: 修改用户所属的 org

* perf: 修改组织问题

* perf: 修改优化 nav 切换

* perf: 修改 org id

* perf: 修改验证逻辑

* perf: 修改 orgs

Co-authored-by: ibuler <ibuler@qq.com>
2022-04-18 20:20:32 +08:00
“huailei000”
3fd8e23c2c fix: 修改站内信文案为全部已读;调整布局遮挡问题 2022-04-18 16:48:41 +08:00
Jiangjie.Bai
a93ecac15c fix: 修改云任务执行权限控制 2022-04-18 16:05:33 +08:00
Jiangjie.Bai
1fb75fbce0 Merge pull request #1668 from jumpserver/dev
v2.21.0-rc4
2022-04-18 15:34:13 +08:00
“huailei000”
1d594fc594 fix: 修复dialog组件不能关闭问题 2022-04-18 15:33:12 +08:00
“huailei000”
a4abe63ec9 fix: 修复资产列表显示空白字段问题 2022-04-18 15:32:23 +08:00
“huailei000”
4262410038 fix: 内部的message通知使用v-html显示 2022-04-18 15:31:34 +08:00
Jiangjie.Bai
2735d638a2 Merge pull request #1664 from jumpserver/dev
v2.21.0-rc3
2022-04-18 11:44:56 +08:00
Jiangjie.Bai
f9bade6a7c feat: 控制临时密码菜单 2022-04-18 11:32:42 +08:00
Jiangjie.Bai
eaf3c5b013 feat: 设置SessionCookieNamePrefix 2022-04-15 21:14:48 +08:00
“huailei000”
2c2e7d10f9 feat: 增加列表查看、复制组件 2022-04-15 20:06:51 +08:00
Jiangjie.Bai
9243a6699c fix: 系统监控添加Magnus 2022-04-15 16:40:08 +08:00
Jiangjie.Bai
e9a2818580 Merge pull request #1656 from jumpserver/dev
v2.21.0-rc2
2022-04-14 18:40:55 +08:00
feng626
4b318c0aaf Merge pull request #1655 from jumpserver/pr@dev@site_msg
feat: 站内信一键已读
2022-04-14 12:18:29 +08:00
feng626
e9211205cb feat: 站内信一键已读 2022-04-14 11:59:36 +08:00
Jiangjie.Bai
e14a31f31c Merge pull request #1654 from jumpserver/dev
v2.21.0-rc1
2022-04-13 20:31:57 +08:00
jiangweidong
b85db6b73f feat: temp password 2022-04-13 20:09:45 +08:00
jiangweidong
6d8dc058cb feat: 支持临时密码 (one-time-password) 登录 2022-04-13 20:09:45 +08:00
jiangweidong
2b3090b44c feat: 支持资产授权和应用授权的批量更新 2022-04-13 16:53:56 +08:00
“huailei000”
f08b3708d4 fix: 修复更多按钮样式 2022-04-13 14:48:58 +08:00
fit2bot
a8a7538655 feat: 添加Endpoint (#1651)
* feat: 添加Endpoint页面

* feat: 添加Endpoint Rule页面

* feat: 获取connect-url连接地址

* feat: 删除配置KOKO、XRDP、MAGNUS

* feat: 修改翻译

* feat: 修改默认endpoint不能删除

Co-authored-by: Jiangjie.Bai <bugatti_it@163.com>
2022-04-12 19:28:42 +08:00
fit2bot
364f2aaf12 perf: 修改翻译 (#1650)
* perf: 修改 workspace

* perf: 修改翻译

Co-authored-by: ibuler <ibuler@qq.com>
Co-authored-by: Jiangjie.Bai <32935519+BaiJiangJie@users.noreply.github.com>
2022-04-12 13:52:21 +08:00
“huailei000”
dcb87c6216 perf: 调整table列表-操作内容居中显示 2022-04-11 17:06:57 +08:00
“huailei000”
06bd68a874 fix: 概览页显示模块位置调整,增加公告显示;修复概览页跳转权限判断 2022-04-11 15:00:53 +08:00
feng626
4a2b663220 fix: 修复消息订阅无法更新bug 2022-04-08 16:05:34 +08:00
“huailei000”
59342b65c8 fix: 修复终端管理-批量更新弹窗不显示label问题 2022-04-08 15:14:08 +08:00
jiangweidong
4edd9097ab feat: 云同步支持京东云 2022-04-07 11:28:41 +08:00
“huailei000”
6e10f4ccc1 fix: 修复公告对齐展示 2022-04-07 11:25:26 +08:00
“huailei000”
9510dec9e0 fix: 在线会话-命令单行文本省略展示 2022-04-07 11:25:01 +08:00
feng626
31bb679a8e feat: ldap一键导入及设置用户组织 2022-04-06 17:51:31 +08:00
feng626
357b67ffe0 fix: 修改系统用户类型 2022-04-06 14:25:22 +08:00
“huailei000”
07036cdea7 feat: i18n获取浏览器默认语言 2022-04-01 16:28:58 +08:00
一路向北
ec2674a956 Update README.md 2022-04-01 15:24:09 +08:00
“huailei000”
e5791c184d feat: 公告支持换行显示 2022-04-01 15:23:38 +08:00
feng626
bcd0384baa Merge pull request #1622 from jumpserver/pr@dev@koko_setting
feat: koko setting
2022-03-30 19:52:24 +08:00
feng626
461a385596 feat: koko setting 2022-03-30 19:51:05 +08:00
ibuler
0c9440d835 perf: terminal icon 2022-03-30 11:27:01 +08:00
ibuler
9f7c145065 perf: 修改lang 2022-03-29 19:58:33 +08:00
Jiangjie.Bai
05d66c4c4f feat: 改密计划支持su切换用户执行 2022-03-29 16:42:39 +08:00
“huailei000”
c1498d4de5 fix: 修复数据库-创建类型 2022-03-29 14:54:32 +08:00
jiangweidong
34f17beb3c feat: 支持日语 (#1612) 2022-03-28 10:08:53 +08:00
halo
c6f39c79fa perf: 优化长命令记录页面自动换行 2022-03-25 19:40:04 +08:00
ibuler
ae5005ceef feat: 修改 app type 2022-03-25 19:39:29 +08:00
Jiangjie.Bai
dad107d768 Revert "perf:: 修复empty组件为函数式组件"
This reverts commit 8ae14bfb37.
2022-03-25 17:04:25 +08:00
“huailei000”
021dbe6e2e fix:修复资产列表-从节点移除 权限位不准确问题 2022-03-25 14:43:30 +08:00
“huailei000”
8ae14bfb37 perf:: 修复empty组件为函数式组件 2022-03-25 14:42:49 +08:00
“huailei000”
ea2c3f1a11 fix: 修复工单详情detail组件布局问题 2022-03-25 14:38:00 +08:00
“huailei000”
dadeea6d1e perf: 优化日期组件显示遮挡问题 2022-03-22 16:59:21 +08:00
feng626
05e5bcc10d fix: user org perm 2022-03-22 16:58:56 +08:00
ibuler
87d211563f perf: 优化 mongo db 2022-03-22 11:31:06 +08:00
ibuler
6b7f868538 feat: mongodb 支持 2022-03-22 11:31:06 +08:00
ibuler
d3c2d0e7e8 perf: 优化名称显示 2022-03-22 10:52:51 +08:00
“huailei000”
088dbc4e42 fix: 修复系统用户-资产列表按钮权限不准确问题 2022-03-21 19:24:37 +08:00
jiangweidong
18016ffdba feat: 支持百度云资产同步 2022-03-21 19:23:56 +08:00
feng626
b57e65a100 Merge pull request #1599 from jumpserver/pr@dev@asset_user_perm
fix: 修复资产详情中授权用户可查看用户权限
2022-03-21 11:16:49 +08:00
feng626
8852924d45 fix: 修复资产详情中授权用户可查看用户权限 2022-03-21 10:55:27 +08:00
“huailei000”
da1f322e1c fix: 修复apikey权限位问题 2022-03-18 18:16:51 +08:00
“huailei000”
b22b01529a fix: 修复2个tab只给其中一个tab权限接口提示报错问题 2022-03-18 17:15:43 +08:00
Jiangjie.Bai
26ba2c091e fix: 从节点移除资产权限位修改 2022-03-18 17:10:58 +08:00
Jiangjie.Bai
23ee181c63 fix: 修复资产详情系统用户页面权限控制 2022-03-18 16:53:49 +08:00
Jiangjie.Bai
3aca8cdffd fix: 修复工单中会话卡片按钮的权限控制 2022-03-18 16:41:20 +08:00
“huailei000”
b5cd17485e fix: 修复工作台-待我审批权限位问题 2022-03-18 15:11:29 +08:00
Jiangjie.Bai
4db98964dd fix: 修复角色更新、删除权限控制 2022-03-18 14:52:21 +08:00
“huailei000”
3a1870cae1 fix: 修复dashboard页权限位 2022-03-18 12:46:40 +08:00
“huailei000”
c36cc7b15f fix: 修复css样式文件报错 2022-03-18 11:31:06 +08:00
“huailei000”
07b69ab9a8 fix: 修复tab组件没有权限页卡不显示时还会提示接口403问题 2022-03-18 11:22:34 +08:00
fit2bot
222eb4fcb0 perf: 优化详情中各种 box (#1582)
* perf: 优化移动到表单

* perf: 优化详情中各种 box

Co-authored-by: ibuler <ibuler@qq.com>
2022-03-18 11:21:25 +08:00
fit2bot
632dc064d9 perf: 优化手机端 view 切换 (#1583)
* perf: 优化手机端 view 切换

* perf: 优化table 移动端适配

* perf: 优化 pagger overflow

Co-authored-by: ibuler <ibuler@qq.com>
2022-03-18 11:20:18 +08:00
ibuler
cc513c96f5 perf: 优化 form label 位置 2022-03-17 20:31:07 +08:00
ibuler
b3752ba867 perf: 优化详情中btn 2022-03-17 19:52:20 +08:00
ibuler
f18e46ba22 perf: 优化移动端显示 2022-03-17 19:47:16 +08:00
146 changed files with 3232 additions and 695 deletions

View File

@@ -1,6 +1,4 @@
FROM node:10 as stage-build
ARG VERSION
ENV VERSION=$VERSION
ARG NPM_REGISTRY="https://registry.npm.taobao.org"
ENV NPM_REGISTY=$NPM_REGISTRY
ARG SASS_BINARY_SITE="https://npm.taobao.org/mirrors/node-sass"
@@ -15,6 +13,8 @@ COPY package.json yarn.lock /data/
RUN yarn install
RUN npm rebuild node-sass
ARG VERSION
ENV VERSION=$VERSION
ADD . /data
RUN cd utils && bash -xieu build.sh build

View File

@@ -19,7 +19,7 @@ VUE_APP_CORE_HOST = 'JUMPSERVER_APIHOST'
$ yarn serve
4. 构建
$ yarn build
$ yarn build:prod
```
## 生产中部署

View File

@@ -1,4 +1,3 @@
const tokens = {
admin: {
token: 'admin-token'
@@ -46,7 +45,6 @@ export default [
}
}
},
// get user info
{
url: '/vue-admin-template/user/info\.*',

View File

@@ -13,3 +13,9 @@ export function getCurrentOrg() {
method: 'get'
})
}
export default {
getCurrentOrg,
getOrgDetail
}

View File

@@ -69,3 +69,8 @@ export function logout() {
export function refreshSessionIdAge() {
return getProfile()
}
export default {
getProfile,
getUserList
}

View File

@@ -7,7 +7,7 @@
:title="title"
@close="onClose"
>
<span class="announcement-main"> {{ announcement.content }}</span>
<span class="announcement-main">{{ announcement.content }}</span>
<span v-if="announcement.link">
<el-link :href="announcement.link" target="_blank" class="link-more">
{{ $t('common.ViewMore') }}
@@ -60,6 +60,7 @@ export default {
}
.announcement-main {
word-wrap:break-word;
white-space: pre-wrap;
}
.link-more {
font-size: 10px;

View File

@@ -1,5 +1,5 @@
<template>
<div :class="grouped ? 'el-button-group' : 'el-button-ungroup'" style="display: flex">
<div :class="grouped ? 'el-button-group' : 'el-button-ungroup'" class="layout">
<template v-for="action in iActions">
<el-dropdown
v-if="action.dropdown"
@@ -15,7 +15,12 @@
</el-button>
<el-dropdown-menu slot="dropdown" style="overflow: auto;max-height: 60vh">
<template v-for="option in action.dropdown">
<div v-if="option.group" :key="'group:'+option.name" class="dropdown-menu-title" style="width:130px">
<div
v-if="option.group"
:key="'group:'+option.name"
class="dropdown-menu-title"
style="width:130px"
>
{{ option.group }}
</div>
<el-dropdown-item
@@ -149,6 +154,11 @@ export default {
</script>
<style scoped>
.layout {
display: flex;
justify-content: center;
}
.dropdown-menu-title {
text-align: left;
font-size: 12px;
@@ -165,7 +175,7 @@ export default {
}
.el-button-ungroup .action-item {
margin-left: 4px
margin-left: 4px;
}
.el-button-ungroup .action-item:first-child {

View File

@@ -2,9 +2,11 @@
<el-dialog
:title="title"
:top="top"
:width="iWidth"
class="dialog"
v-bind="$attrs"
append-to-body
:append-to-body="false"
:modal-append-to-body="false"
v-on="$listeners"
>
<slot />
@@ -39,6 +41,10 @@ export default {
type: String,
default: '3vh'
},
width: {
type: String,
default: '60%'
},
showConfirm: {
type: Boolean,
default: true
@@ -58,6 +64,11 @@ export default {
return {
}
},
computed: {
iWidth() {
return this.$store.getters.isMobile ? '80%' : this.width
}
},
methods: {
onCancel() {
this.$emit('cancel')

View File

@@ -91,18 +91,22 @@ export default {
}
</script>
<style lang='less' scoped>
<style lang='scss' scoped>
.datepicker{
width: 233px;
&>>> .el-range__icon {
margin-top: 2px;
margin-right: 3px;
}
&>>> .el-range-input {
width: 49%;
}
}
.el-input__inner{
border: 1px solid #dcdee2;
border-radius: 3px;
height: 36px;
}
/*.el-date-editor ::v-deep .el-input__icon{*/
/* line-height: 28px;*/
/*}*/
.el-date-editor ::v-deep .el-range-separator{
line-height: 28px;
}

View File

@@ -92,11 +92,11 @@ export default {
return this.selectedRows.length > 0
},
tableQuery() {
const listTableRef = this.$parent.$parent.$parent.$parent
const listTableRef = this.$parent?.$parent?.$parent?.$parent
if (!listTableRef) {
return {}
}
const query = listTableRef.dataTable.getQuery()
const query = listTableRef?.dataTable?.getQuery() || {}
delete query['limit']
delete query['offset']
delete query['date_from']
@@ -199,8 +199,11 @@ export default {
this.mfaDialogShow = false
},
handleExportCancel() {
this.exportDialogShow = false
this.mfaDialogShow = false
const vm = this
setTimeout(() => {
vm.exportDialogShow = false
vm.mfaDialogShow = false
}, 100)
}
}
}

View File

@@ -84,17 +84,17 @@ export default {
},
canImportCreate: {
type: Boolean,
default: true
default: false
},
canImportUpdate: {
type: Boolean,
default: true
default: false
}
},
data() {
return {
showImportDialog: false,
importOption: 'create',
importOption: this.canImportCreate && this.canImportUpdate ? 'create' : this.canImportCreate ? 'create' : 'update',
errorMsg: '',
loadStatus: false,
importTypeOption: 'csv',

View File

@@ -1,7 +1,7 @@
<template>
<div>
<el-row>
<el-col :span="8">
<el-col :md="8" :sm="24">
<div class="tableFilter">
<el-radio-group v-model="importStatusFilter" size="small">
<el-radio-button label="all">{{ $t('common.Total') }}</el-radio-button>
@@ -11,7 +11,7 @@
</el-radio-group>
</div>
</el-col>
<el-col :span="8" style="text-align: center">
<el-col :md="8" :sm="24" style="text-align: center">
<span class="summary-item summary-total"> {{ $t('common.Total') }}: {{ totalCount }}</span>
<span class="summary-item summary-success"> {{ $t('common.Success') }}: {{ successCount }}</span>
<span class="summary-item summary-failed"> {{ $t('common.Failed') }}: {{ failedCount }}</span>

View File

@@ -76,6 +76,14 @@ export default {
extraRightSideActions: {
type: Array,
default: () => []
},
canCreate: {
type: Boolean,
default: false
},
canBulkUpdate: {
type: Boolean,
default: false
}
},
data() {
@@ -102,7 +110,11 @@ export default {
return this.selectedRows.length > 0
},
iImportOptions() {
return assignIfNot(this.importOptions, { url: this.tableUrl })
return assignIfNot(this.importOptions, {
url: this.tableUrl,
canImportCreate: this.canCreate,
canImportUpdate: this.canBulkUpdate
})
},
iExportOptions() {
const options = assignIfNot(this.exportOptions, { url: this.tableUrl })
@@ -164,8 +176,4 @@ export default {
justify-content:center;
}
.export-item {
}
</style>

View File

@@ -1,9 +1,9 @@
<template>
<div class="table-header clearfix">
<div class="table-header clearfix" :class="device">
<slot name="header">
<LeftSide v-if="hasLeftActions" style="float: left" :class="'left-side ' + device" :selected-rows="selectedRows" :table-url="tableUrl" v-bind="$attrs" v-on="$listeners" />
<RightSide v-if="hasRightActions" style="float: right" :selected-rows="selectedRows" :table-url="tableUrl" v-bind="$attrs" v-on="$listeners" />
<div style="display: flex;flex-direction: row" class="search" :class="hasLeftActions ? 'right' : 'left'">
<LeftSide v-if="hasLeftActions" class="left-side" :selected-rows="selectedRows" :table-url="tableUrl" v-bind="$attrs" v-on="$listeners" />
<RightSide v-if="hasRightActions" class="right-side" :selected-rows="selectedRows" :table-url="tableUrl" v-bind="$attrs" v-on="$listeners" />
<div class="search" :class="searchClass">
<AutoDataSearch v-if="hasSearch" class="right-side-item action-search" v-bind="iSearchTableConfig" @tagSearch="handleTagSearch" />
<DatetimeRangePicker v-if="hasDatePicker" v-bind="datePicker" class="datepicker" @dateChange="handleDateChange" />
</div>
@@ -77,6 +77,9 @@ export default {
return 'mobile'
}
return ''
},
searchClass() {
return this.hasLeftActions ? 'right' : 'left'
}
},
methods: {
@@ -143,11 +146,34 @@ export default {
.datepicker{
margin-left: 10px;
}
.left-side {
float: left;
display: block;
line-height: 36px;
}
.right-side {
float: right;
}
.search {
display: flex;
flex-direction: row
}
.mobile .search {
display: inherit;
}
.mobile .search .datepicker {
margin-left: 0;
}
.search.left {
float: left;
}
.search.right {
float: right;
}
.mobile .search.right {
float: left;
}
.mobile .right-side {
padding-top: 5px;
}
</style>

View File

@@ -66,15 +66,16 @@ export default {
canCreate: { action: 'add', checkRoot: true },
canBulkDelete: { action: 'delete', checkRoot: false },
canBulkUpdate: { action: 'change', checkRoot: true },
hasImport: { action: 'add', checkRoot: true },
hasImport: { action: 'add|change', checkRoot: true },
hasExport: { action: 'view', checkRoot: false }
}
const defaults = {}
for (const [k, v] of Object.entries(actions)) {
defaults[k] = this.hasActionPerm(v.action)
let hasPerm = v.action.split('|').some(i => this.hasActionPerm(i.trim()))
if (v.checkRoot) {
defaults[k] = defaults[k] && !this.currentOrgIsRoot
hasPerm = hasPerm && !this.currentOrgIsRoot
}
defaults[k] = hasPerm
}
return Object.assign(defaults, this.headerActions)
},
@@ -192,6 +193,15 @@ export default {
& >>> .el-table__header thead > tr > th {
background-color: white;
}
&>>> .el-table__row .cell {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
&>>> .el-table__expanded-cell pre {
max-height: 500px;
overflow-y: scroll;
}
}
//修改颜色

View File

@@ -10,14 +10,14 @@
v-on="$listeners"
>
<el-row :gutter="20">
<el-col :span="4">
<el-col :md="4" :sm="24">
<div style="line-height: 34px;text-align: center">MFA</div>
</el-col>
<el-col :span="14">
<el-col :md="14" :sm="24">
<el-input v-model="MFAToken" />
<span class="help-tips help-block">{{ $t('common.MFARequireForSecurity') }}</span>
</el-col>
<el-col :span="4">
<el-col :md="4" :sm="24">
<el-button size="mini" type="primary" style="line-height:20px " @click="verifyMFA">
{{ this.$t('common.Confirm') }}
</el-button>

View File

@@ -0,0 +1,82 @@
<template>
<div class="content">
<span>{{ currentValue }}</span>
<span class="right">
<el-tooltip
effect="dark"
placement="top"
:content="this.$t('common.View')"
>
<i class="el-icon-view" @click="onShow()" />
</el-tooltip>
<el-tooltip
effect="dark"
placement="top"
:content="this.$t('common.Copy')"
>
<i class="el-icon-copy-document" @click="onCopy()" />
</el-tooltip>
</span>
</div>
</template>
<script>
import BaseFormatter from './base'
export default {
name: 'ShowKeyCopyFormatter',
extends: BaseFormatter,
proops: {
cellValue: {
type: String,
default: () => ''
}
},
data() {
return {
currentValue: this.switchShowValue()
}
},
methods: {
switchShowValue() {
return '******' + this.cellValue.replace(/[\w-]/g, '')
},
onShow() {
const { currentValue, cellValue, switchShowValue } = this
this.currentValue = currentValue === cellValue ? switchShowValue() : cellValue
},
onCopy: _.throttle(function() {
const inputDom = document.createElement('input')
inputDom.id = 'creatInputDom'
inputDom.value = this.cellValue
document.body.appendChild(inputDom)
inputDom.select()
document?.execCommand('copy')
this.$message({
message: this.$t('common.CopySuccess'),
type: 'success',
duration: 1400
})
document.body.removeChild(inputDom)
}, 1800)
}
}
</script>
<style lang="scss" scoped>
.content {
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
.right {
float: right;
font-size: 15px;
cursor: pointer;
.el-icon-view, .el-icon-copy-document {
&:hover {
color: #1c84c6;
}
}
}
}
</style>

View File

@@ -7,6 +7,7 @@ import DeleteActionFormatter from './DeleteActionFormatter'
import DateFormatter from './DateFormatter'
import SystemUserFormatter from './GrantedSystemUsersShowFormatter'
import ShowKeyFormatter from '@/components/TableFormatters/ShowKeyFormatter'
import ShowKeyCopyFormatter from './ShowKeyCopyFormatter'
import DialogDetailFormatter from './DialogDetailFormatter'
import EditableInputFormatter from './EditableInputFormatter'
import StatusFormatter from './StatusFormatter'
@@ -21,6 +22,7 @@ export default {
DateFormatter,
SystemUserFormatter,
ShowKeyFormatter,
ShowKeyCopyFormatter,
DialogDetailFormatter,
ArrayFormatter,
EditableInputFormatter,
@@ -37,6 +39,7 @@ export {
DateFormatter,
SystemUserFormatter,
ShowKeyFormatter,
ShowKeyCopyFormatter,
DialogDetailFormatter,
ArrayFormatter,
EditableInputFormatter,

View File

@@ -4,8 +4,7 @@ import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { startup } from '@/utils/startup'
import store from '@/store'
import { getPropView, hasRouteViewPerm, isSameView } from '@/utils/jms'
import Vue from 'vue'
import { isSameView } from '@/utils/jms'
NProgress.configure({
showSpinner: false
@@ -30,20 +29,6 @@ function generateViewRoutesIfChange({ to, from }) {
}
}
async function changeCurrentViewIfNeed({ to, from, next }) {
if (!to.path || isSameView(to, from)) {
return
}
const hasPerm = hasRouteViewPerm(to)
Vue.$log.debug('Change has current view, has perm: ', hasPerm)
if (hasPerm) {
return
}
const view = getPropView()
Vue.$log.debug('Get prop view and goto: ', view)
next(`/${view}`)
}
function setPageTitle() {
const currentRoute = router.currentRoute
const loginTitle = store.getters.publicSettings['LOGIN_TITLE']
@@ -53,12 +38,6 @@ function setPageTitle() {
}
}
router.beforeResolve(async(to, from, next) => {
/* must call `next` */
await changeCurrentViewIfNeed({ to, from, next })
next()
})
router.afterEach(async(to, from) => {
// finish progress bar
await setPageTitle()

View File

@@ -26,5 +26,19 @@ export default {
year: 'numeric', month: 'short', day: 'numeric',
hour: 'numeric', minute: 'numeric', hour12: true
}
},
'ja': {
short: {
year: 'numeric', month: 'short', day: 'numeric'
},
medium: {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit',
hourCycle: 'h23', hour12: false
},
long: {
year: 'numeric', month: 'short', day: 'numeric',
hour: 'numeric', minute: 'numeric', hour12: true
}
}
}

View File

@@ -7,8 +7,12 @@ import date from './date'
import VueCookie from 'vue-cookie'
Vue.use(VueI18n)
const cookieLang = VueCookie.get('django_language')
const browserLang = navigator.systemLanguage || navigator.language
let lang = cookieLang || browserLang || 'zh'
lang = lang.slice(0, 2)
const i18n = new VueI18n({
locale: (VueCookie.get('django_language') || 'zh-hans') === 'zh-hans' ? 'cn' : 'en',
locale: lang,
fallbackLocale: 'en',
silentFallbackWarn: true,
silentTranslationWarn: true,

View File

@@ -314,12 +314,15 @@
"Delete": "Delete",
"Disable": "Disable",
"Download": "Download",
"Copy": "Copy",
"CopySuccess": "Copy success",
"Enable": "Enable",
"On/Off": "On/Off",
"EnterForSearch": "Press enter to search",
"Export": "Export",
"Import": "Import",
"ContinueImport": "ContinueImport",
"ImportAll": "Import All",
"Continue": "Continue",
"Stop": "Stop",
"Finished": "Finished",
@@ -416,8 +419,9 @@
},
"isValid": "Is valid",
"nav": {
"TempPassword": "Temporary password",
"APIKey": "API Key",
"Workspace": "Workbench",
"Workbench": "Workbench",
"Navigation": "Navigation",
"Console": "Console",
"Audits": "Audit",
@@ -534,6 +538,7 @@
"Monthly": "Monthly",
"OnlineSessions": "Online sessions",
"OnlineUsers": "Online users",
"ConnectUsers": "Connect users",
"TimesWeekUnit": "times/week",
"TopAssetsOfWeek": "Top assets of week",
"TopUsersOfWeek": "Top user of week",
@@ -651,6 +656,10 @@
},
"route": {
"": "",
"CreateEndpoint": "Create endpoint",
"UpdateEndpoint": "Update endpoint",
"CreateEndpointRule": "Create endpoint rule",
"UpdateEndpointRule": "Update endpoint rule",
"SystemSetting": "System setting",
"Index": "Index",
"Role": "Role",
@@ -767,6 +776,7 @@
"Activity": "Activity",
"SessionOffline": "Sessions offline",
"SessionOnline": "Sessions online",
"RecentSession": "Recent session",
"Sessions": "Sessions",
"Settings": "Settings",
"SystemUserCreate": "System user create",
@@ -888,6 +898,8 @@
}
},
"setting": {
"EnableKoKoSSHHelpText": "Enabled, connect assets to display SSH Client pull-up method",
"SettingInEndpointHelpText": "Configure the service address and port in System Settings / Terminal Settings / Service Endpoints",
"Feature": "Feature",
"SMSProvider": "SMS provider",
"SMS": "SMS",
@@ -982,6 +994,7 @@
"LDAPUser": "LDAP User",
"InsecureCommandAlert": "Insecure command alert",
"helpText": {
"TempPassword": "For a while, there is a period of 300 seconds, failure immediately after use",
"ApiKeyList": "The API key is used to sign the request header. The header of each request is different. Please refer to the usage documentation",
"authLdapSearchFilter": "Choice may be (cn|uid|sAMAccountName)=%(user)s)",
"authLdapSearchOu": "Use | split User OUs",
@@ -1057,7 +1070,7 @@
"refreshLdapCache":"Refreshing Ldap cache ",
"LicenseExpired": "License expired",
"LicenseWillBe": "License will expire at ",
"Expire": "",
"Expire": "Expire",
"WeCom": "WeCom",
"DingTalk": "DingTalk",
"dingTalkTest": "Test",
@@ -1264,6 +1277,7 @@
"Sender": "Sender",
"MarkAsRead": "Mark as read",
"OneClickRead": "Currently read",
"AllClickRead": "All read",
"OneClickReadMsg": "Are you sure you want to mark the current information as read?",
"NoUnreadMsg": "No unread messages",
"SiteMessage": "Site messages",
@@ -1366,6 +1380,8 @@
"AWS_China": "AWS(China)",
"AWS_Int": "AWS(International)",
"HuaweiCloud": "Huawei Cloud",
"BaiduCloud": "Baidu Cloud",
"JDCloud": "JD Cloud",
"Azure":"Azure(China)",
"Azure_Int": "Azure(International)",
"HostnameStrategy": "Used to produce the asset hostname. For example, 1. Instance name (instanceDemo)2. Instance name and Partial IP (instanceDemo-250.1)",
@@ -1432,6 +1448,8 @@
"License": "License",
"LicenseDetail": "License detail",
"ComponentMonitor": "System Monitor",
"Endpoint": "Endpoint",
"EndpointRule": "Endpoint rule",
"ServiceRatio": "Service ratio",
"LoadStatus":"Status",
"NormalLoad":"Normal",

View File

@@ -1,15 +1,21 @@
import zhLocale from 'element-ui/lib/locale/lang/zh-CN'
import enLocale from 'element-ui/lib/locale/lang/en'
import zh from './cn.json'
import jaLocale from 'element-ui/lib/locale/lang/ja'
import zh from './zh.json'
import en from './en.json'
import ja from './ja.json'
export default {
cn: {
zh: {
...zhLocale,
...zh
},
en: {
...enLocale,
...en
},
ja: {
...jaLocale,
...ja
}
}

1560
src/i18n/langs/ja.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,8 @@ import json
i18n_report_path = '/tmp/abc.json'
lang_paths = {
'cn': 'cn.json',
'en': 'en.json'
'en': 'en.json',
'ja': 'ja.json'
}
@@ -51,5 +52,5 @@ def remove_keys(lang):
f.write(data)
for i in ['cn', 'en']:
for i in ['cn', 'en', 'ja']:
remove_keys(i)

View File

@@ -254,6 +254,7 @@
"ReLogin": "重新登录"
},
"common": {
"Component": "组件",
"PrivateCloud": "私有云",
"PublicCloud": "公有云",
"Correlation": "关联",
@@ -326,12 +327,15 @@
"Delete": "删除",
"Disable": "禁用",
"Download": "下载",
"Copy": "复制",
"CopySuccess": "复制成功",
"Enable": "启用",
"On/Off": "启/停",
"EnterForSearch": "按回车进行搜索",
"Export": "导出",
"Import": "导入",
"ContinueImport": "继续导入",
"ImportAll": "导入全部",
"Continue": "继续",
"Stop": "停止",
"Finished": "完成",
@@ -428,8 +432,9 @@
"fileType": "文件类型",
"isValid": "有效",
"nav": {
"TempPassword": "临时密码",
"APIKey": "API Key",
"Workspace": "工作台",
"Workbench": "工作台",
"Navigation": "导航",
"Console": "控制台",
"Audits": "审计台",
@@ -545,6 +550,7 @@
"Monthly": "按月",
"OnlineSessions": "在线会话",
"OnlineUsers": "在线用户",
"ConnectUsers": "连接用户",
"TimesWeekUnit": "次/周",
"TopAssetsOfWeek": "周资产 TOP10",
"TopUsersOfWeek": "周用户 TOP10",
@@ -661,6 +667,10 @@
},
"route": {
"": "",
"CreateEndpoint": "创建端点",
"UpdateEndpoint": "更新端点",
"CreateEndpointRule": "创建端点规则",
"UpdateEndpointRule": "更新端点规则",
"Index": "首页",
"SystemSetting": "系统设置",
"WorkBench": "工作台",
@@ -783,6 +793,7 @@
"Detail": "详情",
"SessionOffline": "历史会话",
"SessionOnline": "在线会话",
"RecentSession": "最近会话",
"Sessions": "会话管理",
"Settings": "系统设置",
"SystemUserCreate": "创建系统用户",
@@ -909,6 +920,8 @@
}
},
"setting": {
"EnableKoKoSSHHelpText": "开启时连接资产会显示 SSH Client 拉起方式",
"SettingInEndpointHelpText": "在 系统设置 / 终端设置 / 服务端点 中配置服务地址和端口",
"Feature": "功能",
"AlibabaCloud": "阿里云",
"TencentCloud": "腾讯云",
@@ -1008,6 +1021,7 @@
"LDAPServerInfo": "LDAP 服务器",
"LDAPUser": "LDAP 用户",
"helpText": {
"TempPassword": "临时密码有效期为 300 秒,使用后立刻失效",
"ApiKeyList": "使用api key签名请求头每个请求的头部是不一样的, 请查阅使用文档",
"authLdapSearchFilter": "可能的选项是(cn或uid或sAMAccountName=%(user)s)",
"authLdapSearchOu": "使用|分隔各OU",
@@ -1304,6 +1318,7 @@
"Sender": "发送人",
"MarkAsRead": "标记已读",
"OneClickRead": "当前已读",
"AllClickRead": "全部已读",
"OneClickReadMsg": "你确定要将当前信息标记为已读吗?",
"NoUnreadMsg": "暂无未读消息",
"SiteMessage": "站内信",
@@ -1409,6 +1424,8 @@
"AWS_China": "AWS(中国)",
"AWS_Int": "AWS(国际)",
"HuaweiCloud": "华为云",
"BaiduCloud": "百度云",
"JDCloud": "京东云",
"Azure":"Azure(中国)",
"Azure_Int": "Azure(国际)",
"HostnameStrategy": "用于生成资产主机名。例如1. 实例名称 (instanceDemo)2. 实例名称和部分IP(后两位) (instanceDemo-250.1)",
@@ -1477,6 +1494,8 @@
"InterfaceSettings": "界面设置",
"License": "许可证",
"ComponentMonitor": "组件监控",
"Endpoint": "服务端点",
"EndpointRule": "端点规则",
"ServiceRatio": "组件负载统计",
"LoadStatus":"组件状态",
"NormalLoad":"正常",

View File

@@ -7,15 +7,22 @@
:show-cancel="false"
:show-confirm="false"
>
<el-row>
<el-col :span="4">
<el-row :gutter="20">
<el-col :md="4" :sm="24">
<div class="select-prop-label">
<label>{{ selectPropertiesLabel }}</label>
</div>
</el-col>
<el-col :span="18">
<el-col :md="18" :sm="24">
<el-checkbox-group v-model="checkedFields" @change="handleCheckedFieldsChange">
<el-checkbox v-for="(value, name) in iFormSetting.fieldsMeta" :key="name" :checked="true" :label="name">{{ value.label }}</el-checkbox>
<el-checkbox
v-for="(value, name) in iFormSetting.fieldsMeta"
:key="name"
:checked="true"
:label="name"
>
{{ value.label }}
</el-checkbox>
</el-checkbox-group>
</el-col>
</el-row>
@@ -90,10 +97,10 @@ export default {
getDefaultFormSetting() {
const vm = this
return {
submitMethod: () => 'post',
submitMethod: () => 'patch',
cleanFormValue: function(value) {
const filterValue = {}
Object.keys(value).filter((key) => vm.checkedFields.includes(key)).forEach((key) => {
Object.keys(value).filter((key) => vm.checkedFields?.includes(key)).forEach((key) => {
filterValue[key] = value[key]
})
const formValue = []

View File

@@ -7,9 +7,18 @@
<i class="el-icon-arrow-down el-icon--right" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item icon="el-icon-user" command="profile">{{ $t('common.nav.Profile') }}</el-dropdown-item>
<el-dropdown-item v-if="$hasPerm('authentication.view_accesskey')" icon="el-icon-key" command="apiKey">{{ $t('common.nav.APIKey') }}</el-dropdown-item>
<el-dropdown-item divided command="logout"><svg-icon icon-class="logout" style="margin-right: 4px" />{{ $t('common.nav.Logout') }}</el-dropdown-item>
<el-dropdown-item icon="el-icon-user" command="profile">
{{ $t('common.nav.Profile') }}
</el-dropdown-item>
<el-dropdown-item v-if="$hasPerm('authentication.view_accesskey')" icon="el-icon-key" command="apiKey">
{{ $t('common.nav.APIKey') }}
</el-dropdown-item>
<el-dropdown-item v-if="$store.getters.publicSettings.AUTH_TEMP_TOKEN && $hasPerm('authentication.view_temptoken')" icon="el-icon-magic-stick" command="tempPassword">
{{ $t('common.nav.TempPassword') }}
</el-dropdown-item>
<el-dropdown-item divided command="logout"><svg-icon icon-class="logout" style="margin-right: 4px" />
{{ $t('common.nav.Logout') }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
@@ -28,11 +37,7 @@ export default {
},
computed: {
...mapGetters([
'currentUser',
'currentRole',
'currentOrgRoles',
'orgs',
'currentOrgPerms'
'currentUser'
])
},
created() {
@@ -50,6 +55,8 @@ export default {
case 'apiKey':
this.$router.push('/profile/key')
break
case 'tempPassword':
this.$router.push('/profile/temp-password')
}
},
logout() {

View File

@@ -25,6 +25,11 @@ export default {
title: '中文(简体)',
code: 'cn',
cookieCode: 'zh-hans' // cookie code是为了让后端知道当前语言
},
{
title: '日本語',
code: 'ja',
cookieCode: 'ja' // cookie code是为了让后端知道当前语言
}
]
}
@@ -58,6 +63,8 @@ export default {
changeMomentLang() {
if (this.currentLang.code.indexOf('en') > -1) {
this.$moment.locale('en')
} else if (this.currentLang.code.indexOf('ja') > -1) {
this.$moment.locale('ja')
} else {
this.$moment.locale('zh-cn')
}

View File

@@ -1,6 +1,6 @@
<template>
<el-select
:value="currentOrg.id"
:value="currentOrgId"
class="org-select organization"
filterable
:placeholder="$t('common.Select')"
@@ -11,7 +11,7 @@
</template>
<el-option-group
v-for="group in orgOption"
v-for="group in orgGroups"
:key="group.label"
:label="group.label"
class="option-group"
@@ -29,7 +29,6 @@
</span>
<span>{{ item.name }}</span>
</el-option>
</el-option-group>
</el-select>
</template>
@@ -55,15 +54,10 @@ export default {
computed: {
...mapGetters([
'currentOrg',
'currentRole',
'orgs'
])
},
created() {
this.init()
},
methods: {
init() {
'usingOrgs',
'currentViewRoute'
]),
orgActionsGroup() {
const orgActions = {
label: this.$t('xpack.Organization.OrganizationList'),
options: [
@@ -82,21 +76,44 @@ export default {
]
}
const hasPerms = this.$hasPerm('orgs.view_organization | orgs.add_organization')
this.orgOption = [
(hasPerms && orgActions),
{
label: this.$t('xpack.Organization.AllOrganization'),
options: this.orgs
}
const isConsole = this.currentViewRoute.name === 'console'
return hasPerms && isConsole ? orgActions : {}
},
orgChoicesGroup() {
return {
label: this.$t('xpack.Organization.AllOrganization'),
options: this.usingOrgs
}
},
orgGroups() {
return [
this.orgActionsGroup,
this.orgChoicesGroup
]
},
currentOrgId() {
const usingOrgIds = this.usingOrgs.map(o => o.id)
let currentOrgId = this.currentOrg.id
const find = usingOrgIds.indexOf(currentOrgId) > -1
if (!find) {
currentOrgId = null
}
return currentOrgId
}
},
methods: {
changeOrg(orgId) {
if (orgId === 'create') {
this.$router.push({ name: 'OrganizationCreate' })
} else if (orgId === 'list') {
this.$router.push({ name: 'OrganizationList' })
} else {
orgUtil.changeOrg(orgId)
const org = this.usingOrgs.find(item => item.id === orgId)
switch (orgId) {
case 'create':
this.$router.push({ name: 'OrganizationCreate' })
break
case 'list':
this.$router.push({ name: 'OrganizationList' })
break
default:
orgUtil.changeOrg(org)
}
}
}
@@ -146,25 +163,25 @@ export default {
background-color: rgba(144, 147, 152, .5);
}
& > > > .el-input__prefix {
&>>> .el-input__prefix {
left: 8px
}
& > > > .el-input--prefix .el-input__inner {
&>>> .el-input--prefix .el-input__inner {
line-height: 35px !important;
height: 35px !important;
}
& > > > .fa-sitemap {
&>>> .fa-sitemap {
padding-left: 4px;
}
& > > > .el-input__icon {
&>>> .el-input__icon {
color: #606266;
}
}
.option-group > > > .el-select-group__title {
.option-group >>> .el-select-group__title {
color: #909399 !important;
padding-left: 15px;
font-size: 12px;

View File

@@ -6,6 +6,7 @@
</el-link>
</el-badge>
<el-drawer
class="drawer"
:visible.sync="show"
:before-close="handleClose"
:modal="false"
@@ -17,7 +18,7 @@
<div slot="title">
<span>{{ $t('notifications.SiteMessage') }}</span>
<div v-if="unreadMsgCount !== 0" class="msg-list-all-read-btn" @click.stop="oneClickRead(messages)">
<a> {{ $t('notifications.OneClickRead') }}</a>
<a style="vertical-align: sub;"> {{ $t('notifications.AllClickRead') }}</a>
</div>
</div>
<div v-if="unreadMsgCount !== 0" class="msg-list">
@@ -137,13 +138,22 @@ export default {
confirmButtonClass: 'el-button--danger',
beforeClose: async(action, instance, done) => {
if (action !== 'confirm') return done()
this.markAsRead(msgs)
this.markAsReadAll(msgs)
done()
}
}).catch(() => {
/* 取消*/
})
},
markAsReadAll(msgs) {
const url = `/api/v1/notifications/site-message/mark-as-read-all/`
this.$axios.patch(url, {}).then(res => {
this.msgDetailVisible = false
this.getMessages()
}).catch(err => {
this.$message(err.detail)
})
},
markAsRead(msgs) {
const url = `/api/v1/notifications/site-message/mark-as-read/`
const msgIds = []
@@ -192,6 +202,9 @@ export default {
</script>
<style lang="scss" scoped>
.drawer {
height: calc(100% - 40px);
}
.el-badge ::v-deep .el-badge__content.is-fixed{
top:10px;
}

View File

@@ -1,8 +1,9 @@
<template>
<el-menu
default-active="activeIndex"
:default-active="currentViewRoute.name"
class="menu-main"
mode="horizontal"
:class="mode"
:mode="mode"
@select="handleSelectView"
>
<el-submenu
@@ -18,10 +19,9 @@
<el-menu-item
v-for="view of views"
:key="view.name"
v-perms="view.perms"
:index="view.name"
>
<i class="icons" :class="view.meta.icon" />
<i v-if="mode === 'horizontal'" class="icons" :class="view.meta.icon" />
<span slot="title" class="icons-title">{{ view.meta.title }}</span>
</el-menu-item>
</el-submenu>
@@ -30,6 +30,7 @@
<script>
import { mapGetters } from 'vuex'
import store from '@/store'
export default {
name: 'ViewSwitcher',
@@ -37,19 +38,30 @@ export default {
showTitle: {
type: Boolean,
default: false
},
mode: {
type: String,
default: 'horizontal'
}
},
data() {
return {}
return {
showTip: true
}
},
computed: {
...mapGetters([
'currentViewRoute'
'currentViewRoute',
'viewRoutes'
]),
views() {
return this.$store.state.permission.addRoutes.filter(
item => item.meta?.showNavSwitcher
)
return this.viewRoutes.filter((item) => {
let show = item.meta?.showNavSwitcher
if (typeof show === 'function') {
show = show()
}
return show
})
},
viewsMapper() {
const mapper = {}
@@ -62,11 +74,12 @@ export default {
return this.viewsMapper[this.currentViewRoute.name]
}
},
created() {
},
methods: {
handleSelectView(key, keyPath) {
async handleSelectView(key, keyPath) {
const routeName = this.viewsMapper[key] || '/'
localStorage.setItem('PreView', key)
// Next 之前要重置 init 状态,否则这些路由守卫就不走了
await store.dispatch('app/reset')
this.$router.push(routeName)
}
}
@@ -131,4 +144,7 @@ export default {
.el-menu-item.is-active {
font-weight: bold;
}
.menu-main.mobile-view-switch >>> .el-submenu__icon-arrow {
right: 10px;
}
</style>

View File

@@ -5,7 +5,7 @@
<Logo v-if="showLogo" :collapse="isCollapse" />
</div>
<div class="active-mobile">
<ViewSwitcher />
<ViewSwitcher mode="vertical" class="mobile-view-switch" />
<Organization class="organization" />
</div>
<div class="nav-title" :class="{'collapsed': isCollapse}">
@@ -174,6 +174,9 @@ export default {
&>>> .menu-main {
margin-left: -10px;
}
&>>> .title-label {
color: white !important;
}
}
@media screen and (max-width: 992px) {
.active-mobile {

View File

@@ -7,7 +7,9 @@
</template>
</PageHeading>
<PageContent>
<el-alert v-if="helpMessage" type="success"> <span v-html="helpMessage" /> </el-alert>
<el-alert v-if="helpMessage" type="success">
<span class="announcement-main" v-html="helpMessage" />
</el-alert>
<slot />
</PageContent>
</div>
@@ -34,7 +36,6 @@ export default {
},
computed: {
iTitle() {
// return ''
return this.title || this.$route.meta.title
}
}
@@ -52,5 +53,9 @@ export default {
.print-margin{
margin-top: 10px;
}
.announcement-main {
word-wrap:break-word;
white-space: pre-wrap;
}
}
</style>

View File

@@ -30,8 +30,12 @@
</el-tab-pane>
</template>
</el-tabs>
<transition name="fade-transform" mode="out-in">
<slot />
<transition name="fade-transform" mode="out-in" appear>
<slot>
<keep-alive>
<component :is="computeActiveComponent" />
</keep-alive>
</slot>
</transition>
</div>
</Page>
@@ -76,9 +80,19 @@ export default {
}
})
return map
},
computeActiveComponent() {
let needActiveComponent = ''
for (const i of this.submenu) {
if (i.component && (i.name === this.iActiveMenu)) {
needActiveComponent = i.component
break
}
}
return needActiveComponent
}
},
mounted() {
created() {
this.iActiveMenu = this.getPropActiveTab()
},
methods: {
@@ -103,10 +117,10 @@ export default {
]
for (const preTab of preActiveTabs) {
const currentTab = typeof preTab === 'object' ? preTab.name : preTab
const currentTab = typeof preTab === 'object' ? preTab?.name : preTab
for (const tabName of this.tabIndices) {
const currentTabName = tabName?.name || ''
if (currentTab && currentTabName && currentTab.toLowerCase() === currentTabName.toLowerCase()) {
if (currentTab?.toLowerCase() === currentTabName?.toLowerCase()) {
return currentTabName
}
}

View File

@@ -4,6 +4,7 @@ import i18n from '@/i18n/i18n'
import SessionRoutes from './sessions'
import LogRoutes from './logs'
import empty from '@/layout/empty'
import store from '@/store'
export default {
path: '/audit/',
@@ -13,8 +14,10 @@ export default {
meta: {
title: i18n.t('common.nav.Audits'),
icon: 'el-icon-s-claim',
showNavSwitcher: true,
permissions: ['rbac.view_audit'],
showNavSwitcher: () => {
return store.getters.auditOrgs.length > 0
},
permissions: [],
view: 'audit'
},
children: [

View File

@@ -1,6 +1,7 @@
import Layout from '@/layout/index'
import i18n from '@/i18n/i18n'
import empty from '@/layout/empty'
import store from '@/store'
import UsersRoute from './users'
import AssetsRoute from './assets'
@@ -20,8 +21,10 @@ export default {
icon: 'el-icon-s-operation',
view: 'console',
type: 'view',
showNavSwitcher: true,
permissions: ['rbac.view_console']
showNavSwitcher: () => {
return store.getters.consoleOrgs.length > 0
},
permissions: []
},
children: [
{

View File

@@ -50,15 +50,15 @@ export const constantRoutes = [
icon: 'dashboard',
title: i18n.t('route.Overview')
},
beforeEnter: (to, from, next) => {
console.log('Enter home view')
const preferView = getPermedPreferView()
beforeEnter: async(to, from, next) => {
const preferView = getPropView()
if (preferView) {
console.log('Go to preferView: ', preferView)
await store.dispatch('app/reset')
next(`/${preferView}/`)
return false
}
next()
return false
}
}
]
@@ -79,11 +79,12 @@ export const constantRoutes = [
// 权限路由
import consoleViewRoutes from './console'
import auditViewRoutes from './audit'
import workspaceViewRoutes from './workspace'
import workbenchViewRoutes from './workbench'
import ticketsRoutes from './tickets'
import settingsRoutes from './settings'
import profileRoutes from './profile'
import { getPermedPreferView } from '@/utils/jms'
import { getPropView } from '@/utils/jms'
import store from '@/store'
/**
* admin
@@ -92,7 +93,7 @@ import { getPermedPreferView } from '@/utils/jms'
export const viewRoutes = [
consoleViewRoutes,
auditViewRoutes,
workspaceViewRoutes,
workbenchViewRoutes,
ticketsRoutes,
settingsRoutes,
profileRoutes

View File

@@ -53,6 +53,17 @@ export default {
resource: 'accesskey',
app: 'authentication'
}
},
{
path: '/profile/temp-password',
component: () => import('@/views/profile/TempPassword'),
name: 'TempPassword',
meta: {
title: i18n.t('common.nav.TempPassword'),
icon: 'magic',
hidden: ({ settings }) => !settings['AUTH_TEMP_TOKEN'],
permissions: ['authentication.view_temptoken']
}
}
]
}

View File

@@ -85,7 +85,7 @@ export default {
component: () => import('@/views/settings/Terminal'),
meta: {
title: i18n.t('setting.Terminal'),
icon: 'terminal',
icon: 'tasks',
permissions: ['settings.change_terminal']
}
},
@@ -152,6 +152,50 @@ export default {
permissions: ['terminal.change_commandstorage']
},
hidden: true
},
{
path: 'endpoint/create',
name: 'EndpointCreate',
component: () => import('@/views/settings/Terminal/Endpoint/EndpointCreateUpdate'),
meta: {
title: i18n.t('route.CreateEndpoint'),
activeMenu: '/settings/terminal',
permissions: ['terminal.add_endpoint']
},
hidden: true
},
{
path: 'endpoint/:id/update',
name: 'EndpointUpdate',
component: () => import('@/views/settings/Terminal/Endpoint/EndpointCreateUpdate'),
meta: {
title: i18n.t('route.UpdateEndpoint'),
activeMenu: '/settings/terminal',
permissions: ['terminal.change_endpoint']
},
hidden: true
},
{
path: 'endpoint-rule/create',
name: 'EndpointRuleCreate',
component: () => import('@/views/settings/Terminal/EndpointRule/EndpointRuleCreateUpdate'),
meta: {
title: i18n.t('route.CreateEndpointRule'),
activeMenu: '/settings/terminal',
permissions: ['terminal.add_endpointrule']
},
hidden: true
},
{
path: 'endpoint-rule/:id/update',
name: 'EndpointRuleUpdate',
component: () => import('@/views/settings/Terminal/EndpointRule/EndpointRuleCreateUpdate'),
meta: {
title: i18n.t('route.UpdateEndpointRule'),
activeMenu: '/settings/terminal',
permissions: ['terminal.change_endpointrule']
},
hidden: true
}
]
},

View File

@@ -2,25 +2,28 @@ import Layout from '@/layout'
import i18n from '@/i18n/i18n'
import { BASE_URL } from '@/utils/common'
import empty from '@/layout/empty'
import store from '@/store'
export default {
path: '/workspace/',
path: '/workbench/',
component: Layout,
name: 'workspace',
redirect: '/workspace/home',
name: 'workbench',
redirect: '/workbench/home',
meta: {
title: i18n.t('common.nav.Workspace'),
title: i18n.t('common.nav.Workbench'),
type: 'view',
view: 'workspace',
view: 'workbench',
icon: 'el-icon-user-solid',
showNavSwitcher: true,
showNavSwitcher: () => {
return store.getters.workbenchOrgs.length > 0
},
showOrganization: true,
permissions: ['rbac.view_workspace']
permissions: []
},
children: [
// 404 page must be placed at the end !!!
{
path: '/workspace/home',
path: '/workbench/home',
name: 'MyHome',
component: () => import('@/views/myhome'),
meta: {
@@ -30,7 +33,7 @@ export default {
}
},
{
path: '/workspace/assets',
path: '/workbench/assets',
name: 'MyAssets',
component: () => import('@/views/myassets'),
meta: {
@@ -40,7 +43,7 @@ export default {
}
},
{
path: '/workspace/apps',
path: '/workbench/apps',
name: 'Apps',
component: empty,
redirect: 'remoteapp',
@@ -82,7 +85,7 @@ export default {
]
},
{
path: '/workspace/ops',
path: '/workbench/ops',
component: empty,
meta: {
permissions: ['ops.add_commandexecution'],

View File

@@ -1,7 +1,13 @@
const getters = {
sidebar: state => state.app.sidebar,
device: state => state.app.device,
inited: state => state.app.inited,
isMobile: state => state.app.device === 'mobile',
token: state => state.users.token,
consoleOrgs: state => state.users.consoleOrgs,
auditOrgs: state => state.users.auditOrgs,
workbenchOrgs: state => state.users.workbenchOrgs,
usingOrgs: state => state.users.usingOrgs,
currentOrg: state => state.users.currentOrg,
currentOrgIsDefault: state => state.users.currentOrg['is_default'],
currentOrgIsRoot: state => {
@@ -10,6 +16,7 @@ const getters = {
currentRole: state => state.users.currentRole,
currentUser: state => state.users.profile,
currentViewRoute: state => state.permission.currentViewRoute,
viewRoutes: state => state.permission.addRoutes,
publicSettings: state => state.settings.publicSettings,
currentOrgRoles: state => state.users.roles,
currentOrgPerms: state => state.users.perms,
@@ -18,7 +25,6 @@ const getters = {
tableConfig: state => state.table.tableConfig,
currentUserIsSuperAdmin: state => state.users.isSuperAdmin,
currentUserIsAdmin: state => state.users.isAdmin,
hasValidLicense: state => state.settings.hasValidLicense,
orgs: state => state.users.orgs
hasValidLicense: state => state.settings.hasValidLicense
}
export default getters

View File

@@ -5,7 +5,8 @@ const state = {
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
withoutAnimation: false
},
device: 'desktop'
device: 'desktop',
inited: false
}
const mutations = {
@@ -25,6 +26,9 @@ const mutations = {
},
TOGGLE_DEVICE: (state, device) => {
state.device = device
},
SET_INIT: (state, value) => {
state.inited = value
}
}
@@ -37,6 +41,12 @@ const actions = {
},
toggleDevice({ commit }, device) {
commit('TOGGLE_DEVICE', device)
},
init({ commit }) {
commit('SET_INIT', true)
},
reset({ commit }) {
commit('SET_INIT', false)
}
}

View File

@@ -193,9 +193,6 @@ const actions = {
break
}
}
if (viewRoute.meta?.showNavSwitcher) {
localStorage.setItem('PreView', viewName)
}
commit('SET_VIEW_ROUTE', viewRoute)
resolve(viewRoute)
})

View File

@@ -1,12 +1,11 @@
import { logout, getProfile } from '@/api/users'
import { logout, getProfile as apiGetProfile } from '@/api/users'
import {
getCurrentOrgLocal,
getCurrentRoleLocal,
getTokenFromCookie,
saveCurrentOrgLocal,
saveCurrentRoleLocal
saveCurrentOrgLocal
} from '@/utils/auth'
import { resetRouter } from '@/router'
import Vue from 'vue'
const getDefaultState = () => {
return {
@@ -14,10 +13,14 @@ const getDefaultState = () => {
currentOrg: '',
profile: {},
username: '',
orgs: [],
auditOrgs: [],
consoleOrgs: [],
workbenchOrgs: [],
usingOrgs: [],
perms: [],
MFAVerifyAt: null,
isSuperAdmin: false,
isAdmin: false,
hasAdminPerm: false,
hasAuditPerm: false
}
@@ -34,57 +37,37 @@ const mutations = {
},
SET_PROFILE: (state, profile) => {
state.profile = profile
const username = profile.username
state.username = username
state.currentOrg = getCurrentOrgLocal(username)
state.currentRole = getCurrentRoleLocal(username)
state.username = profile.username
state.perms = profile.perms
state.orgs = profile.orgs
state.consoleOrgs = profile['console_orgs']
state.workbenchOrgs = profile['workbench_orgs']
state.auditOrgs = profile['audit_orgs']
state.currentOrg = getCurrentOrgLocal(profile.username)
},
SET_ORGS: (state, orgs) => {
state.orgs = orgs
SET_USING_ORGS: (state, orgs) => {
state.usingOrgs = orgs
},
MODIFY_ORG: (state, org) => {
state.orgs = state.orgs.map(oldOrg => {
state.consoleOrgs = state.consoleOrgs.map(oldOrg => {
if (oldOrg.id === org.id) {
oldOrg.name = org.name
}
return oldOrg
}
)
})
},
ADD_ORG: (state, org) => {
state.orgs.push(org)
state.consoleOrgs.push(org)
},
SET_CURRENT_ORG(state, org) {
state.currentOrg = org
saveCurrentOrgLocal(state.username, org)
},
SET_CURRENT_ROLE(state, role) {
state.currentRole = role
saveCurrentRoleLocal(state.username, role)
},
SET_MFA_VERIFY(state) {
state.MFAVerifyAt = (new Date()).valueOf()
}
}
const actions = {
// user login
// login({ commit }, userInfo) {
// const { username, password } = userInfo
// return new Promise((resolve, reject) => {
// login({ username: username.trim(), password: password }).then(response => {
// const { data } = response
// commit('SET_TOKEN', data.token)
// setToken(data.token)
// resolve()
// }).catch(error => {
// reject(error)
// })
// })
// },
// get user Profile
getProfile({ commit, state }, refresh = false) {
return new Promise((resolve, reject) => {
@@ -92,7 +75,7 @@ const actions = {
resolve(state.profile)
return
}
getProfile().then(response => {
apiGetProfile().then(response => {
if (!response) {
reject('Verification failed, please Login again.')
}
@@ -104,18 +87,6 @@ const actions = {
})
})
},
getInOrgs({ commit, dispatch, state }, refresh) {
return new Promise((resolve, reject) => {
if (!refresh && state.role && state.role.length > 0) {
return resolve(state.roles)
}
dispatch('getProfile').then(profile => {
const { orgs } = profile
commit('SET_ORGS', orgs)
resolve(orgs)
}).catch((e) => reject(e))
})
},
addAdminOrg({ commit, state }, org) {
commit('ADD_ORG', org)
},
@@ -135,23 +106,22 @@ const actions = {
})
})
},
// remove token
resetToken({ commit }) {
return new Promise(resolve => {
// removeToken() // must remove token first
commit('RESET_STATE')
resolve()
})
},
setCurrentOrg({ commit }, data) {
commit('SET_CURRENT_ORG', data)
},
setCurrentRole({ commit }, role) {
commit('SET_CURRENT_ROLE', role)
},
setMFAVerify({ commit }) {
commit('SET_MFA_VERIFY')
},
changeToView({ commit }, viewName) {
const mapper = {
console: state.consoleOrgs,
audit: state.auditOrgs,
workbench: state.workbenchOrgs,
tickets: state.consoleOrgs
}
const usingOrgs = mapper[viewName] || state.consoleOrgs
Vue.$log.debug('Set using orgs: ', viewName, usingOrgs)
commit('SET_USING_ORGS', usingOrgs)
}
}

View File

@@ -170,3 +170,11 @@ input[type=file] {
min-width: 70px!important;
}
}
.el-col.el-col-sm-24 .ibox {
margin-bottom: 10px;
}
.el-pagination {
overflow: auto;
}

View File

@@ -5,7 +5,11 @@ const CURRENT_ORG_KEY = 'jms_current_org'
const CURRENT_ROLE_KEY = 'jms_current_role'
export function getTokenFromCookie() {
return VueCookie.get(TOKEN_KEY)
let cookieNamePrefix = VueCookie.get('SESSION_COOKIE_NAME_PREFIX')
if (!cookieNamePrefix || ['""', "''"].indexOf(cookieNamePrefix) > -1) {
cookieNamePrefix = ''
}
return VueCookie.get(cookieNamePrefix + TOKEN_KEY)
}
export function getCurrentRoleLocal(username) {

View File

@@ -33,7 +33,7 @@ function readableSecond(offset) {
return hours.toFixed(1) + ' ' + getTimeUnits('h')
} else if (minutes > 1) {
return minutes.toFixed(1) + ' ' + getTimeUnits('m')
} else if (seconds > 1) {
} else if (seconds >= 0) {
return seconds.toFixed(1) + ' ' + getTimeUnits('s')
}
return ''
@@ -109,7 +109,7 @@ export function getApiPath(that) {
// ticket ...
pagePath = pagePathArray.slice(1, pagePathArray.length).join('/')
} else {
// console,audit,workspace
// console,audit,workbench
pagePath = pagePathArray.slice(2, pagePathArray.length).join('/')
}
return `/api/v1/${pagePath}/`
@@ -159,6 +159,10 @@ export function hasUUID(s) {
return s.search(uuidPattern) !== -1
}
export function replaceUUID(s, n) {
return s.replace(uuidPattern, n)
}
export function getDaysAgo(days, now) {
if (!now) {
now = new Date()
@@ -276,5 +280,19 @@ const scheme = document.location.protocol
const port = document.location.port ? ':' + document.location.port : ''
const BASE_URL = scheme + '//' + document.location.hostname + port
export function groupedDropdownToCascader(group) {
const firstType = group[0]
return {
value: firstType.category,
label: firstType.group,
children: group.map(item => {
return {
value: item.name,
label: item.title
}
})
}
}
export { BASE_URL }

View File

@@ -1,5 +1,4 @@
import store from '@/store'
import Vue from 'vue'
import { constantRoutes } from '@/router'
export function openTaskPage(taskId) {
@@ -43,7 +42,6 @@ export function getResourceNameByPath(path) {
export function getResourceFromApiUrl(apiUrl) {
const re = new RegExp('/api/v1/([A-Za-z0-9_-]+)/([A-Za-z0-9_-]+)/.*')
console.log('Api url: ', apiUrl)
const matched = apiUrl.match(re)
if (!matched) {
return { path: '', app: '', resource: '' }
@@ -81,24 +79,14 @@ export function hasActionPerm(route, action) {
return hasPermission(permsRequired)
}
const viewRequirePermsMapper = {
console: 'rbac.view_console',
audit: 'rbac.view_audit',
workspace: 'rbac.view_workspace'
}
export function getViewRequirePerms(view) {
return viewRequirePermsMapper[view] || 'super'
}
export function getPermedPreferView() {
for (const [view, perms] of Object.entries(viewRequirePermsMapper)) {
const hasPerm = hasPermission(perms)
Vue.$log.debug('Has view perm: ', view, hasPerm)
if (hasPerm) {
return view
}
}
export function getPermedViews() {
const viewShowMapper = [
['console', store.getters.consoleOrgs.length > 0],
['tickets', store.getters.consoleOrgs.length > 0],
['audit', store.getters.auditOrgs.length > 0],
['workbench', true]
]
return viewShowMapper.filter(i => i[1]).map(i => i[0])
}
export function isSameView(to, from) {
@@ -108,13 +96,13 @@ export function isSameView(to, from) {
}
export function getPropView() {
const hasPermedViews = getPermedViews()
const preView = localStorage.getItem('PreView')
const preViewRequirePerms = getViewRequirePerms(preView)
const hasPerm = hasPermission(preViewRequirePerms)
const hasPerm = hasPermedViews.indexOf(preView) > -1
if (hasPerm) {
return preView
}
const preferView = getPermedPreferView()
const preferView = getPermedViews()[0]
if (preferView) {
return preferView
}
@@ -126,18 +114,8 @@ export function getApiUrlRequirePerms(url, action) {
return [`${app}.${action}_${resource}`]
}
export function getRouteViewRequirePerms(route) {
const viewName = route.path.split('/')[1]
return getViewRequirePerms(viewName)
}
export function hasRouteViewPerm(route) {
if (route.name) {
return hasPermission(route.meta.permissions)
}
const viewName = route.path.split('/')[1]
const perms = getViewRequirePerms(viewName)
return hasPermission(perms)
export function isViewHasOrgs(viewName) {
return getPermedViews().indexOf(viewName) > -1
}
export function getConstRouteName() {

View File

@@ -1,53 +1,46 @@
import { hasUUID, BASE_URL } from '@/utils/common'
import { getOrgDetail } from '@/api/orgs'
import store from '@/store'
import { hasUUID, replaceUUID } from '@/utils/common'
export const DEFAULT_ORG_ID = '00000000-0000-0000-0000-000000000002'
// const ROOT_ORG_ID = '00000000-0000-0000-0000-000000000000'
function getPropOrg() {
const orgs = store.getters.orgs
const orgs = store.getters.usingOrgs
const defaultOrg = orgs.find((item) => item.is_default)
if (defaultOrg) {
return defaultOrg
}
return orgs[0]
return orgs.filter(item => !item['is_root'])[0]
}
function change2PropOrg() {
async function change2PropOrg() {
const org = getPropOrg()
setTimeout(() => changeOrg(org.id), 100)
await changeOrg(org)
}
async function changeOrg(org) {
await store.dispatch('users/setCurrentOrg', org)
await store.dispatch('app/reset')
let path = location.href
if (hasUUID(path)) {
path = replaceUUID(path, '')
path = _.trimEnd(path, '/')
location.href = path
} else {
setTimeout(() => location.reload(), 400)
}
}
function hasCurrentOrgPermission() {
const currentOrg = store.getters.currentOrg
const currentOrgId = currentOrg.id
const orgs = store.getters.orgs
const orgs = store.getters.usingOrgs
return orgs.find((item) => item.id === currentOrgId)
}
async function changeOrg(orgId) {
const org = await getOrgDetail(orgId)
if (!org) {
console.debug('Error: org not found')
} else {
console.debug('Change to org: ', org)
}
localStorage.setItem('PreView', '')
store.dispatch('users/setCurrentOrg', org).then(() => {
// console.log('Set current org to: ', org)
if (hasUUID(location.href)) {
location.href = BASE_URL
} else {
window.location.reload(true)
}
})
}
export default {
hasCurrentOrgPermission,
changeOrg,
DEFAULT_ORG_ID,
change2PropOrg
change2PropOrg,
changeOrg,
getPropOrg
}

View File

@@ -7,6 +7,7 @@ import { Message, MessageBox } from 'element-ui'
import store from '@/store'
import axiosRetry from 'axios-retry'
import router from '@/router'
import { DEFAULT_ORG_ID } from '@/utils/org'
// create an axios instance
const service = axios.create({
@@ -22,7 +23,10 @@ function beforeRequestAddToken(config) {
}
const queryOrgId = router.currentRoute.query?.oid
const storeOrgId = store.getters.currentOrg?.id
const orgId = queryOrgId || storeOrgId
let orgId = queryOrgId || storeOrgId
if (!store.getters.publicSettings?.XPACK_ENABLED) {
orgId = DEFAULT_ORG_ID
}
if (orgId) {
config.headers['X-JMS-ORG'] = orgId
}

View File

@@ -1,15 +1,15 @@
// import getPageTitle from '@/utils/get-page-title'
import store from '@/store'
import router from '@/router'
import router, { resetRouter } from '@/router'
import Vue from 'vue'
import { Message } from 'element-ui'
import 'nprogress/nprogress.css' // progress bar style
import { getTokenFromCookie } from '@/utils/auth'
import orgUtil from '@/utils/org'
import { getCurrentOrg } from '@/api/orgs'
import orgs from '@/api/orgs'
import { getPropView, isViewHasOrgs } from '@/utils/jms'
const whiteList = ['/login', process.env.VUE_APP_LOGIN_PATH] // no redirect whitelist
let initial = false
function reject(msg) {
return new Promise((resolve, reject) => reject(msg))
@@ -51,32 +51,34 @@ async function getPublicSetting({ to, from, next }) {
}
async function refreshCurrentOrg() {
getCurrentOrg().then(org => {
orgs.getCurrentOrg().then(org => {
store.dispatch('users/setCurrentOrg', org)
})
}
async function changeCurrentOrgIfNeed({ to, from, next }) {
await store.dispatch('users/getInOrgs')
const adminOrgs = store.getters.orgs
if (!adminOrgs || adminOrgs.length === 0) {
await store.dispatch('users/getProfile')
const usingOrgs = store.getters.usingOrgs
if (!usingOrgs || usingOrgs.length === 0) {
Vue.$log.debug('No using orgs, return: ', usingOrgs)
return
}
await refreshCurrentOrg()
const currentOrg = store.getters.currentOrg
if (!currentOrg || typeof currentOrg !== 'object') {
orgUtil.change2PropOrg()
return reject('Change prop org')
Vue.$log.error('Current org is null or not a object: ', currentOrg)
await orgUtil.change2PropOrg({ to, from, next })
}
if (!orgUtil.hasCurrentOrgPermission()) {
console.error('Not has current org permission')
orgUtil.change2PropOrg()
return reject('Change prop org')
Vue.$log.error('Not has current org permission: ', currentOrg)
await orgUtil.change2PropOrg({ to, from, next })
}
}
export async function generatePageRoutes({ to, from, next }) {
// determine whether the user has obtained his permission roles through getProfile
resetRouter()
try {
// try get user profile
@@ -113,18 +115,38 @@ export async function checkUserFirstLogin({ to, from, next }) {
}
}
export async function startup({ to, from, next }) {
if (initial) {
return true
export async function changeCurrentViewIfNeed({ to, from, next }) {
let viewName = to.path.split('/')[1]
// 这几个是需要检测的, 切换视图组织时,避免 404
if (['console', 'audit', 'workbench', 'tickets', ''].indexOf(viewName) === -1) {
Vue.$log.debug('Current view no need check', viewName)
return
}
initial = true
const has = isViewHasOrgs(viewName)
Vue.$log.debug('Change has current view, has perm: ', viewName, '=>', has)
if (has) {
await store.dispatch('users/changeToView', viewName)
return
}
viewName = getPropView()
// Next 之前要重置 init 状态,否则这些路由守卫就不走了
await store.dispatch('app/reset')
next(`/${viewName}/`)
return new Promise((resolve, reject) => reject(''))
}
export async function startup({ to, from, next }) {
// if (store.getters.inited) { return true }
if (store.getters.inited) { return true }
await store.dispatch('app/init')
// set page title
await getPublicSetting({ to, from, next })
// await setHeadTitle({ to, from, next })
await checkLogin({ to, from, next })
await generatePageRoutes({ to, from, next })
await changeCurrentViewIfNeed({ to, from, next })
await changeCurrentOrgIfNeed({ to, from, next })
await generatePageRoutes({ to, from, next })
await checkUserFirstLogin({ to, from, next })
return true
}

View File

@@ -61,6 +61,7 @@ export default {
name: 'retry',
type: 'info',
title: this.$t('xpack.ChangeAuthPlan.Retry'),
can: this.$hasPerm('xpack.change_applicationchangeauthplantask'),
callback: function({ row, tableData }) {
this.$axios.put(
`/api/v1/xpack/change-auth-plan/app-plan-execution-subtask/${row.id}/`,

View File

@@ -92,7 +92,7 @@ export default {
name: 'detail',
title: this.$t('xpack.ChangeAuthPlan.Detail'),
type: 'info',
can: 'xpack.view_applicationchangeauthplantask',
can: this.$hasPerm('xpack.view_applicationchangeauthplantask'),
callback: function({ row }) {
return this.$router.push({ name: 'AppChangeAuthPlanExecutionDetail', params: { id: row.id }})
}

View File

@@ -1,21 +1,14 @@
<template>
<TabPage :active-menu.sync="config.activeMenu" :submenu="config.submenu">
<keep-alive>
<component :is="config.activeMenu" />
</keep-alive>
</TabPage>
<TabPage :active-menu.sync="config.activeMenu" :submenu="config.submenu" />
</template>
<script>
import { TabPage } from '@/layout/components'
import AssetChangeAuthPlanList from './AssetChangeAuthPlan/ChangeAuthPlanList'
import AppChangeAuthPlanList from './AppChangeAuthPlan/AppChangeAuthPlanList'
export default {
name: 'Index',
components: {
TabPage,
AssetChangeAuthPlanList,
AppChangeAuthPlanList
TabPage
},
data() {
return {
@@ -25,13 +18,14 @@ export default {
{
title: this.$t('xpack.ChangeAuthPlan.AssetChangeAuthPlan'),
name: 'AssetChangeAuthPlanList',
hidden: () => !this.$hasPerm('xpack.view_changeauthplan')
hidden: () => !this.$hasPerm('xpack.view_changeauthplan'),
component: () => import('@/views/accounts/ChangeAuthPlan/AssetChangeAuthPlan/ChangeAuthPlanList.vue')
},
{
title: this.$t('xpack.ChangeAuthPlan.AppChangeAuthPlan'),
name: 'AppChangeAuthPlanList',
hidden: () => !this.$hasPerm('xpack.view_applicationchangeauthplan')
hidden: () => !this.$hasPerm('xpack.view_applicationchangeauthplan'),
component: () => import('@/views/accounts/ChangeAuthPlan/AppChangeAuthPlan/AppChangeAuthPlanList.vue')
}
]
}

View File

@@ -1,6 +1,6 @@
<template>
<el-row :gutter="20">
<el-col :span="14">
<el-col :md="14" :sm="24">
<DetailCard :items="detailCardItems" />
</el-col>
</el-row>

View File

@@ -1,21 +1,14 @@
<template>
<TabPage :active-menu.sync="config.activeMenu" :submenu="config.submenu">
<keep-alive>
<component :is="config.activeMenu" />
</keep-alive>
</TabPage>
<TabPage :active-menu.sync="config.activeMenu" :submenu="config.submenu" />
</template>
<script>
import { TabPage } from '@/layout/components'
import GatheredUserList from './GatheredUserList'
import TaskList from './TaskList'
export default {
name: 'Index',
components: {
TabPage,
GatheredUserList,
TaskList
TabPage
},
data() {
return {
@@ -25,12 +18,14 @@ export default {
{
title: this.$t('xpack.GatherUser.GatherUserList'),
name: 'GatheredUserList',
hidden: !this.$hasPerm('assets.view_gathereduser')
hidden: !this.$hasPerm('assets.view_gathereduser'),
component: () => import('@/views/accounts/GatheredUser/GatheredUserList.vue')
},
{
title: this.$t('xpack.GatherUser.GatherUserTaskList'),
name: 'TaskList',
hidden: !this.$hasPerm('xpack.view_gatherusertask')
hidden: !this.$hasPerm('xpack.view_gatherusertask'),
component: () => import('@/views/accounts/GatheredUser/TaskList.vue')
}
]
}

View File

@@ -1,9 +1,9 @@
<template>
<el-row :gutter="20">
<el-col :span="14">
<el-col :md="14" :sm="24">
<DetailCard :items="detailCardItems" />
</el-col>
<el-col :span="10">
<el-col :md="10" :sm="24">
<!-- <RelationCard ref="RelationCard" type="info" v-bind="nodeRelationConfig" />-->
</el-col>
</el-row>

View File

@@ -1,9 +1,9 @@
<template>
<el-row :gutter="20">
<el-col :span="14">
<el-col :md="14" :sm="24">
<DetailCard :items="detailCardItems" />
</el-col>
<el-col :span="10">
<el-col :md="10" :sm="24">
<QuickActions type="primary" :actions="quickActions" />
</el-col>
</el-row>

View File

@@ -17,11 +17,6 @@ export default {
],
fieldsMeta: {
type: {
type: 'select',
options: [{
label: 'MySQL',
value: 'mysql'
}],
disabled: true
},
domain: {

View File

@@ -4,7 +4,7 @@
<script>
import { GenericListPage } from '@/layout/components'
import { DATABASE } from '@/views/applications/const'
import { DATABASE, KV_DATABASE } from '@/views/applications/const'
function getAppType(arr) {
const searchAppType = []
@@ -25,7 +25,7 @@ export default {
},
data() {
const vm = this
const appType = DATABASE
const appType = [...DATABASE, ...KV_DATABASE]
return {
tableConfig: {
url: '/api/v1/applications/applications/?category=db',

View File

@@ -1,5 +1,7 @@
import i18n from '@/i18n/i18n'
import store from '@/store'
import { groupedDropdownToCascader } from '@/utils/common'
export const CHROME = 'chrome'
export const MYSQL_WORKBENCH = 'mysql_workbench'
export const VMWARE_CLIENT = 'vmware_client'
@@ -87,7 +89,10 @@ export const DATABASE = [
type: 'primary',
category: DATABASE_CATEGORY,
has: hasLicence
},
}
]
export const KV_DATABASE = [
{
name: REDIS,
title: i18n.t(`applications.applicationsType.${REDIS}`),
@@ -95,53 +100,17 @@ export const DATABASE = [
category: DATABASE_CATEGORY,
has: true,
group: i18n.t('applications.NoSQLProtocol')
}
// {
// name: MONGODB,
// title: i18n.t(`applications.applicationsType.${MONGODB}`),
// type: 'primary',
// category: DATABASE_CATEGORY
// }
]
export const AppPlanDatabase = [
{
name: MYSQL,
title: i18n.t(`applications.applicationsType.${MYSQL}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: true,
group: i18n.t('applications.RDBProtocol')
},
{
name: ORACLE,
title: i18n.t(`applications.applicationsType.${ORACLE}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: hasLicence
},
{
name: POSTGRESQL,
title: i18n.t(`applications.applicationsType.${POSTGRESQL}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: hasLicence
},
{
name: MARIADB,
title: i18n.t(`applications.applicationsType.${MARIADB}`),
name: MONGODB,
title: i18n.t(`applications.applicationsType.${MONGODB}`),
type: 'primary',
category: DATABASE_CATEGORY
},
{
name: SQLSERVER,
title: i18n.t(`applications.applicationsType.${SQLSERVER}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: hasLicence
}
]
export const AppPlanDatabase = DATABASE
export const KUBERNETES = 'k8s'
export const CLOUD_CATEGORY = 'cloud'
@@ -157,9 +126,16 @@ export const CLOUD = [
]
export const ApplicationTypes = [
...DATABASE, ...REMOTE_APP, ...CLOUD
...DATABASE, ...KV_DATABASE, ...REMOTE_APP, ...CLOUD
]
export const ApplicationSystemUserTypes = [
...DATABASE, ...CLOUD
...DATABASE, ...KV_DATABASE, ...CLOUD
]
export const ApplicationCascader = [
groupedDropdownToCascader(DATABASE),
groupedDropdownToCascader(KV_DATABASE),
groupedDropdownToCascader(REMOTE_APP),
groupedDropdownToCascader(CLOUD)
]

View File

@@ -1,10 +1,10 @@
<template>
<div>
<el-row :gutter="24">
<el-col :span="16">
<el-col :md="16" :sm="24">
<AccountListTable ref="ListTable" :url="assetUserUrl" :has-import="false" :has-clone="false" />
</el-col>
<el-col :span="8">
<el-col :md="8" :sm="24">
<QuickActions type="primary" :actions="quickActions" />
</el-col>
</el-row>

View File

@@ -1,9 +1,9 @@
<template>
<el-row :gutter="20">
<el-col :span="14">
<el-col :md="14" :sm="24">
<DetailCard :items="detailCardItems" />
</el-col>
<el-col :span="10">
<el-col :md="10" :sm="24">
<QuickActions type="primary" :actions="quickActions" />
<RelationCard ref="NodeRelation" v-perms="'assets.change_asset'" type="info" style="margin-top: 15px" v-bind="nodeRelationConfig" />
<LabelCard v-if="$hasPerm('assets.view_label')" type="warning" style="margin-top: 15px" v-bind="labelConfig" />

View File

@@ -1,10 +1,10 @@
<template>
<div>
<el-row :gutter="20">
<el-col :span="16">
<el-col :md="16" :sm="24">
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
</el-col>
<el-col :span="8">
<el-col :md="8" :sm="24">
<PermUserGroupCard v-bind="UserGroupCardConfig" />
</el-col>
</el-row>

View File

@@ -1,10 +1,10 @@
<template>
<div>
<el-row :gutter="20">
<el-col :span="16">
<el-col :md="16" :sm="24">
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
</el-col>
<el-col :span="8">
<el-col :md="8" :sm="24">
<QuickActions type="primary" :actions="quickActions" />
<RelationCard ref="systemUserRelation" style="margin-top: 15px" v-bind="systemUserRelationConfig" />
</el-col>

View File

@@ -34,8 +34,8 @@
</GenericTreeListPage>
<Dialog width="30%" :title="this.$t('assets.NodeInformation')" :visible.sync="nodeInfoDialogSetting.dialogVisible" :show-cancel="false" :show-confirm="false">
<el-row v-for="item in nodeInfoDialogSetting.items" :key="'card-' + item.key" :gutter="10" class="item">
<el-col :span="6"><div class="item-label"><label>{{ item.label }}: </label></div></el-col>
<el-col :span="18"><div class="item-text">{{ item.value }}</div></el-col>
<el-col :md="6" :sm="24"><div class="item-label"><label>{{ item.label }}: </label></div></el-col>
<el-col :md="18" :sm="24"><div class="item-text">{{ item.value }}</div></el-col>
</el-row>
</Dialog>
<AssetBulkUpdateDialog
@@ -51,7 +51,11 @@
<script>
import GenericTreeListPage from '@/layout/components/GenericTreeListPage/index'
import { DetailFormatter, ActionsFormatter, TagsFormatter } from '@/components/TableFormatters'
import {
DetailFormatter,
ActionsFormatter,
TagsFormatter
} from '@/components/TableFormatters'
import $ from '@/utils/jquery-vendor'
import Dialog from '@/components/Dialog'
import { mapGetters } from 'vuex'

View File

@@ -4,9 +4,10 @@
<script type="text/jsx">
import GenericListTable from '@/layout/components/GenericListTable'
import { ACCOUNT_PROVIDER_ATTRS_MAP, aliyun, aws_china, aws_international, huaweicloud, qcloud, azure, azure_international, vmware, nutanix, qingcloud_private, huaweicloud_private, openstack, gcp } from '../const'
import { ACCOUNT_PROVIDER_ATTRS_MAP, aliyun, aws_china, aws_international, huaweicloud, qcloud, azure, azure_international, vmware, nutanix, qingcloud_private, huaweicloud_private, openstack, gcp, baiducloud, jdcloud } from '../const'
export default {
name: 'AccountList',
components: {
GenericListTable
},
@@ -85,6 +86,14 @@ export default {
name: huaweicloud,
title: ACCOUNT_PROVIDER_ATTRS_MAP[huaweicloud].title
},
{
name: baiducloud,
title: ACCOUNT_PROVIDER_ATTRS_MAP[baiducloud].title
},
{
name: jdcloud,
title: ACCOUNT_PROVIDER_ATTRS_MAP[jdcloud].title
},
{
name: aws_china,
title: ACCOUNT_PROVIDER_ATTRS_MAP[aws_china].title

View File

@@ -1,9 +1,9 @@
<template>
<el-row :gutter="20">
<el-col :span="14">
<el-col :md="14" :sm="24">
<DetailCard :items="detailCardItems" />
</el-col>
<el-col :span="10">
<el-col :md="10" :sm="24">
<QuickActions type="primary" :actions="quickActions" />
</el-col>
</el-row>

View File

@@ -8,6 +8,7 @@ import { DetailFormatter } from '@/components/TableFormatters'
import { openTaskPage } from '@/utils/jms'
export default {
name: 'SyncInstanceTaskList',
components: {
GenericListTable
},

View File

@@ -13,6 +13,8 @@ export const qingcloud_private = 'qingcloud_private'
export const huaweicloud_private = 'huaweicloud_private'
export const openstack = 'openstack'
export const gcp = 'gcp'
export const baiducloud = 'baiducloud'
export const jdcloud = 'jdcloud'
export const ACCOUNT_PROVIDER_ATTRS_MAP = {
[aliyun]: {
@@ -35,6 +37,16 @@ export const ACCOUNT_PROVIDER_ATTRS_MAP = {
title: i18n.t('xpack.Cloud.HuaweiCloud'),
attrs: ['access_key_id', 'access_key_secret']
},
[baiducloud]: {
name: baiducloud,
title: i18n.t('xpack.Cloud.BaiduCloud'),
attrs: ['access_key_id', 'access_key_secret']
},
[jdcloud]: {
name: jdcloud,
title: i18n.t('xpack.Cloud.JDCloud'),
attrs: ['access_key_id', 'access_key_secret']
},
[qcloud]: {
name: qcloud,
title: i18n.t('xpack.Cloud.Qcloud'),

View File

@@ -1,21 +1,14 @@
<template>
<TabPage :active-menu.sync="config.activeMenu" :submenu="config.submenu">
<keep-alive>
<component :is="config.activeMenu" />
</keep-alive>
</TabPage>
<TabPage :active-menu.sync="config.activeMenu" :submenu="config.submenu" />
</template>
<script>
import { TabPage } from '@/layout/components'
import AccountList from './Account/AccountList'
import SyncInstanceTaskList from './SyncInstanceTask/SyncInstanceTaskList'
export default {
name: 'Index',
name: 'CloudIndex',
components: {
TabPage,
AccountList,
SyncInstanceTaskList
TabPage
},
data() {
return {
@@ -25,13 +18,14 @@ export default {
{
title: this.$t('xpack.Cloud.SyncInstanceTaskList'),
name: 'SyncInstanceTaskList',
hidden: () => !this.$hasPerm('xpack.view_syncinstancetask')
hidden: () => !this.$hasPerm('xpack.view_syncinstancetask'),
component: () => import('@/views/assets/Cloud/SyncInstanceTask/SyncInstanceTaskList.vue')
},
{
title: this.$t('xpack.Cloud.AccountList'),
name: 'AccountList',
hidden: () => !this.$hasPerm('xpack.view_account')
hidden: () => !this.$hasPerm('xpack.view_account'),
component: () => import('@/views/assets/Cloud/Account/AccountList.vue')
}
]
}

View File

@@ -1,9 +1,9 @@
<template>
<el-row :gutter="20">
<el-col :span="14">
<el-col :md="14" :sm="24">
<DetailCard :items="detailCardItems" />
</el-col>
<el-col :span="10">
<el-col :md="10" :sm="24">
<RelationCard ref="systemUserRelation" v-bind="systemUserRelationConfig" />
</el-col>
</el-row>

View File

@@ -1,9 +1,9 @@
<template>
<el-row :gutter="20">
<el-col :span="14">
<el-col :md="14" :sm="24">
<DetailCard :items="detailCardItems" />
</el-col>
<el-col :span="10" />
<el-col :md="10" :sm="24" />
</el-row>
</template>

View File

@@ -12,14 +12,14 @@
:destroy-on-close="true"
>
<el-row :gutter="20">
<el-col :span="4">
<div style="line-height: 34px;text-align: center">{{ $t('assets.SshPort') }}</div>
<el-col :md="4" :sm="24">
<div style="line-height: 34px">{{ $t('assets.SshPort') }}</div>
</el-col>
<el-col :span="14">
<el-col :md="14" :sm="24">
<el-input v-model="portInput" />
<span class="help-tips help-block">{{ $t('assets.TestGatewayHelpMessage') }}</span>
</el-col>
<el-col :span="4">
<el-col :md="4" :sm="24">
<el-button size="mini" type="primary" style="line-height:20px " :loading="buttonLoading" @click="dialogConfirm">{{ this.$t('common.Confirm') }}</el-button>
</el-col>
</el-row>

View File

@@ -1,9 +1,9 @@
<template>
<el-row :gutter="20">
<el-col :span="14">
<el-col :md="14" :sm="24">
<DetailCard :items="detailCardItems" />
</el-col>
<el-col :span="10" />
<el-col :md="10" :sm="24" />
</el-row>
</template>

View File

@@ -29,6 +29,7 @@ export default {
[this.$t('common.Basic'), ['name', 'protocol', 'username', 'type']],
[this.$t('common.Auth'), ['password', 'private_key', 'passphrase']],
[this.$t('common.Command filter'), ['cmd_filters']],
[this.$t('assets.UserSwitch'), ['su_enabled', 'su_from']],
[this.$t('common.Other'), ['priority', 'sftp_root', 'comment']]
],
fieldsMeta: {
@@ -73,7 +74,9 @@ export default {
rules: [Required],
helpText: this.$t('assets.SFTPHelpMessage')
},
cmd_filters: fields.cmd_filters
cmd_filters: fields.cmd_filters,
su_enabled: fields.su_enabled,
su_from: fields.su_from
},
cleanFormValue: (values) => {
values['type'] = 'admin'

View File

@@ -184,6 +184,26 @@ function getFields() {
const type = {
}
const su_enabled = {
type: 'switch',
hidden: (item) => item.protocol !== 'ssh'
}
const su_from = {
hidden: (item) => !item.su_enabled,
rules: [Required],
el: {
multiple: false,
clearable: true,
ajax: {
url: '/api/v1/assets/system-users/su-from/',
transformOption: (item) => {
return { label: item.name + '(' + item.username + ')', value: item.id }
}
}
}
}
return {
login_mode: login_mode,
username: username,
@@ -197,7 +217,9 @@ function getFields() {
password: password,
passphrase: passphrase,
system_groups: system_groups,
type: type
type: type,
su_enabled: su_enabled,
su_from: su_from
}
}

View File

@@ -1,9 +1,9 @@
<template>
<el-row :gutter="20">
<el-col :span="20">
<el-col :md="20" :sm="24">
<AccountListTable ref="ListTable" :url="accountUrl" :has-import="false" :has-clone="false" />
</el-col>
<el-col :span="4" />
<el-col :md="4" :sm="24" />
</el-row>
</template>

View File

@@ -1,9 +1,9 @@
<template>
<el-row :gutter="20">
<el-col :span="20">
<el-col :md="20" :sm="24">
<AppAccountListTable ref="ListTable" :url="accountUrl" :has-import="false" :has-clone="false" />
</el-col>
<el-col :span="4" />
<el-col :md="4" :sm="24" />
</el-row>
</template>

View File

@@ -1,10 +1,10 @@
<template>
<div>
<el-row :gutter="20">
<el-col :span="16">
<el-col :md="16" :sm="24">
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
</el-col>
<el-col :span="8">
<el-col :md="8" :sm="24">
<RelationCard ref="assetSelect" type="primary" style="margin-top: 15px" v-bind="appRelationConfig" />
</el-col>
</el-row>

View File

@@ -1,10 +1,10 @@
<template>
<div>
<el-row :gutter="20">
<el-col :span="16">
<el-col :md="16" :sm="24">
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
</el-col>
<el-col :span="8">
<el-col :md="8" :sm="24">
<QuickActions type="primary" :actions="quickActions" />
<AssetRelationCard ref="assetSelect" type="primary" style="margin-top: 15px" v-bind="assetRelationConfig" />
<RelationCard ref="nodeRelation" type="info" style="margin-top: 15px" v-bind="nodeRelationConfig" />

View File

@@ -1,9 +1,9 @@
<template>
<el-row :gutter="20">
<el-col :span="14">
<el-col :md="14" :sm="24">
<DetailCard :items="detailCardItems" />
</el-col>
<el-col :span="10">
<el-col :md="10" :sm="24">
<QuickActions type="primary" :actions="quickActions" />
<RelationCard
v-if="!(object.protocol === 'rdp' || object.protocol === 'vnc')"

View File

@@ -4,10 +4,10 @@
<b>{{ Tips.title }}</b>: <span>{{ Tips.body }}</span>
</el-alert>
<el-row :gutter="20">
<el-col :span="20">
<el-col :md="20" :sm="24">
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
</el-col>
<el-col :span="4" />
<el-col :md="4" :sm="24" />
</el-row>
</div>
</template>

View File

@@ -1,4 +1,5 @@
import i18n from '@/i18n/i18n'
import { groupedDropdownToCascader } from '@/utils/common'
export const AssetProtocols = [
{
@@ -28,3 +29,5 @@ export const AssetProtocols = [
}
]
export const AssetCascader = groupedDropdownToCascader(AssetProtocols)

View File

@@ -9,7 +9,7 @@
<script type="text/jsx">
import GenericListPage from '@/layout/components/GenericListPage'
import { getDayEnd, getDaysAgo } from '@/utils/common'
import { getDaysFuture, getDaysAgo } from '@/utils/common'
import { Dialog, ListTable } from '@/components'
import { DisplayFormatter } from '@/components/TableFormatters'
import { setUrlParam } from '@/utils/common'
@@ -21,7 +21,7 @@ export default {
data() {
const now = new Date()
const dateFrom = getDaysAgo(7, now).toISOString()
const dateTo = getDayEnd(now).toISOString()
const dateTo = getDaysFuture(1, now).toISOString()
const vm = this
return {
tableConfig: {

View File

@@ -4,7 +4,7 @@
<script>
import GenericListPage from '@/layout/components/GenericListPage'
import { getDayEnd, getDaysAgo } from '@/utils/common'
import { getDaysAgo, getDaysFuture } from '@/utils/common'
export default {
components: {
@@ -13,7 +13,7 @@ export default {
data() {
const now = new Date()
const dateFrom = getDaysAgo(7, now).toISOString()
const dateTo = getDayEnd(now).toISOString()
const dateTo = getDaysFuture(1, now).toISOString()
return {
tableConfig: {
permissions: {

View File

@@ -4,7 +4,7 @@
<script>
import GenericListPage from '@/layout/components/GenericListPage'
import { getDayEnd, getDaysAgo } from '@/utils/common'
import { getDaysAgo, getDaysFuture } from '@/utils/common'
export default {
components: {
@@ -13,7 +13,7 @@ export default {
data() {
const now = new Date()
const dateFrom = getDaysAgo(7, now).toISOString()
const dateTo = getDayEnd(now).toISOString()
const dateTo = getDaysFuture(1, now).toISOString()
return {
tableConfig: {
url: '/api/v1/audits/operate-logs/',

View File

@@ -4,7 +4,7 @@
<script>
import GenericListPage from '@/layout/components/GenericListPage'
import { getDayEnd, getDaysAgo } from '@/utils/common'
import { getDaysAgo, getDaysFuture } from '@/utils/common'
export default {
components: {
@@ -13,7 +13,7 @@ export default {
data() {
const now = new Date()
const dateFrom = getDaysAgo(7, now).toISOString()
const dateTo = getDayEnd(now).toISOString()
const dateTo = getDaysFuture(1, now).toISOString()
return {
tableConfig: {
url: '/api/v1/audits/password-change-logs/',

View File

@@ -1,11 +1,11 @@
<template>
<div class="white-bg dashboard-header print-margin">
<el-row>
<el-col :span="12">
<el-col :md="12" :sm="24">
<h2>{{ $t('dashboard.LoginOverview') }}</h2>
</el-col>
<el-col :span="12">
<el-button-group style="float: right; padding: 0">
<el-col :md="12" :sm="24" class="clearfix">
<el-button-group style="float: right; padding: 0" class="clearfix">
<el-button type="default" size="mini" :class="{ 'active': active === 'weekly'}" @click="changeDates('weekly')">{{ $t('dashboard.Weekly') }}</el-button>
<el-button type="default" size="mini" :class="{ 'active': active === 'monthly'}" @click="changeDates('monthly')">{{ $t('dashboard.Monthly') }}</el-button>
</el-button-group>
@@ -13,7 +13,7 @@
</el-row>
<el-row :gutter="20">
<el-col :lg="18" :sm="24">
<LoginMetric :range="active" class="card-item" style="margin-top: -30px" heigth="300px" />
<LoginMetric :range="active" class="card-item" heigth="300px" />
</el-col>
<el-col :lg="6" :sm="24">
<LoginActivePin :range="active" class="card-item" />

View File

@@ -28,27 +28,28 @@ export default {
{
title: this.$t('dashboard.UsersTotal'),
body: {
route: `/users/users`,
route: { name: 'UserList' },
count: this.counter.total_count_users,
comment: 'All users',
disabled: !this.$store.state.users.hasAdmin
comment: this.$t('dashboard.UsersTotal'),
disabled: !this.$hasPerm('users.view_user')
}
},
{
title: this.$t('dashboard.AssetsTotal'),
body: {
route: `/assets/assets`,
route: { name: 'AssetList' },
count: this.counter.total_count_assets,
comment: 'All assets',
disabled: !this.$store.state.users.hasAdmin
comment: this.$t('dashboard.AssetsTotal'),
disabled: !this.$hasPerm('assets.view_asset')
}
},
{
title: this.$t('dashboard.OnlineUsers'),
title: this.$t('dashboard.ConnectUsers'),
body: {
route: { name: `SessionList`, params: { activeMenu: 'OnlineList' }},
count: this.counter.total_count_online_users,
comment: 'Online users'
comment: this.$t('dashboard.OnlineUsers'),
disabled: !this.$hasPerm('terminal.view_session')
}
},
{
@@ -56,7 +57,8 @@ export default {
body: {
route: { name: `SessionList`, params: { activeMenu: 'OnlineList' }},
count: this.counter.total_count_online_sessions,
comment: 'Online sessions'
comment: this.$t('dashboard.OnlineSessions'),
disabled: !this.$hasPerm('terminal.view_session')
}
}
]

View File

@@ -1,12 +1,12 @@
<template>
<el-row :gutter="10" style="margin-bottom: 20px;margin-top: 20px">
<el-col :md="8" :sm="12">
<el-col :md="8" :sm="24">
<TopUser />
</el-col>
<el-col :md="8" :sm="12" class="print-margin-top">
<el-col :md="8" :sm="24" class="print-margin-top">
<TopAssets />
</el-col>
<el-col :md="8" :sm="12">
<el-col :md="8" :sm="24">
<Latest10Sessions class="card-item print-margin-top" />
</el-col>
</el-row>

View File

@@ -1,6 +1,6 @@
<template>
<Page>
<div v-if="this.$hasPerm('rbac.view_console|rbac.view_workspace')">
<div v-if="this.$hasPerm('rbac.view_console|rbac.view_audit')">
<Announcement />
<ResourceSummary />
<DatesLoginSummary />

View File

@@ -1,18 +1,19 @@
<template>
<div>
<Announcement />
<GenericTreeListPage :table-config="tableConfig" :header-actions="headerActions" :tree-setting="treeSetting" />
<GenericTreeListPage
:table-config="tableConfig"
:header-actions="headerActions"
:tree-setting="treeSetting"
/>
</div>
</template>
<script>
import GenericTreeListPage from '@/layout/components/GenericTreeListPage'
import { Announcement } from '@/components'
import { SystemUserFormatter, DialogDetailFormatter } from '@/components/TableFormatters'
export default {
components: {
GenericTreeListPage,
Announcement
GenericTreeListPage
},
data() {
return {
@@ -39,6 +40,10 @@ export default {
url: '/api/v1/perms/users/assets/',
hasTree: true,
columns: ['hostname', 'ip', 'system_users', 'platform', 'comment', 'actions'],
columnsShow: {
default: ['hostname', 'ip', 'system_users', 'platform', 'actions'],
min: ['hostname', 'actions']
},
columnsMeta: {
hostname: {
prop: 'hostname',
@@ -86,7 +91,7 @@ export default {
showOverflowTooltip: true,
align: 'center',
label: this.$t('assets.SystemUsers'),
width: '150px',
width: '120px',
formatter: SystemUserFormatter,
formatterArgs: {
getUrl: ({ row }) => {
@@ -102,6 +107,8 @@ export default {
width: '100px'
},
actions: {
width: '150px',
align: 'center',
formatterArgs: {
hasDelete: false,
loading: true,

View File

@@ -5,8 +5,8 @@
</div>
<ul class="content">
<li v-if="announcement.content" class="item">
<span class="item-title">{{ announcement.subject }}</span>
<span>{{ announcement.content }}</span>
<p class="item-title">{{ announcement.subject }}</p>
<p class="item-content">{{ announcement.content }}</p>
<span v-if="announcement.link">
<el-link :href="announcement.link" target="_blank" class="item-url">
{{ $t('common.ViewMore') }}
@@ -70,6 +70,11 @@ ul,li {
text-align: center;
font-size: 15px;
vertical-align: middle;
margin-left: -10px;
}
.item-content {
white-space: pre-wrap;
margin: 0;
}
}
.item-url {

View File

@@ -14,7 +14,7 @@ export default {
const vm = this
return {
cardConfig: {
title: this.$t('route.SessionOffline')
title: this.$t('route.RecentSession')
},
tableConfig: {
url: '/api/v1/terminal/my-sessions/?limit=5',
@@ -64,7 +64,7 @@ export default {
}
},
hasSelection: false,
paginationSize: 5
paginationSize: 10
}
}
}

View File

@@ -64,7 +64,7 @@ export default {
}
],
hasSelection: false,
paginationSize: 5
paginationSize: 10
}
}
},

View File

@@ -12,7 +12,10 @@
<ul>
<li><span class="title">{{ $t('audits.Username') }}</span><span>{{ users.name }}</span></li>
<li><span class="title">{{ $t('users.Email') }}</span><span>{{ users.email }}</span></li>
<li><span class="title">{{ $t('audits.LoginDate') }}</span><span>{{ this.$moment(users.last_login).format('YYYY-MM-DD HH:mm:ss') }}</span></li>
<li>
<span class="title">{{ $t('audits.LoginDate') }}</span>
<span>{{ $moment(users.last_login, 'YYYY-MM-DD HH:mm:ss').format('YYYY-MM-DD HH:mm:ss') }}</span>
</li>
</ul>
</el-col>
</el-row>

View File

@@ -1,5 +1,6 @@
<template>
<Page>
<Announcement />
<div class="home">
<el-container class="container">
<el-main class="main">
@@ -8,12 +9,11 @@
<el-row>
<el-col :md="16" :xs="24" class="content-left">
<Session />
<Log />
<Ticket v-if="hasValidLicense" />
<Ticket v-if="$hasLicense() && $hasPerm('tickets.view_ticket')" />
</el-col>
<el-col :md="8" :xs="24">
<User />
<Announcement />
<Log />
</el-col>
</el-row>
</div>
@@ -26,8 +26,8 @@
<script>
import { Page } from '@/layout/components'
import { Announcement } from '@/components'
import User from './components/User'
import Announcement from './components/Announcement'
import Ticket from './components/Ticket'
import Log from './components/LoginLog'
import Session from './components/Session'
@@ -35,23 +35,12 @@ import Session from './components/Session'
export default {
name: 'Name',
components: {
Announcement,
Page,
User,
Announcement,
Ticket,
Log,
Session
},
data() {
return {
}
},
computed: {
hasValidLicense() {
return this.$store.getters.hasValidLicense
}
},
methods: {
}
}

View File

@@ -1,9 +1,9 @@
<template>
<el-row :gutter="20">
<el-col :span="14">
<el-col :md="14" :sm="24">
<DetailCard :title="cardTitle" :items="detailCardItems" />
</el-col>
<el-col :span="10">
<el-col :md="10" :sm="24">
<RunInfoCard type="danger" style="margin-top: 15px" v-bind="RunFailedConfig" />
<RunInfoCard type="info" v-bind="RunSuccessConfig" style="margin-top: 15px" />
</el-col>

Some files were not shown because too many files have changed in this diff Show More