perf: 解决冲突

This commit is contained in:
jiangweidong
2023-02-13 11:11:49 +08:00
69 changed files with 771 additions and 207 deletions

View File

@@ -36,3 +36,11 @@ export function uploadPlaybook(form) {
data: form
})
}
export function renameFile(playbookId, node) {
return request({
url: `/api/v1/ops/playbook/${playbookId}/file/`,
method: 'patch',
data: node
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -1,6 +1,7 @@
<template>
<AutoDataForm
v-if="!loading"
ref="AutoDataForm"
v-bind="$data"
@submit="confirm"
/>
@@ -71,7 +72,9 @@ export default {
on: {
input: ([value], updateForm) => {
if (!this.usernameChanged) {
updateForm({ username: value })
if (!this.account?.name) {
updateForm({ username: value })
}
const maybePrivileged = this.defaultPrivilegedAccounts.includes(value)
if (maybePrivileged) {
updateForm({ privileged: true })
@@ -150,7 +153,8 @@ export default {
},
push_now: {
hidden: () => {
return !this.iPlatform.automation?.['push_account_enabled']
const automation = this.iPlatform.automation || {}
return !automation.push_account_enabled || !automation.ansible_enabled || !this.$hasPerm('assets.push_assetaccount')
}
}
},

View File

@@ -12,6 +12,7 @@
>
<AccountCreateUpdateForm
v-if="!loading"
ref="form"
:account="account"
:asset="asset"
@add="addAccount"
@@ -94,7 +95,7 @@ export default {
this.iVisible = false
this.$emit('add', true)
this.$message.success(this.$tc('common.createSuccessMsg'))
})
}).catch(error => this.setFieldError(error))
},
editAccount(form) {
const data = { ...form }
@@ -102,7 +103,35 @@ export default {
this.iVisible = false
this.$emit('add', true)
this.$message.success(this.$tc('common.updateSuccessMsg'))
})
}).catch(error => this.setFieldError(error))
},
setFieldError(error) {
const response = error.response
const data = response.data
const refsAutoDataForm = this.$refs.form.$refs.AutoDataForm
if (response.status === 400) {
for (const key of Object.keys(data)) {
let err = ''
let current = key
let errorTips = data[current]
if (errorTips instanceof Array) {
errorTips = _.filter(errorTips, (item) => Object.keys(item).length > 0)
for (const i of errorTips) {
if (i instanceof Object) {
err += i?.port?.join(',')
} else {
err += errorTips
}
}
} else {
err = errorTips
}
if (current === 'secret') {
current = refsAutoDataForm.form.secret_type?.value || key
}
refsAutoDataForm.setFieldError(current, err)
}
}
}
}
}

View File

