chore: merge to master

Merge Dev to master
This commit is contained in:
老广 2020-08-14 12:26:03 +08:00 committed by GitHub
commit 2f69861361
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
106 changed files with 2614 additions and 385 deletions

View File

@ -1,10 +1,22 @@
FROM node:10 as stage-build
ARG VERSION
ENV VERSION=$VERSION
ARG NPM_REGISTRY="https://registry.npm.taobao.org"
ENV NPM_REGISTY=$NPM_REGISTRY
ARG SASS_BINARY_SITE="https://npm.taobao.org/mirrors/node-sass"
ENV SASS_BINARY_SITE=$SASS_BINARY_SITE
WORKDIR /data
RUN npm config set sass_binary_site=${SASS_BINARY_SITE}
RUN npm config set registry ${NPM_REGISTRY}
RUN yarn config set registry ${NPM_REGISTRY}
COPY package.json yarn.lock /data/
COPY utils /data/utils/
RUN ls && cd utils && bash -xieu build.sh dep
ADD . /data
RUN cd utils && bash -xieu build.sh
RUN cd utils && bash -xieu build.sh build
FROM nginx:alpine
COPY --from=stage-build /data/release/lina /opt/lina

View File

@ -82,8 +82,8 @@
"less-loader": "^5.0.0",
"lint-staged": "^10.1.2",
"mockjs": "1.0.1-beta3",
"node-sass": "^4.9.0",
"runjs": "^4.3.2",
"sass": "^1.26.10",
"sass-loader": "^7.1.0",
"script-ext-html-webpack-plugin": "2.1.3",
"script-loader": "0.7.2",

View File

