Compare commits

..

50 Commits

Author SHA1 Message Date
ibuler
1e777ea67d perf: remove check 2024-12-10 17:28:58 +08:00
ibuler
a290e15b90 perf: 修改抽屉,传递动作 2023-11-08 19:18:49 +08:00
“huailei000”
b47f396cd4 perf: 详情暂时不显示更新按钮 2023-11-08 18:22:35 +08:00
“huailei000”
085554ef7f perf: 优化资产授权报错 2023-11-08 17:15:05 +08:00
feng
a62884564c perf: 在线用户根据websocket添加用户是否活跃状态 2023-11-08 17:03:01 +08:00
fit2bot
a9c0d0677c feat: 右侧抽屉创建 (#3436)
* stash

* perf: 验证用户和网域

* perf: 优化 drawer

* perf: 优化创建后 list reload

* perf: 优化创建

* perf: 替换账号管理相关页面创建、更新组件

* perf: 替换权限管理相关页面组件

* perf: 优化资产这里

* perf: 修改 reload

* perf: 替换权限管理-用户登录页面组件

* perf: 修改资产这里

* perf: 优化一点

* perf: 优化平台列表创建、更新抽屉组件不能正常显示问题

* perf: 优化平台列表创建时表单初始化数据不准确问题

* perf: 优化创建、更新平台表单报错问题

* perf: 优化列表克隆问题

* perf: 优化创建资产时抽屉组件偶尔不显示问题

* perf: 优化工作台创建、更新组件

* perf: GenericCreateUpdateDrawer 组件增加参数visible控制显示隐藏

* perf: 优化visible判断

* perf: 优化资产创建、更新

* perf: 增加el-drawer组件补丁,防止在抽屉里复制拖拽至遮罩层会使抽屉关闭问题

* perf: 优化角色列表创建、更新报错问题;优化关闭抽屉控制台报错问题

* perf: 优化作业管理创建、更新

* perf: 优化工单创建

* perf: 优化远程应用创建、更新

---------

Co-authored-by: ibuler <ibuler@qq.com>
Co-authored-by: “huailei000” <2280131253@qq.com>
Co-authored-by: wangruidong <940853815@qq.com>
Co-authored-by: huailei <31801270+huailei000@users.noreply.github.com>
2023-11-08 11:21:39 +08:00
“huailei000”
5449d74d53 perf: 优化select2组件远程搜索结果为空时是否开启远程搜索状态没有动态更新问题;创建工单搜索资产时不显示问题 2023-11-08 10:21:15 +08:00
jiangweidong
dc401f80b9 fix: 解决更新云同步任务时,优先级无法更新问题 2023-11-07 17:18:45 +08:00
wangruidong
379cd2386a perf: 账号备份增加sftp方式 2023-11-07 15:37:04 +08:00
wangruidong
4e7bdb9c69 perf: 对象存储增加sftp 2023-11-07 15:37:04 +08:00
feng626
41449fb538 Merge pull request #3485 from jumpserver/pr@dev@gather_account
feat: 账号收集添加资产账号信息变化通知
2023-11-07 13:00:45 +08:00
feng626
a75488217c Merge pull request #3495 from jumpserver/pr@dev@su_from
fix: 账号 切换自 翻译修复
2023-11-07 11:09:18 +08:00
feng
8e32792696 fix: 账号 切换自 翻译修复 2023-11-07 11:08:28 +08:00
feng626
b47caf0287 Merge pull request #3493 from jumpserver/pr@dev@account_change_push
fix: 改密 推送 详情里执行任务的权限不对
2023-11-06 18:05:39 +08:00
feng
715ae856f0 fix: 改密 推送 详情里执行任务的权限不对 2023-11-06 18:01:39 +08:00
“huailei000”
19ae27e6c2 perf: ztree父节点是选中状态,点击展开后子节点默认显示选中状态 2023-11-06 16:22:11 +08:00
halo
2132bacff5 feat: 工作台支持配置显示系统工具 2023-11-03 17:34:14 +08:00
老广
e57c5a20d0 Merge pull request #3487 from jumpserver/pr@dev@perf_add_ip_group_access
perf: 更新字段修改
2023-11-02 15:39:48 +08:00
老广
3d35f0aafe Merge pull request #3488 from jumpserver/pr@dev@perf_tag_input
perf: 优化 tab input
2023-11-02 15:38:52 +08:00
ibuler
68ac03db9e perf: 优化 tab input 2023-11-02 15:33:30 +08:00
wangruidong
7b5471a451 perf: 更新字段修改 2023-11-02 15:16:58 +08:00
ibuler
7f052ac85e perf: 优化 tag input 2023-11-02 11:07:20 +08:00
feng
64e75eb9ff feat: 账号收集添加资产账号信息变化通知 2023-11-02 10:50:18 +08:00
ibuler
8e75e5d5e3 perf: 优化 json field m2m 2023-11-02 10:36:00 +08:00
“huailei000”
1810e6833c perf: 公告支持markdown预览 2023-11-01 01:19:09 -05:00
wangruidong
8c7c012785 perf: 添加访问IP控制 2023-10-31 02:45:03 -05:00
feng626
378d82518a Merge pull request #3479 from jumpserver/pr@dev@change_secret_records
feat: 改密记录 推送记录可单独执行
2023-10-31 14:10:53 +08:00
wangruidong
728b04c8e3 perf: 修改语言位置 2023-10-30 21:54:40 -05:00
老广
b8611f095a Merge pull request #3478 from jumpserver/pr@dev@feat_apple_host_support_same_account
feat: 发布机支持使用同名账号连接
2023-10-31 10:19:30 +08:00
feng
f49a1184e2 feat: 改密记录 推送记录可单独执行 2023-10-30 18:42:14 +08:00
dependabot[bot]
77a4441018 build(deps): bump browserify-sign from 4.2.1 to 4.2.2
Bumps [browserify-sign](https://github.com/crypto-browserify/browserify-sign) from 4.2.1 to 4.2.2.
- [Changelog](https://github.com/browserify/browserify-sign/blob/main/CHANGELOG.md)
- [Commits](https://github.com/crypto-browserify/browserify-sign/compare/v4.2.1...v4.2.2)

---
updated-dependencies:
- dependency-name: browserify-sign
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-30 14:30:10 +08:00
ibuler
18f1f0de79 feat: 发布机支持使用同名账号连接 2023-10-30 11:37:22 +08:00
dependabot[bot]
d252c7dd08 build(deps): bump @babel/traverse from 7.20.1 to 7.23.2
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.20.1 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-30 10:38:38 +08:00
dependabot[bot]
e5547f8a4c build(deps): bump crypto-js from 4.1.1 to 4.2.0
Bumps [crypto-js](https://github.com/brix/crypto-js) from 4.1.1 to 4.2.0.
- [Commits](https://github.com/brix/crypto-js/compare/4.1.1...4.2.0)

---
updated-dependencies:
- dependency-name: crypto-js
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-30 10:37:50 +08:00
jiangweidong
63d6578991 fix: 云账号详情页面更新报错 2023-10-30 10:30:12 +08:00
ibuler
da00ae84a8 perf: 资产授权添加协议 2023-10-30 10:14:01 +08:00
ibuler
74a905ee85 perf: 资产授权添加协议 2023-10-30 10:14:01 +08:00
“huailei000”
a03e985df3 perf: table tooltip 修改鼠标移上图标显示 2023-10-25 16:53:58 +08:00
吴小白
2d6005a4e0 perf: 优化构建 2023-10-25 16:38:41 +08:00
“huailei000”
c4ca28d2d9 perf: 优化table组件内容溢出省略时,鼠标可以移入tooltip 2023-10-25 16:37:59 +08:00
wangruidong
3934c17f52 perf: 增加语言切换功能 2023-10-25 03:03:17 -05:00
“huailei000”
51717f8583 perf: 优化创建api key后关闭弹窗列表不刷新问题 2023-10-25 10:47:17 +08:00
“huailei000”
67e99702c1 perf: 远程应用名称不能包含() 2023-10-24 19:30:50 +08:00
feng626
27ce5a3785 Merge pull request #3462 from jumpserver/pr@dev@account_change_secret
fix: 修改账号改密执行权限
2023-10-24 10:51:35 +08:00
feng
49122bb213 fix: 修改账号改密执行权限 2023-10-24 10:49:41 +08:00
halo
263c4d3f89 fix: 修复权限树自动选择的问题 2023-10-23 04:29:49 -05:00
wangruidong
2ed203dc32 perf: 添加作业中心执行历史配置字段 2023-10-23 04:23:15 -05:00
“huailei000”
742a06ea2d perf: 优化LDAP用户列表导入全部用户失败后没有提示信息问题 2023-10-20 16:15:20 +08:00
“huailei000”
1ad4e2c62e perf: 调整账号列表页面默认激活的tab 2023-10-19 19:15:24 +08:00
jiangweidong
7e29a3e836 fix: 新创建的云同步任务策略为空的问题 2023-10-19 03:57:31 -05:00
152 changed files with 3747 additions and 2583 deletions

View File

@@ -33,17 +33,27 @@ jobs:
tag: ${{ steps.get_version.outputs.TAG }}
- uses: actions/setup-node@v2
with:
node-version: '14.16'
build-and-release:
needs: create-realese
name: Build and Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build it and upload
uses: jumpserver/action-build-upload-assets@node14.16
node-version: '16.20'
- name: Install dependencies
run: yarn install
- name: Build web
run: |
sed -i "s@version-dev@${{ steps.get_version.outputs.TAG }}@g" src/layout/components/NavHeader/About.vue
yarn build
- name: Create Upload Assets
run: |
rm -rf build/*
mv lina lina-${{ steps.get_version.outputs.VERSION }}
tar -czf lina-${{ steps.get_version.outputs.VERSION }}.tar.gz lina-${{ steps.get_version.outputs.VERSION }}
echo $(md5sum lina-${{ steps.get_version.outputs.VERSION }}.tar.gz | awk '{print $1}') > build/lina-${{ steps.get_version.outputs.VERSION }}.tar.gz.md5
mv lina-${{ steps.get_version.outputs.VERSION }}.tar.gz build/
- name: Release Upload Assets
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
draft: true
files: |
build/lina-${{ steps.get_version.outputs.TAG }}.tar.gz
build/lina-${{ steps.get_version.outputs.TAG }}.tar.gz.md5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-realese.outputs.upload_url }}

View File

@@ -1,4 +1,4 @@
FROM node:16.17.1-bullseye-slim as stage-build
FROM node:16.20-bullseye-slim as stage-build
ARG TARGETARCH
ARG DEPENDENCIES=" \

1
GITSHA
View File

@@ -1 +0,0 @@
e7772c9bbaf882d8c807ae7a037257af3b24cbad

View File

@@ -5,11 +5,10 @@
"author": "Pan <panfree23@gmail.com>",
"license": "MIT",
"scripts": {
"dev": "vue-cli-service serve",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"dev": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service serve",
"serve": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service serve",
"build": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build",
"build:prod": "vue-cli-service build",
"build:stage": "vue-cli-service build --mode staging",
"preview": "node build/index.js --preview",
"lint": "eslint --ext .js,.vue src",
"fix": "eslint --ext .js,.vue --fix src",

View File

@@ -1,16 +1,5 @@
<template>
<Dialog
v-if="iVisible"
:close-on-click-modal="false"
:destroy-on-close="true"
:show-cancel="false"
:show-confirm="false"
:title="title"
:visible.sync="iVisible"
v-bind="$attrs"
width="70%"
v-on="$listeners"
>
<GenericCreateUpdateDrawer v-bind="$attrs">
<AccountCreateUpdateForm
v-if="!loading"
ref="form"
@@ -20,18 +9,18 @@
@add="addAccount"
@edit="editAccount"
/>
</Dialog>
</GenericCreateUpdateDrawer>
</template>
<script>
import Dialog from '@/components/Dialog/index.vue'
import GenericCreateUpdateDrawer from '@/layout/components/GenericCreateUpdateDrawer/index.vue'
import AccountCreateUpdateForm from '@/components/Apps/AccountCreateUpdateForm/index.vue'
export default {
name: 'CreateAccountDialog',
components: {
Dialog,
AccountCreateUpdateForm
AccountCreateUpdateForm,
GenericCreateUpdateDrawer
},
props: {
visible: {

View File

@@ -14,21 +14,11 @@
@updateAuthDone="onUpdateAuthDone"
/>
<AccountCreateUpdate
v-if="showAddDialog"
:account="account"
:url="url"
:asset="iAsset"
:title="accountCreateUpdateTitle"
:visible.sync="showAddDialog"
@add="addAccountSuccess"
@bulk-create-done="showBulkCreateResult($event)"
/>
<AccountCreateUpdate
v-if="showAddTemplateDialog"
:account="account"
:add-template="true"
:asset="iAsset"
:add-template="addTemplate"
:title="accountCreateUpdateTitle"
:visible.sync="showAddTemplateDialog"
@add="addAccountSuccess"
@bulk-create-done="showBulkCreateResult($event)"
/>
@@ -128,6 +118,7 @@ export default {
showAddDialog: false,
showAddTemplateDialog: false,
createAccountResults: [],
addTemplate: false,
accountCreateUpdateTitle: this.$t('assets.AddAccount'),
iAsset: this.asset,
account: {},
@@ -258,7 +249,7 @@ export default {
name: 'Update',
title: this.$t('common.Update'),
can: this.$hasPerm('accounts.change_account') && !this.$store.getters.currentOrgIsRoot,
callback: ({ row }) => {
callback: ({ row, col }) => {
const data = {
...this.asset,
...row.asset
@@ -266,9 +257,10 @@ export default {
vm.account = row
vm.iAsset = data
vm.showAddDialog = false
vm.addTemplate = false
vm.accountCreateUpdateTitle = this.$t('assets.UpdateAccount')
setTimeout(() => {
vm.showAddDialog = true
vm.$eventBus.$emit('showCreateUpdateDrawer', 'update', { url: this.url, row, col })
})
}
}
@@ -313,8 +305,9 @@ export default {
setTimeout(() => {
vm.iAsset = this.asset
vm.account = {}
vm.addTemplate = false
vm.accountCreateUpdateTitle = this.$t('assets.AddAccount')
vm.showAddDialog = true
vm.$eventBus.$emit('showCreateUpdateDrawer', 'create', { url: vm.url })
})
}
},
@@ -330,8 +323,9 @@ export default {
setTimeout(() => {
vm.iAsset = this.asset
vm.account = {}
vm.addTemplate = true
vm.accountCreateUpdateTitle = this.$t('assets.AddAccount')
vm.showAddTemplateDialog = true
vm.$eventBus.$emit('showCreateUpdateDrawer', 'create', { url: vm.url })
})
}
},

View File

@@ -86,7 +86,7 @@ export default {
{
prop: 'protocols',
formatter: function(row) {
const data = row.protocols.map(p => {
const data = row.protocols?.map(p => {
return <el-tag size='mini'>{p.name}/{p.port} </el-tag>
})
return <span> {data} </span>

View File

@@ -17,6 +17,11 @@ export default {
default: null
}
},
data() {
return {
formatterData: ''
}
},
computed: {
displayValue() {
if ([null, undefined, ''].includes(this.value)) {
@@ -65,7 +70,17 @@ export default {
},
render(h) {
if (typeof this.formatter === 'function') {
return this.formatter(this.item, this.value)
const data = this.formatter(this.item, this.value)
if (data instanceof Promise) {
data.then(res => {
this.formatterData = res
})
} else {
this.formatterData = data
}
return (
<span>{this.formatterData}</span>
)
}
if (this.value instanceof Array) {
const newArr = this.value || []

View File

@@ -1,19 +1,22 @@
<template>
<ElFormRender
ref="form"
:class="mobile? 'mobile' : 'desktop'"
:content="fields"
:form="basicForm"
:label-position="labelPosition"
label-width="20%"
v-bind="$attrs"
v-on="$listeners"
>
<!-- slot 透传 -->
<slot v-for="item in fields" :slot="`id:${item.id}`" :name="`id:${item.id}`" />
<slot v-for="item in fields" :slot="`$id:${item.id}`" :name="`$id:${item.id}`" />
<div>
<ElFormRender
ref="form"
:class="mobile? 'mobile' : 'desktop'"
:content="fields"
:form="basicForm"
:label-position="labelPosition"
class="form-fields"
label-width="22%"
v-bind="$attrs"
v-on="$listeners"
>
<!-- slot 透传 -->
<slot v-for="item in fields" :slot="`id:${item.id}`" :name="`id:${item.id}`" />
<slot v-for="item in fields" :slot="`$id:${item.id}`" :name="`$id:${item.id}`" />
<el-form-item v-if="hasButtons" class="form-buttons">
</ElFormRender>
<div v-if="hasButtons" class="form-buttons">
<el-button
v-for="button in moreButtons"
:key="button.title"
@@ -44,8 +47,8 @@
>
{{ submitBtnText }}
</el-button>
</el-form-item>
</ElFormRender>
</div>
</div>
</template>
<script>
@@ -160,7 +163,7 @@ export default {
}
.el-form ::v-deep .el-form-item__content {
width: 75%;
width: 73%;
}
.mobile.el-form ::v-deep .el-form-item__content {
@@ -198,5 +201,7 @@ export default {
.form-buttons {
margin-top: 20px;
margin-left: 22%;
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,77 @@
<template>
<div>
<el-radio-group v-model="type" @input="handleTypeChange">
<el-radio v-for="tp of types" :key="tp.name" :label="tp.name">
{{ tp.label }}
</el-radio>
</el-radio-group>
<Select2 v-if="type === 'spec'" v-model="selected" v-bind="select2" @change="onChangeEmit" />
</div>
</template>
<script>
import Select2 from '@/components/Form/FormFields/Select2.vue'
export default {
name: 'AllOrSpec',
components: { Select2 },
props: {
value: {
type: [Array],
default: () => ([])
},
select2: {
type: Object,
default: () => ({})
},
resource: {
type: String,
default: ''
}
},
data() {
return {
type: 'all', // all, selected
types: [
{ name: 'all', label: this.$t('common.All') },
{ name: 'spec', label: this.$t('common.Spec') + this.$t('common.WordSep') + this.resource }
],
selected: []
}
},
computed: {
iValue() {
if (this.type === 'all') {
return ['all']
} else {
return this.selected
}
}
},
mounted() {
if (!this.value || this.value.length === 0) {
return
}
console.log('Value: ', this.value)
if (this.value.indexOf('all') > -1) {
this.type = 'all'
} else {
this.type = 'spec'
this.selected = this.value
}
},
methods: {
onChangeEmit() {
this.$emit('input', this.iValue)
},
handleTypeChange() {
this.$emit('input', this.iValue)
}
}
}
</script>
<style scoped>
</style>

View File

@@ -85,7 +85,7 @@ export default {
<span>{label} </span>
{helpText
? (<el-tooltip content={helpText} placement='top'>
<i class='fa fa-info-circle'></i>
<i class='fa fa-question-circle-o'></i>
</el-tooltip>) : ''}
</span>)
}

View File

@@ -88,15 +88,15 @@ export default {
getType() {
const attrType = this.attr.type || 'str'
this.$log.debug('Value field attr type: ', attrType, this.attr, this.match)
if (attrType === 'm2m') {
if (['m2m', 'fk', 'select'].includes(attrType)) {
return 'select'
} else if (attrType === 'bool') {
return 'bool'
} else if (attrType === 'select') {
return 'select'
}
if (['in', 'ip_in'].includes(this.match)) {
return 'array'
} else if (this.match.startsWith('m2m')) {
return 'select'
} else {
return 'string'
}

View File

@@ -215,6 +215,13 @@ export default {
handler(newValue, oldValue) {
},
deep: true
},
iOptions(val) {
if (val.length === 0) {
this.remote = false
} else {
this.remote = true
}
}
},
async mounted() {

View File

@@ -16,9 +16,9 @@
:is="component"
ref="SearchInput"
v-model.trim="filterValue"
:type="inputType"
:fetch-suggestions="autocomplete"
:placeholder="this.$t('common.EnterToContinue')"
:type="inputType"
class="search-input"
@blur="focus = false"
@change="handleConfirm"
@@ -31,7 +31,7 @@
class="show-password"
@click="handleShowPassword"
>
<i class="fa" :class="[isCheckShowPassword ? 'fa-eye-slash' : 'fa-eye']" />
<i :class="[isCheckShowPassword ? 'fa-eye-slash' : 'fa-eye']" class="fa" />
</span>
</div>
</template>
@@ -146,8 +146,7 @@ export default {
display: flex;
flex-wrap: wrap;
align-items: center;
padding-left: 2px;
padding-bottom: 3px;
padding: 1px 2px 1px;
border: 1px solid #dcdee2;
border-radius: 1px;
background-color: #fff;
@@ -158,12 +157,12 @@ export default {
}
&>>> .el-tag {
margin-top: 3px;
margin-top: 1px;
font-family: sans-serif !important;
}
&>>> .el-autocomplete {
height: 26px;
height: 30px;
}
}
@@ -172,7 +171,7 @@ export default {
&>>> .el-input__inner {
max-width: 100%;
border: none;
padding-left: 5px;
padding-left: 10px;
}
}
@@ -182,7 +181,7 @@ export default {
}
.filter-field >>> .el-input__inner {
height: 26px;
height: 29px;
}
.show-password {

View File

@@ -1,5 +1,5 @@
<template>
<div :class="bolder ? 'bolder' : ''" class="input-text">
<div class="input-text">
{{ value.toString() || text }}
</div>
</template>
@@ -9,15 +9,11 @@ export default {
props: {
value: {
type: [String, Boolean],
default: ''
default: () => ''
},
text: {
type: String,
default: ''
},
bolder: {
type: Boolean,
default: true
default: () => ''
}
},
data() {

View File

@@ -75,6 +75,9 @@ export default {
}
})
},
beforeDestroy() {
this.$eventBus.$off('showColumnSettingPopover')
},
methods: {
handleColumnConfirm() {
this.showColumnSettingPopover = false

View File

@@ -124,8 +124,8 @@ export default {
watch: {},
methods: {
getList() {
this.$refs.table.clearSelection()
return this.$refs.table.getList()
this.$refs.table?.clearSelection()
return this.$refs.table?.getList()
},
getData() {
return this.$refs.table.data

View File

@@ -174,6 +174,9 @@ export default {
}
})
},
beforeDestroy() {
this.$eventBus.$off('showExportDialog')
},
methods: {
showExportDialog() {
if (!this.mfaVerifyRequired) {

View File

@@ -147,6 +147,9 @@ export default {
}
})
},
beforeDestroy() {
this.$eventBus.$off('showImportDialog')
},
methods: {
closeDialog() {
this.showImportDialog = false

View File

@@ -38,6 +38,10 @@ export default {
type: Boolean,
default: false
},
createInDrawer: {
type: Boolean,
default: true
},
hasBulkDelete: defaultTrue,
canBulkDelete: defaultTrue,
hasBulkUpdate: defaultFalse,
@@ -187,6 +191,10 @@ export default {
},
methods: {
handleCreate() {
if (this.createInDrawer) {
this.$eventBus.$emit('showCreateUpdateDrawer', 'create', { url: this.tableUrl })
return
}
let route
if (typeof this.createRoute === 'string') {
route = { name: this.createRoute }

View File

@@ -79,11 +79,11 @@ export default {
computed: {
...mapGetters(['currentOrgIsRoot']),
dataTable() {
return this.$refs.dataTable.$refs.dataTable
return this.$refs.dataTable?.$refs?.dataTable
},
iHeaderActions() {
// 如果路由中锁定了 root 组织,就不在检查 root 组织下是否可以创建等
const checkRoot = !(this.$route.meta?.disableOrgsChange === true)
const checkRoot = this.$route.meta?.disableOrgsChange !== true
const actions = {
canCreate: { action: 'add', checkRoot: checkRoot },
canBulkDelete: { action: 'delete', checkRoot: false },
@@ -108,7 +108,7 @@ export default {
const config = deepmerge(this.tableConfig, {
extraQuery: this.extraQuery
})
const checkRoot = !(this.$route.meta?.disableOrgsChange === true)
const checkRoot = this.$route.meta?.disableOrgsChange !== true
const formatterArgs = {
'columnsMeta.actions.formatterArgs.canUpdate': () => {
return this.hasActionPerm('change') && (!checkRoot || !this.currentOrgIsRoot)
@@ -165,12 +165,29 @@ export default {
deep: true
}
},
mounted() {
this.$eventBus.$on('closeCreateUpdateDrawer', ({ success }) => {
if (!success) {
return
}
this.extraQuery = {
...this.extraQuery,
order: '-date_updated'
}
setTimeout(() => {
this.reloadTable()
})
})
},
beforeDestroy() {
this.$eventBus.$off('closeCreateUpdateDrawer')
},
methods: {
handleSelectionChange(val) {
this.selectedRows = val
},
reloadTable() {
this.dataTable.getList()
this.dataTable?.getList()
},
search(attrs) {
this.$emit('TagSearch', attrs)

View File

@@ -102,6 +102,9 @@ export default {
this.componentKey += 1
})
},
beforeDestroy() {
this.$eventBus.$off('treeComponentKey')
},
methods: {
hideRMenu() {
this.$refs.AutoDataZTree?.hideRMenu()

View File

@@ -21,34 +21,12 @@ const defaultPerformDelete = function({ row, col }) {
}
const defaultUpdateCallback = function({ row, col }) {
const id = row.id
let route = { params: { id: id }}
const updateRoute = this.colActions.updateRoute
console.log('Update route: ', updateRoute)
if (typeof updateRoute === 'object') {
route = Object.assign(route, updateRoute)
} else if (typeof updateRoute === 'function') {
route = updateRoute({ row, col })
} else {
route.name = updateRoute
}
this.$router.push(route)
this.$eventBus.$emit('showCreateUpdateDrawer', 'update', { url: this.url, row, col })
}
const defaultCloneCallback = function({ row, col }) {
const id = row.id
let route = { query: { clone_from: id }}
const cloneRoute = this.colActions.cloneRoute
if (typeof cloneRoute === 'object') {
route = Object.assign(route, cloneRoute)
} else if (typeof cloneRoute === 'function') {
route = cloneRoute({ row, col })
} else {
route.name = cloneRoute
}
this.$router.push(route)
console.log('Url: ', this.url)
this.$eventBus.$emit('showCreateUpdateDrawer', 'clone', { url: this.url, row, col })
}
const defaultDeleteCallback = function({ row, col, cellValue, reload }) {

View File

@@ -5,6 +5,7 @@
<script>
import BaseFormatter from './base.vue'
export default {
name: 'GrantedSystemUsersShowFormatter',
extends: BaseFormatter,
@@ -28,7 +29,7 @@ export default {
const formatterArgs = Object.assign(this.formatterArgsDefault, this.col.formatterArgs)
const url = formatterArgs.getUrl({ row: this.row, col: this.col })
const data = await this.$axios.get(url)
this.accounts = data.map((item) => item.name)
this.accounts = data['permed_accounts'].map((item) => item.name)
}
}
}

View File

@@ -57,7 +57,8 @@ export default {
onSelected: this.onSelected.bind(this),
beforeDrop: this.beforeDrop.bind(this),
onDrop: this.onDrop.bind(this),
refresh: this.refresh.bind(this)
refresh: this.refresh.bind(this),
onAsyncSuccess: this.onAsyncSuccess.bind(this)
// 尚未定义的函数
// beforeClick
// beforeDrag
@@ -89,6 +90,16 @@ export default {
$('body').unbind('mousedown')
},
methods: {
onAsyncSuccess(event, treeId, treeNode, msg) {
const nodes = JSON.parse(msg)
nodes.forEach((node) => {
if (treeNode.checked) {
const currentNode = this.zTree.getNodeByParam('id', node.id, null)
currentNode.checked = true
this.zTree.updateNode(currentNode)
}
})
},
refreshTree: function() {
// const refreshIconRef = $('#tree-refresh')
// refreshIconRef.click()

View File

@@ -1,6 +1,27 @@
<template>
<div class="markdown-body">
<VueMarkdown :source="value" />
<el-row v-if="preview">
<div class="action-bar">
<div class="action">
<span>
<i class="fa" :class="[!isShow ? 'fa-eye' : 'fa-eye-slash']" @click="onView" />
</span>
</div>
</div>
<el-col :span="span">
<el-input
v-model="iValue"
autosize
:rows="rows"
type="textarea"
@change="onChange"
/>
</el-col>
<el-col v-if="isShow" :span="span">
<VueMarkdown class="result-html" :source="iValue" :show="true" :html="true" />
</el-col>
</el-row>
<VueMarkdown v-else class="source" :source="iValue" :html="true" />
</div>
</template>
@@ -16,21 +37,71 @@ export default {
value: {
type: String,
default: ''
},
preview: {
type: Boolean,
default: false
},
rows: {
type: Number,
default: 4
}
},
data() {
return {}
return {
span: 12,
isShow: true,
iValue: this.value
}
},
methods: {
onChange() {
this.$emit('change', this.iValue)
},
onView() {
this.isShow = !this.isShow
if (this.isShow) {
this.span = 12
} else {
this.span = 24
}
}
}
}
</script>
<style lang='scss' scoped>
.markdown-body {
background-color: #f3f3f3;
}
.markdown-body * {
padding: 10px;
color: #1a1a1a;
font-size: 13px;
}
>>> .el-textarea__inner {
min-height: 210px !important;
}
.source {
padding: 6px;
background-color: #f3f3f3;
}
.result-html {
height: 100%;
min-height: 210px;
margin-left: 4px;
padding: 5px 10px;
border: 1px solid #DCDFE6;
}
.action-bar {
position: relative;
height: 30px;
line-height: 30px;
border: 1px solid #dcdfe6;
border-bottom: none;
.action {
position: absolute;
right: 6px;
i {
cursor: pointer;
}
}
}
</style>

View File

@@ -4,7 +4,8 @@ export const strMatchValues = ['exact', 'not', 'in', 'contains', 'startswith', '
export const typeMatchMapper = {
str: strMatchValues,
bool: ['exact', 'not'],
m2m: ['m2m'],
m2m: ['m2m', 'm2m_all'],
fk: ['m2m'],
ip: [...strMatchValues, 'ip_in'],
int: [...strMatchValues, 'gte', 'lte'],
select: ['in']
@@ -19,6 +20,7 @@ export const attrMatchOptions = [
{ label: i18n.t('common.Endswith'), value: 'endswith' },
{ label: i18n.t('common.Regex'), value: 'regex' },
{ label: i18n.t('common.BelongTo'), value: 'm2m' },
{ label: i18n.t('common.BelongAll'), value: 'm2m_all' },
{ label: i18n.t('common.IPMatch'), value: 'ip_in' },
{ label: i18n.t('common.GreatEqualThan'), value: 'gte' },
{ label: i18n.t('common.LessEqualThan'), value: 'lte' }

View File

@@ -109,7 +109,8 @@
"ExecutionList": "Execution list",
"Reason": "Reason",
"AccountBackup": "Account backup",
"RecipientHelpText": "Currently, only email sending is supported. If both recipients A and B are set, the account key will be split into two parts: front and back"
"RecipientHelpText": "If both recipients A and B are set, the account key will be split into two parts: front and back",
"RecipientServer":"Receiving server"
},
"DynamicUsername": "Dynamic username",
"AutoCreate": "Auto create",
@@ -498,6 +499,8 @@
"AddPassKey": "AddPassKey"
},
"common": {
"BelongAll": "Include all",
"WordSep": " ",
"Enterprise": "Enterprise",
"SyncTask": "Synchronization task",
"New": "New",
@@ -1290,7 +1293,7 @@
"FileTransfer": "File transfer",
"OnlineSession": "Online session",
"CreateCommandStorage": "Create command storage",
"CreateReplayStorage": "Create replay storage",
"CreateReplayStorage": "Create object storage",
"Dashboard": "Dashboard",
"DatabaseApp": "Database Apps",
"DatabaseAppCreate": "Database app create",
@@ -1362,7 +1365,7 @@
"ApplicationPermissionDetail": "Application permission detail",
"ApplicationPermissionUpdate": "Application permission update",
"RemoteAppUpdate": "Remote app update",
"ReplayStorageUpdate": "Replay storage update",
"ReplayStorageUpdate": "Replay object update",
"Detail": "Detail",
"Activity": "Activity",
"SessionOffline": "Sessions offline",
@@ -1472,6 +1475,7 @@
"terminal": {
"OnlineSessionHelpMsg": "The current session cannot be offline because it is an online session of the current user. Currently, only users who have logged in through web mode are recorded.",
"Offline": "Offline",
"Active": "Active",
"OfflineSuccessMsg": "Offline success",
"BulkOffline": "Bulk offline",
"Marketplace": "Marketplace",
@@ -1540,7 +1544,7 @@
"remoteAddr": "Remote addr",
"replay": "replay",
"replaySession": "Replay session",
"replayStorage": "Replay storage",
"replayStorage": "Object storage",
"riskLevel": "Risk level",
"session": "Session",
"sshPort": "SSH port",
@@ -2318,6 +2322,7 @@
},
"profile": {
"CreateAccessKey": "Create Access key",
"AccessIP": "Access IP",
"ApiKeyWarning": "To reduce the risk of AccessKey exposure, Secret is provided only during creation and cannot be queried again later. Please keep it safe.",
"PasskeyAddDisableInfo": "Your authentication source is {source}, and Passkey addition is not supported."
}

View File

@@ -109,7 +109,8 @@
"ExecutionList": "実行リスト",
"Reason": "理由",
"AccountBackup": "アカウントのバックアップ",
"RecipientHelpText": "現在はメール送信のみをサポートしており、受信者A Bが設定されている場合、アカウントの鍵は前後2つに分割されます"
"RecipientHelpText": "受信者A Bが設定されている場合、アカウントの鍵は前後2つに分割されます",
"RecipientServer":"受信サーバー"
},
"DynamicUsername": "動的ユーザー名",
"AutoCreate": "自動作成",
@@ -498,6 +499,7 @@
"AddPassKey": "パスキー(通行鍵)を追加"
},
"common": {
"BelongAll": "すべての",
"Enterprise": "企業版",
"SyncTask": "同期任務です",
"New": "新筑",
@@ -1296,7 +1298,7 @@
"OnlineSession": "オンラインセッション",
"OnlineUserDevices": "オンラインデバイス",
"CreateCommandStorage": "コマンドストアの作成",
"CreateReplayStorage": "録画ストレージ作成",
"CreateReplayStorage": "オブジェクトストレージ作成する",
"Dashboard": "ダッシュボード",
"DatabaseApp": "データベース",
"DatabaseAppCreate": "データベースアプリケーションの作成",
@@ -1367,7 +1369,7 @@
"ApplicationPermissionDetail": "アプリライセンスの詳細",
"ApplicationPermissionUpdate": "アプリ認可ルールの更新",
"RemoteAppUpdate": "リモートアプリケーションの更新",
"ReplayStorageUpdate": "録画保存の更新",
"ReplayStorageUpdate": "オブジェクトストレージを更新する",
"Detail": "詳細",
"SessionOffline": "履歴セッション",
"SessionOnline": "オンラインセッション",
@@ -1465,6 +1467,7 @@
},
"terminal": {
"Offline": "オフライン",
"Active": "アクティブ",
"OnlineSessionHelpMsg": "セッションが現在のユーザーのオンラインセッションであるため、現在のセッションをオフラインできません。現在はWebログイン済みのユーザーのみが記録されています。",
"OfflineSuccessMsg": "オフラインに成功しました",
"BulkOffline": "オフライン",
@@ -1534,7 +1537,7 @@
"remoteAddr": "リモートアドレス",
"replay": "リプレイ",
"replaySession": "再生セッション",
"replayStorage": "録画保存",
"replayStorage": "オブジェクトストレージ",
"riskLevel": "リスクレベル",
"session": "会話",
"sshPort": "SSHポート",
@@ -2309,6 +2312,7 @@
},
"profile": {
"CreateAccessKey": "Create Access key",
"AccessIP": "Access IP",
"ApiKeyWarning": "AccessKeyの漏洩リスクを低減するため、Secretは作成時にのみ提供され、後で再度クエリできません。安全に保管してください。",
"PasskeyAddDisableInfo": "あなたの認証元は {source} であり、Passkeyの追加はサポートされていません。"
}

View File

@@ -4,7 +4,7 @@
"GeneralAccounts": "普通账号",
"VirtualAccounts": "虚拟账号",
"Accounts": "账号",
"SuFrom": "切换",
"SuFrom": "切换",
"SelectAccount": "选择账号",
"AccountTemplateUpdateSecretHelpText": "账号列表展示通过模版创建的账号。更新密文时,会更新通过模版所创建账号的密文。",
"GenerateSuccessMsg": "账号生成成功",
@@ -47,7 +47,8 @@
"MailRecipient": "邮件收件人",
"IsSuccess": "是否成功",
"Reason": "原因",
"RecipientHelpText": "当前只支持邮件发送, 若收件人 A B 都设置,账号的密钥将被拆分成前后两部分"
"RecipientHelpText": "若收件人 A B 都设置,账号的密钥将被拆分成前后两部分",
"RecipientServer":"接收服务器"
},
"AccountChangeSecret": {
"PasswordRule": "密码生成规则",
@@ -476,6 +477,8 @@
"ReLoginErr": "登录时长已超过 5 分钟,请重新登录"
},
"common": {
"BelongAll": "包含所有",
"WordSep": "",
"PasswordRule": "密码生成规则",
"Length": "长度",
"Uppercase": "大写字母",
@@ -1306,7 +1309,7 @@
"OnlineSession": "在线用户",
"OnlineUserDevices": "在线用户设备",
"CreateCommandStorage": "创建命令存储",
"CreateReplayStorage": "创建录像存储",
"CreateReplayStorage": "创建对象存储",
"Dashboard": "仪表盘",
"DatabaseApp": "数据库",
"RemoteApps": "远程应用",
@@ -1384,7 +1387,7 @@
"ApplicationPermissionDetail": "应用授权详情",
"ApplicationPermissionUpdate": "更新应用授权规则",
"RemoteAppUpdate": "更新远程应用",
"ReplayStorageUpdate": "更新录像存储",
"ReplayStorageUpdate": "更新对象存储",
"Detail": "详情",
"SessionOffline": "历史会话",
"SessionOnline": "在线会话",
@@ -1438,6 +1441,7 @@
"terminal": {
"OnlineSessionHelpMsg": "无法下线当前会话,因为该会话是当前用户的在线会话。当前只记录以 Web 方式登录的用户。",
"Offline": "下线",
"Active": "活跃",
"OfflineSuccessMsg": "下线成功",
"BulkOffline": "批量下线",
"Marketplace": "应用市场",
@@ -1449,7 +1453,7 @@
"UploadFailed": "上传失败",
"Applets": "远程应用",
"AppletHosts": "应用发布机",
"AppletHostSelectHelpMessage": "连接资产时,应用发布机选择是随机的(但优先选择上次使用的),如果想为某个资产固定发布机,可以指定标签 <发布机:发布机名称> 或 <AppletHost:发布机名称>; <br>连接该发布机选择账号时,以下情况会选择用户 <b>专有账号(js开头)</b>,否则使用公用账号(jms开头)<br>&nbsp; 1. 发布机和应用都支持并发; <br>&nbsp; 2. 发布机支持并发,应用不支持并发,当前应用没有使用专有账号; <br>&nbsp; 3. 发布机不支持并发,应用支持并发或不支持,没有任一应用使用专有账号; <br> 注意: 应用支不支持并发是开发者决定,主机支不支持是发布机配置中的 单用户单会话决定",
"AppletHostSelectHelpMessage": "连接资产时,应用发布机选择是随机的(但优先选择上次使用的),如果想为某个资产固定发布机,可以指定标签 <发布机:发布机名称> 或 <AppletHost:发布机名称>; <br>连接该发布机选择账号时,以下情况会选择用户 <b>同名账号 或 专有账号(js开头)</b>,否则使用公用账号(jms开头)<br>&nbsp; 1. 发布机和应用都支持并发; <br>&nbsp; 2. 发布机支持并发,应用不支持并发,当前应用没有使用专有账号; <br>&nbsp; 3. 发布机不支持并发,应用支持并发或不支持,没有任一应用使用专有账号; <br> 注意: 应用支不支持并发是开发者决定,主机支不支持是发布机配置中的 单用户单会话决定",
"uploadZipTips": "请上传 zip 格式的文件",
"HostDeployment": "发布机部署",
"TerminalStat": "CPU/内存/磁盘",
@@ -1506,7 +1510,7 @@
"remoteAddr": "远端地址",
"replay": "回放",
"replaySession": "回放会话",
"replayStorage": "录像存储",
"replayStorage": "对象存储",
"riskLevel": "风险等级",
"session": "会话",
"sshPort": "SSH端口",
@@ -1785,7 +1789,8 @@
"DestinationPort": "目的端口",
"TestParam": "参数",
"sync": "同步",
"testHelpText": "请输入目的地址进行测试"
"testHelpText": "请输入目的地址进行测试",
"Storage": "存储设置"
},
"tickets": {
"BatchApproval": "批量审批",
@@ -1880,6 +1885,7 @@
},
"profile": {
"CreateAccessKey": "创建访问密钥",
"AccessIP": "IP 白名单",
"ApiKeyWarning": "为降低 AccessKey 泄露的风险,只在创建时提供 Secret后续不可再进行查询请妥善保存。",
"PasskeyAddDisableInfo": "你的认证来源是 {source}, 不支持添加 Passkey"
},
@@ -2097,6 +2103,7 @@
"SaveSetting": "同步设置",
"Name": "名称",
"Account": "云账号",
"CreateAccount": "创建云账号",
"Node": "节点",
"WindowsAdminUser": "Windows 特权用户",
"LinuxAdminUser": "Linux 特权用户",

View File

@@ -0,0 +1,187 @@
<template>
<el-drawer
:direction="direction"
:modal="false"
:size="width"
:title="iTitle"
:visible.sync="iVisible"
append-to-body
class="drawer generic-create-update-drawer"
wrapper-closable
v-on="$listeners"
>
<div class="el-drawer__content">
<slot>
<GenericCreateUpdateForm
v-if="iVisible"
:action="iAction"
:action-id="iActionId"
v-bind="$attrs"
@submitSuccess="onSubmitSuccess"
v-on="$listeners"
/>
</slot>
</div>
</el-drawer>
</template>
<script>
import GenericCreateUpdateForm from '../GenericCreateUpdateForm/index.vue'
export default {
name: 'GenericCreateUpdateDrawer',
components: {
GenericCreateUpdateForm
},
props: {
direction: {
type: String,
default: 'rtl'
},
title: {
type: String,
default: ''
},
resource: {
type: String,
default: ''
},
width: {
type: String,
default: '700px'
},
name: {
type: String,
default: ''
},
action: {
type: String,
default: 'create'
},
actionId: {
type: String,
default: ''
},
visible: {
type: Boolean,
default: false
},
actionMeta: {
type: Object,
default: () => ({})
}
},
data() {
return {
iVisible: this.visible,
iAction: this.action,
iActionId: this.actionId,
success: false
}
},
computed: {
iTitle() {
if (this.title) {
return this.title
}
let resource = this.resource
if (!resource) {
const routeName = this.$route.meta.title || this.$route.name
resource = routeName.replace('List', '')
.replace('列表', '')
}
let title
if (this.iAction === 'update') {
title = this.$t('common.Update') + ' ' + resource
} else {
title = this.$t('common.Create') + ' ' + resource
}
return title
}
},
watch: {
visible(val) {
this.setVisible(val)
},
iVisible(val) {
if (!val) {
this.iVisible = false
this.$eventBus.$emit(
'closeCreateUpdateDrawer',
{ action: this.iAction, actionId: this.iActionId, success: this.success }
)
this.$emit('close', { action: this.iAction, actionId: this.iActionId, success: this.success })
}
}
},
mounted() {
this.$eventBus.$on('showCreateUpdateDrawer', (action, { url, row }) => {
const tableUrl = this.$attrs.url
this.$log.debug('Table url: ', tableUrl, ' action url: ', url, 'action: ', action, 'row: ', row)
if (!tableUrl || !url) {
return
}
const tablePath = tableUrl.split('?')[0]
const actionPath = url.split('?')[0]
if (tablePath !== actionPath) {
return
}
this.iAction = action
this.iActionId = row ? row.id : ''
this.$emit('update:actionMeta', { action: action, actionId: this.iActionId, row: row })
this.setVisible(true)
})
},
beforeDestroy() {
this.$eventBus.$off('showCreateUpdateDrawer')
},
methods: {
setVisible: _.debounce(function(val) {
this.iVisible = val
}, 100),
onSubmitSuccess(res, { addContinue }) {
this.success = true
if (!addContinue) {
this.iVisible = false
}
}
}
}
</script>
<style lang='scss' scoped>
.ibox >>> .el-card__body {
padding-top: 30px;
}
.el-drawer__content {
>>> .form-fields {
overflow: auto;
max-height: calc(100vh - 150px);
}
>>> .form-buttons {
//position: absolute;
//bottom: 15px;
width: 100%;
background: white;
}
}
.el-drawer__footer {
text-align: right;
vertical-align: middle;
padding-right: 52px;
border-top: solid 1px #ebeef5;
}
.drawer >>> .el-drawer__header {
font-weight: 500;
font-size: 16px;
}
.el-drawer__footer_buttons {
margin-top: 10px;
}
</style>

View File

@@ -7,6 +7,7 @@
:has-save-continue="iHasSaveContinue"
:is-submitting="isSubmitting"
:method="method"
:submit-btn-text="submitBtnText"
:url="iUrl"
v-bind="$attrs"
@afterRemoteMeta="handleAfterRemoteMeta"
@@ -18,7 +19,6 @@
import AutoDataForm from '@/components/Form/AutoDataForm'
import { getUpdateObjURL } from '@/utils/common'
import { encryptPassword } from '@/utils/crypto'
import deepmerge from 'deepmerge'
export default {
name: 'GenericCreateUpdateForm',
@@ -31,6 +31,14 @@ export default {
type: String,
default: ''
},
action: {
type: String,
default: ''
},
actionId: {
type: [String, Number],
default: ''
},
// 更新的对象
object: {
type: Object,
@@ -116,19 +124,11 @@ export default {
return { name: routeName }
}
},
// 获取下一个路由
getNextRoute: {
type: Function,
default(res, method) {
return method === 'post' ? this.createSuccessNextRoute : this.updateSuccessNextRoute
}
},
// 获取提交的方法
submitMethod: {
type: [Function, String],
default: function() {
const params = this.$route.params
if (params.id) {
if (this.action === 'update') {
return 'put'
} else {
return 'post'
@@ -139,20 +139,20 @@ export default {
getUrl: {
type: Function,
default: function() {
const params = this.$route.params
let url = this.url
if (params.id) {
url = getUpdateObjURL(url, params.id)
if (this.action === 'update') {
url = getUpdateObjURL(url, this.actionId)
}
const clone_from = this.$route.query['clone_from']
const query = clone_from ? `clone_from=${clone_from}` : ''
if (query) {
if (url.indexOf('?') === -1) {
url = `${url}?${query}`
} else {
url = `${url}&${query}`
}
const cloneFrom = this.action === 'clone' ? this.actionId : ''
const query = cloneFrom ? `clone_from=${cloneFrom}` : ''
if (!query) {
return url
}
if (url.indexOf('?') === -1) {
url = `${url}?${query}`
} else {
url = `${url}&${query}`
}
return url
}
@@ -204,18 +204,8 @@ export default {
onPerformSuccess: {
type: Function,
default(res, method, vm, addContinue) {
const route = this.getNextRoute(res, method)
if (!(route.params && route.params.id)) {
route['params'] = deepmerge(route['params'] || {}, { 'id': res.id, order: this.extraQueryOrder })
} else {
route['params'] = deepmerge(route['params'], { order: this.extraQueryOrder })
}
this.$emit('submitSuccess', res)
this.$emit('submitSuccess', res, { method, vm, addContinue })
this.emitPerformSuccessMsg(method, res, addContinue)
if (!addContinue) {
setTimeout(() => this.$router.push(route), 100)
}
}
},
onPerformError: {
@@ -292,6 +282,13 @@ export default {
return this.hasReset
}
return this.isUpdateMethod()
},
submitBtnText() {
if (this.action === 'update') {
return this.$t('common.Update')
} else {
return this.$t('common.Create')
}
}
},
async created() {
@@ -306,6 +303,8 @@ export default {
this.loading = false
}
},
mounted() {
},
methods: {
isUpdateMethod() {
return ['put', 'patch'].indexOf(this.method.toLowerCase()) > -1
@@ -348,25 +347,26 @@ export default {
})
},
async getFormValue() {
const cloneFrom = this.$route.query['clone_from']
if ((!this.isUpdateMethod() && !cloneFrom) || !this.needGetObjectDetail) {
const cloneFrom = this.actionId
if (this.action === 'create' || !this.needGetObjectDetail) {
return Object.assign(this.form, this.initial)
}
let object = this.object
if (!object || Object.keys(object).length === 0) {
if (cloneFrom) {
const [curUrl, query] = this.url.split('?')
const url = `${curUrl}${cloneFrom}/${query ? ('?' + query) : ''}`
object = await this.getObjectDetail(url)
if (object['name']) {
object.name = this.$t('common.cloneFrom') + object.name
} else if (object['hostname']) {
object.hostname = this.$t('common.cloneFrom') + object.hostname
object.name = this.$t('common.cloneFrom') + '' + object.name
}
} else {
object = await this.getObjectDetail(this.iUrl)
if (object && Object.keys(object).length > 0) {
return object
}
if (this.action === 'clone') {
const [curUrl, query] = this.url.split('?')
const url = `${curUrl}${cloneFrom}/${query ? ('?' + query) : ''}`
object = await this.getObjectDetail(url)
if (object['name']) {
object.name = this.$t('common.cloneFrom') + object.name
} else if (object['hostname']) {
object.hostname = this.$t('common.cloneFrom') + object.hostname
object.name = this.$t('common.cloneFrom') + '' + object.name
}
} else {
object = await this.getObjectDetail(this.iUrl)
}
if (object) {
object = _.cloneDeep(object)

View File

@@ -129,13 +129,14 @@ export default {
...mapGetters(['currentOrgIsRoot']),
pageActions() {
return [
{
name: 'update',
title: this.$t('common.Update'),
can: this.validActions.canUpdate,
has: this.validActions.hasUpdate,
callback: this.validActions.updateCallback.bind(this)
},
// 暂时处理,详情中关闭更新操作
// {
// name: 'update',
// title: this.$t('common.Update'),
// can: this.validActions.canUpdate,
// has: this.validActions.hasUpdate,
// callback: this.validActions.updateCallback.bind(this)
// },
{
name: 'delete',
title: this.$t('common.Delete'),

View File

@@ -1,16 +1,17 @@
<template>
<Page v-bind="$attrs">
<GenericListTable ref="ListTable" v-bind="$attrs" />
<ListTable ref="ListTable" v-bind="$attrs" />
</Page>
</template>
<script>
import Page from '@/layout/components/Page'
import GenericListTable from '@/layout/components/GenericListTable'
import ListTable from '@/components/Table/ListTable/index'
export default {
name: 'GenericListPage',
components: {
Page, GenericListTable
Page, ListTable
}
}
</script>

View File

@@ -1,6 +1,6 @@
<template>
<el-dropdown>
<span class="el-dropdown-link">
<span class="el-dropdown-link header-lang">
{{ currentLang.title }}<i class="el-icon-arrow-down el-icon--right" />
</span>
<el-dropdown-menu slot="dropdown">
@@ -16,16 +16,16 @@ export default {
return {
LANG_COOKIE_NAME: 'django_language', // 后端Django需要的COOKIE KEY
supportLanguages: [
{
title: 'English',
code: 'en',
cookieCode: 'en'
},
{
title: '中文(简体)',
code: 'cn',
cookieCode: 'zh-hans' // cookie code是为了让后端知道当前语言
},
{
title: 'English',
code: 'en',
cookieCode: 'en'
},
{
title: '日本語',
code: 'ja',
@@ -51,7 +51,6 @@ export default {
}
},
mounted() {
this.changeLang()
this.changeMomentLang()
},
methods: {
@@ -92,5 +91,7 @@ export default {
</script>
<style scoped>
.header-lang {
color: white;
}
</style>

View File

@@ -24,6 +24,9 @@
<li class="header-item active-menu">
<Help />
</li>
<li class="header-item">
<Language />
</li>
<li class="header-item header-profile">
<AccountDropdown />
</li>
@@ -53,7 +56,7 @@ import Tickets from './Tickets'
import Organization from './Organization'
import SystemSetting from './SystemSetting'
import Logo from '../NavLeft/Logo'
import Language from './Language'
export default {
components: {
Hamburger,
@@ -64,7 +67,8 @@ export default {
WebTerminal,
SiteMessages,
SystemSetting,
Logo
Logo,
Language
},
data() {
return {}

View File

@@ -43,7 +43,7 @@
import Page from '../Page/'
import merge from 'webpack-merge'
const ACTIVE_TAB_KEY = 'activeTab'
const ACTIVE_TAB_KEY = 'tab'
export default {
name: 'TabPage',
@@ -109,18 +109,21 @@ export default {
},
created() {
this.iActiveMenu = this.getPropActiveTab()
this.setToRoute()
},
methods: {
handleTabClick(tab) {
this.$emit('tab-click', tab)
this.$emit('update:activeMenu', tab.name)
this.$cookie.set(ACTIVE_TAB_KEY, tab.name, 1)
if (this.$router.currentRoute.query[ACTIVE_TAB_KEY]) {
this.setToRoute()
},
setToRoute() {
setTimeout(() => {
this.$router.push({
query: merge(this.$route.query, { [ACTIVE_TAB_KEY]: '' })
query: merge(this.$route.query, { [ACTIVE_TAB_KEY]: this.iActiveMenu })
})
}
})
},
getPropActiveTab() {
let activeTab = ''

View File

@@ -9,6 +9,7 @@ export { default as GenericListPage } from './GenericListPage'
export { default as GenericListTable } from './GenericListTable'
export { default as GenericDetailPage } from './GenericDetailPage'
export { default as GenericCreateUpdatePage } from './GenericCreateUpdatePage'
export { default as GenericCreateUpdateDrawer } from './GenericCreateUpdateDrawer'
export { default as GenericCreateUpdateForm } from './GenericCreateUpdateForm'
export { default as GenericUpdateFormDialog } from './GenericUpdateFormDialog'
export { default as GenericListTableDialog } from './GenericListTableDialog'

View File

@@ -22,6 +22,8 @@ import ECharts from 'vue-echarts'
import service from '@/utils/request'
import { message } from '@/utils/message'
import xss from '@/utils/xss'
import ElDrawerPatch from '@/utils/elDrawerPatch.js'
import ElTableTooltipPatch from '@/utils/elTableTooltipPatch.js'
/**
* If you don't want to use mock-server
@@ -41,6 +43,9 @@ Vue.use(ElementUI, { locale })
// 如果想要中文版 element-ui按如下方式声明
// Vue.use(ElementUI)
Vue.use(ElDrawerPatch)
Vue.use(ElTableTooltipPatch)
Vue.config.productionTip = false
Vue.use(VueCookie)

View File

@@ -130,14 +130,14 @@ export default [
{
path: 'create',
name: 'CloudCreate',
component: () => import('@/views/assets/Asset/AssetCreateUpdate/CloudsPlatformCreateUpdate.vue'),
component: () => import('@/views/assets/Asset/AssetCreateUpdate/CloudCreateUpdate.vue'),
hidden: true,
meta: { title: i18n.t('route.CloudCreate'), activeMenu: '/console/assets/assets' }
},
{
path: ':id/update',
name: 'CloudUpdate',
component: () => import('@/views/assets/Asset/AssetCreateUpdate/CloudsPlatformCreateUpdate.vue'),
component: () => import('@/views/assets/Asset/AssetCreateUpdate/CloudCreateUpdate.vue'),
hidden: true,
meta: { title: i18n.t('route.CloudUpdate'), activeMenu: '/console/assets/assets' }
}

View File

@@ -24,13 +24,6 @@ export default [
hidden: true,
meta: { title: i18n.t('route.AssetPermissionCreate'), action: 'create' }
},
{
path: ':id/update',
component: () => import('@/views/perms/AssetPermission/AssetPermissionCreateUpdate.vue'),
name: 'AssetPermissionUpdate',
hidden: true,
meta: { title: i18n.t('route.AssetPermissionUpdate'), action: 'update' }
},
{
path: ':id',
component: () => import('@/views/perms/AssetPermission/AssetPermissionDetail'),

View File

@@ -64,22 +64,6 @@ export default [
name: 'UserGroupList',
meta: { title: i18n.t('route.UserGroupList'), permissions: ['users.view_usergroup'] }
},
{
path: 'create',
component: () => import('@/views/users/Group/UserGroupCreateUpdate.vue'), // Parent router-view
name: 'UserGroupCreate',
hidden: true,
meta: {
title: i18n.t('route.UserGroupCreate')
}
},
{
path: ':id/update',
component: () => import('@/views/users/Group/UserGroupCreateUpdate.vue'), // Parent router-view
name: 'UserGroupUpdate',
hidden: true,
meta: { title: i18n.t('route.UserGroupUpdate') }
},
{
path: ':id',
component: () => import('@/views/users/Group/UserGroupDetail'), // Parent router-view

View File

@@ -1,5 +1,6 @@
import Layout from '@/layout'
import i18n from '@/i18n/i18n'
import empty from '@/layout/empty.vue'
export default {
path: '/profile',
@@ -54,15 +55,31 @@ export default {
},
{
path: '/profile/api-keys',
component: () => import('@/views/profile/ApiKey'),
name: 'ApiKey',
component: empty,
meta: {
title: i18n.t('common.nav.APIKey'),
icon: 'key',
permissions: ['authentication.view_accesskey'],
resource: 'accesskey',
app: 'authentication'
}
icon: 'key'
},
redirect: '',
children: [
{
path: '',
component: () => import('@/views/profile/ApiKey'),
name: 'ApiKey',
icon: 'key',
meta: { title: i18n.t('common.nav.APIKey'), permissions: ['authentication.view_accesskey'] }
},
{
path: ':id/update',
component: () => import('@/views/profile/ApiKeyCreateUpdate/index'),
name: 'ApiKeyCreateUpdate',
hidden: true,
meta: {
title: i18n.t('common.nav.APIKey'),
permissions: ['authentication.change_accesskey'],
activeMenu: '/profile/api-keys'
}
}
]
},
{
path: '/profile/temp-password',

View File

@@ -114,6 +114,72 @@ export default {
permissions: ['settings.change_auth']
}
},
{
path: '/settings/storage',
component: empty,
redirect: '',
meta: {
title: i18n.t('setting.Storage'),
app: 'terminal',
permissions: ['settings.change_terminal']
},
children: [
{
path: '',
name: 'Storage',
component: () => import('@/views/settings/Storage'),
meta: {
title: i18n.t('setting.Storage'),
icon: 'terminal-set',
permissions: ['settings.change_terminal']
}
},
{
path: 'replay-storage/create',
name: 'CreateReplayStorage',
component: () => import('@/views/settings/Storage/ReplayStorageCreateUpdate'),
meta: {
title: i18n.t('route.CreateReplayStorage'),
activeMenu: '/settings/storage',
permissions: ['terminal.add_replaystorage']
},
hidden: true
},
{
path: 'replay-storage/:id/update',
name: 'ReplayStorageUpdate',
component: () => import('@/views/settings/Storage/ReplayStorageCreateUpdate'),
meta: {
title: i18n.t('route.ReplayStorageUpdate'),
activeMenu: '/settings/storage',
permissions: ['terminal.change_replaystorage']
},
hidden: true
},
{
path: 'command-storage/create',
name: 'CreateCommandStorage',
component: () => import('@/views/settings/Storage/CommandStorageCreateUpdate'),
meta: {
title: i18n.t('route.CreateCommandStorage'),
activeMenu: '/settings/storage',
permissions: ['terminal.add_commandstorage']
},
hidden: true
},
{
path: 'command-storage/:id/update',
name: 'CommandStorageUpdate',
component: () => import('@/views/settings/Storage/CommandStorageCreateUpdate'),
meta: {
title: i18n.t('route.CommandStorageUpdate'),
activeMenu: '/settings/storage',
permissions: ['terminal.change_commandstorage']
},
hidden: true
}
]
},
{
path: '/settings/terminal',
component: empty,
@@ -154,50 +220,6 @@ export default {
},
hidden: true
},
{
path: 'replay-storage/create',
name: 'CreateReplayStorage',
component: () => import('@/views/settings/Terminal/Storage/ReplayStorageCreateUpdate'),
meta: {
title: i18n.t('route.CreateReplayStorage'),
activeMenu: '/settings/terminal',
permissions: ['terminal.add_replaystorage']
},
hidden: true
},
{
path: 'replay-storage/:id/update',
name: 'ReplayStorageUpdate',
component: () => import('@/views/settings/Terminal/Storage/ReplayStorageCreateUpdate'),
meta: {
title: i18n.t('route.ReplayStorageUpdate'),
activeMenu: '/settings/terminal',
permissions: ['terminal.change_replaystorage']
},
hidden: true
},
{
path: 'command-storage/create',
name: 'CreateCommandStorage',
component: () => import('@/views/settings/Terminal/Storage/CommandStorageCreateUpdate'),
meta: {
title: i18n.t('route.CreateCommandStorage'),
activeMenu: '/settings/terminal',
permissions: ['terminal.add_commandstorage']
},
hidden: true
},
{
path: 'command-storage/:id/update',
name: 'CommandStorageUpdate',
component: () => import('@/views/settings/Terminal/Storage/CommandStorageCreateUpdate'),
meta: {
title: i18n.t('route.CommandStorageUpdate'),
activeMenu: '/settings/terminal',
permissions: ['terminal.change_commandstorage']
},
hidden: true
},
{
path: 'endpoint/create',
name: 'EndpointCreate',
@@ -275,17 +297,6 @@ export default {
activeMenu: '/settings/applets'
}
},
{
path: 'hosts/create',
name: 'AppletHostCreate',
component: () => import('@/views/settings/Applet/AppletHost/AppletHostCreateUpdate'),
hidden: true,
meta: {
title: i18n.t('route.AppletHostCreate'),
permissions: ['terminal.add_applethost'],
activeMenu: '/settings/applets'
}
},
{
path: 'hosts/:id',
name: 'AppletHostDetail',

View File

@@ -51,17 +51,6 @@ export default {
showOrganization: false
},
children: [
{
path: 'request-host-perm/create',
name: 'RequestAssetPermTicketCreateUpdate',
component: () => import('@/views/tickets/RequestAssetPerm/CreateUpdate'),
meta: {
title: i18n.t('tickets.OpenTicket'),
permissions: ['tickets.view_ticket'],
activeMenu: '/tickets/my-tickets'
},
hidden: true
},
{
path: 'request-host-perm/:id',
name: 'AssetsTicketDetail',

View File

@@ -195,17 +195,6 @@ export default {
activeMenu: '/workbench/ops/templates'
}
},
{
path: 'adhoc/create',
name: 'AdhocCreate',
hidden: true,
component: () => import('@/views/ops/Template/Adhoc/AdhocUpdateCreate'),
meta: {
title: i18n.t('ops.createAdhoc'),
permissions: ['ops.add_adhoc'],
activeMenu: '/workbench/ops/templates'
}
},
{
path: 'adhoc/:id',
component: () => import('@/views/ops/Template/Adhoc/AdhocDetail'),
@@ -217,17 +206,6 @@ export default {
activeMenu: '/workbench/ops/templates'
}
},
{
path: 'playbook/create',
name: 'PlaybookCreate',
hidden: true,
component: () => import('@/views/ops/Template/Playbook/PlaybookCreateUpdate'),
meta: {
title: i18n.t('ops.CreatePlaybook'),
permissions: ['ops.add_playbook'],
activeMenu: '/workbench/ops/templates'
}
},
{
path: 'playbook/:id/update',
name: 'PlaybookUpdate',
@@ -251,6 +229,16 @@ export default {
}
}
]
},
{
path: '/workbench/system-tools',
name: 'SystemTools',
component: () => import('@/views/settings/Tool'),
meta: {
title: i18n.t('setting.SystemTools'),
icon: 'tools',
permissions: ['rbac.view_systemtools']
}
}
]
}

View File

@@ -30,6 +30,7 @@ const getters = {
currentUserIsAdmin: state => state.users.isAdmin,
hasValidLicense: state => state.settings.hasValidLicense,
isSystemAdmin: state => state.users.profile.system_roles.some(i => (i?.id === '00000000-0000-0000-0000-000000000001')),
sqlQueryCounter: state => state.common.sqlQueryCounter
sqlQueryCounter: state => state.common.sqlQueryCounter,
currentCreateArgs: state => state.common.createArgs
}
export default getters

View File

@@ -6,7 +6,7 @@ const getDefaultState = () => {
metaPromiseMap: {},
isRouterAlive: true,
sqlQueryCounter: [],
confirmDialogVisible: false
createArgs: {}
}
}
@@ -29,8 +29,8 @@ const mutations = {
state.sqlQueryCounter.shift()
}
},
setConfirmDialogVisible: (state, show) => {
state.confirmDialogVisible = show
SET_CREATE_ARGS: (state, args) => {
state.createArgs = args
}
}
@@ -79,8 +79,8 @@ const actions = {
}
commit('addSQLQueryCounter', { url, count: sqlCount })
},
showConfirmDialog({ commit, state }, show) {
commit('setConfirmDialogVisible', show)
setCreateArgs({ commit, state }, args) {
commit('SET_CREATE_ARGS', args)
}
}

View File

@@ -24,6 +24,7 @@
body {
height: calc(100% - 50px);
overflow: hidden;
-moz-osx-font-smoothing: grayscale;
color: #676a6c;
font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;

View File

@@ -0,0 +1,28 @@
import { Drawer } from 'element-ui'
const ElDrawer = {
extends: Drawer,
data() {
return {
wrapperMouseDowned: false
}
},
mounted() {
this.$el.onmousedown = (e) => {
this.wrapperMouseDowned = e.target.classList.contains('el-drawer__container')
}
},
methods: {
handleWrapperClick() {
if (this.wrapperClosable && this.wrapperMouseDowned) {
this.closeDrawer()
}
}
}
}
export default {
install(Vue) {
Vue.component(Drawer.name, ElDrawer)
}
}

View File

@@ -0,0 +1,114 @@
// 鼠标可移入tooltip功能
import { Table } from 'element-ui'
import { getCell, getColumnByCell } from 'element-ui/packages/table/src/util'
import { getStyle, hasClass } from 'element-ui/src/utils/dom'
import i18n from '@/i18n/i18n'
import { message } from './message'
Object.assign(Table.components.TableBody.methods, {
handleCellMouseEnter(event, row) {
const { table } = this
const cell = getCell(event)
if (cell) {
const column = getColumnByCell(table, cell)
table.hoverState = { cell, column, row }
const { hoverState } = table
table.$emit('cell-mouse-enter', hoverState.row, hoverState.column, hoverState.cell, event)
}
// 判断是否text-overflow, 如果是就显示tooltip
const cellChild = event.target.querySelector('.cell')
if (!(hasClass(cellChild, 'el-tooltip') && cellChild.childNodes.length)) {
return
}
// use range width instead of scrollWidth to determine whether the text is overflowing
// to address a potential FireFox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1074543#c3
const range = document.createRange()
range.setStart(cellChild, 0)
range.setEnd(cellChild, cellChild.childNodes.length)
const rangeWidth = range.getBoundingClientRect().width
const padding = (parseInt(getStyle(cellChild, 'paddingLeft'), 10) || 0) +
(parseInt(getStyle(cellChild, 'paddingRight'), 10) || 0)
if (
(rangeWidth + padding > cellChild.offsetWidth ||
cellChild.scrollWidth > cellChild.offsetWidth) &&
this.$refs.tooltip
) {
const { tooltip } = this.$refs
const { tooltipEnterable } = this.table
const showTooltip = () => {
this.tooltipContent = cell.innerText || cell.textContent
tooltip.referenceElm = cell
tooltip.doDestroy()
tooltip.setExpectedState(true)
this.activateTooltip(tooltip)
tooltip.$refs.popper.style.cursor = 'pointer'
// 点击复制
tooltip.$refs.popper.onclick = () => {
message.success(i18n.t('common.CopySuccess'))
}
}
if (tooltipEnterable && tooltip.showPopper) {
clearTimeout(tooltip.timeoutEnter)
tooltip.timeoutEnter = setTimeout(() => {
if (!tooltip.expectedState) {
tooltip.handleClosePopper()
}
tooltip.timeoutEnter = null
}, 100)
return
}
if (!tooltipEnterable && tooltip.$refs.popper) {
tooltip.$refs.popper.style.display = 'none'
}
showTooltip()
}
},
handleCellMouseLeave(event) {
const { tooltip } = this.$refs
if (tooltip) {
tooltip.setExpectedState(false)
const { tooltipEnterable } = this.table
if (tooltipEnterable) {
clearTimeout(tooltip.timeoutLeave)
tooltip.timeoutLeave = setTimeout(() => {
if (!tooltip.expectedState) {
tooltip.handleClosePopper()
}
tooltip.timeoutLeave = null
}, 100)
} else {
tooltip.handleClosePopper()
}
}
const cell = getCell(event)
if (!cell) return
const oldHoverState = this.table.hoverState || {}
this.table.$emit(
'cell-mouse-leave',
oldHoverState.row,
oldHoverState.column,
oldHoverState.cell,
event,
)
}
})
/**
* @description 扩展el-table实现当showOverflowTooltip时鼠标可移入tooltip功能
* @prop {Boolean} tooltipEnterable 仅在列属性showOverflowTooltip为true时生效鼠标是否可进入到 tooltip 中默认为true
*/
const ElTable = {
extends: Table,
props: {
tooltipEnterable: {
type: Boolean,
default: true
}
}
}
export default (Vue) => {
Vue.component('ElTable', ElTable)
}

View File

@@ -2,12 +2,10 @@
import store from '@/store'
import router, { resetRouter } from '@/router'
import Vue from 'vue'
import VueCookie from 'vue-cookie'
import { message } from '@/utils/message'
import orgUtil from '@/utils/org'
import orgs from '@/api/orgs'
import { getPropView, isViewHasOrgs } from '@/utils/jms'
import request from '@/utils/request'
const whiteList = ['/login', process.env.VUE_APP_LOGIN_PATH] // no redirect whitelist
@@ -20,24 +18,24 @@ async function checkLogin({ to, from, next }) {
next()
}
// Determine whether the user has logged in
const sessionExpire = VueCookie.get('jms_session_expire')
if (!sessionExpire) {
request.get(process.env['VUE_APP_LOGOUT_PATH']).finally(() => {
window.location = process.env.VUE_APP_LOGIN_PATH
})
return reject('No session mark found in cookie')
} else if (sessionExpire === 'close') {
let startTime = new Date().getTime()
setInterval(() => {
const endTime = new Date().getTime()
const delta = (endTime - startTime)
startTime = endTime
Vue.$log.debug('Set session expire: ', delta)
VueCookie.set('jms_session_expire', 'close', { expires: '2m' })
}, 10 * 1000)
} else if (sessionExpire === 'age') {
Vue.$log.debug('Session expire on age')
}
// const sessionExpire = VueCookie.get('jms_session_expire')
// if (!sessionExpire) {
// request.get(process.env['VUE_APP_LOGOUT_PATH']).finally(() => {
// window.location = process.env.VUE_APP_LOGIN_PATH
// })
// return reject('No session mark found in cookie')
// } else if (sessionExpire === 'close') {
// let startTime = new Date().getTime()
// setInterval(() => {
// const endTime = new Date().getTime()
// const delta = (endTime - startTime)
// startTime = endTime
// Vue.$log.debug('Set session expire: ', delta)
// VueCookie.set('jms_session_expire', 'close', { expires: '2m' })
// }, 10 * 1000)
// } else if (sessionExpire === 'age') {
// Vue.$log.debug('Session expire on age')
// }
try {
return await store.dispatch('users/getProfile')
} catch (e) {

View File

@@ -1,15 +1,16 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
<GenericCreateUpdateDrawer v-bind="$data" />
</template>
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import { GenericCreateUpdateDrawer } from '@/layout/components'
import getChangeSecretFields from '@/views/accounts/AccountBackup/fields'
import { encryptPassword } from '@/utils/crypto'
export default {
name: 'AccountBackupPlanUpdate',
components: {
GenericCreateUpdatePage
GenericCreateUpdateDrawer
},
data() {
const vm = this
@@ -19,7 +20,18 @@ export default {
fields: [
[this.$t('common.Basic'), ['name']],
[this.$t('accounts.AccountBackup.Types'), ['types']],
[this.$t('accounts.AccountBackup.Backup'), ['recipients_part_one', 'recipients_part_two']],
[this.$t('accounts.AccountBackup.Backup'),
[
'backup_type',
'is_password_divided_by_email',
'recipients_part_one',
'recipients_part_two',
'is_password_divided_by_obj_storage',
'obj_recipients_part_one',
'obj_recipients_part_two',
'zip_encrypt_password'
]
],
[this.$t('xpack.Timer'), ['is_periodic', 'crontab', 'interval']],
[this.$t('common.Other'), ['comment']]
],
@@ -32,8 +44,13 @@ export default {
is_periodic: fields.is_periodic,
crontab: fields.crontab,
interval: fields.interval,
is_password_divided_by_email: fields.is_password_divided_by_email,
zip_encrypt_password: fields.zip_encrypt_password,
is_password_divided_by_obj_storage: fields.is_password_divided_by_obj_storage,
recipients_part_one: fields.recipients_part_one,
recipients_part_two: fields.recipients_part_two,
obj_recipients_part_one: fields.obj_recipients_part_one,
obj_recipients_part_two: fields.obj_recipients_part_two,
types: {
component: 'el-cascader',
label: this.$t('accounts.AccountBackup.Types'),
@@ -57,6 +74,9 @@ export default {
createSuccessNextRoute: { name: 'AccountBackupList' },
updateSuccessNextRoute: { name: 'AccountBackupList' },
cleanFormValue(data) {
if (data['zip_encrypt_password'] !== '') {
data['zip_encrypt_password'] = encryptPassword(data['zip_encrypt_password'])
}
if (data['interval'] === '') {
delete data['interval']
}

View File

@@ -1,16 +1,21 @@
<template>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<div>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<AccountBackupCreateUpdate />
</div>
</template>
<script>
import { GenericListTable } from '@/layout/components'
import AccountBackupCreateUpdate from './AccountBackupCreateUpdate.vue'
import { ArrayFormatter, DetailFormatter } from '@/components/Table/TableFormatters'
import { openTaskPage } from '@/utils/jms'
export default {
name: 'AccountBackupPlanList',
components: {
GenericListTable
GenericListTable,
AccountBackupCreateUpdate
},
data() {
const vm = this
@@ -64,7 +69,7 @@ export default {
return {
name: 'AccountBackupList',
query: {
activeTab: 'AccountBackupPlanExecutionList',
tab: 'AccountBackupPlanExecutionList',
plan_id: row.id
}
}
@@ -74,12 +79,8 @@ export default {
actions: {
width: '164px',
formatterArgs: {
onClone: ({ row }) => {
vm.$router.push({ name: 'AccountBackupPlanCreate', query: { clone_from: row.id }})
},
onUpdate: ({ row }) => {
vm.$router.push({ name: 'AccountBackupPlanUpdate', params: { id: row.id }})
},
hasClone: true,
hasUpdate: true,
extraActions: [
{
title: vm.$t('xpack.Execute'),

View File

@@ -12,6 +12,9 @@ function getAccountBackupFields() {
const recipients_part_one = {
label: i18n.t('accounts.AccountChangeSecret.Addressee') + ' A',
helpText: i18n.t('accounts.AccountBackup.RecipientHelpText'),
hidden: (formValue) => {
return formValue.backup_type !== 'email'
},
el: {
value: [],
ajax: {
@@ -26,6 +29,9 @@ function getAccountBackupFields() {
const recipients_part_two = {
label: i18n.t('accounts.AccountChangeSecret.Addressee') + ' B',
helpText: i18n.t('accounts.AccountBackup.RecipientHelpText'),
hidden: (formValue) => {
return !(formValue.backup_type === 'email' && formValue.is_password_divided_by_email)
},
el: {
value: [],
ajax: {
@@ -36,6 +42,39 @@ function getAccountBackupFields() {
}
}
}
const obj_recipients_part_one = {
label: i18n.t('accounts.AccountBackup.RecipientServer') + ' A',
helpText: i18n.t('accounts.AccountBackup.RecipientHelpText'),
hidden: (formValue) => {
return formValue.backup_type !== 'object_storage'
},
el: {
value: [],
ajax: {
url: '/api/v1/terminal/replay-storages/?type=sftp&fields_size=mini',
transformOption: (item) => {
return { label: item.name + '(' + item.meta.SFTP_HOST + ':' + item.meta.SFTP_ROOT_PATH + ')', value: item.id }
}
}
}
}
const obj_recipients_part_two = {
label: i18n.t('accounts.AccountBackup.RecipientServer') + ' B',
helpText: i18n.t('accounts.AccountBackup.RecipientHelpText'),
hidden: (formValue) => {
return !(formValue.backup_type === 'object_storage' && formValue.is_password_divided_by_obj_storage)
},
el: {
value: [],
ajax: {
url: '/api/v1/terminal/replay-storages/?type=sftp&fields_size=mini',
transformOption: (item) => {
return { label: item.name + '(' + item.meta.SFTP_HOST + ':' + item.meta.SFTP_ROOT_PATH + ')', value: item.id }
}
}
}
}
const is_periodic = {
type: 'switch'
@@ -61,13 +100,32 @@ function getAccountBackupFields() {
{ validator: validatorInterval }
]
}
const is_password_divided_by_email = {
hidden: (formValue) => {
return formValue.backup_type !== 'email'
}
}
const is_password_divided_by_obj_storage = {
hidden: (formValue) => {
return formValue.backup_type !== 'object_storage'
}
}
const zip_encrypt_password = {
hidden: (formValue) => {
return formValue.backup_type !== 'object_storage'
}
}
return {
is_periodic: is_periodic,
crontab: crontab,
interval: interval,
is_password_divided_by_email: is_password_divided_by_email,
is_password_divided_by_obj_storage: is_password_divided_by_obj_storage,
recipients_part_one: recipients_part_one,
recipients_part_two: recipients_part_two
recipients_part_two: recipients_part_two,
obj_recipients_part_one: obj_recipients_part_one,
obj_recipients_part_two: obj_recipients_part_two,
zip_encrypt_password: zip_encrypt_password
}
}

View File

@@ -1,16 +1,16 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
<GenericCreateUpdateDrawer v-bind="$data" />
</template>
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import { GenericCreateUpdateDrawer } from '@/layout/components'
import { getChangeSecretFields } from '@/views/accounts/AccountChangeSecret/fields'
import { AssetSelect, AutomationParams } from '@/components'
export default {
name: 'AccountChangeSecretCreateUpdate',
components: {
GenericCreateUpdatePage
GenericCreateUpdateDrawer
},
data() {
return {

View File

@@ -4,7 +4,8 @@
<script>
import GenericListTable from '@/layout/components/GenericListTable'
import { DetailFormatter } from '@/components/Table/TableFormatters'
import { ActionsFormatter, DetailFormatter } from '@/components/Table/TableFormatters'
import { openTaskPage } from '@/utils/jms'
export default {
name: 'AccountChangeSecretExecutionTaskList',
@@ -23,7 +24,7 @@ export default {
tableConfig: {
url: `/api/v1/accounts/change-secret-records/?execution_id=${this.object.id}`,
columns: [
'asset', 'account', 'date_finished', 'is_success', 'error'
'asset', 'account', 'date_finished', 'is_success', 'error', 'actions'
],
columnsMeta: {
asset: {
@@ -59,13 +60,40 @@ export default {
}
},
is_success: {
label: this.$t('accounts.AccountChangeSecret.Success')
label: this.$t('accounts.AccountChangeSecret.Success'),
formatter: (row) => {
if (row.status === 'pending') {
return <i Class='fa fa fa-spinner fa-spin'/>
}
if (row.is_success) {
return <i Class='fa fa-check text-primary'/>
}
return <i Class='fa fa-times text-danger'/>
}
},
timedelta: {
label: this.$t('accounts.AccountChangeSecret.TimeDelta'),
width: '90px',
formatter: function(row) {
return row.timedelta ? row.timedelta.toFixed(2) + 's' : 0
actions: {
formatter: ActionsFormatter,
formatterArgs: {
hasUpdate: false,
hasDelete: false,
hasClone: false,
moreActionsTitle: this.$t('common.More'),
extraActions: [
{
name: 'Retry',
title: this.$t('accounts.AccountChangeSecret.Retry'),
can: this.$hasPerm('accounts.add_changesecretexecution'),
type: 'primary',
callback: ({ row }) => {
this.$axios.post(
'/api/v1/accounts/change-secret-records/execute/',
{ record_id: row.id }
).then(res => {
openTaskPage(res['task'])
})
}
}
]
}
}
}

View File

@@ -35,7 +35,7 @@ export default {
attrs: {
type: 'primary',
label: this.$t('accounts.AccountChangeSecret.Execute'),
disabled: !this.$hasPerm('accounts.add_changesecretexection') || !this.object.is_active
disabled: !this.$hasPerm('accounts.add_changesecretexecution') || !this.object.is_active
},
callbacks: {
click: function() {

View File

@@ -1,16 +1,21 @@
<template>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<div>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<AccountChangeSecretCreateUpdate />
</div>
</template>
<script>
import { GenericListTable } from '@/layout/components'
import AccountChangeSecretCreateUpdate from './AccountChangeSecretCreateUpdate.vue'
import { DetailFormatter } from '@/components/Table/TableFormatters'
import { openTaskPage } from '@/utils/jms'
export default {
name: 'AccountChangeSecretList',
components: {
GenericListTable
GenericListTable,
AccountChangeSecretCreateUpdate
},
data() {
const vm = this
@@ -69,7 +74,7 @@ export default {
return {
name: 'AccountChangeSecretList',
query: {
activeTab: 'AccountChangeSecretExecutionList',
tab: 'AccountChangeSecretExecutionList',
automation_id: row.id
}
}
@@ -82,12 +87,6 @@ export default {
actions: {
width: '164px',
formatterArgs: {
onClone: ({ row }) => {
vm.$router.push({ name: 'AccountChangeSecretCreate', query: { clone_from: row.id }})
},
onUpdate: ({ row }) => {
vm.$router.push({ name: 'AccountChangeSecretUpdate', params: { id: row.id }})
},
extraActions: [
{
title: vm.$t('xpack.Execute'),

View File

@@ -1,16 +1,21 @@
<template>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<div>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<TaskCreateUpdate />
</div>
</template>
<script>
import { GenericListTable } from '@/layout/components'
import { DetailFormatter } from '@/components/Table/TableFormatters'
import TaskCreateUpdate from './TaskCreateUpdate.vue'
import { openTaskPage } from '@/utils/jms'
export default {
name: 'AccountGatherTaskList',
components: {
GenericListTable
GenericListTable,
TaskCreateUpdate
},
data() {
const vm = this
@@ -33,7 +38,7 @@ export default {
formatterArgs: {
route: 'AccountGatherTaskDetail',
routeQuery: {
activeTab: 'Detail'
tab: 'Detail'
}
}
},
@@ -62,7 +67,7 @@ export default {
return {
name: 'AccountGatherList',
query: {
activeTab: 'AccountGatherTaskExecutionList',
tab: 'AccountGatherTaskExecutionList',
automation_id: row.id
}
}

View File

@@ -1,21 +1,22 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
<GenericCreateUpdateDrawer v-bind="$data" />
</template>
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import { GenericCreateUpdateDrawer } from '@/layout/components'
import { CronTab } from '@/components'
import i18n from '@/i18n/i18n'
export default {
components: {
GenericCreateUpdatePage
GenericCreateUpdateDrawer
},
data() {
return {
fields: [
[this.$t('common.Basic'), ['name', 'nodes']],
[this.$t('xpack.Timer'), ['is_periodic', 'crontab', 'interval']],
[this.$t('common.Other'), ['is_sync_account', 'is_active', 'comment']]
[this.$t('common.Other'), ['is_sync_account', 'is_active', 'recipients', 'comment']]
],
url: '/api/v1/accounts/gather-account-automations/',
hasDetailInMsg: false,
@@ -54,6 +55,19 @@ export default {
},
is_periodic: {
type: 'switch'
},
recipients: {
label: i18n.t('accounts.AccountChangeSecret.Addressee'),
helpText: i18n.t('accounts.AccountChangeSecret.OnlyMailSend'),
el: {
value: [],
ajax: {
url: '/api/v1/users/users/?fields_size=mini',
transformOption: (item) => {
return { label: item.name + '(' + item.username + ')', value: item.id }
}
}
}
}
},
createSuccessNextRoute: { name: 'AccountGatherList' },

View File

@@ -1,17 +1,17 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
<GenericCreateUpdateDrawer v-bind="$data" />
</template>
<script>
import i18n from '@/i18n/i18n'
import { GenericCreateUpdatePage } from '@/layout/components'
import { GenericCreateUpdateDrawer } from '@/layout/components'
import { getChangeSecretFields } from '@/views/accounts/AccountChangeSecret/fields'
import { AssetSelect, AutomationParams } from '@/components'
export default {
name: 'AccountPushCreateUpdate',
components: {
GenericCreateUpdatePage
GenericCreateUpdateDrawer
},
data() {
return {

View File

@@ -35,7 +35,7 @@ export default {
attrs: {
type: 'primary',
label: this.$t('accounts.AccountChangeSecret.Execute'),
disabled: !this.$hasPerm('accounts.add_changesecretexection') || !this.object.is_active
disabled: !this.$hasPerm('accounts.add_changesecretexecution') || !this.object.is_active
},
callbacks: {
click: function() {

View File

@@ -4,10 +4,11 @@
<script>
import GenericListTable from '@/layout/components/GenericListTable'
import { DetailFormatter } from '@/components/Table/TableFormatters'
import { ActionsFormatter, DetailFormatter } from '@/components/Table/TableFormatters'
import { openTaskPage } from '@/utils/jms'
export default {
name: 'AccountChangeSecretExecutionTaskList',
name: 'AccountPushExecutionTaskList',
components: {
GenericListTable
},
@@ -23,7 +24,7 @@ export default {
tableConfig: {
url: `/api/v1/accounts/push-account-records/?execution_id=${this.object.id}`,
columns: [
'asset', 'account', 'date_finished', 'is_success', 'error'
'asset', 'account', 'date_finished', 'is_success', 'error', 'actions'
],
columnsMeta: {
asset: {
@@ -59,13 +60,40 @@ export default {
}
},
is_success: {
label: this.$t('accounts.AccountChangeSecret.Success')
label: this.$t('accounts.AccountChangeSecret.Success'),
formatter: (row) => {
if (row.status === 'pending') {
return <i Class='fa fa fa-spinner fa-spin'/>
}
if (row.is_success) {
return <i Class='fa fa-check text-primary'/>
}
return <i Class='fa fa-times text-danger'/>
}
},
timedelta: {
label: this.$t('accounts.AccountChangeSecret.TimeDelta'),
width: '90px',
formatter: function(row) {
return row.timedelta ? row.timedelta.toFixed(2) + 's' : 0
actions: {
formatter: ActionsFormatter,
formatterArgs: {
hasUpdate: false,
hasDelete: false,
hasClone: false,
moreActionsTitle: this.$t('common.More'),
extraActions: [
{
name: 'Retry',
title: this.$t('accounts.AccountChangeSecret.Retry'),
can: this.$hasPerm('accounts.add_changesecretexecution'),
type: 'primary',
callback: ({ row }) => {
this.$axios.post(
'/api/v1/accounts/push-account-records/execute/',
{ record_id: row.id }
).then(res => {
openTaskPage(res['task'])
})
}
}
]
}
}
}

View File

@@ -32,6 +32,11 @@ export default {
title: this.$t('common.BasicInfo'),
name: 'AccountPushExecutionInfo',
hidden: () => !this.$hasPerm('accounts.view_pushaccountexecution')
},
{
title: this.$t('accounts.AccountChangeSecret.TaskList'),
name: 'AccountPushExecutionTaskList',
hidden: () => !this.$hasPerm('accounts.view_changesecretrecord')
}
],
getTitle: this.getExecutionTitle

View File

@@ -1,16 +1,21 @@
<template>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<div>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<AccountPushCreateUpdate />
</div>
</template>
<script>
import { DetailFormatter } from '@/components/Table/TableFormatters'
import { openTaskPage } from '@/utils/jms'
import { GenericListTable } from '@/layout/components'
import AccountPushCreateUpdate from './AccountPushCreateUpdate.vue'
export default {
name: 'AccountPushList',
components: {
GenericListTable
GenericListTable,
AccountPushCreateUpdate
},
data() {
const vm = this
@@ -80,7 +85,7 @@ export default {
return {
name: 'AccountPushList',
query: {
activeTab: 'AccountPushExecutionList',
tab: 'AccountPushExecutionList',
automation_id: row.id
}
}

View File

@@ -1,16 +1,16 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
<GenericCreateUpdateDrawer v-bind="$data" />
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import GenericCreateUpdateDrawer from '@/layout/components/GenericCreateUpdateDrawer'
import { templateFields, templateFieldsMeta } from './const.js'
import { encryptPassword } from '@/utils/crypto'
export default {
name: 'GatewayCreateUpdate',
components: {
GenericCreateUpdatePage
GenericCreateUpdateDrawer
},
data() {
const vm = this

View File

@@ -8,11 +8,13 @@
:url="secretUrl"
:visible.sync="showViewSecretDialog"
/>
<AccountTemplateCreateUpdate />
</div>
</template>
<script>
import { GenericListPage } from '@/layout/components'
import AccountTemplateCreateUpdate from './AccountTemplateCreateUpdate.vue'
import { ActionsFormatter } from '@/components/Table/TableFormatters'
import ViewSecret from '@/components/Apps/AccountListTable/ViewSecret'
@@ -20,6 +22,7 @@ export default {
name: 'AccountTemplateList',
components: {
GenericListPage,
AccountTemplateCreateUpdate,
ViewSecret
},
data() {
@@ -86,11 +89,7 @@ export default {
hasExport: false,
hasImport: false,
hasMoreActions: false,
createRoute: () => {
return {
name: 'AccountTemplateCreate'
}
}
createRoute: 'AccountTemplateCreate'
}
}
}

View File

@@ -1,14 +1,19 @@
<template>
<ListTable v-bind="config" />
<div>
<ListTable v-bind="config" />
<VirtualUpdate />
</div>
</template>
<script>
import { ListTable } from '@/components'
import VirtualUpdate from './VirtualUpdate.vue'
export default {
name: 'VirtualAccountList',
components: {
ListTable
ListTable,
VirtualUpdate
},
data() {
return {

View File

@@ -1,14 +1,14 @@
<template>
<GenericCreateUpdatePage v-bind="config" @getObjectDone="handleObjectDone" />
<GenericCreateUpdateDrawer v-bind="config" @getObjectDone="handleObjectDone" />
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage/index.vue'
import GenericCreateUpdateDrawer from '@/layout/components/GenericCreateUpdateDrawer/index.vue'
import TextReadonly from '@/components/Form/FormFields/TextReadonly.vue'
export default {
name: 'CreateUpdate',
components: { GenericCreateUpdatePage },
components: { GenericCreateUpdateDrawer },
data() {
return {
object: {},

View File

@@ -1,19 +1,19 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
<GenericCreateUpdateDrawer v-bind="$data" />
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import rules from '@/components/Form/DataForm/rules'
import { userJSONSelectMeta } from '@/views/users/const'
import { assetJSONSelectMeta } from '@/views/assets/const'
import AccountFormatter from '@/views/perms/AssetPermission/components/AccountFormatter.vue'
import { WeekCronSelect } from '@/components/Form/FormFields'
import GenericCreateUpdateDrawer from '@/layout/components/GenericCreateUpdateDrawer'
export default {
name: 'AclCreateUpdate',
components: {
GenericCreateUpdatePage
GenericCreateUpdateDrawer
},
data() {
return {

View File

@@ -1,13 +1,18 @@
<template>
<GenericListPage :header-actions="headerActions" :help-message="helpMsg" :table-config="tableConfig" />
<div>
<GenericListPage :header-actions="headerActions" :help-message="helpMsg" :table-config="tableConfig" />
<AssetLoginAclCreateUpdate />
</div>
</template>
<script>
import { GenericListPage } from '@/layout/components'
import AssetLoginAclCreateUpdate from './AssetLoginAclCreateUpdate.vue'
export default {
components: {
GenericListPage
GenericListPage,
AssetLoginAclCreateUpdate
},
data() {
return {

View File

@@ -1,5 +1,5 @@
<template>
<GenericCreateUpdatePage
<GenericCreateUpdateDrawer
:fields="fields"
:fields-meta="fieldsMeta"
:initial="initial"
@@ -9,7 +9,7 @@
</template>
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import GenericCreateUpdateDrawer from '@/layout/components/GenericCreateUpdateDrawer/index.vue'
import AccountFormatter from '@/views/perms/AssetPermission/components/AccountFormatter.vue'
import rules from '@/components/Form/DataForm/rules'
import { userJSONSelectMeta } from '@/views/users/const'
@@ -18,7 +18,7 @@ import { assetJSONSelectMeta } from '@/views/assets/const'
export default {
name: 'CommandFilterAclCreateUpdate',
components: {
GenericCreateUpdatePage
GenericCreateUpdateDrawer
},
data() {
return {

View File

@@ -2,6 +2,7 @@
<div>
<el-alert type="success">{{ helpMsg }}</el-alert>
<ListTable :header-actions="headerActions" :table-config="tableConfig" />
<CommandFilterAclCreateUpdate />
</div>
</template>
@@ -10,10 +11,12 @@
import { ListTable } from '@/components'
import { DetailFormatter } from '@/components/Table/TableFormatters'
import AmountFormatter from '@/components/Table/TableFormatters/AmountFormatter.vue'
import CommandFilterAclCreateUpdate from './CommandFilterAclCreateUpdate.vue'
export default {
components: {
ListTable
ListTable,
CommandFilterAclCreateUpdate
},
data() {
return {
@@ -44,15 +47,8 @@ export default {
width: '160px',
formatter: AmountFormatter,
formatterArgs: {
route: 'AccountGatherList',
getRoute({ row }) {
return {
name: 'CommandFilterAclList',
query: {
activeTab: 'CommandGroup',
command_filters: row.id
}
}
routeQuery: {
tab: 'GroupUser'
}
}
}

View File

@@ -1,5 +1,5 @@
<template>
<GenericCreateUpdatePage
<GenericCreateUpdateDrawer
:fields="fields"
:initial="initial"
:fields-meta="fieldsMeta"
@@ -10,11 +10,11 @@
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import GenericCreateUpdateDrawer from '@/layout/components/GenericCreateUpdateDrawer/index.vue'
export default {
name: 'CommandGroupCreateUpdate',
components: { GenericCreateUpdatePage },
components: { GenericCreateUpdateDrawer },
data() {
const regexPlaceholder = 'rm.*|reboot|shutdown'
const commandPlaceholder = 'rm\rreboot'

View File

@@ -1,14 +1,19 @@
<template>
<ListTable :header-actions="headerActions" :table-config="tableConfig" />
<div>
<ListTable :header-actions="headerActions" :table-config="tableConfig" />
<CommandGroupCreateUpdate />
</div>
</template>
<script>
import { ListTable } from '@/components'
import { DetailFormatter } from '@/components/Table/TableFormatters'
import CommandGroupCreateUpdate from './CommandGroupCreateUpdate.vue'
export default {
components: {
ListTable
ListTable,
CommandGroupCreateUpdate
},
data() {
const _id = this.$route.query.command_filters

View File

@@ -42,7 +42,7 @@ export default {
const query = _.cloneDeep(this.$route.query)
const newQuery = {
...query,
activeTab: tab.name
tab: tab.name
}
this.$nextTick(() => {
this.$router.replace({ query: newQuery })

View File

@@ -1,9 +1,9 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
<GenericCreateUpdateDrawer v-bind="$data" />
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import GenericCreateUpdateDrawer from '@/layout/components/GenericCreateUpdateDrawer'
import rules from '@/components/Form/DataForm/rules'
import { userJSONSelectMeta } from '@/views/users/const'
import { assetJSONSelectMeta } from '@/views/assets/const'
@@ -12,7 +12,7 @@ import { Select2 } from '@/components/Form/FormFields'
export default {
name: 'AclCreateUpdate',
components: {
GenericCreateUpdatePage
GenericCreateUpdateDrawer
},
data() {
return {

View File

@@ -1,13 +1,19 @@
<template>
<GenericListPage :header-actions="headerActions" :help-message="helpText" :table-config="tableConfig" />
<div>
<GenericListPage :header-actions="headerActions" :help-message="helpText" :table-config="tableConfig" />
<ConnectMethodAclCreateUpdate />
</div>
</template>
<script>
import GenericListPage from '@/layout/components/GenericListPage/index.vue'
import ConnectMethodAclCreateUpdate from './ConnectMethodAclCreateUpdate.vue'
export default {
name: 'ConnectMethodListAcl',
components: { GenericListPage },
components: {
GenericListPage,
ConnectMethodAclCreateUpdate
},
data() {
return {
helpText: this.$t('acl.ConnectMethodACLHelpMsg'),

View File

@@ -1,9 +1,9 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
<GenericCreateUpdateDrawer v-bind="$data" />
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import GenericCreateUpdateDrawer from '@/layout/components/GenericCreateUpdateDrawer'
import { WeekCronSelect } from '@/components/Form/FormFields'
import { Required } from '@/components/Form/DataForm/rules'
import { userJSONSelectMeta } from '@/views/users/const'
@@ -11,7 +11,7 @@ import { userJSONSelectMeta } from '@/views/users/const'
export default {
name: 'AclCreateUpdate',
components: {
GenericCreateUpdatePage
GenericCreateUpdateDrawer
},
data() {
return {
@@ -70,18 +70,6 @@ export default {
}
}
},
getUrl() {
const query = this.$route.query
const params = this.$route.params
let url = `/api/v1/acls/login-acls/`
if (params.id) {
url = `${url}${params.id}/`
}
if (query.user) {
url = `${url}?user=${query.user}`
}
return url
},
cleanFormValue(value) {
if (!Array.isArray(value.rules.ip_group)) {
value.rules.ip_group = value.rules.ip_group ? value.rules.ip_group.split(',') : []

View File

@@ -1,13 +1,18 @@
<template>
<ListTable :header-actions="headerActions" :table-config="tableConfig" />
<div>
<ListTable :header-actions="headerActions" :table-config="tableConfig" />
<UserLoginACLCreateUpdate />
</div>
</template>
<script>
import ListTable from '@/components/Table/ListTable/index.vue'
import UserLoginACLCreateUpdate from './UserLoginACLCreateUpdate.vue'
export default {
components: {
ListTable
ListTable,
UserLoginACLCreateUpdate
},
props: {
url: {

View File

@@ -1,15 +1,22 @@
<template>
<GenericCreateUpdatePage v-if="!loading" v-bind="iConfig" />
<GenericCreateUpdateDrawer
v-if="!loading"
:visible="visible"
:action="action"
:action-id="actionId"
v-bind="iConfig"
v-on="$listeners"
/>
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import { assetFieldsMeta } from '@/views/assets/const'
import { encryptPassword } from '@/utils/crypto'
import { getUpdateObjURL, setUrlParam } from '@/utils/common'
import GenericCreateUpdateDrawer from '@/layout/components/GenericCreateUpdateDrawer/index.vue'
export default {
components: { GenericCreateUpdatePage },
components: { GenericCreateUpdateDrawer },
props: {
url: {
type: String,
@@ -28,10 +35,6 @@ export default {
type: [Array, Function],
default: () => []
},
createSuccessNextRoute: {
type: Object,
default: () => ({ name: 'AssetList' })
},
updateSuccessNextRoute: {
type: Object,
default: () => ({ name: 'AssetList' })
@@ -41,11 +44,28 @@ export default {
default: (initial) => {
return initial
}
},
platformId: {
type: Number,
default: 0
},
action: {
type: String,
default: 'create'
},
visible: {
type: Boolean,
default: false
},
row: {
type: Object,
default: () => ({})
}
},
data() {
return {
loading: true,
actionId: this.row?.id,
platform: {},
defaultConfig: {
initial: {},
@@ -62,16 +82,16 @@ export default {
[this.$t('common.Other'), ['domain', 'labels', 'is_active', 'comment']]
],
fieldsMeta: assetFieldsMeta(this),
performSubmit(validValues) {
performSubmit: (validValues) => {
console.log('This row: ', this.row, 'This action: ', this.action)
let url = this.url
const { id = '' } = this.$route.params
const values = _.cloneDeep(validValues)
const submitMethod = id ? 'put' : 'post'
const submitMethod = this.action === 'update' ? 'put' : 'post'
if (values.nodes && values.nodes.length === 0) {
delete values['nodes']
}
if (id) {
url = getUpdateObjURL(url, id)
if (this.action === 'update') {
url = getUpdateObjURL(url, this.row.id)
delete values['accounts']
} else {
const accounts = values?.accounts || []
@@ -89,10 +109,7 @@ export default {
iConfig() {
const { addFields, addFieldsMeta, defaultConfig } = this
let url = this.url
const { id = '' } = this.$route.params
if (this.$route.query.platform && !id) {
url = setUrlParam(url, 'platform', this.$route.query.platform)
}
url = setUrlParam(url, 'platform', this.platformId)
// null, undefined
defaultConfig.fields = defaultConfig.fields.filter(Boolean)
const config = _.merge(defaultConfig, { url })
@@ -105,6 +122,12 @@ export default {
}
}
const tp = this.platform.type?.value
const category = this.platform.category?.value
if (tp && category) {
defaultConfig.fieldsMeta.platform.el.ajax.url = `/api/v1/assets/platforms/?category=${category}&type=${tp}`
}
for (const [name, meta] of Object.entries(addFieldsMeta)) {
if (config.fieldsMeta[name]) {
config.fieldsMeta[name] = Object.assign(config.fieldsMeta[name], meta)
@@ -129,16 +152,16 @@ export default {
},
async setInitial() {
const { defaultConfig } = this
const { node, platform } = this.$route?.query || {}
const { node } = this.$route?.query || {}
const nodesInitial = node ? [node] : []
const platformId = platform || 1
const platformId = this.platformId
const url = `/api/v1/assets/platforms/${platformId}/`
this.platform = await this.$axios.get(url)
const initial = {
labels: [],
is_active: true,
nodes: nodesInitial,
platform: parseInt(platformId),
platform: platformId,
protocols: []
}
if (this.updateInitial) {

View File

@@ -1,9 +1,9 @@
<template>
<BaseAssetCreateUpdate v-bind="$data" />
<BaseAssetCreateUpdate v-bind="Object.assign($data, $attrs)" v-on="$listeners" />
</template>
<script>
import BaseAssetCreateUpdate from './BaseAssetCreateUpdate'
import BaseAssetCreateUpdate from './BaseAssetCreateDrawer.vue'
export default {
name: 'CloudCreateUpdate',

View File

@@ -1,9 +1,9 @@
<template>
<BaseAssetCreateUpdate v-bind="$data" />
<BaseAssetCreateUpdate v-bind="Object.assign($data, $attrs)" v-on="$listeners" />
</template>
<script>
import BaseAssetCreateUpdate from './BaseAssetCreateUpdate'
import BaseAssetCreateUpdate from './BaseAssetCreateDrawer.vue'
export default {
name: 'WebCreateUpdate',

View File

@@ -1,9 +1,9 @@
<template>
<BaseAssetCreateUpdate v-bind="$data" />
<BaseAssetCreateUpdate v-bind="Object.assign($data, $attrs)" v-on="$listeners" />
</template>
<script>
import BaseAssetCreateUpdate from './BaseAssetCreateUpdate'
import BaseAssetCreateUpdate from './BaseAssetCreateDrawer.vue'
import { UploadKey } from '@/components'
import rules from '@/components/Form/DataForm/rules'
@@ -18,7 +18,7 @@ export default {
}
},
mounted() {
this.url = `${this.url}?platform=${this.$route.query.platform}`
console.log('Attrs: ', this.$attrs)
},
methods: {
getAddFields() {

View File

@@ -1,9 +1,9 @@
<template>
<BaseAssetCreateUpdate v-bind="$data" />
<BaseAssetCreateUpdate v-bind="Object.assign($data, $attrs)" v-on="$listeners" />
</template>
<script>
import BaseAssetCreateUpdate from './BaseAssetCreateUpdate'
import BaseAssetCreateUpdate from './BaseAssetCreateDrawer.vue'
export default {
name: 'DeviceCreateUpdate',

View File

@@ -1,9 +1,9 @@
<template>
<BaseAssetCreateUpdate v-bind="$data" />
<BaseAssetCreateUpdate v-bind="Object.assign($data, $attrs)" v-on="$listeners" />
</template>
<script>
import BaseAssetCreateUpdate from './BaseAssetCreateUpdate'
import BaseAssetCreateUpdate from './BaseAssetCreateDrawer.vue'
export default {
name: 'GPTCreateUpdate',

View File

@@ -1,9 +1,9 @@
<template>
<BaseAssetCreateUpdate v-bind="$data" />
<BaseAssetCreateUpdate v-bind="Object.assign($data, $attrs)" v-on="$listeners" />
</template>
<script>
import BaseAssetCreateUpdate from './BaseAssetCreateUpdate'
import BaseAssetCreateUpdate from './BaseAssetCreateDrawer.vue'
export default {
name: 'HostCreateUpdate',

View File

@@ -1,9 +1,9 @@
<template>
<BaseAssetCreateUpdate v-bind="$data" />
<BaseAssetCreateUpdate v-bind="Object.assign($data, $attrs)" />
</template>
<script>
import BaseAssetCreateUpdate from './BaseAssetCreateUpdate'
import BaseAssetCreateUpdate from './BaseAssetCreateDrawer.vue'
export default {
name: 'WebCreateUpdate',

View File

@@ -137,7 +137,7 @@ export default {
name: 'AppletHostDetail',
params: { id: assetId },
query: {
activeTab: 'Accounts'
tab: 'Accounts'
}
})
return
@@ -146,7 +146,7 @@ export default {
name: 'AssetDetail',
params: { id: assetId },
query: {
activeTab: 'Account'
tab: 'Account'
}
})
}

View File

@@ -0,0 +1,73 @@
<template>
<div>
<component
:is="component"
:visible.sync="visible"
:action="action"
:platform-id="platform.id"
:row="row"
@close="onClose"
/>
</div>
</template>
<script>
import HostCreateUpdate from './HostCreateUpdate.vue'
import DatabaseCreateUpdate from './DatabaseCreateUpdate.vue'
import CloudCreateUpdate from './CloudCreateUpdate.vue'
import WebCreateUpdate from './WebCreateUpdate.vue'
import DeviceCreateUpdate from './DeviceCreateUpdate.vue'
import CustomCreateUpdate from './CustomCreateUpdate.vue'
export default {
name: 'WebCreateUpdate',
components: {
HostCreateUpdate,
DatabaseCreateUpdate,
CloudCreateUpdate,
WebCreateUpdate,
DeviceCreateUpdate,
CustomCreateUpdate
},
props: {
},
data() {
return {
visible: false,
category: '',
component: '',
platform: { id: 0 },
row: {},
action: '',
components: {
host: HostCreateUpdate,
database: DatabaseCreateUpdate,
cloud: CloudCreateUpdate,
web: WebCreateUpdate,
device: DeviceCreateUpdate,
custom: CustomCreateUpdate
}
}
},
mounted() {
this.$eventBus.$on('assetCreateUpdate', (platform, action, { url, row }) => {
this.platform = platform
this.category = platform.category.value
this.component = this.components[this.category]
this.row = row
this.action = action
this.visible = true
})
},
beforeDestroy() {
this.$eventBus.$off('assetCreateUpdate')
},
methods: {
onClose() {
console.log('On closed draw')
this.row = {}
this.component = ''
}
}
}
</script>

View File

@@ -149,7 +149,7 @@ export default {
formatterArgs: {
route: 'AssetPermissionDetail',
routeQuery: {
activeTab: 'AssetPermissionUser'
tab: 'AssetPermissionUser'
}
}
},
@@ -160,7 +160,7 @@ export default {
formatterArgs: {
route: 'AssetPermissionDetail',
routeQuery: {
activeTab: 'AssetPermissionUser'
tab: 'AssetPermissionUser'
}
}
},
@@ -171,7 +171,7 @@ export default {
formatterArgs: {
route: 'AssetPermissionDetail',
routeQuery: {
activeTab: 'AssetPermissionAsset'
tab: 'AssetPermissionAsset'
}
}
}

View File

@@ -15,8 +15,7 @@ export default {
category: 'custom',
url: '/api/v1/assets/customs/',
tableConfig: {
columnsExclude: [
],
columnsExclude: [],
columnsMeta: {
autofill: {
width: '100px'

View File

@@ -1,5 +1,5 @@
<template>
<BaseList v-bind="config" />
<BaseList v-bind="Object.assign(config, $attrs)" />
</template>
<script>

View File

@@ -17,11 +17,13 @@
:port="GatewayPort"
:visible.sync="GatewayVisible"
/>
<AssetCreateUpdate />
</div>
</template>
<script>
import { ListTable } from '@/components'
import AssetCreateUpdate from '../../AssetCreateUpdate'
import {
ActionsFormatter, ArrayFormatter, ChoicesFormatter, DetailFormatter, ProtocolsFormatter, TagsFormatter
} from '@/components/Table/TableFormatters'
@@ -37,7 +39,8 @@ export default {
ListTable,
GatewayDialog,
PlatformDialog,
AssetBulkUpdateDialog
AssetBulkUpdateDialog,
AssetCreateUpdate
},
props: {
url: {
@@ -77,28 +80,13 @@ export default {
data() {
const vm = this
const onAction = (row, action) => {
let routeAction = action
if (action === 'Clone') {
routeAction = 'Create'
const platform = {
...row.platform,
type: row.type,
category: row.category
}
const routeName = _.capitalize(row.category.value) + routeAction
const route = {
name: routeName,
params: {},
query: {}
}
if (action === 'Clone') {
route.query.clone_from = row.id
} else if (action === 'Update') {
route.params.id = row.id
}
if (['Create', 'Update'].includes(routeAction)) {
route.query.platform = row.platform.id
route.query.type = row.type.value
route.query.category = row.type.category
}
const { href } = vm.$router.resolve(route)
window.open(href, '_blank')
console.log('ON action: ', action)
vm.$eventBus.$emit('assetCreateUpdate', platform, action.toLowerCase(), { url: this.url, row })
}
const extraQuery = this.$route.params?.extraQuery || {}
return {

View File

@@ -1,12 +1,13 @@
<template>
<Dialog
<el-drawer
v-if="iVisible"
:show-cancel="false"
:show-confirm="false"
:title="$tc('assets.SelectPlatforms')"
:visible.sync="iVisible"
top="1vh"
width="60%"
class="platform-dialog"
direction="rtl"
size="600px"
>
<div style="margin: 0 10px">
<el-row :gutter="20">
@@ -20,7 +21,7 @@
<el-col
v-for="(platform, index) of ps"
:key="platform.id"
:span="6"
:span="12"
>
<el-card
:style="{ borderLeftColor: randomBorderColor(index) }"
@@ -35,16 +36,13 @@
</el-collapse>
</el-row>
</div>
</Dialog>
</el-drawer>
</template>
<script>
import Dialog from '@/components/Dialog'
export default {
name: 'PlatformDialog',
components: {
Dialog
},
props: {
visible: {
@@ -77,6 +75,7 @@ export default {
computed: {
iVisible: {
set(val) {
console.log('Platform visible changed: ', val)
this.$emit('update:visible', val)
},
get() {
@@ -149,19 +148,12 @@ export default {
localStorage.setItem('RecentPlatforms', JSON.stringify(recentPlatformIds))
},
createAsset(platform) {
const route = _.capitalize(platform.category.value) + 'Create' || 'HostCreate'
this.addToRecentPlatforms(platform)
this.iVisible = false
const query = {
node: this.$route.query?.node || this.$route.query?.node_id || '',
platform: platform.id,
type: platform.type.value,
category: platform.category.value
}
const router = { name: route, query }
const { href } = this.$router.resolve(router)
window.open(href, '_blank')
setTimeout(() => {
this.iVisible = false
})
const url = `/api/v1/assets/${platform.category.value}s/`
this.$eventBus.$emit('assetCreateUpdate', platform, 'create', { url })
}
}
}
@@ -186,8 +178,17 @@ export default {
font-weight: 500;
color: #303133;
}
.platform-dialog {
>>> .el-drawer__header {
margin-bottom: 10px;
}
}
>>> .el-collapse {
border: none;
padding: 0 20px;
.el-collapse-item:last-child {
.el-collapse-item__header {
border: none;

View File

@@ -92,7 +92,7 @@ export default {
const query = _.cloneDeep(this.$route.query)
const newQuery = {
...query,
activeTab: tab.name
tab: tab.name
}
this.$nextTick(() => {
this.$router.replace({ query: newQuery })

View File

@@ -1,25 +1,26 @@
<template>
<GenericCreateUpdatePage
<GenericCreateUpdateDrawer
:initial="initial"
:resource="$tc('xpack.Cloud.Account')"
v-bind="$data"
/>
</template>
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import { RequiredChange, specialEmojiCheck } from '@/components/Form/DataForm/rules'
import { ACCOUNT_PROVIDER_ATTRS_MAP, aliyun } from '../const'
import { UploadKey } from '@/components'
import { encryptPassword } from '@/utils/crypto'
import GenericCreateUpdateDrawer from '@/layout/components/GenericCreateUpdateDrawer/index.vue'
export default {
components: {
GenericCreateUpdatePage
GenericCreateUpdateDrawer
},
data() {
const vm = this
const accountProvider = this.$route.query.provider || aliyun
const accountProviderAttrs = ACCOUNT_PROVIDER_ATTRS_MAP[accountProvider]
const accountProviderAttrs = ACCOUNT_PROVIDER_ATTRS_MAP[accountProvider] || {}
function setFieldAttrs() {
const fieldsObject = {}
@@ -127,12 +128,6 @@ export default {
values.attrs.ip_group = attrs.ip_group.filter(Boolean)
}
return values
},
afterGetFormValue(formValue) {
if (!formValue.attrs) {
return formValue
}
return formValue
}
}
},

View File

@@ -39,7 +39,7 @@ export default {
this.$router.push({
name: routeName,
params: { id: id },
query: { provider: vm.Account.provider }
query: { provider: vm.Account.provider.value }
})
}
}

View File

@@ -1,9 +1,13 @@
<template>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<div>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<AccountCreateUpdate />
</div>
</template>
<script type="text/jsx">
import GenericListTable from '@/layout/components/GenericListTable'
import AccountCreateUpdate from './AccountCreateUpdate.vue'
import {
ACCOUNT_PROVIDER_ATTRS_MAP, aliyun, aws_china, aws_international, azure, azure_international, baiducloud,
ctyun_private, fc, gcp, huaweicloud, huaweicloud_private, jdcloud, kingsoftcloud, lan, nutanix, openstack, qcloud,
@@ -13,13 +17,15 @@ import {
export default {
name: 'AccountList',
components: {
GenericListTable
GenericListTable,
AccountCreateUpdate
},
data() {
const vm = this
const url = '/api/v1/xpack/cloud/accounts/'
return {
tableConfig: {
url: '/api/v1/xpack/cloud/accounts/',
url: url,
permissions: {
app: 'xpack',
resource: 'account'
@@ -45,7 +51,13 @@ export default {
updateRoute: 'AccountUpdate',
hasClone: false,
onUpdate: ({ row, col }) => {
vm.$router.push({ name: 'AccountUpdate', params: { id: row.id }, query: { provider: row.provider?.value }})
vm.$router.push({
params: { id: row.id },
query: { provider: row.provider?.value }
})
setTimeout(() => {
vm.$eventBus.$emit('showCreateUpdateDrawer', 'update', { row, col })
})
},
extraActions: [
{
@@ -72,7 +84,10 @@ export default {
},
moreCreates: {
callback: (option) => {
vm.$router.push({ name: 'AccountCreate', query: { provider: option.name }})
vm.$router.push({ query: { provider: option.name }})
setTimeout(() => {
vm.$eventBus.$emit('showCreateUpdateDrawer', 'create', { url })
})
},
dropdown: [
{

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