@@ -146,7 +146,10 @@ export default {
width: '70px'
},
secret_type: {
width: '100px'
width: '100px',
formatter: function(row) {
return row.secret_type.label
}
},
source: {
formatter: function(row) {
@@ -246,6 +249,13 @@ export default {
hasCreate: false,
hasImport: this.hasImport,
hasExport: this.hasExport && this.$hasPerm('accounts.view_accountsecret'),
handleImportClick: ({ selectedRows }) => {
this.$eventBus.$emit('showImportDialog', {
selectedRows,
url: '/api/v1/accounts/accounts/',
name: this?.name
})
},
exportOptions: {
url: this.exportUrl,
mfaVerifyRequired: true

View File

@@ -39,7 +39,7 @@
<el-form-item :label="$tc('common.DateUpdated')">
<span>{{ account['date_updated'] | date }}</span>
</el-form-item>
<el-form-item :label="$tc('accounts.PasswordRecord')">
<el-form-item v-if="showPasswordRecord" :label="$tc('accounts.PasswordRecord')">
<el-button type="text" @click="onShowPasswordHistory">{{ secretInfo.version }}</el-button>
</el-form-item>
</el-form>
@@ -78,6 +78,10 @@ export default {
url: {
type: String,
default: ''
},
showPasswordRecord: {
type: Boolean,
default: true
}
},
data() {

View File

@@ -12,6 +12,7 @@
>
<AssetTreeTable
ref="ListPage"
v-bind="$attrs"
:header-actions="headerActions"
:table-config="tableConfig"
class="tree-table"
@@ -183,4 +184,7 @@ export default {
.page ::v-deep .treebox {
height: inherit !important;
}
.asset-select-dialog ::v-deep .el-icon-circle-check {
display: none;
}
</style>

View File

@@ -15,6 +15,7 @@
:visible.sync="dialogVisible"
v-bind="$attrs"
:base-url="baseUrl"
:tree-url-query="treeUrlQuery"
@cancel="handleCancel"
@confirm="handleConfirm"
v-on="$listeners"
@@ -34,6 +35,10 @@ export default {
type: String,
default: '/api/v1/assets/assets/'
},
treeUrlQuery: {
type: Object,
default: () => {}
},
value: {
type: Array,
default: () => []

View File

@@ -39,6 +39,10 @@ export default {
type: String,
default: '/api/v1/assets/nodes/children/tree/'
},
treeUrlQuery: {
type: Object,
default: () => ({})
},
treeSetting: {
type: Object,
default: () => ({})
@@ -54,6 +58,9 @@ export default {
},
data() {
const showAssets = this.treeSetting?.showAssets || this.showAssets
const treeUrlQuery = this.setTreeUrlQuery()
const assetTreeUrl = `${this.treeUrl}?assets=${showAssets ? '1' : '0'}&${treeUrlQuery}`
return {
treeTabConfig: {
activeMenu: 'CustomTree',
@@ -72,7 +79,7 @@ export default {
showSearch: true,
url: this.url,
nodeUrl: this.nodeUrl,
treeUrl: `${this.treeUrl}?assets=${showAssets ? '1' : '0'}`,
treeUrl: assetTreeUrl,
callback: {
onSelected: (event, treeNode) => this.getAssetsUrl(treeNode)
},
@@ -117,6 +124,15 @@ export default {
treeSetting.showDelete = this.$hasPerm('assets.delete_node')
},
methods: {
setTreeUrlQuery() {
let str = ''
for (const key in this.treeUrlQuery) {
str += `${key}=${this.treeUrlQuery[key]}&`
}
str = str.substr(0, str.length - 1)
return str
},
decorateRMenu() {
const show_current_asset = this.$cookie.get('show_current_asset') || '0'
if (show_current_asset === '1') {
@@ -145,6 +161,8 @@ export default {
} else if (treeNode.meta.type === 'platform') {
url = setUrlParam(url, 'platform', treeNode.id)
}
const query = this.setTreeUrlQuery()
url = query ? `${url}&${query}` : url
this.$set(this.tableConfig, 'url', url)
setRouterQuery(this, url)
}

View File

@@ -237,7 +237,8 @@ export default {
col.formatter = (row, column, cellValue) => {
let value = cellValue
let padding = '0'
if (!value && value !== 0) {
const excludes = [undefined, null, '']
if (excludes.indexOf(value) !== -1) {
padding = '6px'
value = '-'
}

View File

@@ -18,7 +18,7 @@ export const IpCheck = {
required: true,
validator: (rule, value, callback) => {
value = value?.trim()
if (/^[\w://.?]+$/.test(value)) {
if (/^[\w://.?-]+$/.test(value)) {
callback()
} else {
callback(new Error(i18n.t('common.FormatError')))

View File

@@ -59,6 +59,9 @@ export default {
if (!fieldMeta) {
continue
}
if (fieldMeta['write_only']) {
continue
}
let value = this.object[name]

View File

@@ -56,7 +56,7 @@ export default {
},
props: {
value: {
type: [Array],
type: [String, Array],
default: () => []
},
title: {
@@ -147,7 +147,7 @@ export default {
item.port = selected.port
},
setDefaultItems(choices) {
if (this.value.length > 0) {
if (this.value instanceof Array && this.value.length > 0) {
const protocols = []
this.value.forEach(item => {
// 有默认值的情况下设置为只读或者有id、有setting是平台

View File

@@ -1,11 +1,13 @@
<template>
<div>
<UserConfirmDialog
v-if="mfaDialogShow"
:url="url"
@UserConfirmDone="showExportDialog"
@UserConfirmCancel="handleExportCancel"
/>
<div v-if="mfaDialogShow">
<UserConfirmDialog
:url="url"
@UserConfirmDone="showExportDialog"
@UserConfirmCancel="handleExportCancel"
@AuthMFAError="handleAuthMFAError"
/>
</div>
<Dialog
v-if="exportDialogShow"
:title="$tc('common.Export')"
@@ -218,6 +220,9 @@ export default {
vm.exportDialogShow = false
vm.mfaDialogShow = false
}, 100)
},
handleAuthMFAError() {
this.mfaDialogShow = false
}
}
}

View File

@@ -50,13 +50,13 @@
</template>
<script>
import AutoDataZTree from '../AutoDataZTree'
import TabTree from '../TabTree'
import Dialog from '@/components/Dialog'
import ListTable from '../ListTable'
import IBox from '../IBox'
import { setUrlParam } from '@/utils/common'
import ListTable from '@/components/ListTable/index'
import FileTree from '@/components/TreeTable/components/FileTree.vue'
import IBox from '../IBox'
import TabTree from '../TabTree'
import AutoDataZTree from '../AutoDataZTree'
export default {
name: 'TreeTable',

View File

@@ -165,6 +165,8 @@ export default {
}
this.title = this.$t('common.CurrentUserVerify')
this.visible = true
}).catch(() => {
this.$emit('AuthMFAError', true)
})
})
},

View File

@@ -130,7 +130,8 @@
"users": "User",
"Rules": "Rule",
"Action": "Action",
"ip_group_help_text": "The format is a comma-separated string, * means match all. Example: 192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64"
"ip_group_help_text": "The format is a comma-separated string, * means match all. Example: 192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64",
"apply_login_account": "Apply login account"
},
"applications": {
"": "",
@@ -197,6 +198,7 @@
},
"assets": {
"Secure": "Secure",
"AssetBulkUpdateTips": "device、cloud、webBatch update of domain is not supported",
"LabelInputFormatValidation": "Format error, correct format isname:value",
"IP/Host": "IP/Host",
"OrganizationAsset": "Organization asset",
@@ -429,6 +431,8 @@
"ReLoginErr": "Login time has exceeded 5 minutes, please login again"
},
"common": {
"About": "About",
"PermissionCompany": "Permission company",
"ApproverNumbers": "Approver numbers",
"ConvenientOperate": "Convenient operate",
"Overview": "Overview",
@@ -588,7 +592,7 @@
"bulkRemoveErrorMsg": "Bulk remove failed: ",
"bulkRemoveSuccessMsg": "Bulk remove success",
"SelectAtLeastOneAssetOrNodeErrMsg": "Select at least one asset or node",
"RequiredSystemUserErrMsg": "Required systemuser",
"RequiredSystemUserErrMsg": "Required account",
"createBy": "Create by",
"cloneFrom": "Clone from",
"createErrorMsg": "Create error",
@@ -819,6 +823,7 @@
"Weekly": "Weekly"
},
"ops": {
"RunAgain": "Run again",
"ManualInput": "Manual input",
"Execute": "Execute",
"ID": "ID",
@@ -1555,6 +1560,10 @@
"basicTools": "Basic tool"
},
"tickets": {
"ApplyAsset": "Apply asset",
"LoginConfirm": "User login confirm",
"CommandConfirm": "Command confirm",
"LoginAssetConfirm": "Asset login confirm",
"PermissionName": "Permission name",
"Accept": "Accept",
"AssignedMe": "Assigned me",
@@ -1996,13 +2005,13 @@
"favicon": "Website icon",
"faviconTip": "Tips: website icon. (suggest image size: 16px*16px)",
"loginImage": "Image of login page",
"loginImageTip": "Tips: This will be displayed on the enterprise user login page. (suggest image size: 492px*472px)",
"loginImageTip": "Tips: It will be displayed on the enterprise version user login page (recommended image size: 492*472px)",
"loginTitle": "Title of login page",
"loginTitleTip": "Tips: This will be displayed on the enterprise user login page. (eg: Welcome to the JumpServer open source fortress)",
"logoIndex": "Logo (It contains text)",
"logoIndexTip": "Tips: This will appear at the top left of the administration page. (suggest image size: 185px*55px)",
"logoLogout": "Logo (It contains no text)",
"logoLogoutTip": "Tips: This will be displayed on the enterprise user logout page. (suggest image size: 82px*82px)",
"logoLogoutTip": "Tips: It will be displayed on the web terminal page of enterprise users (recommended image size: 82px*82px)",
"restoreDialogMessage": "This will restore default Settings of the interface !!!",
"restoreDialogTitle": "Are you sure?",
"technologyConsult": "Technology Consult",

View File

@@ -130,7 +130,8 @@
"apply_login_system_user": "システムユーザーへのログイン申請",
"apply_login_user": "ログインユーザーの申請",
"login_confirm_user": "登録再確認受付者",
"RuleDetail": "ルールの詳細"
"RuleDetail": "ルールの詳細",
"apply_login_account": "ログインアカウントの申請"
},
"applications": {
"": "",
@@ -197,6 +198,7 @@
},
"assets": {
"Secure": "安全である",
"AssetBulkUpdateTips": "ネットワークデバイス、クラウドサービス、Web、一括更新ネットワークドメインはサポートされていません",
"LabelInputFormatValidation": "フォーマットが正しくありませんname:value",
"IP/Host": "IP/ノア",
"OrganizationAsset": "組織資産",
@@ -429,6 +431,8 @@
"ReLoginErr": "ログイン時間が 5 分を超えました。もう一度ログインしてください"
},
"common": {
"About": "について",
"PermissionCompany": "授权公司",
"ApproverNumbers": "承認者数",
"ConvenientOperate": "便利な操作",
"Overview": "概要",
@@ -588,7 +592,7 @@
"bulkDeleteSuccessMsg": "一括削除に成功しました",
"bulkRemoveErrorMsg": "一括削除に失敗しました:",
"SelectAtLeastOneAssetOrNodeErrMsg": "資産またはードは少なくとも1つを選択します",
"RequiredSystemUserErrMsg": "先にシステムユーザーを選択してください",
"RequiredSystemUserErrMsg": "必要なアカウント",
"bulkRemoveSuccessMsg": "一括削除に成功しました",
"createBy": "作成者",
"cloneFrom": "クローン",
@@ -818,6 +822,7 @@
"Weekly": "週ごと"
},
"ops": {
"RunAgain": "再実行",
"ManualInput": "手動入力",
"Execute": "実行",
"ID": "ID",
@@ -1547,6 +1552,10 @@
"Applets": "リモート アプリケーション"
},
"tickets": {
"ApplyAsset": "リソースの適用",
"LoginConfirm": "ユーザーログインの確認",
"CommandConfirm": "コマンドの確認",
"LoginAssetConfirm": "資産ログインの確認",
"OneAssigneeType": "一次受付者タイプ",
"OneAssignee": "一級受付者",
"TwoAssigneeType": "二級受付者タイプ",
@@ -1991,13 +2000,13 @@
"favicon": "Webサイトのアイコン",
"faviconTip": "ヒント: webサイトのアイコン (推奨画像サイズ: 16px * 16px)",
"loginImage": "ログインページの画像",
"loginImageTip": "ヒント: 企業版ユーザーログインページに表示されます (推奨画像サイズ: 492 * 472px)",
"loginImageTip": "ヒント: エンタープライズ版のユーザーログインページに表示されます (推奨画像サイズ: 492*472px)",
"loginTitle": "ログインページのタイトル",
"loginTitleTip": "ヒント: 企業版ユーザーログインページに表示されます",
"logoIndex": "ロゴ (文字付き)",
"logoIndexTip": "ヒント: 管理ページの左上に表示されます (推奨画像サイズ: 185px * 55px)",
"logoLogout": "ロゴ (文字なし)",
"logoLogoutTip": "ヒント: 企業版ユーザーの終了ページに表示されます (推奨画像サイズ: 82px * 82px)",
"logoLogoutTip": "ヒント: エンタープライズ ユーザーの Web 端末ページに表示されます (推奨画像サイズ: 82px*82px)",
"restoreDialogMessage": "本当にデフォルトの初期化を再開しますか?",
"restoreDialogTitle": "確認しますか",
"technologyConsult": "技術コンサルティング",

View File

@@ -97,6 +97,7 @@
}
},
"acl": {
"apply_login_account": "申请登录账号",
"IgnoreCase": "忽略大小写",
"Content": "内容",
"CommandFilterACL": "命令过滤",
@@ -197,6 +198,7 @@
},
"assets": {
"Secure": "安全",
"AssetBulkUpdateTips": "网络设备、云服务、web不支持批量更新网域",
"LabelInputFormatValidation": "标签格式错误正确格式为name:value",
"GatewayList": "网关列表",
"AccessKey": "访问密钥",
@@ -425,6 +427,8 @@
"ReLoginErr": "登录时长已超过 5 分钟,请重新登录"
},
"common": {
"About": "关于",
"PermissionCompany": "授权公司",
"Filename": "文件名",
"ApproverNumbers": "审批人数量",
"ConvenientOperate": "便捷操作",
@@ -600,7 +604,7 @@
"bulkDeleteSuccessMsg": "批量删除成功",
"bulkRemoveErrorMsg": "批量移除失败: ",
"SelectAtLeastOneAssetOrNodeErrMsg": "资产或者节点至少选择一项",
"RequiredSystemUserErrMsg": "请选择系统用户",
"RequiredSystemUserErrMsg": "请选择账号",
"bulkRemoveSuccessMsg": "批量移除成功",
"createBy": "创建者",
"cloneFrom": "克隆自",
@@ -814,6 +818,7 @@
"Weekly": "按周"
},
"ops": {
"RunAgain": "再次执行",
"AdhocUpdate": "更新命令",
"Add": "新增",
"Modify": "修改",
@@ -1541,6 +1546,10 @@
"testHelpText": "请输入目的地址进行测试"
},
"tickets": {
"ApplyAsset": "申请资产",
"LoginConfirm": "用户登录复合",
"CommandConfirm": "命令复合",
"LoginAssetConfirm": "资产登录复合",
"RelevantAssignees": "相关受理人",
"OneAssigneeType": "一级受理人类型",
"OneAssignee": "一级受理人",
@@ -1905,11 +1914,11 @@
"loginImage": "登录页面图片",
"loginImageTip": "提示:将会显示在企业版用户登录页面(建议图片大小为: 492*472px",
"loginTitle": "登录页面标题",
"loginTitleTip": "提示将会显示在企业版用户登录页面eg: 欢迎使用JumpServer开源堡垒机)",
"loginTitleTip": "提示:将会显示在企业版用户 SSH 登录 KoKo 登录页面eg: 欢迎使用JumpServer开源堡垒机)",
"logoIndex": "Logo (带文字)",
"logoIndexTip": "提示:将会显示在管理页面左上方(建议图片大小为: 185px*55px",
"logoLogout": "Logo (不带文字)",
"logoLogoutTip": "提示:将会显示在企业版用户退出页面建议图片大小为82px*82px",
"logoLogoutTip": "提示:将会显示在企业版用户的 Web 终端页面建议图片大小为82px*82px",
"restoreDialogMessage": "您确定要恢复默认初始化吗?",
"restoreDialogTitle": "你确认吗",
"technologyConsult": "技术咨询",

View File

@@ -7,6 +7,9 @@
top="1vh"
width="70%"
>
<el-alert v-if="tips" class="tips" type="success">
{{ tips }}
</el-alert>
<el-row :gutter="20">
<el-col :md="4" :sm="24">
<div class="select-prop-label">
@@ -56,6 +59,10 @@ export default {
type: Object,
default: () => ({})
},
tips: {
type: String,
default: ''
},
visible: {
type: Boolean,
default: false
@@ -152,4 +159,7 @@ export default {
padding-right: 30px;
}
.tips {
margin-bottom: 10px;
}
</style>

View File

@@ -0,0 +1,124 @@
<template>
<Dialog
v-if="iVisible"
:title="''"
class="about-dialog"
:visible.sync="iVisible"
width="50%"
top="10%"
:show-cancel="false"
:show-confirm="false"
>
<div class="box">
<div class="head">
<img :src="logoTextSrc" class="sidebar-logo-text" alt="logo">
</div>
<div class="text">{{ $tc('ops.version') }}<strong> dev </strong> <span v-if="!publicSettings.XPACK_LICENSE_IS_VALID"> GPLv3. </span></div>
<div class="text">{{ $tc('common.PermissionCompany') }}{{ corporation }}</div>
<el-divider class="divider" />
<div class="text">
<span v-for="(i, index) in actions" :key="index" class="text-link" @click="onClick(i.name)">
<i class="icon" :class="i.icon" />{{ i.label }}
<el-divider v-if="index !== actions.length - 1" direction="vertical" />
</span>
</div>
</div>
</Dialog>
</template>
<script>
import Dialog from '@/components/Dialog'
import { mapGetters } from 'vuex'
export default {
components: {
Dialog
},
props: {
visible: {
type: Boolean,
default: false
}
},
data() {
return {
logoTextSrc: require('@/assets/img/logo_text_green.png'),
actions: [
{
name: 'github',
label: 'github',
icon: 'fa fa-github'
},
{
name: 'download',
label: this.$tc('common.Download'),
icon: 'fa fa-download'
}
]
}
},
computed: {
...mapGetters([
'publicSettings'
]),
iVisible: {
set(val) {
this.$emit('update:visible', val)
},
get() {
return this.visible
}
},
corporation() {
return this.publicSettings.XPACK_LICENSE_INFO.corporation
}
},
methods: {
onClick(type) {
switch (type) {
case 'download':
window.open('/core/download/', '_blank')
break
case 'github':
window.open('https://github.com/jumpserver/jumpserver', '_blank')
break
}
}
}
}
</script>
<style lang="scss" scoped>
.about-dialog {
&>>> .el-dialog__header {
background-color: #FAFBFD;
border-bottom: none;
}
&>>> .el-dialog__body {
background-color: #FAFBFD;
padding: 10px 40px 20px;
}
&>>> .el-dialog__footer {
padding: 0;
}
}
.head {
text-align: center;
}
.box {
.text {
margin-bottom: 10px;
font-size: 14px;
color: #666;
.icon {
margin-right: 4px;
}
span {
cursor: pointer;
}
}
}
>>> .divider.el-divider {
margin: 15px 0!important;
}
</style>

View File

@@ -1,23 +1,31 @@
<template>
<el-dropdown :show-timeout="50" @command="handleCommand">
<span class="el-dropdown-link" style="vertical-align: middle;">
<svg-icon icon-class="question-mark" style="font-size: 16px;" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="docs">{{ $t('common.nav.Docs') }}</el-dropdown-item>
<el-dropdown-item command="support">{{ $t('common.nav.Support') }}</el-dropdown-item>
<el-dropdown-item command="toolsDownload">{{ $t('common.nav.Download') }}</el-dropdown-item>
<el-dropdown-item v-if="!hasLicence" command="enterprise">{{ $t('common.nav.EnterpriseEdition') }}</el-dropdown-item>
<el-dropdown-item command="github">GITHUB</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<div>
<el-dropdown :show-timeout="50" @command="handleCommand">
<span class="el-dropdown-link" style="vertical-align: middle;">
<svg-icon icon-class="question-mark" style="font-size: 16px;" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="docs">{{ $t('common.nav.Docs') }}</el-dropdown-item>
<el-dropdown-item command="support">{{ $t('common.nav.Support') }}</el-dropdown-item>
<el-dropdown-item v-if="!hasLicence" command="enterprise">{{ $t('common.nav.EnterpriseEdition') }}</el-dropdown-item>
<el-dropdown-item command="about">{{ $tc('common.About') }}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<About :visible.sync="visible" />
</div>
</template>
<script>
import About from './About.vue'
export default {
name: 'Help',
components: {
About
},
data() {
return {
visible: false,
URLSite: {
HELP_DOCUMENT_URL: '',
HELP_SUPPORT_URL: ''
@@ -45,11 +53,8 @@ export default {
case 'enterprise':
window.open('https://jumpserver.org/enterprise.html', '_blank')
break
case 'toolsDownload':
window.open('/core/download/', '_blank')
break
case 'github':
window.open('https://github.com/jumpserver/jumpserver', '_blank')
case 'about':
this.visible = true
break
default:
window.open(this.URLSite.HELP_DOCUMENT_URL, '_blank')

View File

@@ -166,7 +166,7 @@ export default [
name: 'AccountGatherList',
meta: {
title: i18n.t('accounts.AccountGather.AccountGatherTaskList'),
permissions: ['accounts.view_gatheraccountsautomation']
permissions: ['accounts.view_gatheredaccount']
}
},
{

View File

@@ -0,0 +1,107 @@
<template>
<TreeTable :table-config="tableConfig" :tree-setting="treeSetting" :header-actions="headerActions" />
</template>
<script>
import TreeTable from '@/components/TreeTable'
import { toSafeLocalDateStr } from '@/utils/common'
import { ActionsFormatter } from '@/components/TableFormatters'
export default {
components: {
TreeTable
},
data() {
const vm = this
return {
treeSetting: {
showMenu: false,
showRefresh: true,
showAssets: true,
url: '/api/v1/accounts/gathered-accounts/',
nodeUrl: '/api/v1/assets/nodes/',
// ?assets=0不显示资产. =1显示资产
treeUrl: '/api/v1/assets/nodes/children/tree/?assets=1'
},
tableConfig: {
url: '/api/v1/accounts/gathered-accounts/',
hasTree: true,
columns: [
'asset', 'username', 'date_last_login', 'present', 'address_last_login', 'date_updated'
],
columnsMeta: {
asset: {
label: vm.$t('assets.Asset'),
formatter: function(row) {
const to = {
name: 'AssetDetail',
params: { id: row.asset.id }
}
if (vm.$hasPerm('assets.view_asset')) {
return <router-link to={to}>{row.asset.name}</router-link>
} else {
return <span>{row.asset.name}</span>
}
}
},
username: {
showOverflowTooltip: true
},
present: {
width: 80
},
address_last_login: {
width: 120
},
date_updated: {
formatter: function(row, col, cell) {
return toSafeLocalDateStr(row.date_updated)
}
},
actions: {
formatter: ActionsFormatter,
formatterArgs: {
hasClone: false,
hasUpdate: false, // can set function(row, value)
hasDelete: false, // can set function(row, value)
moreActionsTitle: this.$t('common.More'),
extraActions: [
{
name: 'View',
title: this.$t('common.Sync'),
can: this.$hasPerm('accounts.add_gatheredaccount'),
type: 'primary',
callback: ({ row }) => {
console.log('row', row.id)
this.$axios.post(
`/api/v1/accounts/gathered-accounts/${row.id}/sync/`,
).then(res => {
this.$message.success(this.$tc('common.updateSuccessMsg'))
}).catch(res => {
})
}
}
]
}
}
}
},
headerActions: {
hasLeftActions: false,
hasCreate: false,
hasImport: false,
hasExport: false,
searchConfig: {
exclude: ['asset'],
options: [
]
}
}
}
}
}
</script>
<style>
</style>

View File

@@ -16,6 +16,12 @@ export default {
submenu: [
{
title: this.$t('accounts.AccountGather.AccountGatherTaskList'),
name: 'AccountGatherList',
hidden: !this.$hasPerm('accounts.view_gatheredaccount'),
component: () => import('@/views/accounts/AccountGather/AccountGatherList.vue')
},
{
title: this.$t('accounts.AccountGather.AccountGatherList'),
name: 'AccountGatherTaskList',
hidden: !this.$hasPerm('accounts.view_gatheraccountsautomation'),
component: () => import('@/views/accounts/AccountGather/AccountGatherTaskList.vue')

View File

@@ -50,7 +50,7 @@ export default {
}
],
url: `/api/v1/accounts/account-templates/${this.object.id}/`,
excludes: ['privileged', 'secret', 'passphrase', 'specific']
excludes: ['privileged', 'secret', 'passphrase', 'spec_info']
}
},
computed: {

View File

@@ -1,7 +1,13 @@
<template>
<div>
<GenericListPage :table-config="tableConfig" :header-actions="headerActions" />
<ViewSecret v-if="showViewSecretDialog" :visible.sync="showViewSecretDialog" :account="account" :url="secretUrl" />
<ViewSecret
v-if="showViewSecretDialog"
:visible.sync="showViewSecretDialog"
:url="secretUrl"
:account="account"
:show-password-record="false"
/>
</div>
</template>

View File

@@ -32,6 +32,24 @@ export default {
return {
quickActions: [
{
title: this.$t('common.Activate'),
type: 'switcher',
attrs: {
model: vm.object.is_active,
disabled: !vm.$hasPerm('accounts.change_account')
},
callbacks: Object.freeze({
change: (val) => {
this.$axios.patch(
`/api/v1/accounts/accounts/${this.object.id}/`,
{ is_active: val }
).then(res => {
this.$message.success(this.$tc('common.updateSuccessMsg'))
})
}
})
},
{
title: this.$t('assets.Privileged'),
type: 'switcher',
@@ -79,7 +97,7 @@ export default {
model: vm.object.su_from?.id || '',
label: vm.object.su_from?.name ? vm.object.su_from?.name + `(${vm.object.su_from?.username})` : '',
ajax: {
url: `/api/v1/accounts/accounts/${vm.object.id}/su-from-accounts/?fields_size=mini`,
url: `/api/v1/accounts/accounts/su-from-accounts/?account=${vm.object.id}&fields_size=mini`,
transformOption: (item) => {
return { label: item.name + '(' + item.username + ')', value: item.id }
}

View File

@@ -61,24 +61,11 @@ export default {
cleanFormValue(values) {
// Update 的时候
const { id = '' } = this.$route.params
const accounts = values?.accounts
const query = this.$route.query || {}
if (id) delete values['accounts']
if (values.nodes && values.nodes.length === 0) {
delete values['nodes']
}
if (accounts && accounts.length !== 0) {
accounts.forEach(i => {
if (i.hasOwnProperty('id')) {
// 克隆资产时 template 为 false
i.template = !query.hasOwnProperty('clone_from')
}
return i
})
}
console.log('values[\'accounts\']', values['accounts'])
return values
}
}

View File

@@ -10,7 +10,24 @@ export default {
components: { BaseAssetCreateUpdate },
data() {
return {
url: '/api/v1/assets/clouds/'
url: '/api/v1/assets/clouds/',
addFieldsMeta: {
protocols: {
hidden: (formValue) => {
const address = formValue['address']
if (!address) return
let port = address.startsWith('https://') ? 443 : 80
try {
const url = new URL(address)
if (url.port) { port = url.port }
} catch (e) {
// pass
}
const protocols = formValue['protocols']?.[0] || {}
protocols.port = port
}
}
}
}
}
}

View File

@@ -21,18 +21,18 @@ export default {
getAddFields() {
const platform = this.$route.query.platform_type
const baseFields = [[this.$t('common.Basic'), ['db_name']]]
let tlsParams = ['use_ssl', 'ca_cert']
let tlsFields = ['use_ssl', 'ca_cert']
switch (platform) {
case 'redis':
tlsParams = tlsParams.concat(['client_cert', 'client_key'])
tlsFields = tlsFields.concat(['client_cert', 'client_key'])
break
case 'mongodb':
tlsParams = tlsParams.concat(['client_key', 'allow_invalid_cert'])
tlsFields = tlsFields.concat(['client_key', 'allow_invalid_cert'])
break
}
if (tlsParams.length > 2) {
if (tlsFields.length > 2) {
const secureField = [
this.$t('assets.Secure'), tlsParams, 3
this.$t('assets.Secure'), tlsFields, 2
]
baseFields.push(secureField)
}

View File

@@ -132,9 +132,14 @@ export default {
handleConfirm() {
this.iVisible = false
// 过滤掉添加里还没有id的账号
const hasIdAccounts = this.accounts.filter(i => i?.id)
const data = _.xorBy(hasIdAccounts, this.accountsSelected, 'id')
this.accounts.push(...data)
const hasIdAccounts = this.accounts.filter(i => i?.id).map(item => item.id)
const newAddAccounts = this.accountsSelected.filter(i => {
if (!hasIdAccounts.includes(i.id)) {
i.template = true
return i
}
})
this.accounts.push(...newAddAccounts)
this.$emit('onConfirm', this.accounts)
},
handleCancel() {

View File

@@ -40,6 +40,10 @@ export default {
formConfig: {
initial: { secret_type: 'password' },
url: '/api/v1/accounts/account-templates/',
getUrl: function() {
return '/api/v1/accounts/account-templates/'
},
needGetObjectDetail: false,
hasDetailInMsg: false,
fields: [
...templateFields(this)

View File

@@ -3,6 +3,7 @@
v-if="visible"
:form-setting="formSetting"
:selected-rows="selectedRows"
:tips="tips"
:visible="visible"
v-on="$listeners"
/>
@@ -30,6 +31,7 @@ export default {
data() {
const meta = assetFieldsMeta(this)
return {
tips: this.$t('assets.AssetBulkUpdateTips'),
formSetting: {
url: '/api/v1/assets/assets/',
hasSaveContinue: false,

View File

@@ -69,6 +69,7 @@ export default {
}
if (action === 'Clone') {
route.query.clone_from = row.id
route.query.platform = row.platform.id
} else if (action === 'Update') {
route.params.id = row.id
route.query.platform = row.platform.id

View File

@@ -7,7 +7,7 @@
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import { Required, specialEmojiCheck } from '@/components/DataForm/rules'
import { RequiredChange, specialEmojiCheck } from '@/components/DataForm/rules'
import { ACCOUNT_PROVIDER_ATTRS_MAP, aliyun } from '../const'
import { UploadKey } from '@/components'
import { encryptPassword } from '@/utils/crypto'
@@ -25,7 +25,7 @@ export default {
const updateNotRequiredFields = ['access_key_secret', 'client_secret', 'password', 'sc_password', 'oc_password', 'cert_file', 'key_file']
for (const item of accountProviderAttrs?.attrs) {
fieldsObject[item] = {
rules: updateNotRequiredFields.includes(item) && vm.$route.params.id ? [] : [Required]
rules: updateNotRequiredFields.includes(item) && vm.$route.params.id ? [] : [RequiredChange]
}
}
return fieldsObject
@@ -46,7 +46,7 @@ export default {
],
fieldsMeta: {
name: {
rules: [Required, specialEmojiCheck]
rules: [RequiredChange, specialEmojiCheck]
},
attrs: {
encryptedFields: ['access_key_secret'],
@@ -76,12 +76,12 @@ export default {
}
},
password: {
rules: this.$route.params.id ? [] : [Required]
rules: this.$route.params.id ? [] : [RequiredChange]
}
}
},
provider: {
rules: [Required],
rules: [RequiredChange],
el: {
disabled: true
}

View File

@@ -119,13 +119,24 @@ export default {
updateSuccessNextRoute: { name: 'CloudCenter' },
createSuccessNextRoute: { name: 'CloudCenter' },
afterGetFormValue(formValue) {
formValue.protocols = formValue.protocols?.split(' ').map(i => {
const [name, port] = i.split('/')
return { name, port }
})
formValue.ip_network_segment_group = formValue.ip_network_segment_group.toString()
return formValue
},
cleanFormValue(value) {
if (!Array.isArray(value.ip_network_segment_group)) {
value.ip_network_segment_group = value.ip_network_segment_group ? value.ip_network_segment_group.split(',') : []
let protocols = ''
const ipNetworkSegments = value.ip_network_segment_group
if (!Array.isArray(ipNetworkSegments)) {
value.ip_network_segment_group = ipNetworkSegments ? ipNetworkSegments.split(',') : []
}
if (value.protocols.length > 0) {
protocols = value.protocols.map(i => (i.name + '/' + i.port)).join(' ')
}
value.protocols = protocols
return value
},
onPerformError(error, method, vm) {

View File

@@ -58,6 +58,10 @@ export default {
prop: 'date_sync',
label: this.$t('xpack.Cloud.DateSync'),
formatter: DateFormatter
},
{
prop: 'actions',
has: false
}
]
}

View File

@@ -54,8 +54,8 @@ export default {
formatter: DateFormatter
},
{
prop: 'id',
label: this.$t('common.Action'),
prop: 'actions',
label: this.$t('common.Actions'),
align: 'center',
formatter: ActionsFormatter,
formatterArgs: {

View File

@@ -52,20 +52,9 @@ export default {
url: `/api/v1/xpack/cloud/accounts/${this.object.id}`,
detailFields: [
'name', 'account_display', 'node_display',
{
key: this.$t('xpack.Cloud.LinuxAdminUser'),
value: this.object.unix_admin_user?.name
},
{
key: this.$t('xpack.Cloud.WindowsAdminUser'),
value: this.object.windows_admin_user?.name
},
{
key: this.$t('assets.Protocols'),
value: this.object.protocols,
formatter: (item, val) => {
return <div>{val.map((v) => <el-tag size='small'>{v['name']}/{v['port']}</el-tag>)}</div>
}
value: this.object.protocols
},
{
key: this.$t('xpack.Cloud.IPNetworkSegment'),
@@ -85,7 +74,7 @@ export default {
value: this.object.regions,
formatter(row, value) {
return (<div>{
value.map((content) => {
value?.map((content) => {
return <div>{ content }</div>
})}
</div>)

View File

@@ -21,15 +21,27 @@ export default {
app: 'xpack',
resource: 'syncinstancetask'
},
columnsShow: {
min: ['name', 'account', 'hostname_strategy', 'actions'],
default: [
'name', 'account', 'hostname_strategy', 'protocols', 'is_periodic', 'actions'
]
},
columnsMeta: {
sync_ip_type: {
width: '120px'
},
hostname_strategy: {
width: '120px'
width: '150px',
formatter: function(row) {
return <span>{ row.hostname_strategy.label }</span>
}
},
account_display: {
label: this.$t('xpack.Cloud.Account')
account: {
label: this.$t('xpack.Cloud.Account'),
formatter: function(row) {
return <span>{ row.account.name }</span>
}
},
periodic_display: {
width: '150px'

View File

@@ -1,5 +1,5 @@
<template>
<TabPage :active-menu.sync="config.activeMenu" :submenu="config.submenu" />
<TabPage :active-menu.sync="config.activeMenu" v-bind="config" />
</template>
<script>
@@ -27,7 +27,10 @@ export default {
hidden: () => !this.$hasPerm('xpack.view_account'),
component: () => import('@/views/assets/Cloud/Account/AccountList.vue')
}
]
],
actions: {
deleteSuccessRoute: 'AssetList'
}
}
}
}

View File

@@ -24,7 +24,13 @@ export default {
label: this.$t('assets.Assets'),
el: {
value: [],
baseUrl: '/api/v1/assets/assets/?domain_enabled=true'
baseUrl: '/api/v1/assets/assets/?domain_enabled=true',
treeUrlQuery: {
domain_enabled: true
},
canSelect: (row) => {
return row.platform?.name !== 'Gateway'
}
}
}
},

View File

@@ -5,6 +5,7 @@
:fields="fields"
:initial="initial"
:fields-meta="fieldsMeta"
:has-detail-in-msg="false"
:clean-form-value="cleanFormValue"
:after-get-form-value="afterGetFormValue"
:after-get-remote-meta="handleAfterGetRemoteMeta"

View File

@@ -42,23 +42,27 @@ export default {
options() {
const seriesList = []
const labels = this.data.map(item => item.label)
for (let i = 0; i < this.data.length; i++) {
const total = _.sumBy(this.data, function(i) { return i.total })
for (let i = 0, len = this.data.length; i < len; i++) {
const current = this.data[i]
let num = (current.total / total) * 100
num = _.floor(num, 2)
const color = '#' + Math.floor(Math.random() * (256 * 256 * 256 - 1)).toString(16)
seriesList.push({
type: 'bar',
stack: 'total',
barWidth: 32,
name: current.label,
itemStyle: {
borderRadius: 0
},
data: [{
value: current.total,
itemStyle: {
color: this.colors[i]
borderRadius: 0,
color: () => {
return this.colors[i] || color
}
}],
color: this.colors[i]
},
data: [num],
color: () => {
return this.colors[i] || color
}
})
}
return {
@@ -69,8 +73,9 @@ export default {
itemHeight: 10,
textStyle: {
color: '#000',
lineHeight: 30
lineHeight: 10
},
bottom: 30,
data: labels
},
@@ -126,11 +131,11 @@ export default {
}
},
grid: {
top: '0%',
top: '60%',
containLabel: true,
bottom: '-50%',
bottom: '-10',
left: '0%',
right: '0%'
right: 1
},
series: seriesList,
xAxis: {

View File

@@ -13,6 +13,7 @@ import CodeEditor from '@/components/FormFields/CodeEditor'
import { CronTab } from '@/components'
import i18n from '@/i18n/i18n'
import VariableHelpDialog from '@/views/ops/Job/VariableHelpDialog'
import { Required } from '@/components/DataForm/rules'
export default {
components: {
@@ -46,6 +47,7 @@ export default {
},
fieldsMeta: {
name: {
rules: [Required],
hidden: (formValue) => {
return this.instantTask
}
@@ -77,6 +79,7 @@ export default {
}
},
playbook: {
rules: [Required],
hidden: (formValue) => {
return formValue.type !== 'playbook'
},
@@ -95,9 +98,7 @@ export default {
type: 'assetSelect',
component: AssetSelect,
label: this.$t('perms.Asset'),
rules: [{
required: false
}],
rules: [Required],
el: {
baseUrl: '/api/v1/perms/users/self/assets/',
baseNodeUrl: '/api/v1/perms/users/self/nodes/',
@@ -105,6 +106,7 @@ export default {
}
},
args: {
rules: [Required],
hidden: (formValue) => {
return formValue.type !== 'adhoc'
},

View File

@@ -48,10 +48,7 @@ export default {
}
},
comment: {
width: '240px',
formatter: (row) => {
return row.type.label
}
width: '240px'
},
summary: {
label: this.$t('ops.Summary(success/total)'),

View File

@@ -5,7 +5,7 @@
:visible.sync="iVisible"
width="20%"
top="1vh"
:show-cancel="true"
:show-cancel="false"
:show-confirm="true"
@confirm="onConfirm"
>

View File

@@ -45,6 +45,7 @@ import { TreeTable } from '@/components'
import CodeEditor from '@/components/FormFields/CodeEditor'
import item from '@/layout/components/NavLeft/Item'
import NewNodeDialog from '@/views/ops/Template/Playbook/PlaybookDetail/Editor/NewNodeDialog.vue'
import { renameFile } from '@/api/ops'
export default {
name: 'CommandExecution',
@@ -113,16 +114,17 @@ export default {
}
}.bind(this),
refresh: function(event, treeNode) {
// const parentNode = this.zTree.getNodeByParam('id', this.parentId)
// this.zTree.expandNode(parentNode, true, false, false, false)
},
onRename: function(event, treeId, treeNode, isCancel) {
const url = `/api/v1/ops/playbook/${this.object.id}/file/`
if (isCancel) {
return
}
this.$axios.patch(url, { key: treeNode.id, new_name: treeNode.name, is_directory: treeNode.isParent })
.then(data => {
renameFile(this.object.id, {
key: treeNode.id,
new_name: treeNode.name,
is_directory: treeNode.isParent
}).then()
.finally(() => {
this.refreshTree()
})
}.bind(this)

View File

@@ -79,7 +79,7 @@ export default {
this.$emit('completed')
this.$message.success('terminal.UploadSucceed')
}).catch(err => {
console.log(err)
this.$message.error(err)
})
}
}

View File

@@ -44,7 +44,7 @@ export default {
min: ['name', 'actions'],
default: [
'name', 'users', 'user_groups', 'assets',
'nodes', 'accounts', 'is_valid'
'nodes', 'accounts', 'is_valid', 'actions'
]
},
columnsMeta: {

View File

@@ -73,6 +73,9 @@ export default {
choicesSelected.push(this.SPEC)
this.showSpecAccounts = true
}
if (this.value.indexOf(this.SPEC) > -1) {
this.showSpecAccounts = true
}
this.choicesSelected = choicesSelected
this.specAccountsInput = specAccountsInput
},
@@ -95,11 +98,12 @@ export default {
this.outputValue()
},
outputValue() {
let choicesSelected = this.choicesSelected
if (this.showSpecAccounts) {
this.$emit('change', [...this.choicesSelected, ...this.specAccountsInput])
} else {
this.$emit('change', this.choicesSelected)
choicesSelected = [...this.choicesSelected, ...this.specAccountsInput]
}
this.$emit('input', choicesSelected)
this.$emit('change', choicesSelected)
}
}
}

View File

@@ -59,7 +59,10 @@ export default {
accounts: fieldsManager.accounts,
date_start: fieldsManager.date_start,
date_expired: fieldsManager.date_expired,
is_active: fieldsManager.is_active
is_active: fieldsManager.is_active,
actions: {
label: this.$t('common.Action')
}
}
if (this.permType !== 'asset') {
url = '/api/v1/perms/application-permissions/'

View File

@@ -22,7 +22,6 @@ export default {
helpMessage: this.$t('setting.helpText.ConnectionTokenList'),
tableConfig: {
url: ajaxUrl,
columnsExclude: ['actions'],
columnsExtra: ['action'],
columnsShow: {
min: ['id', 'actions'],
@@ -38,7 +37,9 @@ export default {
action: {
label: this.$t('common.Action'),
formatter: function(row) {
return row.actions.map(item => { return item.label }).join(', ')
return row.actions.map(item => {
return item.label
}).join(', ')
}
},
actions: {
@@ -51,7 +52,7 @@ export default {
name: 'Expired',
title: this.$t('setting.Expire'),
type: 'info',
can: ({ row }) => row['is_valid'],
can: ({ row }) => !row['is_expired'],
callback: function({ row }) {
this.$axios.patch(`${ajaxUrl}${row.id}/expire/`,
).then(res => {

View File

@@ -63,7 +63,7 @@ export default {
title: this.$t('users.setWeCom'),
attrs: {
type: 'primary',
label: this.$store.state.users.profile.is_wecom_bound ? this.$t('common.unbind') : this.$t('common.bind')
label: this.$store.state.users.profile.wecom_id ? this.$t('common.unbind') : this.$t('common.bind')
},
has: this.$store.getters.publicSettings.AUTH_WECOM,
callbacks: {
@@ -77,7 +77,7 @@ export default {
title: this.$t('users.setDingTalk'),
attrs: {
type: 'primary',
label: this.$store.state.users.profile.is_dingtalk_bound ? this.$t('common.unbind') : this.$t('common.bind')
label: this.$store.state.users.profile.dingtalk_id ? this.$t('common.unbind') : this.$t('common.bind')
},
has: this.$store.getters.publicSettings.AUTH_DINGTALK,
callbacks: {
@@ -91,7 +91,7 @@ export default {
title: this.$t('users.setFeiShu'),
attrs: {
type: 'primary',
label: this.$store.state.users.profile.is_feishu_bound ? this.$t('common.unbind') : this.$t('common.bind')
label: this.$store.state.users.profile.feishu_id ? this.$t('common.unbind') : this.$t('common.bind')
},
has: this.$store.getters.publicSettings.AUTH_FEISHU,
callbacks: {

View File

@@ -68,7 +68,7 @@ export default {
width: '105px',
formatter: (row, col, cellValue) => {
const display = row['risk_level'].label
if (cellValue === 0) {
if (cellValue?.value === 0) {
return display
} else {
return <span class='text-danger'> {display} </span>

View File

@@ -44,8 +44,10 @@ export default {
formatter: function(row) {
return toSafeLocalDateStr(row.timestamp * 1000)
}
},
actions: {
has: false
}
}
},
headerActions: {

View File

@@ -116,8 +116,9 @@ export default {
value: this.session.asset
},
{
key: this.$t('sessions.systemUser'),
value: this.session.system_user
key: this.$t('assets.Account'),
value: this.session.account
},
{
key: this.$t('sessions.protocol'),
@@ -125,7 +126,7 @@ export default {
},
{
key: this.$t('sessions.loginFrom'),
value: this.session.login_from
value: this.session.login_from?.label || '-'
},
{
key: this.$t('sessions.remoteAddr'),

View File

@@ -138,7 +138,8 @@ export default {
dateStart: dateFrom
},
searchConfig: {
getUrlQuery: false
getUrlQuery: false,
exclude: ['is_finished']
}
}
}

View File

@@ -1,5 +1,5 @@
<template>
<TabPage :submenu="submenu" :active-menu.sync="activeMenu">
<TabPage :active-menu.sync="activeMenu" :submenu="submenu">
<keep-alive>
<component :is="activeMenu" />
</keep-alive>
@@ -38,22 +38,9 @@ export default {
OAuth2
},
data() {
return {
loading: true,
activeMenu: 'Basic',
submenu: [
{
title: this.$t('common.Basic'),
name: 'Basic'
},
{
title: this.$t('setting.Ldap'),
name: 'LDAP'
},
{
title: this.$t('setting.CAS'),
name: 'CAS'
},
let extraBackends = []
if (this.$store.getters.hasValidLicense) {
extraBackends = [
{
title: this.$t('setting.OIDC'),
name: 'OIDC'
@@ -88,6 +75,25 @@ export default {
}
]
}
return {
loading: true,
activeMenu: 'Basic',
submenu: [
{
title: this.$t('common.Basic'),
name: 'Basic'
},
{
title: this.$t('setting.Ldap'),
name: 'LDAP'
},
{
title: this.$t('setting.CAS'),
name: 'CAS'
},
...extraBackends
]
}
},
computed: {
componentData() {
@@ -96,8 +102,7 @@ export default {
},
mounted() {
},
methods: {
}
methods: {}
}
</script>

View File

@@ -1,19 +1,99 @@
<template>
<div>
<ListTable :table-config="tableConfig" :header-actions="headerActions" />
<Dialog
:visible.sync="dialogSettings.visible"
:destroy-on-close="true"
:show-cancel="false"
:title="$tc('sessions.terminalUpdateStorage')"
:show-confirm="false"
>
<GenericCreateUpdateForm v-bind="dialogSettings.iFormSetting" />
</Dialog>
</div>
</template>
<script>
import ListTable from '@/components/ListTable'
import { GenericCreateUpdateForm } from '@/layout/components'
import Dialog from '@/components/Dialog'
import Select2 from '@/components/FormFields/Select2'
export default {
components: {
ListTable
ListTable,
Dialog,
GenericCreateUpdateForm
},
data() {
const vm = this
return {
dialogSettings: {
selectedRows: [],
visible: false,
iFormSetting: {
url: '/api/v1/terminal/terminals/',
getUrl: () => '/api/v1/terminal/terminals/',
fields: [
['', ['command_storage', 'replay_storage']]
],
fieldsMeta: {
command_storage: {
label: this.$t('sessions.commandStorage'),
component: Select2,
el: {
ajax: {
url: `/api/v1/terminal/command-storages/`
},
multiple: false
}
},
replay_storage: {
label: this.$t('sessions.replayStorage'),
component: Select2,
el: {
ajax: {
url: `/api/v1/terminal/replay-storages/`
},
multiple: false
}
}
},
submitMethod: () => 'post',
cleanFormValue: (value) => {
const formValue = []
let object = {}
for (const row of this.dialogSettings.selectedRows) {
object = Object.assign({}, value, { id: row.id })
formValue.push(object)
}
return formValue
},
onSubmit: (validValues) => {
const url = '/api/v1/terminal/terminals/'
const msg = this.$t('common.updateSuccessMsg')
validValues = Object.values(validValues)
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)
}
}
})
},
hasSaveContinue: false
}
},
tableConfig: {
url: '/api/v1/terminal/terminals/',
permissions: {

View File

@@ -1,5 +1,5 @@
<template>
<ListTable :table-config="tableConfig" :header-actions="headerActions" />
<ListTable ref="list" :table-config="tableConfig" :header-actions="headerActions" />
</template>
<script type="text/jsx">
@@ -18,6 +18,7 @@ export default {
}
},
data() {
const vm = this
return {
tableConfig: {
hasSelection: false,
@@ -75,10 +76,20 @@ export default {
{
name: 'detail',
title: this.$t('ops.output'),
type: 'primary',
callback: function({ row, tableData }) {
openTaskPage(row.id)
}
},
{
name: 'run',
title: this.$t('ops.RunAgain'),
type: 'primary',
callback: function({ row, tableData }) {
this.$axios.post(`/api/v1/ops/task-executions/?from=${row.id}`, {}).then(data => {
vm.refreshTable()
openTaskPage(data.task_id)
})
}
}
]
}
@@ -89,6 +100,11 @@ export default {
hasLeftActions: false
}
}
},
methods: {
refreshTable() {
this.$refs.list.reloadTable()
}
}
}
</script>

View File

@@ -34,8 +34,8 @@ export default {
url: this.url,
columnsExclude: ['process_map', 'rel_snapshot'],
columnsShow: {
min: ['title', 'type', 'state', 'actions'],
default: ['title', 'type', 'state', 'status', 'actions']
min: ['title', 'serial_num', 'type', 'state', 'date_created'],
default: ['title', 'serial_num', 'type', 'state', 'status', 'date_created']
},
columnsMeta: {
serial_num: {
@@ -141,7 +141,7 @@ export default {
valueLabel: this.$t('tickets.Pending')
}
},
exclude: ['state', 'id', 'title'],
exclude: ['state', 'id', 'title', 'type'],
options: [
{
value: 'state',
@@ -163,6 +163,29 @@ export default {
}
]
},
{
value: 'type',
label: this.$t('assets.Type'),
type: 'choice',
children: [
{
value: 'apply_asset',
label: this.$t('tickets.ApplyAsset')
},
{
value: 'login_confirm',
label: this.$t('tickets.LoginConfirm')
},
{
value: 'command_confirm',
label: this.$t('tickets.CommandConfirm')
},
{
value: 'login_asset_confirm',
label: this.$t('tickets.LoginAssetConfirm')
}
]
},
{
value: 'id',
label: 'ID'

View File

@@ -1,5 +1,5 @@
<template>
<GenericTicketDetail :object="object" :special-card-items="specialCardItems" />
<GenericTicketDetail :object="object" />
</template>
<script>
@@ -28,23 +28,6 @@ export default {
}
},
computed: {
specialCardItems() {
const { object } = this
return object.type === 'login_confirm' ? [] : [
{
key: this.$t('acl.apply_login_asset'),
value: object.rel_snapshot.apply_login_asset
},
{
key: this.$t('acl.apply_login_system_user'),
value: object.rel_snapshot.apply_login_system_user
},
{
key: this.$t('acl.apply_login_user'),
value: object.rel_snapshot.apply_login_user
}
]
}
},
methods: {}
}

View File

@@ -19,7 +19,7 @@
<el-form-item :label="$tc('tickets.Asset')">
<Select2 v-model="requestForm.assets" v-bind="assetSelect2" style="width: 50% !important" />
</el-form-item>
<el-form-item :label="$tc('tickets.SystemUser')" :rules="isRequired">
<el-form-item :label="$tc('perms.Account')" :rules="isRequired">
<AccountFormatter v-model="requestForm.accounts" style="width: 50% !important" />
</el-form-item>
<el-form-item :label="$tc('common.DateStart')" required>
@@ -72,8 +72,8 @@ export default {
treeNodes,
statusMap: this.object.status.value === 'open' ? STATUS_MAP['pending'] : STATUS_MAP[this.object.state.value],
requestForm: {
nodes: this.object.apply_nodes,
assets: this.object.apply_assets,
nodes: this.object.apply_nodes?.map(i => i.id),
assets: this.object.apply_assets?.map(i => i.id),
accounts: this.object.apply_accounts,
actions: this.object.apply_actions,
apply_date_expired: this.object.apply_date_expired,
@@ -119,14 +119,14 @@ export default {
return [
{
key: this.$tc('perms.Node'),
value: object.apply_nodes.map(item => item.value).join(', ')
value: object.apply_nodes.map(item => item.name).join(', ')
},
{
key: this.$tc('tickets.Asset'),
value: object.apply_assets.map(item => item.name).join(', ')
},
{
key: this.$tc('assets.Accounts'),
key: this.$tc('perms.Account'),
value: object.apply_accounts.join(', ')
},
{
@@ -146,7 +146,6 @@ export default {
assignedCardItems() {
const vm = this
const { object } = this
const rel_snapshot = object.rel_snapshot
return [
{
key: this.$tc('tickets.PermissionName'),
@@ -162,14 +161,14 @@ export default {
},
{
key: this.$tc('perms.Node'),
value: rel_snapshot.apply_nodes.map(item => item.value).join(', ')
value: object.apply_nodes.map(item => item.name).join(', ')
},
{
key: this.$tc('assets.Asset'),
value: rel_snapshot.apply_assets.map(item => item.name).join(', ')
value: object.apply_assets.map(item => item.name).join(', ')
},
{
key: this.$tc('perms.Accounts'),
key: this.$tc('perms.Account'),
value: (object.apply_accounts || []).join(', ')
},
{

View File

@@ -6,14 +6,14 @@
<div slot="header" class="clearfix">
<span>{{ i + 1 + ' ' + vm.$t('tickets.LevelApproval') }}</span>
</div>
<el-radio-group v-model="item.strategy" @change="onChange()">
<el-radio-group v-model="item.strategy.value" @change="onChange()">
<el-radio label="super_admin">{{ vm.$t('tickets.SuperAdmin') }}</el-radio>
<el-radio label="org_admin">{{ vm.$t('tickets.OrgAdmin') }}</el-radio>
<el-radio label="super_org_admin">{{ vm.$t('tickets.SuperOrgAdmin') }}</el-radio>
<el-radio label="custom_user">{{ vm.$t('tickets.CustomUser') }}</el-radio>
</el-radio-group>
<br>
<Select2 v-show="item.strategy === 'custom_user'" v-model="item.assignees" v-bind="select2Option" @change="onChange()" />
<Select2 v-show="item.strategy.value === 'custom_user'" v-model="item.assignees" v-bind="select2Option" @change="onChange()" />
</el-card>
</div>
</div>

View File

@@ -15,7 +15,7 @@
<span class="item-value">{{ session.asset }}</span>
</el-col>
<el-col>
<span class="item-label">{{ $t('tickets.Account') }}</span>
<span class="item-label">{{ $t('assets.Account') }}</span>
<span class="item-value">{{ session.account }}</span>
</el-col>
<el-col>

View File

@@ -1,6 +1,6 @@
<template>
<IBox>
<div style="height: 540px;">
<div style="height: 660px;">
<el-steps direction="vertical" :active="ticketSteps">
<el-step
:title="`${this.$t('tickets.OpenTicket')}${object.title}`"
@@ -19,11 +19,11 @@
<div slot="description">
<div class="processors">
<div class="processors-content">
<span v-for="assignee of item.assignees_display" :key="assignee" style="display: block">
<span v-for="assignee of item.assignees_display.slice(0,4)" :key="assignee" style="display: block">
{{ assignee }}
</span>
</div>
<el-button v-if="item.assignees.length > 5" type="text" @click="lookOver(item.assignees_display)">
<el-button v-if="item.assignees.length > 4" type="text" @click="lookOver(item.assignees_display)">
{{ $tc('tickets.CheckViewAcceptor') }}
</el-button>
</div>

View File

@@ -80,7 +80,7 @@ export default {
'view_audit': ['rbac.view_audit'],
'view_workbench': ['rbac.view_workbench'],
'view_setting': ['settings.view_setting'],
'cloud_import': ['assets.view_asset'],
'cloud_import': ['assets.view_asset', 'assets.view_platform'],
'terminal_node': ['settings.change_terminal'],
'rbac.orgrolebinding': ['rbac.view_orgrole', 'users.view_user'],
'rbac.systemrolebinding': ['rbac.view_systemrole', 'users.view_user'],
@@ -96,9 +96,11 @@ export default {
],
'acls.loginacl': ['users.view_user'],
'acls.loginassetacl': ['users.view_user'],
'assets.view_asset': ['assets.view_node'],
'assets.view_asset': ['assets.view_node', 'assets.view_platform'],
'assets.commandfilterrule': ['assets.view_commandfilter'],
'assets.gateway': ['assets.view_domain'],
'assets.add_gateway': ['assets.view_domain', 'assets.view_platform', 'assets.view_node'],
'assets.change_gateway': ['assets.view_domain', 'assets.view_platform', 'assets.view_node'],
'assets.add_asset': ['assets.view_platform'],
'assets.change_asset': ['assets.view_platform'],
'accounts.view_account': ['assets.view_node'],