@ -8,7 +8,7 @@
<i v-if="item.fa" :class="'fa ' + item.fa" />{{ item.title }}
</span>
</el-button>
<el-dropdown v-if="iMoreActions.length > 0" trigger="click" @command="handleClick">
<el-dropdown v-if="iMoreActions.length > 0" trigger="click" :placement="moreActionsPlacement" @command="handleClick">
<el-button :size="size" :type="moreActionsType" class="btn-more-actions">
{{ iMoreActionsTitle }}<i class="el-icon-arrow-down el-icon--right" />
</el-button>
@ -52,6 +52,11 @@ export default {
moreActionsType: {
type: String,
default: 'default'
},
moreActionsPlacement: {
type: String,
default: 'bottom'
//
}
},
computed: {

View File

@ -156,20 +156,20 @@ export default {
.el-select{
width: 100%;
}
.page /deep/ .page-heading{
.page ::v-deep .page-heading{
display: none;
}
.el-dialog__wrapper /deep/.el-dialog__body{
.el-dialog__wrapper ::v-deep .el-dialog__body{
padding: 5px 10px;
}
.page /deep/ .treebox{
.page ::v-deep .treebox{
height: inherit !important;
}
.asset-select-dialog >>> .transition-box:first-child {
background-color: #f3f3f3 ;
}
.el-dialog__wrapper /deep/.el-dialog__body .wrapper-content {
.el-dialog__wrapper ::v-deep .el-dialog__body .wrapper-content {
padding: 10px;
}

View File

@ -120,7 +120,7 @@ export default {
{
prop: 'ip',
label: this.$t('assets.ip'),
width: 140
width: '120px'
},
{
prop: 'username',
@ -130,7 +130,7 @@ export default {
{
prop: 'version',
label: this.$t('assets.Version'),
width: '50px'
width: '70px'
},
{
prop: 'date_created',

View File

@ -76,6 +76,9 @@ export default {
case 'field':
type = ''
field.component = Select2
if (fieldMeta.required) {
field.el.clearable = false
}
break
case 'string':
type = 'input'

View File

@ -47,7 +47,9 @@ export default {
}
const option = {
label: field.label,
type: field.type,
value: name
}
if (field.type === 'choice' && field.choices) {
option.children = field.choices.map(item => {

View File

@ -88,27 +88,27 @@ export default {
</script>
<style lang="less" scoped>
.el-form /deep/ .el-form-item {
.el-form ::v-deep .el-form-item {
margin-bottom: 12px;
}
.el-form /deep/ .el-form-item__content {
.el-form ::v-deep .el-form-item__content {
width: 75%;
}
.el-form /deep/ .el-form-item__label {
.el-form ::v-deep .el-form-item__label {
padding: 0 30px 0 0;
}
.el-form /deep/ .el-form-item__error {
.el-form ::v-deep .el-form-item__error {
position: inherit;
}
.el-form /deep/ .form-group-header {
.el-form ::v-deep .form-group-header {
margin-left: 50px;
}
.el-form /deep/ .help-block {
.el-form ::v-deep .help-block {
display: block;
margin-top: 5px;
margin-bottom: 10px;
@ -116,7 +116,7 @@ export default {
font-size: 12px;
line-height: 18px;
}
.el-form /deep/ .help-block a {
.el-form ::v-deep .help-block a {
color: #1c84c6;
}
</style>

View File

@ -953,6 +953,8 @@ export default {
})
},
search(attrs, reset) {
//
this.page = defaultFirstPage
// Orange
if (reset) {
this.innerQuery = merge({}, attrs)
@ -962,6 +964,8 @@ export default {
return this.getList()
},
searchDate(attrs) {
//
this.page = defaultFirstPage
this.innerQuery = merge(this.innerQuery, attrs)
return this.getList()
},

View File

@ -1,16 +1,16 @@
.el-data-table /deep/ .el-pagination{
.el-data-table ::v-deep .el-pagination{
text-align: center !important;
}
.el-data-table /deep/ .el-table td{
.el-data-table ::v-deep .el-table td{
padding: 4px 0;
}
.el-data-table /deep/ .el-table th{
.el-data-table ::v-deep .el-table th{
padding: 4px 0;
}
.el-data-table/deep/ .el-form-item{
.el-data-table ::v-deep .el-form-item{
margin-bottom:10px !important ;
margin-top:10px;
}
.el-data-table/deep/ .el-pagination{
.el-data-table ::v-deep .el-pagination{
padding:15px 0 !important ;
}
}

View File

@ -138,16 +138,16 @@ export default {
<style lang="less" scoped>
.el-table /deep/ .el-table__row > td {
.el-table ::v-deep .el-table__row > td {
line-height: 1.5;
padding: 8px 0;
}
.el-table /deep/ .el-table__row > td> div > span {
.el-table ::v-deep .el-table__row > td> div > span {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.el-table /deep/ .el-table__header > thead > tr >th {
.el-table ::v-deep .el-table__header > thead > tr >th {
padding: 8px 0;
background-color: #F5F5F6;
font-size: 13px;
@ -158,11 +158,11 @@ export default {
}
//
.el-pagination /deep/ .el-pagination__total{
.el-pagination ::v-deep .el-pagination__total{
float: left;
}
.el-pagination /deep/ .el-pagination__sizes{
.el-pagination ::v-deep .el-pagination__sizes{
float: left;
}
//

View File

@ -52,7 +52,7 @@ export default {
// $('.treebox').css('height', window.innerHeight - 60)
},
beforeDestroy() {
$.fn.zTree.destroy()
$.fn.zTree.destroy(this.iZTreeID)
},
methods: {
initTree: function() {
@ -158,7 +158,7 @@ export default {
top: 100%;
z-index: 1000;
}
.ztree /deep/ .fa-refresh {
.ztree ::v-deep .fa-refresh {
font: normal normal normal 14px/1 FontAwesome !important;
}
.dropdown a:hover {

View File

@ -100,10 +100,10 @@ export default {
border-radius: 3px;
height: 36px;
}
/*.el-date-editor /deep/ .el-input__icon{*/
/*.el-date-editor ::v-deep .el-input__icon{*/
/* line-height: 28px;*/
/*}*/
.el-date-editor /deep/ .el-range-separator{
.el-date-editor ::v-deep .el-range-separator{
line-height: 28px;
}
</style>

View File

@ -3,7 +3,7 @@ export default {
name: 'ItemValue',
props: {
value: {
type: [String, Number, Function, Array, Object],
type: [String, Number, Function, Array, Object, Boolean],
default: ''
},
item: {
@ -15,10 +15,21 @@ export default {
default: null
}
},
methods: {
toChoicesDisplay(value) {
if (!value) {
return this.$t('common.No')
}
return this.$t('common.Yes')
}
},
render(h) {
if (typeof this.formatter === 'function') {
return this.formatter(this.item, this.value)
}
if (typeof this.value === 'boolean') {
return <span>{this.toChoicesDisplay(this.value)}</span>
}
return <span>{this.value}</span>
}
}

View File

@ -1,11 +1,19 @@
<template>
<IBox :title="title" fa="fa-info-circle">
<div class="content">
<el-row v-if="this.$route.params.id" :gutter="10" class="item">
<el-col :span="6"><div :style="{ 'text-align': align }" class="item-label"><label>ID: </label></div></el-col>
<el-col :span="18"><div class="item-text">{{ this.$route.params.id }}</div></el-col>
</el-row>
<el-row v-for="item in items" :key="'card-' + item.key" :gutter="10" class="item">
<el-col :span="6"><div :style="{ 'text-align': align }" class="item-label"><label>{{ item.key }}: </label></div></el-col>
<el-col :span="18"><div class="item-text">
<ItemValue :value="item.value" v-bind="item" />
</div></el-col>
<el-col :span="6">
<div :style="{ 'text-align': align }" class="item-label"><label>{{ item.key }}: </label></div>
</el-col>
<el-col :span="18">
<div class="item-text">
<ItemValue :value="item.value" v-bind="item" />
</div>
</el-col>
</el-row>
<slot />
</div>

View File

@ -67,6 +67,8 @@ export default {
const query = listTableRef.dataTable.getQuery()
delete query['limit']
delete query['offset']
delete query['date_from']
delete query['date_to']
return query
},
tableHasQuery() {
@ -77,7 +79,7 @@ export default {
{
label: this.$t('common.imExport.ExportAll'),
value: 'all',
can: this.canExportAll
can: this.canExportAll && !this.tableHasQuery
},
{
label: this.$t('common.imExport.ExportOnlySelectedItems'),

View File

@ -74,8 +74,8 @@ export default {
return this.url
},
downloadImportTempUrl() {
const baseUrl = (process.env.VUE_APP_ENV === 'production') ? (`${this.url}`) : (`${process.env.VUE_APP_BASE_API}${this.url}`)
return baseUrl + '?format=csv&template=import&limit=1'
const url = (this.url.indexOf('?') === -1) ? `${this.url}?format=csv&template=import&limit=1` : `${this.url}&format=csv&template=import&limit=1`
return url
},
uploadHelpTextClass() {
const cls = ['el-upload__tip']

View File

@ -33,6 +33,9 @@ export default {
}
},
iCanDelete() {
if (this.col.objects === 'all') {
return false
}
return this.col.objects.indexOf(this.cellValue) === -1
}
}

View File

@ -1,8 +1,8 @@
<template>
<IBox fa="fa-edit" :title="title" v-bind="$attrs">
<div class="quick-actions">
<div v-for="action of actions" :key="action.title" class="quick-actions">
<table>
<ActionItem v-for="action of actions" :key="action.title" :action="action" />
<ActionItem v-if="action.has === undefined || action.has" :action="action" />
</table>
</div>
</IBox>

View File

@ -37,7 +37,6 @@
import Select2 from '../Select2'
import IBox from '../IBox'
import { createSourceIdCache } from '@/api/common'
export default {
name: 'RelationCard',
components: {

View File

@ -8,6 +8,7 @@
:remote-method="filterOptions"
:multiple="multiple"
filterable
:clearable="clearable"
popper-append-to-body
class="select2"
v-bind="$attrs"
@ -69,6 +70,10 @@ export default {
type: Boolean,
default: true
},
clearable: {
type: Boolean,
default: true
},
//
value: {
type: [Array, String, Number, Boolean],
@ -172,6 +177,9 @@ export default {
},
methods: {
async loadMore(load) {
if (!this.iAjax.url) {
return
}
if (!this.params.hasMore) {
return
}

View File

@ -1,14 +1,35 @@
<template>
<div class="filter-field">
<el-cascader ref="Cascade" :options="options" :props="config" @change="handleMenuItemChange" />
<el-tag v-for="(v, k) in filterTags" :key="k" :name="k" closable size="small" class="filter-tag" type="info" @close="handleTagClose(k)">
<el-tag
v-for="(v, k) in filterTags"
:key="k"
:name="k"
closable
size="small"
class="filter-tag"
type="info"
:disable-transitions="true"
@close="handleTagClose(k)"
@click="handleTagClick(v,k)"
>
<strong v-if="v.label">{{ v.label + ':' }}</strong>
<span v-if="v.valueLabel">{{ v.valueLabel }}</span>
<span v-else>{{ v.value }}</span>
</el-tag>
<span v-if="keyLabel" slot="prefix" class="filterTitle">{{ keyLabel + ':' }}</span>
<el-input ref="SearchInput" v-model="filterValue" :placeholder="placeholder" class="search-input" @blur="focus = false" @focus="focus = true" @change="handleConfirm" />
<el-input
ref="SearchInput"
v-model="filterValue"
:placeholder="placeholder"
class="search-input"
@blur="focus = false"
@focus="focus = true"
@change="handleConfirm"
/>
</div>
</template>
<script>
@ -65,13 +86,13 @@ export default {
}
},
watch: {
filterTags: {
handler(val) {
this.$nextTick(() => this.$emit('tagSearch', this.filterMaps))
// this.$emit('tagSearch', this.filterMaps)
},
deep: true
}
// filterTags: {
// handler(val) {
// this.$nextTick(() => this.$emit('tagSearch', this.filterMaps))
// // this.$emit('tagSearch', this.filterMaps)
// },
// deep: true
// }
},
mounted() {
setTimeout(() => {
@ -116,6 +137,7 @@ export default {
},
handleTagClose(evt) {
this.$delete(this.filterTags, evt)
this.$emit('tagSearch', this.filterMaps)
return true
},
handleConfirm() {
@ -124,9 +146,33 @@ export default {
}
const tag = { key: this.filterKey, label: this.keyLabel, value: this.filterValue, valueLabel: this.valueLabel }
this.$set(this.filterTags, this.filterKey, tag)
this.$emit('tagSearch', this.filterMaps)
this.filterKey = ''
this.filterValue = ''
this.valueLabel = ''
},
handleTagClick(v, k) {
let unableChange = false
for (const field of this.options) {
if (field.value === v.key) {
if (field.type === 'choice') {
unableChange = true
}
if (field.type === 'boolean') {
unableChange = true
}
}
}
if (unableChange) {
return
}
if (this.filterValue.length !== 0) {
this.handleConfirm()
}
this.$delete(this.filterTags, k)
this.filterKey = v.key
this.filterValue = v.value
this.$refs.SearchInput.focus()
}
}
}

View File

@ -16,6 +16,7 @@
"chrome_password": "登录密码",
"mysql_workbench": "MySQL Workbench",
"mysql_workbench_ip": "数据库IP",
"mysql_workbench_port": "数据库端口",
"mysql_workbench_name": "数据库名",
"mysql_workbench_username": "数据库账号",
"mysql_workbench_password": "数据库密码",
@ -28,7 +29,9 @@
"custom_target": "目标地址",
"custom_username": "登录账号",
"custom_password": "登录密码",
"Custom": "自定义"
"Custom": "自定义",
"cluster": "集群",
"kubernetes":"Kubernetes"
},
"assets": {
"Action": "动作",
@ -133,7 +136,11 @@
"command_filter_list": "命令过滤器列表",
"date_joined": "创建日期",
"ip": "IP",
"sshkey": "sshkey"
"sshkey": "sshkey",
"GroupsHelpMessage": "请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)",
"HomeHelpMessage": "默认家目录 /home/系统用户名: /home/username",
"Home": "家目录",
"LinuxUserAffiliateGroup": "用户附属组"
},
"audits": {
@ -148,9 +155,11 @@
},
"common": {
"Action": "动作",
"RequestTickets": "申请工单",
"Actions": "操作",
"Activate": "激活",
"Active": "激活中",
"actionsTips":"剪切板权限控制目前仅支持 RDP/VNC 协议的连接",
"Add": "添加",
"AddSuccessMsg": "添加成功",
"Auth": "认证",
@ -185,6 +194,7 @@
"MFARequireForSecurity": "为了安全请输入MFA",
"Members": "成员",
"More": "更多",
"Message": "消息",
"MoreActions": "更多操作",
"Name": "名称",
"No": "否",
@ -217,6 +227,7 @@
"bulkDeleteErrorMsg": "批量删除失败: ",
"bulkDeleteSuccessMsg": "批量删除成功",
"bulkRemoveErrorMsg": "批量移除失败: ",
"NeedAssetsAndSystemUserErrMsg": "请先选择授权的系统用户和资产",
"bulkRemoveSuccessMsg": "批量移除成功",
"createBy": "创建者",
"createErrorMsg": "创建失败",
@ -224,6 +235,7 @@
"createdBy": "创建人",
"dateCreated": "创建日期",
"dateExpired": "失效日期",
"dateFinished": "完成日期",
"dateStart": "开始日期",
"deleteErrorMsg": "删除失败",
"deleteFailedMsg": "删除失败",
@ -374,6 +386,7 @@
"addDatabaseAppToThisPermission": "添加数据库应用",
"addNodeToThisPermission": "添加节点",
"addRemoteAppToThisPermission": "添加远程应用",
"addK8sAppToThisPermission": "添加Kubernetes应用",
"addSystemUserToThisPermission": "添加系统用户",
"addUserGroupToThisPermission": "添加用户组",
"addUserToThisPermission": "添加用户",
@ -382,6 +395,7 @@
"assetCount": "资产数量",
"connect": "连接",
"databaseApp": "数据库应用",
"KubernetesApp": "kubernetes应用",
"dateStart": "开始日期",
"downloadFile": "下载文件",
"hostName": "主机名",
@ -392,9 +406,14 @@
"refreshSuccess": "刷新成功",
"remoteApp": "远程应用",
"remoteAppCount": "远程应用数量",
"DatabaseAppCount": "数据库应用数量",
"KubernetesAppCount": "Kubernetes应用数量",
"systemUserCount": "系统用户数量",
"upDownload": "上传下载",
"uploadFile": "上传文件",
"clipboardCopyPaste":"复制粘贴",
"clipboardCopy":"剪切板复制",
"clipboardPaste":"剪切板粘贴",
"userCount": "用户数量",
"userGroupCount": "用户组数量",
"usersAndUserGroups": "用户或用户组"
@ -439,6 +458,15 @@
"DatabaseAppPermissionDetail": "数据库授权详情",
"DatabaseAppPermissionUpdate": "更新数据库授权规则",
"DatabaseAppUpdate": "数据库应用更新",
"KubernetesApp": "Kubernetes",
"KubernetesAppCreate": "创建Kubernetes应用",
"KubernetesAppDetail": "Kubernetes详情",
"KubernetesAppPermission": "Kubernetes授权",
"KubernetesAppPermissionCreate": "创建Kubernetes授权规则",
"KubernetesAppPermissionDetail": "Kubernetes授权详情",
"KubernetesAppPermissionUpdate": "更新Kubernetes授权规则",
"KubernetesAppUpdate": "更新Kubernetes应用",
"DomainCreate": "创建网域",
"DomainDetail": "网域详情",
"DomainList": "网域列表",
@ -484,6 +512,7 @@
"TaskMonitor": "任务监控",
"Terminal": "终端管理",
"TicketDetail": "工单详情",
"TicketCreate": "创建工单",
"Tickets": "工单管理",
"UserCreate": "创建用户",
"UserDetail": "用户详情",
@ -545,6 +574,7 @@
"systemUser": "系统用户",
"terminalDetail": "终端详情",
"terminalUpdate": "更新终端",
"terminalUpdateStorage": "更新终端存储",
"terminate": "终断",
"sessionTerminate": "会话终断",
"test": "测试",
@ -555,7 +585,12 @@
},
"Monitor": "监控",
"sessionMonitor": "监控",
"TerminateTaskSendSuccessMsg": "终断任务已下发,请稍后刷新查看"
"TerminateTaskSendSuccessMsg": "终断任务已下发,请稍后刷新查看",
"helpText": {
"esUrl": "提示:如果有多台主机,请使用逗号 ( , ) 进行分割。eg: http://www.jumpserver.a.com,http://www.jumpserver.b.com",
"esIndex": "es提供默认indexjumpserver",
"esDocType": "es默认文档类型command"
}
},
"setting": {
"ApiKeyList": "API Key 列表",
@ -686,12 +721,13 @@
},
"tickets": {
"Accept": "接受",
"AssignedMe": "待处理",
"AssignedMe": "待我审批",
"Assignee": "处理人",
"Assignees": "待处理人",
"Close": "关闭",
"Comment": "备注",
"MyTickets": "我的工单",
"MyTickets": "我发起的",
"RequestPerm":"授权申请",
"Reject": "拒绝",
"date": "日期",
"reply": "回复",
@ -700,7 +736,20 @@
"type": "类型",
"user": "用户",
"Status": "状态",
"Open": "打开"
"Open": "待处理",
"OpenTicket": "创建工单",
"HandleTicket": "处理工单",
"FinishedTicket": "完成工单",
"IP": "IP",
"Hostname": "主机名",
"Asset": "资产",
"SystemUser": "系统用户",
"RequestAssetPerm": "申请资产授权",
"Applicant": "申请人",
"Pending": "待处理",
"Approved": "已同意",
"Rejected": "已拒绝",
"Closed": "已关闭"
},
"tree": {
"AddAssetToNode": "添加资产到节点",
@ -751,6 +800,8 @@
"ResetAndDownloadSSHKey": "重置并下载密钥",
"ResetPublicKeyAndDownload": "重置并下载SSH密钥",
"Role": "角色",
"SuperRole": "超级角色",
"OrgRole": "组织角色",
"SSHKey": "SSH公钥",
"SSHKeySetting": "SSH公钥设置",
"Secure": "安全",
@ -841,8 +892,11 @@
"AccountCreate": "创建账户",
"AccountList": "账户列表",
"AccountUpdate": "更新账户",
"AccountDetail": "账户详情",
"Cloud": "云管中心",
"CloudCenter": "云管中心",
"Provider": "云服务商",
"Validity": "有效",
"IsAlwaysUpdateHelpTips": "每次执行同步任务时是否同步更新资产的信息包括主机名、IP、系统平台、管理用户",
"SyncInstanceTaskCreate": "创建同步实例任务",
"SyncInstanceTaskList": "同步实例任务列表",
@ -901,8 +955,10 @@
"OrganizationDetail": "组织详情",
"OrganizationList": "组织管理",
"OrganizationUpdate": "更新组织",
"OrganizationMembership": "组织成员",
"DeleteOrgTitle": "请确保组织内的以下信息已删除",
"DeleteOrgMsg": "用户列表、用户组、资产列表、网域列表、管理用户、系统用户、标签管理、资产授权规则"
"DeleteOrgMsg": "用户列表、用户组、资产列表、网域列表、管理用户、系统用户、标签管理、资产授权规则",
"OrgRole": "组织角色"
},
"RestoreButton": "恢复默认",
"SubscriptionID": "订阅授权ID",

View File

@ -17,6 +17,7 @@
"mysql_workbench": "MySQL Workbench",
"mysql_workbench_ip": "DB IP",
"mysql_workbench_name": "DB Name",
"mysql_workbench_port": "DB Port",
"mysql_workbench_username": "DB Account",
"mysql_workbench_password": "DB Password",
"vmware_client": "vSphere Client",
@ -28,7 +29,9 @@
"custom_target": "target URL",
"custom_username": "Account",
"custom_password": "Password",
"Custom": "Custom"
"Custom": "Custom",
"cluster": "Cluster",
"kubernetes":"Kubernetes"
},
"assets": {
"Action": "Action",
@ -133,7 +136,11 @@
"command_filter_list": "Command filter list",
"date_joined": "Date joined",
"ip": "IP",
"sshkey": "sshkey"
"sshkey": "sshkey",
"GroupsHelpMessage": "Please fill in user groups, separated by commas if there are multiple user groups(Please fill in the existing user groups)",
"HomeHelpMessage": "Default home directory: /home/system username",
"Home": "Home",
"LinuxUserAffiliateGroup": "Linux user affiliate group"
},
"audits": {
"Hosts": "Host",
@ -148,8 +155,10 @@
"common": {
"Nothing": "Nothing",
"Action": "Action",
"RequestTickets": "Request tickets",
"Actions": "Actions",
"Activate": "Activate",
"actionsTips":"Clipboard's copy and paste control only support RDP/VNC protocol.",
"Active": "Active",
"Add": "Add",
"AddSuccessMsg": "Add success",
@ -185,6 +194,7 @@
"MFARequireForSecurity": "MFA required for security",
"Members": "Members",
"More": "More",
"Message": "Message",
"MoreActions": "Actions",
"Name": "Name",
"No": "No",
@ -218,11 +228,13 @@
"bulkDeleteSuccessMsg": "Bulk delete success",
"bulkRemoveErrorMsg": "Bulk remove failed: ",
"bulkRemoveSuccessMsg": "Bulk remove success",
"NeedAssetsAndSystemUserErrMsg": "Need assets and systemuser",
"createBy": "Create by",
"createErrorMsg": "Create error",
"createSuccessMsg": "Create success",
"createdBy": "Created by",
"dateCreated": "Date created",
"dateFinished": "Date finished",
"dateExpired": "Date expired",
"dateStart": "Date start",
"deleteErrorMsg": "Delete failed",
@ -371,6 +383,7 @@
"UserGroups": "UserGroups",
"addAssetToThisPermission": "Add asset to this permission",
"addDatabaseAppToThisPermission": "Add DatabaseApp to this permission",
"addK8sAppToThisPermission": "Add KubernetesApp to this permission",
"addNodeToThisPermission": "Add node to this permission",
"addRemoteAppToThisPermission": "Add RemoteApp to this permission",
"addSystemUserToThisPermission": "System user",
@ -381,6 +394,7 @@
"assetCount": "Asset count",
"connect": "Connect",
"databaseApp": "DatabaseApp",
"KubernetesApp": "KubernetesApp",
"dateStart": "Date start",
"downloadFile": "Download file",
"hostName": "Hostname",
@ -391,9 +405,14 @@
"refreshSuccess": "Refresh success",
"remoteApp": "RemoteApp",
"remoteAppCount": "RemoteApp count",
"DatabaseAppCount": "DatabaseApp count",
"KubernetesAppCount": "KubernetesApp count",
"systemUserCount": "System user count",
"upDownload": "Upload download",
"uploadFile": "Upload file",
"clipboardCopyPaste":"Copy Paste",
"clipboardCopy":"Clipboard copy",
"clipboardPaste":"Clipboard paste",
"userCount": "User count",
"userGroupCount": "User group count",
"usersAndUserGroups": "Users and user groups"
@ -438,6 +457,14 @@
"DatabaseAppPermissionDetail": "Databases permissions detail",
"DatabaseAppPermissionUpdate": "Databases permissions update",
"DatabaseAppUpdate": "Database app update",
"KubernetesApp": "kubernetes apps",
"KubernetesAppCreate": "kubernetes app create",
"KubernetesAppDetail": "kubernetes app detail",
"KubernetesAppPermission": "kubernetes permissions",
"KubernetesAppPermissionCreate": "kubernetes permissions create",
"KubernetesAppPermissionDetail": "kubernetes permissions detail",
"KubernetesAppPermissionUpdate": "kubernetes permissions update",
"KubernetesAppUpdate": "kubernetes app update",
"DomainCreate": "Domain create",
"DomainDetail": "Domain detail",
"DomainList": "Domains",
@ -483,6 +510,7 @@
"TaskMonitor": "Task monitor",
"Terminal": "Terminal",
"TicketDetail": "Ticket detail",
"TicketCreate": "Ticket create",
"Tickets": "Tickets",
"UserCreate": "User create",
"UserDetail": "User detail",
@ -544,6 +572,7 @@
"systemUser": "System user",
"terminalDetail": "Terminal detail",
"terminalUpdate": "Update terminal",
"terminalUpdateStorage": "Update terminal storage",
"terminate": "Terminate",
"sessionTerminate": "Session Terminate",
"test": "Test",
@ -554,7 +583,12 @@
},
"Monitor": "Monitor",
"sessionMonitor": "Session Monitor",
"TerminateTaskSendSuccessMsg": "Terminate task has been send, Please check later"
"TerminateTaskSendSuccessMsg": "Terminate task has been send, Please check later",
"helpText": {
"esUrl": "Tip: If you have multiple hosts, use comma (,) to split (eg: http://www.jumpserver.a.com,http://www.jumpserver.b.com)",
"esIndex":"Es provides the default index: jumpserver",
"esDocType": "Es provides the default document type: command"
}
},
"setting": {
"ApiKeyList": "Api key list",
@ -687,6 +721,10 @@
"Accept": "Accept",
"AssignedMe": "Assigned me",
"Assignee": "Assignee",
"RequestPerm":"Request Perm",
"OpenTicket": "Open Ticket",
"HandleTicket": "Handle Ticket",
"FinishedTicket": "Finished Ticket",
"Assignees": "Assignees",
"Close": "Close",
"Comment": "Comment",
@ -699,7 +737,16 @@
"type": "Type",
"user": "User",
"Status": "Status",
"Open": "Open"
"Open": "Open",
"IP": "IP",
"Hostname": "Hostname",
"Asset": "Asset",
"SystemUser": "System user",
"Applicant": "Applicant",
"Pending": "Pending",
"Approved": "Approved",
"Rejected": "Rejected",
"Closed": "Closed"
},
"tree": {
"AddAssetToNode": "Add asset to node",
@ -750,6 +797,8 @@
"ResetAndDownloadSSHKey": "Reset and download SSH Key",
"ResetPublicKeyAndDownload": "Reset public key and download",
"Role": "Role",
"SuperRole": "Super role",
"OrgRole": "Org role",
"SSHKey": "SSH Key",
"SSHKeySetting": "SSH Key setting",
"Secure": "Secure",
@ -839,8 +888,11 @@
"AccountCreate": "Create account",
"AccountList": "Account list",
"AccountUpdate": "Update account",
"AccountDetail": "Account detail",
"Cloud": "Cloud center",
"CloudCenter": "Cloud center",
"Provider": "Provider",
"Validity": "Validity",
"IsAlwaysUpdateHelpTips": "Whether the asset information, including Hostname, IP, Platform, and AdminUser, is updated synchronously each time a synchronization task is performed",
"SyncInstanceTaskCreate": "Create sync instance task",
"SyncInstanceTaskList": "Sync instance task list",
@ -899,8 +951,10 @@
"OrganizationDetail": "Org detail",
"OrganizationList": "Organlizations",
"OrganizationUpdate": "Update org",
"OrganizationMembership": "Organization membership",
"DeleteOrgTitle":"Please ensure that the following information in the organization has been deleted",
"DeleteOrgMsg":"User list、User group、Asset list、Domain list、Admin user、System user、Labels、Asset permission"
"DeleteOrgMsg":"User list、User group、Asset list、Domain list、Admin user、System user、Labels、Asset permission",
"OrgRole": "Org role"
},
"RestoreButton": "Restore Default",
"SubscriptionID": "Subscription ID",

View File

@ -1,7 +1,7 @@
<template>
<Page>
<IBox>
<GenericCreateUpdateForm v-bind="$attrs" v-on="$listeners" />
<GenericCreateUpdateForm ref="createUpdateForm" v-bind="$attrs" v-on="$listeners" />
</IBox>
</Page>
</template>

View File

@ -77,7 +77,7 @@ export default {
position: relative;
overflow: hidden;
width: 100%;
/deep/ {
::v-deep {
.el-scrollbar__bar {
bottom: 0px;
}

View File

@ -63,5 +63,32 @@ export default [
component: () => import('@/views/applications/DatabaseApp/DatabaseAppDetail/index'),
meta: { title: i18n.t('route.DatabaseAppDetail'), activeMenu: '/applications/database-apps' },
hidden: true
},
{
path: 'kubernetes-apps',
name: 'KubernetesAppList',
component: () => import('@/views/applications/KubernetesApp/KubernetesAppList'),
meta: { title: i18n.t('route.KubernetesApp') }
},
{
path: 'kubernetes-apps/create',
name: 'KubernetesAppCreate',
component: () => import('@/views/applications/KubernetesApp/KubernetesAppCreateUpdate'),
meta: { title: i18n.t('route.KubernetesAppCreate'), activeMenu: '/applications/kubernetes-apps', action: 'create' },
hidden: true
},
{
path: 'kubernetes-apps/:id/update',
name: 'KubernetesAppUpdate',
component: () => import('@/views/applications/KubernetesApp/KubernetesAppCreateUpdate'),
meta: { title: i18n.t('route.KubernetesAppUpdate'), activeMenu: '/applications/kubernetes-apps', action: 'update' },
hidden: true
},
{
path: 'kubernetes-apps/:id',
name: 'KubernetesAppDetail',
component: () => import('@/views/applications/KubernetesApp/KubernetesAppDetail/index'),
meta: { title: i18n.t('route.KubernetesAppDetail'), activeMenu: '/applications/kubernetes-apps' },
hidden: true
}
]

View File

@ -1,5 +1,7 @@
import i18n from '@/i18n/i18n'
import rolec from '@/utils/role'
import { BASE_URL } from '@/utils/common'
export default [
{
path: 'tasks',
@ -42,9 +44,9 @@ export default [
// meta: { title: i18n.t('route.CeleryTaskLog') }
// },
{
path: 'task/monitor',
path: `${BASE_URL}/core/flower?_=${Date.now()}`,
name: 'TaskMonitor',
component: () => window.open(`/core/flower?_=${Date.now()}`),
// component: () => window.open(`/core/flower?_=${Date.now()}`),
meta: { title: i18n.t('route.TaskMonitor'), permissions: [rolec.PERM_SUPER] }
}
]

View File

@ -100,8 +100,38 @@ const databasePermissionRoutes = [
}
]
const kubernetesPermissionRoutes = [
{
path: 'kubernetes-app-permissions',
name: 'KubernetesAppPermissionList',
component: () => import('@/views/perms/KubernetesAppPermission/KubernetesAppPermissionList'),
meta: { title: i18n.t('route.KubernetesAppPermission') }
},
{
path: 'kubernetes-app-permissions/create',
component: () => import('@/views/perms/KubernetesAppPermission/KubernetesAppPermissionCreateUpdate'), // Parent router-view
name: 'KubernetesAppPermissionCreate',
hidden: true,
meta: { title: i18n.t('route.KubernetesAppPermissionCreate'), activeMenu: '/perms/kubernetes-app-permissions' }
},
{
path: 'kubernetes-app-permissions/update',
component: () => import('@/views/perms/KubernetesAppPermission/KubernetesAppPermissionCreateUpdate'), // Parent router-view
name: 'KubernetesAppPermissionUpdate',
hidden: true,
meta: { title: i18n.t('route.KubernetesAppPermissionUpdate'), activeMenu: '/perms/kubernetes-app-permissions', action: 'update' }
},
{
path: 'kubernetes-app-permissions/:id',
component: () => import('@/views/perms/KubernetesAppPermission/KubernetesAppPermissionDetail/index'),
name: 'KubernetesAppPermissionDetail',
hidden: true,
meta: { title: i18n.t('route.KubernetesAppPermissionDetail'), activeMenu: '/perms/kubernetes-app-permissions' }
}
]
export default [
... assetPermissionRoutes,
... remoteAppPermissionRoutes,
... databasePermissionRoutes
... databasePermissionRoutes,
... kubernetesPermissionRoutes
]

View File

@ -1,6 +1,8 @@
import i18n from '@/i18n/i18n'
import rolec from '@/utils/role'
import empty from '@/layout/empty'
import { BASE_URL } from '@/utils/common'
export default [
{
path: 'session',
@ -22,15 +24,15 @@ export default [
hidden: true
},
{
path: 'luna',
path: `${BASE_URL}/luna/?_=${Date.now()}`,
name: 'WebTerminal',
component: () => window.open(`/luna/?_=${Date.now()}`),
// component: () => window.open(`/luna/?_=${Date.now()}`),
meta: { title: i18n.t('route.WebTerminal') }
},
{
path: 'sftp',
path: `${BASE_URL}/koko/elfinder/sftp/?`,
name: 'FileManager',
component: () => window.open(`/koko/elfinder/sftp/?`),
// component: () => window.open(`/koko/elfinder/sftp/?`),
meta: { title: i18n.t('route.FileManager') }
},
{

View File

@ -4,7 +4,7 @@ export default [
path: 'tickets',
name: 'TicketList',
component: () => import('@/views/tickets/TicketList'),
meta: { title: i18n.t('route.Tickets'), icon: 'check-square-o' }
meta: { title: i18n.t('route.Tickets'), icon: 'check-square-o', activeMenu: '/tickets/tickets' }
},
{
path: 'tickets/:id',
@ -12,5 +12,19 @@ export default [
component: () => import('@/views/tickets/TicketDetail/index'),
meta: { title: i18n.t('route.TicketDetail'), activeMenu: '/tickets/tickets' },
hidden: true
},
{
path: 'tickets/request-asset-perm/create',
name: 'RequestAssetPermTicketCreateUpdate',
component: () => import('@/views/tickets/RequestAssetPerm/RequestAssetPermTicketCreateUpdate'),
meta: { title: i18n.t('route.TicketCreate'), activeMenu: '/tickets/tickets' },
hidden: true
},
{
path: 'tickets/request-asset-perm/:id',
name: 'AssetsTicketDetail',
component: () => import('@/views/tickets/RequestAssetPerm/Detail/index'),
meta: { title: i18n.t('route.TicketDetail'), activeMenu: '/tickets/tickets' },
hidden: true
}
]

View File

@ -1,10 +1,7 @@
import Layout from '@/layout/index'
import i18n from '@/i18n/i18n'
import rolec from '@/utils/role'
const scheme = document.location.protocol
const port = document.location.port ? ':' + document.location.port : ''
const URL = scheme + '//' + document.location.hostname + port
import { BASE_URL } from '@/utils/common'
export default [
// 404 page must be placed at the end !!!
@ -47,6 +44,12 @@ export default [
name: 'MyDatebases',
component: () => import('@/userviews/apps/DatabaseApp'),
meta: { title: i18n.t('route.DatabaseApp'), permissions: [rolec.PERM_USE] }
},
{
path: '/apps/kubernetes',
name: 'MyKubernetes',
component: () => import('@/userviews/apps/KubernetesApp'),
meta: { title: i18n.t('route.KubernetesApp'), permissions: [rolec.PERM_USE] }
}
]
},
@ -66,6 +69,45 @@ export default [
}
]
},
{
path: '/tickets',
component: Layout,
children: [
{
path: '',
name: 'TicketList',
component: () => import('@/views/tickets/TicketList'),
meta: { title: i18n.t('route.Tickets'), icon: 'check-square-o', activeMenu: '/tickets', permissions: [rolec.PERM_USE] }
},
{
path: 'tickets/request-asset-perm/create',
name: 'RequestAssetPermTicketCreateUpdate',
component: () => import('@/views/tickets/RequestAssetPerm/RequestAssetPermTicketCreateUpdate'),
meta: { title: i18n.t('route.TicketDetail'), activeMenu: '/tickets', permissions: [rolec.PERM_USE] },
hidden: true
},
{
path: 'tickets/request-asset-perm/:id',
name: 'AssetsTicketDetail',
component: () => import('@/views/tickets/RequestAssetPerm/Detail/index'),
meta: { title: i18n.t('route.TicketDetail'), activeMenu: '/tickets', permissions: [rolec.PERM_USE] },
hidden: true
},
{
path: 'tickets/:id',
name: 'TicketDetail',
component: () => import('@/views/tickets/TicketDetail/index'),
meta: { title: i18n.t('route.TicketDetail'), activeMenu: '/tickets', permissions: [rolec.PERM_USE] },
hidden: true
}
],
meta: {
title: i18n.t('route.Tickets'),
icon: 'history',
permissions: [rolec.PERM_USE],
licenseRequired: true
}
},
{
path: `external-luna`,
component: Layout,
@ -74,7 +116,7 @@ export default [
},
children: [
{
path: `${URL}/luna/`,
path: `${BASE_URL}/luna/`,
meta: { title: i18n.t('route.WebTerminal'), icon: 'window-maximize', activeMenu: '/assets', permissions: [rolec.PERM_USE] }
}
]
@ -87,7 +129,7 @@ export default [
},
children: [
{
path: `${URL}/koko/elfinder/sftp/`,
path: `${BASE_URL}/koko/elfinder/sftp/`,
meta: { title: i18n.t('route.WebFTP'), icon: 'file', activeMenu: '/assets', permissions: [rolec.PERM_USE] }
}
]

View File

@ -84,7 +84,7 @@ li.is-active {
// line-height: 30px !important;
//}
////重置字体大小 菜单宽度
//.el-submenu /deep/ .el-submenu__title, .submenu-title-noDropdown{
//.el-submenu ::v-deep .el-submenu__title, .submenu-title-noDropdown{
// height: 46px !important;
// line-height: 46px !important;
//}

View File

@ -69,13 +69,13 @@ export default {
headerActions: {
hasExport: false,
hasImport: false,
hasRefresh: false,
hasRefresh: true,
hasCreate: false,
hasBulkDelete: false,
hasBulkUpdate: false,
hasLeftActions: false,
hasSearch: true,
hasRightActions: false
hasRightActions: true
}
}
},

View File

@ -0,0 +1,94 @@
<template>
<Page>
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
</Page>
</template>
<script>
import ListTable from '@/components/ListTable/index'
import Page from '@/layout/components/Page/index'
import { ActionsFormatter } from '@/components/ListTable/formatters'
export default {
name: 'KubernetesApp',
components: {
ListTable,
Page
},
props: {
},
data() {
return {
tableConfig: {
url: `/api/v1/perms/users/k8s-apps/`,
columns: [
{
prop: 'name',
align: 'center',
label: this.$t('assets.Name'),
sortable: true
},
{
prop: 'type_display',
align: 'center',
label: this.$t('assets.Type')
},
{
prop: 'cluster',
align: 'center',
label: this.$t('applications.cluster')
},
{
prop: 'comment',
align: 'center',
label: this.$t('assets.Comment')
},
{
prop: 'id',
align: 'center',
label: this.$t('assets.Action'),
formatter: ActionsFormatter,
formatterArgs: {
hasDelete: false,
hasUpdate: false,
extraActions: [
{
name: 'connect',
fa: 'fa-terminal',
type: 'primary',
callback: function({ row, col, cellValue, reload }) {
window.open(`/luna/?type=k8s_app&login_to=${cellValue}`, '_blank')
}
}
]
}
}
]
},
headerActions: {
hasExport: false,
hasImport: false,
hasRefresh: true,
hasCreate: false,
hasBulkDelete: false,
hasBulkUpdate: false,
hasLeftActions: false,
hasSearch: true,
hasRightActions: true
}
}
},
computed: {
},
mounted() {
},
methods: {
}
}
</script>
<style lang='less' scoped>
</style>

View File

@ -68,13 +68,13 @@ export default {
headerActions: {
hasExport: false,
hasImport: false,
hasRefresh: false,
hasRefresh: true,
hasCreate: false,
hasBulkDelete: false,
hasBulkUpdate: false,
hasLeftActions: false,
hasSearch: true,
hasRightActions: false
hasRightActions: true
}
}
},

View File

@ -54,12 +54,37 @@ export default {
type: 'primary',
label: this.$t('common.Update')
},
has: this.object.mfa_enabled,
callbacks: {
click: function() {
window.location.href = `/core/auth/profile/otp/update/?next=${this.$route.fullPath}`
}.bind(this)
}
},
{
title: this.$t('users.UpdatePassword'),
attrs: {
type: 'primary',
label: this.$t('common.Update')
},
callbacks: {
click: function() {
this.$emit('update:activeMenu', 'PasswordUpdate')
}.bind(this)
}
},
{
title: this.$t('users.UpdateSSHKey'),
attrs: {
type: 'primary',
label: this.$t('common.Update')
},
callbacks: {
click: function() {
this.$emit('update:activeMenu', 'SSHUpdate')
}.bind(this)
}
},
{
title: this.$t('users.ResetPublicKeyAndDownload'),
attrs: {

View File

@ -1,7 +1,7 @@
<template>
<GenericDetailPage :object.sync="user" :active-menu.sync="config.activeMenu" v-bind="config" v-on="$listeners">
<keep-alive>
<component :is="config.activeMenu" :object="user" />
<component :is="config.activeMenu" :object="user" @update:activeMenu="handleUpdate" />
</keep-alive>
</GenericDetailPage>
</template>
@ -60,6 +60,9 @@ export default {
])
}
return submenu
},
handleUpdate(value) {
this.config.activeMenu = value
}
}
}

View File

@ -136,6 +136,13 @@ export function getDaysAgo(days, now) {
return new Date(now.getTime() - 3600 * 1000 * 24 * days)
}
export function getDaysFuture(days, now) {
if (!now) {
now = new Date()
}
return new Date(now.getTime() + 3600 * 1000 * 24 * days)
}
export function setUrlParam(url, name, value) {
const urlArray = url.split('?')
if (urlArray.length === 1) {
@ -164,3 +171,9 @@ export function getDayFuture(days, now) {
}
return new Date(now.getTime() + 3600 * 1000 * 24 * days)
}
const scheme = document.location.protocol
const port = document.location.port ? ':' + document.location.port : ''
const BASE_URL = scheme + '//' + document.location.hostname + port
export { BASE_URL }

View File

@ -1,6 +1,5 @@
import axios from 'axios'
import i18n from '@/i18n/i18n'
import NProgress from 'nprogress' // progress bar
import { Message, MessageBox } from 'element-ui'
import store from '@/store'
@ -36,7 +35,7 @@ function beforeRequestAddTimezone(config) {
service.interceptors.request.use(
config => {
// do something before request is sent
NProgress.start()
// NProgress.start()
beforeRequestAddToken(config)
beforeRequestAddTimezone(config)
return config
@ -101,7 +100,7 @@ service.interceptors.response.use(
* You can also judge the status by HTTP Status Code
*/
response => {
NProgress.done()
// NProgress.done()
const res = response.data
if (response.config.raw === 1) {
@ -110,7 +109,7 @@ service.interceptors.response.use(
return res
},
error => {
NProgress.done()
// NProgress.done()
if (!error.response) {
return Promise.reject(error)
}

View File

@ -18,7 +18,14 @@ export default {
],
columnsMeta: {
get_type_display: {
label: this.$t('applications.type')
label: this.$t('applications.type'),
width: '80px'
},
host: {
width: '140px'
},
port: {
width: '60px'
},
database: {
showOverflowTooltip: true

View File

@ -0,0 +1,37 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
</template>
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
export default {
components: {
GenericCreateUpdatePage
},
data() {
return {
initial: {
type: 'k8s'
},
fields: [
[this.$t('common.Basic'), ['name', 'type']],
[this.$t('applications.kubernetes'), ['cluster']],
[this.$t('common.Other'), ['comment']]
],
fieldsMeta: {
type: {
disabled: true
}
},
url: '/api/v1/applications/k8s-apps/'
}
},
computed: {
}
}
</script>
<style lang="less" scoped>
</style>

View File

@ -0,0 +1,63 @@
<template>
<el-row :gutter="20">
<el-col :md="14" :sm="24">
<DetailCard :title="cardTitle" :items="detailItems" />
</el-col>
</el-row>
</template>
<script>
import DetailCard from '@/components/DetailCard'
import { toSafeLocalDateStr } from '@/utils/common'
export default {
name: 'DatabaseAppDetail',
components: {
DetailCard
},
props: {
object: {
type: Object,
default: () => ({})
}
},
computed: {
cardTitle() {
return this.object.name
},
detailItems() {
return [
{
key: this.$t('common.Name'),
value: this.object.name
},
{
key: this.$t('applications.type'),
value: this.object.type_display
},
{
key: this.$t('applications.cluster'),
value: this.object.cluster
},
{
key: this.$t('common.dateCreated'),
value: toSafeLocalDateStr(this.object.date_created)
},
{
key: this.$t('common.createdBy'),
value: this.object.created_by
},
{
key: this.$t('common.Comment'),
value: this.object.comment
}
]
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,44 @@
<template>
<GenericDetailPage :object.sync="KubernetesApp" :active-menu.sync="config.activeMenu" v-bind="config" v-on="$listeners">
<keep-alive>
<component :is="config.activeMenu" :object="KubernetesApp" />
</keep-alive>
</GenericDetailPage>
</template>
<script>
import { GenericDetailPage, TabPage } from '@/layout/components'
import KubernetesAppDetail from './KubernetesAppDetail'
export default {
components: {
GenericDetailPage,
KubernetesAppDetail,
TabPage
},
data() {
return {
KubernetesApp: {
name: '', type_display: '', cluster: '', date_created: '', created_by: '', comment: ''
},
config: {
activeMenu: 'KubernetesAppDetail',
submenu: [
{
title: this.$t('route.KubernetesAppDetail'),
name: 'KubernetesAppDetail'
}
],
actions: {
detailApiUrl: `/api/v1/applications/k8s-apps/${this.$route.params.id}/`,
deleteApiUrl: `/api/v1/applications/k8s-apps/${this.$route.params.id}/`
}
}
}
}
}
</script>
<style lang='scss' scoped>
</style>

View File

@ -0,0 +1,36 @@
<template>
<GenericListPage :table-config="tableConfig" :header-actions="headerActions" />
</template>
<script>
import { GenericListPage } from '@/layout/components'
export default {
components: {
GenericListPage
},
data() {
return {
tableConfig: {
url: '/api/v1/applications/k8s-apps/',
columns: [
'name', 'cluster', 'comment', 'actions'
],
columnsMeta: {
comment: {
width: '340px'
}
}
},
headerActions: {
hasBulkDelete: false,
createRoute: 'KubernetesAppCreate'
}
}
}
}
</script>
<style>
</style>

View File

@ -21,7 +21,8 @@ export default {
],
columnsMeta: {
type: {
displayKey: 'get_type_display'
displayKey: 'get_type_display',
width: '140px'
},
asset: {
showOverflowTooltip: true,

View File

@ -18,7 +18,7 @@ export const REMOTE_APP_TYPE_FIELDS_MAP = {
label: i18n.t('applications.chrome_username')
},
{
id: 'chrome_password', el: {}, attrs: {}, type: 'input', prop: 'chrome_password',
id: 'chrome_password', el: { 'show-password': true }, attrs: {}, type: 'input', prop: 'chrome_password',
label: i18n.t('applications.chrome_password')
}
],
@ -27,6 +27,10 @@ export const REMOTE_APP_TYPE_FIELDS_MAP = {
id: 'mysql_workbench_ip', el: {}, attrs: {}, type: 'input', prop: 'mysql_workbench_ip',
label: i18n.t('applications.mysql_workbench_ip')
},
{
id: 'mysql_workbench_port', el: {}, attrs: {}, type: 'input', prop: 'mysql_workbench_port',
label: i18n.t('applications.mysql_workbench_port')
},
{
id: 'mysql_workbench_name', el: {}, attrs: {}, type: 'input', prop: 'mysql_workbench_name',
label: i18n.t('applications.mysql_workbench_name')
@ -36,7 +40,7 @@ export const REMOTE_APP_TYPE_FIELDS_MAP = {
label: i18n.t('applications.mysql_workbench_username')
},
{
id: 'mysql_workbench_password', el: {}, attrs: {}, type: 'input', prop: 'mysql_workbench_password',
id: 'mysql_workbench_password', el: { 'show-password': true }, attrs: {}, type: 'input', prop: 'mysql_workbench_password',
label: i18n.t('applications.mysql_workbench_password')
}
],
@ -50,7 +54,7 @@ export const REMOTE_APP_TYPE_FIELDS_MAP = {
label: i18n.t('applications.vmware_username')
},
{
id: 'vmware_password', el: {}, attrs: {}, type: 'input', prop: 'vmware_password',
id: 'vmware_password', el: { 'show-password': true }, attrs: {}, type: 'input', prop: 'vmware_password',
label: i18n.t('applications.vmware_password')
}
],
@ -68,7 +72,7 @@ export const REMOTE_APP_TYPE_FIELDS_MAP = {
label: i18n.t('applications.custom_username')
},
{
id: 'custom_password', el: {}, attrs: {}, type: 'input', prop: 'custom_password',
id: 'custom_password', el: { 'show-password': true }, attrs: {}, type: 'input', prop: 'custom_password',
label: i18n.t('applications.custom_password')
}
]

View File

@ -33,7 +33,8 @@ export default {
},
{
prop: 'assets_amount',
label: this.$t('assets.Assets')
label: this.$t('assets.Assets'),
width: '80px'
},
{
prop: 'comment',

View File

@ -176,7 +176,7 @@ export default {
},
{
key: this.$t('assets.Domain'),
value: this.object.domain
value: this.object.domain_display
},
{
key: this.$t('assets.Vendor'),
@ -208,7 +208,7 @@ export default {
},
{
key: this.$t('assets.IsActive'),
value: this.object.is_active.toString()
value: this.object.is_active
},
{
key: this.$t('assets.SerialNumber'),

View File

@ -150,14 +150,14 @@ export default {
background-color: #fff;
}
.el-select /deep/ .el-input__inner {
.el-select ::v-deep .el-input__inner {
width: 100px;
}
.input-button {
margin-top: 4px;
}
.input-button /deep/ .el-button.el-button--mini {
.input-button ::v-deep .el-button.el-button--mini {
height: 25px;
padding: 5px;
}

View File

@ -22,6 +22,15 @@ export default {
url: `/api/v1/assets/cmd-filters/${this.object.id}/rules/`,
columns: ['type', 'content', 'priority', 'action', 'comment', 'actions'],
columnsMeta: {
type: {
width: '100px'
},
priority: {
width: '70px'
},
action: {
width: '90px'
},
content: {
showOverflowTooltip: true
},

View File

@ -25,8 +25,15 @@ export default {
sortable: 'custom',
formatter: DisplayFormatter
},
ip: {
width: '140px'
},
port: {
width: '60px'
},
protocol: {
sortable: 'custom'
sortable: 'custom',
width: '100px'
},
actions: {
formatterArgs: {

View File

@ -29,7 +29,8 @@ export default {
},
{
prop: 'asset_count',
label: this.$t('assets.Assets')
label: this.$t('assets.Assets'),
width: '80px'
},
{
prop: 'id',

View File

@ -28,7 +28,8 @@ export default {
{
prop: 'base',
label: this.$t('assets.BasePlatform'),
sortable: 'custom'
sortable: 'custom',
width: '140px'
},
{
prop: 'comment',

View File

@ -8,7 +8,7 @@ import UploadKey from '@/components/UploadKey'
import { Select2 } from '@/components'
// const asciiProtocols = ['ssh', 'telnet', 'mysql']
const graphProtocols = ['vnc', 'rdp']
const graphProtocols = ['vnc', 'rdp', 'k8s']
export default {
name: 'SystemUserCreateUpdate',
@ -28,17 +28,41 @@ export default {
},
fields: [
[this.$t('common.Basic'), ['name', 'login_mode', 'username', 'username_same_with_user', 'priority', 'protocol']],
[this.$t('common.Auth'), ['auto_push', 'auto_generate_key', 'password', 'private_key']],
[this.$t('assets.AutoPush'), ['auto_push', 'sudo', 'shell', 'home', 'system_groups']],
[this.$t('common.Auth'), ['auto_generate_key', 'password', 'private_key']],
[this.$t('common.Command filter'), ['cmd_filters']],
[this.$t('common.Other'), ['sftp_root', 'sudo', 'shell', 'comment']]
[this.$t('common.Other'), ['sftp_root', 'comment']]
],
fieldsMeta: {
login_mode: {
helpText: this.$t('assets.LoginModeHelpMessage')
helpText: this.$t('assets.LoginModeHelpMessage'),
hidden: (form) => {
if (form.protocol === 'k8s') {
return true
}
}
},
username: {
el: {
disabled: false
},
on: {
input: ([value], updateForm) => {
if (value) {
updateForm({ home: '/home/' + value })
}
}
},
rules: [{ required: true }],
hidden: (form) => {
if (form.login_mode === 'manual') {
this.fieldsMeta.username.rules[0].required = false
} else {
this.fieldsMeta.username.rules[0].required = true
}
if (form.username_same_with_user) {
this.fieldsMeta.username.rules[0].required = false
}
}
},
private_key: {
@ -47,6 +71,9 @@ export default {
if (form.login_mode !== 'auto') {
return true
}
if (form.protocol === 'k8s') {
return true
}
return form.auto_generate_key === true
}
},
@ -55,6 +82,9 @@ export default {
helpText: this.$t('assets.UsernameHelpMessage'),
hidden: (form) => {
this.fieldsMeta.username.el.disabled = form.username_same_with_user
if (form.protocol === 'k8s') {
return true
}
return false
}
},
@ -68,8 +98,20 @@ export default {
if (form.login_mode !== 'auto') {
return true
}
if (form.protocol === 'k8s') {
return true
}
}
},
token: {
rules: [
{ required: true }
],
el: {
type: 'password'
},
hidden: form => form.protocol !== 'k8s'
},
protocol: {
rules: [
{ required: true }
@ -97,7 +139,14 @@ export default {
},
auto_push: {
type: 'switch',
hidden: form => form.login_mode !== 'auto'
hidden: form => {
if (form.login_mode !== 'auto') {
return true
}
if (form.protocol === 'k8s') {
return true
}
}
},
sftp_root: {
rules: [
@ -111,7 +160,7 @@ export default {
{ required: true }
],
helpText: this.$t('assets.SudoHelpMessage'),
hidden: (item) => item.protocol !== 'ssh'
hidden: (item) => item.protocol !== 'ssh' || !item.auto_push
},
password: {
helpText: this.$t('assets.PasswordHelpMessage'),
@ -119,22 +168,32 @@ export default {
if (form.login_mode !== 'auto') {
return true
}
if (form.protocol === 'k8s') {
return true
}
return form.auto_generate_key === true
}
},
shell: {
hidden: (item) => item.protocol !== 'ssh',
hidden: (item) => item.protocol !== 'ssh' || !item.auto_push,
rules: [
{ required: true }
]
},
home: {
label: this.$t('assets.Home'),
hidden: (item) => item.protocol !== 'ssh' || !item.auto_push || item.username_same_with_user,
helpText: this.$t('assets.HomeHelpMessage')
},
system_groups: {
label: this.$t('assets.LinuxUserAffiliateGroup'),
hidden: (item) => ['ssh', 'rdp'].indexOf(item.protocol) === -1 || !item.auto_push || item.username_same_with_user,
helpText: this.$t('assets.GroupsHelpMessage')
}
},
url: '/api/v1/assets/system-users/',
authHiden: false
}
},
computed: {
}
}
</script>

View File

@ -34,15 +34,18 @@ export default {
{
prop: 'protocol',
label: this.$t('assets.Protocol'),
sortable: 'custom'
sortable: 'custom',
width: '100px'
},
{
prop: 'login_mode_display',
label: this.$t('assets.LoginModel')
label: this.$t('assets.LoginModel'),
width: '120px'
},
{
prop: 'assets_amount',
label: this.$t('assets.Assets')
label: this.$t('assets.Assets'),
width: '80px'
},
{
prop: 'comment',

View File

@ -32,6 +32,7 @@ export default {
],
columnsMeta: {
hosts: {
width: '60px',
formatter: (row, col, cellValue) => {
const onClick = () => {
vm.relationDialog.tableConfig.url = setUrlParam(vm.relationDialog.tableConfig.url, 'commandexecution', row.id)

View File

@ -38,7 +38,7 @@ export default {
showOverflowTooltip: true
},
operate: {
width: '90px'
width: '100px'
},
is_success: {
width: '80px'

View File

@ -29,15 +29,18 @@ export default {
ip: {
width: '140px'
},
city: {
width: '90px'
},
status: {
width: '80px'
width: '85px'
},
mfa: {
label: 'MFA',
width: '80px'
},
type: {
width: '100px'
width: '110px'
},
datetime: {
width: '160px'

View File

@ -23,7 +23,8 @@ export default {
showOverflowTooltip: true
},
resource_type: {
showOverflowTooltip: true
showOverflowTooltip: true,
width: '180px'
},
resource: {
showOverflowTooltip: true

View File

@ -26,7 +26,8 @@ export default {
showOverflowTooltip: true
},
remote_addr: {
showOverflowTooltip: true
showOverflowTooltip: true,
width: '140px'
},
datetime: {
width: '180px'

View File

@ -27,7 +27,7 @@
<el-option
v-for="item in options"
:key="item.id"
:disabled="item.protocol !== 'ssh' && item.login_mode!== 'auto'"
:disabled="item.protocol !== 'ssh' || item.login_mode!== 'auto'"
:label="`${item.name}(${item.username})`"
:value="item.id"
/>
@ -266,7 +266,7 @@ export default {
overflow: auto;
/*border-right: solid 1px red;*/
}
.vue-codemirror-wrap /deep/ .CodeMirror{
.vue-codemirror-wrap ::v-deep .CodeMirror{
width: 600px;
height: 100px;
border: 1px solid #eee;

View File

@ -48,10 +48,6 @@ export default {
},
detailCardItems() {
return [
{
key: this.$t('ops.ID'),
value: this.object.id
},
{
key: this.$t('ops.hosts'),
value: JSON.stringify(this.object.hosts.length)
@ -95,11 +91,11 @@ export default {
},
{
key: this.$t('ops.isFinished'),
value: this.toBooleanDisplay(this.object.latest_execution.is_finished)
value: this.object.latest_execution.is_finished
},
{
key: this.$t('ops.isSuccess'),
value: this.toBooleanDisplay(this.object.latest_execution.is_success)
value: this.object.latest_execution.is_success
},
{
key: this.$t('ops.tasks'),
@ -132,15 +128,6 @@ export default {
lines.push(`${i}. ${content.name} ::: ${content.action.module}`)
}
return lines
},
toBooleanDisplay(value) {
if (value === true) {
return this.$t('ops.Yes')
} else if (value === false) {
return this.$t('ops.No')
} else {
return this.$t('ops.Unkown')
}
}
}
}

View File

@ -28,7 +28,7 @@ export default {
stat: {
label: this.$t('ops.stat'),
align: 'center',
width: '80px',
width: '100px',
formatter: function(row) {
const summary = <div>
<span class='text-primary'>{row.stat.success}</span>/
@ -52,16 +52,17 @@ export default {
},
is_finished: {
align: 'center',
width: '200px',
width: '100px',
label: this.$t('ops.isFinished')
},
is_success: {
align: 'center',
width: '200px',
width: '100px',
label: this.$t('ops.isSuccess')
},
timedelta: {
label: this.$t('ops.time'),
width: '100px',
formatter: function(row) {
return row.timedelta.toFixed(2) + 's'
}

View File

@ -48,10 +48,6 @@ export default {
},
detailCardItems() {
return [
{
key: this.$t('ops.ID'),
value: this.object.id
},
{
key: this.$t('ops.taskName'),
value: this.object.task_display.replace('@', '')
@ -70,11 +66,11 @@ export default {
},
{
key: this.$t('ops.isFinished'),
value: this.toChoicesDisplay(this.object.is_finished)
value: this.object.is_finished
},
{
key: this.$t('ops.isSuccess'),
value: this.toChoicesDisplay(this.object.is_success)
value: this.object.is_success
},
{
key: this.$t('ops.output'),
@ -89,14 +85,6 @@ export default {
}
]
}
},
methods: {
toChoicesDisplay(c) {
if (!c) {
return this.$t('ops.No')
}
return this.$t('ops.Yes')
}
}
}
</script>

View File

@ -34,6 +34,7 @@ export default {
},
hosts: {
label: this.$t('ops.hosts'),
width: '80px',
formatter: (row, column, cellValue) => {
if (cellValue instanceof Array) {
return cellValue.length
@ -43,7 +44,8 @@ export default {
showOverflowTooltip: true
},
pattern: {
label: this.$t('ops.pattern')
label: this.$t('ops.pattern'),
width: '80px'
},
run_as: {
label: this.$t('ops.runAs'),
@ -67,6 +69,7 @@ export default {
},
actions: {
prop: 'id',
width: '80px',
formatterArgs: {
hasEdit: false,
hasDelete: false,

View File

@ -49,10 +49,6 @@ export default {
},
detailCardItems() {
return [
{
key: this.$t('ops.ID'),
value: this.object.id
},
{
key: this.$t('common.Name'),
value: this.object.name
@ -83,11 +79,11 @@ export default {
},
{
key: this.$t('ops.isFinished'),
value: this.toChoicesDisplay(this.object.latest_execution.is_finished)
value: this.object.latest_execution.is_finished
},
{
key: this.$t('ops.isSuccess'),
value: this.toChoicesDisplay(this.object.latest_execution.is_success)
value: this.object.latest_execution.is_success
},
{
key: this.$t('ops.contents'),
@ -115,12 +111,6 @@ export default {
}
},
methods: {
toChoicesDisplay(c) {
if (!c) {
return this.$t('ops.No')
}
return this.$t('ops.Yes')
},
toContentsDisplay(contents) {
const lines = []
for (let i = 0; i < contents.length; i++) {

View File

@ -27,12 +27,13 @@ export default {
],
columnsMeta: {
date_start: {
formatter: (row) => toSafeLocalDateStr(row.date_start)
formatter: (row) => toSafeLocalDateStr(row.date_start),
width: '160px'
},
stat: {
label: this.$t('ops.stat'),
align: 'center',
width: '80px',
width: '100px',
formatter: function(row) {
return (
<div>
@ -57,16 +58,17 @@ export default {
},
is_finished: {
align: 'center',
width: '200px',
width: '100px',
label: this.$t('ops.isFinished')
},
is_success: {
align: 'center',
width: '200px',
width: '100px',
label: this.$t('ops.isSuccess')
},
timedelta: {
label: this.$t('ops.time'),
width: '100px',
formatter: function(row) {
return row.timedelta.toFixed(2) + 's'
}

View File

@ -91,7 +91,8 @@ export default {
},
actions: {
label: this.$t('perms.Actions'),
component: AssetPermissionFormActionField
component: AssetPermissionFormActionField,
helpText: this.$t('common.actionsTips')
},
date_start: {
label: this.$t('common.dateStart')

View File

@ -22,7 +22,6 @@ export default {
},
tableConfig: {
url: '/api/v1/perms/asset-permissions/',
hasSelection: false,
hasTree: true,
columns: ['name', 'users_amount', 'user_groups_amount', 'assets_amount', 'nodes_amount', 'system_users_amount', 'is_valid', 'actions'],
columnsMeta: {
@ -36,6 +35,7 @@ export default {
},
users_amount: {
label: this.$t('perms.User'),
width: '60px',
formatter: DetailFormatter,
formatterArgs: {
routeQuery: {
@ -45,6 +45,7 @@ export default {
},
user_groups_amount: {
label: this.$t('perms.UserGroups'),
width: '100px',
formatter: DetailFormatter,
formatterArgs: {
routeQuery: {
@ -54,6 +55,7 @@ export default {
},
assets_amount: {
label: this.$t('perms.Asset'),
width: '60px',
formatter: DetailFormatter,
formatterArgs: {
routeQuery: {
@ -63,6 +65,7 @@ export default {
},
nodes_amount: {
label: this.$t('perms.Node'),
width: '60px',
formatter: DetailFormatter,
formatterArgs: {
routeQuery: {
@ -72,6 +75,7 @@ export default {
},
system_users_amount: {
label: this.$t('perms.SystemUser'),
width: '100px',
formatter: DetailFormatter,
formatterArgs: {
routeQuery: {
@ -129,7 +133,6 @@ export default {
}
]
},
hasRightActions: false,
hasBulkDelete: false,
hasBulkUpdate: false,
extraMoreActions: [

View File

@ -6,17 +6,18 @@
:default-expand-all="true"
:default-checked-keys="value"
:props="defaultProps"
v-bind="$attrs"
@check="handleCheckChange"
/>
</template>
<script>
export default {
name: 'AssetPermissionFormActionFiel',
name: 'AssetPermissionFormActionField',
props: {
value: {
type: Array,
default: () => ['all', 'connect', 'upload_file', 'download_file', 'updownload']
default: () => ['all', 'connect', 'upload_file', 'download_file', 'updownload', 'clipboard_copy_paste', 'clipboard_copy', 'clipboard_paste']
}
},
data() {
@ -47,6 +48,20 @@ export default {
label: this.$t('perms.downloadFile')
}
]
},
{
id: 'clipboard_copy_paste',
label: this.$t('perms.clipboardCopyPaste'),
children: [
{
id: 'clipboard_copy',
label: this.$t('perms.clipboardCopy')
},
{
id: 'clipboard_paste',
label: this.$t('perms.clipboardPaste')
}
]
}
]
}

View File

@ -67,7 +67,7 @@ export default {
value: this.object.user_groups_amount
},
{
key: this.$t('perms.remoteAppCount'),
key: this.$t('perms.DatabaseAppCount'),
value: this.object.database_apps_amount
},
{

View File

@ -26,6 +26,7 @@ export default {
},
users_amount: {
label: this.$t('perms.User'),
width: '110px',
formatter: DetailFormatter,
formatterArgs: {
routeQuery: {
@ -35,6 +36,7 @@ export default {
},
user_groups_amount: {
label: this.$t('perms.UserGroups'),
width: '110px',
formatter: DetailFormatter,
formatterArgs: {
routeQuery: {
@ -44,6 +46,7 @@ export default {
},
database_apps_amount: {
label: this.$t('perms.databaseApp'),
width: '110px',
formatter: DetailFormatter,
formatterArgs: {
routeQuery: {
@ -53,6 +56,7 @@ export default {
},
system_users_amount: {
label: this.$t('perms.SystemUser'),
width: '110px',
formatter: DetailFormatter,
formatterArgs: {
routeQuery: {
@ -63,7 +67,6 @@ export default {
}
},
headerActions: {
hasRightActions: false,
hasBulkDelete: false
}
}

View File

@ -0,0 +1,80 @@
<template>
<GenericCreateUpdatePage :fields="fields" :initial="initial" :fields-meta="fieldsMeta" :url="url" />
</template>
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import { getDayFuture } from '@/utils/common'
export default {
components: {
GenericCreateUpdatePage
},
data() {
return {
initial: {
is_active: true,
date_start: new Date().toISOString(),
date_expired: getDayFuture(36500, new Date()).toISOString()
},
fields: [
[this.$t('common.' + 'Basic'), ['name']],
[this.$t('perms.' + 'User'), ['users', 'user_groups']],
[this.$t('perms.' + 'KubernetesApp'), ['k8s_apps', 'system_users']],
[this.$t('common.Other'), ['is_active', 'date_start', 'date_expired', 'comment']]
],
url: '/api/v1/perms/k8s-app-permissions/',
fieldsMeta: {
users: {
el: {
value: [],
ajax: {
url: '/api/v1/users/users/?fields_size=mini',
transformOption: (item) => {
return { label: item.name + '(' + item.username + ')', value: item.id }
}
}
}
},
user_groups: {
el: {
value: [],
url: '/api/v1/users/groups/'
}
},
k8s_apps: {
label: this.$t('perms.KubernetesApp'),
el: {
value: [],
url: '/api/v1/applications/k8s-apps/'
}
},
system_users: {
el: {
value: [],
ajax: {
url: '/api/v1/assets/system-users/?protocol=k8s'
}
}
},
date_start: {
label: this.$t('common.dateStart')
},
date_expired: {
label: this.$t('common.dateExpired')
},
actions: {
label: this.$t('perms.Actions')
},
is_active: {
type: 'checkbox'
}
}
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,105 @@
<template>
<el-row :gutter="20">
<el-col :md="14" :sm="24">
<DetailCard :items="detailCardItems" />
</el-col>
<el-col :md="10" :sm="24">
<QuickActions type="primary" :actions="quickActions" />
</el-col>
</el-row>
</template>
<script>
import DetailCard from '@/components/DetailCard'
import QuickActions from '@/components/QuickActions'
import { toSafeLocalDateStr } from '@/utils/common'
export default {
name: 'KubernetesAppPermissionDetail',
components: {
DetailCard,
QuickActions
},
props: {
object: {
type: Object,
default: () => ({})
}
},
data() {
return {
quickActions: [
{
title: this.$t('common.Active'),
type: 'switcher',
attrs: {
model: this.object.is_active
},
callbacks: {
change: function(val) {
this.$axios.patch(
`/api/v1/perms/k8s-app-permissions/${this.object.id}/`,
{ is_active: val }
).then(res => {
this.$message.success(this.$t('common.updateSuccessMsg'))
}).catch(err => {
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + err))
})
}.bind(this)
}
}
]
}
},
computed: {
detailCardItems() {
return [
{
key: this.$t('common.Name'),
value: this.object.name
},
{
key: this.$t('perms.userCount'),
value: this.object.users_amount
},
{
key: this.$t('perms.userGroupCount'),
value: this.object.user_groups_amount
},
{
key: this.$t('perms.KubernetesAppCount'),
value: this.object.k8s_apps_amount
},
{
key: this.$t('perms.systemUserCount'),
value: this.object.system_users_amount
},
{
key: this.$t('perms.dateStart'),
value: toSafeLocalDateStr(this.object.date_start)
},
{
key: this.$t('common.dateExpired'),
value: toSafeLocalDateStr(this.object.date_expired)
},
{
key: this.$t('common.dateCreated'),
value: toSafeLocalDateStr(this.object.date_created)
},
{
key: this.$t('common.createdBy'),
value: this.object.created_by
},
{
key: this.$t('common.Comment'),
value: this.object.comment
}
]
}
}
}
</script>
<style lang="less" scoped>
</style>

View File

@ -0,0 +1,152 @@
<template>
<el-row :gutter="20">
<el-col :md="14" :sm="24">
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
</el-col>
<el-col :md="10" :sm="24">
<RelationCard type="primary" v-bind="k8sAppRelationConfig" />
<RelationCard type="info" style="margin-top: 15px" v-bind="systemUserRelationConfig" />
</el-col>
</el-row>
</template>
<script>
import ListTable from '@/components/ListTable'
import RelationCard from '@/components/RelationCard'
import { DeleteActionFormatter } from '@/components/ListTable/formatters/index'
export default {
name: 'KubernetesAppPermissionKubernetesApp',
components: {
ListTable,
RelationCard
},
props: {
object: {
type: Object,
default: () => ({})
}
},
data() {
return {
tableConfig: {
url: `/api/v1/perms/k8s-app-permissions/${this.object.id}/k8s-apps/all/`,
columns: [
'k8sapp_display', 'delete_action'
],
columnsMeta: {
k8sapp_display: {
label: this.$t('perms.KubernetesApp'),
align: 'center'
},
delete_action: {
prop: 'k8sapp',
label: this.$t('common.Actions'),
align: 'center',
width: 150,
objects: this.object.k8s_apps,
formatter: DeleteActionFormatter,
deleteUrl: `/api/v1/perms/k8s-app-permissions-k8s-apps-relations/?k8sapppermission=${this.$route.params.id}&k8sapp=`
}
},
tableAttrs: {
border: false
}
},
headerActions: {
hasSearch: true,
hasRefresh: true,
hasLeftActions: true,
hasRightActions: true,
hasExport: false,
hasImport: false,
hasCreate: false,
hasBulkDelete: false,
hasBulkUpdate: false
},
k8sAppRelationConfig: {
icon: 'fa-edit',
title: this.$t('perms.addK8sAppToThisPermission'),
objectsAjax: {
url: '/api/v1/applications/k8s-apps/'
},
hasObjectsId: this.object.k8s_apps,
showHasObjects: false,
performAdd: (items) => {
const relationUrl = `/api/v1/perms/k8s-app-permissions-k8s-apps-relations/`
const objectId = this.object.id
const data = items.map(v => {
return {
k8sapppermission: objectId,
k8sapp: v.value
}
})
return this.$axios.post(relationUrl, data)
},
onAddSuccess: (objects, that) => {
this.$log.debug('Select value', that.select2.value)
that.iHasObjects = [...that.iHasObjects, ...objects]
that.$refs.select2.clearSelected()
this.$message.success(this.$t('common.updateSuccessMsg'))
this.$refs.ListTable.reloadTable()
}
},
systemUserRelationConfig: {
icon: 'fa-edit',
title: this.$t('perms.addSystemUserToThisPermission'),
objectsAjax: {
url: '/api/v1/assets/system-users/',
processResults(data) {
let results = data.results
results = results.filter((item) => item.protocol === 'k8s').map((item) => {
return { label: item.name, value: item.id }
})
const more = !!data.next
return { results: results, pagination: more, total: data.count }
}
},
hasObjectsId: this.object.system_users,
performAdd: (items) => {
const relationUrl = `/api/v1/perms/k8s-app-permissions-system-users-relations/`
const objectId = this.object.id
const data = items.map(v => {
return {
k8sapppermission: objectId,
systemuser: v.value
}
})
return this.$axios.post(relationUrl, data)
},
onAddSuccess: (objects, that) => {
this.$log.debug('Select value', that.select2.value)
that.iHasObjects = [...that.iHasObjects, ...objects]
that.$refs.select2.clearSelected()
this.$message.success(this.$t('common.updateSuccessMsg'))
this.$refs.ListTable.reloadTable()
},
performDelete: (item) => {
const itemId = item.value
const objectId = this.object.id
const relationUrl = `/api/v1/perms/k8s-app-permissions-system-users-relations/?k8sapppermission=${objectId}&systemuser=${itemId}`
return this.$axios.delete(relationUrl)
},
onDeleteSuccess: (obj, that) => {
const theRemoveIndex = that.iHasObjects.findIndex((v) => v.value === obj.value)
that.iHasObjects.splice(theRemoveIndex, 1)
while (that.select2.disabledValues.indexOf(obj.value) !== -1) {
const i = that.select2.disabledValues.indexOf(obj.value)
this.$log.debug('disabled values remove index: ', i)
that.select2.disabledValues.splice(i, 1)
}
this.$message.success(this.$t('common.deleteSuccessMsg'))
this.$refs.ListTable.reloadTable()
}
}
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,148 @@
<template>
<el-row :gutter="20">
<el-col :md="14" :sm="24">
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
</el-col>
<el-col :md="10" :sm="24">
<RelationCard type="primary" v-bind="userRelationConfig" />
<RelationCard type="info" style="margin-top: 15px" v-bind="groupRelationConfig" />
</el-col>
</el-row>
</template>
<script>
import ListTable from '@/components/ListTable'
import RelationCard from '@/components/RelationCard'
import { DeleteActionFormatter } from '@/components/ListTable/formatters/index'
export default {
name: 'KubernetesAppPermissionUser',
components: {
ListTable,
RelationCard
},
props: {
object: {
type: Object,
default: () => ({})
}
},
data() {
return {
tableConfig: {
url: `/api/v1/perms/k8s-app-permissions/${this.object.id}/users/all/`,
columns: [
'user_display', 'delete_action'
],
columnsMeta: {
user_display: {
label: this.$t('perms.User'),
align: 'center'
},
delete_action: {
prop: 'user',
label: this.$t('common.Actions'),
align: 'center',
width: 150,
objects: this.object.users,
formatter: DeleteActionFormatter,
deleteUrl: `/api/v1/perms/k8s-app-permissions-users-relations/?k8sapppermission=${this.object.id}&user=`
}
},
tableAttrs: {
border: false
}
},
headerActions: {
hasSearch: true,
hasRefresh: true,
hasLeftActions: true,
hasRightActions: true,
hasExport: false,
hasImport: false,
hasCreate: false,
hasBulkDelete: false,
hasBulkUpdate: false
},
userRelationConfig: {
icon: 'fa-user',
title: this.$t('perms.addUserToThisPermission'),
objectsAjax: {
url: '/api/v1/users/users/?fields_size=mini',
transformOption: (item) => {
return { label: item.name + '(' + item.username + ')', value: item.id }
}
},
hasObjectsId: this.object.users,
showHasObjects: false,
performAdd: (items) => {
const relationUrl = `/api/v1/perms/k8s-app-permissions-users-relations/`
const objectId = this.object.id
const data = items.map(v => {
return {
k8sapppermission: objectId,
user: v.value
}
})
return this.$axios.post(relationUrl, data)
},
onAddSuccess: (objects, that) => {
this.$log.debug('Select value', that.select2.value)
that.iHasObjects = [...that.iHasObjects, ...objects]
that.$refs.select2.clearSelected()
this.$message.success(this.$t('common.updateSuccessMsg'))
this.$refs.ListTable.reloadTable()
}
},
groupRelationConfig: {
icon: 'fa-group',
title: this.$t('perms.addUserGroupToThisPermission'),
objectsAjax: {
url: '/api/v1/users/groups/'
},
hasObjectsId: this.object.user_groups,
performAdd: (items) => {
const relationUrl = `/api/v1/perms/k8s-app-permissions-user-groups-relations/`
const objectId = this.object.id
const data = items.map(v => {
return {
k8sapppermission: objectId,
usergroup: v.value
}
})
return this.$axios.post(relationUrl, data)
},
onAddSuccess: (objects, that) => {
this.$log.debug('Select value', that.select2.value)
that.iHasObjects = [...that.iHasObjects, ...objects]
that.$refs.select2.clearSelected()
this.$message.success(this.$t('common.updateSuccessMsg'))
this.$refs.ListTable.reloadTable()
},
performDelete: (item) => {
const objectId = this.object.id
const itemId = item.value
const relationUrl = `/api/v1/perms/k8s-app-permissions-user-groups-relations/?k8sapppermission=${objectId}&usergroup=${itemId}`
return this.$axios.delete(relationUrl)
},
onDeleteSuccess: (obj, that) => {
const theRemoveIndex = that.iHasObjects.findIndex((v) => v.value === obj.value)
that.iHasObjects.splice(theRemoveIndex, 1)
while (that.select2.disabledValues.indexOf(obj.value) !== -1) {
const i = that.select2.disabledValues.indexOf(obj.value)
this.$log.debug('disabled values remove index: ', i)
that.select2.disabledValues.splice(i, 1)
}
this.$message.success(this.$t('common.deleteSuccessMsg'))
this.$refs.ListTable.reloadTable()
}
}
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,63 @@
<template>
<GenericDetailPage :object.sync="KubernetesAppPermission" :active-menu.sync="config.activeMenu" v-bind="config" v-on="$listeners" @tab-click="TabClick">
<keep-alive>
<component :is="config.activeMenu" :object="KubernetesAppPermission" />
</keep-alive>
</GenericDetailPage>
</template>
<script>
import { GenericDetailPage, TabPage } from '@/layout/components'
import KubernetesAppPermissionDetail from './KubernetesAppPermissionDetail'
import KubernetesAppPermissionUser from './KubernetesAppPermissionUser'
import KubernetesAppPermissionKubernetesApp from './KubernetesAppPermissionKubernetesApp'
export default {
components: {
KubernetesAppPermissionKubernetesApp,
KubernetesAppPermissionDetail,
KubernetesAppPermissionUser,
GenericDetailPage,
TabPage
},
data() {
return {
KubernetesAppPermission: {},
config: {
activeMenu: 'KubernetesAppPermissionDetail',
submenu: [
{
title: this.$t('common.BasicInfo'),
name: 'KubernetesAppPermissionDetail'
},
{
title: this.$t('perms.usersAndUserGroups'),
name: 'KubernetesAppPermissionUser'
},
{
title: this.$t('perms.KubernetesApp'),
name: 'KubernetesAppPermissionKubernetesApp'
}
],
actions: {
detailApiUrl: `/api/v1/perms/k8s-app-permissions/${this.$route.params.id}/`,
deleteApiUrl: `/api/v1/perms/k8s-app-permissions/${this.$route.params.id}/`
}
}
}
},
methods: {
TabClick(tab) {
if (tab.name !== 'KubernetesAppPermissionDetail') {
this.$set(this.config, 'hasRightSide', false)
} else {
this.$set(this.config, 'hasRightSide', true)
}
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,79 @@
<template>
<GenericListPage :table-config="tableConfig" :header-actions="headerActions" />
</template>
<script>
import { GenericListPage } from '@/layout/components'
import { DetailFormatter } from '@/components/ListTable/formatters'
export default {
components: {
GenericListPage
},
data() {
return {
tableConfig: {
url: '/api/v1/perms/k8s-app-permissions/',
columns: ['name', 'users_amount', 'user_groups_amount', 'k8s_apps_amount', 'system_users_amount', 'is_valid', 'actions'],
columnsMeta: {
name: {
formatterArgs: {
routeQuery: {
activeTab: 'KubernetesAppPermissionDetail'
}
},
showOverflowTooltip: true
},
users_amount: {
label: this.$t('perms.User'),
width: '110px',
formatter: DetailFormatter,
formatterArgs: {
routeQuery: {
activeTab: 'KubernetesAppPermissionUser'
}
}
},
user_groups_amount: {
label: this.$t('perms.UserGroups'),
width: '110px',
formatter: DetailFormatter,
formatterArgs: {
routeQuery: {
activeTab: 'KubernetesAppPermissionUser'
}
}
},
k8s_apps_amount: {
label: this.$t('perms.KubernetesApp'),
width: '140px',
formatter: DetailFormatter,
formatterArgs: {
routeQuery: {
activeTab: 'KubernetesAppPermissionKubernetesApp'
}
}
},
system_users_amount: {
label: this.$t('perms.SystemUser'),
width: '110px',
formatter: DetailFormatter,
formatterArgs: {
routeQuery: {
activeTab: 'KubernetesAppPermissionKubernetesApp'
}
}
}
}
},
headerActions: {
hasBulkDelete: false
}
}
}
}
</script>
<style>
</style>

View File

@ -30,6 +30,7 @@ export default {
},
users_amount: {
label: this.$t('users.Users'),
width: '110px',
formatter: DetailFormatter,
formatterArgs: {
routeQuery: {
@ -39,6 +40,7 @@ export default {
},
user_groups_amount: {
label: this.$t('users.UserGroups'),
width: '110px',
formatter: DetailFormatter,
formatterArgs: {
routeQuery: {
@ -48,6 +50,7 @@ export default {
},
remote_apps_amount: {
label: this.$t('assets.RemoteApps'),
width: '110px',
formatter: DetailFormatter,
formatterArgs: {
routeQuery: {
@ -57,6 +60,7 @@ export default {
},
system_users_amount: {
label: this.$t('assets.SystemUsers'),
width: '110px',
formatter: DetailFormatter,
formatterArgs: {
routeQuery: {
@ -67,7 +71,6 @@ export default {
}
},
headerActions: {
hasRightActions: false,
hasBulkDelete: false
}
}

View File

@ -36,7 +36,7 @@ export default {
},
risk_level: {
label: this.$t('sessions.riskLevel'),
width: '120px',
width: '105px',
formatter: (row, col, cellValue) => {
const display = row.risk_level_display
if (cellValue === 0) {

View File

@ -51,19 +51,22 @@ export default {
label: this.$t('sessions.hosts'),
rules: [
{ required: true, message: this.$t('common.fieldRequiredError') }
]
],
helpText: this.$t('sessions.helpText.esUrl')
},
index: {
label: this.$t('sessions.index'),
rules: [
{ required: true, message: this.$t('common.fieldRequiredError') }
]
],
helpText: this.$t('sessions.helpText.esIndex')
},
doc_type: {
label: this.$t('sessions.docType'),
rules: [
{ required: true, message: this.$t('common.fieldRequiredError') }
]
],
helpText: this.$t('sessions.helpText.esDocType')
}
},
fieldsMap: {

View File

@ -39,6 +39,7 @@ export default {
},
timestamp: {
label: this.$t('sessions.date'),
width: '160px',
sortable: 'custom',
formatter: function(row) {
return toSafeLocalDateStr(row.timestamp * 1000)

View File

@ -13,6 +13,7 @@
import DetailCard from '@/components/DetailCard/index'
import QuickActions from '@/components/QuickActions'
import { terminateSession } from '@/api/sessions'
import { toSafeLocalDateStr } from '@/utils/common'
export default {
name: 'SessionDetailCard',
components: {
@ -59,11 +60,11 @@ export default {
},
{
key: this.$t('sessions.dateStart'),
value: this.sessionData.date_start
value: toSafeLocalDateStr(this.sessionData.date_start)
},
{
key: this.$t('sessions.dateEnd'),
value: this.sessionData.date_end
value: toSafeLocalDateStr(this.sessionData.date_end)
}
]
},

View File

@ -36,7 +36,7 @@ export default {
index: {
label: this.$t('sessions.id'),
align: 'center',
width: '60px',
width: '40px',
formatter: function(row, column, cellValue, index) {
const label = index + 1
const route = { to: { name: 'SessionDetail', params: { id: row.id }}}
@ -51,18 +51,19 @@ export default {
},
command_amount: {
label: this.$t('sessions.command'),
width: '100px'
width: '90px'
},
system_user: {
showOverflowTooltip: true
showOverflowTooltip: true,
width: '100px'
},
login_from: {
label: this.$t('sessions.loginFrom'),
width: '120px',
width: '115px',
showOverflowTooltip: true
},
remote_addr: {
width: '120px'
width: '140px'
},
protocol: {
label: this.$t('sessions.protocol'),

View File

@ -1,17 +1,94 @@
<template>
<GenericListPage :table-config="tableConfig" :header-actions="headerActions" />
<div>
<GenericListPage :table-config="tableConfig" :header-actions="headerActions" />
<Dialog
:visible.sync="dialogSettings.visible"
:destroy-on-close="true"
:show-cancel="false"
:title="$t('sessions.terminalUpdateStorage')"
:show-confirm="false"
>
<GenericCreateUpdateForm v-bind="dialogSettings.iFormSetting" />
</Dialog>
</div>
</template>
<script>
import { GenericListPage } from '@/layout/components'
import { GenericListPage, GenericCreateUpdateForm } from '@/layout/components'
import Dialog from '@/components/Dialog'
import Select2 from '@/components/Select2'
import { BooleanFormatter } from '@/components/ListTable/formatters'
export default {
components: {
GenericListPage
GenericListPage,
Dialog,
GenericCreateUpdateForm
},
data() {
return {
dialogSettings: {
selectedRows: [],
visible: false,
iFormSetting: {
url: '/api/v1/terminal/terminals/',
fields: [
['', ['command_storage', 'replay_storage']]
],
fieldsMeta: {
command_storage: {
component: Select2,
rules: [{ required: true }],
el: {
ajax: {
url: `/api/v1/terminal/command-storages/`
},
multiple: false
}
},
replay_storage: {
component: Select2,
rules: [{ required: true }],
el: {
ajax: {
url: `/api/v1/terminal/replay-storages/`
},
multiple: false
}
}
},
getMethod: () => 'post',
cleanFormValue: function(value) {
const formValue = []
let object = {}
for (const row of this.dialogSettings.selectedRows) {
object = Object.assign({}, value, { id: row.id })
formValue.push(object)
}
return formValue
}.bind(this),
onSubmit: function(validValues) {
const url = '/api/v1/terminal/terminals/'
const msg = this.$t('common.updateSuccessMsg')
this.$axios.patch(url, validValues).then((res) => {
this.$message.success(msg)
this.dialogSettings.visible = false
}).catch(error => {
this.$emit('submitError', error)
const response = error.response
const data = response.data
if (response.status === 400) {
for (const key of Object.keys(data)) {
let value = data[key]
if (value instanceof Array) {
value = value.join(';')
}
this.$refs.form.setFieldError(key, value)
}
}
})
}.bind(this)
}
},
tableConfig: {
url: '/api/v1/terminal/terminals/',
columns: ['name', 'remote_addr', 'session_online', 'is_active', 'is_alive', 'actions'],
@ -32,7 +109,8 @@ export default {
label: this.$t('sessions.alive')
},
session_online: {
label: this.$t('sessions.session')
label: this.$t('sessions.session'),
width: '80px'
}
}
},
@ -52,6 +130,17 @@ export default {
can: true,
callback: this.handleStorageConfiguration
}
],
extraMoreActions: [
{
name: 'updateSelected',
title: this.$t('common.updateSelected'),
can: ({ selectedRows }) => selectedRows.length > 0,
callback: function({ selectedRows, reloadTable }) {
this.dialogSettings.selectedRows = selectedRows
this.dialogSettings.visible = true
}.bind(this)
}
]
}
}
@ -65,5 +154,4 @@ export default {
</script>
<style>
</style>

View File

@ -1,5 +1,5 @@
<template>
<TicketListTable :url="url" />
<TicketListTable :url="url" :has-more-actions="true" />
</template>
<script>

View File

@ -0,0 +1,47 @@
<template>
<el-select v-model="value" v-bind="$attrs" class="select2" v-on="$listeners">
<el-option-group
v-for="group in options"
:key="group.org_name"
:label="group.org_name"
>
<el-option
v-for="item in group.org_admins"
:key="item.id"
:label="item.name + '(' + item.username + ')'"
:value="item.id"
/>
</el-option-group>
</el-select>
</template>
<script>
export default {
name: 'GroupSelectFormatter',
props: {
url: {
type: String,
default: ''
}
},
data() {
return {
value: '',
options: []
}
},
created() {
this.$axios.get(this.url).then(
res => {
this.options = res
}
)
}
}
</script>
<style scoped>
.select2 {
width: 100%;
}
</style>

View File

@ -0,0 +1,188 @@
<template>
<GenericTicketDetail
:object="object"
:detail-card-items="detailCardItems"
:special-card-items="specialCardItems"
:approve="handleApprove"
>
<IBox v-if="hasActionPerm&&object.status !== 'closed'" class="box">
<div slot="header" class="clearfix ibox-title">
<i class="fa fa-edit" /> {{ $t('common.Actions') }}
</div>
<template>
<el-form ref="requestForm" :model="requestForm" label-width="140px" label-position="left" class="assets">
<el-form-item :label="$t('tickets.Asset')" required>
<Select2 v-model="requestForm.asset" v-bind="asset_select2" style="width: 30% !important" />
</el-form-item>
<el-form-item :label="$t('tickets.SystemUser')" required>
<Select2 v-model="requestForm.systemuser" v-bind="systemuser_select2" style="width: 30% !important" />
</el-form-item>
<el-form-item :label="$t('assets.Action')" required>
<AssetPermissionFormActionField v-model="requestForm.actions" style="width: 30% !important" />
</el-form-item>
</el-form>
</template>
</IBox>
</GenericTicketDetail>
</template>
<script>
import { formatTime, getDateTimeStamp } from '@/utils/index'
import { toSafeLocalDateStr } from '@/utils/common'
import { STATUS_MAP } from '../../const'
import Select2 from '@/components/Select2'
import IBox from '@/components/IBox'
import AssetPermissionFormActionField from '@/views/perms/AssetPermission/components/AssetPermissionFormActionField'
import GenericTicketDetail from '@/views/tickets/components/GenericTicketDetail'
export default {
name: '',
components: { GenericTicketDetail, IBox, Select2, AssetPermissionFormActionField },
props: {
object: {
type: Object,
default: () => ({})
}
},
data() {
return {
statusMap: this.object.status === 'open' ? STATUS_MAP[this.object.status] : STATUS_MAP[this.object.action],
requestForm: {
asset: this.object.confirmed_assets,
systemuser: '',
actions: this.object.actions
},
comments: '',
assets: [],
asset_select2: {
multiple: true,
value: this.object.confirmed_assets,
ajax: {
url: this.object.assets_waitlist_url,
transformOption: (item) => {
return { label: item.hostname, value: item.id }
}
}
},
systemuser_select2: {
multiple: false,
ajax: {
url: this.object.system_user_waitlist_url,
transformOption: (item) => {
return { label: item.name + '(' + item.username + ')', value: item.id }
}
}
}
}
},
computed: {
detailCardItems() {
return [
{
key: this.$t('tickets.status'),
value: this.object.status,
formatter: (item, val) => {
return <el-tag type={this.statusMap.type} size='mini'> { this.statusMap.title }</el-tag>
}
},
{
key: this.$t('tickets.type'),
value: this.object.type_display
},
{
key: this.$t('tickets.user'),
value: this.object.user_display
},
{
key: this.$t('tickets.Assignees'),
value: this.object.assignees_display
},
{
key: this.$t('tickets.Assignee'),
value: this.object.assignee_display
},
{
key: this.$t('common.dateCreated'),
value: toSafeLocalDateStr(this.object.date_created)
}
]
},
specialCardItems() {
return [
// {
// key: this.$t('tickets.Assignee'),
// value: this.object.assignee_display
// },
{
key: this.$t('tickets.IP'),
value: this.object.ips
},
{
key: this.$t('tickets.Hostname'),
value: this.object.hostname
},
{
key: this.$t('common.dateStart'),
value: toSafeLocalDateStr(this.object.date_start)
},
{
key: this.$t('common.dateExpired'),
value: toSafeLocalDateStr(this.object.date_expired)
}
]
},
hasActionPerm() {
return this.object.assignees.indexOf(this.$store.state.users.profile.id) !== -1
}
},
methods: {
formatTime(dateStr) {
return formatTime(getDateTimeStamp(dateStr))
},
toSafeLocalDateStr(dataStr) {
return toSafeLocalDateStr(dataStr)
},
reloadPage() {
window.location.reload()
},
handleApprove() {
if (this.requestForm.asset.length === 0 || this.requestForm.systemuser === '') {
return this.$message.error(this.$t('common.NeedAssetsAndSystemUserErrMsg'))
} else {
this.$axios.patch(`/api/v1/tickets/tickets/request-asset-perm/${this.object.id}/`, {
confirmed_system_user: this.requestForm.systemuser,
confirmed_assets: this.requestForm.asset,
actions: this.requestForm.actions
}).then(res => {
this.$axios.post(`/api/v1/tickets/tickets/request-asset-perm/${this.object.id}/approve/`).then(
() => {
this.$message.success(this.$t('common.updateSuccessMsg'))
this.reloadPage()
}
)
}).catch(
() => this.$message.success(this.$t('common.updateErrorMsg'))
)
}
}
}
}
</script>
<style scoped>
.assets{
margin-top: 14px;
}
.feed-activity-list .feed-element {
border-bottom: 1px solid #e7eaec;
}
.feed-element > .pull-left {
margin-right: 10px;
}
.feed-element .header-avatar {
width: 38px;
height: 38px;
}
.box {
margin-bottom: 15px;
}
</style>

View File

@ -0,0 +1,46 @@
<template>
<GenericDetailPage :object.sync="ticket" :active-menu.sync="config.activeMenu" v-bind="config" v-on="$listeners">
<component :is="config.activeMenu" :object="ticket" />
</GenericDetailPage>
</template>
<script>
import { GenericDetailPage, TabPage } from '@/layout/components'
import TicketDetail from './TicketDetail'
export default {
components: {
GenericDetailPage,
TicketDetail,
TabPage
},
data() {
return {
ticket: { title: '', user_display: '', type_display: '', status: '', assignees_display: '', date_created: '' },
config: {
activeMenu: 'TicketDetail',
submenu: [
{
title: this.$t('route.TicketDetail'),
name: 'TicketDetail'
}
],
getObjectName: this.getObjectName,
hasRightSide: false
}
}
},
mounted() {
},
methods: {
getObjectName() {
return this.ticket.title
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,90 @@
<template>
<GenericCreateUpdatePage v-bind="$data" :perform-submit="performSubmit" :create-success-next-route="createSuccessNextRoute" />
</template>
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import Select2 from '@/components/Select2'
import { getDaysFuture } from '@/utils/common'
import AssetPermissionFormActionField from '@/views/perms/AssetPermission/components/AssetPermissionFormActionField'
export default {
components: {
GenericCreateUpdatePage
},
data() {
const now = new Date()
const date_expired = getDaysFuture(7, now).toISOString()
const date_start = now.toISOString()
return {
initial: {
ips_or_not: true,
date_expired: date_expired,
date_start: date_start,
org_id: 'DEFAULT',
actions: ['all', 'connect', 'updownload', 'upload_file', 'download_file']
},
fields: [
[this.$t('common.Basic'), ['title', 'org_id', 'assignees']],
[this.$t('tickets.RequestPerm'), ['ips', 'hostname', 'actions', 'date_start', 'date_expired']]
],
fieldsMeta: {
ips: {
helpText: '请输入逗号分割的IP地址组'
},
hostname: {
helpText: '支持模糊匹配'
},
actions: {
label: this.$t('perms.Actions'),
component: AssetPermissionFormActionField,
helpText: this.$t('common.actionsTips')
},
org_id: {
component: Select2,
el: {
multiple: false,
options: this.$store.state.users.profile.user_all_orgs
},
on: {
changeOptions: ([event], updateForm) => {
console.log(event[0].value) // output: input value
this.fieldsMeta.assignees.el.ajax.url = `/api/v1/tickets/tickets/request-asset-perm/assignees/?org_id=${event[0].value}`
}
}
},
assignees: {
el: {
multiple: true,
value: [],
ajax: {
url: '/api/v1/tickets/tickets/request-asset-perm/assignees/',
transformOption: (item) => {
return { label: item.name + '(' + item.username + ')', value: item.id }
}
}
}
}
},
url: '/api/v1/tickets/tickets/request-asset-perm/',
createSuccessNextRoute: {
name: 'TicketList'
}
}
},
methods: {
performSubmit(validValues) {
const ips = validValues.ips
if (ips) {
validValues.ips = ips.split(',')
}
return this.$axios['post'](this.url, validValues)
}
}
}
</script>
<style lang="less" scoped>
</style>

View File

@ -1,64 +1,16 @@
<template>
<el-row>
<DetailCard :title="cardTitle" :items="detailCardItems">
<div class="feed-activity-list">
<div class="feed-element">
<a href="#" class="pull-left">
<el-avatar :src="imageUrl" class="header-avatar" />
</a>
<div class="media-body ">
<strong>{{ object.user_display }}</strong> <small class="text-muted"> {{ formatTime(object.date_created) }}</small>
<br>
<small class="text-muted">{{ toSafeLocalDateStr(object.date_created) }} </small>
<div style="padding-top: 10px">
<span v-html="object.body" />
</div>
</div>
</div>
<template v-if="comments">
<div v-for="item in comments" :key="item.user_display + item.body">
<div class="feed-element">
<a href="#" class="pull-left">
<el-avatar :src="imageUrl" class="header-avatar" />
</a>
<div class="media-body ">
<strong>{{ item.user_display }}</strong> <small class="text-muted">{{ formatTime(item.date_created) }}</small>
<br>
<small class="text-muted">{{ toSafeLocalDateStr(item.date_created) }}</small>
<div style="padding-top: 10px">
{{ item.body }}
</div>
</div>
</div>
</div>
</template>
<el-form ref="comments" :model="form" label-width="45px" style="padding-top: 20px">
<el-form-item :label="$t('tickets.reply')">
<el-input v-model="form.comments" :autosize="{ minRows: 4 }" type="textarea" />
</el-form-item>
<el-form-item style="float: right">
<template v-if="hasActionPerm">
<el-button :disabled="object.status === 'closed'" type="primary" size="small" @click="handleApprove"><i class="fa fa-check" />{{ $t('tickets.Accept') }}</el-button>
<el-button :disabled="object.status === 'closed'" type="warning" size="small" @click="handleReject"><i class="fa fa-ban" />{{ $t('tickets.Reject') }}</el-button>
</template>
<el-button :disabled="object.status === 'closed'" type="danger" size="small" @click="handleClosed"><i class="fa fa-times" />{{ $t('tickets.Close') }}</el-button>
<el-button :disabled="object.status === 'closed'" type="info" size="small" @click="handleComment"><i class="fa fa-pencil" />{{ $t('tickets.Comment') }}</el-button>
</el-form-item>
</el-form>
</div>
</DetailCard>
</el-row>
<GenericTicketDetail :object="object" :detail-card-items="detailCardItems" />
</template>
<script>
import DetailCard from '@/components/DetailCard'
import { STATUS_MAP } from '../const'
import { formatTime, getDateTimeStamp } from '@/utils/index'
import { toSafeLocalDateStr } from '@/utils/common'
import GenericTicketDetail from '@/views/tickets/components/GenericTicketDetail'
export default {
name: 'TicketDetail',
components: {
DetailCard
GenericTicketDetail
},
props: {
object: {
@ -68,6 +20,7 @@ export default {
},
data() {
return {
statusMap: this.object.status === 'open' ? STATUS_MAP[this.object.status] : STATUS_MAP[this.object.action],
imageUrl: require('@/assets/img/admin.png'),
form: {
comments: ''
@ -76,14 +29,10 @@ export default {
}
},
computed: {
cardTitle() {
return this.object.title
},
detailCardItems() {
const vm = this
return [
{
key: this.$t('tickets.user'),
key: this.$t('tickets.Applicant'),
value: this.object.user_display
},
{
@ -93,13 +42,8 @@ export default {
{
key: this.$t('tickets.status'),
value: this.object.status,
callback: function(row, data) {
const open = vm.$t('common.Open')
const close = vm.$t('common.Close')
if (data === 'open') {
return <el-button type='primary' size='mini'>{open}</el-button>
}
return <el-button type='danger' size='mini'>{close}</el-button>
formatter: (item, val) => {
return <el-tag type={this.statusMap.type} size='mini'> { this.statusMap.title }</el-tag>
}
},
{
@ -115,101 +59,18 @@ export default {
value: toSafeLocalDateStr(this.object.date_created)
}
]
},
hasActionPerm() {
return this.object.assignees.indexOf(this.$store.state.users.profile.id) !== -1
}
},
mounted() {
const url = `/api/v1/tickets/tickets/${this.object.id}/comments/`
this.$axios.get(url).then(res => {
this.comments = res
}).catch(err => {
this.$message.error(err)
})
},
methods: {
formatTime(dateStr) {
return formatTime(getDateTimeStamp(dateStr))
},
toSafeLocalDateStr(dataStr) {
return toSafeLocalDateStr(dataStr)
},
reloadPage() {
window.location.reload()
},
createComment(successCallback) {
const commentText = this.form.comments
const ticketId = this.object.id
const commentUrl = `/api/v1/tickets/tickets/${ticketId}/comments/`
if (!commentText) { return }
const body = {
body: commentText,
ticket: ticketId
}
this.$axios.post(commentUrl, body).then(res => {
if (successCallback) {
successCallback()
} else {
this.reloadPage()
}
})
},
handleApprove() {
this.createComment(function() {})
const url = `/api/v1/tickets/tickets/${this.object.id}/`
const data = { action: 'approve' }
this.$axios.patch(url, data).then(res => this.reloadPage()).catch(err => this.$message.error(err))
},
handleReject() {
this.createComment(function() {})
const url = `/api/v1/tickets/tickets/${this.object.id}/`
const data = { action: 'reject' }
this.$axios.patch(url, data).then(res => this.reloadPage()).catch(err => this.$message.error(err))
},
handleClosed() {
const url = `/api/v1/tickets/tickets/${this.object.id}/`
const data = { status: 'closed' }
this.$axios.patch(url, data).then(res => this.reloadPage()).catch(err => this.$message.error(err))
},
handleComment() {
this.createComment()
}
}
}
</script>
<style scoped>
.feed-activity-list {
padding-top: 20px;
line-height: 1.5;
}
.feed-activity-list .feed-element {
border-bottom: 1px solid #e7eaec;
}
.feed-element:first-child {
margin-top: 0;
}
.feed-element {
padding-top: 15px;
padding-bottom: 15px;
}
.feed-element,
.media-body {
overflow: hidden;
}
.feed-element > .pull-left {
margin-right: 10px;
}
.feed-element .header-avatar {
width: 38px;
height: 38px;
}
.text-muted {
color: #888888;
}
.el-button--mini {
padding: 4px 6px;
}
</style>

View File

@ -32,7 +32,7 @@ export default {
},
methods: {
getObjectName() {
return this.ticket.user_display
return this.ticket.title
}
}
}

View File

@ -5,7 +5,6 @@
import ListTable from '@/components/ListTable'
import { DetailFormatter } from '@/components/ListTable/formatters'
import { toSafeLocalDateStr } from '@/utils/common'
export default {
name: 'TicketListTable',
components: {
@ -15,6 +14,10 @@ export default {
url: {
type: String,
default: '/api/v1/tickets/tickets/'
},
hasMoreActions: {
type: Boolean,
default: false
}
},
data() {
@ -28,7 +31,13 @@ export default {
formatter: DetailFormatter,
sortable: 'custom',
formatterArgs: {
route: 'TicketDetail'
getRoute: function({ row }) {
if (row.type === 'request_asset') {
return 'AssetsTicketDetail'
} else {
return 'TicketDetail'
}
}
}
},
{
@ -39,32 +48,45 @@ export default {
{
prop: 'type_display',
label: this.$t('tickets.type'),
sortable: 'custom'
width: '110px'
},
{
prop: 'status',
label: this.$t('tickets.status'),
align: 'center',
width: '100px',
width: '90px',
sortable: 'custom',
formatter: row => {
if (row.status === 'open') {
return <i class='fa fa-check-circle-o text-primary'/>
return <el-tag type='success' size='mini'style='align-items:center; display: flex; justify-content:center;'> { this.$t('tickets.Pending') }</el-tag>
}
if (row.status === 'closed') {
return <el-tag type='info' size='mini' style='align-items:center; display: flex; justify-content:center;'> { this.$t('tickets.Closed') }</el-tag>
}
switch (row.action) {
case 'approve':
return <el-tag type='primary' size='mini' style='align-items:center; display: flex; justify-content:center;'> { this.$t('tickets.Approved') }</el-tag>
case 'reject':
return <el-tag type='danger' size='mini' style='align-items:center; display: flex; justify-content:center;'> { this.$t('tickets.Rejected') }</el-tag>
case '':
return <el-tag type='info' size='mini' style='align-items:center; display: flex; justify-content:center;'> { this.$t('tickets.Closed') }</el-tag>
}
return <i class='fa fa-times-circle-o text-danger'/>
}
},
{
prop: 'date_created',
label: this.$t('tickets.date'),
sortable: 'custom',
formatter: (row) => toSafeLocalDateStr(row.date_created)
formatter: (row) => toSafeLocalDateStr(row.date_created),
width: '160px'
}
]
},
ticketActions: {
hasLeftActions: false,
hasLeftActions: this.hasMoreActions,
hasRightActions: false,
hasCreate: false,
hasBulkDelete: false,
searchConfig: {
default: {
status: {
@ -74,9 +96,28 @@ export default {
valueLabel: this.$t('tickets.Open')
}
}
}
},
moreActionsTitle: this.$t('common.RequestTickets'),
moreActionsType: 'primary',
extraMoreActions: this.genExtraMoreActions()
}
}
},
methods: {
genExtraMoreActions() {
return [
{
name: '',
title: this.$t('tickets.RequestAssetPerm'),
type: 'primary',
can: true,
callback: this.onCallback
}
]
},
onCallback() {
this.$router.push({ name: 'RequestAssetPermTicketCreateUpdate' })
}
}
}
</script>

View File

@ -0,0 +1,198 @@
<template>
<IBox class="box">
<div slot="header" class="clearfix ibox-title">
<i class="fa fa-comments" /> {{ $t('common.Message') }}
</div>
<template v-if="comments">
<div v-for="item in comments" :key="item.user_display + item.body" class="feed-activity-list">
<div class="feed-element">
<a href="#" class="pull-left">
<el-avatar :src="imageUrl" class="header-avatar" />
</a>
<div class="media-body ">
<strong>{{ item.user_display }}</strong> <small class="text-muted">{{ formatTime(item.date_created) }}</small>
<br>
<small class="text-muted">{{ toSafeLocalDateStr(item.date_created) }}</small>
<div style="padding-top: 10px">
{{ item.body }}
</div>
</div>
</div>
</div>
</template>
<slot />
<el-form ref="comments" :model="form" label-width="45px" style="padding-top: 20px">
<el-form-item :label="$t('tickets.reply')">
<el-input v-model="form.comments" :autosize="{ minRows: 4 }" type="textarea" />
</el-form-item>
<el-form-item style="float: right">
<template v-if="hasActionPerm">
<el-button
:disabled="object.status === 'closed'"
type="primary"
size="small"
@click="handleApprove"
>
<i class="fa fa-check" />{{ $t('tickets.Accept') }}
</el-button>
<el-button
:disabled="object.status === 'closed'"
type="warning"
size="small"
@click="handleReject"
>
<i class="fa fa-ban" />{{ $t('tickets.Reject') }}
</el-button>
</template>
<el-button
:disabled="object.status === 'closed'"
type="danger"
size="small"
@click="handleClose"
>
<i class="fa fa-times" />{{ $t('tickets.Close') }}
</el-button>
<el-button
:disabled="object.status === 'closed'"
type="info"
size="small"
@click="handleComment"
>
<i class="fa fa-pencil" />{{ $t('tickets.reply') }}
</el-button>
</el-form-item>
</el-form>
</IBox>
</template>
<script>
import IBox from '@/components/IBox'
import { formatTime, getDateTimeStamp } from '@/utils'
import { toSafeLocalDateStr } from '@/utils/common'
export default {
name: 'Comments',
components: { IBox },
props: {
object: {
type: Object,
default: () => ({})
},
approve: {
type: Function,
default: null
}
},
data() {
return {
comments: '',
imageUrl: require('@/assets/img/admin.png'),
form: {
comments: ''
}
}
},
computed: {
hasActionPerm() {
return this.object.assignees.indexOf(this.$store.state.users.profile.id) !== -1
}
},
mounted() {
const url = `/api/v1/tickets/tickets/${this.object.id}/comments/`
this.$axios.get(url).then(res => {
this.comments = res
console.log(this.comments)
}).catch(err => {
this.$message.error(err)
})
},
methods: {
formatTime(dateStr) {
return formatTime(getDateTimeStamp(dateStr))
},
toSafeLocalDateStr(dataStr) {
return toSafeLocalDateStr(dataStr)
},
defaultApprove() {
this.createComment(function() {
})
const url = `/api/v1/tickets/tickets/${this.object.id}/`
const data = { action: 'approve' }
this.$axios.patch(url, data).then(res => this.reloadPage()).catch(err => this.$message.error(err))
},
createComment(successCallback) {
const commentText = this.form.comments
const ticketId = this.object.id
const commentUrl = `/api/v1/tickets/tickets/${ticketId}/comments/`
if (!commentText) { return }
const body = {
body: commentText,
ticket: ticketId
}
this.$axios.post(commentUrl, body).then(res => {
if (successCallback) {
successCallback()
} else {
this.reloadPage()
}
})
},
handleApprove() {
const handler = this.approve || this.defaultApprove
handler()
},
handleReject() {
this.createComment(function() {})
const url = `/api/v1/tickets/tickets/${this.object.id}/`
const data = { action: 'reject' }
this.$axios.patch(url, data).then(res => this.reloadPage()).catch(err => this.$message.error(err))
},
handleClose() {
const url = `/api/v1/tickets/tickets/${this.object.id}/`
const data = { status: 'closed' }
this.$axios.patch(url, data).then(res => this.reloadPage()).catch(err => this.$message.error(err))
},
handleComment() {
this.createComment()
},
reloadPage() {
window.location.reload()
}
}
}
</script>
<style lang='less' scoped>
.box {
margin-bottom: 15px;
}
.feed-activity-list {
//padding-top: 20px;
line-height: 1.5;
}
.feed-activity-list .feed-element {
border-bottom: 1px solid #e7eaec;
}
.feed-element:first-child {
margin-top: 0;
}
.feed-element {
padding-top: 15px;
padding-bottom: 15px;
}
.feed-element,
.media-body {
overflow: hidden;
}
.feed-element > .pull-left {
margin-right: 10px;
}
.feed-element .header-avatar {
width: 38px;
height: 38px;
}
.text-muted {
color: #888888;
}
</style>

View File

@ -0,0 +1,73 @@
<template>
<IBox class="box">
<div slot="header" class="clearfix ibox-title">
<i class="fa fa-info-circle" /> {{ $t('common.BasicInfo') }}
</div>
<div class="content">
<el-row :gutter="10">
<el-col v-for="item in detailCardItems" :key="'card-' + item.key" :span="12">
<el-row class="item">
<el-col :span="6">
<div :style="{ 'text-align': 'align' }" class="item-label">
<label>{{ item.key }}: </label>
</div>
</el-col>
<el-col :span="18">
<div class="item-text">
<ItemValue :value="item.value" v-bind="item" />
</div>
</el-col>
</el-row>
</el-col>
</el-row>
<el-divider v-if="specialCardItems.length>0" />
<el-row :gutter="10">
<el-col v-for="item in specialCardItems" :key="'card-' + item.key" :span="12">
<el-row class="item">
<el-col :span="6">
<div :style="{ 'text-align': 'align' }" class="item-label">
<label>{{ item.key }}: </label>
</div>
</el-col>
<el-col :span="18">
<div class="item-text">
<ItemValue :value="item.value" v-bind="item" />
</div>
</el-col>
</el-row>
</el-col>
</el-row>
</div>
</IBox>
</template>
<script>
import ItemValue from './ItemValue'
import IBox from '@/components/IBox'
export default {
name: 'Details',
components: { ItemValue, IBox },
props: {
specialCardItems: {
type: Array,
default: () => ([])
},
detailCardItems: {
type: Array,
default: () => ([])
}
},
data() {
return {}
}
}
</script>
<style lang='less' scoped>
.box {
margin-bottom: 15px;
}
.content {
font-size: 13px;
line-height: 2.5;
}
</style>

View File

@ -0,0 +1,43 @@
<template>
<el-row>
<el-col :span="17">
<Details :detail-card-items="detailCardItems" :special-card-items="specialCardItems" />
<slot id="MoreDetails" />
<Comments :object="object" v-bind="$attrs" />
</el-col>
<el-col :span="6" :offset="1">
<Steps :object="object" />
</el-col>
</el-row>
</template>
<script>
import Details from './Details'
import Comments from './Comments'
import Steps from './Steps'
export default {
name: 'GenericTicketDetail',
components: { Steps, Comments, Details },
props: {
object: {
type: Object,
default: () => ({})
},
specialCardItems: {
type: Array,
default: () => ([])
},
detailCardItems: {
type: Array,
default: () => ([])
}
},
data() {
return {}
}
}
</script>
<style lang='less' scoped>
</style>

View File

@ -0,0 +1,29 @@
<script type="text/jsx">
export default {
name: 'ItemValue',
props: {
value: {
type: [String, Number, Function, Array, Object],
default: ''
},
item: {
type: Object,
default: () => ({})
},
formatter: {
type: Function,
default: null
}
},
render(h) {
if (typeof this.formatter === 'function') {
return this.formatter(this.item, this.value)
}
return <span>{this.value}</span>
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,70 @@
<template>
<IBox>
<div style="height: 350px;">
<el-steps direction="vertical" :active="ticketSteps">
<el-step
:title="`${this.$t('tickets.OpenTicket')}${object.type_display}`"
:description="`${this.$t('tickets.Applicant')}${object.user_display}`"
>
<div slot="description">
<div>{{ `${this.$t('tickets.Applicant')}${object.user_display}` }}</div>
<div>{{ `${this.$t('common.dateCreated')}: ${toSafeLocalDateStr(this.object.date_created)}` }}</div>
</div>
</el-step>
<el-step
:title="`${this.$t('tickets.HandleTicket')}`"
:description="`${this.$t('tickets.Assignees')}${object.assignees_display}`"
/>
<el-step
:title="`${this.$t('tickets.FinishedTicket')}`"
:description="ticketSteps===STATUS.close ? `${this.$t('tickets.Assignee')}${object.assignee_display}`:'' "
>
<div v-if="ticketSteps===STATUS.close" slot="description">
<div>{{ `${this.$t('tickets.Assignee')}${object.assignee_display}` }}</div>
<div>{{ `${this.$t('common.dateFinished')}: ${toSafeLocalDateStr(this.object.date_updated)}` }}</div>
</div>
</el-step>
</el-steps>
</div>
</IBox>
</template>
<script>
import { formatTime, getDateTimeStamp } from '@/utils/index'
import { toSafeLocalDateStr } from '@/utils/common'
import IBox from '@/components/IBox'
export default {
name: 'Steps',
components: { IBox },
props: {
object: {
type: Object,
default: () => ({})
}
},
data() {
return {
STATUS: { open: 2, close: 3 }
}
},
computed: {
ticketSteps() {
return this.object.status === 'open' ? this.STATUS.open : this.STATUS.close
}
},
methods: {
formatTime(dateStr) {
return formatTime(getDateTimeStamp(dateStr))
},
toSafeLocalDateStr(dataStr) {
return toSafeLocalDateStr(dataStr)
}
}
}
</script>
<style lang='less' scoped>
.box {
margin-bottom: 15px;
}
</style>

View File

@ -0,0 +1,21 @@
import i18n from '@/i18n/i18n'
export const OPEN = 'open'
export const APPROVE = 'approve'
export const REJECT = 'reject'
export const OTHER = ''
export const STATUS_MAP = {
[OPEN]: {
type: 'success', title: i18n.t('tickets.Pending')
},
[APPROVE]: {
type: 'primary', title: i18n.t('tickets.Approved')
},
[REJECT]: {
type: 'danger', title: i18n.t('tickets.Rejected')
},
[OTHER]: {
type: 'info', title: i18n.t('tickets.Closed')
}
}

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