This commit is contained in:
OrangeM21
2020-04-10 14:54:10 +08:00
30 changed files with 722 additions and 232 deletions

View File

@@ -7,3 +7,17 @@ export function terminateSession(data) {
data: data
})
}
export function getSessionDetail(id) {
return request({
url: `/api/v1/terminal/sessions/${id}/`,
method: 'get'
})
}
export function getSessionCommands(id) {
return request({
url: `/api/v1/terminal/commands/?session_id=${id}`,
method: 'get'
})
}

View File

@@ -1,5 +1,5 @@
<template>
<DataForm v-loading="loading" :fields="totalFields" v-bind="$attrs" v-on="$listeners">
<DataForm ref="form" v-loading="loading" :fields="totalFields" v-bind="$attrs" v-on="$listeners">
<FormGroupHeader v-for="(group, i) in groups" :slot="'id:'+group.name" :key="'group-'+group.name" :title="group.title" :line="i != 0" />
</DataForm>
</template>
@@ -48,7 +48,6 @@ export default {
},
mounted() {
this.optionUrlMeta()
console.log('auto data form', this.$attrs)
},
methods: {
optionUrlMeta() {
@@ -76,47 +75,65 @@ export default {
type = ''
field.component = Select2
break
case 'string':
type = 'input'
if (!fieldMeta.max_length) {
field.el.type = 'textarea'
}
break
default:
type = 'input'
break
}
if (type === 'radio-group') {
const options = fieldMeta.choices.map(v => {
return { label: v.display_name, value: v.value }
})
if (options.length > 4) {
type = 'select'
field.el.filterable = true
}
}
field.type = type
return field
},
generateFieldByName(name, field) {
switch (name) {
case 'email':
field.el = { type: 'email' }
field.el.type = 'email'
break
case 'password':
field.el = { type: 'password' }
field.el.type = 'password'
break
case 'comment':
field.el = { type: 'textarea' }
field.el.type = 'textarea'
break
}
return field
},
generateFieldByOther(field, fieldMeta) {
const filedRules = field.rules || []
if (fieldMeta.required) {
if (field.type === 'input') {
field.rules = [rules.Required]
filedRules.push(rules.Required)
} else {
field.rules = [rules.RequiredChange]
filedRules.push(rules.RequiredChange)
}
}
field.rules = filedRules
return field
},
generateField(name) {
let field = {}
let field = { id: name, prop: name, el: {}}
const fieldMeta = this.meta[name] || {}
field.id = name
field.prop = name
field.label = fieldMeta.label
field = this.generateFieldByType(fieldMeta.type, field, fieldMeta)
field = this.generateFieldByName(name, field)
field = this.generateFieldByOther(field, fieldMeta)
field = Object.assign(field, this.fieldsMeta[name] || {})
if (name === 'name') {
console.log(field)
}
return field
},
generateFieldGroup(data) {
@@ -132,9 +149,6 @@ export default {
generateFields(data) {
let fields = []
for (let field of data) {
console.log('is array', field instanceof Array)
console.log('is string', typeof field === 'string')
console.log('is object', field instanceof Object)
if (field instanceof Array) {
const items = this.generateFieldGroup(field)
fields = [...fields, ...items]

View File

@@ -4,7 +4,7 @@
<script>
import DataTable from '../DataTable'
import { DetailFormatter, DisplayFormatter, BooleanFormatter, ActionsFormatter } from '@/components/ListTable/formatters/index'
import { DetailFormatter, DisplayFormatter, BooleanFormatter, ActionsFormatter } from '@/components/ListTable/formatters'
import { optionUrlMeta } from '@/api/common'
export default {
name: 'AutoDataTable',
@@ -25,6 +25,11 @@ export default {
meta: {}
}
},
computed: {
expandField() {
return true
}
},
mounted() {
this.optionUrlMeta()
},
@@ -54,7 +59,8 @@ export default {
label: this.$tc('Actions'),
align: 'center',
formatter: ActionsFormatter,
width: '200px'
width: '150px',
actions: this.config.actions || {}
}
break
case 'is_valid':

View File

@@ -42,7 +42,9 @@
v-else-if="data.type === 'checkbox-group'"
:key="opt.label"
v-bind="opt"
/>
>
{{ opt.value }}
</el-checkbox>
<!-- WARNING: radio label 属性来表示 value 的含义 -->
<!-- FYI: radio value 属性可以在没有 radio-group 时用来关联到同一个 v-model -->
<el-radio

View File

@@ -1,6 +1,6 @@
<template>
<ElFormRender
ref="dataForm"
ref="form"
:content="fields"
v-bind="$attrs"
:form="basicForm"
@@ -14,8 +14,8 @@
<el-form-item v-if="defaultButton">
<slot name="button-start" />
<el-button size="small" @click="resetForm('dataForm')">{{ $tc('Reset') }}</el-button>
<el-button size="small" type="primary" @click="submitForm('dataForm')">{{ $tc('Submit') }}</el-button>
<el-button size="small" @click="resetForm('form')">{{ $tc('Reset') }}</el-button>
<el-button size="small" type="primary" @click="submitForm('form')">{{ $tc('Submit') }}</el-button>
</el-form-item>
<slot name="Actions" />
</ElFormRender>
@@ -53,9 +53,10 @@ export default {
methods: {
// 获取表单数据
submitForm(formName) {
this.$refs[formName].validate((valid) => {
const form = this.$refs[formName]
form.validate((valid) => {
if (valid) {
this.$emit('submit', this.$refs[formName].getFormValue())
this.$emit('submit', form.getFormValue(), form)
} else {
this.$emit('invalid', valid)
return false

View File

@@ -0,0 +1,37 @@
<template>
<div style="border: none; background: none;">
<span v-show="row.users.length"><label>{{ this.$t('perms.User') }}:</label>{{ format(row.users) }}<br></span>
<span v-show="row.user_groups.length"><label>{{ this.$t('perms.UserGroups') }}:</label>{{ format(row.user_groups) }}<br></span>
<span v-show="row.assets.length"><label>{{ this.$t('perms.Asset') }}:</label>{{ format(row.assets) }}<br></span>
<span v-show="row.nodes.length"><label>{{ this.$t('perms.Node') }}</label>:{{ format(row.nodes) }}<br></span>
<span v-show="row.system_users.length"><label>{{ this.$t('perms.SystemUser') }}:</label>{{ format(row.system_users) }}<br></span>
<span v-show="row.actions.length"><label>{{ this.$t('perms.Actions') }}:</label>{{ format(row.actions) }}</span>
</div>
</template>
<script>
import BaseFormatter from '@/components/ListTable/formatters/base'
export default {
name: 'ExpandAssetPermissionFormatter',
extends: BaseFormatter,
methods: {
format(val) {
if (val instanceof Array) {
return val.join(',')
}
return val
}
}
}
</script>
<style scoped>
label {
display: inline-block;
max-width: 100%;
margin: 5px;
font-weight: 700;
}
</style>

View File

@@ -0,0 +1,15 @@
<template>
<pre>{{ cellValue }}</pre>
</template>
<script>
import BaseFormatter from './base'
export default {
name: 'ExpandPreFormatter',
extends: BaseFormatter
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,21 @@
<template>
<pre style="border: none; background: none; white-space: pre-wrap">
{{ '$ '+ row.input }}
<br>
{{ row.output }}
</pre>
</template>
<script>
import BaseFormatter from '@/components/ListTable/formatters/base'
export default {
name: 'OutputExpandFormatter',
extends: BaseFormatter
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,23 @@
<template>
<el-link class="detail" :type="col.type || 'success'" @click="goDetail">{{ col.linkName }}</el-link>
</template>
<script>
import BaseFormatter from './base'
export default {
name: 'RouterFormatter',
extends: BaseFormatter,
methods: {
goDetail() {
const routeName = this.col.route || ''
this.$router.push({ name: routeName, params: { id: this.cellValue }})
}
}
}
</script>
<style scoped>
</style>

View File

@@ -1,6 +1,33 @@
export { default as DetailFormatter } from './DetailFormatter'
export { default as DisplayFormatter } from './DisplayFormatter'
export { default as BooleanFormatter } from './ChoicesFormatter'
export { default as ActionsFormatter } from './ActionsFormatter'
export { default as LengthFormatter } from './LengthFormatter'
import DetailFormatter from './DetailFormatter'
import DisplayFormatter from './DisplayFormatter'
import BooleanFormatter from './ChoicesFormatter'
import ActionsFormatter from './ActionsFormatter'
import ExpandPreFormatter from './ExpandPreFormatter'
import LengthFormatter from './LengthFormatter'
import RouterFormatter from './RouterFormatter'
import OutputExpandFormatter from './OutputExpandFormatter'
import ExpandAssetPermissionFormatter from './ExpandAssetPermissionFormatter'
export default {
DetailFormatter,
DisplayFormatter,
BooleanFormatter,
ActionsFormatter,
ExpandPreFormatter,
LengthFormatter,
RouterFormatter,
OutputExpandFormatter,
ExpandAssetPermissionFormatter
}
export {
DetailFormatter,
DisplayFormatter,
BooleanFormatter,
ActionsFormatter,
ExpandPreFormatter,
LengthFormatter,
RouterFormatter,
OutputExpandFormatter,
ExpandAssetPermissionFormatter
}

View File

@@ -67,7 +67,10 @@ const cn = {
'cancel': '取消',
'Import': '导入',
'Export': '导出',
'Other': '其它'
'Other': '其它',
'Create success': '创建成功',
'Deactive selected': '禁用所选',
'Active selected': '激活所选'
},
route: {
'dashboard': '仪表盘',
@@ -96,6 +99,7 @@ const cn = {
'Sessions': '会话管理',
'SessionOnline': '在线会话',
'SessionOffline': '历史会话',
'SessionDetail': '会话详情',
'Commands': '命令记录',
'WebTerminal': 'Web终端',
'FileManager': '文件管理',
@@ -112,7 +116,8 @@ const cn = {
'PasswordChangeLog': '改密日志',
'Settings': '系统设置',
'UserCreate': '创建用户',
'UserGroupCreate': '创建用户组'
'UserGroupCreate': '创建用户组',
'UserUpdate': '更新用户'
},
// 用户模块翻译
users: {
@@ -326,41 +331,22 @@ const cn = {
'app_path': '应用路径'
},
perms: {
'asset_permission': '资产授权',
'asset_permission_list': '资产授权列表',
'asset_permission_detail': '资产授权详情',
'create_asset_permission': '创建资产授权规则',
'update_asset_permission': '更新资产授权规则',
'Asset permissions': '资产授权',
'name': '名称',
'user': '用户',
'userGroup': '用户组',
'asset': '资产',
'node': '节点',
'systemUser': '系统用户',
'validity': '有效',
'action': '动作',
'update': '更新',
'delete': '删除',
'search': '搜索',
'user_count': '用户数量',
'user_group_count': '用户组数量',
'asset_count': '资产数量',
'node_count': '节点数量',
'system_user_count': '系统用户数量',
'date_start': '开始日期',
'date_expired': '失效日期',
'date_created': '创建日期',
'created_by': '创建者',
'comment': '备注',
'quick_update': '快速更新',
'active': '激活中',
'users_and_user_groups': '用户或用户组',
'assets_and_node': '资产或节点',
'RefreshPermissionCache': '刷新授权缓存',
'ReFreshSuccess': '刷新成功',
'ReFreshFail': '刷新失败',
'All': '全部',
'Connect': '连接',
'UpDownload': '上传下载',
'UploadFile': '上传文件',
'DownloadFile': '下载文件',
'Basic': '基本',
'User': '用户',
'Asset': '资产',
'Actions': '动作',
'UserGroups': '用户组',
'Node': '节点',
'SystemUser': '系统用户',
//
'RemoteApp': '远程应用',
//
@@ -373,30 +359,33 @@ const cn = {
'systemUser': '系统用户',
'remoteAddr': '远端地址',
'protocol': '协议',
'loginForm': '登录来源',
'loginFrom': '登录来源',
'command': '命令',
'dateStart': '开始日期',
'duration': '时长',
'terminate': '终断',
'date_end': '结束日期',
'dateEnd': '结束日期',
'commands': '命令记录',
'replay': '回放',
'download': '下载',
'RiskLevel': '风险等级',
'riskLevel': '风险等级',
'session': '会话',
'date': '日期',
'addr': '地址',
'active': '激活中',
'alive': '在线',
'StorageConfiguration': '存储配置',
'join': '加入'
'join': '加入',
'goto': '转到',
'sessionDetail': '会话详情',
'quickModify': '快速修改'
},
jobcenter: {
'RunTimes': '执行次数',
'hosts': '主机',
'success': '成功',
'date': '日期',
'time': '时间',
'Hosts': '主机',
'Success': '成功',
'Date': '日期',
'Time': '时间',
'run': '执行'
},
tickets: {

View File

@@ -18,7 +18,11 @@ const en = {
'assets': 'Assets',
'applications': 'Applications',
'perms': 'Perms',
'sessions': 'Sessions',
'Sessions': 'Sessions',
'SessionOnline': 'Session Online',
'SessionOffline': 'Session Offline',
'SessionDetail': 'Session Detail',
'Goto': 'Goto',
'jobcenter': 'Job Center'
},
users: {
@@ -265,18 +269,16 @@ const en = {
'database_app_count': 'DatabaseApp count'
},
sessions: {
'session_online_list': '',
'session_detail': ' Session detail',
'id': '',
'user': 'Use',
'asset': 'Asset',
'system_user': 'System user',
'remote_addr': 'Remote addr',
'systemUser': 'System user',
'remoteAddr': 'Remote addr',
'protocol': 'Protocol',
'login_form': 'Login from',
'loginFrom': 'Login from',
'command': 'Command',
'date_start': 'Date start',
'duration': 'Druation',
'dateStart': 'Date start',
'duration': 'Duration',
'action': 'Action',
'search': 'Search',
'terminate_selected': 'Terminate selected',
@@ -284,12 +286,15 @@ const en = {
'submit': 'Submit',
'terminate': 'Terminate',
'command_list': 'Command list',
'date_end': 'Date end',
'dateEnd': 'Date end',
'quick_modify': 'Quick modify',
'terminate_session': 'Terminate session',
'confirm': 'Confirm',
'commands': 'Commands',
'join': 'join'
'join': 'join',
'goto': 'Goto',
'sessionDetail': 'Session Detail',
'quickModify': 'Quick Modify'
},
setting: {
'setting': 'System Setting',

View File

@@ -1,10 +1,17 @@
<template>
<Page>
<Page v-loading="loadding">
<IBox>
<AutoDataForm :form="form" :fields="fields" :url="url" v-bind="$attrs" v-on="$listeners" @submit="handleSubmit">
<slot v-for="item in fields" :slot="`id:${item}`" :name="`id:${item}`" />
<slot v-for="item in fields" :slot="`$id:${item}`" :name="`$id:${item}`" />
</AutoDataForm>
<AutoDataForm
v-if="!loadding"
ref="form"
:method="method"
:form="form"
:fields="fields"
:url="totalUrl"
v-bind="$attrs"
v-on="$listeners"
@submit="handleSubmit"
/>
</IBox>
</Page>
</template>
@@ -21,46 +28,105 @@ export default {
type: String,
required: true
},
method: {
type: String,
default: 'post'
},
fields: {
type: Array,
default: () => {
return []
}
},
form: {
object: {
type: Object,
default: () => { return {} }
default: () => ({})
},
initial: {
type: Object,
default: () => ({})
},
onSubmit: {
type: Function,
default: null
},
getMethod: {
type: Function,
default: function() {
const params = this.$route.params
if (params.id) {
return 'put'
} else {
return 'post'
}
}
},
getUrl: {
type: Function,
default: function() {
const params = this.$route.params
let url = this.url
if (params.id) {
url = `${url}/${params.id}/`
}
return url
}
}
},
data() {
return {
form: {},
loadding: true
}
},
computed: {
method() {
const method = this.getMethod(this)
return method
},
totalUrl() {
return this.getUrl()
}
},
mounted() {
console.log('generic', this.$attrs)
console.log(this.fields)
if (this.method === 'put') {
this.getObjectDetail()
} else {
this.form = Object.assign(this.form, this.initial)
this.loadding = false
}
},
methods: {
handleSubmit(values) {
handleSubmit(values, form) {
let handler = this.onSubmit || this.defaultOnSubmit
handler = handler.bind(this)
const fields = form.$refs.elForm.fields
console.log('submit', values)
return handler(values)
console.log('form.fields', fields)
return handler(values, form)
},
defaultOnSubmit(validValues) {
this.$axios.post(this.url, validValues).then(
() => {
const msg = this.$tc('Create success')
this.$message.success(msg)
setTimeout(() => {
this.$router.push({ name: 'UserList' })
}, 500)
defaultPerformSubmit(validValues) {
return this.$axios[this.method](this.totalUrl, validValues)
},
defaultOnSubmit(validValues, form) {
this.defaultPerformSubmit(validValues).then(() => {
const msg = this.$tc('Create success')
this.$message.success(msg)
this.$router.push({ name: 'UserList' })
}).catch(error => {
console.log(form)
const response = error.response
const data = response.data
if (response.status === 400) {
this.errors.name = '你报错了滴滴滴'
console.log(data)
}
)
})
},
getObjectDetail() {
this.$axios.get(this.totalUrl).then(data => {
this.form = data
}).catch(error => {
console.log(error)
}).finally(() => {
this.loadding = false
})
}
}
}

View File

@@ -25,7 +25,7 @@
import Page from '../Page/'
import ActionsGroup from '@/components/ActionsGroup'
export default {
name: 'BaseDetailPage',
name: 'GenericDetailPage',
components: {
Page,
ActionsGroup

View File

@@ -3,7 +3,7 @@ export { default as NavHeader } from './NavHeader'
export { default as AppMain } from './AppMain'
export { default as Page } from './Page'
export { default as TagsView } from './TagsView'
export { default as BaseDetailPage } from './BaseDetailPage'
export { default as GenericDetailPage } from './GenericDetailPage'
export { default as SubMenuPage } from './SubMenuPage'
export { default as Footer } from './Footer'
export { default as IBox } from './IBox'

View File

@@ -70,14 +70,14 @@ export const constantRoutes = [
component: () => import('@/views/users/UserCreateUpdate.vue'), // Parent router-view
name: 'UserCreate',
hidden: true,
meta: { title: 'UserCreate', activeMenu: '/users/users' }
meta: { title: 'UserCreate', activeMenu: '/users/users', action: 'create' }
},
{
path: 'users/update/:id',
path: 'users/:id/update',
component: () => import('@/views/users/UserCreateUpdate.vue'), // Parent router-view
name: 'UserEdit',
name: 'UserUpdate',
hidden: true,
meta: { title: 'UserEdit' }
meta: { title: 'UserUpdate', activeMenu: '/users/users', action: 'update' }
},
{
path: 'users/detail/:id',
@@ -274,6 +274,13 @@ export const constantRoutes = [
component: () => import('@/views/sessions/CommandList'),
meta: { title: 'Commands' }
},
{
path: 'sessions/:id',
name: 'SessionDetail',
component: () => import('@/views/sessions/SessionDetail'),
meta: { title: 'SessionDetail' },
hidden: true
},
{
path: 'luna',
name: 'WebTerminal',

View File

@@ -235,3 +235,29 @@ td .el-button.el-button--mini {
.el-radio__input.is-checked+.el-radio__label {
color: inherit;
}
.el-radio__input.is-checked .el-radio__inner {
border-color: #409EFF;
background-color: #409EFF;
}
.el-checkbox__input.is-checked .el-checkbox__inner {
border-color: #409EFF;
background-color: #409EFF;
}
.el-radio__inner:hover {
border-color: #409EFF;
}
.el-textarea__inner {
border-radius: 0;
}
.el-checkbox__input.is-checked .el-checkbox__inner, .el-checkbox__input.is-indeterminate .el-checkbox__inner {
border-color: #409EFF;
background-color: #409EFF;
}
.el-checkbox__inner:hover {
border-color: #409EFF;
}

View File

@@ -5,7 +5,6 @@
@import './sidebar.scss';
@import './element-index.css';
@import './menu.scss';
@import 'vue-select/src/scss/vue-select.scss';
@import "./font-awesome/font-awesome.min.css";
body {

View File

@@ -53,37 +53,34 @@ service.interceptors.response.use(
const res = response.data
// if the custom code is not 20000, it is judged as an error.
if (response.status < 200 || response.status > 300) {
if (response.status >= 200 && response.status < 400) {
if (response.config.raw === 1) {
return response
}
return res
} else if (response.status === 50008 || response.status === 50012 || response.status === 50014) {
MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
confirmButtonText: 'Re-Login',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
} else if (response.status === 400) {
console.log('status is 400')
return Promise.reject(res || 'Error')
} else {
Message({
message: res.message || res.error || 'Error',
type: 'error',
duration: 5 * 1000
})
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
// 自定义错误码
if (response.status === 50008 || response.status === 50012 || response.status === 50014) {
// to re-login
MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
confirmButtonText: 'Re-Login',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
}
return Promise.reject(new Error(res.message || 'Error'))
} else {
if (response.config.raw === 1) {
return response
}
return res
}
},
error => {
console.log('err' + error) // for debug
Message({
message: error.message,
type: 'error',

View File

@@ -3,8 +3,9 @@
</template>
<script>
import { timeOffset, toSafeLocalDateStr } from '@/utils/common'
import { GenericListPage } from '@/layout/components'
import { DetailFormatter, ActionsFormatter } from '@/components/ListTable/formatters/index'
import { ActionsFormatter } from '@/components/ListTable/formatters/index'
export default {
components: {
@@ -14,47 +15,71 @@ export default {
return {
tableConfig: {
url: '/api/v1/ops/tasks/',
columns: [
{
prop: 'name',
columns: ['name', 'runtimes', 'host_amount', 'is_success', 'date_start', 'time', 'actions'],
columnsMeta: {
name: {
label: this.$tc('Name'),
formatter: DetailFormatter,
sortable: 'custom',
route: 'UserDetail'
showOverflowTooltip: true
},
{
prop: 'latest_execution',
label: this.$t('jobcenter.RunTimes')
runtimes: {
label: this.$t('jobcenter.RunTimes'),
formatter: function(row) {
const summary = <div>
<span class='text-primary'>{row.summary.success}</span>/
<span class='text-danger'>{row.summary.failed}</span>/
<span>{row.summary.total}</span>
</div>
return summary
}
},
{
prop: 'latest_execution.hosts_amount',
label: this.$t('jobcenter.hosts')
host_amount: {
label: this.$t('jobcenter.Hosts'),
formatter: function(row) {
return row.latest_execution.hosts_amount
}
},
{
prop: 'latest_execution.is_success',
label: this.$t('jobcenter.success')
is_success: {
label: this.$t('jobcenter.Success'),
formatter: row => {
if (row.latest_execution.is_success) {
return <i class='fa fa-check text-primary'/>
}
return <i class='fa fa-times text-danger'/>
}
},
{
prop: 'latest_execution.date_start',
label: this.$t('jobcenter.date'),
sortable: 'custom'
date_start: {
label: this.$t('jobcenter.Date'),
formatter: function(row) {
return toSafeLocalDateStr(row.latest_execution.date_start)
}
},
{
prop: 'latest_execution.timedelta',
label: this.$t('jobcenter.time')
time: {
label: this.$t('jobcenter.Time'),
formatter: function(row) {
return timeOffset(row.latest_execution.date_start, row.latest_execution.date_finished)
}
},
{
actions: {
prop: 'id',
label: this.$tc('Action'),
align: 'center',
formatter: ActionsFormatter,
width: '200px',
actions: [
]
actions: {
hasUpdate: false,
extraActions: [
{
name: 'run',
title: this.$t('jobcenter.run'),
type: 'primary',
callback: function({ cellValue, tableData }) {
// 跳转页面
const replayUrl = '/ops/celery/task/' + cellValue
window.open(replayUrl)
}
}
]
}
}
],
hasEdit: false,
hasDelete: false
}
},
headerActions: {
hasCreate: false,

View File

@@ -11,6 +11,8 @@ export default {
data() {
return {
form: {
is_active: true,
actions: ['all', 'connect', 'updownload', 'upload_file', 'download_file'],
date_expired: '2099-12-31 00:00:00 +0800'
},
fields: [
@@ -53,7 +55,15 @@ export default {
}
},
actions: {
label: this.$t('perms.Actions')
label: this.$t('perms.Actions'),
type: 'checkbox-group',
options: [
{ label: 'all', value: this.$t('perms.All') },
{ label: 'connect', value: this.$t('perms.Connect') },
{ label: 'updownload', value: this.$t('perms.UpDownload') },
{ label: 'upload_file', value: this.$t('perms.UploadFile') },
{ label: 'download_file', value: this.$t('perms.DownloadFile') }
]
},
is_active: {
type: 'checkbox'

View File

@@ -4,7 +4,7 @@
<script>
import { GenericListPage } from '@/layout/components'
import { LengthFormatter } from '@/components/ListTable/formatters/index'
import { LengthFormatter, ExpandAssetPermissionFormatter } from '@/components/ListTable/formatters/index'
export default {
components: {
@@ -14,8 +14,13 @@ export default {
return {
tableConfig: {
url: '/api/v1/perms/asset-permissions/',
columns: ['name', 'users', 'user_groups', 'assets', 'nodes', 'system_users', 'is_active', 'actions'],
hasSelection: false,
columns: ['expand', 'name', 'users', 'user_groups', 'assets', 'nodes', 'system_users', 'is_active', 'actions'],
columnsMeta: {
expand: {
type: 'expand',
formatter: ExpandAssetPermissionFormatter
},
users: {
formatter: LengthFormatter
},
@@ -36,9 +41,29 @@ export default {
headerActions: {
hasDelete: false,
hasUpdate: false,
createRoute: 'AssetPermissionCreate'
hasBulkDelete: false,
createRoute: 'AssetPermissionCreate',
extraActions: [
{
name: 'RefreshPermissionCache',
title: this.$t('perms.RefreshPermissionCache'),
type: 'primary',
has: true,
callback: this.HandleRefreshPermissionCache
}
]
}
}
},
methods: {
HandleRefreshPermissionCache() {
const url = '/api/v1/perms/asset-permissions/cache/refresh/'
this.$axios.get(url).then(res => {
this.$message.success(this.$t('perms.ReFreshSuccess'))
}).catch(err => {
this.$message.error(this.$t('perms.ReFreshFail') + ':' + err)
})
}
}
}
</script>

View File

@@ -4,6 +4,8 @@
<script>
import { GenericListPage } from '@/layout/components'
import { toSafeLocalDateStr } from '@/utils/common'
import { RouterFormatter, OutputExpandFormatter } from '@/components/ListTable/formatters'
export default {
components: {
@@ -12,55 +14,47 @@ export default {
data() {
return {
tableConfig: {
axiosConfig: {
raw: 1,
params: {
display: 1,
is_finished: 0
}
},
hasSelection: false,
hasOperation: false,
url: '/api/v1/terminal/commands/',
columns: [
{
type: 'expand'
'expandCol', 'input', 'risk_level', 'user',
'asset', 'system_user', 'session', 'timestamp'
],
columnsMeta: {
expandCol: {
type: 'expand',
prop: 'output',
formatter: OutputExpandFormatter
},
{
prop: 'input',
input: {
label: this.$t('sessions.command')
},
{
prop: 'output',
label: '命令输出结果 (怎么放到隐藏内容 )',
expand: true
risk_level: {
label: this.$t('sessions.riskLevel')
},
{
prop: 'risk_level',
label: this.$t('sessions.RiskLevel')
user: {
label: this.$t('sessions.user')
},
{
prop: 'user',
label: this.$t('sessions.user'),
sortable: true
},
{
prop: 'asset',
asset: {
label: this.$t('sessions.asset')
},
{
prop: 'system_user',
system_user: {
label: this.$t('sessions.systemUser')
},
{
prop: 'session',
label: this.$t('sessions.session')
session: {
label: this.$t('sessions.session'),
formatter: RouterFormatter,
route: 'SessionDetail',
linkName: this.$t('sessions.goto')
},
{
prop: 'timestamp',
label: this.$t('sessions.date')
timestamp: {
label: this.$t('sessions.date'),
formatter: function(row) {
return toSafeLocalDateStr(row.timestamp * 1000)
}
}
],
},
tableActions: {
hasEdit: false,
hasDelete: false

View File

@@ -0,0 +1,168 @@
<template>
<GenericDetailPage :submenu="submenu" :active-menu="activeSubMenu" :title="title">
<div slot="detail">
<el-row :gutter="20">
<el-col :span="14">
<DetailCard :title="cardTitle" :items="detailItems" />
</el-col>
<el-col :span="10">
<el-card class="box-card primary">
<div slot="header" class="clearfix">
<i class="fa fa-user" />
<span>{{ cardActions }}</span>
</div>
</el-card>
</el-col>
</el-row>
</div>
<div slot="command">
<el-row :gutter="20">
<el-col :span="14">
<ListTable :table-config="tableConfig" :header-actions="headerActions" />
</el-col>
<el-col :span="10">
<el-card class="box-card primary">
<div slot="header" class="clearfix">
<i class="fa fa-user" />
<span>{{ cardActions }}</span>
</div>
</el-card>
</el-col>
</el-row>
</div>
</GenericDetailPage>
</template>
<script>
import { GenericDetailPage } from '@/layout/components'
import DetailCard from '@/components/DetailCard/index'
import ListTable from '@/components/ListTable'
import { getSessionDetail, getSessionCommands } from '@/api/sessions'
import { OutputExpandFormatter } from '@/components/ListTable/formatters'
import { toSafeLocalDateStr } from '@/utils/common'
export default {
name: 'SessionDetail',
components: {
GenericDetailPage,
DetailCard,
ListTable
},
data() {
return {
tableConfig: {
hasSelection: false,
url: `/api/v1/terminal/commands/?session_id=${this.$route.params.id}`,
columns: [
'expandCol', 'index', 'input', 'timestamp'
],
columnsMeta: {
expandCol: {
type: 'expand',
prop: 'output',
formatter: OutputExpandFormatter
},
index: {
type: 'index'
},
input: {
label: this.$t('sessions.command')
},
timestamp: {
label: this.$t('sessions.date'),
formatter: function(row) {
return toSafeLocalDateStr(row.timestamp * 1000)
}
}
}
},
headerActions: {
hasExport: false,
hasImport: false,
hasRefresh: false,
hasCreate: false,
hasBulkDelete: false,
hasBulkUpdate: false,
hasLeftActions: false,
hasSearch: false,
hasRightActions: false
},
activeSubMenu: 'detail',
sessionData: {},
commandData: {},
submenu: [
{
title: this.$t('route.SessionDetail'),
name: 'detail'
},
{
title: this.$t('sessions.command'),
name: 'command'
}
]
}
},
computed: {
title() {
return this.$t('sessions.sessionDetail')
},
cardTitle() { return this.sessionData.id },
cardActions() {
return this.$t('sessions.quickModify')
},
detailItems() {
return [
{
key: this.$t('sessions.user'),
value: this.sessionData.user
},
{
key: this.$t('sessions.asset'),
value: this.sessionData.asset
},
{
key: this.$t('sessions.systemUser'),
value: this.sessionData.system_user
},
{
key: this.$t('sessions.protocol'),
value: this.sessionData.protocol
},
{
key: this.$t('sessions.loginFrom'),
value: this.sessionData.login_from
},
{
key: this.$t('sessions.remoteAddr'),
value: this.sessionData.remote_addr
},
{
key: this.$t('sessions.dateStart'),
value: this.sessionData.date_start
},
{
key: this.$t('sessions.dateEnd'),
value: this.sessionData.date_end
}
]
}
},
mounted() {
getSessionDetail(this.$route.params.id).then(data => {
this.sessionData = data
})
getSessionCommands(this.$route.params.id).then(data => {
this.commandData = data
})
},
methods: {
}
}
</script>
<style scoped>
</style>

View File

@@ -22,14 +22,16 @@ export default {
],
columnsMeta: {
index: {
type: 'index',
label: this.$t('sessions.id')
label: this.$t('sessions.id'),
formatter: function(row, column, cellValue, index) {
return <a class='detail el-link el-link--success is-underline' href= { '/terminal/sessions/' + row.id }>{ index + 1}</a>
}
},
command_amount: {
label: this.$t('sessions.command')
},
login_from: {
label: this.$t('sessions.loginForm')
label: this.$t('sessions.loginFrom')
},
protocol: {
label: this.$t('sessions.protocol'),

View File

@@ -22,8 +22,10 @@ export default {
],
columnsMeta: {
index: {
type: 'index',
label: this.$t('sessions.id')
label: this.$t('sessions.id'),
formatter: function(row, column, cellValue, index) {
return <a class='detail el-link el-link--success is-underline' href= { '/terminal/sessions/' + row.id }>{ index + 1}</a>
}
},
command_amount: {
label: this.$t('sessions.command')

View File

@@ -1,10 +1,5 @@
<template>
<GenericCreateUpdatePage :fields="fields" :form="form" :fields-meta="fieldsMeta" :url="url">
<!-- <FormGroupHeader slot="id:name" title="账户" :line="false" />-->
<!-- <FormGroupHeader slot="id:password_strategy" title="认证" :line="true" />-->
<!-- <FormGroupHeader slot="id:role" title="角色安全" :line="true" />-->
<!-- <FormGroupHeader slot="id:phone" title="认证" :line="true" />-->
</GenericCreateUpdatePage>
<GenericCreateUpdatePage :fields="fields" :initial="initial" :fields-meta="fieldsMeta" :url="url" />
</template>
<script>
@@ -14,30 +9,46 @@ export default {
GenericCreateUpdatePage
},
data() {
const errors = { name: '' }
return {
form: {
initial: {
password_strategy: 0,
mfa_level: 0,
source: 'ldap',
source: 'local',
role: 'Admin',
date_expired: '2099-12-31 00:00:00 +0800'
},
fields: [
[this.$t('users.' + 'Account'), ['name', 'username', 'email', 'groups']],
[this.$t('users.' + 'Authentication'), ['password_strategy', 'password', 'mfa_level', 'source']],
[this.$t('users.' + 'Authentication'), ['password_strategy', 'password', 'public_key', 'mfa_level', 'source']],
[this.$t('users.' + 'Secure'), ['role', 'date_expired']],
[this.$tc('Other'), ['phone', 'wechat', 'comment']]
],
errors: errors,
url: '/api/v1/users/users/',
fieldsMeta: {
name: {
el: {
error: '无措'
}
},
password_strategy: {
hidden: () => {
return this.$route.params.id
}
},
password: {
hidden: (formValue, item) => {
console.log('hidden password', formValue.password_strategy)
if (this.$route.params.id === undefined) {
return formValue.password_strategy !== 1
} else {
return true
if (this.$route.meta.action === 'update') {
return false
}
return formValue.password_strategy !== 1
}
},
public_key: {
hidden: (formValue, item) => {
return this.$route.meta.action !== 'update'
}
},
groups: {
@@ -49,6 +60,11 @@ export default {
}
}
},
mounted() {
setTimeout(() => {
this.errors.name = 'dididi'
}, 3000)
},
methods: {
debug() {
console.log(this)
@@ -58,10 +74,4 @@ export default {
</script>
<style lang="less" scoped>
.el-form /deep/ .el-select{
width:100%;
}
.el-form /deep/ .el-form-item__content > .el-date-editor{
width:100%;
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<BaseDetailPage :submenu="submenu" :active-menu="activeSubMenu" :title="title">
<GenericDetailPage :submenu="submenu" :active-menu="activeSubMenu" :title="title">
<div slot="info">
<el-row :gutter="20">
<el-col :span="14">
@@ -18,18 +18,18 @@
</el-col>
</el-row>
</div>
</BaseDetailPage>
</GenericDetailPage>
</template>
<script>
import { getUserGroupDetail, getUserGroupMembers } from '@/api/user'
import { BaseDetailPage } from '@/layout/components'
import { GenericDetailPage } from '@/layout/components'
import DetailCard from '@/components/DetailCard'
import Select2 from '@/components/Select2'
export default {
components: {
BaseDetailPage,
GenericDetailPage,
DetailCard,
Select2
},

View File

@@ -26,7 +26,8 @@ export default {
performBulkDelete: function(rows) {
console.log('hello')
}
}
},
helpMessage: '用户列表'
}
}
}

View File

@@ -14,14 +14,18 @@ export default {
tableConfig: {
url: '/api/v1/users/users/',
columns: [
'name', 'username', 'role', 'groups_display', 'source', 'is_active', 'actions'
'name', 'username', 'role', 'groups_display', 'source', 'is_valid', 'actions'
],
detailRoute: 'UserDetail'
columnsMeta: {
},
detailRoute: 'UserDetail',
actions: {
updateRoute: 'UserUpdate'
}
},
headerActions: {
createRoute: 'UserCreate',
onCreate: () => {
},
extraMoreActions: [
{
name: 'deactiveSelected',