merge: with v3 (#2250)

* perf: 增加celery输出

* fix: 修复网域列表资产选中后更新时不显示选中问题

* fix: 选择资产时会根据接口返回状态设置disabled

* feat: 资产选择form 匹配正确的字段

* feat: 资产选择form 匹配正确的字段

* perf: 优化仪表盘环形图数据显示

* feat: 修改资产选择组件支持我的资产选择

* fix: 改密计划选择资产组件添加disabled状态

* fix: 修复资产账号点击资产树接口500问题

* fix: 更新网域选择资产组件添加disabled状态

* perf: 优化显示

* perf: 修改 Domain 列表页面

* perf: 修改网域列表网关字段跳转

* perf: 修改 GatewayList 页面

* perf: 选择资产组件弹窗显示资产详情

* perf: 修改资产详情账号列表页面

* perf: 修改资产账号详情

* fix: 修复改密详情页面报错误提示问题

* fix: 修复资产树点击资产接口报错问题

Co-authored-by: Aaron3S <chenyang@fit2cloud.com>
Co-authored-by: “huailei000” <2280131253@qq.com>
Co-authored-by: Bai <baijiangjie@gmail.com>
This commit is contained in:
fit2bot
2022-12-06 17:04:53 +08:00
committed by GitHub
parent a272fbb542
commit 791f173cea
26 changed files with 225 additions and 129 deletions

View File

@@ -98,11 +98,24 @@ export default {
columnsShow: {
min: ['name', 'username', 'actions'],
default: [
'name', 'hostname', 'ip', 'username',
'version', 'privileged', 'actions'
'name', 'username', 'version', 'privileged', 'actions', 'secret_type', 'actions'
]
},
columnsMeta: {
name: {
showOverflowTooltip: true,
formatter: function(row) {
const to = {
name: 'AssetAccountDetail',
params: { id: row.id }
}
if (vm.$hasPerm('assets.view_account')) {
return <router-link to={ to } >{ row.name }</router-link>
} else {
return <span>{ row.name }</span>
}
}
},
asset: {
label: this.$t('assets.Asset'),
showOverflowTooltip: true,

View File

@@ -31,7 +31,7 @@
<script>
import TreeTable from '@/components/TreeTable'
import { DetailFormatter } from '@/components/TableFormatters'
import { DialogDetailFormatter } from '@/components/TableFormatters'
import Select2 from '@/components/FormFields/Select2'
import Dialog from '@/components/Dialog'
@@ -39,6 +39,14 @@ export default {
componentName: 'AssetSelect',
components: { TreeTable, Select2, Dialog },
props: {
baseUrl: {
type: String,
default: '/api/v1/assets/assets/'
},
baseNodeUrl: {
type: String,
default: '/api/v1/assets/nodes/'
},
value: {
type: Array,
default: () => []
@@ -55,12 +63,20 @@ export default {
}
},
data() {
const iValue = []
for (const item of this.value) {
if (typeof item === 'object') {
iValue.push(item.id)
} else {
iValue.push(item)
}
}
const select2Config = {
value: this.value,
value: iValue,
multiple: true,
clearable: true,
ajax: {
url: '/api/v1/assets/assets/?fields_size=mini',
url: this.baseUrl,
transformOption: (item) => {
return { label: item.name + '(' + item.address + ')', value: item.id }
}
@@ -69,24 +85,22 @@ export default {
const vm = this
return {
dialogVisible: false,
initialValue: _.cloneDeep(this.value),
initialValue: _.cloneDeep(iValue),
rowSelected: [],
initSelection: null,
treeSetting: {
showMenu: false,
showRefresh: true,
showAssets: false,
showSearch: true,
customTreeHeader: true,
url: '/api/v1/assets/assets/?fields_size=mini',
nodeUrl: '/api/v1/assets/nodes/',
url: this.baseUrl,
nodeUrl: this.baseNodeUrl,
// ?assets=0不显示资产. =1显示资产
treeUrl: '/api/v1/assets/nodes/children/tree/?assets=0'
treeUrl: `${this.baseNodeUrl}/children/tree/?assets=0`
},
select2Config: select2Config,
dialogSelect2Config: select2Config,
tableConfig: {
url: '/api/v1/assets/assets/?fields_size=mini',
url: this.baseUrl,
hasTree: true,
canSelect: this.canSelect,
columns: [
@@ -95,25 +109,65 @@ export default {
label: this.$t('assets.Name'),
sortable: true,
showOverflowTooltip: true,
formatter: DetailFormatter,
formatter: DialogDetailFormatter,
formatterArgs: {
route: 'AssetDetail'
getDialogTitle: function({ col, row }) { this.$t('assets.AssetDetail') }.bind(this),
getDetailItems: function({ col, row }) {
return [
{
key: this.$t('assets.Name'),
value: row.name
},
{
key: this.$t('assets.AssetAddress'),
value: row.address
},
{
key: this.$t('assets.Protocols'),
value: row.protocols.map(item => item.name).join(', ')
},
{
key: this.$t('assets.Category'),
value: row.category.label
},
{
key: this.$t('assets.Type'),
value: row.type.label
},
{
key: this.$t('assets.Platform'),
value: row.platform?.name || ''
},
{
key: this.$t('common.Active'),
value: row.is_active
},
{
key: this.$t('assets.Comment'),
value: row.comment
}
]
}.bind(this)
}
},
{
prop: 'ip',
prop: 'address',
label: this.$t('assets.ipDomain'),
sortable: 'custom'
},
{
prop: 'platform',
label: this.$t('assets.Platform'),
sortable: true
sortable: true,
formatter: function(row) {
return row.platform.name
}
},
{
prop: 'protocols',
formatter: function(row) {
return <span> {row.protocols?.toString()} </span>
const data = row.protocols.map(p => <el-tag size='mini'>{p.name}/{p.port} </el-tag>)
return <span> {data} </span>
},
label: this.$t('assets.Protocols')
}
@@ -128,7 +182,7 @@ export default {
}
},
theRowDefaultIsSelected: (row) => {
return this.value.indexOf(row.id) > -1
return iValue.indexOf(row.id) > -1
}
},
headerActions: {
@@ -215,5 +269,4 @@ export default {
.page ::v-deep .treebox {
height: inherit !important;
}
</style>

View File

@@ -128,9 +128,9 @@ export default {
query['asset'] = ''
url = `${this.setting.url}${combinator}node_id=${objectId}&show_current_asset=${show_current_asset}`
} else if (treeNode.meta.type === 'asset') {
query['asset'] = treeNode.meta.data.id
query['asset'] = treeNode.meta.data?.id || treeNode.id
query['node'] = ''
url = `${this.setting.url}${combinator}asset_id=${objectId}&show_current_asset=${show_current_asset}`
url = `${this.setting.url}${combinator}asset_id=${query.asset}&show_current_asset=${show_current_asset}`
}
this.$router.push({ query })
this.$emit('urlChange', url)

View File

@@ -68,7 +68,9 @@ export default {
<style lang="scss" scoped>
.card >>> .el-card__body {
padding: 0
padding: 0;
}
.el-card {
border: 0!important;
}
</style>

View File

@@ -104,6 +104,7 @@
},
"assets": {
"UserSwitchFrom": "User switch from",
"AssetAddress": "IP/Host",
"sshkeyAccount": "ssh key account",
"passwordAccount": "Password account",
"SelectTemplate": "Select template",
@@ -180,6 +181,7 @@
"DomainHelpMessage": "The domain function is added to address the fact that some environments (such as the hybrid cloud) cannot be connected directly by jumping on the gateway server.\nJMS => Domain gateway => Target assets",
"FullName": "Full name",
"Gateway": "Gateway",
"GatewayList": "Gateway",
"GatewayProtocolHelpText": "SSH protocol gateway, support proxy SSH, RDP, VNC",
"Hostname": "Hostname",
"IP": "IP",
@@ -412,7 +414,6 @@
"Push": "Push",
"Receivers": "Receivers",
"QuickUpdate": "Quick update",
"QuickSelect": "Quick select",
"RemoveSuccessMsg": "Remove success",
"Reset": "Reset",
"Search": "Search",

View File

@@ -109,6 +109,7 @@
"NoSQLProtocol": "非リレーショナルデータベース"
},
"assets": {
"AssetAddress": "IP/ホスト名",
"UserSwitchFrom": "ユーザーは",
"sshkeyAccount": "SSHキー",
"passwordAccount": "パスワード",

View File

@@ -419,6 +419,7 @@
"DateLast3Months": "最近三月",
"DateLastMonth": "最近一月",
"DateLastWeek": "最近一周",
"DateStart": "开始日期",
"Delete": "删除",
"Disable": "禁用",
"Download": "下载",
@@ -493,7 +494,6 @@
"dateCreated": "创建日期",
"dateExpired": "失效日期",
"dateFinished": "完成日期",
"DateStart": "开始日期",
"deleteErrorMsg": "删除失败",
"deleteFailedMsg": "删除失败",
"deleteSelected": "删除所选",
@@ -1675,7 +1675,6 @@
"OpenStack": "OpenStack",
"GCP": "谷歌云",
"FC": "Fusion Compute",
"LAN": "局域网",
"AWS_China": "AWS(中国)",
"AWS_Int": "AWS(国际)",
"HuaweiCloud": "华为云",

View File

@@ -33,7 +33,7 @@ export default {
return {
quickActions: [
{
title: this.$t('assets.PrivilegedTemplate'),
title: this.$t('assets.Privileged'),
type: 'switcher',
attrs: {
model: vm.object.privileged,

View File

@@ -45,7 +45,7 @@ export default {
url = setUrlParam(url, 'asset', '')
url = setUrlParam(url, 'node', nodeId)
} else if (treeNode.meta.type === 'asset') {
const assetId = treeNode.meta.data.id
const assetId = treeNode.meta.data?.id || treeNode?.id
url = setUrlParam(url, 'node', '')
url = setUrlParam(url, 'asset', assetId)
}

View File

@@ -23,8 +23,8 @@ export default {
return {
plan: { name: '', username: '', comment: '' },
config: {
activeMenu: 'ChangeAuthPlanInfo',
url: '/api/v1/assets/change-secret-automations/',
activeMenu: 'ChangeSecreAtutomationInfo',
url: '/api/v1/assets/change-secret-automations',
submenu: [
{
title: this.$t('common.BasicInfo'),

View File

@@ -52,6 +52,11 @@ export const getFields = () => {
rules: [
{ required: false }
],
el: {
canSelect: (row) => {
return row.enabled_info.change_secret_enabled
}
},
label: i18n.t('xpack.Asset')
},
passphrase: {

View File

@@ -98,7 +98,7 @@ export default {
url = setUrlParam(url, 'asset', '')
url = setUrlParam(url, 'node', nodeId)
} else if (treeNode.meta.type === 'asset') {
const assetId = treeNode.meta.data.id
const assetId = treeNode.meta.data?.id || treeNode.id
url = setUrlParam(url, 'node', '')
url = setUrlParam(url, 'asset', assetId)
}

View File

@@ -23,7 +23,10 @@ export default {
component: AssetSelect,
label: this.$t('assets.Assets'),
el: {
value: []
value: [],
canSelect: (row) => {
return row.enabled_info.domain_enabled
}
}
}
},

View File

@@ -55,8 +55,7 @@ export default {
tableConfig: {
url: `/api/v1/assets/gateways/?domain=${this.$route.params.id}`,
columns: [
'name', 'address', 'platform', 'password_account',
'ssh_key_account', 'connectivity', 'is_active', 'actions'
'name', 'address', 'platform', 'connectivity', 'is_active', 'actions'
],
columnsMeta: {
name: {
@@ -72,34 +71,6 @@ export default {
address: {
width: '140px'
},
password_account: {
label: this.$t('assets.passwordAccount'),
formatter: function(row) {
const [accountInfo] = row.effective_accounts.filter(
item => item.secret_type === 'password'
)
if (!accountInfo) return <span>-</span>
const to = {
name: 'AssetAccountDetail',
params: { id: accountInfo.id }
}
return <router-link to={ to } >{ accountInfo.username }</router-link>
}
},
ssh_key_account: {
label: this.$t('assets.sshkeyAccount'),
formatter: function(row) {
const [accountInfo] = row.effective_accounts.filter(
item => item.secret_type === 'ssh_key'
)
if (!accountInfo) return <span>-</span>
const to = {
name: 'AssetAccountDetail',
params: { id: accountInfo.id }
}
return <router-link to={ to } >{ accountInfo.username }</router-link>
}
},
actions: {
formatterArgs: {
updateRoute: 'GatewayUpdate',

View File

@@ -28,7 +28,7 @@ export default {
name: 'Detail'
},
{
title: this.$t('assets.Gateway'),
title: this.$t('assets.GatewayList'),
name: 'GatewayList',
hidden: () => !this.$hasPerm('assets.view_gateway')
}

View File

@@ -15,27 +15,32 @@ export default {
tableConfig: {
url: '/api/v1/assets/domains/',
columns: [
'name', 'asset_count', 'application_count', 'gateway_count', 'date_created',
'comment', 'org_name', 'actions'
'name', 'asset_count', 'gateway_count', 'comment',
'date_created', 'org_name', 'actions'
],
columnsShow: {
min: ['name', 'actions'],
default: ['name', 'asset_count', 'application_count', 'gateway_count', 'comment', 'actions']
default: ['name', 'asset_count', 'gateway_count', 'comment', 'actions']
},
columnsMeta: {
asset_count: {
label: this.$t('assets.Assets')
},
application_count: {
label: this.$t('assets.Applications')
prop: 'assets',
label: this.$t('assets.Assets'),
formatter: function(row) {
return <span> { row.assets.length } </span>
}
},
gateway_count: {
prop: 'gateways',
label: this.$t('assets.Gateway'),
formatter: DetailFormatter,
formatterArgs: {
permissions: 'assets.view_gateway',
routeQuery: {
activeTab: 'GatewayList'
},
getTitle: function({ cellValue }) {
return cellValue.length
}
}
}

View File

@@ -1,10 +1,24 @@
<template>
<el-row :gutter="16">
<el-col :lg="12" :sm="12" class="margin-top-16">
<DataCard :config="logConfig" />
<DataCard :config="logConfig">
<div class="custom">
<span>{{ logConfig.total }}</span>
<span>
<svg-icon :icon-class="logConfig.icon" class="font" />
</span>
</div>
</DataCard>
</el-col>
<el-col :lg="12" :sm="12" class="margin-top-16">
<DataCard :config="assetConfig" />
<DataCard :config="assetConfig">
<div class="custom">
<span>{{ assetConfig.total }}</span>
<span>
<svg-icon :icon-class="assetConfig.icon" class="font" />
</span>
</div>
</DataCard>
</el-col>
</el-row>
</template>
@@ -62,17 +76,22 @@ export default {
&total_count_commands=1
&total_count_commands_danger=1
`)
const logActive = data.total_count_user_login_success_logs === 0 ? 0 : ((data.total_count_user_login_success_logs / data.total_count_user_login_logs) * 100).toFixed(0)
const logTotal = logActive === 100 ? 0 : 100 - logActive
const logs = [
{ name: this.$t('dashboard.ActiveUser'), value: data.total_count_user_login_logs },
{ name: this.$t('dashboard.InActiveUser'), value: data.total_count_user_login_success_logs }
{ name: this.$t('dashboard.ActiveUser'), value: logActive },
{ name: this.$t('dashboard.InActiveUser'), value: logTotal }
]
this.$set(this.logConfig, 'data', logs)
this.$set(this.logConfig, 'total', data.total_count_user_login_logs)
this.$set(this.logConfig, 'active', data.total_count_user_login_success_logs)
this.$set(this.logConfig, 'weekAdd', data.total_count_user_login_success_logs)
const assetActive = data.total_count_commands_danger === 0 ? 0 : ((data.total_count_commands_danger / data.total_count_commands) * 100).toFixed(0)
const assetTotal = assetActive === 100 ? 0 : 100 - assetActive
const assets = [
{ name: this.$t('dashboard.ActiveAsset'), value: data.total_count_commands },
{ name: this.$t('dashboard.InActiveAsset'), value: data.total_count_commands_danger }
{ name: this.$t('dashboard.ActiveAsset'), value: assetActive },
{ name: this.$t('dashboard.InActiveAsset'), value: assetTotal }
]
this.$set(this.assetConfig, 'data', assets)
this.$set(this.assetConfig, 'total', data.total_count_commands)

View File

@@ -6,7 +6,7 @@
</div>
<LineChart v-bind="chartConfig" />
</div>
<SummaryCountCard :config="config" :items="summaryItems" class="margin-top-16" />
<SummaryCountCard :config="chartTitleConfig" :items="summaryItems" class="margin-top-16" />
</div>
</template>
@@ -33,6 +33,10 @@ export default {
title: this.$t('dashboard.SessionTrend'),
tip: this.$t('dashboard.SessionTrend')
},
chartTitleConfig: {
title: this.$t('route.BatchCommand'),
tip: this.$t('route.BatchCommand')
},
chartConfig: {
datesMetrics: [],
secondaryName: this.$t('dashboard.IndexName'),

View File

@@ -51,22 +51,27 @@ export default {
async init() {
const data = await this.$axios.get(`/api/v1/index/?total_count_users=1
&total_count_users_this_week=1
&total_count_today_login_users=1
&total_count_login_users=1
&total_count_assets=1
&total_count_assets_this_week=1
&total_count_today_active_assets=1
`)
const userActive = data.total_count_login_users === 0 ? 0 : ((data.total_count_login_users / data.total_count_users) * 100).toFixed(0)
const userTotal = userActive === 100 ? 0 : 100 - userActive
const users = [
{ name: this.$t('dashboard.ActiveUser'), value: data.total_count_users },
{ name: this.$t('dashboard.InActiveUser'), value: data.total_count_today_login_users }
{ name: this.$t('dashboard.ActiveUser'), value: userActive },
{ name: this.$t('dashboard.InActiveUser'), value: userTotal }
]
this.$set(this.userConfig, 'data', users)
this.$set(this.userConfig, 'total', data.total_count_users)
this.$set(this.userConfig, 'active', data.total_count_today_login_users)
this.$set(this.userConfig, 'active', data.total_count_login_users)
this.$set(this.userConfig, 'weekAdd', data.total_count_users_this_week)
const assetActive = data.total_count_today_active_assets === 0 ? 0 : ((data.total_count_today_active_assets / data.total_count_assets) * 100).toFixed(0)
const assetTotal = assetActive === 100 ? 0 : 100 - assetActive
const assets = [
{ name: this.$t('dashboard.ActiveAsset'), value: data.total_count_assets },
{ name: this.$t('dashboard.InActiveAsset'), value: data.total_count_today_active_assets }
{ name: this.$t('dashboard.ActiveAsset'), value: assetActive },
{ name: this.$t('dashboard.InActiveAsset'), value: assetTotal }
]
this.$set(this.assetConfig, 'data', assets)
this.$set(this.assetConfig, 'total', data.total_count_assets)

View File

@@ -1,10 +1,11 @@
<template>
<div class="card">
<div>
<div class="card-content">
<div class="title">
<Title :config="config" />
</div>
<div class="sub">{{ config.subTitle }}</div>
<slot class="custom">
<div class="num">{{ config.total }}</div>
<div class="add">
<span class="add-num">
@@ -15,6 +16,7 @@
<svg-icon v-if="config.icon" :icon-class="config.icon" class="font" />
</span>
</div>
</slot>
</div>
<div class="ring">
<RingChart :config="config" />
@@ -48,6 +50,10 @@ export default {
width: 100%;
padding: 20px;
background-color: #FFF;
.card-content {
padding-bottom: 16px;
border-bottom: 1px solid #EFF0F1;
}
.title {
margin-bottom: 12px;
}
@@ -67,8 +73,13 @@ export default {
.add {
display: flex;
justify-content: space-between;
padding-bottom: 16px;
border-bottom: 1px solid #EFF0F1;
}
.custom {
display: flex;
justify-content: space-between;
font-weight: 500;
font-size: 32px;
padding-bottom: 18px;
}
.ring {
padding: 26px 0 10px;

View File

@@ -64,7 +64,7 @@ export default {
color: [color, 'rgba(43, 147, 124, 0.05)'],
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
formatter: '{a} <br/>{b}: {d}%'
},
series: [
{

View File

@@ -59,7 +59,7 @@ export default {
value: row.name
},
{
key: this.$t('assets.Address'),
key: this.$t('assets.AssetAddress'),
value: row.address
},
{
@@ -76,7 +76,7 @@ export default {
},
{
key: this.$t('assets.Platform'),
value: row.platform
value: row.platform?.name || ''
},
{
key: this.$t('common.Active'),

View File

@@ -31,8 +31,9 @@ export default {
visible: false,
width: '60%',
tableConfig: {
hasSelection: false,
url: `/api/v1/ops/adhocs/`,
columns: ['name', 'module', 'args', 'actions'],
columns: ['name', 'module', 'args', 'comment', 'actions'],
columnsMeta: {
actions: {
formatter: ActionsFormatter,
@@ -42,7 +43,7 @@ export default {
hasDelete: false,
extraActions: [
{
title: '选择',
title: this.$tc('common.Select'),
name: 'select',
can: true,
callback: ({ row }) => {

View File

@@ -65,6 +65,10 @@ export default {
this.showOpenAdhocDialog = true
},
openAdhocSaveDialog() {
if (!this.iValue.length > 0) {
this.$message.error(this.$tc('ops.CommandNotBeNone'))
return
}
this.showOpenAdhocSaveDialog = true
},
onSelectAdhoc(adhoc) {

View File

@@ -86,6 +86,8 @@ export default {
required: false
}],
el: {
baseUrl: '/api/v1/perms/users/self/assets/',
baseNodeUrl: '/api/v1/perms/users/self/nodes/',
value: []
}
},

View File

@@ -4,6 +4,7 @@
<script type="text/jsx">
import ListTable from '@/components/ListTable'
import { openTaskPage } from '@/utils/jms'
export default {
name: 'TaskHistory',
@@ -21,13 +22,9 @@ export default {
tableConfig: {
url: `/api/v1/ops/task-executions/?task_id=${this.object.id}`,
columns: [
'date_start', 'date_finished', 'state', 'is_finished', 'actions'
'date_start', 'date_finished', 'is_success', 'is_finished', 'actions'
],
columnsMeta: {
state: {
formatter: (row) => {
}
},
actions: {
prop: 'id',
formatterArgs: {
@@ -38,10 +35,10 @@ export default {
extraActions: [
{
name: 'detail',
title: this.$t('ops.detail'),
title: this.$t('ops.output'),
type: 'primary',
callback: function({ row, tableData }) {
return this.$router.push({ name: 'HistoryExecutionDetail', params: { id: row.id }})
openTaskPage(row.id)
}
}
]