升级前端剩余功能

This commit is contained in:
bwcx_jzy 2024-01-08 14:42:36 +08:00
parent 36461e207e
commit d50f857946
No known key found for this signature in database
GPG Key ID: E187D6E9DDDE8C53
38 changed files with 5258 additions and 3671 deletions

View File

@ -1,5 +1,20 @@
# 🚀 版本日志 # 🚀 版本日志
### 2.11.0.7-beta
### 🐞 解决BUG、优化功能
### 注意
1. 取消全局 loading局部loading
2. 编辑器延迟 1 秒加载(避免样式错乱)
3. 所有快捷复制区域变小为一个点击复制图标
4. 弹窗、抽屉样式变动
5. 取消操作引导(临时)
6. 表格将跟随列内容长度自动拉伸出现横向滚动(不会折叠)
------
### 2.11.0.6-beta (2024-01-05) ### 2.11.0.6-beta (2024-01-05)
### 🐞 解决BUG、优化功能 ### 🐞 解决BUG、优化功能

View File

@ -1,8 +1,8 @@
<template> <template>
<a-modal <a-modal
destroyOnClose destroyOnClose
:width="style.width"
v-model:open="visibleModel" v-model:open="visibleModel"
:width="style.width"
:bodyStyle="style.bodyStyle" :bodyStyle="style.bodyStyle"
:style="style.style" :style="style.style"
:footer="null" :footer="null"
@ -69,6 +69,9 @@ export default {
...mapState(useGuideStore, ['getFullscreenViewLogStyle']), ...mapState(useGuideStore, ['getFullscreenViewLogStyle']),
regModifier() { regModifier() {
return this.regModifiers.join('') return this.regModifiers.join('')
},
style() {
return this.getFullscreenViewLogStyle()
} }
}, },
props: { props: {
@ -96,12 +99,10 @@ export default {
// //
wordBreak: false wordBreak: false
}, },
visibleModel: false, visibleModel: false
style: {}
} }
}, },
created() { created() {
this.style = this.getFullscreenViewLogStyle()
this.visibleModel = this.visible this.visibleModel = this.visible
}, },
mounted() { mounted() {

View File

@ -42,8 +42,9 @@ export default {
}, },
computed: {}, computed: {},
created() { created() {
console.log(this.$options._scopeId)
this.domId = this.domId =
(this.$options._parentVnode?.tag || '' + '-' + this.$options._componentTag || '') + '-' + new Date().getTime() (this.$options._parentVnode?.tag || '' + '-' + this.$options._scopeId || '') + '-' + new Date().getTime()
}, },
mounted() { mounted() {
@ -97,14 +98,15 @@ export default {
// this.wp = 100; // this.wp = 100;
//; //;
this.rows = document.querySelector('#' + this.domId).offsetHeight / 16 this.rows = document.querySelector('#' + this.domId).offsetHeight / 16
this.cols = document.querySelector('#' + this.domId).offsetWidth / 8 this.cols = document.querySelector('#' + this.domId).offsetWidth / 8.4
this.hp = this.rows * 8 this.hp = this.rows * 8
this.wp = this.cols * 8 this.wp = this.cols * 8
// //
this.terminal = new Terminal({ this.terminal = new Terminal({
fontSize: 14, fontSize: 14,
rows: parseInt(this.rows), // rows: parseInt(this.rows), //
cols: parseInt(this.cols), // //
cols: parseInt(this.cols),
convertEol: true, // convertEol: true, //
cursorBlink: true, cursorBlink: true,
// Whether input should be disabled. // Whether input should be disabled.
@ -164,8 +166,8 @@ export default {
<style scoped> <style scoped>
.flex-100 { .flex-100 {
display: flex; /* display: flex; */
flex-flow: column; /* flex-flow: column; */
height: 100%; height: 100%;
flex: 1; flex: 1;
} }
@ -173,3 +175,4 @@ export default {
/* box-shadow: inset 0 0 10px 0 #e8e8e8; */ /* box-shadow: inset 0 0 10px 0 #e8e8e8; */
} }
</style> </style>
<style></style>

View File

@ -168,5 +168,6 @@ declare module 'vue' {
UserOutlined: typeof import('@ant-design/icons-vue')['UserOutlined'] UserOutlined: typeof import('@ant-design/icons-vue')['UserOutlined']
ViewPre: typeof import('./../components/logView/view-pre.vue')['default'] ViewPre: typeof import('./../components/logView/view-pre.vue')['default']
WarningOutlined: typeof import('@ant-design/icons-vue')['WarningOutlined'] WarningOutlined: typeof import('@ant-design/icons-vue')['WarningOutlined']
WarningTwoTone: typeof import('@ant-design/icons-vue')['WarningTwoTone']
} }
} }

View File

@ -822,18 +822,6 @@
placeholder="如果需要定时自动构建则填写,cron 表达式.默认未开启秒级别,需要去修改配置文件中:[system.timerMatchSecond]" placeholder="如果需要定时自动构建则填写,cron 表达式.默认未开启秒级别,需要去修改配置文件中:[system.timerMatchSecond]"
:options="CRON_DATA_SOURCE" :options="CRON_DATA_SOURCE"
> >
<!-- <template v-slot:dataSource>
<a-select-opt-group v-for="group in CRON_DATA_SOURCE" :key="group.title">
<template v-slot:label>
<span>
{{ group.title }}
</span>
</template>
<a-select-option v-for="opt in group.children" :key="opt.title" :value="opt.value">
{{ opt.title }} {{ opt.value }}
</a-select-option>
</a-select-opt-group>
</template> -->
<template #option="item"> {{ item.title }} {{ item.value }} </template> <template #option="item"> {{ item.title }} {{ item.value }} </template>
</a-auto-complete> </a-auto-complete>
</a-form-item> </a-form-item>
@ -1096,7 +1084,6 @@
确认 确认
</a-button> </a-button>
</a-space> </a-space>
<!-- </div> -->
</template> </template>
</a-drawer> </a-drawer>
<!-- 选择脚本 --> <!-- 选择脚本 -->
@ -1104,17 +1091,19 @@
destroyOnClose destroyOnClose
:title="`选择脚本`" :title="`选择脚本`"
placement="right" placement="right"
:visible="chooseScriptVisible != 0" :open="chooseScriptVisible != 0"
width="50vw" width="70vw"
:zIndex="1009" :zIndex="1009"
@close=" @close="
() => { () => {
this.chooseScriptVisible = 0 this.chooseScriptVisible = 0
} }
" "
:footer-style="{ textAlign: 'right' }"
> >
<scriptPage <scriptPage
v-if="chooseScriptVisible" v-if="chooseScriptVisible"
ref="scriptPage"
:choose="this.chooseScriptVisible === 1 ? 'checkbox' : 'radio'" :choose="this.chooseScriptVisible === 1 ? 'checkbox' : 'radio'"
:choose-val=" :choose-val="
this.chooseScriptVisible === 1 this.chooseScriptVisible === 1
@ -1140,13 +1129,36 @@
} }
" "
></scriptPage> ></scriptPage>
<template #footer>
<a-space>
<a-button
@click="
() => {
this.chooseScriptVisible = false
}
"
>
取消
</a-button>
<a-button
type="primary"
@click="
() => {
this.$refs['scriptPage'].handerConfirm()
}
"
>
确认
</a-button>
</a-space>
</template>
</a-drawer> </a-drawer>
<!-- 查看命令示例 --> <!-- 查看命令示例 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
width="50vw" width="50vw"
v-model:value="viewScriptTemplVisible" v-model:open="viewScriptTemplVisible"
title="构建命令示例" title="构建命令示例"
:footer="null" :footer="null"
:maskClosable="false" :maskClosable="false"
@ -1160,15 +1172,15 @@
> >
<a-collapse-panel v-for="(group, index) in buildScipts" :key="`${index}`" :header="group.title"> <a-collapse-panel v-for="(group, index) in buildScipts" :key="`${index}`" :header="group.title">
<a-list size="small" bordered :data-source="group.children"> <a-list size="small" bordered :data-source="group.children">
<template v-slot:renderItem="opt"> <template #renderItem="{ item }">
<a-list-item> <a-list-item>
<a-space> <a-space>
{{ opt.title }} {{ item.title }}
<SwapOutlined <SwapOutlined
@click=" @click="
() => { () => {
temp = { ...temp, script: opt.value } temp = { ...temp, script: item.value }
viewScriptTemplVisible = false viewScriptTemplVisible = false
} }
" "

View File

@ -181,7 +181,7 @@
} }
" "
/> />
<!-- 选择确认区域 --> <!-- 选择确认区域
<div style="padding-top: 50px" v-if="this.choose"> <div style="padding-top: 50px" v-if="this.choose">
<div <div
:style="{ :style="{
@ -209,7 +209,7 @@
<a-button type="primary" @click="handerConfirm"> 确定 </a-button> <a-button type="primary" @click="handerConfirm"> 确定 </a-button>
</a-space> </a-space>
</div> </div>
</div> </div> -->
</div> </div>
</template> </template>

View File

@ -681,7 +681,7 @@
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-modal> </a-modal>
<!-- 选择确认区域 --> <!-- 选择确认区域
<div style="padding-top: 50px" v-if="this.choose"> <div style="padding-top: 50px" v-if="this.choose">
<div <div
:style="{ :style="{
@ -709,7 +709,7 @@
<a-button type="primary" @click="handerConfirm"> 确定 </a-button> <a-button type="primary" @click="handerConfirm"> 确定 </a-button>
</a-space> </a-space>
</div> </div>
</div> </div> -->
</div> </div>
</template> </template>
@ -1319,7 +1319,7 @@ export default {
}) })
return return
} }
$emit(this, 'confirm', selectData) this.$emit('confirm', selectData)
} }
}, },
emits: ['cancel', 'confirm'] emits: ['cancel', 'confirm']

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="full-content"> <div>
<!-- 数据表格 --> <!-- 数据表格 -->
<a-table <a-table
:data-source="list" :data-source="list"
@ -15,22 +15,26 @@
" "
bordered bordered
rowKey="id" rowKey="id"
:row-selection="rowSelection"
:scroll="{
x: 'max-content'
}"
> >
<template #title> <template v-slot:title>
<a-space> <a-space>
<a-space> <a-space>
<a-input <a-input
allowClear allowClear
class="search-input-item" class="search-input-item"
@pressEnter="loadData" @pressEnter="loadData"
v-model="listQuery['%issuerDnName%']" v-model:value="listQuery['%issuerDnName%']"
placeholder="颁发者" placeholder="颁发者"
/> />
<a-input <a-input
allowClear allowClear
class="search-input-item" class="search-input-item"
@pressEnter="loadData" @pressEnter="loadData"
v-model="listQuery['%subjectDnName%']" v-model:value="listQuery['%subjectDnName%']"
placeholder="主题" placeholder="主题"
/> />
<a-tooltip title="按住 Ctr 或者 Alt/Option 键点击按钮快速回到第一页"> <a-tooltip title="按住 Ctr 或者 Alt/Option 键点击按钮快速回到第一页">
@ -40,36 +44,43 @@
</a-space> </a-space>
</a-space> </a-space>
</template> </template>
<a-tooltip #tooltip slot-scope="text" placement="topLeft" :title="text"> <template #bodyCell="{ column, text, record, index }">
<span>{{ text }}</span> <template v-if="column.tooltip">
</a-tooltip> <a-tooltip placement="topLeft" :title="text">
<a-popover #name slot-scope="text, item" title="证书描述"> <span>{{ text }}</span>
<template #content> </a-tooltip>
<p>描述{{ item.description }}</p> </template>
<template v-else-if="column.dataIndex === 'name'">
<a-popover title="证书描述">
<template v-slot:content>
<p>描述{{ record.description }}</p>
</template>
<!-- {{ text }} -->
<a-button type="link" style="padding: 0" @click="handleEdit(record)" size="small">{{ text }}</a-button>
</a-popover>
</template>
<template v-else-if="column.dataIndex === 'fileExists'">
<a-tag v-if="text" color="green">存在</a-tag>
<a-tag v-else color="red">丢失</a-tag>
</template>
<template v-else-if="column.dataIndex === 'workspaceId'">
<a-tag v-if="text === 'GLOBAL'">全局</a-tag>
<a-tag v-else>工作空间</a-tag>
</template>
<template v-else-if="column.dataIndex === 'operation'">
<a-space>
<a-button size="small" type="primary" @click="handleDeployFile(record)">部署</a-button>
<a-button size="small" type="primary" @click="handleDownload(record)">导出</a-button>
<a-button size="small" type="primary" danger @click="handleDelete(record)">删除</a-button>
</a-space>
</template> </template>
<!-- {{ text }} -->
<a-button type="link" style="padding: 0" @click="handleEdit(item)" size="small">{{ text }}</a-button>
</a-popover>
<template #fileExists slot-scope="text">
<a-tag v-if="text" color="green">存在</a-tag>
<a-tag v-else color="red">丢失</a-tag>
</template>
<template #global slot-scope="text">
<a-tag v-if="text === 'GLOBAL'">全局</a-tag>
<a-tag v-else>工作空间</a-tag>
</template>
<template #operation slot-scope="text, record">
<a-space>
<a-button size="small" type="primary" @click="handleDeployFile(record)">部署</a-button>
<a-button size="small" type="primary" @click="handleDownload(record)">导出</a-button>
<a-button size="small" type="danger" @click="handleDelete(record)">删除</a-button>
</a-space>
</template> </template>
</a-table> </a-table>
<!-- 导入 --> <!-- 导入 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model="editCertVisible" :confirmLoading="confirmLoading"
v-model:open="editCertVisible"
width="700px" width="700px"
title="导入证书" title="导入证书"
@ok="handleEditCertOk" @ok="handleEditCertOk"
@ -77,7 +88,7 @@
> >
<a-form ref="importCertForm" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }"> <a-form ref="importCertForm" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }">
<a-form-item label="证书类型" name="type"> <a-form-item label="证书类型" name="type">
<a-radio-group v-model="temp.type"> <a-radio-group v-model:value="temp.type">
<a-radio value="pkcs12"> pkcs12(pfx) </a-radio> <a-radio value="pkcs12"> pkcs12(pfx) </a-radio>
<a-radio value="JKS"> JKS </a-radio> <a-radio value="JKS"> JKS </a-radio>
<a-radio value="X.509"> X.509(pemkeycrtcer) </a-radio> <a-radio value="X.509"> X.509(pemkeycrtcer) </a-radio>
@ -88,9 +99,10 @@
<a-upload <a-upload
v-if="temp.type" v-if="temp.type"
:file-list="uploadFileList" :file-list="uploadFileList"
:remove=" @remove="
() => { () => {
uploadFileList = [] uploadFileList = []
return true
} }
" "
:before-upload=" :before-upload="
@ -101,7 +113,7 @@
" "
:accept="typeAccept[temp.type]" :accept="typeAccept[temp.type]"
> >
<a-button><a-icon type="upload" />选择文件</a-button> <a-button><UploadOutlined />选择文件</a-button>
</a-upload> </a-upload>
<template v-else>请选选择类型</template> <template v-else>请选选择类型</template>
</a-form-item> </a-form-item>
@ -111,42 +123,46 @@
name="password" name="password"
help="如果未填写将解析压缩包里面的 txt" help="如果未填写将解析压缩包里面的 txt"
> >
<a-input v-model="temp.password" placeholder="证书密码" /> <a-input v-model:value="temp.password" placeholder="证书密码" />
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-modal> </a-modal>
<!-- 编辑证书 --> <!-- 编辑证书 -->
<a-modal destroyOnClose v-model:visible="editVisible" :title="`编辑证书`" @ok="handleEditOk" :maskClosable="false"> <a-modal
destroyOnClose
:confirmLoading="confirmLoading"
v-model:open="editVisible"
:title="`编辑证书`"
@ok="handleEditOk"
:maskClosable="false"
>
<a-form ref="editForm" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }"> <a-form ref="editForm" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
<a-form-item label="证书共享" name="global"> <a-form-item label="证书共享" name="global">
<a-radio-group v-model="temp.global"> <a-radio-group v-model:value="temp.global">
<a-radio :value="true"> 全局 </a-radio> <a-radio :value="true"> 全局 </a-radio>
<a-radio :value="false"> 当前工作空间 </a-radio> <a-radio :value="false"> 当前工作空间 </a-radio>
</a-radio-group> </a-radio-group>
</a-form-item> </a-form-item>
<a-form-item label="证书描述" name="description"> <a-form-item label="证书描述" name="description">
<a-textarea v-model="temp.description" placeholder="请输入证书描述" /> <a-textarea v-model:value="temp.description" placeholder="请输入证书描述" />
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-modal> </a-modal>
<!-- 发布文件 --> <!-- 发布文件 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model="releaseFileVisible" v-model:open="releaseFileVisible"
title="部署证书" title="部署证书"
width="50%" width="50%"
:maskClosable="false" :maskClosable="false"
@ok=" @ok="releaseFileOk()"
() => {
this.$refs.releaseFile?.tryCommit()
}
"
> >
<a-alert message="证书将打包成 zip 文件上传到对应的文件夹" type="info" show-icon /> <a-alert message="证书将打包成 zip 文件上传到对应的文件夹" type="info" show-icon style="margin-bottom: 10px" />
<releaseFile ref="releaseFile" v-if="releaseFileVisible" @commit="handleCommitTask"></releaseFile> <releaseFile ref="releaseFile" v-if="releaseFileVisible" @commit="handleCommitTask"></releaseFile>
</a-modal> </a-modal>
</div> </div>
</template> </template>
<script> <script>
import { import {
certificateImportFile, certificateImportFile,
@ -183,28 +199,27 @@ export default {
title: '序列号 (SN)', title: '序列号 (SN)',
dataIndex: 'serialNumberStr', dataIndex: 'serialNumberStr',
ellipsis: true, ellipsis: true,
width: 150, width: 150
scopedSlots: { customRender: 'name' }
}, },
{ {
title: '证书类型', title: '证书类型',
dataIndex: 'keyType', dataIndex: 'keyType',
ellipsis: true, ellipsis: true,
width: '80px', width: '80px',
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
{ {
title: '文件状态', title: '文件状态',
dataIndex: 'fileExists', dataIndex: 'fileExists',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'fileExists' },
width: '80px' width: '80px'
}, },
{ {
title: '共享', title: '共享',
dataIndex: 'workspaceId', dataIndex: 'workspaceId',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'global' },
width: '90px' width: '90px'
}, },
{ {
@ -212,63 +227,63 @@ export default {
dataIndex: 'issuerDnName', dataIndex: 'issuerDnName',
ellipsis: true, ellipsis: true,
width: 200, width: 200,
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
{ {
title: '主题', title: '主题',
dataIndex: 'subjectDnName', dataIndex: 'subjectDnName',
ellipsis: true, ellipsis: true,
width: 150, width: 150,
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
{ {
title: '密钥算法', title: '密钥算法',
dataIndex: 'sigAlgName', dataIndex: 'sigAlgName',
ellipsis: true, ellipsis: true,
width: 150, width: 150,
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
{ {
title: '算法 OID', title: '算法 OID',
dataIndex: 'sigAlgOid', dataIndex: 'sigAlgOid',
ellipsis: true, ellipsis: true,
width: 150, width: 150,
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
{ {
title: '生效时间', title: '生效时间',
dataIndex: 'effectiveTime', dataIndex: 'effectiveTime',
customRender: (text) => parseTime(text), customRender: ({ text }) => parseTime(text),
sorter: true, sorter: true,
width: '160px' width: '170px'
}, },
{ {
title: '到期时间', title: '到期时间',
dataIndex: 'expirationTime', dataIndex: 'expirationTime',
sorter: true, sorter: true,
customRender: (text) => parseTime(text), customRender: ({ text }) => parseTime(text),
width: '160px' width: '170px'
}, },
{ {
title: '版本号', title: '版本号',
dataIndex: 'certVersion', dataIndex: 'certVersion',
ellipsis: true, ellipsis: true,
width: '80px', width: '80px',
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
{ {
title: '创建人', title: '创建人',
dataIndex: 'createUser', dataIndex: 'createUser',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'modifyUser' },
width: '120px' width: '120px'
}, },
{ {
title: '修改人', title: '修改人',
dataIndex: 'modifyUser', dataIndex: 'modifyUser',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'modifyUser' },
width: '120px' width: '120px'
}, },
{ {
@ -276,8 +291,8 @@ export default {
dataIndex: 'createTimeMillis', dataIndex: 'createTimeMillis',
sorter: true, sorter: true,
ellipsis: true, ellipsis: true,
customRender: (text) => parseTime(text), customRender: ({ text }) => parseTime(text),
width: '160px' width: '170px'
}, },
{ {
title: '操作', title: '操作',
@ -295,12 +310,23 @@ export default {
type: [{ required: true, message: '请选择证书类型', trigger: 'blur' }] type: [{ required: true, message: '请选择证书类型', trigger: 'blur' }]
}, },
releaseFileVisible: false, releaseFileVisible: false,
editVisible: false editVisible: false,
confirmLoading: false,
tableSelections: []
} }
}, },
computed: { computed: {
pagination() { pagination() {
return COMPUTED_PAGINATION(this.listQuery) return COMPUTED_PAGINATION(this.listQuery)
},
rowSelection() {
return {
onChange: (selectedRowKeys) => {
this.tableSelections = selectedRowKeys
},
selectedRowKeys: this.tableSelections,
type: 'radio'
}
} }
}, },
mounted() { mounted() {
@ -339,12 +365,9 @@ export default {
// Cert // Cert
handleEditCertOk() { handleEditCertOk() {
// //
this.$refs['importCertForm'].validate((valid) => { this.$refs['importCertForm'].validate().then(() => {
if (!valid) {
return false
}
if (this.uploadFileList.length === 0) { if (this.uploadFileList.length === 0) {
$notification.error({ this.$notification.error({
message: '请选择证书文件' message: '请选择证书文件'
}) })
return false return false
@ -355,23 +378,29 @@ export default {
formData.append('password', this.temp.password || '') formData.append('password', this.temp.password || '')
// //
certificateImportFile(formData).then((res) => { this.confirmLoading = true
if (res.code === 200) { certificateImportFile(formData)
// .then((res) => {
$notification.success({ if (res.code === 200) {
message: res.msg //
}) this.$notification.success({
message: res.msg
})
this.editCertVisible = false this.editCertVisible = false
this.loadData() this.loadData()
} }
}) })
.finally(() => {
this.confirmLoading = false
})
}) })
}, },
// //
handleDelete(record) { handleDelete(record) {
$confirm({ this.$confirm({
title: '系统提示', title: '系统提示',
zIndex: 1009,
content: '真的要删除该证书么,删除会将证书文件一并删除奥?', content: '真的要删除该证书么,删除会将证书文件一并删除奥?',
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',
@ -382,7 +411,7 @@ export default {
} }
deleteCert(params).then((res) => { deleteCert(params).then((res) => {
if (res.code === 200) { if (res.code === 200) {
$notification.success({ this.$notification.success({
message: res.msg message: res.msg
}) })
this.loadData() this.loadData()
@ -402,27 +431,33 @@ export default {
}, },
// //
handleEdit(item) { handleEdit(item) {
this.temp = { ...item, global: item.workspaceId === 'GLOBAL', workspaceId: '' } this.temp = {
...item,
global: item.workspaceId === 'GLOBAL',
workspaceId: ''
}
this.editVisible = true this.editVisible = true
this.$refs['editForm']?.resetFields() this.$refs['editForm']?.resetFields()
}, },
// //
handleEditOk() { handleEditOk() {
this.$refs['editForm'].validate((valid) => { this.$refs['editForm'].validate().then(() => {
if (!valid) { this.confirmLoading = true
return false certificateEdit(this.temp)
} .then((res) => {
certificateEdit(this.temp).then((res) => { if (res.code === 200) {
if (res.code === 200) { //
// this.$notification.success({
$notification.success({ message: res.msg
message: res.msg })
})
this.editVisible = false this.editVisible = false
this.loadData() this.loadData()
} }
}) })
.finally(() => {
this.confirmLoading = false
})
}) })
}, },
handleDeployFile(record) { handleDeployFile(record) {
@ -431,18 +466,40 @@ export default {
}, },
handleCommitTask(data) { handleCommitTask(data) {
certificateDeploy({ ...data, id: this.temp.id }).then((res) => { this.confirmLoading = true
if (res.code === 200) { certificateDeploy({ ...data, id: this.temp.id })
// .then((res) => {
$notification.success({ if (res.code === 200) {
message: res.msg //
}) this.$notification.success({
message: res.msg
})
this.releaseFileVisible = false this.releaseFileVisible = false
} }
}) })
.finally(() => {
this.confirmLoading = false
})
},
releaseFileOk() {
this.$refs.releaseFile?.tryCommit()
},
//
handerConfirm() {
if (!this.tableSelections.length) {
this.$notification.warning({
message: '请选择要使用的证书'
})
return
}
const selectData = this.list.filter((item) => {
return item.id === this.tableSelections[0]
})[0]
this.$emit('confirm', `${selectData.serialNumberStr}:${selectData.keyType}`)
} }
} }
} }
</script> </script>
<style scoped></style>

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,5 @@
<template> <template>
<div class="full-content"> <div>
<!-- <div ref="filter" class="filter"> -->
<!-- <a-button type="primary" @click="handleFilter">刷新</a-button> -->
<!-- </div> -->
<!-- 数据表格 --> <!-- 数据表格 -->
<a-table <a-table
size="middle" size="middle"
@ -11,17 +8,19 @@
:pagination="pagination" :pagination="pagination"
@change="changePage" @change="changePage"
bordered bordered
:rowKey="(record, index) => index" :scroll="{
x: 'max-content'
}"
> >
<template #title> <template v-slot:title>
<a-space> <a-space>
<a-select v-model="listQuery.nodeId" allowClear placeholder="请选择节点" class="search-input-item"> <a-select v-model:value="listQuery.nodeId" allowClear placeholder="请选择节点" class="search-input-item">
<a-select-option v-for="node in nodeList" :key="node.id">{{ node.name }}</a-select-option> <a-select-option v-for="node in nodeList" :key="node.id">{{ node.name }}</a-select-option>
</a-select> </a-select>
<a-select v-model="listQuery.outGivingId" allowClear placeholder="分发项目" class="search-input-item"> <a-select v-model:value="listQuery.outGivingId" allowClear placeholder="分发项目" class="search-input-item">
<a-select-option v-for="dispatch in dispatchList" :key="dispatch.id">{{ dispatch.name }}</a-select-option> <a-select-option v-for="dispatch in dispatchList" :key="dispatch.id">{{ dispatch.name }}</a-select-option>
</a-select> </a-select>
<a-select v-model="listQuery.status" allowClear placeholder="请选择状态" class="search-input-item"> <a-select v-model:value="listQuery.status" allowClear placeholder="请选择状态" class="search-input-item">
<a-select-option v-for="(item, key) in dispatchStatusMap" :key="key" :value="key">{{ <a-select-option v-for="(item, key) in dispatchStatusMap" :key="key" :value="key">{{
item item
}}</a-select-option> }}</a-select-option>
@ -37,91 +36,110 @@
</a-tooltip> </a-tooltip>
</a-space> </a-space>
</template> </template>
<a-tooltip #outGivingId slot-scope="text" placement="topLeft" :title="text"> <template #bodyCell="{ column, text, record, index }">
<span>{{ text }}</span> <template v-if="column.dataIndex === 'outGivingId'">
</a-tooltip> <a-tooltip placement="topLeft" :title="text">
<span>{{ text }}</span>
</a-tooltip>
</template>
<a-tooltip #nodeName slot-scope="text, record" placement="topLeft" :title="text"> <template v-else-if="column.dataIndex === 'nodeName'">
<span>{{ <a-tooltip
nodeList.filter((item) => item.id === record.nodeId) && placement="topLeft"
nodeList.filter((item) => item.id === record.nodeId)[0] && :title="
nodeList.filter((item) => item.id === record.nodeId)[0].name nodeList.filter((item) => item.id === record.nodeId) &&
}}</span> nodeList.filter((item) => item.id === record.nodeId)[0] &&
</a-tooltip> nodeList.filter((item) => item.id === record.nodeId)[0].name
<a-tooltip #projectId slot-scope="text" placement="topLeft" :title="text"> "
<span>{{ text }}</span> >
</a-tooltip> <span>{{
<a-tooltip nodeList.filter((item) => item.id === record.nodeId) &&
#outGivingResultMsg nodeList.filter((item) => item.id === record.nodeId)[0] &&
slot-scope="text, item" nodeList.filter((item) => item.id === record.nodeId)[0].name
placement="topLeft" }}</span>
:title="readJsonStrField(item.result, 'msg')" </a-tooltip>
> </template>
<span <template v-else-if="column.dataIndex === 'projectId'">
>{{ readJsonStrField(item.result, 'code') }}-{{ readJsonStrField(item.result, 'msg') || item.result }}</span <a-tooltip placement="topLeft" :title="text">
> <span>{{ text }}</span>
</a-tooltip> </a-tooltip>
<a-tooltip </template>
#outGivingResultTime <template v-else-if="column.dataIndex === 'mode'">
slot-scope="text, item" <a-tooltip placement="topLeft" :title="`${dispatchMode[text] || ''} 关联数据:${record.modeData || ''}`">
placement="topLeft" <span>{{ dispatchMode[text] || '' }}</span>
:title="readJsonStrField(item.result, 'upload_duration')" </a-tooltip>
> </template>
<span>{{ readJsonStrField(item.result, 'upload_duration') }}</span>
</a-tooltip> <template v-else-if="column.dataIndex === 'outGivingResultMsg'">
<a-tooltip <a-tooltip placement="topLeft" :title="readJsonStrField(record.result, 'msg')">
#outGivingResultSize <span
slot-scope="text, item" >{{ readJsonStrField(record.result, 'code') }}-{{
placement="topLeft" readJsonStrField(record.result, 'msg') || record.result
:title="readJsonStrField(item.result, 'upload_file_size')" }}</span
> >
{{ readJsonStrField(item.result, 'upload_file_size') }} </a-tooltip>
</a-tooltip> </template>
<a-tooltip <template v-else-if="column.dataIndex === 'outGivingResultTime'">
#outGivingResultMsgData <a-tooltip placement="topLeft" :title="readJsonStrField(record.result, 'upload_duration')">
slot-scope="text, item" <span>{{ readJsonStrField(record.result, 'upload_duration') }}</span>
placement="topLeft" </a-tooltip>
:title="`${readJsonStrField(item.result, 'data')}`" </template>
> <template v-else-if="column.dataIndex === 'outGivingResultSize'">
<template v-if="item.fileSize"> {{ Math.floor((item.progressSize / item.fileSize) * 100) }}% </template> <a-tooltip placement="topLeft" :title="readJsonStrField(record.result, 'upload_file_size')">
{{ readJsonStrField(item.result, 'data') }} {{ readJsonStrField(record.result, 'upload_file_size') }}
</a-tooltip> </a-tooltip>
<a-tooltip #status slot-scope="text"> </template>
<!-- {{ dispatchStatusMap[text] || "未知" }} --> <template v-else-if="column.dataIndex === 'outGivingResultMsgData'">
<a-tag v-if="text === 2" color="green">{{ dispatchStatusMap[text] || '未知' }}</a-tag> <a-tooltip placement="topLeft" :title="`${readJsonStrField(record.result, 'data')}`">
<a-tag v-else-if="text === 1 || text === 0 || text === 5" color="orange">{{ <template v-if="record.fileSize">
dispatchStatusMap[text] || '未知' {{ Math.floor((record.progressSize / record.fileSize) * 100) }}%
}}</a-tag> </template>
<a-tag v-else-if="text === 3 || text === 4 || text === 6" color="red">{{ {{ readJsonStrField(record.result, 'data') }}
dispatchStatusMap[text] || '未知' </a-tooltip>
}}</a-tag> </template>
<a-tag v-else>{{ dispatchStatusMap[text] || '未知' }}</a-tag> <template v-else-if="column.dataIndex === 'status'">
</a-tooltip> <!-- {{ dispatchStatusMap[text] || "未知" }} -->
<template #operation slot-scope="text, record"> <a-tag v-if="text === 2" color="green">{{ dispatchStatusMap[text] || '未知' }}</a-tag>
<a-button type="primary" size="small" @click="handleDetail(record)">详情</a-button> <a-tag v-else-if="text === 1 || text === 0 || text === 5" color="orange">{{
dispatchStatusMap[text] || '未知'
}}</a-tag>
<a-tag v-else-if="text === 3 || text === 4 || text === 6" color="red">{{
dispatchStatusMap[text] || '未知'
}}</a-tag>
<a-tag v-else>{{ dispatchStatusMap[text] || '未知' }}</a-tag>
</template>
<template v-else-if="column.dataIndex === 'operation'">
<a-button type="primary" size="small" @click="handleDetail(record)">详情</a-button>
</template>
</template> </template>
</a-table> </a-table>
<!-- 详情区 --> <!-- 详情区 -->
<a-modal destroyOnClose v-model:visible="detailVisible" width="600px" title="详情信息" :footer="null"> <a-modal destroyOnClose v-model:open="detailVisible" width="600px" title="详情信息" :footer="null">
<a-list item-layout="horizontal" :data-source="detailData"> <a-list item-layout="horizontal" :data-source="detailData">
<a-list-item #renderItem slot-scope="item"> <template #renderItem="{ item }">
<a-list-item-meta :description="item.description"> <a-list-item>
<h4 #title>{{ item.title }}</h4> <a-list-item-meta :description="item.description">
</a-list-item-meta> <template v-slot:title>
</a-list-item> <h4>{{ item.title }}</h4>
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list> </a-list>
</a-modal> </a-modal>
</div> </div>
</template> </template>
<script> <script>
import { getNodeListAll } from '@/api/node' import { getNodeListAll } from '@/api/node'
import { dispatchStatusMap, getDishPatchListAll, getDishPatchLogList } from '@/api/dispatch' import { dispatchStatusMap, getDishPatchListAll, getDishPatchLogList, dispatchMode } from '@/api/dispatch'
import { CHANGE_PAGE, COMPUTED_PAGINATION, PAGE_DEFAULT_LIST_QUERY, readJsonStrField, parseTime } from '@/utils/const' import { CHANGE_PAGE, COMPUTED_PAGINATION, PAGE_DEFAULT_LIST_QUERY, readJsonStrField, parseTime } from '@/utils/const'
export default { export default {
data() { data() {
return { return {
loading: false, dispatchMode,
loading: true,
list: [], list: [],
nodeList: [], nodeList: [],
dispatchList: [], dispatchList: [],
@ -135,40 +153,54 @@ export default {
{ {
title: '分发项目 ID', title: '分发项目 ID',
dataIndex: 'outGivingId', dataIndex: 'outGivingId',
ellipsis: true, width: 100,
scopedSlots: { customRender: 'outGivingId' } ellipsis: true
}, },
{ title: '节点名称', dataIndex: 'nodeName', ellipsis: true, scopedSlots: { customRender: 'nodeName' } }, {
{ title: '项目 ID', dataIndex: 'projectId', ellipsis: true, scopedSlots: { customRender: 'projectId' } }, title: '节点名称',
dataIndex: 'nodeName',
ellipsis: true,
width: 150
},
{
title: '项目 ID',
dataIndex: 'projectId',
ellipsis: true,
width: 100
},
{
title: '分发方式',
dataIndex: 'mode',
ellipsis: true,
width: '100px'
},
{ {
title: '分发结果', title: '分发结果',
dataIndex: 'outGivingResultMsg', dataIndex: 'outGivingResultMsg',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'outGivingResultMsg' } width: 200
}, },
{ {
title: '分发状态消息', title: '分发状态消息',
dataIndex: 'outGivingResultMsgData', dataIndex: 'outGivingResultMsgData',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'outGivingResultMsgData' } width: 100
}, },
{ {
title: '分发耗时', title: '分发耗时',
dataIndex: 'outGivingResultTime', dataIndex: 'outGivingResultTime',
width: '120px', width: '120px'
scopedSlots: { customRender: 'outGivingResultTime' }
}, },
{ {
title: '文件大小', title: '文件大小',
dataIndex: 'outGivingResultSize', dataIndex: 'outGivingResultSize',
width: '100px', width: '100px'
scopedSlots: { customRender: 'outGivingResultSize' }
}, },
{ {
title: '开始时间', title: '开始时间',
dataIndex: 'startTime', dataIndex: 'startTime',
customRender: (text) => { customRender: ({ text }) => {
return parseTime(text) return parseTime(text)
}, },
sorter: true, sorter: true,
@ -178,7 +210,7 @@ export default {
title: '结束时间', title: '结束时间',
dataIndex: 'endTime', dataIndex: 'endTime',
sorter: true, sorter: true,
customRender: (text) => { customRender: ({ text }) => {
return parseTime(text) return parseTime(text)
}, },
width: '170px' width: '170px'
@ -187,11 +219,17 @@ export default {
title: '操作人', title: '操作人',
dataIndex: 'modifyUser', dataIndex: 'modifyUser',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'modifyUser' },
width: 120 width: 120
}, },
{ title: '状态', dataIndex: 'status', width: 100, ellipsis: true, scopedSlots: { customRender: 'status' } } {
// { title: "", dataIndex: "operation", align: "center", scopedSlots: { customRender: "operation" }, width: "100px" }, title: '状态',
dataIndex: 'status',
width: 100,
ellipsis: true,
fixed: 'right'
},
{ title: '操作', dataIndex: 'operation', align: 'center', width: '100px', fixed: 'right' }
] ]
} }
}, },
@ -258,4 +296,3 @@ export default {
} }
} }
</script> </script>
<style scoped></style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="full-content"> <div>
<!-- 数据表格 --> <!-- 数据表格 -->
<a-table <a-table
:data-source="list" :data-source="list"
@ -8,12 +8,14 @@
:pagination="pagination" :pagination="pagination"
@change="changePage" @change="changePage"
bordered bordered
:rowKey="(record, index) => index" :scroll="{
x: 'max-content'
}"
> >
<template #title> <template v-slot:title>
<a-space> <a-space>
<a-input <a-input
v-model="listQuery['%name%']" v-model:value="listQuery['%name%']"
@pressEnter="loadData" @pressEnter="loadData"
placeholder="日志名称" placeholder="日志名称"
class="search-input-item" class="search-input-item"
@ -25,96 +27,107 @@
<a-button type="primary" @click="handleAdd">新增</a-button> <a-button type="primary" @click="handleAdd">新增</a-button>
</a-space> </a-space>
</template> </template>
<a-tooltip #name slot-scope="text" placement="topLeft" :title="text"> <template #bodyCell="{ column, text, record, index }">
<span>{{ text }}</span> <template v-if="column.tooltip">
</a-tooltip> <a-tooltip placement="topLeft" :title="text">
<span>{{ text }}</span>
</a-tooltip>
</template>
<template #operation slot-scope="text, record"> <template v-else-if="column.dataIndex === 'operation'">
<a-space> <a-space>
<a-button type="primary" size="small" @click="handleEdit(record)">编辑</a-button> <a-button type="primary" size="small" @click="handleEdit(record)">编辑</a-button>
<a-button type="primary" size="small" @click="handleLogRead(record)">查看</a-button> <a-button type="primary" size="small" @click="handleLogRead(record)">查看</a-button>
<a-button type="danger" size="small" @click="handleDelete(record)">删除</a-button> <a-button type="primary" danger size="small" @click="handleDelete(record)">删除</a-button>
</a-space> </a-space>
</template>
</template> </template>
</a-table> </a-table>
<!-- 编辑区 --> <!-- 编辑区 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model="editVisible" v-model:open="editVisible"
width="60%" width="60%"
title="编辑日志搜索" title="编辑日志搜索"
:confirmLoading="confirmLoading"
@ok="handleEditOk" @ok="handleEditOk"
:maskClosable="false" :maskClosable="false"
> >
<a-form ref="editForm" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }"> <a-form ref="editForm" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }">
<a-form-item label="日志名称" name="name"> <a-form-item label="日志名称" name="name">
<a-input v-model="temp.name" :maxLength="50" placeholder="日志项目名称" /> <a-input v-model:value="temp.name" :maxLength="50" placeholder="日志项目名称" />
</a-form-item> </a-form-item>
<a-form-item label="绑定节点" required> <a-form-item label="绑定节点" required>
<a-row v-for="(item, index) in temp.projectList" :key="index"> <a-space direction="vertical" style="width: 100%">
<a-col :span="11"> <a-row v-for="(item, index) in temp.projectList" :key="index">
<span>节点: </span> <a-col :span="11">
<a-select <span>节点: </span>
style="width: 80%" <a-select
v-model="item.nodeId" style="width: 80%"
placeholder="请选择节点" v-model:value="item.nodeId"
@change=" placeholder="请选择节点"
() => { @change="
temp = { () => {
...temp, temp = {
projectList: temp.projectList.map((item, index1) => { ...temp,
if (index1 === index && item.projectId) { projectList: temp.projectList.map((item, index1) => {
return Object.assign(item, { projectId: undefined }) if (index1 === index && item.projectId) {
} return Object.assign(item, { projectId: undefined })
return item }
}) return item
})
}
} }
}
"
>
<a-select-option
v-for="nodeItem in nodeList"
:key="nodeItem.id"
:disabled="
!nodeProjectList[nodeItem.id] || !nodeProjectList[nodeItem.id].projects || nodeItem.openStatus !== 1
" "
> >
{{ nodeItem.name }}
</a-select-option>
</a-select>
</a-col>
<a-col :span="11">
<span>项目: </span>
<a-select
:disabled="!item.nodeId"
style="width: 80%"
v-model="item.projectId"
:placeholder="`请选择项目`"
>
<!-- <a-select-option value=""> 请先选择节点</a-select-option> -->
<template v-if="nodeProjectList[item.nodeId]">
<a-select-option <a-select-option
v-for="project in nodeProjectList[item.nodeId].projects" v-for="nodeItem in nodeList"
:key="nodeItem.id"
:disabled=" :disabled="
temp.projectList.filter((item, nowIndex) => { !nodeProjectList[nodeItem.id] ||
return ( !nodeProjectList[nodeItem.id].projects ||
item.nodeId === project.nodeId && item.projectId === project.projectId && nowIndex !== index nodeItem.openStatus !== 1
)
}).length > 0
" "
:key="project.projectId"
> >
{{ project.name }} {{ nodeItem.name }}
</a-select-option> </a-select-option>
</template> </a-select>
</a-select> </a-col>
</a-col> <a-col :span="11">
<a-col :span="2"> <span>项目: </span>
<a-button type="danger" @click="() => temp.projectList.splice(index, 1)" icon="delete"></a-button> <a-select
</a-col> :disabled="!item.nodeId"
</a-row> style="width: 80%"
v-model:value="item.projectId"
:placeholder="`请选择项目`"
>
<!-- <a-select-option value=""> 请先选择节点</a-select-option> -->
<template v-if="nodeProjectList[item.nodeId]">
<a-select-option
v-for="project in nodeProjectList[item.nodeId].projects"
:disabled="
temp.projectList.filter((item, nowIndex) => {
return (
item.nodeId === project.nodeId && item.projectId === project.projectId && nowIndex !== index
)
}).length > 0
"
:key="project.projectId"
>
{{ project.name }}
</a-select-option>
</template>
</a-select>
</a-col>
<a-col :span="2">
<a-button type="primary" danger @click="() => temp.projectList.splice(index, 1)"
><DeleteOutlined
/></a-button>
</a-col>
</a-row>
<a-button type="primary" @click="() => temp.projectList.push({})">添加</a-button> <a-button type="primary" @click="() => temp.projectList.push({})">添加</a-button>
</a-space>
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-modal> </a-modal>
@ -123,7 +136,7 @@
destroyOnClose destroyOnClose
placement="right" placement="right"
:width="`${this.getCollapsed ? 'calc(100vw - 80px)' : 'calc(100vw - 200px)'}`" :width="`${this.getCollapsed ? 'calc(100vw - 80px)' : 'calc(100vw - 200px)'}`"
:visible="logReadVisible" :open="logReadVisible"
@close=" @close="
() => { () => {
this.logReadVisible = false this.logReadVisible = false
@ -137,7 +150,7 @@
</template> </template>
<logReadView <logReadView
v-if="logReadVisible" v-if="logReadVisible"
:data="temp" :data="this.temp"
@changeTitle=" @changeTitle="
(logFile) => { (logFile) => {
const cacheData = { ...this.temp.cacheData, logFile: logFile } const cacheData = { ...this.temp.cacheData, logFile: logFile }
@ -148,11 +161,12 @@
</a-drawer> </a-drawer>
</div> </div>
</template> </template>
<script> <script>
import { deleteLogRead, editLogRead, getLogReadList } from '@/api/log-read' import { deleteLogRead, editLogRead, getLogReadList } from '@/api/log-read'
import { getNodeListAll, getProjectListAll } from '@/api/node' import { getNodeListAll, getProjectListAll } from '@/api/node'
import { CHANGE_PAGE, COMPUTED_PAGINATION, PAGE_DEFAULT_LIST_QUERY, itemGroupBy, parseTime } from '@/utils/const' import { CHANGE_PAGE, COMPUTED_PAGINATION, PAGE_DEFAULT_LIST_QUERY, itemGroupBy, parseTime } from '@/utils/const'
import { useGuideStore } from '@/stores/guide'
import { mapState } from 'pinia' import { mapState } from 'pinia'
import logReadView from './logReadView' import logReadView from './logReadView'
@ -172,21 +186,26 @@ export default {
temp: {}, temp: {},
editVisible: false, editVisible: false,
columns: [ columns: [
{ title: '名称', dataIndex: 'name', ellipsis: true, scopedSlots: { customRender: 'name' } }, {
title: '名称',
dataIndex: 'name',
ellipsis: true,
tooltip: true
},
{ {
title: '修改人', title: '修改人',
dataIndex: 'modifyUser', dataIndex: 'modifyUser',
ellipsis: true, ellipsis: true,
align: 'center', align: 'center',
scopedSlots: { customRender: 'modifyUser' }, tooltip: true,
width: 120 width: 120
}, },
{ {
title: '修改时间', title: '修改时间',
dataIndex: 'modifyTimeMillis', dataIndex: 'modifyTimeMillis',
sorter: true, sorter: true,
customRender: (text) => { customRender: ({ text }) => {
if (!text || text === '0') { if (!text || text === '0') {
return '' return ''
} }
@ -198,18 +217,19 @@ export default {
title: '操作', title: '操作',
dataIndex: 'operation', dataIndex: 'operation',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'operation' },
width: 180, width: 180,
align: 'center' align: 'center'
} }
], ],
rules: { rules: {
name: [{ required: true, message: '请填写日志项目名称', trigger: 'blur' }] name: [{ required: true, message: '请填写日志项目名称', trigger: 'blur' }]
} },
confirmLoading: false
} }
}, },
computed: { computed: {
...mapGetters(['getCollapsed']), ...mapState(useGuideStore, ['getCollapsed']),
pagination() { pagination() {
return COMPUTED_PAGINATION(this.listQuery) return COMPUTED_PAGINATION(this.listQuery)
} }
@ -277,7 +297,9 @@ export default {
}, },
// //
handleEdit(record) { handleEdit(record) {
this.temp = Object.assign({}, record, { projectList: JSON.parse(record.nodeProject) }) this.temp = Object.assign({}, record, {
projectList: JSON.parse(record.nodeProject)
})
this.loadNodeList().then(() => { this.loadNodeList().then(() => {
this.editVisible = true this.editVisible = true
@ -285,39 +307,41 @@ export default {
}, },
handleEditOk() { handleEditOk() {
// //
this.$refs['editForm'].validate((valid) => { this.$refs['editForm'].validate().then(() => {
if (!valid) {
return false
}
const temp = Object.assign({}, this.temp) const temp = Object.assign({}, this.temp)
temp.projectList = temp.projectList?.filter((item) => { temp.projectList = temp.projectList?.filter((item) => {
return item.nodeId && item.projectId return item.nodeId && item.projectId
}) })
if (!temp.projectList || !temp.projectList.length) { if (!temp.projectList || !temp.projectList.length) {
$notification.warn({ this.$notification.warn({
message: '至少选择一个节点和项目' message: '至少选择一个节点和项目'
}) })
return false return false
} }
// console.log(temp); // console.log(temp);
this.confirmLoading = true
editLogRead(temp).then((res) => { editLogRead(temp)
if (res.code === 200) { .then((res) => {
// if (res.code === 200) {
$notification.success({ //
message: res.msg this.$notification.success({
}) message: res.msg
this.$refs['editForm'].resetFields() })
this.editVisible = false this.$refs['editForm'].resetFields()
this.loadData() this.editVisible = false
} this.loadData()
}) }
})
.finally(() => {
this.confirmLoading = false
})
}) })
}, },
// //
handleDelete(record) { handleDelete(record) {
$confirm({ this.$confirm({
title: '系统提示', title: '系统提示',
zIndex: 1009,
content: '真的要删除日志搜索么?', content: '真的要删除日志搜索么?',
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',
@ -325,7 +349,7 @@ export default {
// //
deleteLogRead(record.id).then((res) => { deleteLogRead(record.id).then((res) => {
if (res.code === 200) { if (res.code === 200) {
$notification.success({ this.$notification.success({
message: res.msg message: res.msg
}) })
this.loadData() this.loadData()
@ -352,4 +376,3 @@ export default {
} }
} }
</script> </script>
<style scoped></style>

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<!-- 布局 --> <!-- 布局 -->
<a-layout class="file-layout node-full-content"> <a-layout class="file-layout">
<!-- 目录树 --> <!-- 目录树 -->
<a-layout-sider theme="light" class="sider" width="25%"> <a-layout-sider theme="light" class="sider" width="25%">
<div class="dir-container"> <div class="dir-container">
@ -24,7 +24,7 @@
</div> </div>
<a-directory-tree <a-directory-tree
:replace-fields="treeReplaceFields" :fieldNames="treeReplaceFields"
@select="nodeClick" @select="nodeClick"
:loadData="onTreeData" :loadData="onTreeData"
:treeData="treeList" :treeData="treeList"
@ -45,7 +45,7 @@
<a-input <a-input
placeholder="关键词,支持正则" placeholder="关键词,支持正则"
:style="`width: 250px`" :style="`width: 250px`"
v-model="temp.cacheData.keyword" v-model:value="temp.cacheData.keyword"
@pressEnter="sendSearchLog" @pressEnter="sendSearchLog"
> >
</a-input> </a-input>
@ -55,7 +55,7 @@
显示前N行 显示前N行
<a-input-number <a-input-number
id="inputNumber" id="inputNumber"
v-model="temp.cacheData.beforeCount" v-model:value="temp.cacheData.beforeCount"
:min="0" :min="0"
:max="1000" :max="1000"
@pressEnter="sendSearchLog" @pressEnter="sendSearchLog"
@ -65,22 +65,22 @@
显示后N行 显示后N行
<a-input-number <a-input-number
id="inputNumber" id="inputNumber"
v-model="temp.cacheData.afterCount" v-model:value="temp.cacheData.afterCount"
:min="0" :min="0"
:max="1000" :max="1000"
@pressEnter="sendSearchLog" @pressEnter="sendSearchLog"
/> />
</div> </div>
<a-popover title="正则语法参考"> <a-popover title="正则语法参考">
<template #content> <template v-slot:content>
<ul> <ul>
<li><b>^.*\d+.*$</b> - 匹配包含数字的行</li> <li><b>^.*\d+.*$</b> - 匹配包含数字的行</li>
<li><b>.*(a|b).*</b> - 匹配包含 a 或者 b 的行</li> <li><b>.*(a|b).*</b> - 匹配包含 a 或者 b 的行</li>
<li><b>.*(异常).*</b> - 匹配包含 异常 的行</li> <li><b>.*(异常).*</b> - 匹配包含 异常 的行</li>
</ul> </ul>
</template> </template>
<a-button type="link" style="padding: 0" icon="unordered-list" <a-button type="link" style="padding: 0"
><span style="margin-left: 2px">语法参考</span></a-button ><UnorderedListOutlined /><span style="margin-left: 2px">语法参考</span></a-button
> >
</a-popover> </a-popover>
</a-space> </a-space>
@ -111,7 +111,7 @@
文件前N行 文件前N行
<a-input-number <a-input-number
id="inputNumber" id="inputNumber"
v-model="temp.cacheData.head" v-model:value="temp.cacheData.head"
:min="0" :min="0"
:max="1000" :max="1000"
@pressEnter="sendSearchLog" @pressEnter="sendSearchLog"
@ -121,14 +121,14 @@
文件后N行 文件后N行
<a-input-number <a-input-number
id="inputNumber" id="inputNumber"
v-model="temp.cacheData.tail" v-model:value="temp.cacheData.tail"
:min="0" :min="0"
:max="1000" :max="1000"
@pressEnter="sendSearchLog" @pressEnter="sendSearchLog"
/> />
</div> </div>
<a-popover title="搜索配置参考"> <a-popover title="搜索配置参考">
<template #content> <template v-slot:content>
<ul> <ul>
<li><b>从尾搜索文件前0行文件后3行</b> - 在文件最后 3 行中搜索</li> <li><b>从尾搜索文件前0行文件后3行</b> - 在文件最后 3 行中搜索</li>
<li><b>从头搜索文件前0行文件后3行</b> - 在文件第 3 - 2147483647 行中搜索</li> <li><b>从头搜索文件前0行文件后3行</b> - 在文件第 3 - 2147483647 行中搜索</li>
@ -139,18 +139,18 @@
<li><b>从头搜索文件前20行文件后3行</b> - 在文件第 3 - 20 行中搜索</li> <li><b>从头搜索文件前20行文件后3行</b> - 在文件第 3 - 20 行中搜索</li>
</ul> </ul>
</template> </template>
<a-button type="link" style="padding: 0" icon="unordered-list" <a-button type="link" style="padding: 0"
><span style="margin-left: 2px">搜索参考</span></a-button ><UnorderedListOutlined /><span style="margin-left: 2px">搜索参考</span></a-button
> >
</a-popover> </a-popover>
</a-space> </a-space>
</a-space> </a-space>
</div> </div>
<a-tabs v-if="temp.cacheData" v-model="activeTagKey" :tabBarStyle="{ marginBottom: 0 }"> <a-tabs v-if="temp.cacheData" v-model:value="activeTagKey" :tabBarStyle="{ marginBottom: 0 }">
<template v-for="item in temp.projectList"> <template v-for="item in temp.projectList">
<a-tab-pane forceRender v-if="nodeName[item.nodeId]" :key="`${item.nodeId},${item.projectId}`"> <a-tab-pane forceRender v-if="nodeName[item.nodeId]">
<template #tab> <template v-slot:tab>
{{ nodeName[item.nodeId] && nodeName[item.nodeId].name }} {{ nodeName[item.nodeId] && nodeName[item.nodeId].name }}
{{ {{
nodeProjectList[item.nodeId] && nodeProjectList[item.nodeId] &&
@ -175,12 +175,15 @@
</a-layout> </a-layout>
</div> </div>
</template> </template>
<script> <script>
import { getNodeListAll, getProjectListAll } from '@/api/node' import { getNodeListAll, getProjectListAll } from '@/api/node'
import { getFileList } from '@/api/node-project' import { getFileList } from '@/api/node-project'
import { itemGroupBy } from '@/utils/const' import { itemGroupBy } from '@/utils/const'
import { getWebSocketUrl } from '@/api/config' import { getWebSocketUrl } from '@/api/config'
import { mapState } from 'pinia' import { mapState } from 'pinia'
import { useUserStore } from '@/stores/user'
import { useAppStore } from '@/stores/app'
import viewPre from '@/components/logView/view-pre' import viewPre from '@/components/logView/view-pre'
import { updateCache } from '@/api/log-read' import { updateCache } from '@/api/log-read'
@ -210,7 +213,8 @@ export default {
} }
}, },
computed: { computed: {
...mapGetters(['getLongTermToken', 'getWorkspaceId']), ...mapState(useUserStore, ['getLongTermToken']),
...mapState(useAppStore, ['getWorkspaceId']),
selectPath() { selectPath() {
if (!Object.keys(this.tempNode).length) { if (!Object.keys(this.tempNode).length) {
return '' return ''
@ -255,7 +259,7 @@ export default {
'/socket/console', '/socket/console',
`userId=${this.getLongTermToken}&id=${itemProjectData.id}&nodeId=${ `userId=${this.getLongTermToken}&id=${itemProjectData.id}&nodeId=${
item.nodeId item.nodeId
}&type=console&copyId=&workspaceId=${this.getWorkspaceId()}` }&type=console&workspaceId=${this.getWorkspaceId()}`
) )
const domId = `pre-dom-${item.nodeId},${item.projectId}` const domId = `pre-dom-${item.nodeId},${item.projectId}`
this.socketCache = { ...this.socketCache, [domId]: {} } this.socketCache = { ...this.socketCache, [domId]: {} }
@ -263,7 +267,11 @@ export default {
this.socketCache = { this.socketCache = {
...this.socketCache, ...this.socketCache,
[domId]: { socket: socket, projectId: item.projectId, nodeId: item.nodeId } [domId]: {
socket: socket,
projectId: item.projectId,
nodeId: item.nodeId
}
} }
// //
@ -278,27 +286,27 @@ export default {
}) })
this.activeTagKey = this.temp.cacheData.useNodeId + ',' + this.temp.cacheData.useProjectId this.activeTagKey = this.temp.cacheData.useNodeId + ',' + this.temp.cacheData.useProjectId
// console.log(cacheData); // console.log(cacheData);
// websocketserver
window.onbeforeunload = () => {
this.close()
}
}, },
beforeDestroy() { beforeUnmount() {
Object.keys(this.socketCache).forEach((item) => { this.close()
clearInterval(this.socketCache[item].heart)
})
},
destroyed() {
Object.keys(this.socketCache).forEach((item) => {
clearInterval(this.socketCache[item].heart)
})
}, },
methods: { methods: {
close() {
Object.keys(this.socketCache).forEach((item) => {
clearInterval(this.socketCache[item].heart)
this.socketCache[item].socket?.close()
})
},
initWebSocket(id, url) { initWebSocket(id, url) {
let socket const socket = new WebSocket(url)
if (!socket || socket.readyState !== socket.OPEN || socket.readyState !== socket.CONNECTING) {
socket = new WebSocket(url)
}
socket.onerror = (err) => { socket.onerror = (err) => {
console.error(err) console.error(err)
$notification.error({ this.$notification.error({
key: 'log-read-error', key: 'log-read-error',
message: 'web socket 错误,请检查是否开启 ws 代理' message: 'web socket 错误,请检查是否开启 ws 代理'
}) })
@ -307,9 +315,9 @@ export default {
socket.onclose = (err) => { socket.onclose = (err) => {
//onclose //onclose
console.error(err) console.error(err)
$notification.info({ this.$notification.info({
key: 'log-read-close', key: 'log-read-close',
message: '会话已经关闭' message: '会话已经关闭[tail-log]'
}) })
clearInterval(this.socketCache[id].heart) clearInterval(this.socketCache[id].heart)
} }
@ -377,7 +385,11 @@ export default {
nodeChange(value) { nodeChange(value) {
const keyArray = value.split(',') const keyArray = value.split(',')
const cacheData = { ...this.temp.cacheData, useNodeId: keyArray[0], useProjectId: keyArray[1] } const cacheData = {
...this.temp.cacheData,
useNodeId: keyArray[0],
useProjectId: keyArray[1]
}
this.temp = { ...this.temp, cacheData: cacheData } this.temp = { ...this.temp, cacheData: cacheData }
this.loadFileData() this.loadFileData()
// //
@ -392,14 +404,17 @@ export default {
if (node.dataRef.textFileEdit) { if (node.dataRef.textFileEdit) {
this.tempFileNode = node.dataRef this.tempFileNode = node.dataRef
// let cacheData = ; // let cacheData = ;
const cacheData = { ...this.temp.cacheData, logFile: this.selectFilePath } const cacheData = {
...this.temp.cacheData,
logFile: this.selectFilePath
}
this.temp = { ...this.temp, cacheData: cacheData } this.temp = { ...this.temp, cacheData: cacheData }
this.$emit('changeTitle', this.selectFilePath) this.$emit('changeTitle', this.selectFilePath)
// //
this.sendSearchLog() this.sendSearchLog()
} else { } else {
// //
$message.error('当前文件不可读,需要配置可读文件白名单') this.$message.error('当前文件不可读,需要配置可读文件授权')
} }
} }
}, },
@ -485,7 +500,8 @@ export default {
} }
// //
} }
} },
emits: ['changeTitle']
} }
</script> </script>
@ -502,13 +518,10 @@ export default {
.file-content { .file-content {
height: calc(100vh - 80px); height: calc(100vh - 80px);
overflow-y: auto; overflow-y: auto;
/* margin: 10px 10px 0; */
padding: 0 10px; padding: 0 10px;
background-color: #fff; background-color: #fff;
} }
.log-filter { .log-filter {
/* margin-top: -22px; */
/* margin-bottom: 10px; */
padding: 0 10px 10px; padding: 0 10px 10px;
line-height: 0; line-height: 0;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;

View File

@ -0,0 +1,743 @@
<template>
<div>
<a-modal
destroyOnClose
:open="true"
:closable="!uploading"
:footer="uploading ? null : undefined"
width="50%"
:keyboard="false"
:title="'分发项目-' + data.name"
@ok="handleDispatchOk"
:maskClosable="false"
:confirmLoading="confirmLoading"
@cancel="
() => {
$emit('cancel')
}
"
>
<a-form ref="dispatchForm" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
<a-form-item label="方式" name="type">
<a-radio-group v-model:value="temp.type" name="type" :disabled="!!percentage" @change="restForm">
<a-radio :value="'use-build'">构建产物</a-radio>
<a-radio :value="'file-storage'">文件中心</a-radio>
<a-radio :value="'static-file-storage'">静态文件</a-radio>
<a-radio :value="'upload'">上传文件</a-radio>
<a-radio :value="'download'">远程下载</a-radio>
</a-radio-group>
</a-form-item>
<!-- 手动上传 -->
<a-form-item label="选择分发文件" name="clearOld" v-if="temp.type === 'upload'">
<a-progress v-if="percentage" :percent="percentage">
<template #format="percent">
{{ percent }}%
<template v-if="percentageInfo.total"> ({{ renderSize(percentageInfo.total) }}) </template>
<template v-if="percentageInfo.duration"> 用时:{{ formatDuration(percentageInfo.duration) }} </template>
</template>
</a-progress>
<a-upload :file-list="fileList" :disabled="!!percentage" @remove="handleRemove" :before-upload="beforeUpload">
<LoadingOutlined v-if="percentage" />
<a-button v-else type="primary"><UploadOutlined />选择文件</a-button>
</a-upload>
</a-form-item>
<!-- 远程下载 -->
<a-form-item label="远程下载URL" name="url" v-else-if="temp.type === 'download'">
<a-input v-model:value="temp.url" placeholder="远程下载地址" />
</a-form-item>
<!-- 在线构建 -->
<template v-else-if="temp.type == 'use-build'">
<a-form-item label="选择构建">
<a-space>
{{ chooseBuildInfo.name }}
<a-button
type="primary"
@click="
() => {
chooseVisible = 1
}
"
>
选择构建
</a-button>
</a-space>
</a-form-item>
<a-form-item label="选择产物">
<a-space>
<a-tag v-if="chooseBuildInfo.buildNumberId">#{{ chooseBuildInfo.buildNumberId }}</a-tag>
<a-button
type="primary"
:disabled="!chooseBuildInfo.id"
@click="
() => {
chooseVisible = 2
}
"
>
选择产物
</a-button>
</a-space>
</a-form-item>
</template>
<!-- 文件中心 -->
<template v-else-if="temp.type === 'file-storage'">
<a-form-item label="选择文件">
<a-space>
{{ chooseFileInfo.name }}
<a-button
type="primary"
@click="
() => {
chooseVisible = 3
}
"
>
选择文件
</a-button>
</a-space>
</a-form-item></template
>
<!-- 静态文件 -->
<template v-else-if="temp.type === 'static-file-storage'">
<a-form-item label="选择文件">
<a-space>
{{ chooseFileInfo.name }}
<a-button
type="primary"
@click="
() => {
chooseVisible = 4
}
"
>
选择文件
</a-button>
</a-space>
</a-form-item></template
>
<a-form-item name="clearOld">
<template v-slot:label>
清空发布
<a-tooltip>
<template v-slot:title>
清空发布是指在上传新文件前,会将项目文件夹目录里面的所有文件先删除后再保存新文件
</template>
<QuestionCircleOutlined />
</a-tooltip>
</template>
<a-switch v-model:checked="temp.clearOld" checked-children="" un-checked-children="" />
</a-form-item>
<a-form-item name="unzip" v-if="temp.type !== 'use-build'">
<template v-slot:label>
是否解压
<a-tooltip>
<template v-slot:title>
如果上传的压缩文件是否自动解压 支持的压缩包类型有 tar.bz2, tar.gz, tar, bz2, zip, gz</template
>
<QuestionCircleOutlined />
</a-tooltip>
</template>
<a-switch v-model:checked="temp.autoUnzip" checked-children="" un-checked-children="" />
</a-form-item>
<a-form-item label="剔除文件夹" v-if="temp.autoUnzip">
<a-input-number
style="width: 100%"
v-model:value="temp.stripComponents"
:min="0"
placeholder="解压时候自动剔除压缩包里面多余的文件夹名"
/>
</a-form-item>
<a-form-item label="分发后操作" name="afterOpt">
<a-select v-model:value="temp.afterOpt" placeholder="请选择发布后操作">
<a-select-option v-for="item in afterOptList" :key="item.value">{{ item.title }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="secondaryDirectory" label="二级目录">
<a-input v-model:value="temp.secondaryDirectory" placeholder="不填写则发布至项目的根目录" />
</a-form-item>
<a-form-item name="selectProject" label="筛选项目" help="筛选之后本次发布操作只发布筛选项,并且只对本次操作生效">
<a-select mode="multiple" v-model:value="temp.selectProjectArray" placeholder="请选择指定发布的项目">
<a-select-option v-for="item in itemProjectList" :key="item.id" :value="`${item.projectId}@${item.nodeId}`">
{{ item.nodeName }}-{{ item.cacheProjectName || item.projectId }}
</a-select-option>
</a-select>
</a-form-item>
</a-form>
</a-modal>
<!-- 选择构建 -->
<a-drawer
destroyOnClose
:title="`选择构建`"
placement="right"
:open="chooseVisible === 1"
width="80vw"
:zIndex="1009"
@close="
() => {
this.chooseVisible = 0
}
"
:footer-style="{ textAlign: 'right' }"
>
<build-list
v-if="chooseVisible === 1"
:choose="'radio'"
layout="table"
mode="choose"
ref="buildList"
@confirm="
(data) => {
this.chooseBuildInfo = {
id: data[0].id,
name: data[0].name
}
this.chooseVisible = 0
}
"
@cancel="
() => {
this.chooseVisible = 0
}
"
></build-list>
<template #footer>
<a-space>
<a-button
@click="
() => {
this.chooseVisible = 0
}
"
>
取消
</a-button>
<a-button
type="primary"
@click="
() => {
this.$refs['buildList'].handerConfirm()
}
"
>
确认
</a-button>
</a-space>
</template>
</a-drawer>
<!-- 选择构建产物 -->
<a-drawer
destroyOnClose
:title="`选择构建产物`"
placement="right"
:open="chooseVisible === 2"
width="80vw"
:zIndex="1009"
@close="
() => {
this.chooseVisible = 0
}
"
:footer-style="{ textAlign: 'right' }"
>
<!-- 选择构建产物 -->
<build-history
v-if="chooseVisible === 2"
:choose="'radio'"
mode="choose"
ref="buildHistory"
@confirm="
(data) => {
this.chooseBuildInfo = {
...this.chooseBuildInfo,
buildNumberId: data[0]
}
this.chooseVisible = 0
}
"
@cancel="
() => {
this.chooseVisible = 0
}
"
></build-history>
<template #footer>
<a-space>
<a-button
@click="
() => {
this.chooseVisible = 0
}
"
>
取消
</a-button>
<a-button
type="primary"
@click="
() => {
this.$refs['buildHistory'].handerConfirm()
}
"
>
确认
</a-button>
</a-space>
</template>
</a-drawer>
<!-- 选择文件 -->
<a-drawer
destroyOnClose
:title="`选择文件`"
placement="right"
:open="chooseVisible === 3"
width="80vw"
:zIndex="1009"
@close="
() => {
this.chooseVisible = 0
}
"
:footer-style="{ textAlign: 'right' }"
>
<!-- 选择文件 -->
<file-storage
v-if="chooseVisible === 3"
:choose="'radio'"
mode="choose"
ref="fileStorage"
@confirm="
(data) => {
this.chooseFileInfo = { id: data[0].id, name: data[0].name }
this.chooseVisible = 0
}
"
@cancel="
() => {
this.chooseVisible = 0
}
"
></file-storage>
<template #footer>
<a-space>
<a-button
@click="
() => {
this.chooseVisible = 0
}
"
>
取消
</a-button>
<a-button
type="primary"
@click="
() => {
this.$refs['fileStorage'].handerConfirm()
}
"
>
确认
</a-button>
</a-space>
</template>
</a-drawer>
<!-- 选择静态文件 -->
<a-drawer
destroyOnClose
:title="`选择静态文件`"
placement="right"
:open="chooseVisible === 4"
width="80vw"
:zIndex="1009"
@close="
() => {
this.chooseVisible = 0
}
"
:footer-style="{ textAlign: 'right' }"
>
<!-- 选择静态文件 -->
<static-file-storage
v-if="chooseVisible === 4"
:choose="'radio'"
mode="choose"
ref="staticFileStorage"
@confirm="
(data) => {
this.chooseFileInfo = { id: data[0].id, name: data[0].name }
this.chooseVisible = 0
}
"
@cancel="
() => {
this.chooseVisible = 0
}
"
></static-file-storage>
<template #footer>
<a-space>
<a-button
@click="
() => {
this.chooseVisible = 0
}
"
>
取消
</a-button>
<a-button
type="primary"
@click="
() => {
this.$refs['staticFileStorage'].handerConfirm()
}
"
>
确认
</a-button>
</a-space>
</template>
</a-drawer>
</div>
</template>
<script>
import { uploadPieces } from '@/utils/upload-pieces'
import {
remoteDownload,
uploadDispatchFile,
uploadDispatchFileMerge,
afterOptList,
getDispatchProject,
useBuild,
useuseFileStorage,
useuseStaticFileStorage
} from '@/api/dispatch'
import { renderSize, formatDuration } from '@/utils/const'
import BuildList from '@/pages/build/list-info'
import BuildHistory from '@/pages/build/history'
import FileStorage from '@/pages/file-manager/fileStorage/list'
import StaticFileStorage from '@/pages/file-manager/staticFileStorage/list'
import { getBuildGet } from '@/api/build-info'
import { hasFile } from '@/api/file-manager/file-storage'
import { hasStaticFile } from '@/api/file-manager/static-storage'
export default {
components: {
BuildList,
BuildHistory,
FileStorage,
StaticFileStorage
},
props: {
data: Object
},
data() {
return {
afterOptList,
percentage: 0,
percentageInfo: {},
uploading: false,
itemProjectList: [],
fileList: [],
rules: {
afterOpt: [{ required: true, message: '请选择发布后操作', trigger: 'blur' }],
url: [{ required: true, message: '请输入远程地址', trigger: 'blur' }]
},
temp: { type: 'upload' },
chooseVisible: 0,
chooseBuildInfo: {},
chooseFileInfo: {},
confirmLoading: false
}
},
created() {
this.temp = {
...this.temp,
afterOpt: this.data.afterOpt,
id: this.data.id,
clearOld: this.data.clearOld,
secondaryDirectory: this.data.secondaryDirectory,
type: this.data.mode || 'upload'
}
getDispatchProject(this.data.id, true).then((res) => {
if (res.code === 200) {
this.itemProjectList = res.data?.projectList
this.percentage = 0
this.percentageInfo = {}
this.fileList = []
this.restForm()
}
})
if (this.data.mode === 'use-build') {
//
const buildData = (this.data.modeData || '').split(':')
if (buildData.length === 2) {
getBuildGet({
id: buildData[0]
}).then((res) => {
if (res.code === 200 && res.data) {
this.chooseBuildInfo = {
id: res.data.id,
name: res.data.name,
buildNumberId: buildData[1]
}
}
})
}
} else if (this.data.mode === 'download') {
//
this.temp = { ...this.temp, url: this.data.modeData }
} else if (this.data.mode === 'file-storage') {
//
if (this.data.modeData) {
hasFile({ fileSumMd5: this.data.modeData }).then((res) => {
if (res.code === 200 && res.data) {
this.chooseFileInfo = { id: res.data.id, name: res.data.name }
}
})
}
} else if (this.data.mode === 'static-file-storage') {
//
if (this.data.modeData) {
hasStaticFile({ fileId: this.data.modeData }).then((res) => {
if (res.code === 200 && res.data) {
this.chooseFileInfo = { id: res.data.id, name: res.data.name }
}
})
}
}
// console.log(this.temp);
},
methods: {
renderSize,
formatDuration,
//
handleRemove(file) {
const index = this.fileList.indexOf(file)
const newFileList = this.fileList.slice()
newFileList.splice(index, 1)
this.fileList = newFileList
return true
},
//
beforeUpload(file) {
//
this.fileList = [file]
return false
},
//
handleDispatchOk() {
// console.log(this.temp);
this.temp = {
...this.temp,
selectProject: (this.temp.selectProjectArray && this.temp.selectProjectArray.join(',')) || ''
}
//
this.$refs['dispatchForm'].validate().then((valid) => {
if (!valid) {
return false
}
// const key = this.temp.type;
if (this.temp.type == 'upload') {
//
if (this.fileList.length === 0) {
this.$notification.error({
message: '请选择文件'
})
return false
}
this.percentage = 0
this.percentageInfo = {}
let file = this.fileList[0]
this.uploading = true
this.confirmLoading = true
uploadPieces({
file,
process: (process, end, total, duration) => {
this.percentage = Math.max(this.percentage, process)
this.percentageInfo = { end, total, duration }
},
success: (uploadData) => {
//
uploadDispatchFileMerge({
...uploadData[0],
id: this.temp.id,
afterOpt: this.temp.afterOpt,
clearOld: this.temp.clearOld,
autoUnzip: this.temp.autoUnzip,
secondaryDirectory: this.temp.secondaryDirectory || '',
stripComponents: this.temp.stripComponents || 0,
selectProject: this.temp.selectProject
})
.then((res) => {
if (res.code === 200) {
this.fileList = []
this.$notification.success({
message: res.msg
})
}
setTimeout(() => {
this.percentage = 0
this.percentageInfo = {}
this.$emit('cancel')
}, 2000)
this.uploading = false
})
.catch(() => {
this.uploading = false
})
.finally(() => {
this.confirmLoading = false
})
},
error: (msg) => {
this.$notification.error({
message: msg
})
this.uploading = false
this.confirmLoading = false
},
uploadCallback: (formData) => {
return new Promise((resolve, reject) => {
formData.append('id', this.temp.id)
//
uploadDispatchFile(formData)
.then((res) => {
if (res.code === 200) {
resolve()
} else {
reject()
}
})
.catch(() => {
reject()
})
})
}
})
return true
} else if (this.temp.type == 'download') {
if (!this.temp.url) {
this.$notification.error({
message: '请填写远程URL'
})
return false
}
this.confirmLoading = true
remoteDownload(this.temp)
.then((res) => {
if (res.code === 200) {
this.$notification.success({
message: res.msg
})
this.$emit('cancel')
}
})
.finally(() => {
this.confirmLoading = false
})
return true
} else if (this.temp.type == 'use-build') {
//
if (!this.chooseBuildInfo || !this.chooseBuildInfo.id || !this.chooseBuildInfo.buildNumberId) {
this.$notification.error({
message: '请填写构建和产物'
})
return false
}
this.confirmLoading = true
useBuild({
...this.temp,
buildId: this.chooseBuildInfo.id,
buildNumberId: this.chooseBuildInfo.buildNumberId
})
.then((res) => {
if (res.code === 200) {
this.$notification.success({
message: res.msg
})
this.$emit('cancel')
}
})
.finally(() => {
this.confirmLoading = false
})
return true
} else if (this.temp.type == 'file-storage') {
//
if (!this.chooseFileInfo || !this.chooseFileInfo.id) {
this.$notification.error({
message: '请选择文件中心的文件'
})
return false
}
this.confirmLoading = true
useuseFileStorage({
...this.temp,
fileId: this.chooseFileInfo.id
})
.then((res) => {
if (res.code === 200) {
this.$notification.success({
message: res.msg
})
this.$emit('cancel')
}
})
.finally(() => {
this.confirmLoading = false
})
return true
} else if (this.temp.type == 'static-file-storage') {
//
if (!this.chooseFileInfo || !this.chooseFileInfo.id) {
this.$notification.error({
message: '请选择静态文件中的文件'
})
return false
}
this.confirmLoading = true
useuseStaticFileStorage({
...this.temp,
fileId: this.chooseFileInfo.id
})
.then((res) => {
if (res.code === 200) {
this.$notification.success({
message: res.msg
})
this.$emit('cancel')
}
})
.finally(() => {
this.confirmLoading = false
})
return true
}
})
},
//
restForm(e) {
// console.log(e);
if (e) {
this.temp = { ...this.temp, type: e.target.value }
}
this.$refs['dispatchForm'] && this.$refs['dispatchForm'].clearValidate()
}
},
emits: ['cancel']
}
</script>
<style scoped>
/deep/ .ant-progress-text {
width: auto;
}
</style>

View File

@ -1,132 +1,203 @@
<template> <template>
<div> <div>
<!-- 嵌套表格 --> <a-drawer
<a-table destroyOnClose
:loading="childLoading" :title="`查看 ${name} 状态`"
:columns="childColumns" placement="right"
size="middle" width="85vw"
:bordered="true" :open="true"
:data-source="list" @close="
:pagination="false" () => {
rowKey="id_no" $emit('close')
}
"
> >
<template #title> <a-tabs v-model:value="tabKey" tab-position="left">
<a-space> <a-tab-pane key="1" tab="状态">
<div> <!-- 嵌套表格 -->
当前状态 <a-table
<a-tag v-if="data.status === 2" color="green">{{ statusMap[data.status] || '未知' }}</a-tag> :loading="childLoading"
<a-tag v-else-if="data.status === 1 || data.status === 0" color="orange">{{ :columns="childColumns"
statusMap[data.status] || '未知' size="middle"
}}</a-tag> :bordered="true"
<a-tag v-else-if="data.status === 3 || data.status === 4" color="red">{{ :data-source="list"
statusMap[data.status] || '未知' :pagination="false"
}}</a-tag> rowKey="id_no"
<a-tag v-else>{{ statusMap[data.status] || '未知' }}</a-tag> :scroll="{
x: 'max-content'
}"
>
<template #title>
<a-space>
<div>
当前状态
<a-tag v-if="data.status === 2" color="green">{{ statusMap[data.status] || '未知' }}</a-tag>
<a-tag v-else-if="data.status === 1 || data.status === 0" color="orange">{{
statusMap[data.status] || '未知'
}}</a-tag>
<a-tag v-else-if="data.status === 3 || data.status === 4" color="red">{{
statusMap[data.status] || '未知'
}}</a-tag>
<a-tag v-else>{{ statusMap[data.status] || '未知' }}</a-tag>
</div>
<div>状态描述{{ data.statusMsg || '-' }}</div>
<a-button type="primary" size="small" :loading="childLoading" @click="loadData">刷新</a-button>
<a-statistic-countdown
format=" s 秒"
title="刷新倒计时"
:value="countdownTime"
@finish="silenceLoadData"
/>
</a-space>
</template>
<template #bodyCell="{ column, text, record, index }">
<template v-if="column.dataIndex === 'nodeId'">
<a-tooltip placement="topLeft" :title="text">
<a-button type="link" style="padding: 0" size="small" @click="toNode(text)">
<span>{{ nodeNameMap[text] || text }}</span>
<FullscreenOutlined />
</a-button>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'projectName'">
<a-tooltip placement="topLeft" :title="text">
<template v-if="record.disabled">
<a-tooltip title="当前项目被禁用">
<EyeInvisibleOutlined />
</a-tooltip>
</template>
<span>{{ text || record.cacheProjectName }}</span>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'outGivingStatus'">
<a-tag v-if="text === 2" color="green">{{ dispatchStatusMap[text] || '未知' }}</a-tag>
<a-tag v-else-if="text === 1 || text === 0 || text === 5" color="orange">{{
dispatchStatusMap[text] || '未知'
}}</a-tag>
<a-tag v-else-if="text === 3 || text === 4 || text === 6" color="red">{{
dispatchStatusMap[text] || '未知'
}}</a-tag>
<a-tag v-else>{{ dispatchStatusMap[text] || '未知' }}</a-tag>
</template>
<template v-else-if="column.dataIndex === 'outGivingResultMsg'">
<a-tooltip placement="topLeft" :title="readJsonStrField(record.outGivingResult, 'msg')">
<span
>{{ readJsonStrField(record.outGivingResult, 'code') }}-{{
readJsonStrField(record.outGivingResult, 'msg') || record.outGivingResult
}}</span
>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'outGivingResultTime'">
<a-tooltip placement="topLeft" :title="readJsonStrField(record.outGivingResult, 'upload_duration')">
<span>{{ readJsonStrField(record.outGivingResult, 'upload_duration') }}</span>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'outGivingResultSize'">
<a-tooltip placement="topLeft" :title="readJsonStrField(record.outGivingResult, 'upload_file_size')">
{{ readJsonStrField(record.outGivingResult, 'upload_file_size') }}
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'outGivingResultMsgData'">
<a-tooltip placement="topLeft" :title="`${readJsonStrField(record.outGivingResult, 'data')}`">
<template v-if="record.fileSize">
{{ Math.floor((record.progressSize / record.fileSize) * 100) }}%
</template>
{{ readJsonStrField(record.outGivingResult, 'data') }}
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'projectStatus'">
<a-tooltip v-if="record.errorMsg" :title="record.errorMsg">
<WarningOutlined />
</a-tooltip>
<a-switch
v-else
:checked="text"
:disabled="true"
size="small"
checked-children="运行中"
un-checked-children="未运行"
/>
</template>
<template v-else-if="column.dataIndex === 'projectPid'">
<a-tooltip
placement="topLeft"
:title="`进程号:${record.projectPid || '-'} / 端口号:${record.projectPort || '-'}`"
>
<span>{{ record.projectPid || '-' }}/{{ record.projectPort || '-' }}</span>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'child-operation'">
<a-space>
<a-button size="small" :disabled="!record.projectName" type="primary" @click="handleFile(record)"
>文件</a-button
>
<a-button size="small" :disabled="!record.projectName" type="primary" @click="handleConsole(record)"
>控制台</a-button
>
</a-space>
</template>
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="2" tab="配置">
<!-- 配置分发 -->
<div style="width: 50vw">
<draggable v-model="list" :group="`sortValue`" item-key="id" handle=".move" chosenClass="box-shadow">
<template #item="{ element }">
<a-row class="item-row">
<a-col :span="18">
<span> 节点名 {{ element.nodeName }} </span>
<span> 项目名 {{ element.cacheProjectName }} </span>
</a-col>
<a-col :span="6">
<a-space>
<a-switch
checked-children="启用"
un-checked-children="禁用"
:checked="element.disabled ? false : true"
@change="
(checked) => {
list = list.map((item2) => {
if (element.id === item2.id) {
item2.disabled = !checked
}
return { ...item2 }
})
}
"
/>
<a-button
type="primary"
danger
size="small"
@click="handleRemoveProject(element)"
:disabled="!list || list.length <= 1"
>
解绑
</a-button>
<a-tooltip placement="left" :title="`长按可以拖动排序`" class="move">
<MenuOutlined />
</a-tooltip>
</a-space>
</a-col>
</a-row>
</template>
</draggable>
<a-col style="margin-top: 10px">
<a-space>
<a-button type="primary" size="small" @click="viewDispatchManagerOk">保存</a-button>
</a-space>
</a-col>
</div> </div>
<div>状态描述{{ data.statusMsg || '-' }}</div> </a-tab-pane>
<a-button type="primary" :loading="childLoading" @click="loadData">刷新</a-button> </a-tabs>
</a-drawer>
<a-statistic-countdown format=" s 秒" title="刷新倒计时" :value="countdownTime" @finish="silenceLoadData" />
</a-space>
</template>
<a-tooltip #nodeId slot-scope="text" placement="topLeft" :title="text">
<a-button type="link" style="padding: 0" size="small" @click="toNode(text)">
<span>{{ nodeNameMap[text] || text }}</span>
<a-icon type="fullscreen" />
</a-button>
</a-tooltip>
<template #projectName slot-scope="text, item">
<template v-if="item.disabled">
<a-tooltip title="当前项目被禁用">
<a-icon type="eye-invisible" />
</a-tooltip>
</template>
<a-tooltip #projectName placement="topLeft" :title="text">
<span>{{ text || item.cacheProjectName }}</span>
</a-tooltip>
</template>
<template #outGivingStatus slot-scope="text">
<a-tag v-if="text === 2" color="green">{{ dispatchStatusMap[text] || '未知' }}</a-tag>
<a-tag v-else-if="text === 1 || text === 0 || text === 5" color="orange">{{
dispatchStatusMap[text] || '未知'
}}</a-tag>
<a-tag v-else-if="text === 3 || text === 4 || text === 6" color="red">{{
dispatchStatusMap[text] || '未知'
}}</a-tag>
<a-tag v-else>{{ dispatchStatusMap[text] || '未知' }}</a-tag>
</template>
<a-tooltip
#outGivingResultMsg
slot-scope="text, item"
placement="topLeft"
:title="readJsonStrField(item.outGivingResult, 'msg')"
>
<span
>{{ readJsonStrField(item.outGivingResult, 'code') }}-{{
readJsonStrField(item.outGivingResult, 'msg') || item.outGivingResult
}}</span
>
</a-tooltip>
<a-tooltip
#outGivingResultTime
slot-scope="text, item"
placement="topLeft"
:title="readJsonStrField(item.outGivingResult, 'upload_duration')"
>
<span>{{ readJsonStrField(item.outGivingResult, 'upload_duration') }}</span>
</a-tooltip>
<a-tooltip
#outGivingResultSize
slot-scope="text, item"
placement="topLeft"
:title="readJsonStrField(item.outGivingResult, 'upload_file_size')"
>
{{ readJsonStrField(item.outGivingResult, 'upload_file_size') }}
</a-tooltip>
<a-tooltip
#outGivingResultMsgData
slot-scope="text, item"
placement="topLeft"
:title="`${readJsonStrField(item.outGivingResult, 'data')}`"
>
<template v-if="item.fileSize"> {{ Math.floor((item.progressSize / item.fileSize) * 100) }}% </template>
{{ readJsonStrField(item.outGivingResult, 'data') }}
</a-tooltip>
<template #projectStatus slot-scope="text, item">
<a-tooltip v-if="item.errorMsg" :title="item.errorMsg">
<a-icon type="warning" />
</a-tooltip>
<a-switch
v-else
:checked="text"
:disabled="true"
size="small"
checked-children="运行中"
un-checked-children="未运行"
/>
</template>
<a-tooltip
#projectPid
slot-scope="text, record"
placement="topLeft"
:title="`进程号:${record.projectPid || '-'} / 端口号:${record.projectPort || '-'}`"
>
<span>{{ record.projectPid || '-' }}/{{ record.projectPort || '-' }}</span>
</a-tooltip>
<template #child-operation slot-scope="text, record">
<a-space>
<a-button size="small" :disabled="!record.projectName" type="primary" @click="handleFile(record)"
>文件</a-button
>
<a-button size="small" :disabled="!record.projectName" type="primary" @click="handleConsole(record)"
>控制台</a-button
>
</a-space>
</template>
</a-table>
<!-- 项目文件组件 --> <!-- 项目文件组件 -->
<a-drawer <a-drawer
@ -134,7 +205,7 @@
:title="drawerTitle" :title="drawerTitle"
placement="right" placement="right"
width="85vw" width="85vw"
:visible="drawerFileVisible" :open="drawerFileVisible"
@close="onFileClose" @close="onFileClose"
> >
<file <file
@ -152,7 +223,7 @@
:title="drawerTitle" :title="drawerTitle"
placement="right" placement="right"
width="85vw" width="85vw"
:visible="drawerConsoleVisible" :open="drawerConsoleVisible"
@close="onConsoleClose" @close="onConsoleClose"
> >
<console <console
@ -169,7 +240,7 @@
:title="drawerTitle" :title="drawerTitle"
placement="right" placement="right"
width="85vw" width="85vw"
:visible="drawerReadFileVisible" :open="drawerReadFileVisible"
@close="onReadFileClose" @close="onReadFileClose"
> >
<file-read <file-read
@ -183,8 +254,15 @@
</a-drawer> </a-drawer>
</div> </div>
</template> </template>
<script> <script>
import { getDispatchProject, dispatchStatusMap, statusMap } from '@/api/dispatch' import {
getDispatchProject,
dispatchStatusMap,
statusMap,
removeProject,
saveDispatchProjectConfig
} from '@/api/dispatch'
import { getNodeListAll } from '@/api/node' import { getNodeListAll } from '@/api/node'
import { getRuningProjectInfo } from '@/api/node-project' import { getRuningProjectInfo } from '@/api/node-project'
import { import {
@ -199,25 +277,30 @@ import {
import File from '@/pages/node/node-layout/project/project-file' import File from '@/pages/node/node-layout/project/project-file'
import Console from '@/pages/node/node-layout/project/project-console' import Console from '@/pages/node/node-layout/project/project-console'
import FileRead from '@/pages/node/node-layout/project/project-file-read' import FileRead from '@/pages/node/node-layout/project/project-file-read'
import draggable from 'vuedraggable-es'
export default { export default {
components: { components: {
File, File,
Console, Console,
FileRead FileRead,
draggable
}, },
props: { props: {
id: { id: {
type: String type: String
},
name: {
type: String
} }
}, },
data() { data() {
return { return {
loading: false, childLoading: true,
childLoading: false,
statusMap, statusMap,
dispatchStatusMap, dispatchStatusMap,
list: [], list: [],
tabKey: '1',
data: {}, data: {},
drawerTitle: '', drawerTitle: '',
drawerFileVisible: false, drawerFileVisible: false,
@ -226,82 +309,79 @@ export default {
nodeNameMap: {}, nodeNameMap: {},
childColumns: [ childColumns: [
{ title: '节点名称', dataIndex: 'nodeId', width: 120, ellipsis: true, scopedSlots: { customRender: 'nodeId' } }, {
title: '节点名称',
dataIndex: 'nodeId',
width: 120,
ellipsis: true
},
{ {
title: '项目名称', title: '项目名称',
dataIndex: 'projectName', dataIndex: 'projectName',
width: 120, width: 120,
ellipsis: true, ellipsis: true
scopedSlots: { customRender: 'projectName' }
}, },
{ {
title: '项目状态', title: '项目状态',
dataIndex: 'projectStatus', dataIndex: 'projectStatus',
width: 120, width: 120,
ellipsis: true, ellipsis: true
scopedSlots: { customRender: 'projectStatus' }
}, },
{ {
title: '进程/端口', title: '进程/端口',
dataIndex: 'projectPid', dataIndex: 'projectPid',
width: '120px', width: '120px',
ellipsis: true, ellipsis: true
scopedSlots: { customRender: 'projectPid' }
}, },
{ {
title: '分发状态', title: '分发状态',
dataIndex: 'outGivingStatus', dataIndex: 'outGivingStatus',
width: '120px', width: '120px'
scopedSlots: { customRender: 'outGivingStatus' }
}, },
{ {
title: '分发结果', title: '分发结果',
dataIndex: 'outGivingResultMsg', dataIndex: 'outGivingResultMsg',
ellipsis: true, ellipsis: true,
width: 120, width: 120
scopedSlots: { customRender: 'outGivingResultMsg' }
}, },
{ {
title: '分发状态消息', title: '分发状态消息',
dataIndex: 'outGivingResultMsgData', dataIndex: 'outGivingResultMsgData',
ellipsis: true, ellipsis: true,
width: 120, width: 120
scopedSlots: { customRender: 'outGivingResultMsgData' }
}, },
{ {
title: '分发耗时', title: '分发耗时',
dataIndex: 'outGivingResultTime', dataIndex: 'outGivingResultTime',
width: '120px', width: '120px'
scopedSlots: { customRender: 'outGivingResultTime' }
}, },
{ {
title: '文件大小', title: '文件大小',
dataIndex: 'outGivingResultSize', dataIndex: 'outGivingResultSize',
width: '100px', width: '100px'
scopedSlots: { customRender: 'outGivingResultSize' }
}, },
{ {
title: '最后分发时间', title: '最后分发时间',
dataIndex: 'lastTime', dataIndex: 'lastTime',
width: '170px', width: '170px',
ellipsis: true, ellipsis: true,
customRender: (text) => parseTime(text) customRender: ({ text }) => parseTime(text)
}, },
{ {
title: '操作', title: '操作',
dataIndex: 'child-operation', dataIndex: 'child-operation',
fixed: 'right', fixed: 'right',
scopedSlots: { customRender: 'child-operation' },
width: '140px', width: '140px',
align: 'center' align: 'center'
} }
], ],
countdownTime: Date.now(), countdownTime: Date.now(),
refreshInterval: 5 refreshInterval: 5,
temp: {}
} }
}, },
computed: {}, computed: {},
watch: {}, watch: {},
created() { created() {
@ -337,6 +417,13 @@ export default {
}, },
// //
silenceLoadData() { silenceLoadData() {
if (this.tabKey !== '1') {
//
//
this.countdownTime = Date.now() + this.refreshInterval * 1000
return
}
this.childLoading = true
this.handleReloadById().then(() => { this.handleReloadById().then(() => {
// //
this.countdownTime = Date.now() + this.refreshInterval * 1000 this.countdownTime = Date.now() + this.refreshInterval * 1000
@ -350,7 +437,10 @@ export default {
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
let projectList = let projectList =
res.data?.projectList?.map((item) => { res.data?.projectList?.map((item) => {
return { ...item, id_no: `${item.id}-${item.nodeId}-${item.projectId}-${new Date().getTime()}` } return {
...item,
id_no: `${item.id}-${item.nodeId}-${item.projectId}-${new Date().getTime()}`
}
}) || [] }) || []
this.data = res.data?.data || {} this.data = res.data?.data || {}
let oldProjectList = this.list let oldProjectList = this.list
@ -363,13 +453,14 @@ export default {
const nodeProjects = itemGroupBy(projectList, 'nodeId') const nodeProjects = itemGroupBy(projectList, 'nodeId')
this.getRuningProjectInfo(nodeProjects) this.getRuningProjectInfo(nodeProjects)
} }
this.childLoading = false
resolve() resolve()
}) })
.catch(() => { .catch(() => {
resolve()
})
.finally(() => {
// //
this.childLoading = false this.childLoading = false
resolve()
}) })
}) })
}, },
@ -419,7 +510,12 @@ export default {
} else { } else {
this.list = this.list.map((element) => { this.list = this.list.map((element) => {
if (element.nodeId === data.type) { if (element.nodeId === data.type) {
return { ...element, projectStatus: false, projectPid: '-', errorMsg: res2.msg } return {
...element,
projectStatus: false,
projectPid: '-',
errorMsg: res2.msg
}
} }
return element return element
}) })
@ -430,7 +526,12 @@ export default {
.catch(() => { .catch(() => {
this.list = this.list.map((element) => { this.list = this.list.map((element) => {
if (element.nodeId === data.type) { if (element.nodeId === data.type) {
return { ...element, projectStatus: false, projectPid: '-', errorMsg: '网络异常' } return {
...element,
projectStatus: false,
projectPid: '-',
errorMsg: '网络异常'
}
} }
return element return element
}) })
@ -496,25 +597,86 @@ export default {
} }
}) })
window.open(newpage.href, '_blank') window.open(newpage.href, '_blank')
},
//
handleRemoveProject(item) {
const html =
"<b style='font-size: 20px;'>真的要释放(删除)当前项目么?</b>" +
"<ul style='font-size: 20px;color:red;font-weight: bold;'>" +
'<li>不会真实请求节点删除项目信息</b></li>' +
'<li>一般用于服务器无法连接且已经确定不再使用</li>' +
'<li>如果误操作会产生冗余数据!!!</li>' +
' </ul>'
this.$confirm({
title: '危险操作!!!',
zIndex: 1009,
content: h('div', null, [h('p', { innerHTML: html }, null)]),
okButtonProps: { type: 'primary', size: 'small', danger: true },
cancelButtonProps: { type: 'primary' },
okText: '确认',
cancelText: '取消',
onOk: () => {
removeProject({
nodeId: item.nodeId,
projectId: item.projectId,
id: this.id
}).then((res) => {
if (res.code === 200) {
this.$notification.success({
message: res.msg
})
this.loadData()
}
})
}
})
},
//
viewDispatchManagerOk() {
const temp = {
data: this.list.map((item, index) => {
return {
nodeId: item.nodeId,
projectId: item.projectId,
sortValue: index,
disabled: item.disabled
}
}),
id: this.id
}
saveDispatchProjectConfig(temp).then((res) => {
if (res.code === 200) {
this.$notification.success({
message: res.msg
})
}
})
} }
} },
emits: ['close']
} }
</script> </script>
<style scoped> <style scoped>
/deep/ .ant-progress-text { /deep/ .ant-progress-text {
width: auto; width: auto;
} }
/* .replica-btn-del {
position: absolute;
right: 0;
top: 74px;
} */
/deep/ .ant-statistic div { /deep/ .ant-statistic div {
display: inline-block; display: inline-block;
} }
/deep/ .ant-statistic-content-value, /deep/ .ant-statistic-content-value,
/deep/ .ant-statistic-content { /deep/ .ant-statistic-content {
font-size: 16px; font-size: 16px;
} }
.box-shadow {
box-shadow: 0 0 10px 5px rgba(223, 222, 222, 0.5);
border-radius: 5px;
}
.item-row {
padding: 10px;
margin: 5px;
border: 1px solid #e8e8e8;
border-radius: 2px;
}
</style> </style>

View File

@ -52,7 +52,7 @@ export default {
data() { data() {
return { return {
temp: {}, temp: {},
submitAble: false submitAble: true
} }
}, },
mounted() { mounted() {
@ -61,9 +61,11 @@ export default {
methods: { methods: {
// load data // load data
loadData() { loadData() {
this.loading = true
getDispatchWhiteList({ workspaceId: this.workspaceId }).then((res) => { getDispatchWhiteList({ workspaceId: this.workspaceId }).then((res) => {
if (res.code === 200) { if (res.code === 200) {
this.temp = res.data this.temp = res.data
this.submitAble = false
} }
}) })
}, },

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="full-content"> <div>
<template v-if="this.useSuggestions"> <template v-if="this.useSuggestions">
<a-result <a-result
title="当前工作空间还没有 Docker" title="当前工作空间还没有 Docker"
@ -23,6 +23,9 @@
bordered bordered
rowKey="id" rowKey="id"
:row-selection="rowSelection" :row-selection="rowSelection"
:scroll="{
x: 'max-content'
}"
> >
<template v-slot:title> <template v-slot:title>
<a-space> <a-space>
@ -41,59 +44,68 @@
> >
</a-space> </a-space>
</template> </template>
<template v-slot:tooltip="text"> <template #bodyCell="{ column, text, record }">
<a-tooltip placement="topLeft" :title="text"> <template v-if="column.tooltip">
<span>{{ text }}</span> <a-tooltip placement="topLeft" :title="text">
</a-tooltip> <span>{{ text }}</span>
</template>
<template v-slot:status="text, record">
<template v-if="record.machineDocker">
<a-tag color="green" v-if="record.machineDocker.status === 1">正常</a-tag>
<a-tooltip v-else :title="record.machineDocker.failureMsg">
<a-tag color="red">无法连接</a-tag>
</a-tooltip> </a-tooltip>
</template> </template>
<a-tooltip v-else title="集群关联的 docker 信息丢失,不能继续使用管理功能"> <template v-else-if="column.dataIndex instanceof Array && column.dataIndex.includes('status')">
<a-tag color="red">信息丢失</a-tag> <template v-if="record.machineDocker">
</a-tooltip> <a-tag color="green" v-if="record.machineDocker.status === 1">正常</a-tag>
</template> <a-tooltip v-else :title="record.machineDocker.failureMsg">
<template v-slot:tags="tags"> <a-tag color="red">无法连接</a-tag>
<a-tooltip </a-tooltip>
:title=" </template>
(tags || '')
.split(':') <a-tooltip v-else title="集群关联的 docker 信息丢失,不能继续使用管理功能">
.filter((item) => item) <a-tag color="red">信息丢失</a-tag>
.join(',') </a-tooltip>
" </template>
> <template v-else-if="column.dataIndex === 'tags'">
<a-tag v-for="item in (tags || '').split(':').filter((item) => item)" :key="item"> {{ item }}</a-tag> <a-tooltip
</a-tooltip> :title="
</template> (text || '')
<template v-slot:operation="text, record"> .split(':')
<a-space> .filter((item) => item)
<a-button .join(',')
size="small" "
type="primary"
:disabled="!record.machineDocker || record.machineDocker.status !== 1"
@click="handleConsole(record)"
>控制台</a-button
> >
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button> <a-tag v-for="item in (text || '').split(':').filter((item) => item)" :key="item"> {{ item }}</a-tag>
<a-button size="small" type="primary" danger @click="handleDelete(record)">删除</a-button> </a-tooltip>
</a-space> </template>
<template v-else-if="column.dataIndex === 'operation'">
<a-space>
<a-button
size="small"
type="primary"
:disabled="!record.machineDocker || record.machineDocker.status !== 1"
@click="handleConsole(record)"
>控制台</a-button
>
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
<a-button size="small" type="primary" danger @click="handleDelete(record)">删除</a-button>
</a-space>
</template>
</template> </template>
</a-table> </a-table>
<!-- 编辑区 --> <!-- 编辑区 -->
<a-modal destroyOnClose v-model:value="editVisible" title="编辑 Docker" @ok="handleEditOk" :maskClosable="false"> <a-modal
destroyOnClose
:confirmLoading="confirmLoading"
v-model:open="editVisible"
title="编辑 Docker"
@ok="handleEditOk"
:maskClosable="false"
>
<a-form ref="editForm" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }"> <a-form ref="editForm" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }">
<a-form-item label="容器名称" name="name"> <a-form-item label="容器名称" name="name">
<a-input v-model:value="temp.name" placeholder="容器名称" /> <a-input v-model:value="temp.name" placeholder="容器名称" />
</a-form-item> </a-form-item>
<a-form-item label="标签" name="tagInput" help="标签用于容器构建选择容器功能fromTag"> <a-form-item label="标签" name="tagInput" help="标签用于容器构建选择容器功能fromTag">
<template> <a-space direction="vertical" style="width: 100%">
<div> <div>
<a-tooltip :key="index" :title="tag" v-for="(tag, index) in temp.tagsArray"> <a-tooltip :key="index" :title="tag" v-for="(tag, index) in temp.tagsArray">
<a-tag <a-tag
@ -109,32 +121,31 @@
</a-tag> </a-tag>
</a-tooltip> </a-tooltip>
</div> </div>
</template>
<a-input <a-input
v-if="temp.inputVisible" v-if="temp.inputVisible"
ref="tagInput" ref="tagInput"
type="text" type="text"
size="small" size="small"
placeholder="请输入标签名 字母数字 长度 1-10" placeholder="请输入标签名 字母数字 长度 1-10"
v-model:value="temp.tagInput" v-model:value="temp.tagInput"
@blur="handleInputConfirm" @blur="handleInputConfirm"
@keyup.enter="handleInputConfirm" @keyup.enter="handleInputConfirm"
/> />
<template v-else> <template v-else>
<a-tag <a-tag
v-if="!temp.tagsArray || temp.tagsArray.length < 10" v-if="!temp.tagsArray || temp.tagsArray.length < 10"
style="background: #fff; borderstyle: dashed" style="background: #fff; borderstyle: dashed"
@click="showInput" @click="showInput"
> >
<a-icon type="plus" /> 添加 <PlusOutlined /> 添加
</a-tag> </a-tag>
</template> </template>
</a-space>
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-modal> </a-modal>
<!-- 控制台 -->
<!-- <a-drawer destroyOnClose :title="`${temp.name} 控制台`" placement="right" :width="`${this.getCollapsed ? 'calc(100vw - 80px)' : 'calc(100vw - 200px)'}`" :visible="consoleVisible" @close="onClose"> -->
<console <console
v-if="consoleVisible" v-if="consoleVisible"
:visible="consoleVisible" :visible="consoleVisible"
@ -146,7 +157,8 @@
<!-- 同步到其他工作空间 --> <!-- 同步到其他工作空间 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model:value="syncToWorkspaceVisible" :confirmLoading="confirmLoading"
v-model:open="syncToWorkspaceVisible"
title="同步到其他工作空间" title="同步到其他工作空间"
@ok="handleSyncToWorkspace" @ok="handleSyncToWorkspace"
:maskClosable="false" :maskClosable="false"
@ -182,7 +194,7 @@
<script> <script>
import { deleteDcoker, dockerList, editDocker, syncToWorkspace } from '@/api/docker-api' import { deleteDcoker, dockerList, editDocker, syncToWorkspace } from '@/api/docker-api'
import { CHANGE_PAGE, COMPUTED_PAGINATION, PAGE_DEFAULT_LIST_QUERY, parseTime } from '@/utils/const' import { CHANGE_PAGE, COMPUTED_PAGINATION, PAGE_DEFAULT_LIST_QUERY, parseTime } from '@/utils/const'
import { useGuideStore } from '@/stores/guide'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { useAppStore } from '@/stores/app' import { useAppStore } from '@/stores/app'
import Console from './console' import Console from './console'
@ -216,46 +228,43 @@ export default {
}, },
{ {
title: 'host', title: 'host',
dataIndex: 'machineDocker.host', dataIndex: ['machineDocker', 'host'],
width: 150, width: 150,
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
{ {
title: '状态', title: '状态',
dataIndex: 'machineDocker.status', dataIndex: ['machineDocker', 'status'],
ellipsis: true, ellipsis: true,
align: 'center', align: 'center',
width: '100px', width: '100px'
scopedSlots: { customRender: 'status' }
}, },
{ {
title: 'docker版本', title: 'docker版本',
dataIndex: 'machineDocker.dockerVersion', dataIndex: ['machineDocker', 'dockerVersion'],
ellipsis: true, ellipsis: true,
width: '120px', width: '120px',
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
{ {
title: '标签', title: '标签',
dataIndex: 'tags', dataIndex: 'tags',
width: 100, width: 100,
ellipsis: true, ellipsis: true
scopedSlots: { customRender: 'tags' }
}, },
{ {
title: '最后修改人', title: '最后修改人',
dataIndex: 'modifyUser', dataIndex: 'modifyUser',
width: '120px', width: '120px',
ellipsis: true, ellipsis: true
scopedSlots: { customRender: 'modifyUser' }
}, },
{ {
title: '创建时间', title: '创建时间',
dataIndex: 'createTimeMillis', dataIndex: 'createTimeMillis',
ellipsis: true, ellipsis: true,
sorter: true, sorter: true,
customRender: (text) => parseTime(text), customRender: ({ text }) => parseTime(text),
width: '170px' width: '170px'
}, },
{ {
@ -263,13 +272,13 @@ export default {
dataIndex: 'modifyTimeMillis', dataIndex: 'modifyTimeMillis',
sorter: true, sorter: true,
ellipsis: true, ellipsis: true,
customRender: (text) => parseTime(text), customRender: ({ text }) => parseTime(text),
width: '170px' width: '170px'
}, },
{ {
title: '操作', title: '操作',
dataIndex: 'operation', dataIndex: 'operation',
scopedSlots: { customRender: 'operation' },
fixed: 'right', fixed: 'right',
align: 'center', align: 'center',
width: '190px' width: '190px'
@ -291,11 +300,11 @@ export default {
}, },
workspaceList: [], workspaceList: [],
tableSelections: [], tableSelections: [],
syncToWorkspaceVisible: false syncToWorkspaceVisible: false,
confirmLoading: false
} }
}, },
computed: { computed: {
...mapState(useGuideStore, ['getCollapsed']),
...mapState(useUserStore, ['getUserInfo']), ...mapState(useUserStore, ['getUserInfo']),
...mapState(useAppStore, ['getWorkspaceId']), ...mapState(useAppStore, ['getWorkspaceId']),
pagination() { pagination() {
@ -398,27 +407,28 @@ export default {
// //
handleEditOk() { handleEditOk() {
// //
this.$refs['editForm'].validate((valid) => { this.$refs['editForm'].validate().then(() => {
if (!valid) {
return false
}
const temp = Object.assign({}, this.temp) const temp = Object.assign({}, this.temp)
temp.tags = (temp.tagsArray || []).join(',') temp.tags = (temp.tagsArray || []).join(',')
delete temp.tagsArray delete temp.tagsArray
delete temp.inputVisible delete temp.inputVisible
delete temp.tagInput delete temp.tagInput
this.confirmLoading = true
editDocker(temp).then((res) => { editDocker(temp)
if (res.code === 200) { .then((res) => {
// if (res.code === 200) {
this.$notification.success({ //
message: res.msg this.$notification.success({
}) message: res.msg
this.editVisible = false })
this.loadData() this.editVisible = false
} this.loadData()
}) }
})
.finally(() => {
this.confirmLoading = false
})
}) })
}, },
// //
@ -459,14 +469,7 @@ export default {
}) })
}, },
handleInputConfirm() { handleInputConfirm() {
this.$refs['editForm'].validateField('tagInput', (errmsg) => { this.$refs['editForm'].validateFields('tagInput').then(() => {
if (errmsg) {
// console.log(err);
this.$notification.warn({
message: errmsg
})
return false
}
const inputValue = this.temp.tagInput const inputValue = this.temp.tagInput
let tags = this.temp.tagsArray || [] let tags = this.temp.tagsArray || []
if (inputValue && tags.indexOf(inputValue) === -1) { if (inputValue && tags.indexOf(inputValue) === -1) {
@ -480,6 +483,15 @@ export default {
inputVisible: false inputVisible: false
} }
}) })
// .catch((error) => {
// console.log(error)
// if (errmsgs) {
// this.$notification.warn({
// message: errmsgs
// })
// return false
// }
// })
}, },
// //
@ -507,19 +519,24 @@ export default {
return false return false
} }
// //
this.confirmLoading = true
syncToWorkspace({ syncToWorkspace({
ids: this.tableSelections.join(','), ids: this.tableSelections.join(','),
toWorkspaceId: this.temp.workspaceId toWorkspaceId: this.temp.workspaceId
}).then((res) => {
if (res.code == 200) {
this.$notification.success({
message: res.msg
})
this.tableSelections = []
this.syncToWorkspaceVisible = false
return false
}
}) })
.then((res) => {
if (res.code == 200) {
this.$notification.success({
message: res.msg
})
this.tableSelections = []
this.syncToWorkspaceVisible = false
return false
}
})
.finally(() => {
this.confirmLoading = false
})
} }
} }
} }

View File

@ -20,6 +20,9 @@
@change="changePage" @change="changePage"
:pagination="pagination" :pagination="pagination"
bordered bordered
:scroll="{
x: 'max-content'
}"
> >
<template v-slot:title> <template v-slot:title>
<a-space> <a-space>
@ -40,56 +43,60 @@
</a-tooltip> </a-tooltip>
</a-space> </a-space>
</template> </template>
<template v-slot:tooltip="text">
<a-tooltip placement="topLeft" :title="text"> <template #bodyCell="{ column, text, record }">
<span>{{ text }}</span> <template v-if="column.tooltip">
</a-tooltip> <a-tooltip placement="topLeft" :title="text">
</template> <span>{{ text }}</span>
<template v-slot:status="text, record"> </a-tooltip>
<template v-if="record.machineDocker"> </template>
<a-tag color="green" v-if="record.machineDocker.status === 1">正常</a-tag> <template v-else-if="column.dataIndex instanceof Array && column.dataIndex.includes('status')">
<a-tooltip v-else :title="record.machineDocker.failureMsg"> <template v-if="record.machineDocker">
<a-tag color="red">无法连接</a-tag> <a-tag color="green" v-if="record.machineDocker.status === 1">正常</a-tag>
<a-tooltip v-else :title="record.machineDocker.failureMsg">
<a-tag color="red">无法连接</a-tag>
</a-tooltip>
</template>
<a-tooltip v-else title="集群关联的 docker 信息丢失,不能继续使用管理功能">
<a-tag color="red">信息丢失</a-tag>
</a-tooltip> </a-tooltip>
</template> </template>
<a-tooltip v-else title="集群关联的 docker 信息丢失,不能继续使用管理功能"> <template v-else-if="column.dataIndex === 'operation'">
<a-tag color="red">信息丢失</a-tag> <a-space>
</a-tooltip> <template v-if="record.machineDocker">
</template> <a-button
size="small"
:disabled="record.machineDocker.status !== 1"
type="primary"
@click="handleConsole(record, 'server')"
>服务</a-button
>
<a-button
size="small"
:disabled="record.machineDocker.status !== 1"
type="primary"
@click="handleConsole(record, 'node')"
>节点</a-button
>
</template>
<template v-else>
<a-button size="small" :disabled="true" type="primary">服务</a-button>
<a-button size="small" :disabled="true" type="primary">节点</a-button>
</template>
<template v-slot:operation="text, record"> <a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
<a-space> <a-button size="small" type="primary" danger @click="handleDelete(record)">删除</a-button>
<template v-if="record.machineDocker"> </a-space>
<a-button </template>
size="small"
:disabled="record.machineDocker.status !== 1"
type="primary"
@click="handleConsole(record, 'server')"
>服务</a-button
>
<a-button
size="small"
:disabled="record.machineDocker.status !== 1"
type="primary"
@click="handleConsole(record, 'node')"
>节点</a-button
>
</template>
<template v-else>
<a-button size="small" :disabled="true" type="primary">服务</a-button>
<a-button size="small" :disabled="true" type="primary">节点</a-button>
</template>
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
<a-button size="small" type="primary" danger @click="handleDelete(record)">删除</a-button>
</a-space>
</template> </template>
</a-table> </a-table>
<!-- 创建集群区 --> <!-- 编辑集群区 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model:value="editVisible" :confirmLoading="confirmLoading"
v-model:open="editVisible"
title="编辑 Docker 集群" title="编辑 Docker 集群"
@ok="handleEditOk" @ok="handleEditOk"
:maskClosable="false" :maskClosable="false"
@ -106,26 +113,31 @@
</a-modal> </a-modal>
<!-- 控制台 --> <!-- 控制台 -->
<a-drawer <!-- <a-drawer
destroyOnClose destroyOnClose
:title="`${temp.name} 控制台`" :title="`${temp.name} 控制台`"
placement="right" placement="right"
:width="`${this.getCollapsed ? 'calc(100vw - 80px)' : 'calc(100vw - 200px)'}`" :width="`${this.getCollapsed ? 'calc(100vw - 80px)' : 'calc(100vw - 200px)'}`"
:visible="consoleVisible" :open="consoleVisible"
@close=" @close="
() => { () => {
this.consoleVisible = false this.consoleVisible = false
} }
" "
> > -->
<console <console
v-if="consoleVisible" v-if="consoleVisible"
:id="temp.id" :id="temp.id"
:visible="consoleVisible" :visible="consoleVisible"
:initMenu="temp.menuKey" :initMenu="temp.menuKey"
urlPrefix="" urlPrefix=""
></console> @close="
</a-drawer> () => {
this.consoleVisible = false
}
"
></console>
<!-- </a-drawer> -->
</div> </div>
</template> </template>
@ -154,7 +166,7 @@ export default {
title: '名称', title: '名称',
dataIndex: 'name', dataIndex: 'name',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
{ {
@ -162,57 +174,55 @@ export default {
dataIndex: 'swarmId', dataIndex: 'swarmId',
ellipsis: true, ellipsis: true,
align: 'center', align: 'center',
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
{ {
title: '容器标签', title: '容器标签',
dataIndex: 'tag', dataIndex: 'tag',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
{ {
title: '状态', title: '状态',
dataIndex: 'status', dataIndex: ['machineDocker', 'status'],
ellipsis: true, ellipsis: true,
align: 'center', align: 'center',
width: '100px', width: '100px'
scopedSlots: { customRender: 'status' }
}, },
{ {
title: '最后修改人', title: '最后修改人',
dataIndex: 'modifyUser', dataIndex: 'modifyUser',
width: 120, width: 120,
ellipsis: true, ellipsis: true
scopedSlots: { customRender: 'modifyUser' }
}, },
{ {
title: '修改时间', title: '修改时间',
dataIndex: 'modifyTimeMillis', dataIndex: 'modifyTimeMillis',
sorter: true, sorter: true,
ellipsis: true, ellipsis: true,
customRender: (text) => parseTime(text), customRender: ({ text }) => parseTime(text),
width: '170px' width: '170px'
}, },
{ {
title: '集群创建时间', title: '集群创建时间',
dataIndex: 'machineDocker.swarmCreatedAt', dataIndex: ['machineDocker', 'swarmCreatedAt'],
sorter: true, sorter: true,
ellipsis: true, ellipsis: true,
customRender: (text) => parseTime(text), customRender: ({ text }) => parseTime(text),
width: '170px' width: '170px'
}, },
{ {
title: '集群修改时间', title: '集群修改时间',
dataIndex: 'machineDocker.swarmUpdatedAt', dataIndex: ['machineDocker', 'swarmUpdatedAt'],
sorter: true, sorter: true,
ellipsis: true, ellipsis: true,
customRender: (text) => parseTime(text), customRender: ({ text }) => parseTime(text),
width: '170px' width: '170px'
}, },
{ {
title: '操作', title: '操作',
dataIndex: 'operation', dataIndex: 'operation',
scopedSlots: { customRender: 'operation' }, fixed: 'right',
align: 'center', align: 'center',
width: '220px' width: '220px'
} }
@ -225,7 +235,8 @@ export default {
{ required: true, message: '请填写关联容器标签', trigger: 'blur' }, { required: true, message: '请填写关联容器标签', trigger: 'blur' },
{ pattern: /^\w{1,10}$/, message: '标签限制为字母数字且长度 1-10' } { pattern: /^\w{1,10}$/, message: '标签限制为字母数字且长度 1-10' }
] ]
} },
confirmLoading: false
} }
}, },
computed: { computed: {
@ -287,55 +298,26 @@ export default {
// //
handleEditOk() { handleEditOk() {
// //
this.$refs['editForm'].validate((valid) => { this.$refs['editForm'].validate().then(() => {
if (!valid) { this.confirmLoading = true
return false
}
editDockerSwarm(this.temp).then((res) => { editDockerSwarm(this.temp)
if (res.code === 200) { .then((res) => {
// if (res.code === 200) {
this.$notification.success({ //
message: res.msg this.$notification.success({
}) message: res.msg
this.editVisible = false })
this.loadData() this.editVisible = false
} this.loadData()
}) }
})
.finally(() => {
this.confirmLoading = false
})
}) })
}, },
// //
// handleUnbind(record) {
// const html =
// "<b style='font-size: 20px;'></b>" +
// "<ul style='font-size: 20px;color:red;font-weight: bold;'>" +
// "<li>,</b></li>" +
// " </ul>";
// //
// this.$confirm({
// title: "",
// content: h("div", null, [h("p", { domProps: { innerHTML: html } }, null)]),
// okButtonProps: { props: { type: "danger", size: "small" } },
// cancelButtonProps: { props: { type: "primary" } },
// okText: "",
// cancelText: "",
// onOk: () => {
// //
// const params = {
// id: record.id,
// };
// unbindSwarm(params).then((res) => {
// if (res.code === 200) {
// this.$notification.success({
// message: res.msg,
// });
// this.loadData();
// }
// });
// },
// });
// },
// //
handleDelete(record) { handleDelete(record) {
this.$confirm({ this.$confirm({

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="full-content"> <div>
<div> <div>
<!-- 数据表格 --> <!-- 数据表格 -->
<a-table <a-table
@ -16,29 +16,32 @@
bordered bordered
rowKey="id" rowKey="id"
:row-selection="rowSelection" :row-selection="rowSelection"
:scroll="{
x: 'max-content'
}"
> >
<template #title> <template v-slot:title>
<a-space> <a-space>
<a-input <a-input
v-model="listQuery['%name%']" v-model:value="listQuery['%name%']"
@pressEnter="loadData" @pressEnter="loadData"
placeholder="文件名称" placeholder="文件名称"
class="search-input-item" class="search-input-item"
/> />
<a-input <a-input
v-model="listQuery['%aliasCode%']" v-model:value="listQuery['%aliasCode%']"
@pressEnter="loadData" @pressEnter="loadData"
placeholder="别名码" placeholder="别名码"
class="search-input-item" class="search-input-item"
/> />
<a-input <a-input
v-model="listQuery['extName']" v-model:value="listQuery['extName']"
@pressEnter="loadData" @pressEnter="loadData"
placeholder="后缀,精准搜索" placeholder="后缀,精准搜索"
class="search-input-item" class="search-input-item"
/> />
<a-input <a-input
v-model="listQuery['id']" v-model:value="listQuery['id']"
@pressEnter="loadData" @pressEnter="loadData"
placeholder="文件id,精准搜索" placeholder="文件id,精准搜索"
class="search-input-item" class="search-input-item"
@ -49,7 +52,8 @@
<a-button type="primary" @click="handleUpload">上传文件</a-button> <a-button type="primary" @click="handleUpload">上传文件</a-button>
<a-button type="primary" @click="handleRemoteDownload">远程下载</a-button> <a-button type="primary" @click="handleRemoteDownload">远程下载</a-button>
<a-button <a-button
type="danger" type="primary"
danger
:disabled="!tableSelections || tableSelections.length <= 0" :disabled="!tableSelections || tableSelections.length <= 0"
@click="handleBatchDelete" @click="handleBatchDelete"
> >
@ -57,62 +61,75 @@
</a-button> </a-button>
</a-space> </a-space>
</template> </template>
<a-tooltip #tooltip slot-scope="text" placement="topLeft" :title="text"> <template #bodyCell="{ column, text, record, index }">
<span>{{ text }}</span> <template v-if="column.tooltip">
</a-tooltip> <a-tooltip placement="topLeft" :title="text">
<a-tooltip #id slot-scope="text, item" placement="topLeft" :title="text"> <span>{{ (text || '').slice(0, 8) }}</span>
<span v-if="item.status === 0 || item.status === 2">-</span> </a-tooltip>
<span v-else>{{ text }}</span> </template>
</a-tooltip> <template v-else-if="column.dataIndex === 'id'">
<a-popover #name slot-scope="text, item" title="文件信息"> <a-tooltip placement="topLeft" :title="text">
<template #content> <span v-if="record.status === 0 || record.status === 2">-</span>
<p>文件名{{ text }}</p> <span v-else>{{ (text || '').slice(0, 8) }}</span>
<p>文件描述{{ item.description }}</p> </a-tooltip>
<p v-if="item.status !== undefined">下载状态{{ statusMap[item.status] || '未知' }}</p> </template>
<p v-if="item.progressDesc">状态描述{{ item.progressDesc }}</p> <template v-else-if="column.dataIndex === 'name'">
<a-popover title="文件信息">
<template v-slot:content>
<p>文件名{{ text }}</p>
<p>文件描述{{ record.description }}</p>
<p v-if="record.status !== undefined">下载状态{{ statusMap[record.status] || '未知' }}</p>
<p v-if="record.progressDesc">状态描述{{ record.progressDesc }}</p>
</template>
<!-- {{ text }} -->
<a-button type="link" style="padding: 0" @click="handleEdit(record)" size="small">{{ text }}</a-button>
</a-popover>
</template> </template>
<!-- {{ text }} -->
<a-button type="link" style="padding: 0" @click="handleEdit(item)" size="small">{{ text }}</a-button>
</a-popover>
<a-tooltip #renderSize slot-scope="text" placement="topLeft" :title="renderSize(text)"> <template v-else-if="column.dataIndex === 'size'">
<span>{{ renderSize(text) }}</span> <a-tooltip placement="topLeft" :title="renderSize(text)">
</a-tooltip> <span>{{ renderSize(text) }}</span>
<a-tooltip #source slot-scope="text" placement="topLeft" :title="`${sourceMap[text] || '未知'}`"> </a-tooltip>
<span>{{ sourceMap[text] || '未知' }}</span> </template>
</a-tooltip> <template v-else-if="column.dataIndex === 'source'">
<a-tooltip placement="topLeft" :title="`${sourceMap[text] || '未知'}`">
<span>{{ sourceMap[text] || '未知' }}</span>
</a-tooltip>
</template>
<template #exists slot-scope="text"> <template v-else-if="column.dataIndex === 'exists'">
<a-tag v-if="text" color="green">存在</a-tag> <a-tag v-if="text" color="green">存在</a-tag>
<a-tag v-else color="red">丢失</a-tag> <a-tag v-else color="red">丢失</a-tag>
</template> </template>
<template #global slot-scope="text"> <template v-else-if="column.dataIndex === 'workspaceId'">
<a-tag v-if="text === 'GLOBAL'">全局</a-tag> <a-tag v-if="text === 'GLOBAL'">全局</a-tag>
<a-tag v-else>工作空间</a-tag> <a-tag v-else>工作空间</a-tag>
</template> </template>
<template #operation slot-scope="text, record"> <template v-else-if="column.dataIndex === 'operation'">
<a-space> <a-space>
<!-- <a-button type="primary" size="small" @click="handleEdit(record)">编辑</a-button> --> <!-- <a-button type="primary" size="small" @click="handleEdit(record)">编辑</a-button> -->
<a-button size="small" :disabled="!record.exists" type="primary" @click="handleDownloadUrl(record)" <a-button size="small" :disabled="!record.exists" type="primary" @click="handleDownloadUrl(record)"
>下载</a-button >下载</a-button
> >
<a-button size="small" :disabled="!record.exists" type="primary" @click="handleReleaseFile(record)" <a-button size="small" :disabled="!record.exists" type="primary" @click="handleReleaseFile(record)"
>发布</a-button >发布</a-button
> >
<a-button type="danger" size="small" @click="handleDelete(record)">删除</a-button> <a-button type="primary" danger size="small" @click="handleDelete(record)">删除</a-button>
</a-space> </a-space>
</template>
</template> </template>
</a-table> </a-table>
<!-- 上传文件 --> <!-- 上传文件 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model="uploadVisible" v-model:open="uploadVisible"
:closable="!uploading" :closable="!uploading"
:footer="uploading ? null : undefined" :footer="uploading ? null : undefined"
:keyboard="false" :keyboard="false"
:title="`上传文件`" :title="`上传文件`"
@ok="handleUploadOk" @ok="handleUploadOk"
:maskClosable="false" :maskClosable="false"
:confirmLoading="confirmLoading"
> >
<a-form ref="form" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }"> <a-form ref="form" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
<a-form-item label="选择文件" name="file"> <a-form-item label="选择文件" name="file">
@ -127,9 +144,10 @@
<a-upload <a-upload
:file-list="fileList" :file-list="fileList"
:disabled="!!percentage" :disabled="!!percentage"
:remove=" @remove="
(file) => { (file) => {
this.fileList = [] this.fileList = []
return true
} }
" "
:before-upload=" :before-upload="
@ -140,20 +158,20 @@
} }
" "
> >
<a-icon type="loading" v-if="percentage" /> <LoadingOutlined v-if="percentage" />
<a-button v-else type="primary" icon="upload">选择文件</a-button> <a-button v-else type="primary"><UploadOutlined />选择文件</a-button>
</a-upload> </a-upload>
</a-form-item> </a-form-item>
<a-form-item label="保留天数" name="keepDay"> <a-form-item label="保留天数" name="keepDay">
<a-input-number <a-input-number
v-model="temp.keepDay" v-model:value="temp.keepDay"
:min="1" :min="1"
style="width: 100%" style="width: 100%"
placeholder="文件保存天数,默认 3650 天" placeholder="文件保存天数,默认 3650 天"
/> />
</a-form-item> </a-form-item>
<a-form-item label="文件共享" name="global"> <a-form-item label="文件共享" name="global">
<a-radio-group v-model="temp.global"> <a-radio-group v-model:value="temp.global">
<a-radio :value="true"> 全局 </a-radio> <a-radio :value="true"> 全局 </a-radio>
<a-radio :value="false"> 当前工作空间 </a-radio> <a-radio :value="false"> 当前工作空间 </a-radio>
</a-radio-group> </a-radio-group>
@ -161,7 +179,7 @@
<a-form-item label="别名码" name="aliasCode" help="用于区别文件是否为同一类型,可以针对同类型进行下载管理"> <a-form-item label="别名码" name="aliasCode" help="用于区别文件是否为同一类型,可以针对同类型进行下载管理">
<a-input-search <a-input-search
:maxLength="50" :maxLength="50"
v-model="temp.aliasCode" v-model:value="temp.aliasCode"
placeholder="请输入别名码" placeholder="请输入别名码"
@search=" @search="
() => { () => {
@ -169,38 +187,39 @@
} }
" "
> >
<template #enterButton> <template v-slot:enterButton>
<a-button type="primary"> 随机生成 </a-button> <a-button type="primary"> 随机生成 </a-button>
</template> </template>
</a-input-search> </a-input-search>
</a-form-item> </a-form-item>
<a-form-item label="文件描述" name="description"> <a-form-item label="文件描述" name="description">
<a-textarea v-model="temp.description" placeholder="请输入文件描述" /> <a-textarea v-model:value="temp.description" placeholder="请输入文件描述" />
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-modal> </a-modal>
<!-- 编辑文件 --> <!-- 编辑文件 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model:visible="editVisible" :confirmLoading="confirmLoading"
v-model:open="editVisible"
:title="`修改文件`" :title="`修改文件`"
@ok="handleEditOk" @ok="handleEditOk"
:maskClosable="false" :maskClosable="false"
> >
<a-form ref="editForm" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }"> <a-form ref="editForm" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
<a-form-item label="文件名" name="name"> <a-form-item label="文件名" name="name">
<a-input placeholder="文件名" v-model="temp.name" /> <a-input placeholder="文件名" v-model:value="temp.name" />
</a-form-item> </a-form-item>
<a-form-item label="保留天数" name="keepDay"> <a-form-item label="保留天数" name="keepDay">
<a-input-number <a-input-number
v-model="temp.keepDay" v-model:value="temp.keepDay"
:min="1" :min="1"
style="width: 100%" style="width: 100%"
placeholder="文件保存天数,默认 3650 天" placeholder="文件保存天数,默认 3650 天"
/> />
</a-form-item> </a-form-item>
<a-form-item label="文件共享" name="global"> <a-form-item label="文件共享" name="global">
<a-radio-group v-model="temp.global"> <a-radio-group v-model:value="temp.global">
<a-radio :value="true"> 全局 </a-radio> <a-radio :value="true"> 全局 </a-radio>
<a-radio :value="false"> 当前工作空间 </a-radio> <a-radio :value="false"> 当前工作空间 </a-radio>
</a-radio-group> </a-radio-group>
@ -208,7 +227,7 @@
<a-form-item label="别名码" name="aliasCode" help="用于区别文件是否为同一类型,可以针对同类型进行下载管理"> <a-form-item label="别名码" name="aliasCode" help="用于区别文件是否为同一类型,可以针对同类型进行下载管理">
<a-input-search <a-input-search
:maxLength="50" :maxLength="50"
v-model="temp.aliasCode" v-model:value="temp.aliasCode"
placeholder="请输入别名码" placeholder="请输入别名码"
@search=" @search="
() => { () => {
@ -216,38 +235,39 @@
} }
" "
> >
<template #enterButton> <template v-slot:enterButton>
<a-button type="primary"> 随机生成 </a-button> <a-button type="primary"> 随机生成 </a-button>
</template> </template>
</a-input-search> </a-input-search>
</a-form-item> </a-form-item>
<a-form-item label="文件描述" name="description"> <a-form-item label="文件描述" name="description">
<a-textarea v-model="temp.description" placeholder="请输入文件描述" /> <a-textarea v-model:value="temp.description" placeholder="请输入文件描述" />
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-modal> </a-modal>
<!--远程下载 --> <!--远程下载 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model="uploadRemoteFileVisible" v-model:open="uploadRemoteFileVisible"
title="远程下载文件" title="远程下载文件"
@ok="handleRemoteUpload" @ok="handleRemoteUpload"
:maskClosable="false" :maskClosable="false"
:confirmLoading="confirmLoading"
> >
<a-form :model="temp" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }" :rules="rules" ref="remoteForm"> <a-form :model="temp" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }" :rules="rules" ref="remoteForm">
<a-form-item label="远程下载URL" name="url"> <a-form-item label="远程下载URL" name="url">
<a-input v-model="temp.url" placeholder="远程下载地址" /> <a-input v-model:value="temp.url" placeholder="远程下载地址" />
</a-form-item> </a-form-item>
<a-form-item label="保留天数" name="keepDay"> <a-form-item label="保留天数" name="keepDay">
<a-input-number <a-input-number
v-model="temp.keepDay" v-model:value="temp.keepDay"
:min="1" :min="1"
style="width: 100%" style="width: 100%"
placeholder="文件保存天数,默认 3650 天" placeholder="文件保存天数,默认 3650 天"
/> />
</a-form-item> </a-form-item>
<a-form-item label="文件共享" name="global"> <a-form-item label="文件共享" name="global">
<a-radio-group v-model="temp.global"> <a-radio-group v-model:value="temp.global">
<a-radio :value="true"> 全局 </a-radio> <a-radio :value="true"> 全局 </a-radio>
<a-radio :value="false"> 当前工作空间 </a-radio> <a-radio :value="false"> 当前工作空间 </a-radio>
</a-radio-group> </a-radio-group>
@ -255,7 +275,7 @@
<a-form-item label="别名码" name="aliasCode" help="用于区别文件是否为同一类型,可以针对同类型进行下载管理"> <a-form-item label="别名码" name="aliasCode" help="用于区别文件是否为同一类型,可以针对同类型进行下载管理">
<a-input-search <a-input-search
:maxLength="50" :maxLength="50"
v-model="temp.aliasCode" v-model:value="temp.aliasCode"
placeholder="请输入别名码" placeholder="请输入别名码"
@search=" @search="
() => { () => {
@ -263,20 +283,20 @@
} }
" "
> >
<template #enterButton> <template v-slot:enterButton>
<a-button type="primary"> 随机生成 </a-button> <a-button type="primary"> 随机生成 </a-button>
</template> </template>
</a-input-search> </a-input-search>
</a-form-item> </a-form-item>
<a-form-item label="文件描述" name="description"> <a-form-item label="文件描述" name="description">
<a-textarea v-model="temp.description" placeholder="请输入文件描述" /> <a-textarea v-model:value="temp.description" placeholder="请输入文件描述" />
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-modal> </a-modal>
<!-- 断点下载 --> <!-- 断点下载 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model="triggerVisible" v-model:open="triggerVisible"
title="断点/分片下载" title="断点/分片下载"
width="50%" width="50%"
:footer="null" :footer="null"
@ -284,42 +304,30 @@
> >
<a-form ref="editTriggerForm" :model="temp" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }"> <a-form ref="editTriggerForm" :model="temp" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
<a-tabs default-active-key="1"> <a-tabs default-active-key="1">
<template #tabBarExtraContent> <template v-slot:rightExtra>
<a-tooltip title="重置下载 token 信息,重置后之前的下载 token 将失效"> <a-tooltip title="重置下载 token 信息,重置后之前的下载 token 将失效">
<a-button type="primary" size="small" @click="resetTrigger">重置</a-button> <a-button type="primary" size="small" @click="resetTrigger">重置</a-button>
</a-tooltip> </a-tooltip>
</template> </template>
<a-tab-pane key="1" tab="断点/分片单文件下载"> <a-tab-pane key="1" tab="断点/分片单文件下载">
<a-space style="display: block" direction="vertical" align="baseline"> <a-space direction="vertical" style="width: 100%">
<a-alert <a-alert type="info" :message="`下载地址(点击可以复制)`">
v-clipboard:copy="`${temp.triggerDownloadUrl}`" <template v-slot:description>
v-clipboard:success=" <a-typography-paragraph :copyable="{ tooltip: false, text: temp.triggerDownloadUrl }">
() => { <a-tag>GET</a-tag>
tempVue.prototype.$notification.success({ message: '复制成功' }) <span>{{ `${temp.triggerDownloadUrl}` }} </span>
} </a-typography-paragraph>
"
v-clipboard:error="
() => {
tempVue.prototype.$notification.error({ message: '复制失败' })
}
"
type="info"
:message="`下载地址(点击可以复制)`"
>
<template #description>
<a-tag>GET</a-tag> <span>{{ `${temp.triggerDownloadUrl}` }} </span>
<a-icon type="copy" />
</template> </template>
</a-alert> </a-alert>
<a :href="temp.triggerDownloadUrl" target="_blank"> <a :href="temp.triggerDownloadUrl" target="_blank">
<a-button size="small" icon="download" type="primary">立即下载</a-button> <a-button size="small" type="primary"><DownloadOutlined />立即下载</a-button>
</a> </a>
</a-space> </a-space>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="2" tab="断点/分片别名下载" v-if="temp.triggerAliasDownloadUrl"> <a-tab-pane tab="断点/分片别名下载" v-if="temp.triggerAliasDownloadUrl">
<a-space style="display: block" direction="vertical" align="baseline"> <a-space direction="vertical" style="width: 100%">
<a-alert message="温馨提示" type="warning"> <a-alert message="温馨提示" type="warning">
<template #description> <template v-slot:description>
<ul> <ul>
<li> <li>
支持自定义排序字段sort=createTimeMillis:desc 支持自定义排序字段sort=createTimeMillis:desc
@ -331,28 +339,16 @@
</ul> </ul>
</template> </template>
</a-alert> </a-alert>
<a-alert <a-alert type="info" :message="`下载地址(点击可以复制)`">
v-clipboard:copy="`${temp.triggerAliasDownloadUrl}`" <template v-slot:description>
v-clipboard:success=" <a-typography-paragraph :copyable="{ tooltip: false, text: temp.triggerAliasDownloadUrl }">
() => { <a-tag>GET</a-tag>
tempVue.prototype.$notification.success({ message: '复制成功' }) <span>{{ `${temp.triggerAliasDownloadUrl}` }} </span>
} </a-typography-paragraph>
"
v-clipboard:error="
() => {
tempVue.prototype.$notification.error({ message: '复制失败' })
}
"
type="info"
:message="`下载地址(点击可以复制)`"
>
<template #description>
<a-tag>GET</a-tag> <span>{{ `${temp.triggerAliasDownloadUrl}` }} </span>
<a-icon type="copy" />
</template> </template>
</a-alert> </a-alert>
<a :href="temp.triggerAliasDownloadUrl" target="_blank"> <a :href="temp.triggerAliasDownloadUrl" target="_blank">
<a-button size="small" icon="download" type="primary">立即下载</a-button> <a-button size="small" type="primary"><DownloadOutlined />立即下载</a-button>
</a> </a>
</a-space> </a-space>
</a-tab-pane> </a-tab-pane>
@ -362,19 +358,45 @@
<!-- 发布文件 --> <!-- 发布文件 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model="releaseFileVisible" :confirmLoading="confirmLoading"
v-model:open="releaseFileVisible"
title="发布文件" title="发布文件"
width="50%" width="50%"
:maskClosable="false" :maskClosable="false"
@ok=" @ok="releaseFileOk()"
() => {
this.$refs.releaseFile?.tryCommit()
}
"
> >
<releaseFile ref="releaseFile" v-if="releaseFileVisible" @commit="handleCommitTask"></releaseFile> <releaseFile ref="releaseFile" v-if="releaseFileVisible" @commit="handleCommitTask"></releaseFile>
</a-modal> </a-modal>
</div> </div>
<!-- 选择确认区域 -->
<div style="padding-top: 50px" v-if="this.choose">
<div
:style="{
position: 'absolute',
right: 0,
bottom: 0,
width: '100%',
borderTop: '1px solid #e9e9e9',
padding: '10px 16px',
background: '#fff',
textAlign: 'right',
zIndex: 1
}"
>
<a-space>
<a-button
@click="
() => {
this.$emit('cancel')
}
"
>
取消
</a-button>
<a-button type="primary" @click="handerConfirm"> 确定 </a-button>
</a-space>
</div>
</div>
</div> </div>
</template> </template>
@ -401,7 +423,7 @@ import {
triggerUrl triggerUrl
} from '@/api/file-manager/file-storage' } from '@/api/file-manager/file-storage'
import { uploadPieces } from '@/utils/upload-pieces' import { uploadPieces } from '@/utils/upload-pieces'
// import Vue from 'vue' import * as Vue from 'vue'
import releaseFile from './releaseFile.vue' import releaseFile from './releaseFile.vue'
import { addReleaseTask } from '@/api/file-manager/release-task-log' import { addReleaseTask } from '@/api/file-manager/release-task-log'
@ -409,49 +431,72 @@ export default {
components: { components: {
releaseFile releaseFile
}, },
props: {
choose: {
// "radio"
type: String,
default: ''
}
},
data() { data() {
return { return {
loading: false, loading: false,
listQuery: Object.assign({}, PAGE_DEFAULT_LIST_QUERY), listQuery: Object.assign({}, PAGE_DEFAULT_LIST_QUERY),
list: [], list: [],
columns: [ columns: [
{ title: '文件MD5', dataIndex: 'id', ellipsis: true, width: 100, scopedSlots: { customRender: 'id' } }, {
{ title: '名称', dataIndex: 'name', ellipsis: true, width: 150, scopedSlots: { customRender: 'name' } }, title: '文件MD5',
dataIndex: 'id',
ellipsis: true,
width: 100
},
{
title: '名称',
dataIndex: 'name',
ellipsis: true,
width: 150
},
{ {
title: '别名码', title: '别名码',
dataIndex: 'aliasCode', dataIndex: 'aliasCode',
ellipsis: true, ellipsis: true,
width: 100, width: 100,
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
{ {
title: '大小', title: '大小',
dataIndex: 'size', dataIndex: 'size',
sorter: true, sorter: true,
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'renderSize' },
width: '100px' width: '100px'
}, },
{ {
title: '后缀', title: '后缀',
dataIndex: 'extName', dataIndex: 'extName',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'tooltip' }, tooltip: true,
width: '80px' width: '80px'
}, },
{ {
title: '共享', title: '共享',
dataIndex: 'workspaceId', dataIndex: 'workspaceId',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'global' },
width: '90px' width: '90px'
}, },
{ title: '来源', dataIndex: 'source', ellipsis: true, scopedSlots: { customRender: 'source' }, width: '80px' }, {
title: '来源',
dataIndex: 'source',
ellipsis: true,
width: '80px'
},
{ {
title: '过期天数', title: '过期天数',
dataIndex: 'validUntil', dataIndex: 'validUntil',
sorter: true, sorter: true,
customRender: (text) => { customRender: ({ text }) => {
if (!text) { if (!text) {
return '-' return '-'
} }
@ -463,35 +508,35 @@ export default {
title: '文件状态', title: '文件状态',
dataIndex: 'exists', dataIndex: 'exists',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'exists' },
width: '80px' width: '80px'
}, },
{ {
title: '创建人', title: '创建人',
dataIndex: 'createUser', dataIndex: 'createUser',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'tooltip' }, tooltip: true,
width: '120px' width: '120px'
}, },
{ {
title: '修改人', title: '修改人',
dataIndex: 'modifyUser', dataIndex: 'modifyUser',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'tooltip' }, tooltip: true,
width: '120px' width: '120px'
}, },
{ {
title: '创建时间', title: '创建时间',
dataIndex: 'createTimeMillis', dataIndex: 'createTimeMillis',
sorter: true, sorter: true,
customRender: (text) => parseTime(text), customRender: ({ text }) => parseTime(text),
width: '170px' width: '170px'
}, },
{ {
title: '修改时间', title: '修改时间',
dataIndex: 'modifyTimeMillis', dataIndex: 'modifyTimeMillis',
sorter: true, sorter: true,
customRender: (text) => parseTime(text), customRender: ({ text }) => parseTime(text),
width: '170px' width: '170px'
}, },
{ {
@ -499,7 +544,7 @@ export default {
dataIndex: 'operation', dataIndex: 'operation',
align: 'center', align: 'center',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'operation' },
fixed: 'right', fixed: 'right',
width: '170px' width: '170px'
} }
@ -522,7 +567,8 @@ export default {
tempVue: null, tempVue: null,
triggerVisible: false, triggerVisible: false,
releaseFileVisible: false, releaseFileVisible: false,
tableSelections: [] tableSelections: [],
confirmLoading: false
} }
}, },
computed: { computed: {
@ -534,7 +580,8 @@ export default {
onChange: (selectedRowKeys) => { onChange: (selectedRowKeys) => {
this.tableSelections = selectedRowKeys this.tableSelections = selectedRowKeys
}, },
selectedRowKeys: this.tableSelections selectedRowKeys: this.tableSelections,
type: this.choose || 'checkbox'
} }
} }
}, },
@ -569,14 +616,10 @@ export default {
// //
handleUploadOk() { handleUploadOk() {
// //
this.$refs['form'].validate((valid) => { this.$refs['form'].validate().then(() => {
if (!valid) {
return false
}
// //
if (this.fileList.length === 0) { if (this.fileList.length === 0) {
$notification.error({ this.$notification.error({
message: '请选择文件' message: '请选择文件'
}) })
return false return false
@ -584,6 +627,7 @@ export default {
this.percentage = 0 this.percentage = 0
this.percentageInfo = {} this.percentageInfo = {}
this.uploading = true this.uploading = true
this.confirmLoading = true
uploadPieces({ uploadPieces({
file: this.fileList[0], file: this.fileList[0],
uploadBeforeAbrot: (md5) => { uploadBeforeAbrot: (md5) => {
@ -594,13 +638,14 @@ export default {
if (res.code === 200) { if (res.code === 200) {
if (res.data) { if (res.data) {
// //
$notification.warning({ this.$notification.warning({
message: `当前文件已经存在啦,文件名:${res.data.name} ,是否共享:${ message: `当前文件已经存在啦,文件名:${res.data.name} ,是否共享:${
res.data.workspaceId === 'GLOBAL' ? '是' : '否' res.data.workspaceId === 'GLOBAL' ? '是' : '否'
}` }`
}) })
// //
this.uploading = false this.uploading = false
this.confirmLoading = false
} else { } else {
resolve() resolve()
} }
@ -619,23 +664,31 @@ export default {
if (res.code === 200) { if (res.code === 200) {
this.fileList = [] this.fileList = []
this.loadData() this.loadData()
this.uploadVisible = false
this.$notification.success({
message: res.msg
})
} }
setTimeout(() => { setTimeout(() => {
this.percentage = 0 this.percentage = 0
this.percentageInfo = {} this.percentageInfo = {}
this.uploadVisible = false
}, 2000) }, 2000)
this.uploading = false this.uploading = false
}) })
.catch(() => { .catch(() => {
this.uploading = false this.uploading = false
}) })
.finally(() => {
this.confirmLoading = false
})
}, },
error: (msg) => { error: (msg) => {
$notification.error({ this.$notification.error({
message: msg message: msg
}) })
this.uploading = false this.uploading = false
this.confirmLoading = false
}, },
uploadCallback: (formData) => { uploadCallback: (formData) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -660,33 +713,40 @@ export default {
}, },
// //
handleEdit(item) { handleEdit(item) {
this.temp = { ...item, global: item.workspaceId === 'GLOBAL', workspaceId: '' } this.temp = {
...item,
global: item.workspaceId === 'GLOBAL',
workspaceId: ''
}
this.editVisible = true this.editVisible = true
this.$refs['editForm']?.resetFields() this.$refs['editForm']?.resetFields()
}, },
// //
handleEditOk() { handleEditOk() {
this.$refs['editForm'].validate((valid) => { this.$refs['editForm'].validate().then(() => {
if (!valid) { this.confirmLoading = true
return false fileEdit(this.temp)
} .then((res) => {
fileEdit(this.temp).then((res) => { if (res.code === 200) {
if (res.code === 200) { //
// this.$notification.success({
$notification.success({ message: res.msg
message: res.msg })
})
this.editVisible = false this.editVisible = false
this.loadData() this.loadData()
} }
}) })
.finally(() => {
this.confirmLoading = false
})
}) })
}, },
// //
handleDelete(record) { handleDelete(record) {
$confirm({ this.$confirm({
title: '系统提示', title: '系统提示',
zIndex: 1009,
content: '真的要删除当前文件么?' + record.name, content: '真的要删除当前文件么?' + record.name,
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',
@ -696,7 +756,7 @@ export default {
id: record.id id: record.id
}).then((res) => { }).then((res) => {
if (res.code === 200) { if (res.code === 200) {
$notification.success({ this.$notification.success({
message: res.msg message: res.msg
}) })
@ -709,13 +769,14 @@ export default {
// //
handleBatchDelete() { handleBatchDelete() {
if (!this.tableSelections || this.tableSelections.length <= 0) { if (!this.tableSelections || this.tableSelections.length <= 0) {
$notification.warning({ this.$notification.warning({
message: '没有选择任何数据' message: '没有选择任何数据'
}) })
return return
} }
$confirm({ this.$confirm({
title: '系统提示', title: '系统提示',
zIndex: 1009,
content: '真的要删除这些文件么?', content: '真的要删除这些文件么?',
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',
@ -723,7 +784,7 @@ export default {
// //
delFile({ ids: this.tableSelections.join(',') }).then((res) => { delFile({ ids: this.tableSelections.join(',') }).then((res) => {
if (res.code === 200) { if (res.code === 200) {
$notification.success({ this.$notification.success({
message: res.msg message: res.msg
}) })
this.tableSelections = [] this.tableSelections = []
@ -744,21 +805,23 @@ export default {
// //
handleRemoteUpload() { handleRemoteUpload() {
// //
this.$refs['remoteForm'].validate((valid) => { this.$refs['remoteForm'].validate().then(() => {
if (!valid) { this.confirmLoading = true
return false remoteDownload(this.temp)
} .then((res) => {
remoteDownload(this.temp).then((res) => { if (res.code === 200) {
if (res.code === 200) { //
// this.$notification.success({
$notification.success({ message: res.msg
message: res.msg })
})
this.uploadRemoteFileVisible = false this.uploadRemoteFileVisible = false
this.loadData() this.loadData()
} }
}) })
.finally(() => {
this.confirmLoading = false
})
}) })
}, },
// //
@ -770,7 +833,9 @@ export default {
}).then((res) => { }).then((res) => {
if (res.code === 200) { if (res.code === 200) {
this.fillDownloadUrlResult(res) this.fillDownloadUrlResult(res)
this.triggerVisible = true this.$nextTick(() => {
this.triggerVisible = true
})
} }
}) })
}, },
@ -781,7 +846,7 @@ export default {
rest: 'rest' rest: 'rest'
}).then((res) => { }).then((res) => {
if (res.code === 200) { if (res.code === 200) {
$notification.success({ this.$notification.success({
message: res.msg message: res.msg
}) })
this.fillDownloadUrlResult(res) this.fillDownloadUrlResult(res)
@ -804,21 +869,51 @@ export default {
}, },
handleCommitTask(data) { handleCommitTask(data) {
addReleaseTask({ ...data, fileId: this.temp.fileId }).then((res) => { this.confirmLoading = true
if (res.code === 200) { addReleaseTask({ ...data, fileId: this.temp.fileId, fileType: 1 })
// .then((res) => {
$notification.success({ if (res.code === 200) {
message: res.msg //
}) this.$notification.success({
message: res.msg
})
this.releaseFileVisible = false this.releaseFileVisible = false
this.loadData() this.loadData()
} }
})
.finally(() => {
this.confirmLoading = false
})
},
releaseFileOk() {
this.$refs.releaseFile?.tryCommit()
},
//
handerConfirm() {
if (!this.tableSelections.length) {
this.$notification.warning({
message: '请选择要使用的文件'
})
return
}
const selectData = this.list.filter((item) => {
return this.tableSelections.indexOf(item.id) > -1
}) })
if (!selectData.length) {
this.$notification.warning({
message: '请选择要使用的文件'
})
return
}
this.$emit('confirm', selectData)
} }
} },
emits: ['cancel', 'confirm']
} }
</script> </script>
<style scoped> <style scoped>
/deep/ .ant-progress-text { /deep/ .ant-progress-text {
width: auto; width: auto;

View File

@ -8,11 +8,11 @@
:wrapper-col="{ span: 20 }" :wrapper-col="{ span: 20 }"
> >
<a-form-item label="任务名" name="name"> <a-form-item label="任务名" name="name">
<a-input placeholder="请输入任务名" :maxLength="50" v-model="temp.name" /> <a-input placeholder="请输入任务名" :maxLength="50" v-model:value="temp.name" />
</a-form-item> </a-form-item>
<a-form-item label="发布方式" name="taskType"> <a-form-item label="发布方式" name="taskType">
<a-radio-group v-model="temp.taskType" @change="taskTypeChange"> <a-radio-group v-model:value="temp.taskType" @change="taskTypeChange">
<a-radio :value="0"> SSH </a-radio> <a-radio :value="0"> SSH </a-radio>
<a-radio :value="1"> 节点 </a-radio> <a-radio :value="1"> 节点 </a-radio>
</a-radio-group> </a-radio-group>
@ -25,7 +25,7 @@
show-search show-search
option-filter-prop="children" option-filter-prop="children"
mode="multiple" mode="multiple"
v-model="temp.taskDataIds" v-model:value="temp.taskDataIds"
placeholder="请选择SSH" placeholder="请选择SSH"
> >
<a-select-option v-for="ssh in sshList" :key="ssh.id"> <a-select-option v-for="ssh in sshList" :key="ssh.id">
@ -34,7 +34,7 @@
</a-select> </a-select>
</a-col> </a-col>
<a-col :span="1" style="margin-left: 10px"> <a-col :span="1" style="margin-left: 10px">
<a-icon type="reload" @click="loadSshList" /> <ReloadOutlined @click="loadSshList" />
</a-col> </a-col>
</a-row> </a-row>
</a-form-item> </a-form-item>
@ -45,7 +45,7 @@
show-search show-search
option-filter-prop="children" option-filter-prop="children"
mode="multiple" mode="multiple"
v-model="temp.taskDataIds" v-model:value="temp.taskDataIds"
placeholder="请选择节点" placeholder="请选择节点"
> >
<a-select-option v-for="ssh in nodeList" :key="ssh.id"> <a-select-option v-for="ssh in nodeList" :key="ssh.id">
@ -54,16 +54,15 @@
</a-select> </a-select>
</a-col> </a-col>
<a-col :span="1" style="margin-left: 10px"> <a-col :span="1" style="margin-left: 10px">
<a-icon type="reload" @click="loadNodeList" /> <ReloadOutlined @click="loadNodeList" />
</a-col> </a-col>
</a-row> </a-row>
</a-form-item> </a-form-item>
<a-form-item name="releasePathParent" label="发布目录"> <a-form-item name="releasePathParent" label="发布目录">
<template #help> <template v-slot:help>
<a-tooltip title="需要配置授权目录(白名单才能正常使用发布),授权目录主要是用于确定可以发布到哪些目录中" <a-tooltip title="需要配置授权目录(授权才能正常使用发布),授权目录主要是用于确定可以发布到哪些目录中"
><a-button ><a-button
icon="info-circle"
size="small" size="small"
type="link" type="link"
@click=" @click="
@ -72,55 +71,68 @@
} }
" "
> >
配置目录 <InfoCircleOutlined />配置目录
</a-button> </a-button>
</a-tooltip></template </a-tooltip>
> </template>
<a-input-group compact> <a-input-group compact>
<a-select <a-select
show-search show-search
allowClear allowClear
style="width: 30%" style="width: 30%"
v-model="temp.releasePathParent" v-model:value="temp.releasePathParent"
placeholder="请选择发布的一级目录" placeholder="请选择发布的一级目录"
> >
<a-select-option v-for="item in accessList" :key="item"> <a-select-option v-for="item in accessList" :key="item">
<a-tooltip :title="item">{{ item }}</a-tooltip> <a-tooltip :title="item">{{ item }}</a-tooltip>
</a-select-option> </a-select-option>
<a-icon #suffixIcon type="reload" @click="loadAccesList" /> <template v-slot:suffixIcon>
<ReloadOutlined @click="loadAccesList" />
</template>
</a-select> </a-select>
<a-form-item-rest>
<a-input style="width: 70%" v-model="temp.releasePathSecondary" placeholder="请填写发布的二级目录" /> <a-input style="width: 70%" v-model:value="temp.releasePathSecondary" placeholder="请填写发布的二级目录" />
</a-form-item-rest>
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
<a-form-item label="执行脚本" name="releaseBeforeCommand"> <a-form-item label="执行脚本" name="releaseBeforeCommand">
<a-tabs tabPosition="right"> <a-form-item-rest>
<a-tab-pane key="before" tab="上传前"> <a-tabs tabPosition="right">
<div style="height: 40vh; overflow-y: scroll"> <a-tab-pane key="before" tab="上传前">
<code-editor <div style="height: 40vh; overflow-y: scroll">
v-model="temp.beforeScript" <code-editor
:options="{ mode: temp.taskType === 0 ? 'shell' : '', tabSize: 2, theme: 'abcdef' }" v-model:content="temp.beforeScript"
></code-editor> :options="{
</div> mode: temp.taskType === 0 ? 'shell' : '',
<div style="margin-top: 10px">文件上传前需要执行的脚本(非阻塞命令)</div> tabSize: 2,
</a-tab-pane> theme: 'abcdef'
<a-tab-pane key="after" tab="上传后"> }"
<div style="height: 40vh; overflow-y: scroll"> ></code-editor>
<code-editor </div>
v-model="temp.afterScript" <div style="margin-top: 10px">文件上传前需要执行的脚本(非阻塞命令)</div>
:options="{ mode: temp.taskType === 0 ? 'shell' : '', tabSize: 2, theme: 'abcdef' }" </a-tab-pane>
></code-editor> <a-tab-pane key="after" tab="上传后">
</div> <div style="height: 40vh; overflow-y: scroll">
<div style="margin-top: 10px">文件上传成功后需要执行的脚本(非阻塞命令)</div> <code-editor
</a-tab-pane> v-model:content="temp.afterScript"
</a-tabs> :options="{
mode: temp.taskType === 0 ? 'shell' : '',
tabSize: 2,
theme: 'abcdef'
}"
></code-editor>
</div>
<div style="margin-top: 10px">文件上传成功后需要执行的脚本(非阻塞命令)</div>
</a-tab-pane>
</a-tabs>
</a-form-item-rest>
</a-form-item> </a-form-item>
</a-form> </a-form>
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model="configDir" v-model:value="configDir"
:title="`配置授权目录`" :title="`配置授权目录`"
:footer="null" :footer="null"
:maskClosable="false" :maskClosable="false"
@ -142,6 +154,7 @@
</a-modal> </a-modal>
</div> </div>
</template> </template>
<script> <script>
import { getSshListAll } from '@/api/ssh' import { getSshListAll } from '@/api/ssh'
import { getDispatchWhiteList } from '@/api/dispatch' import { getDispatchWhiteList } from '@/api/dispatch'
@ -159,7 +172,13 @@ export default {
releaseFileRules: { releaseFileRules: {
name: [{ required: true, message: '请输入文件任务名', trigger: 'blur' }], name: [{ required: true, message: '请输入文件任务名', trigger: 'blur' }],
taskType: [{ required: true, message: '请选择发布方式', trigger: 'blur' }], taskType: [{ required: true, message: '请选择发布方式', trigger: 'blur' }],
releasePath: [{ required: true, message: '请选择发布的一级目录和填写二级目录', trigger: 'blur' }], releasePath: [
{
required: true,
message: '请选择发布的一级目录和填写二级目录',
trigger: 'blur'
}
],
taskDataIds: [{ required: true, message: '请选择发布的SSH', trigger: 'blur' }] taskDataIds: [{ required: true, message: '请选择发布的SSH', trigger: 'blur' }]
}, },
sshList: [], sshList: [],
@ -185,14 +204,14 @@ export default {
}, },
// //
tryCommit() { tryCommit() {
this.$refs['releaseFileForm'].validate((valid) => { this.$refs['releaseFileForm'].validate().then(() => {
if (!valid) { this.$emit('commit', {
return false ...this.temp,
} taskDataIds: this.temp.taskDataIds?.join(',')
this.$emit('commit', { ...this.temp, taskDataIds: this.temp.taskDataIds?.join(',') }) })
}) })
}, },
// //
loadAccesList() { loadAccesList() {
getDispatchWhiteList().then((res) => { getDispatchWhiteList().then((res) => {
if (res.code === 200) { if (res.code === 200) {
@ -220,6 +239,7 @@ export default {
} }
}) })
} }
} },
emits: ['commit']
} }
</script> </script>

View File

@ -22,8 +22,8 @@
<a-form-item label="执行日志"> <a-form-item label="执行日志">
<a-tabs :activeKey="activeKey" @change="tabCallback"> <a-tabs :activeKey="activeKey" @change="tabCallback">
<a-tab-pane v-for="item in temp.taskList" :key="item.id"> <a-tab-pane v-for="item in temp.taskList" :key="item.id">
<template #tab> <template v-slot:tab>
<a-icon v-if="!logMap[item.id] || logMap[item.id].run" type="loading" /> <LoadingOutlined v-if="!logMap[item.id] || logMap[item.id].run" type="loading" />
<template v-if="temp.taskData && temp.taskData.taskType === 0"> <template v-if="temp.taskData && temp.taskData.taskType === 0">
{{ {{
sshList.filter((item2) => { sshList.filter((item2) => {
@ -45,7 +45,7 @@
}} }}
</template> </template>
<template> <template>
<a-tooltip v-if="item.statusMsg" :title="item.statusMsg"><a-icon type="info-circle" /></a-tooltip> <a-tooltip v-if="item.statusMsg" :title="item.statusMsg"><InfoCircleOutlined /></a-tooltip>
</template> </template>
</template> </template>
<log-view :ref="`logView-${item.id}`" height="60vh" /> <log-view :ref="`logView-${item.id}`" height="60vh" />
@ -57,7 +57,7 @@
<a-tab-pane key="before" tab="上传前"> <a-tab-pane key="before" tab="上传前">
<div style="height: 40vh; overflow-y: scroll"> <div style="height: 40vh; overflow-y: scroll">
<code-editor <code-editor
:code="temp.taskData && temp.taskData.beforeScript" :content="temp.taskData && temp.taskData.beforeScript"
:options="{ :options="{
mode: temp.taskData && temp.taskData.taskType === 0 ? 'shell' : '', mode: temp.taskData && temp.taskData.taskType === 0 ? 'shell' : '',
tabSize: 2, tabSize: 2,
@ -70,7 +70,7 @@
<a-tab-pane key="after" tab="上传后"> <a-tab-pane key="after" tab="上传后">
<div style="height: 40vh; overflow-y: scroll"> <div style="height: 40vh; overflow-y: scroll">
<code-editor <code-editor
:code="temp.taskData && temp.taskData.afterScript" :content="temp.taskData && temp.taskData.afterScript"
:options="{ :options="{
mode: temp.taskData && temp.taskData.taskType === 0 ? 'shell' : '', mode: temp.taskData && temp.taskData.taskType === 0 ? 'shell' : '',
tabSize: 2, tabSize: 2,
@ -85,9 +85,10 @@
</a-form> </a-form>
</div> </div>
</template> </template>
<script> <script>
import { taskDetails, statusMap, taskLogInfoList } from '@/api/file-manager/release-task-log' import { taskDetails, statusMap, taskLogInfoList } from '@/api/file-manager/release-task-log'
import LogView from '@/components/logView' import LogView from '@/components/logView/index2'
import codeEditor from '@/components/codeEditor' import codeEditor from '@/components/codeEditor'
import { getSshListAll } from '@/api/ssh' import { getSshListAll } from '@/api/ssh'
import { getNodeListAll } from '@/api/node' import { getNodeListAll } from '@/api/node'
@ -114,7 +115,7 @@ export default {
nodeList: [] nodeList: []
} }
}, },
beforeDestroy() { beforeUnmount() {
if (this.logTimerMap) { if (this.logTimerMap) {
this.temp.taskList?.forEach((item) => { this.temp.taskList?.forEach((item) => {
clearInterval(this.logTimerMap[item.id]) clearInterval(this.logTimerMap[item.id])
@ -190,7 +191,7 @@ export default {
taskLogInfoList(params).then((res) => { taskLogInfoList(params).then((res) => {
if (res.code === 200) { if (res.code === 200) {
if (!res.data) { if (!res.data) {
$notification.warning({ this.$notification.warning({
message: res.msg message: res.msg
}) })
if (res.data.status !== 0) { if (res.data.status !== 0) {
@ -226,7 +227,7 @@ export default {
if (this.logTimerMap[key]) { if (this.logTimerMap[key]) {
return return
} }
nextTick(() => { this.$nextTick(() => {
const data = this.temp.taskList?.filter((item1) => { const data = this.temp.taskList?.filter((item1) => {
return item1.id === key return item1.id === key
})[0] })[0]
@ -236,4 +237,3 @@ export default {
} }
} }
</script> </script>
<style scoped></style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="full-content"> <div>
<a-table <a-table
size="middle" size="middle"
:data-source="commandList" :data-source="commandList"
@ -13,11 +13,14 @@
} }
" "
rowKey="id" rowKey="id"
:scroll="{
x: 'max-content'
}"
> >
<template #title> <template v-slot:title>
<a-space> <a-space>
<a-input <a-input
v-model="listQuery['%name%']" v-model:value="listQuery['%name%']"
@pressEnter="loadData" @pressEnter="loadData"
placeholder="任务名" placeholder="任务名"
class="search-input-item" class="search-input-item"
@ -25,7 +28,7 @@
<a-select <a-select
show-search show-search
option-filter-prop="children" option-filter-prop="children"
v-model="listQuery.status" v-model:value="listQuery.status"
allowClear allowClear
placeholder="状态" placeholder="状态"
class="search-input-item" class="search-input-item"
@ -35,7 +38,7 @@
<a-select <a-select
show-search show-search
option-filter-prop="children" option-filter-prop="children"
v-model="listQuery.taskType" v-model:value="listQuery.taskType"
allowClear allowClear
placeholder="发布类型" placeholder="发布类型"
class="search-input-item" class="search-input-item"
@ -47,45 +50,61 @@
</a-tooltip> </a-tooltip>
</a-space> </a-space>
</template> </template>
<a-tooltip #tooltip slot-scope="text" placement="topLeft" :title="text"> <template #bodyCell="{ column, text, record, index }">
<span>{{ text }}</span> <template v-if="column.tooltip">
</a-tooltip> <a-tooltip placement="topLeft" :title="text">
<span>{{ text }}</span>
</a-tooltip>
</template>
<template #fileId slot-scope="text, item"> <template v-else-if="column.dataIndex === 'fileId'">
<a-button type="link" style="padding: 0" @click="handleViewFile(item)" size="small">{{ text }}</a-button> <a-tooltip :title="text">
</template> <a-button type="link" style="padding: 0" @click="handleViewFile(record)" size="small">{{
(text || '').slice(0, 10)
}}</a-button>
</a-tooltip>
</template>
<template #status slot-scope="text"> <template v-else-if="column.dataIndex === 'status'">
<a-tag v-if="text === 2" color="green">{{ statusMap[text] || '未知' }}</a-tag> <a-tag v-if="text === 2" color="green">{{ statusMap[text] || '未知' }}</a-tag>
<a-tag v-else-if="text === 0 || text === 1" color="orange">{{ statusMap[text] || '未知' }}</a-tag> <a-tag v-else-if="text === 0 || text === 1" color="orange">{{ statusMap[text] || '未知' }}</a-tag>
<a-tag v-else-if="text === 4" color="blue"> {{ statusMap[text] || '未知' }} </a-tag> <a-tag v-else-if="text === 4" color="blue">
<a-tag v-else-if="text === 3" color="red">{{ statusMap[text] || '未知' }}</a-tag> {{ statusMap[text] || '未知' }}
<a-tag v-else>{{ statusMap[text] || '未知' }}</a-tag> </a-tag>
</template> <a-tag v-else-if="text === 3" color="red">{{ statusMap[text] || '未知' }}</a-tag>
<template #taskType slot-scope="text"> <a-tag v-else>{{ statusMap[text] || '未知' }}</a-tag>
<span>{{ taskTypeMap[text] || '未知' }}</span> </template>
</template> <template v-else-if="column.dataIndex === 'taskType'">
<span>{{ taskTypeMap[text] || '未知' }}</span>
</template>
<template v-else-if="column.dataIndex === 'fileType'">
<span v-if="text == 2">静态文件</span>
<span v-else>文件中心</span>
</template>
<template #operation slot-scope="text, record"> <template v-else-if="column.dataIndex === 'operation'">
<a-space> <a-space>
<a-button type="primary" size="small" @click="handleView(record)">查看</a-button> <a-button type="primary" size="small" @click="handleView(record)">查看</a-button>
<a-button type="primary" size="small" @click="handleRetask(record)">重建</a-button> <a-button type="primary" size="small" @click="handleRetask(record)">重建</a-button>
<a-button <a-button
type="danger" type="primary"
size="small" danger
:disabled="!(record.status === 0 || record.status === 1)" size="small"
@click="handleCancelTask(record)" :disabled="!(record.status === 0 || record.status === 1)"
>取消</a-button @click="handleCancelTask(record)"
> >取消</a-button
<a-button >
type="danger" <a-button
size="small" type="primary"
:disabled="record.status === 0 || record.status === 1" danger
@click="handleDelete(record)" size="small"
>删除</a-button :disabled="record.status === 0 || record.status === 1"
> @click="handleDelete(record)"
</a-space> >删除</a-button
>
</a-space>
</template>
</template> </template>
</a-table> </a-table>
<!-- 任务详情 --> <!-- 任务详情 -->
@ -93,19 +112,20 @@
title="任务详情" title="任务详情"
placement="right" placement="right"
:width="'80vw'" :width="'80vw'"
:visible="detailsVisible" :open="detailsVisible"
@close=" @close="
() => { () => {
this.detailsVisible = false this.detailsVisible = false
} }
" "
> >
<task-details-page v-if="detailsVisible" :taskId="temp.id" /> <task-details-page v-if="detailsVisible" :taskId="this.temp.id" />
</a-drawer> </a-drawer>
<!-- 重建任务 --> <!-- 重建任务 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model="releaseFileVisible" :confirmLoading="confirmLoading"
v-model:open="releaseFileVisible"
title="发布文件" title="发布文件"
width="50%" width="50%"
:maskClosable="false" :maskClosable="false"
@ -119,11 +139,11 @@
:wrapper-col="{ span: 20 }" :wrapper-col="{ span: 20 }"
> >
<a-form-item label="任务名" name="name"> <a-form-item label="任务名" name="name">
<a-input placeholder="请输入任务名" :maxLength="50" v-model="temp.name" /> <a-input placeholder="请输入任务名" :maxLength="50" v-model:value="temp.name" />
</a-form-item> </a-form-item>
<a-form-item label="发布方式" name="taskType"> <a-form-item label="发布方式" name="taskType">
<a-radio-group v-model="temp.taskType" :disabled="true"> <a-radio-group v-model:value="temp.taskType" :disabled="true">
<a-radio :value="0"> SSH </a-radio> <a-radio :value="0"> SSH </a-radio>
<a-radio :value="1"> 节点 </a-radio> <a-radio :value="1"> 节点 </a-radio>
</a-radio-group> </a-radio-group>
@ -136,7 +156,7 @@
show-search show-search
option-filter-prop="children" option-filter-prop="children"
mode="multiple" mode="multiple"
v-model="temp.taskDataIds" v-model:value="temp.taskDataIds"
placeholder="请选择SSH" placeholder="请选择SSH"
> >
<a-select-option v-for="ssh in sshList" :key="ssh.id"> <a-select-option v-for="ssh in sshList" :key="ssh.id">
@ -145,7 +165,7 @@
</a-select> </a-select>
</a-col> </a-col>
<a-col :span="1" style="margin-left: 10px"> <a-col :span="1" style="margin-left: 10px">
<a-icon type="reload" @click="loadSshList" /> <ReloadOutlined @click="loadSshList" />
</a-col> </a-col>
</a-row> </a-row>
</a-form-item> </a-form-item>
@ -156,7 +176,7 @@
show-search show-search
option-filter-prop="children" option-filter-prop="children"
mode="multiple" mode="multiple"
v-model="temp.taskDataIds" v-model:value="temp.taskDataIds"
placeholder="请选择节点" placeholder="请选择节点"
> >
<a-select-option v-for="ssh in nodeList" :key="ssh.id"> <a-select-option v-for="ssh in nodeList" :key="ssh.id">
@ -165,45 +185,55 @@
</a-select> </a-select>
</a-col> </a-col>
<a-col :span="1" style="margin-left: 10px"> <a-col :span="1" style="margin-left: 10px">
<a-icon type="reload" @click="loadNodeList" /> <ReloadOutlined @click="loadNodeList" />
</a-col> </a-col>
</a-row> </a-row>
</a-form-item> </a-form-item>
<a-form-item name="releasePathParent" label="发布目录"> <a-form-item name="releasePathParent" label="发布目录">
<a-input placeholder="请输入发布目录" :disabled="true" v-model="temp.releasePath" /> <a-input placeholder="请输入发布目录" :disabled="true" v-model:value="temp.releasePath" />
</a-form-item> </a-form-item>
<a-form-item name="releasePathParent" label="文件id"> <a-form-item name="releasePathParent" label="文件id">
<a-input placeholder="请输入发布的文件id" v-model="temp.fileId" /> <a-input placeholder="请输入发布的文件id" v-model:value="temp.fileId" />
</a-form-item> </a-form-item>
<a-form-item label="执行脚本" name="releaseBeforeCommand"> <a-form-item label="执行脚本" name="releaseBeforeCommand">
<a-tabs tabPosition="right"> <a-form-item-rest>
<a-tab-pane key="before" tab="上传前"> <a-tabs tabPosition="right">
<div style="height: 40vh; overflow-y: scroll"> <a-tab-pane key="before" tab="上传前">
<code-editor <div style="height: 40vh; overflow-y: scroll">
v-model="temp.beforeScript" <code-editor
:options="{ mode: temp.taskType === 0 ? 'shell' : '', tabSize: 2, theme: 'abcdef' }" v-model:content="temp.beforeScript"
></code-editor> :options="{
</div> mode: temp.taskType === 0 ? 'shell' : '',
<div style="margin-top: 10px">文件上传前需要执行的脚本(非阻塞命令)</div> tabSize: 2,
</a-tab-pane> theme: 'abcdef'
<a-tab-pane key="after" tab="上传后"> }"
<div style="height: 40vh; overflow-y: scroll"> ></code-editor>
<code-editor </div>
v-model="temp.afterScript" <div style="margin-top: 10px">文件上传前需要执行的脚本(非阻塞命令)</div>
:options="{ mode: temp.taskType === 0 ? 'shell' : '', tabSize: 2, theme: 'abcdef' }" </a-tab-pane>
></code-editor> <a-tab-pane key="after" tab="上传后">
</div> <div style="height: 40vh; overflow-y: scroll">
<div style="margin-top: 10px">文件上传成功后需要执行的脚本(非阻塞命令)</div> <code-editor
</a-tab-pane> v-model:content="temp.afterScript"
</a-tabs> :options="{
mode: temp.taskType === 0 ? 'shell' : '',
tabSize: 2,
theme: 'abcdef'
}"
></code-editor>
</div>
<div style="margin-top: 10px">文件上传成功后需要执行的脚本(非阻塞命令)</div>
</a-tab-pane>
</a-tabs>
</a-form-item-rest>
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-modal> </a-modal>
<!-- 查看文件 --> <!-- 查看文件 -->
<a-modal destroyOnClose v-model:visible="viewFileVisible" :title="`查看文件`" :footer="null" :maskClosable="false"> <a-modal destroyOnClose v-model:open="viewFileVisible" :title="`查看文件`" :footer="null" :maskClosable="false">
<a-form :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }"> <a-form :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
<a-form-item label="文件名" name="name"> <a-form-item label="文件名" name="name">
{{ temp.name }} {{ temp.name }}
@ -214,10 +244,10 @@
<a-form-item label="文件大小" name="size"> <a-form-item label="文件大小" name="size">
{{ renderSize(temp.size) }} {{ renderSize(temp.size) }}
</a-form-item> </a-form-item>
<a-form-item label="过期时间" name="keepDay"> <a-form-item label="过期时间" name="validUntil" v-if="temp.validUntil">
{{ parseTime(temp.validUntil) }} {{ parseTime(temp.validUntil) }}
</a-form-item> </a-form-item>
<a-form-item label="文件共享" name="global"> <a-form-item label="文件共享" name="global" v-if="temp.workspaceId">
{{ temp.workspaceId === 'GLOBAL' ? '全局' : '工作空间' }} {{ temp.workspaceId === 'GLOBAL' ? '全局' : '工作空间' }}
</a-form-item> </a-form-item>
<a-form-item label="文件描述" name="description"> <a-form-item label="文件描述" name="description">
@ -244,7 +274,7 @@ import { getSshListAll } from '@/api/ssh'
import codeEditor from '@/components/codeEditor' import codeEditor from '@/components/codeEditor'
import { hasFile } from '@/api/file-manager/file-storage' import { hasFile } from '@/api/file-manager/file-storage'
import { getNodeListAll } from '@/api/node' import { getNodeListAll } from '@/api/node'
import { hasStaticFile } from '@/api/file-manager/static-storage'
export default { export default {
components: { components: {
taskDetailsPage, taskDetailsPage,
@ -259,45 +289,66 @@ export default {
statusMap, statusMap,
taskTypeMap, taskTypeMap,
detailsVisible: false, detailsVisible: false,
confirmLoading: false,
columns: [ columns: [
{ title: '任务名称', dataIndex: 'name', ellipsis: true, width: 150, scopedSlots: { customRender: 'tooltip' } }, {
title: '任务名称',
dataIndex: 'name',
ellipsis: true,
width: 150,
tooltip: true
},
{ {
title: '分发类型', title: '分发类型',
dataIndex: 'taskType', dataIndex: 'taskType',
width: '100px', width: '100px',
ellipsis: true, ellipsis: true
scopedSlots: { customRender: 'taskType' } },
{
title: '文件来源',
dataIndex: 'fileType',
width: '100px',
ellipsis: true
},
{
title: '状态',
dataIndex: 'status',
width: '100px',
ellipsis: true
}, },
{ title: '状态', dataIndex: 'status', width: '100px', ellipsis: true, scopedSlots: { customRender: 'status' } },
{ {
title: '状态描述', title: '状态描述',
dataIndex: 'statusMsg', dataIndex: 'statusMsg',
ellipsis: true, ellipsis: true,
width: 200, width: 200,
scopedSlots: { customRender: 'tooltip' } tooltip: true
},
{
title: '文件ID',
dataIndex: 'fileId',
ellipsis: true,
width: 150
}, },
{ title: '文件ID', dataIndex: 'fileId', ellipsis: true, width: 150, scopedSlots: { customRender: 'fileId' } },
{ {
title: '发布目录', title: '发布目录',
dataIndex: 'releasePath', dataIndex: 'releasePath',
width: '100px', width: '100px',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
{ {
title: '执行人', title: '执行人',
dataIndex: 'modifyUser', dataIndex: 'modifyUser',
width: '120px', width: '120px',
ellipsis: true, ellipsis: true
scopedSlots: { customRender: 'modifyUser' }
}, },
{ {
title: '任务时间', title: '任务时间',
dataIndex: 'createTimeMillis', dataIndex: 'createTimeMillis',
sorter: true, sorter: true,
ellipsis: true, ellipsis: true,
customRender: (text) => parseTime(text), customRender: ({ text }) => parseTime(text),
width: '170px' width: '170px'
}, },
{ {
@ -305,7 +356,7 @@ export default {
dataIndex: 'modifyTimeMillis', dataIndex: 'modifyTimeMillis',
sorter: true, sorter: true,
ellipsis: true, ellipsis: true,
customRender: (text) => parseTime(text), customRender: ({ text }) => parseTime(text),
width: '170px' width: '170px'
}, },
@ -313,7 +364,7 @@ export default {
title: '操作', title: '操作',
dataIndex: 'operation', dataIndex: 'operation',
align: 'center', align: 'center',
scopedSlots: { customRender: 'operation' },
fixed: 'right', fixed: 'right',
width: '230px' width: '230px'
} }
@ -361,8 +412,9 @@ export default {
// //
handleDelete(row) { handleDelete(row) {
$confirm({ this.$confirm({
title: '系统提示', title: '系统提示',
zIndex: 1009,
content: '真的要删除该执行记录吗?', content: '真的要删除该执行记录吗?',
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',
@ -372,7 +424,7 @@ export default {
id: row.id id: row.id
}).then((res) => { }).then((res) => {
if (res.code === 200) { if (res.code === 200) {
$notification.success({ this.$notification.success({
message: res.msg message: res.msg
}) })
this.loadData() this.loadData()
@ -430,27 +482,33 @@ export default {
}, },
// //
handleReCrateTask() { handleReCrateTask() {
this.$refs['releaseFileForm'].validate((valid) => { this.$refs['releaseFileForm'].validate().then(() => {
if (!valid) { this.confirmLoading = true
return false reReleaseTask({
} ...this.temp,
reReleaseTask({ ...this.temp, taskDataIds: this.temp.taskDataIds?.join(',') }).then((res) => { taskDataIds: this.temp.taskDataIds?.join(',')
if (res.code === 200) {
//
$notification.success({
message: res.msg
})
this.releaseFileVisible = false
this.loadData()
}
}) })
.then((res) => {
if (res.code === 200) {
//
this.$notification.success({
message: res.msg
})
this.releaseFileVisible = false
this.loadData()
}
})
.finally(() => {
this.confirmLoading = false
})
}) })
}, },
// //
handleCancelTask(record) { handleCancelTask(record) {
$confirm({ this.$confirm({
title: '系统提示', title: '系统提示',
zIndex: 1009,
content: '真的取消当前发布任务吗?', content: '真的取消当前发布任务吗?',
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',
@ -458,7 +516,7 @@ export default {
// //
cancelReleaseTask({ id: record.id }).then((res) => { cancelReleaseTask({ id: record.id }).then((res) => {
if (res.code === 200) { if (res.code === 200) {
$notification.success({ this.$notification.success({
message: res.msg message: res.msg
}) })
this.loadData() this.loadData()
@ -469,22 +527,39 @@ export default {
}, },
// //
handleViewFile(record) { handleViewFile(record) {
hasFile({ if (record.fileType === 2) {
fileSumMd5: record.fileId //
}).then((res) => { hasStaticFile({
if (res.code === 200) { fileId: record.fileId
if (res.data) { }).then((res) => {
this.temp = res.data if (res.code === 200) {
this.viewFileVisible = true if (res.data) {
} else { this.temp = res.data
$notification.warning({ this.viewFileVisible = true
message: '文件不存在啦' } else {
}) this.$notification.warning({
message: '文件不存在啦'
})
}
} }
} })
}) } else {
hasFile({
fileSumMd5: record.fileId
}).then((res) => {
if (res.code === 200) {
if (res.data) {
this.temp = res.data
this.viewFileVisible = true
} else {
this.$notification.warning({
message: '文件不存在啦'
})
}
}
})
}
} }
} }
} }
</script> </script>
<style scoped></style>

View File

@ -0,0 +1,609 @@
<template>
<div>
<div>
<!-- 数据表格 -->
<a-table
:data-source="list"
size="middle"
:columns="columns"
:pagination="pagination"
@change="
(pagination, filters, sorter) => {
this.listQuery = CHANGE_PAGE(this.listQuery, { pagination, sorter })
this.loadData()
}
"
bordered
rowKey="id"
:row-selection="rowSelection"
:scroll="{
x: 'max-content'
}"
>
<template v-slot:title>
<a-space>
<a-input
v-model:value="listQuery['%name%']"
@pressEnter="loadData"
placeholder="文件名称"
class="search-input-item"
/>
<a-input
v-model:value="listQuery['extName']"
@pressEnter="loadData"
placeholder="后缀,精准搜索"
class="search-input-item"
/>
<a-input
v-model:value="listQuery['id']"
@pressEnter="loadData"
placeholder="文件id,精准搜索"
class="search-input-item"
/>
<a-tooltip title="按住 Ctr 或者 Alt/Option 键点击按钮快速回到第一页">
<a-button type="primary" :loading="loading" @click="loadData">搜索</a-button>
</a-tooltip>
<!-- <a-button type="primary" @click="handleUpload">上传文件</a-button> -->
<a-button type="primary" @click="reScanner">扫描</a-button>
<a-button
type="primary"
danger
:disabled="!tableSelections || tableSelections.length <= 0"
@click="handleBatchDelete"
>
批量删除
</a-button>
</a-space>
</template>
<template #bodyCell="{ column, text, record, index }">
<template v-if="column.tooltip">
<a-tooltip placement="topLeft" :title="text">
<span>{{ text }}</span>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'id'">
<a-tooltip placement="topLeft" :title="text">
<span v-if="record.status === 0 || record.status === 2">-</span>
<span v-else>{{ (text || '').slice(0, 8) }}</span>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'name'">
<a-popover title="文件信息">
<template v-slot:content>
<p>文件ID{{ record.id }}</p>
<p>文件名{{ text }}</p>
<p>文件描述{{ record.description }}</p>
</template>
<!-- {{ text }} -->
<a-button type="link" style="padding: 0" @click="handleEdit(record)" size="small">{{ text }}</a-button>
</a-popover>
</template>
<template v-else-if="column.dataIndex === 'size'">
<a-tooltip placement="topLeft" :title="renderSize(text)">
<span>{{ renderSize(text) }}</span>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'source'">
<a-tooltip placement="topLeft" :title="`${sourceMap[text] || '未知'}`">
<span>{{ sourceMap[text] || '未知' }}</span>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'status'">
<a-tag v-if="text === 1" color="green">存在</a-tag>
<a-tag v-else color="red">丢失</a-tag>
</template>
<template v-else-if="column.dataIndex === 'type'">
<a-tag v-if="text === 1">文件</a-tag>
<a-tag v-else>文件夹</a-tag>
</template>
<template v-else-if="column.dataIndex === 'operation'">
<a-space>
<!-- <a-button type="primary" size="small" @click="handleEdit(record)">编辑</a-button> -->
<a-button
size="small"
:disabled="!(record.status === 1 && record.type === 1)"
type="primary"
@click="handleDownloadUrl(record)"
>
下载</a-button
>
<a-button
size="small"
:disabled="!(record.status === 1 && record.type === 1)"
type="primary"
@click="handleReleaseFile(record)"
>发布</a-button
>
<a-button type="primary" danger size="small" @click="handleDelete(record)">删除</a-button>
</a-space>
</template>
</template>
</a-table>
<!-- 编辑文件 -->
<a-modal
destroyOnClose
v-model:open="editVisible"
:title="`修改文件`"
:confirmLoading="confirmLoading"
@ok="handleEditOk"
:maskClosable="false"
>
<a-form ref="editForm" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
<a-form-item label="文件名" name="name">
<a-input placeholder="文件名" :disabled="true" v-model:value="temp.name" />
</a-form-item>
<a-form-item label="文件描述" name="description">
<a-textarea v-model:value="temp.description" placeholder="请输入文件描述" />
</a-form-item>
</a-form>
</a-modal>
<!-- 断点下载 -->
<a-modal
destroyOnClose
v-model:open="triggerVisible"
title="断点/分片下载"
width="50%"
:footer="null"
:maskClosable="false"
>
<a-form ref="editTriggerForm" :model="temp" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
<a-tabs default-active-key="1">
<template v-slot:rightExtra>
<a-tooltip title="重置下载 token 信息,重置后之前的下载 token 将失效">
<a-button type="primary" size="small" @click="resetTrigger">重置</a-button>
</a-tooltip>
</template>
<a-tab-pane key="1" tab="断点/分片单文件下载">
<a-space direction="vertical" style="width: 100%">
<a-alert type="info" :message="`下载地址(点击可以复制)`">
<template v-slot:description>
<a-typography-paragraph :copyable="{ text: temp.triggerDownloadUrl }">
<a-tag>GET</a-tag>
<span>{{ `${temp.triggerDownloadUrl}` }} </span>
</a-typography-paragraph>
</template>
</a-alert>
<a :href="temp.triggerDownloadUrl" target="_blank">
<a-button size="small" type="primary"><DownloadOutlined />立即下载</a-button>
</a>
</a-space>
</a-tab-pane>
<a-tab-pane tab="断点/分片别名下载" v-if="temp.triggerAliasDownloadUrl">
<a-space direction="vertical" style="width: 100%">
<a-alert message="温馨提示" type="warning">
<template v-slot:description>
<ul>
<li>
支持自定义排序字段sort=createTimeMillis:desc
<p>描述根据创建时间升序第一个</p>
</li>
<li>支持的字段可以通过接口返回的查看</li>
<li>通用的字段有createTimeMillismodifyTimeMillis</li>
</ul>
</template>
</a-alert>
<a-alert type="info" :message="`下载地址(点击可以复制)`">
<template v-slot:description>
<a-typography-paragraph :copyable="{ text: temp.triggerAliasDownloadUrl }">
<a-tag>GET</a-tag>
<span>{{ `${temp.triggerAliasDownloadUrl}` }} </span>
</a-typography-paragraph>
</template>
</a-alert>
<a :href="temp.triggerAliasDownloadUrl" target="_blank">
<a-button size="small" type="primary"><DownloadOutlined />立即下载</a-button>
</a>
</a-space>
</a-tab-pane>
</a-tabs>
</a-form>
</a-modal>
<!-- 发布文件 -->
<a-modal
destroyOnClose
v-model:open="releaseFileVisible"
title="发布文件"
width="50%"
:maskClosable="false"
:confirmLoading="confirmLoading"
@ok="releaseFileOk()"
>
<releaseFile ref="releaseFile" v-if="releaseFileVisible" @commit="handleCommitTask"></releaseFile>
</a-modal>
</div>
<!-- 选择确认区域
<div style="padding-top: 50px" v-if="this.choose">
<div
:style="{
position: 'absolute',
right: 0,
bottom: 0,
width: '100%',
borderTop: '1px solid #e9e9e9',
padding: '10px 16px',
background: '#fff',
textAlign: 'right',
zIndex: 1
}"
>
<a-space>
<a-button
@click="
() => {
this.$emit('cancel')
}
"
>
取消
</a-button>
<a-button type="primary" @click="handerConfirm"> 确定 </a-button>
</a-space>
</div>
</div>
-->
</div>
</template>
<script>
import {
CHANGE_PAGE,
COMPUTED_PAGINATION,
PAGE_DEFAULT_LIST_QUERY,
parseTime,
renderSize,
formatDuration,
randomStr
} from '@/utils/const'
// import { uploadFile, uploadFileMerge, hasFile } from "@/api/file-manager/file-storage";
import { staticFileStorageList, delFile, triggerUrl, fileEdit, staticScanner } from '@/api/file-manager/static-storage'
import releaseFile from '@/pages/file-manager/fileStorage/releaseFile'
import { addReleaseTask } from '@/api/file-manager/release-task-log'
export default {
components: {
releaseFile
},
props: {
choose: {
// "radio"
type: String,
default: ''
}
},
data() {
return {
loading: true,
listQuery: Object.assign({}, PAGE_DEFAULT_LIST_QUERY),
list: [],
columns: [
{
title: '名称',
dataIndex: 'name',
ellipsis: true,
width: 150
},
{
title: '描述',
dataIndex: 'description',
ellipsis: true,
width: 150,
tooltip: true
},
{
title: '路径',
dataIndex: 'absolutePath',
ellipsis: true,
width: 150,
tooltip: true
},
{
title: '大小',
dataIndex: 'size',
sorter: true,
ellipsis: true,
width: '100px'
},
{
title: '后缀',
dataIndex: 'extName',
ellipsis: true,
tooltip: true,
width: '80px'
},
{
title: '类型',
dataIndex: 'type',
ellipsis: true,
width: '80px'
},
{
title: '文件状态',
dataIndex: 'status',
ellipsis: true,
width: '80px'
},
{
title: '文件修改时间',
dataIndex: 'lastModified',
sorter: true,
customRender: ({ text }) => parseTime(text),
width: '170px'
},
{
title: '操作',
dataIndex: 'operation',
align: 'center',
ellipsis: true,
fixed: 'right',
width: '170px'
}
],
rules: {
name: [{ required: true, message: '请输入文件名称', trigger: 'blur' }],
url: [{ required: true, message: '请输入远程地址', trigger: 'blur' }]
},
temp: {},
fileList: [],
percentage: 0,
percentageInfo: {},
uploading: false,
uploadVisible: false,
editVisible: false,
tempVue: null,
triggerVisible: false,
releaseFileVisible: false,
tableSelections: [],
confirmLoading: false
}
},
computed: {
pagination() {
return COMPUTED_PAGINATION(this.listQuery)
},
rowSelection() {
return {
onChange: (selectedRowKeys) => {
this.tableSelections = selectedRowKeys
},
selectedRowKeys: this.tableSelections,
type: this.choose || 'checkbox'
}
}
},
created() {
this.loadData()
},
methods: {
randomStr,
CHANGE_PAGE,
renderSize,
formatDuration,
parseTime,
//
loadData(pointerEvent) {
this.loading = true
this.listQuery.page = pointerEvent?.altKey || pointerEvent?.ctrlKey ? 1 : this.listQuery.page
staticFileStorageList(this.listQuery).then((res) => {
if (res.code === 200) {
this.list = res.data.result
this.listQuery.total = res.data.total
}
this.loading = false
})
},
//
handleEdit(item) {
this.temp = {
...item,
global: item.workspaceId === 'GLOBAL',
workspaceId: ''
}
this.editVisible = true
this.$refs['editForm']?.resetFields()
},
//
handleEditOk() {
this.$refs['editForm'].validate().then(() => {
this.confirmLoading = true
fileEdit(this.temp)
.then((res) => {
if (res.code === 200) {
//
this.$notification.success({
message: res.msg
})
this.editVisible = false
this.loadData()
}
})
.finally(() => {
this.confirmLoading = false
})
})
},
//
handleDelete(record) {
this.$confirm({
title: '系统提示',
zIndex: 1009,
content: '真的要删除当前文件么?' + record.name,
okText: '确认',
cancelText: '取消',
onOk: () => {
//
delFile({
id: record.id,
thorough: false
}).then((res) => {
if (res.code === 200) {
this.$notification.success({
message: res.msg
})
this.loadData()
}
})
}
})
},
//
handleBatchDelete() {
if (!this.tableSelections || this.tableSelections.length <= 0) {
this.$notification.warning({
message: '没有选择任何数据'
})
return
}
this.$confirm({
title: '系统提示',
zIndex: 1009,
content: '真的要删除这些文件么?',
okText: '确认',
cancelText: '取消',
onOk: () => {
//
delFile({
ids: this.tableSelections.join(','),
thorough: false
}).then((res) => {
if (res.code === 200) {
this.$notification.success({
message: res.msg
})
this.tableSelections = []
this.loadData()
}
})
}
})
},
//
handleDownloadUrl(record) {
this.temp = Object.assign({}, record)
this.tempVue = Vue
triggerUrl({
id: record.id
}).then((res) => {
if (res.code === 200) {
this.fillDownloadUrlResult(res)
this.$nextTick(() => {
this.triggerVisible = true
})
}
})
},
//
resetTrigger() {
triggerUrl({
id: this.temp.id,
rest: 'rest'
}).then((res) => {
if (res.code === 200) {
this.$notification.success({
message: res.msg
})
this.fillDownloadUrlResult(res)
}
})
},
fillDownloadUrlResult(res) {
this.temp = {
...this.temp,
triggerDownloadUrl: `${location.protocol}//${location.host}${res.data.triggerDownloadUrl}`,
triggerAliasDownloadUrl: res.data?.triggerAliasDownloadUrl
? `${location.protocol}//${location.host}${res.data.triggerAliasDownloadUrl}?sort=createTimeMillis:desc`
: ''
}
},
//
handleReleaseFile(record) {
this.releaseFileVisible = true
this.temp = { fileId: record.id }
},
handleCommitTask(data) {
this.confirmLoading = true
addReleaseTask({ ...data, fileId: this.temp.fileId, fileType: 2 })
.then((res) => {
if (res.code === 200) {
//
this.$notification.success({
message: res.msg
})
this.releaseFileVisible = false
this.loadData()
}
})
.finally(() => {
this.confirmLoading = false
})
},
releaseFileOk() {
this.$refs.releaseFile?.tryCommit()
},
//
handerConfirm() {
if (!this.tableSelections.length) {
this.$notification.warning({
message: '请选择要使用的文件'
})
return
}
const selectData = this.list.filter((item) => {
return this.tableSelections.indexOf(item.id) > -1
})
if (!selectData.length) {
this.$notification.warning({
message: '请选择要使用的文件'
})
return
}
this.$emit('confirm', selectData)
},
//
reScanner() {
staticScanner({}).then((res) => {
if (res.code === 200) {
this.$notification.success({
message: res.msg
})
this.loadData()
}
})
}
},
emits: ['cancel', 'confirm']
}
</script>
<style scoped>
/deep/ .ant-progress-text {
width: auto;
}
</style>

View File

@ -18,7 +18,7 @@
<a-input-search <a-input-search
placeholder="请输入工作空间备注,留空使用默认的名称" placeholder="请输入工作空间备注,留空使用默认的名称"
enter-button="确定" enter-button="确定"
v-model="element.name" v-model:value="element.name"
@search="editOk(element)" @search="editOk(element)"
/> />
</template> </template>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="full-content"> <div>
<!-- 数据表格 --> <!-- 数据表格 -->
<a-table <a-table
:data-source="list" :data-source="list"
@ -8,25 +8,27 @@
:pagination="pagination" :pagination="pagination"
@change="changePage" @change="changePage"
bordered bordered
:rowKey="(record, index) => index" :scroll="{
x: 'max-content'
}"
> >
<template #title> <template v-slot:title>
<a-space> <a-space>
<a-input <a-input
v-model="listQuery['%name%']" v-model:value="listQuery['%name%']"
@pressEnter="loadData" @pressEnter="loadData"
placeholder="监控名称" placeholder="监控名称"
class="search-input-item" class="search-input-item"
/> />
<a-select v-model="listQuery.status" allowClear placeholder="开启状态" class="search-input-item"> <a-select v-model:value="listQuery.status" allowClear placeholder="开启状态" class="search-input-item">
<a-select-option :value="1">开启</a-select-option> <a-select-option :value="1">开启</a-select-option>
<a-select-option :value="0">关闭</a-select-option> <a-select-option :value="0">关闭</a-select-option>
</a-select> </a-select>
<a-select v-model="listQuery.autoRestart" allowClear placeholder="自动重启" class="search-input-item"> <a-select v-model:value="listQuery.autoRestart" allowClear placeholder="自动重启" class="search-input-item">
<a-select-option :value="1"></a-select-option> <a-select-option :value="1"></a-select-option>
<a-select-option :value="0"></a-select-option> <a-select-option :value="0"></a-select-option>
</a-select> </a-select>
<a-select v-model="listQuery.alarm" allowClear placeholder="报警状态" class="search-input-item"> <a-select v-model:value="listQuery.alarm" allowClear placeholder="报警状态" class="search-input-item">
<a-select-option :value="1">报警中</a-select-option> <a-select-option :value="1">报警中</a-select-option>
<a-select-option :value="0">未报警</a-select-option> <a-select-option :value="0">未报警</a-select-option>
</a-select> </a-select>
@ -36,50 +38,35 @@
<a-button type="primary" @click="handleAdd">新增</a-button> <a-button type="primary" @click="handleAdd">新增</a-button>
</a-space> </a-space>
</template> </template>
<a-tooltip #name slot-scope="text" placement="topLeft" :title="text"> <template #bodyCell="{ column, text, record }">
<span>{{ text }}</span> <template v-if="column.dataIndex === 'name'">
</a-tooltip> <a-tooltip placement="topLeft" :title="text">
<a-switch <span>{{ text }}</span>
#status </a-tooltip>
size="small" </template>
slot-scope="text" <template v-else-if="column.dataIndex === 'status'">
:checked="text" <a-switch size="small" :checked="text" disabled checked-children="开启" un-checked-children="关闭" />
disabled </template>
checked-children="开启" <template v-else-if="column.dataIndex === 'autoRestart'">
un-checked-children="关闭" <a-switch size="small" :checked="text" disabled checked-children="" un-checked-children="" />
/> </template>
<a-switch <template v-else-if="column.dataIndex === 'alarm'">
#autoRestart <a-switch size="small" :checked="text" disabled checked-children="报警中" un-checked-children="未报警" />
size="small" </template>
slot-scope="text"
:checked="text" <template v-else-if="column.dataIndex === 'operation'">
disabled <a-space>
checked-children="是" <a-button type="primary" size="small" @click="handleEdit(record)">编辑</a-button>
un-checked-children="否" <a-button type="primary" danger size="small" @click="handleDelete(record)">删除</a-button>
/> </a-space>
<a-switch </template>
#alarm
size="small"
slot-scope="text"
:checked="text"
disabled
checked-children="报警中"
un-checked-children="未报警"
/>
<a-tooltip #parent slot-scope="text" placement="topLeft" :title="text">
<span>{{ text }}</span>
</a-tooltip>
<template #operation slot-scope="text, record">
<a-space>
<a-button type="primary" size="small" @click="handleEdit(record)">编辑</a-button>
<a-button type="danger" size="small" @click="handleDelete(record)">删除</a-button>
</a-space>
</template> </template>
</a-table> </a-table>
<!-- 编辑区 --> <!-- 编辑区 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model="editMonitorVisible" :confirmLoading="confirmLoading"
v-model:open="editMonitorVisible"
width="60%" width="60%"
title="编辑监控" title="编辑监控"
@ok="handleEditMonitorOk" @ok="handleEditMonitorOk"
@ -87,54 +74,47 @@
> >
<a-form ref="editMonitorForm" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }"> <a-form ref="editMonitorForm" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }">
<a-form-item label="监控名称" name="name"> <a-form-item label="监控名称" name="name">
<a-input v-model="temp.name" :maxLength="50" placeholder="监控名称" /> <a-input v-model:value="temp.name" :maxLength="50" placeholder="监控名称" />
</a-form-item> </a-form-item>
<a-form-item label="开启状态" name="status"> <a-form-item label="开启状态" name="status">
<a-space size="large"> <a-space size="large">
<a-switch v-model="temp.status" checked-children="" un-checked-children="" /> <a-switch v-model:checked="temp.status" checked-children="" un-checked-children="" />
<div> <div>
自动重启: 自动重启:
<a-switch v-model="temp.autoRestart" checked-children="" un-checked-children="" /> <a-form-item-rest>
<a-switch v-model:checked="temp.autoRestart" checked-children="" un-checked-children="" />
</a-form-item-rest>
</div> </div>
</a-space> </a-space>
</a-form-item> </a-form-item>
<!-- <a-form-item label="自动重启" name="autoRestart"> <!-- <a-form-item label="自动重启" name="autoRestart">
</a-form-item> --> </a-form-item> -->
<!-- <a-form-item label="监控周期" name="cycle"> <!-- <a-form-item label="监控周期" name="cycle">
<a-radio-group v-model="temp.cycle" name="cycle"> <a-radio-group v-model="temp.cycle" name="cycle">
<a-radio :value="1">1 分钟</a-radio> <a-radio :value="1">1 分钟</a-radio>
<a-radio :value="5">5 分钟</a-radio> <a-radio :value="5">5 分钟</a-radio>
<a-radio :value="10">10 分钟</a-radio> <a-radio :value="10">10 分钟</a-radio>
<a-radio :value="30">30 分钟</a-radio> <a-radio :value="30">30 分钟</a-radio>
</a-radio-group> </a-radio-group>
</a-form-item> --> </a-form-item> -->
<a-form-item label="监控周期" name="execCron"> <a-form-item label="监控周期" name="execCron">
<a-auto-complete <a-auto-complete
v-model="temp.execCron" v-model:value="temp.execCron"
placeholder="如果需要定时自动执行则填写,cron 表达式.默认未开启秒级别,需要去修改配置文件中:[system.timerMatchSecond]" placeholder="如果需要定时自动执行则填写,cron 表达式.默认未开启秒级别,需要去修改配置文件中:[system.timerMatchSecond]"
option-label-prop="value" :options="CRON_DATA_SOURCE"
> >
<template #dataSource> <template #option="item"> {{ item.title }} {{ item.value }} </template>
<a-select-opt-group v-for="group in cronDataSource" :key="group.title">
<span #label>
{{ group.title }}
</span>
<a-select-option v-for="opt in group.children" :key="opt.title" :value="opt.value">
{{ opt.title }} {{ opt.value }}
</a-select-option>
</a-select-opt-group>
</template>
</a-auto-complete> </a-auto-complete>
</a-form-item> </a-form-item>
<a-form-item label="监控项目" name="projects"> <a-form-item label="监控项目" name="projects">
<a-select <a-select
option-label-prop="label" option-label-prop="label"
v-model="projectKeys" v-model:value="projectKeys"
mode="multiple" mode="multiple"
placeholder="选择要监控的项目,file 类型项目不可以监控" placeholder="选择要监控的项目,file 类型项目不可以监控"
show-search show-search
@ -151,19 +131,20 @@
:disabled="!noFileModes.includes(project.runMode)" :disabled="!noFileModes.includes(project.runMode)"
:key="project.id" :key="project.id"
> >
{{ project.nodeName }}{{ project.name }} - {{ project.runMode }} {{ project.nodeName }}{{ project.name }} -
{{ project.runMode }}
</a-select-option> </a-select-option>
</a-select-opt-group> </a-select-opt-group>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item name="notifyUser" class="jpom-notify"> <a-form-item name="notifyUser" class="jpom-notify">
<template #label> <template v-slot:label>
联系人 <a-tooltip>
<a-tooltip v-show="!temp.id"> 联系人
<template #title> <template v-slot:title>
如果这里的报警联系人无法选择说明这里面的管理员没有设置邮箱在右上角下拉菜单里面的用户资料里可以设置 如果这里的报警联系人无法选择说明这里面的管理员没有设置邮箱在右上角下拉菜单里面的用户资料里可以设置
</template> </template>
<question-circle-filled /> <QuestionCircleOutlined v-show="!temp.id" />
</a-tooltip> </a-tooltip>
</template> </template>
<a-transfer <a-transfer
@ -179,25 +160,21 @@
@change="handleChange" @change="handleChange"
> >
<template #render="item"> <template #render="item">
<span> <template v-if="item.disabled">
<span v-if="item.disabled"> <a-tooltip title="如果不可以选择则表示对应的用户没有配置邮箱">
<a-tooltip title="如果不可以选择则表示对应的用户没有配置邮箱"> <WarningTwoTone />
<a-icon type="warning" theme="twoTone" /> {{ item.name }}
{{ item.name }} </a-tooltip>
</a-tooltip> </template>
</span> <template v-else> {{ item.name }}</template>
<span>{{ item.name }}</span>
-
{{ item.name }}
</span>
</template> </template>
</a-transfer> </a-transfer>
</a-form-item> </a-form-item>
<a-form-item name="webhook"> <a-form-item name="webhook">
<template #label> <template v-slot:label>
WebHooks <a-tooltip>
<a-tooltip v-show="!temp.id"> WebHooks
<template #title> <template v-slot:title>
<ul> <ul>
<li>发生报警时候请求</li> <li>发生报警时候请求</li>
<li> <li>
@ -206,15 +183,16 @@
<li>runStatus 值为 true 表示项目当前为运行中(异常恢复),false 表示项目当前未运行(发生异常)</li> <li>runStatus 值为 true 表示项目当前为运行中(异常恢复),false 表示项目当前未运行(发生异常)</li>
</ul> </ul>
</template> </template>
<question-circle-filled /> <QuestionCircleOutlined v-show="!temp.id" />
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model="temp.webhook" placeholder="接收报警消息,非必填GET请求" /> <a-input v-model:value="temp.webhook" placeholder="接收报警消息,非必填GET请求" />
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-modal> </a-modal>
</div> </div>
</template> </template>
<script> <script>
import { deleteMonitor, editMonitor, getMonitorList } from '@/api/monitor' import { deleteMonitor, editMonitor, getMonitorList } from '@/api/monitor'
import { noFileModes } from '@/api/node-project' import { noFileModes } from '@/api/node-project'
@ -234,7 +212,7 @@ export default {
return { return {
loading: false, loading: false,
listQuery: Object.assign({}, PAGE_DEFAULT_LIST_QUERY), listQuery: Object.assign({}, PAGE_DEFAULT_LIST_QUERY),
cronDataSource: CRON_DATA_SOURCE, CRON_DATA_SOURCE,
list: [], list: [],
userList: [], userList: [],
nodeProjectList: [], nodeProjectList: [],
@ -248,30 +226,50 @@ export default {
temp: {}, temp: {},
editMonitorVisible: false, editMonitorVisible: false,
columns: [ columns: [
{ title: '名称', dataIndex: 'name', ellipsis: true, scopedSlots: { customRender: 'name' } }, {
{ title: '监控周期', dataIndex: 'execCron', ellipsis: true, scopedSlots: { customRender: 'execCron' } }, title: '名称',
{ title: '开启状态', dataIndex: 'status', ellipsis: true, scopedSlots: { customRender: 'status' }, width: 120 }, dataIndex: 'name',
ellipsis: true
},
{
title: '监控周期',
dataIndex: 'execCron',
ellipsis: true
},
{
title: '开启状态',
dataIndex: 'status',
ellipsis: true,
width: 120
},
{ {
title: '自动重启', title: '自动重启',
dataIndex: 'autoRestart', dataIndex: 'autoRestart',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'autoRestart' },
width: 120
},
{
title: '报警状态',
dataIndex: 'alarm',
ellipsis: true,
width: 120 width: 120
}, },
{ title: '报警状态', dataIndex: 'alarm', ellipsis: true, scopedSlots: { customRender: 'alarm' }, width: 120 },
{ {
title: '修改人', title: '修改人',
dataIndex: 'modifyUser', dataIndex: 'modifyUser',
ellipsis: true, ellipsis: true,
align: 'center', align: 'center',
scopedSlots: { customRender: 'modifyUser' },
width: 120 width: 120
}, },
{ {
title: '修改时间', title: '修改时间',
dataIndex: 'modifyTimeMillis', dataIndex: 'modifyTimeMillis',
sorter: true, sorter: true,
customRender: (text) => { customRender: ({ text }) => {
if (!text || text === '0') { if (!text || text === '0') {
return '' return ''
} }
@ -283,13 +281,20 @@ export default {
title: '操作', title: '操作',
dataIndex: 'operation', dataIndex: 'operation',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'operation' }, fixed: 'right',
width: 120 width: '120px'
} }
], ],
rules: { rules: {
name: [{ required: true, message: 'Please input monitor name', trigger: 'blur' }] name: [
} {
required: true,
message: '请输入监控名称',
trigger: 'blur'
}
]
},
confirmLoading: false
} }
}, },
computed: { computed: {
@ -302,24 +307,6 @@ export default {
this.loadData() this.loadData()
}, },
methods: { methods: {
//
introGuide() {
this.$store.dispatch('tryOpenGuide', {
key: 'monitor',
options: {
hidePrev: true,
steps: [
{
title: '导航助手',
element: document.querySelector('.jpom-notify'),
intro:
'如果这里的报警联系人无法选择,说明这里面的管理员没有设置邮箱,在右上角下拉菜单里面的用户资料里可以设置。'
}
]
}
})
},
// //
loadData(pointerEvent) { loadData(pointerEvent) {
this.loading = true this.loading = true
@ -336,7 +323,7 @@ export default {
loadUserList(fn) { loadUserList(fn) {
getUserListAll().then((res) => { getUserListAll().then((res) => {
if (res.code === 200) { if (res.code === 200) {
nextTick(() => { this.$nextTick(() => {
this.userList = res.data.map((element) => { this.userList = res.data.map((element) => {
let canUse = element.email || element.dingDing || element.workWx let canUse = element.email || element.dingDing || element.workWx
return { key: element.id, name: element.name, disabled: !canUse } return { key: element.id, name: element.name, disabled: !canUse }
@ -387,11 +374,6 @@ export default {
this.editMonitorVisible = true this.editMonitorVisible = true
this.loadUserList() this.loadUserList()
this.loadNodeProjectList() this.loadNodeProjectList()
nextTick(() => {
setTimeout(() => {
this.introGuide()
}, 500)
})
}, },
// //
handleEdit(record) { handleEdit(record) {
@ -425,10 +407,7 @@ export default {
}, },
handleEditMonitorOk() { handleEditMonitorOk() {
// //
this.$refs['editMonitorForm'].validate((valid) => { this.$refs['editMonitorForm'].validate().then(() => {
if (!valid) {
return false
}
let projects = this.nodeProjectList.filter((item) => { let projects = this.nodeProjectList.filter((item) => {
return this.projectKeys.includes(item.id) return this.projectKeys.includes(item.id)
}) })
@ -448,7 +427,7 @@ export default {
.map((item) => item.key) .map((item) => item.key)
if (targetKeysTemp.length <= 0 && !this.temp.webhook) { if (targetKeysTemp.length <= 0 && !this.temp.webhook) {
$notification.warn({ this.$notification.warn({
message: '请选择一位报警联系人或者填写webhook' message: '请选择一位报警联系人或者填写webhook'
}) })
return false return false
@ -461,23 +440,29 @@ export default {
projects: JSON.stringify(projects), projects: JSON.stringify(projects),
notifyUser: JSON.stringify(targetKeysTemp) notifyUser: JSON.stringify(targetKeysTemp)
} }
editMonitor(params).then((res) => { this.confirmLoading = true
if (res.code === 200) { editMonitor(params)
// .then((res) => {
$notification.success({ if (res.code === 200) {
message: res.msg //
}) this.$notification.success({
this.$refs['editMonitorForm'].resetFields() message: res.msg
this.editMonitorVisible = false })
this.loadData() this.$refs['editMonitorForm'].resetFields()
} this.editMonitorVisible = false
}) this.loadData()
}
})
.finally(() => {
this.confirmLoading = false
})
}) })
}, },
// //
handleDelete(record) { handleDelete(record) {
$confirm({ this.$confirm({
title: '系统提示', title: '系统提示',
zIndex: 1009,
content: '真的要删除监控么?', content: '真的要删除监控么?',
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',
@ -485,7 +470,7 @@ export default {
// //
deleteMonitor(record.id).then((res) => { deleteMonitor(record.id).then((res) => {
if (res.code === 200) { if (res.code === 200) {
$notification.success({ this.$notification.success({
message: res.msg message: res.msg
}) })
this.loadData() this.loadData()
@ -502,4 +487,3 @@ export default {
} }
} }
</script> </script>
<style scoped></style>

View File

@ -1,6 +1,5 @@
<template> <template>
<div class="full-content"> <div>
<!-- <div ref="filter" class="filter"></div> -->
<!-- 数据表格 --> <!-- 数据表格 -->
<a-table <a-table
:data-source="list" :data-source="list"
@ -8,60 +7,72 @@
:columns="columns" :columns="columns"
:pagination="pagination" :pagination="pagination"
bordered bordered
:rowKey="(record, index) => index" :scroll="{
x: 'max-content'
}"
@change="change" @change="change"
> >
<template #title> <template v-slot:title>
<a-space> <a-space>
<a-select v-model="listQuery.nodeId" allowClear placeholder="请选择节点" class="search-input-item"> <a-select v-model:value="listQuery.nodeId" allowClear placeholder="请选择节点" class="search-input-item">
<a-select-option v-for="(nodeName, key) in nodeMap" :key="key">{{ nodeName }}</a-select-option> <a-select-option v-for="(nodeName, key) in nodeMap" :key="key">{{ nodeName }}</a-select-option>
</a-select> </a-select>
<a-select v-model="listQuery.status" allowClear placeholder="报警状态" class="search-input-item"> <a-select v-model:value="listQuery.status" allowClear placeholder="报警状态" class="search-input-item">
<a-select-option :value="1">正常</a-select-option> <a-select-option :value="1">正常</a-select-option>
<a-select-option :value="0">异常</a-select-option> <a-select-option :value="0">异常</a-select-option>
</a-select> </a-select>
<a-select v-model="listQuery.notifyStatus" allowClear placeholder="通知状态" class="search-input-item"> <a-select v-model:value="listQuery.notifyStatus" allowClear placeholder="通知状态" class="search-input-item">
<a-select-option :value="1">成功</a-select-option> <a-select-option :value="1">成功</a-select-option>
<a-select-option :value="0">失败</a-select-option> <a-select-option :value="0">失败</a-select-option>
</a-select> </a-select>
<a-range-picker <a-range-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" @change="onchangeTime" />
class="search-input-item"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
@change="onchangeTime"
/>
<a-tooltip title="按住 Ctr 或者 Alt/Option 键点击按钮快速回到第一页"> <a-tooltip title="按住 Ctr 或者 Alt/Option 键点击按钮快速回到第一页">
<a-button :loading="loading" type="primary" @click="loadData">搜索</a-button> <a-button :loading="loading" type="primary" @click="loadData">搜索</a-button>
</a-tooltip> </a-tooltip>
</a-space> </a-space>
</template> </template>
<a-tooltip #nodeId slot-scope="text" placement="topLeft" :title="text"> <template #bodyCell="{ column, text, record }">
<span>{{ nodeMap[text] }}</span> <template v-if="column.dataIndex === 'nodeId'">
</a-tooltip> <a-tooltip placement="topLeft" :title="text">
<a-tooltip #tooltip slot-scope="text" placement="topLeft" :title="text"> <span>{{ nodeMap[text] }}</span>
<span>{{ text }}</span> </a-tooltip>
</a-tooltip> </template>
<span #status slot-scope="text">{{ text ? '正常' : '异常' }}</span> <template v-else-if="column.tooltip">
<template #notifyStyle slot-scope="text"> <a-tooltip placement="topLeft" :title="text">
{{ notifyStyle[text] || '未知' }} <span>{{ text }}</span>
</template> </a-tooltip>
<span #notifyStatus slot-scope="text">{{ text ? '成功' : '失败' }}</span> </template>
<template #operation slot-scope="text, record"> <template v-else-if="column.dataIndex === 'status'">
<a-button size="small" type="primary" @click="handleDetail(record)">详情</a-button> <span>{{ text ? '正常' : '异常' }}</span>
</template>
<template v-else-if="column.dataIndex === 'notifyStyle'">
{{ notifyStyle[text] || '未知' }}
</template>
<template v-else-if="column.dataIndex === 'notifyStatus'">
<span>{{ text ? '成功' : '失败' }}</span>
</template>
<template v-else-if="column.dataIndex === 'operation'">
<a-button size="small" type="primary" @click="handleDetail(record)">详情</a-button>
</template>
</template> </template>
</a-table> </a-table>
<!-- 详情区 --> <!-- 详情区 -->
<a-modal destroyOnClose v-model:visible="detailVisible" width="600px" title="详情信息" :footer="null"> <a-modal destroyOnClose v-model:open="detailVisible" width="600px" title="详情信息" :footer="null">
<a-list item-layout="horizontal" :data-source="detailData"> <a-list item-layout="horizontal" :data-source="detailData">
<a-list-item #renderItem slot-scope="item"> <template #renderItem="{ item }">
<a-list-item-meta :description="item.description"> <a-list-item>
<h4 #title>{{ item.title }}</h4> <a-list-item-meta :description="item.description">
</a-list-item-meta> <template v-slot:title>
</a-list-item> <h4>{{ item.title }}</h4>
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list> </a-list>
</a-modal> </a-modal>
</div> </div>
</template> </template>
<script> <script>
import { getMonitorLogList, notifyStyle } from '@/api/monitor' import { getMonitorLogList, notifyStyle } from '@/api/monitor'
import { getNodeListAll } from '@/api/node' import { getNodeListAll } from '@/api/node'
@ -79,52 +90,59 @@ export default {
notifyStyle, notifyStyle,
detailData: [], detailData: [],
columns: [ columns: [
{ title: '报警标题', dataIndex: 'title', ellipsis: true, scopedSlots: { customRender: 'tooltip' } }, {
{ title: '节点名称', dataIndex: 'nodeId', width: 100, ellipsis: true, scopedSlots: { customRender: 'nodeId' } }, title: '报警标题',
dataIndex: 'title',
ellipsis: true,
tooltip: true
},
{
title: '节点名称',
dataIndex: 'nodeId',
width: 100,
ellipsis: true
},
{ {
title: '项目 ID', title: '项目 ID',
dataIndex: 'projectId', dataIndex: 'projectId',
width: 100, width: 100,
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
{ {
title: '报警状态', title: '报警状态',
dataIndex: 'status', dataIndex: 'status',
width: 100, width: 100,
align: 'center', align: 'center',
ellipsis: true, ellipsis: true
scopedSlots: { customRender: 'status' }
}, },
{ {
title: '报警方式', title: '报警方式',
dataIndex: 'notifyStyle', dataIndex: 'notifyStyle',
width: 100, width: 100,
align: 'center', align: 'center',
ellipsis: true, ellipsis: true
scopedSlots: { customRender: 'notifyStyle' }
}, },
{ {
title: '报警时间', title: '报警时间',
dataIndex: 'createTime', dataIndex: 'createTime',
customRender: (text) => { customRender: ({ text }) => {
return parseTime(text) return parseTime(text)
}, },
width: 170 width: '170px'
}, },
{ {
title: '通知状态', title: '通知状态',
dataIndex: 'notifyStatus', dataIndex: 'notifyStatus',
width: 100, width: 100,
ellipsis: true, ellipsis: true
scopedSlots: { customRender: 'notifyStatus' }
}, },
{ {
title: '操作', title: '操作',
dataIndex: 'operation', dataIndex: 'operation',
align: 'center', align: 'center',
scopedSlots: { customRender: 'operation' }, fixed: 'right',
width: 80 width: '80px'
} }
] ]
} }
@ -186,12 +204,17 @@ export default {
this.temp = Object.assign({}, record) this.temp = Object.assign({}, record)
this.detailData.push({ title: '标题', description: this.temp.title }) this.detailData.push({ title: '标题', description: this.temp.title })
this.detailData.push({ title: '内容', description: this.temp.content }) this.detailData.push({ title: '内容', description: this.temp.content })
this.detailData.push({ title: '通知对象', description: this.temp.notifyObject }) this.detailData.push({
title: '通知对象',
description: this.temp.notifyObject
})
if (!this.temp.notifyStatus) { if (!this.temp.notifyStatus) {
this.detailData.push({ title: '通知异常', description: this.temp.notifyError }) this.detailData.push({
title: '通知异常',
description: this.temp.notifyError
})
} }
} }
} }
} }
</script> </script>
<style scoped></style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="full-content"> <div>
<!-- 数据表格 --> <!-- 数据表格 -->
<a-table <a-table
size="middle" size="middle"
@ -8,17 +8,19 @@
:pagination="pagination" :pagination="pagination"
@change="changePage" @change="changePage"
bordered bordered
:rowKey="(record, index) => index" :scroll="{
x: 'max-content'
}"
> >
<template #title> <template v-slot:title>
<a-space> <a-space>
<a-input <a-input
v-model="listQuery['%name%']" v-model:value="listQuery['%name%']"
@pressEnter="loadData" @pressEnter="loadData"
placeholder="监控名称" placeholder="监控名称"
class="search-input-item" class="search-input-item"
/> />
<a-select v-model="listQuery.status" allowClear placeholder="开启状态" class="search-input-item"> <a-select v-model:value="listQuery.status" allowClear placeholder="开启状态" class="search-input-item">
<a-select-option :value="1">开启</a-select-option> <a-select-option :value="1">开启</a-select-option>
<a-select-option :value="0">关闭</a-select-option> <a-select-option :value="0">关闭</a-select-option>
</a-select> </a-select>
@ -28,33 +30,31 @@
<a-button type="primary" @click="handleAdd">新增</a-button> <a-button type="primary" @click="handleAdd">新增</a-button>
</a-space> </a-space>
</template> </template>
<a-tooltip #name slot-scope="text" placement="topLeft" :title="text"> <template #bodyCell="{ column, text, record }">
<span>{{ text }}</span> <template v-if="column.dataIndex === 'name'">
</a-tooltip> <a-tooltip placement="topLeft" :title="text">
<a-switch <span>{{ text }}</span>
#status </a-tooltip>
size="small" </template>
slot-scope="text" <template v-else-if="column.dataIndex === 'status'">
:checked="text" <a-switch size="small" :checked="text" checked-children="开启" un-checked-children="关闭" />
checked-children="开启" </template>
un-checked-children="关闭" <!-- <a-switch slot="autoRestart" slot-scope="text" :checked="text" checked-children="" un-checked-children="" /> -->
/> <!-- <a-switch slot="alarm" slot-scope="text" :checked="text" disabled checked-children="报警中" un-checked-children="未报警" /> -->
<!-- <a-switch #autoRestart slot-scope="text" :checked="text" checked-children="" un-checked-children="" /> -->
<!-- <a-switch #alarm slot-scope="text" :checked="text" disabled checked-children="报警中" un-checked-children="未报警" /> --> <template v-else-if="column.dataIndex === 'operation'">
<a-tooltip #parent slot-scope="text" placement="topLeft" :title="text"> <a-space>
<span>{{ text }}</span> <a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
</a-tooltip> <a-button size="small" type="primary" danger @click="handleDelete(record)">删除</a-button>
<template #operation slot-scope="text, record"> </a-space>
<a-space> </template>
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
<a-button size="small" type="danger" @click="handleDelete(record)">删除</a-button>
</a-space>
</template> </template>
</a-table> </a-table>
<!-- 编辑区 --> <!-- 编辑区 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model="editOperateMonitorVisible" :confirmLoading="confirmLoading"
v-model:open="editOperateMonitorVisible"
width="50vw" width="50vw"
title="编辑监控" title="编辑监控"
@ok="handleEditOperateMonitorOk" @ok="handleEditOperateMonitorOk"
@ -62,10 +62,10 @@
> >
<a-form ref="editMonitorForm" :rules="rules" :model="temp" :label-col="{ span: 5 }" :wrapper-col="{ span: 17 }"> <a-form ref="editMonitorForm" :rules="rules" :model="temp" :label-col="{ span: 5 }" :wrapper-col="{ span: 17 }">
<a-form-item label="监控名称" name="name"> <a-form-item label="监控名称" name="name">
<a-input v-model="temp.name" :maxLength="50" placeholder="监控名称" /> <a-input v-model:value="temp.name" :maxLength="50" placeholder="监控名称" />
</a-form-item> </a-form-item>
<a-form-item label="开启状态" name="status"> <a-form-item label="开启状态" name="status">
<a-switch v-model="temp.start" checked-children="" un-checked-children="" /> <a-switch v-model:checked="temp.start" checked-children="" un-checked-children="" />
</a-form-item> </a-form-item>
<a-form-item label="监控用户" name="monitorUser"> <a-form-item label="监控用户" name="monitorUser">
<a-transfer <a-transfer
@ -101,13 +101,13 @@
/> />
</a-form-item> </a-form-item>
<a-form-item name="notifyUser" class="jpom-monitor-notify"> <a-form-item name="notifyUser" class="jpom-monitor-notify">
<template #label> <template v-slot:label>
报警联系人 <a-tooltip>
<a-tooltip v-show="!temp.id"> 报警联系人
<template #title> <template v-slot:title>
如果这里的报警联系人无法选择说明这里面的管理员没有设置邮箱在右上角下拉菜单里面的用户资料里可以设置 如果这里的报警联系人无法选择说明这里面的管理员没有设置邮箱在右上角下拉菜单里面的用户资料里可以设置
</template> </template>
<question-circle-filled /> <QuestionCircleOutlined v-show="!temp.id" />
</a-tooltip> </a-tooltip>
</template> </template>
<a-transfer <a-transfer
@ -124,6 +124,7 @@
</a-modal> </a-modal>
</div> </div>
</template> </template>
<script> <script>
import { import {
deleteMonitorOperate, deleteMonitorOperate,
@ -152,14 +153,23 @@ export default {
methodFeatureKeys: [], methodFeatureKeys: [],
editOperateMonitorVisible: false, editOperateMonitorVisible: false,
columns: [ columns: [
{ title: '名称', dataIndex: 'name', scopedSlots: { customRender: 'name' } }, {
{ title: '开启状态', dataIndex: 'status', scopedSlots: { customRender: 'status' } }, title: '名称',
{ title: '修改人', dataIndex: 'modifyUser', scopedSlots: { customRender: 'modifyUser' } }, dataIndex: 'name'
},
{
title: '开启状态',
dataIndex: 'status'
},
{
title: '修改人',
dataIndex: 'modifyUser'
},
{ {
title: '修改时间', title: '修改时间',
dataIndex: 'modifyTimeMillis', dataIndex: 'modifyTimeMillis',
sorter: true, sorter: true,
customRender: (text) => { customRender: ({ text }) => {
if (!text || text === '0') { if (!text || text === '0') {
return '' return ''
} }
@ -171,13 +181,20 @@ export default {
title: '操作', title: '操作',
dataIndex: 'operation', dataIndex: 'operation',
align: 'center', align: 'center',
scopedSlots: { customRender: 'operation' }, fixed: 'right',
width: 120 width: '120px'
} }
], ],
rules: { rules: {
name: [{ required: true, message: 'Please input monitor name', trigger: 'blur' }] name: [
} {
required: true,
message: '请输入监控名称',
trigger: 'blur'
}
]
},
confirmLoading: false
} }
}, },
computed: { computed: {
@ -191,23 +208,6 @@ export default {
this.loadOptTypeData() this.loadOptTypeData()
}, },
methods: { methods: {
//
introGuide() {
this.$store.dispatch('tryOpenGuide', {
key: 'operate-log',
options: {
hidePrev: true,
steps: [
{
title: '导航助手',
element: document.querySelector('.jpom-monitor-notify'),
intro: '这里面的报警联系人如果无法选择,您需要设置管理员的邮箱地址。'
}
]
}
})
},
// //
loadData(pointerEvent) { loadData(pointerEvent) {
this.loading = true this.loading = true
@ -263,11 +263,6 @@ export default {
this.monitorUserKeys = [] this.monitorUserKeys = []
this.loadUserList() this.loadUserList()
this.editOperateMonitorVisible = true this.editOperateMonitorVisible = true
nextTick(() => {
setTimeout(() => {
this.introGuide()
}, 500)
})
}, },
// //
handleEdit(record) { handleEdit(record) {
@ -306,30 +301,27 @@ export default {
// //
handleEditOperateMonitorOk() { handleEditOperateMonitorOk() {
// //
this.$refs['editMonitorForm'].validate((valid) => { this.$refs['editMonitorForm'].validate().then(() => {
if (!valid) {
return false
}
if (this.monitorUserKeys.length === 0) { if (this.monitorUserKeys.length === 0) {
$notification.error({ this.$notification.error({
message: '请选择监控用户' message: '请选择监控用户'
}) })
return false return false
} }
if (this.methodFeatureKeys.length === 0) { if (this.methodFeatureKeys.length === 0) {
$notification.error({ this.$notification.error({
message: '请选择监控操作' message: '请选择监控操作'
}) })
return false return false
} }
if (this.classFeatureKeys.length === 0) { if (this.classFeatureKeys.length === 0) {
$notification.error({ this.$notification.error({
message: '请选择监控的功能' message: '请选择监控的功能'
}) })
return false return false
} }
if (this.notifyUserKeys.length === 0) { if (this.notifyUserKeys.length === 0) {
$notification.error({ this.$notification.error({
message: '请选择报警联系人' message: '请选择报警联系人'
}) })
return false return false
@ -340,23 +332,29 @@ export default {
this.temp.monitorFeature = JSON.stringify(this.classFeatureKeys) this.temp.monitorFeature = JSON.stringify(this.classFeatureKeys)
this.temp.notifyUser = JSON.stringify(this.notifyUserKeys) this.temp.notifyUser = JSON.stringify(this.notifyUserKeys)
this.temp.start ? (this.temp.status = 'on') : (this.temp.status = 'no') this.temp.start ? (this.temp.status = 'on') : (this.temp.status = 'no')
editMonitorOperate(this.temp).then((res) => { this.confirmLoading = false
if (res.code === 200) { editMonitorOperate(this.temp)
// .then((res) => {
$notification.success({ if (res.code === 200) {
message: res.msg //
}) this.$notification.success({
this.$refs['editMonitorForm'].resetFields() message: res.msg
this.editOperateMonitorVisible = false })
this.loadData() this.$refs['editMonitorForm'].resetFields()
} this.editOperateMonitorVisible = false
}) this.loadData()
}
})
.finally(() => {
this.confirmLoading = false
})
}) })
}, },
// //
handleDelete(record) { handleDelete(record) {
$confirm({ this.$confirm({
title: '系统提示', title: '系统提示',
zIndex: 1009,
content: '真的要删除操作监控么?', content: '真的要删除操作监控么?',
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',
@ -364,7 +362,7 @@ export default {
// //
deleteMonitorOperate(record.id).then((res) => { deleteMonitorOperate(record.id).then((res) => {
if (res.code === 200) { if (res.code === 200) {
$notification.success({ this.$notification.success({
message: res.msg message: res.msg
}) })
this.loadData() this.loadData()
@ -381,4 +379,3 @@ export default {
} }
} }
</script> </script>
<style scoped></style>

View File

@ -1,12 +1,14 @@
<template> <template>
<div class="full-content"> <div>
<workspaceEnv ref="workspaceEnv" :workspaceId="this.getWorkspaceId()" :global="true" /> <workspaceEnv ref="workspaceEnv" :workspaceId="this.getWorkspaceId()" :global="true" />
</div> </div>
</template> </template>
<script> <script>
import workspaceEnv from '@/pages/system/workspace-env.vue' import workspaceEnv from '@/pages/system/workspace-env.vue'
import { mapGetters } from 'vuex' import { mapState } from 'pinia'
import { useAppStore } from '@/stores/app'
export default { export default {
components: { components: {
@ -15,7 +17,7 @@ export default {
data() { data() {
return {} return {}
}, },
computed: { ...mapGetters(['getWorkspaceId']) }, computed: { ...mapState(useAppStore, ['getWorkspaceId']) },
created() {}, created() {},
methods: {} methods: {}
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="full-content"> <div>
<!-- 数据表格 --> <!-- 数据表格 -->
<a-table <a-table
:data-source="list" :data-source="list"
@ -10,6 +10,9 @@
bordered bordered
rowKey="id" rowKey="id"
:row-selection="rowSelection" :row-selection="rowSelection"
:scroll="{
x: 'max-content'
}"
> >
<template v-slot:title> <template v-slot:title>
<a-space> <a-space>
@ -66,70 +69,72 @@
</a-tooltip> </a-tooltip>
</a-space> </a-space>
</template> </template>
<template #bodyCell="{ column, text, record }">
<template v-if="column.dataIndex === 'nodeId'">
<a-tooltip placement="topLeft" :title="text">
<span>{{ nodeMap[text] }}</span>
</a-tooltip>
</template>
<template v-else-if="column.tooltip">
<a-tooltip placement="topLeft" :title="text">
<span>{{ text }}</span>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'workspaceId'">
<a-tag v-if="text === 'GLOBAL'">全局</a-tag>
<a-tag v-else>工作空间</a-tag>
</template>
<template v-else-if="column.dataIndex === 'operation'">
<a-space>
<template v-if="mode === 'manage'">
<a-button size="small" type="primary" @click="handleExec(record)">执行</a-button>
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
<a-button size="small" type="primary" @click="handleLog(record)">日志</a-button>
<a-dropdown>
<a @click="(e) => e.preventDefault()">
更多
<DownOutlined />
</a>
<template v-slot:overlay>
<a-menu>
<a-menu-item>
<a-button size="small" type="primary" @click="handleTrigger(record)">触发器</a-button>
</a-menu-item>
<template v-slot:nodeId="text"> <a-menu-item>
<a-tooltip placement="topLeft" :title="text"> <a-button size="small" type="primary" danger @click="handleDelete(record)">删除</a-button>
<span>{{ nodeMap[text] }}</span> </a-menu-item>
</a-tooltip> <a-menu-item>
</template> <a-button
<template v-slot:tooltip="text"> size="small"
<a-tooltip placement="topLeft" :title="text"> type="primary"
<span>{{ text }}</span> danger
</a-tooltip> :disabled="!record.nodeIds"
</template> @click="handleUnbind(record)"
<template v-slot:global="text"> >解绑</a-button
<a-tag v-if="text === 'GLOBAL'">全局</a-tag> >
<a-tag v-else>工作空间</a-tag> </a-menu-item>
</template> </a-menu>
<template v-slot:operation="text, record"> </template>
<a-space> </a-dropdown>
<template v-if="mode === 'manage'"> </template>
<a-button size="small" type="primary" @click="handleExec(record)">执行</a-button> <template v-else>
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button> <a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
<a-button size="small" type="primary" @click="handleLog(record)">日志</a-button> </template>
<a-dropdown> </a-space>
<a class="ant-dropdown-link" @click="(e) => e.preventDefault()"> </template>
更多
<a-icon type="down" />
</a>
<template v-slot:overlay>
<a-menu>
<a-menu-item>
<a-button size="small" type="primary" @click="handleTrigger(record)">触发器</a-button>
</a-menu-item>
<a-menu-item>
<a-button size="small" type="primary" danger @click="handleDelete(record)">删除</a-button>
</a-menu-item>
<a-menu-item>
<a-button
size="small"
type="primary"
danger
:disabled="!record.nodeIds"
@click="handleUnbind(record)"
>解绑</a-button
>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template>
<template v-else>
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
</template>
</a-space>
</template> </template>
</a-table> </a-table>
<!-- 编辑区 --> <!-- 编辑区 -->
<a-modal <a-modal
:zIndex="1009"
destroyOnClose destroyOnClose
v-model:value="editScriptVisible" :zIndex="1009"
v-model:open="editScriptVisible"
title="编辑 Script" title="编辑 Script"
@ok="handleEditScriptOk" @ok="handleEditScriptOk"
:maskClosable="false" :maskClosable="false"
width="80vw" width="80vw"
:confirmLoading="confirmLoading"
> >
<a-form ref="editScriptForm" :rules="rules" :model="temp" :label-col="{ span: 3 }" :wrapper-col="{ span: 19 }"> <a-form ref="editScriptForm" :rules="rules" :model="temp" :label-col="{ span: 3 }" :wrapper-col="{ span: 19 }">
<a-form-item v-if="temp.id" label="ScriptId" name="id"> <a-form-item v-if="temp.id" label="ScriptId" name="id">
@ -139,41 +144,41 @@
<a-input :maxLength="50" v-model:value="temp.name" placeholder="名称" /> <a-input :maxLength="50" v-model:value="temp.name" placeholder="名称" />
</a-form-item> </a-form-item>
<a-form-item label="Script 内容" name="context"> <a-form-item label="Script 内容" name="context">
<div style="height: 40vh; overflow-y: scroll"> <a-form-item-rest>
<code-editor <div style="height: 40vh; overflow-y: scroll">
v-model:value="temp.context" <code-editor v-model:content="temp.context" :options="{ mode: 'shell', tabSize: 2, theme: 'abcdef' }">
:options="{ mode: 'shell', tabSize: 2, theme: 'abcdef' }" </code-editor>
></code-editor> </div>
</div> </a-form-item-rest>
</a-form-item> </a-form-item>
<!-- <a-form-item label="默认参数" name="defArgs"> <!-- <a-form-item label="默认参数" name="defArgs">
<a-input v-model="temp.defArgs" placeholder="默认参数" /> <a-input v-model="temp.defArgs" placeholder="默认参数" />
</a-form-item> --> </a-form-item> -->
<a-form-item label="默认参数"> <a-form-item label="默认参数">
<div v-for="(item, index) in commandParams" :key="item.key"> <a-space direction="vertical" style="width: 100%">
<a-row type="flex" justify="center" align="middle"> <a-row v-for="(item, index) in commandParams" :key="item.key">
<a-col :span="22"> <a-col :span="22">
<a-input <a-space direction="vertical" style="width: 100%">
:addon-before="`参数${index + 1}描述`" <a-input
v-model:value="item.desc" :addon-before="`参数${index + 1}描述`"
placeholder="参数描述,参数描述没有实际作用,仅是用于提示参数的含义" v-model:value="item.desc"
/> placeholder="参数描述,参数描述没有实际作用,仅是用于提示参数的含义" />
<a-input <a-input
:addon-before="`参数${index + 1}值`" :addon-before="`参数${index + 1}值`"
v-model:value="item.value" v-model:value="item.value"
placeholder="参数值,添加默认参数后在手动执行脚本时需要填写参数值" placeholder="参数值,添加默认参数后在手动执行脚本时需要填写参数值"
/> /></a-space>
</a-col> </a-col>
<a-col :span="2"> <a-col :span="2">
<a-row type="flex" justify="center" align="middle"> <a-row type="flex" justify="center" align="middle">
<a-col> <a-col>
<a-icon @click="() => commandParams.splice(index, 1)" type="minus-circle" style="color: #ff0000" /> <MinusCircleOutlined @click="() => commandParams.splice(index, 1)" style="color: #ff0000" />
</a-col> </a-col>
</a-row> </a-row>
</a-col> </a-col>
</a-row> </a-row>
<a-divider style="margin: 5px 0" /> <a-divider style="margin: 5px 0" />
</div> </a-space>
<a-button type="primary" @click="() => commandParams.push({})">添加参数</a-button> <a-button type="primary" @click="() => commandParams.push({})">添加参数</a-button>
</a-form-item> </a-form-item>
@ -181,27 +186,15 @@
<a-auto-complete <a-auto-complete
v-model:value="temp.autoExecCron" v-model:value="temp.autoExecCron"
placeholder="如果需要定时自动执行则填写,cron 表达式.默认未开启秒级别,需要去修改配置文件中:[system.timerMatchSecond]" placeholder="如果需要定时自动执行则填写,cron 表达式.默认未开启秒级别,需要去修改配置文件中:[system.timerMatchSecond]"
option-label-prop="value" :options="CRON_DATA_SOURCE"
> >
<template v-slot:dataSource> <template #option="item"> {{ item.title }} {{ item.value }} </template>
<a-select-opt-group v-for="group in cronDataSource" :key="group.title">
<template v-slot:label>
<span>
{{ group.title }}
</span>
</template>
<a-select-option v-for="opt in group.children" :key="opt.title" :value="opt.value">
{{ opt.title }} {{ opt.value }}
</a-select-option>
</a-select-opt-group>
</template>
</a-auto-complete> </a-auto-complete>
</a-form-item> </a-form-item>
<a-form-item label="描述" name="description"> <a-form-item label="描述" name="description">
<a-input <a-textarea
v-model:value="temp.description" v-model:value="temp.description"
:maxLength="200" :maxLength="200"
type="textarea"
:rows="3" :rows="3"
style="resize: none" style="resize: none"
placeholder="详细描述" placeholder="详细描述"
@ -246,7 +239,7 @@
:title="drawerTitle" :title="drawerTitle"
placement="right" placement="right"
width="85vw" width="85vw"
:visible="drawerConsoleVisible" :open="drawerConsoleVisible"
@close="onConsoleClose" @close="onConsoleClose"
> >
<script-console v-if="drawerConsoleVisible" :defArgs="temp.defArgs" :id="temp.id" /> <script-console v-if="drawerConsoleVisible" :defArgs="temp.defArgs" :id="temp.id" />
@ -254,12 +247,13 @@
<!-- 同步到其他工作空间 --> <!-- 同步到其他工作空间 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model:value="syncToWorkspaceVisible" :confirmLoading="confirmLoading"
v-model:open="syncToWorkspaceVisible"
title="同步到其他工作空间" title="同步到其他工作空间"
@ok="handleSyncToWorkspace" @ok="handleSyncToWorkspace"
:maskClosable="false" :maskClosable="false"
> >
<a-alert message="温馨提示" type="warning"> <a-alert message="温馨提示" type="warning" show-icon>
<template v-slot:description> <template v-slot:description>
<ul> <ul>
<li>同步机制采用<b>脚本名称</b>确定是同一个脚本</li> <li>同步机制采用<b>脚本名称</b>确定是同一个脚本</li>
@ -287,7 +281,7 @@
<!-- 触发器 --> <!-- 触发器 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model:value="triggerVisible" v-model:open="triggerVisible"
title="触发器" title="触发器"
width="50%" width="50%"
:footer="null" :footer="null"
@ -295,13 +289,13 @@
> >
<a-form ref="editTriggerForm" :rules="rules" :model="temp" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }"> <a-form ref="editTriggerForm" :rules="rules" :model="temp" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
<a-tabs default-active-key="1"> <a-tabs default-active-key="1">
<template v-slot:tabBarExtraContent> <template v-slot:rightExtra>
<a-tooltip title="重置触发器 token 信息,重置后之前的触发器 token 将失效"> <a-tooltip title="重置触发器 token 信息,重置后之前的触发器 token 将失效">
<a-button type="primary" size="small" @click="resetTrigger">重置</a-button> <a-button type="primary" size="small" @click="resetTrigger">重置</a-button>
</a-tooltip> </a-tooltip>
</template> </template>
<a-tab-pane key="1" tab="执行"> <a-tab-pane key="1" tab="执行">
<a-space style="display: block" direction="vertical" align="baseline"> <a-space direction="vertical" style="width: 100%">
<a-alert message="温馨提示" type="warning"> <a-alert message="温馨提示" type="warning">
<template v-slot:description> <template v-slot:description>
<ul> <ul>
@ -313,52 +307,18 @@
</ul> </ul>
</template> </template>
</a-alert> </a-alert>
<a-alert <a-alert type="info" :message="`单个触发器地址(点击可以复制)`">
v-clipboard:copy="temp.triggerUrl"
v-clipboard:success="
() => {
tempVue.prototype.$notification.success({
message: '复制成功'
})
}
"
v-clipboard:error="
() => {
tempVue.prototype.$notification.error({
message: '复制失败'
})
}
"
type="info"
:message="`单个触发器地址(点击可以复制)`"
>
<template v-slot:description> <template v-slot:description>
<a-tag>GET</a-tag> <span>{{ temp.triggerUrl }} </span> <a-typography-paragraph :copyable="{ text: temp.triggerUrl }">
<a-icon type="copy" /> <a-tag>GET</a-tag> <span>{{ temp.triggerUrl }} </span>
</a-typography-paragraph>
</template> </template>
</a-alert> </a-alert>
<a-alert <a-alert type="info" :message="`批量触发器地址(点击可以复制)`">
v-clipboard:copy="temp.batchTriggerUrl"
v-clipboard:success="
() => {
tempVue.prototype.$notification.success({
message: '复制成功'
})
}
"
v-clipboard:error="
() => {
tempVue.prototype.$notification.error({
message: '复制失败'
})
}
"
type="info"
:message="`批量触发器地址(点击可以复制)`"
>
<template v-slot:description> <template v-slot:description>
<a-tag>POST</a-tag> <span>{{ temp.batchTriggerUrl }} </span> <a-typography-paragraph :copyable="{ text: temp.batchTriggerUrl }">
<a-icon type="copy" /> <a-tag>POST</a-tag> <span>{{ temp.batchTriggerUrl }} </span>
</a-typography-paragraph>
</template> </template>
</a-alert> </a-alert>
</a-space> </a-space>
@ -370,8 +330,8 @@
<a-drawer <a-drawer
destroyOnClose destroyOnClose
title="脚本执行历史" title="脚本执行历史"
width="50vw" width="70vw"
:visible="drawerLogVisible" :open="drawerLogVisible"
@close=" @close="
() => { () => {
this.drawerLogVisible = false this.drawerLogVisible = false
@ -380,7 +340,7 @@
> >
<script-log v-if="drawerLogVisible" :scriptId="temp.id" /> <script-log v-if="drawerLogVisible" :scriptId="temp.id" />
</a-drawer> </a-drawer>
<div style="padding-top: 50px" v-if="mode === 'choose'"> <!-- <div style="padding-top: 50px" v-if="mode === 'choose'">
<div <div
:style="{ :style="{
position: 'absolute', position: 'absolute',
@ -407,7 +367,7 @@
<a-button type="primary" @click="handerConfirm"> 确定 </a-button> <a-button type="primary" @click="handerConfirm"> 确定 </a-button>
</a-space> </a-space>
</div> </div>
</div> </div> -->
</div> </div>
</template> </template>
@ -425,10 +385,12 @@ import codeEditor from '@/components/codeEditor'
import { getNodeListAll } from '@/api/node' import { getNodeListAll } from '@/api/node'
import ScriptConsole from '@/pages/script/script-console' import ScriptConsole from '@/pages/script/script-console'
import { CHANGE_PAGE, COMPUTED_PAGINATION, CRON_DATA_SOURCE, PAGE_DEFAULT_LIST_QUERY, parseTime } from '@/utils/const' import { CHANGE_PAGE, COMPUTED_PAGINATION, CRON_DATA_SOURCE, PAGE_DEFAULT_LIST_QUERY, parseTime } from '@/utils/const'
import { mapGetters } from 'vuex'
import { getWorkSpaceListAll } from '@/api/workspace' import { getWorkSpaceListAll } from '@/api/workspace'
import * as Vue from 'vue'
import ScriptLog from '@/pages/script/script-log' import ScriptLog from '@/pages/script/script-log'
import { mapState } from 'pinia'
import { useAppStore } from '@/stores/app'
export default { export default {
components: { components: {
ScriptConsole, ScriptConsole,
@ -456,7 +418,7 @@ export default {
// choose: this.choose, // choose: this.choose,
loading: false, loading: false,
listQuery: Object.assign({}, PAGE_DEFAULT_LIST_QUERY), listQuery: Object.assign({}, PAGE_DEFAULT_LIST_QUERY),
cronDataSource: CRON_DATA_SOURCE, CRON_DATA_SOURCE,
list: [], list: [],
temp: {}, temp: {},
nodeList: [], nodeList: [],
@ -469,8 +431,8 @@ export default {
dataIndex: 'id', dataIndex: 'id',
ellipsis: true, ellipsis: true,
sorter: true, sorter: true,
width: 150, width: 50,
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
{ {
title: '名称', title: '名称',
@ -478,22 +440,22 @@ export default {
ellipsis: true, ellipsis: true,
sorter: true, sorter: true,
width: 150, width: 150,
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
{ {
title: '共享', title: '共享',
dataIndex: 'workspaceId', dataIndex: 'workspaceId',
sorter: true, sorter: true,
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'global' },
width: '90px' width: '90px'
}, },
{ {
title: '描述', title: '描述',
dataIndex: 'description', dataIndex: 'description',
ellipsis: true, ellipsis: true,
width: 300, width: 100,
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
{ {
title: '定时执行', title: '定时执行',
@ -501,7 +463,7 @@ export default {
ellipsis: true, ellipsis: true,
sorter: true, sorter: true,
width: '100px', width: '100px',
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
{ {
title: '修改时间', title: '修改时间',
@ -509,7 +471,7 @@ export default {
sorter: true, sorter: true,
width: '170px', width: '170px',
ellipsis: true, ellipsis: true,
customRender: (text) => parseTime(text) customRender: ({ text }) => parseTime(text)
}, },
{ {
title: '创建时间', title: '创建时间',
@ -517,20 +479,20 @@ export default {
sorter: true, sorter: true,
width: '170px', width: '170px',
ellipsis: true, ellipsis: true,
customRender: (text) => parseTime(text) customRender: ({ text }) => parseTime(text)
}, },
{ {
title: '创建人', title: '创建人',
dataIndex: 'createUser', dataIndex: 'createUser',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'tooltip' }, tooltip: true,
width: '120px' width: '120px'
}, },
{ {
title: '修改人', title: '修改人',
dataIndex: 'modifyUser', dataIndex: 'modifyUser',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'tooltip' }, tooltip: true,
width: '120px' width: '120px'
}, },
{ {
@ -538,14 +500,14 @@ export default {
dataIndex: 'lastRunUser', dataIndex: 'lastRunUser',
ellipsis: true, ellipsis: true,
width: '120px', width: '120px',
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
this.mode === 'manage' this.mode === 'manage'
? { ? {
title: '操作', title: '操作',
dataIndex: 'operation', dataIndex: 'operation',
align: 'center', align: 'center',
scopedSlots: { customRender: 'operation' },
fixed: 'right', fixed: 'right',
width: '240px' width: '240px'
} }
@ -553,7 +515,7 @@ export default {
title: '操作', title: '操作',
dataIndex: 'operation', dataIndex: 'operation',
align: 'center', align: 'center',
scopedSlots: { customRender: 'operation' },
fixed: 'right', fixed: 'right',
width: '100px' width: '100px'
} }
@ -567,11 +529,12 @@ export default {
workspaceList: [], workspaceList: [],
triggerVisible: false, triggerVisible: false,
commandParams: [], commandParams: [],
drawerLogVisible: false drawerLogVisible: false,
confirmLoading: false
} }
}, },
computed: { computed: {
...mapGetters(['getWorkspaceId']), ...mapState(useAppStore, ['getWorkspaceId']),
pagination() { pagination() {
return COMPUTED_PAGINATION(this.listQuery) return COMPUTED_PAGINATION(this.listQuery)
}, },
@ -662,10 +625,7 @@ export default {
// Script // Script
handleEditScriptOk() { handleEditScriptOk() {
// //
this.$refs['editScriptForm'].validate((valid) => { this.$refs['editScriptForm'].validate().then(() => {
if (!valid) {
return false
}
if (this.commandParams && this.commandParams.length > 0) { if (this.commandParams && this.commandParams.length > 0) {
for (let i = 0; i < this.commandParams.length; i++) { for (let i = 0; i < this.commandParams.length; i++) {
if (!this.commandParams[i].desc) { if (!this.commandParams[i].desc) {
@ -682,18 +642,23 @@ export default {
// //
this.temp.nodeIds = this.temp?.chooseNode?.join(',') this.temp.nodeIds = this.temp?.chooseNode?.join(',')
delete this.temp.nodeList delete this.temp.nodeList
editScript(this.temp).then((res) => { this.confirmLoading = true
if (res.code === 200) { editScript(this.temp)
// .then((res) => {
this.$notification.success({ if (res.code === 200) {
message: res.msg //
}) this.$notification.success({
message: res.msg
})
this.editScriptVisible = false this.editScriptVisible = false
this.loadData() this.loadData()
this.$refs['editScriptForm'].resetFields() this.$refs['editScriptForm'].resetFields()
} }
}) })
.finally(() => {
this.confirmLoading = false
})
}) })
}, },
handleDelete(record) { handleDelete(record) {
@ -748,9 +713,9 @@ export default {
this.$confirm({ this.$confirm({
title: '危险操作!!!', title: '危险操作!!!',
zIndex: 1009, zIndex: 1009,
content: h('div', null, [h('p', { domProps: { innerHTML: html } }, null)]), content: h('div', null, [h('p', { innerHTML: html }, null)]),
okButtonProps: { props: { type: 'danger', size: 'small' } }, okButtonProps: { type: 'primary', danger: true, size: 'small' },
cancelButtonProps: { props: { type: 'primary' } }, cancelButtonProps: { type: 'primary' },
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',
onOk: () => { onOk: () => {
@ -793,24 +758,29 @@ export default {
return false return false
} }
// //
this.confirmLoading = true
syncToWorkspace({ syncToWorkspace({
ids: this.tableSelections.join(','), ids: this.tableSelections.join(','),
toWorkspaceId: this.temp.workspaceId toWorkspaceId: this.temp.workspaceId
}).then((res) => {
if (res.code === 200) {
this.$notification.success({
message: res.msg
})
this.tableSelections = []
this.syncToWorkspaceVisible = false
return false
}
}) })
.then((res) => {
if (res.code === 200) {
this.$notification.success({
message: res.msg
})
this.tableSelections = []
this.syncToWorkspaceVisible = false
return false
}
})
.finally(() => {
this.confirmLoading = false
})
}, },
// //
handleTrigger(record) { handleTrigger(record) {
this.temp = Object.assign({}, record) this.temp = Object.assign({}, record)
this.tempVue = Vue
getTriggerUrl({ getTriggerUrl({
id: record.id id: record.id
}).then((res) => { }).then((res) => {
@ -853,12 +823,12 @@ export default {
return return
} }
if (this.choose === 'checkbox') { if (this.choose === 'checkbox') {
$emit(this, 'confirm', this.tableSelections.join(',')) this.$emit('confirm', this.tableSelections.join(','))
} else { } else {
const selectData = this.list.filter((item) => { const selectData = this.list.filter((item) => {
return item.id === this.tableSelections[0] return item.id === this.tableSelections[0]
})[0] })[0]
$emit(this, 'confirm', `${selectData.id}`) this.$emit('confirm', `${selectData.id}`)
} }
} }
}, },

View File

@ -1,21 +1,51 @@
<template> <template>
<div class=""> <div class="">
<!-- 数据表格 --> <!-- 数据表格 -->
<a-table :data-source="list" size="middle" :columns="columns" @change="changePage" :pagination="pagination" bordered rowKey="id"> <a-table
:data-source="list"
size="middle"
:columns="columns"
@change="changePage"
:pagination="pagination"
bordered
rowKey="id"
:scroll="{
x: 'max-content'
}"
>
<template v-slot:title> <template v-slot:title>
<a-space> <a-space>
<a-input v-model:value="listQuery['%scriptName%']" placeholder="名称" @pressEnter="loadData" allowClear class="search-input-item" /> <a-input
<a-select show-search option-filter-prop="children" v-model:value="listQuery.triggerExecType" allowClear placeholder="触发类型" class="search-input-item"> v-model:value="listQuery['%scriptName%']"
placeholder="名称"
@pressEnter="loadData"
allowClear
class="search-input-item"
/>
<a-select
show-search
option-filter-prop="children"
v-model:value="listQuery.triggerExecType"
allowClear
placeholder="触发类型"
class="search-input-item"
>
<a-select-option v-for="(val, key) in triggerExecTypeMap" :key="key">{{ val }}</a-select-option> <a-select-option v-for="(val, key) in triggerExecTypeMap" :key="key">{{ val }}</a-select-option>
</a-select> </a-select>
<a-select show-search option-filter-prop="children" v-model:value="listQuery.status" allowClear placeholder="状态" class="search-input-item"> <a-select
show-search
option-filter-prop="children"
v-model:value="listQuery.status"
allowClear
placeholder="状态"
class="search-input-item"
>
<a-select-option v-for="(val, key) in statusMap" :key="key">{{ val }}</a-select-option> <a-select-option v-for="(val, key) in statusMap" :key="key">{{ val }}</a-select-option>
</a-select> </a-select>
<a-range-picker <a-range-picker
v-model:value="listQuery['createTimeMillis']" v-model:value="listQuery['createTimeMillis']"
allowClear allowClear
inputReadOnly inputReadOnly
class="search-input-item"
:show-time="{ format: 'HH:mm:ss' }" :show-time="{ format: 'HH:mm:ss' }"
:placeholder="['执行时间开始', '执行时间结束']" :placeholder="['执行时间开始', '执行时间结束']"
format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss"
@ -26,41 +56,43 @@
</a-tooltip> </a-tooltip>
</a-space> </a-space>
</template> </template>
<template v-slot:scriptName="text"> <template #bodyCell="{ column, text, record }">
<a-tooltip placement="topLeft" :title="text"> <template v-if="column.dataIndex === 'scriptName'">
<span>{{ text }}</span> <a-tooltip placement="topLeft" :title="text">
</a-tooltip> <span>{{ text }}</span>
</template> </a-tooltip>
<template v-slot:modifyUser="text"> </template>
<a-tooltip placement="topLeft" :title="text"> <template v-else-if="column.dataIndex === 'modifyUser'">
<span>{{ text }}</span> <a-tooltip placement="topLeft" :title="text">
</a-tooltip> <span>{{ text }}</span>
</template> </a-tooltip>
<template v-slot:triggerExecTypeMap="text"> </template>
<span>{{ triggerExecTypeMap[text] || "未知" }}</span> <template v-else-if="column.dataIndex === 'triggerExecType'">
</template> <span>{{ triggerExecTypeMap[text] || '未知' }}</span>
<template v-slot:global="text"> </template>
<a-tag v-if="text === 'GLOBAL'">全局</a-tag> <template v-else-if="column.dataIndex === 'workspaceId'">
<a-tag v-else>工作空间</a-tag> <a-tag v-if="text === 'GLOBAL'">全局</a-tag>
</template> <a-tag v-else>工作空间</a-tag>
<template v-slot:createTimeMillis="text, record"> </template>
<a-tooltip :title="`${parseTime(record.createTimeMillis)}`"> <template v-else-if="column.dataIndex === 'createTimeMillis'">
<span>{{ parseTime(record.createTimeMillis) }}</span> <a-tooltip :title="`${parseTime(record.createTimeMillis)}`">
</a-tooltip> <span>{{ parseTime(record.createTimeMillis) }}</span>
</template> </a-tooltip>
<template v-slot:exitCode="text"> </template>
<a-tag v-if="text == 0" color="green">成功</a-tag> <template v-else-if="column.dataIndex === 'exitCode'">
<a-tag v-else color="orange">{{ text || "-" }}</a-tag> <a-tag v-if="text == 0" color="green">成功</a-tag>
</template> <a-tag v-else color="orange">{{ text || '-' }}</a-tag>
<template v-slot:status="text"> </template>
<span>{{ statusMap[text] || "" }}</span> <template v-else-if="column.dataIndex === 'status'">
</template> <span>{{ statusMap[text] || '' }}</span>
<template v-slot:operation="text, record"> </template>
<a-space> <template v-else-if="column.dataIndex === 'operation'">
<a-button type="primary" size="small" @click="viewLog(record)">查看日志</a-button> <a-space>
<a-button type="primary" size="small" @click="viewLog(record)">查看日志</a-button>
<a-button type="primary" danger size="small" @click="handleDelete(record)">删除</a-button> <a-button type="primary" danger size="small" @click="handleDelete(record)">删除</a-button>
</a-space> </a-space>
</template>
</template> </template>
</a-table> </a-table>
<!-- 日志 --> <!-- 日志 -->
@ -70,7 +102,7 @@
:visible="logVisible != 0" :visible="logVisible != 0"
@close=" @close="
() => { () => {
logVisible = 0; logVisible = 0
} }
" "
:temp="temp" :temp="temp"
@ -79,20 +111,20 @@
</template> </template>
<script> <script>
import { getScriptLogList, scriptDel, triggerExecTypeMap } from "@/api/server-script"; import { getScriptLogList, scriptDel, triggerExecTypeMap } from '@/api/server-script'
import ScriptLogView from "@/pages/script/script-log-view"; import ScriptLogView from '@/pages/script/script-log-view'
import { statusMap } from "@/api/command"; import { statusMap } from '@/api/command'
import { CHANGE_PAGE, COMPUTED_PAGINATION, PAGE_DEFAULT_LIST_QUERY, parseTime } from "@/utils/const"; import { CHANGE_PAGE, COMPUTED_PAGINATION, PAGE_DEFAULT_LIST_QUERY, parseTime } from '@/utils/const'
export default { export default {
components: { components: {
ScriptLogView, ScriptLogView
}, },
props: { props: {
scriptId: { scriptId: {
type: String, type: String,
default: "", default: ''
}, }
}, },
data() { data() {
return { return {
@ -105,123 +137,123 @@ export default {
logVisible: 0, logVisible: 0,
columns: [ columns: [
{ {
title: "名称", title: '名称',
dataIndex: "scriptName", dataIndex: 'scriptName',
width: 100, width: 100,
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: "scriptName" }, scopedSlots: { customRender: 'scriptName' }
}, },
{ {
title: "执行时间", title: '执行时间',
dataIndex: "createTimeMillis", dataIndex: 'createTimeMillis',
sorter: true, sorter: true,
ellipsis: true, ellipsis: true,
width: "160px", width: '160px',
scopedSlots: { customRender: "createTimeMillis" }, scopedSlots: { customRender: 'createTimeMillis' }
}, },
{ {
title: "触发类型", title: '触发类型',
dataIndex: "triggerExecType", dataIndex: 'triggerExecType',
width: 100, width: 100,
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: "triggerExecTypeMap" }, scopedSlots: { customRender: 'triggerExecTypeMap' }
}, },
{ {
title: "状态", title: '状态',
dataIndex: "status", dataIndex: 'status',
width: 100, width: 100,
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: "status" }, scopedSlots: { customRender: 'status' }
}, },
{ {
title: "执行域", title: '执行域',
dataIndex: "workspaceId", dataIndex: 'workspaceId',
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: "global" }, scopedSlots: { customRender: 'global' },
width: "90px", width: '90px'
}, },
{ {
title: "退出码", title: '退出码',
dataIndex: "exitCode", dataIndex: 'exitCode',
width: 100, width: 100,
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: "exitCode" }, scopedSlots: { customRender: 'exitCode' }
}, },
{ {
title: "执行人", title: '执行人',
dataIndex: "modifyUser", dataIndex: 'modifyUser',
ellipsis: true, ellipsis: true,
width: "100px", width: '100px',
scopedSlots: { customRender: "modifyUser" }, scopedSlots: { customRender: 'modifyUser' }
}, },
{ {
title: "操作", title: '操作',
dataIndex: "operation", dataIndex: 'operation',
align: "center", align: 'center',
fixed: "right", fixed: 'right',
scopedSlots: { customRender: "operation" }, scopedSlots: { customRender: 'operation' },
width: "150px", width: '150px'
}, }
], ]
}; }
}, },
computed: { computed: {
pagination() { pagination() {
return COMPUTED_PAGINATION(this.listQuery); return COMPUTED_PAGINATION(this.listQuery)
}, }
}, },
mounted() { mounted() {
this.loadData(); this.loadData()
}, },
methods: { methods: {
// //
loadData(pointerEvent) { loadData(pointerEvent) {
this.listQuery.page = pointerEvent?.altKey || pointerEvent?.ctrlKey ? 1 : this.listQuery.page; this.listQuery.page = pointerEvent?.altKey || pointerEvent?.ctrlKey ? 1 : this.listQuery.page
this.listQuery.scriptId = this.scriptId; this.listQuery.scriptId = this.scriptId
this.loading = true; this.loading = true
getScriptLogList(this.listQuery).then((res) => { getScriptLogList(this.listQuery).then((res) => {
if (res.code === 200) { if (res.code === 200) {
this.list = res.data.result; this.list = res.data.result
this.listQuery.total = res.data.total; this.listQuery.total = res.data.total
} }
this.loading = false; this.loading = false
}); })
}, },
parseTime, parseTime,
viewLog(record) { viewLog(record) {
this.logVisible = new Date() * Math.random(); this.logVisible = new Date() * Math.random()
this.temp = record; this.temp = record
}, },
handleDelete(record) { handleDelete(record) {
this.$confirm({ this.$confirm({
title: "系统提示", title: '系统提示',
content: "真的要删除执行记录么?", content: '真的要删除执行记录么?',
zIndex: 1009, zIndex: 1009,
okText: "确认", okText: '确认',
cancelText: "取消", cancelText: '取消',
onOk: () => { onOk: () => {
// //
const params = { const params = {
id: record.scriptId, id: record.scriptId,
executeId: record.id, executeId: record.id
}; }
// //
scriptDel(params).then((res) => { scriptDel(params).then((res) => {
if (res.code === 200) { if (res.code === 200) {
this.$notification.success({ this.$notification.success({
message: res.msg, message: res.msg
}); })
this.loadData(); this.loadData()
} }
}); })
}, }
}); })
}, },
// //
changePage(pagination, filters, sorter) { changePage(pagination, filters, sorter) {
this.listQuery = CHANGE_PAGE(this.listQuery, { pagination, sorter }); this.listQuery = CHANGE_PAGE(this.listQuery, { pagination, sorter })
this.loadData(); this.loadData()
}, }
}, }
}; }
</script> </script>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="full-content"> <div>
<a-table <a-table
size="middle" size="middle"
:data-source="commandList" :data-source="commandList"
@ -7,18 +7,20 @@
bordered bordered
:pagination="pagination" :pagination="pagination"
@change="changePage" @change="changePage"
:rowKey="(record, index) => index" :scroll="{
x: 'max-content'
}"
> >
<template #title> <template v-slot:title>
<a-space> <a-space>
<a-input <a-input
v-model="listQuery['%commandName%']" v-model:value="listQuery['%commandName%']"
@pressEnter="getCommandLogData" @pressEnter="getCommandLogData"
placeholder="搜索命令名称" placeholder="搜索命令名称"
class="search-input-item" class="search-input-item"
/> />
<a-input <a-input
v-model="listQuery['%sshName%']" v-model:value="listQuery['%sshName%']"
@pressEnter="getCommandLogData" @pressEnter="getCommandLogData"
placeholder="搜索ssh名称" placeholder="搜索ssh名称"
class="search-input-item" class="search-input-item"
@ -26,7 +28,7 @@
<a-select <a-select
show-search show-search
option-filter-prop="children" option-filter-prop="children"
v-model="listQuery.status" v-model:value="listQuery.status"
allowClear allowClear
placeholder="状态" placeholder="状态"
class="search-input-item" class="search-input-item"
@ -36,7 +38,7 @@
<a-select <a-select
show-search show-search
option-filter-prop="children" option-filter-prop="children"
v-model="listQuery.triggerExecType" v-model:value="listQuery.triggerExecType"
allowClear allowClear
placeholder="触发类型" placeholder="触发类型"
class="search-input-item" class="search-input-item"
@ -48,39 +50,51 @@
</a-tooltip> </a-tooltip>
</a-space> </a-space>
</template> </template>
<a-tooltip #sshName slot-scope="text" placement="topLeft" :title="text"> <template #bodyCell="{ column, text, record }">
<span>{{ text }}</span> <template v-if="column.dataIndex === 'sshName'">
</a-tooltip> <a-tooltip placement="topLeft" :title="text">
<a-tooltip #commandName slot-scope="text" placement="topLeft" :title="text"> <span>{{ text }}</span>
<span>{{ text }}</span> </a-tooltip>
</a-tooltip> </template>
<template #status slot-scope="text"> <template v-else-if="column.dataIndex === 'commandName'">
<span>{{ statusMap[text] || '未知' }}</span> <a-tooltip placement="topLeft" :title="text">
</template> <span>{{ text }}</span>
<template #triggerExecTypeMap slot-scope="text"> </a-tooltip>
<span>{{ triggerExecTypeMap[text] || '未知' }}</span> </template>
</template> <template v-else-if="column.dataIndex === 'status'">
<span>{{ statusMap[text] || '未知' }}</span>
</template>
<template v-else-if="column.dataIndex === 'triggerExecType'">
<span>{{ triggerExecTypeMap[text] || '未知' }}</span>
</template>
<template v-else-if="column.dataIndex === 'exitCode'">
<a-tag v-if="text == 0" color="green">成功</a-tag>
<a-tag v-else color="orange">{{ text || '-' }}</a-tag>
</template>
<template #operation slot-scope="text, record"> <template v-else-if="column.dataIndex === 'operation'">
<a-space> <a-space>
<a-button type="primary" size="small" :disabled="!record.hasLog" @click="handleView(record)">查看</a-button> <a-button type="primary" size="small" :disabled="!record.hasLog" @click="handleView(record)">查看</a-button>
<a-button type="primary" size="small" :disabled="!record.hasLog" @click="handleDownload(record)" <a-button type="primary" size="small" :disabled="!record.hasLog" @click="handleDownload(record)"
><a-icon type="download" />日志</a-button ><DownloadOutlined />日志</a-button
> >
<a-button type="danger" size="small" @click="handleDelete(record)">删除</a-button> <a-button type="primary" danger size="small" @click="handleDelete(record)">删除</a-button>
</a-space> </a-space>
</template>
</template> </template>
</a-table> </a-table>
<!-- 构建日志 --> <!-- 构建日志 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
:width="'80vw'" :width="style.width"
v-model:visible="logVisible" :bodyStyle="style.bodyStyle"
:style="style.style"
v-model:open="logVisible"
title="执行日志" title="执行日志"
:footer="null" :footer="null"
:maskClosable="false" :maskClosable="false"
> >
<command-log v-if="logVisible" :temp="temp" /> <command-log :height="style.bodyStyle.height" v-if="logVisible" :temp="temp" />
</a-modal> </a-modal>
</div> </div>
</template> </template>
@ -89,7 +103,8 @@
import { deleteCommandLog, downloadLog, getCommandLogList, statusMap, triggerExecTypeMap } from '@/api/command' import { deleteCommandLog, downloadLog, getCommandLogList, statusMap, triggerExecTypeMap } from '@/api/command'
import { CHANGE_PAGE, COMPUTED_PAGINATION, PAGE_DEFAULT_LIST_QUERY, parseTime } from '@/utils/const' import { CHANGE_PAGE, COMPUTED_PAGINATION, PAGE_DEFAULT_LIST_QUERY, parseTime } from '@/utils/const'
import CommandLog from './command-view-log' import CommandLog from './command-view-log'
import { mapState } from 'pinia'
import { useGuideStore } from '@/stores/guide'
export default { export default {
components: { components: {
CommandLog CommandLog
@ -104,58 +119,80 @@ export default {
triggerExecTypeMap: triggerExecTypeMap, triggerExecTypeMap: triggerExecTypeMap,
logVisible: false, logVisible: false,
columns: [ columns: [
{ title: 'ssh 名称', dataIndex: 'sshName', ellipsis: true, scopedSlots: { customRender: 'sshName' } }, {
{ title: '命令名称', dataIndex: 'commandName', ellipsis: true, scopedSlots: { customRender: 'commandName' } }, title: 'ssh 名称',
{ title: '状态', dataIndex: 'status', width: 100, ellipsis: true, scopedSlots: { customRender: 'status' } }, dataIndex: 'sshName',
ellipsis: true
},
{
title: '命令名称',
dataIndex: 'commandName',
ellipsis: true
},
{
title: '状态',
dataIndex: 'status',
width: 100,
ellipsis: true
},
{
title: '退出码',
dataIndex: 'exitCode',
width: 100,
ellipsis: true
},
{ {
title: '触发类型', title: '触发类型',
dataIndex: 'triggerExecType', dataIndex: 'triggerExecType',
width: 100, width: 100,
ellipsis: true, ellipsis: true
scopedSlots: { customRender: 'triggerExecTypeMap' }
}, },
{ {
title: '执行时间', title: '执行时间',
dataIndex: 'createTimeMillis', dataIndex: 'createTimeMillis',
sorter: true, sorter: true,
ellipsis: true, ellipsis: true,
customRender: (text) => { customRender: ({ text }) => {
return parseTime(text) return parseTime(text)
}, },
width: 170 width: '170px'
}, },
{ {
title: '结束时间', title: '结束时间',
dataIndex: 'modifyTimeMillis', dataIndex: 'modifyTimeMillis',
sorter: true, sorter: true,
ellipsis: true, ellipsis: true,
customRender: (text) => { customRender: ({ text }) => {
return parseTime(text) return parseTime(text)
}, },
width: 170 width: '170px'
}, },
{ {
title: '执行人', title: '执行人',
dataIndex: 'modifyUser', dataIndex: 'modifyUser',
width: 120, width: 120,
ellipsis: true, ellipsis: true
scopedSlots: { customRender: 'modifyUser' }
}, },
{ {
title: '操作', title: '操作',
dataIndex: 'operation', dataIndex: 'operation',
align: 'center', align: 'center',
scopedSlots: { customRender: 'operation' }, fixed: 'right',
width: 200 width: '200px'
} }
] ]
} }
}, },
computed: { computed: {
...mapState(useGuideStore, ['getFullscreenViewLogStyle']),
pagination() { pagination() {
return COMPUTED_PAGINATION(this.listQuery) return COMPUTED_PAGINATION(this.listQuery)
},
style() {
return this.getFullscreenViewLogStyle()
} }
}, },
created() {},
mounted() { mounted() {
this.getCommandLogData() this.getCommandLogData()
}, },
@ -184,8 +221,9 @@ export default {
}, },
// //
handleDelete(row) { handleDelete(row) {
$confirm({ this.$confirm({
title: '系统提示', title: '系统提示',
zIndex: 1009,
content: '真的要删除该执行记录吗?', content: '真的要删除该执行记录吗?',
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',
@ -193,7 +231,7 @@ export default {
// //
deleteCommandLog(row.id).then((res) => { deleteCommandLog(row.id).then((res) => {
if (res.code === 200) { if (res.code === 200) {
$notification.success({ this.$notification.success({
message: res.msg message: res.msg
}) })
this.getCommandLogData() this.getCommandLogData()
@ -209,4 +247,3 @@ export default {
} }
} }
</script> </script>
<style scoped></style>

View File

@ -1,27 +1,34 @@
<template> <template>
<div style="margin-top: -10px"> <div>
<a-tabs :activeKey="activeKey" @change="tabCallback"> <a-tabs :activeKey="activeKey" @change="tabCallback">
<a-tab-pane v-for="item in logList" :key="item.id"> <a-tab-pane v-for="item in logList" :key="item.id">
<span #tab> <template v-slot:tab>
<a-icon v-if="!logMap[item.id] || logMap[item.id].run" type="loading" /> <span>
{{ item.sshName }} <LoadingOutlined v-if="!logMap[item.id] || logMap[item.id].run" />
</span> {{ item.sshName }}
</span>
</template>
<!-- <a-input :id="`build-log-textarea-${item.id}`" v-model="logMap[item.id].logText" type="textarea" class="console" readOnly style="resize: none; height: 60vh" /> --> <!-- <a-input :id="`build-log-textarea-${item.id}`" v-model="logMap[item.id].logText" type="textarea" class="console" readOnly style="resize: none; height: 60vh" /> -->
<log-view :ref="`logView-${item.id}`" height="60vh" /> <log-view1 :ref="`logView-${item.id}`" :height="`calc(${height} - 130px)`" />
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
</div> </div>
</template> </template>
<script> <script>
import { getCommandLogBarchList, getCommandLogInfo } from '@/api/command' import { getCommandLogBarchList, getCommandLogInfo } from '@/api/command'
import LogView from '@/components/logView' import LogView1 from '@/components/logView/index2'
export default { export default {
components: { components: {
LogView LogView1
}, },
props: { props: {
temp: { temp: {
type: Object type: Object
},
height: {
type: String,
default: '60vh'
} }
}, },
data() { data() {
@ -32,7 +39,7 @@ export default {
logMap: {} logMap: {}
} }
}, },
beforeDestroy() { beforeUnmount() {
if (this.logTimerMap) { if (this.logTimerMap) {
this.logList.forEach((item) => { this.logList.forEach((item) => {
clearInterval(this.logTimerMap[item.id]) clearInterval(this.logTimerMap[item.id])
@ -79,7 +86,7 @@ export default {
getCommandLogInfo(params).then((res) => { getCommandLogInfo(params).then((res) => {
if (res.code === 200) { if (res.code === 200) {
if (!res.data) { if (!res.data) {
$notification.warning({ this.$notification.warning({
message: res.msg message: res.msg
}) })
this.logMap[item.id].tryCount = this.logMap[item.id].tryCount + 1 this.logMap[item.id].tryCount = this.logMap[item.id].tryCount + 1
@ -115,7 +122,7 @@ export default {
this.logMap[item.id].line = res.data.line this.logMap[item.id].line = res.data.line
// if (lines.length) { // if (lines.length) {
// // // //
// nextTick(() => { // this.$nextTick(() => {
// setTimeout(() => { // setTimeout(() => {
// const textarea = document.getElementById("build-log-textarea-" + item.id); // const textarea = document.getElementById("build-log-textarea-" + item.id);
// if (textarea) { // if (textarea) {
@ -135,7 +142,7 @@ export default {
if (this.logTimerMap[key]) { if (this.logTimerMap[key]) {
return return
} }
nextTick(() => { this.$nextTick(() => {
const index = this.logList const index = this.logList
.map((item1, index) => { .map((item1, index) => {
return item1.id == key ? index : -1 return item1.id == key ? index : -1
@ -147,6 +154,7 @@ export default {
} }
} }
</script> </script>
<style scoped> <style scoped>
/* .console { /* .console {
padding: 5px; padding: 5px;

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="full-content"> <div>
<a-table <a-table
:data-source="commandList" :data-source="commandList"
:columns="columns" :columns="columns"
@ -9,23 +9,26 @@
@change="changePage" @change="changePage"
:row-selection="rowSelection" :row-selection="rowSelection"
rowKey="id" rowKey="id"
:scroll="{
x: 'max-content'
}"
> >
<template #title> <template v-slot:title>
<a-space> <a-space>
<a-input <a-input
v-model="listQuery['%name%']" v-model:value="listQuery['%name%']"
@pressEnter="getCommandData" @pressEnter="getCommandData"
placeholder="搜索命令" placeholder="搜索命令"
class="search-input-item" class="search-input-item"
/> />
<a-input <a-input
v-model="listQuery['%desc%']" v-model:value="listQuery['%desc%']"
@pressEnter="getCommandData" @pressEnter="getCommandData"
placeholder="描述" placeholder="描述"
class="search-input-item" class="search-input-item"
/> />
<a-input <a-input
v-model="listQuery['%autoExecCron%']" v-model:value="listQuery['%autoExecCron%']"
@pressEnter="getCommandData" @pressEnter="getCommandData"
placeholder="定时执行" placeholder="定时执行"
class="search-input-item" class="search-input-item"
@ -35,8 +38,8 @@
</a-tooltip> </a-tooltip>
<a-button type="primary" @click="createCommand">新建命令</a-button> <a-button type="primary" @click="createCommand">新建命令</a-button>
<a-dropdown> <a-dropdown>
<a class="ant-dropdown-link" @click="(e) => e.preventDefault()"> 更多 <down-outlined /> </a> <a @click="(e) => e.preventDefault()"> 更多 <DownOutlined /> </a>
<template #overlay> <template v-slot:overlay>
<a-menu> <a-menu>
<a-menu-item> <a-menu-item>
<a-button <a-button
@ -50,7 +53,7 @@
</template> </template>
</a-dropdown> </a-dropdown>
<a-tooltip> <a-tooltip>
<template #title> <template v-slot:title>
<div>命令模版是用于在线管理一些脚本命令如初始化软件环境管理应用程序等</div> <div>命令模版是用于在线管理一些脚本命令如初始化软件环境管理应用程序等</div>
<div> <div>
@ -61,63 +64,76 @@
命令文件并自动加载环境变量/etc/profile/etc/bashrc~/.bashrc、~/.bash_profile 命令文件并自动加载环境变量/etc/profile/etc/bashrc~/.bashrc、~/.bash_profile
</li> </li>
<li> <li>
执行命令包含<b>#disabled-template-auto-evn</b> 将取消自动加载环境变量(注意是整行不能包含空格) 执行命令包含<b>#disabled-template-auto-evn</b>
将取消自动加载环境变量(注意是整行不能包含空格)
</li> </li>
<li>命令文件将上传至 ${user.home}/.jpom/xxxx.sh 执行完成将自动删除</li> <li>命令文件将上传至 ${user.home}/.jpom/xxxx.sh 执行完成将自动删除</li>
</ul> </ul>
</div> </div>
</template> </template>
<question-circle-filled /> <QuestionCircleOutlined />
</a-tooltip> </a-tooltip>
</a-space> </a-space>
</template> </template>
<a-tooltip #name slot-scope="text" placement="topLeft" :title="text"> <template #bodyCell="{ column, text, record }">
<span>{{ text }}</span> <template v-if="column.dataIndex === 'name'">
</a-tooltip> <a-tooltip placement="topLeft" :title="text">
<a-tooltip #desc slot-scope="text" placement="topLeft" :title="text"> <span>{{ text }}</span>
<span>{{ text }}</span> </a-tooltip>
</a-tooltip> </template>
<template v-else-if="column.dataIndex === 'desc'">
<a-tooltip placement="topLeft" :title="text">
<span>{{ text }}</span>
</a-tooltip>
</template>
<template #operation slot-scope="text, record"> <template v-else-if="column.dataIndex === 'operation'">
<a-space> <a-space>
<a-button size="small" type="primary" @click="handleExecute(record)">执行</a-button> <a-button size="small" type="primary" @click="handleExecute(record)">执行</a-button>
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button> <a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
<a-button size="small" type="primary" @click="handleTrigger(record)">触发器</a-button> <a-button size="small" type="primary" @click="handleTrigger(record)">触发器</a-button>
<a-button size="small" type="danger" @click="handleDelete(record)">删除</a-button> <a-button size="small" type="primary" danger @click="handleDelete(record)">删除</a-button>
</a-space> </a-space>
</template>
</template> </template>
</a-table> </a-table>
<!-- 编辑命令 --> <!-- 编辑命令 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model="editCommandVisible" v-model:open="editCommandVisible"
width="80vw" width="80vw"
title="编辑 命令" title="编辑 命令"
@ok="handleEditCommandOk" @ok="handleEditCommandOk"
:maskClosable="false" :maskClosable="false"
:confirmLoading="confirmLoading"
> >
<a-form ref="editCommandForm" :rules="rules" :model="temp" :label-col="{ span: 3 }" :wrapper-col="{ span: 20 }"> <a-form ref="editCommandForm" :rules="rules" :model="temp" :label-col="{ span: 3 }" :wrapper-col="{ span: 20 }">
<a-form-item label="命令名称" name="name"> <a-form-item label="命令名称" name="name">
<a-input v-model="temp.name" :maxLength="100" placeholder="命令名称" /> <a-input v-model:value="temp.name" :maxLength="100" placeholder="命令名称" />
</a-form-item> </a-form-item>
<a-form-item <a-form-item
name="command" name="command"
help="脚本存放路径:${user.home}/.jpom/xxxx.sh执行脚本路径${user.home}执行脚本方式bash ${user.home}/.jpom/xxxx.sh par1 par2" help="脚本存放路径:${user.home}/.jpom/xxxx.sh执行脚本路径${user.home}执行脚本方式bash ${user.home}/.jpom/xxxx.sh par1 par2"
> >
<template #label> <template v-slot:label>
命令内容 <a-tooltip>
<a-tooltip v-show="!temp.id"> 命令内容
<template #title> <template v-slot:title>
<ul> <ul>
<li>可以引用工作空间的环境变量 变量占位符 ${xxxx} xxxx 为变量名称</li> <li>可以引用工作空间的环境变量 变量占位符 ${xxxx} xxxx 为变量名称</li>
</ul> </ul>
</template> </template>
<question-circle-filled /> <QuestionCircleOutlined v-show="!temp.id" />
</a-tooltip> </a-tooltip>
</template> </template>
<div style="height: 40vh; overflow-y: scroll"> <div style="height: 40vh; overflow-y: scroll">
<code-editor v-model="temp.command" :options="{ mode: 'shell', tabSize: 2, theme: 'abcdef' }"></code-editor> <a-form-item-rest>
<code-editor
v-model:content="temp.command"
:options="{ mode: 'shell', tabSize: 2, theme: 'abcdef' }"
></code-editor>
</a-form-item-rest>
</div> </div>
</a-form-item> </a-form-item>
<a-form-item label="SSH节点"> <a-form-item label="SSH节点">
@ -126,7 +142,7 @@
option-filter-prop="children" option-filter-prop="children"
placeholder="请选择SSH节点" placeholder="请选择SSH节点"
mode="multiple" mode="multiple"
v-model="chooseSsh" v-model:value="chooseSsh"
> >
<a-select-option v-for="item in sshList" :key="item.id" :value="item.id"> <a-select-option v-for="item in sshList" :key="item.id" :value="item.id">
{{ item.name }} {{ item.name }}
@ -135,56 +151,49 @@
</a-form-item> </a-form-item>
<a-form-item label="默认参数"> <a-form-item label="默认参数">
<div v-for="(item, index) in commandParams" :key="item.key"> <a-form-item-rest>
<a-row type="flex" justify="center" align="middle"> <a-space direction="vertical" style="width: 100%">
<a-col :span="22"> <a-row v-for="(item, index) in commandParams" :key="item.key">
<a-input <a-col :span="22">
:addon-before="`参数${index + 1}描述`" <a-space direction="vertical" style="width: 100%">
v-model="item.desc" <a-input
placeholder="参数描述,参数描述没有实际作用,仅是用于提示参数的含义" :addon-before="`参数${index + 1}描述`"
/> v-model:value="item.desc"
<a-input placeholder="参数描述,参数描述没有实际作用,仅是用于提示参数的含义"
:addon-before="`参数${index + 1}值`" />
v-model="item.value" <a-input
placeholder="参数值,添加默认参数后在手动执行脚本时需要填写参数值" :addon-before="`参数${index + 1}值`"
/> v-model:value="item.value"
</a-col> placeholder="参数值,添加默认参数后在手动执行脚本时需要填写参数值"
<a-col :span="2"> />
<a-row type="flex" justify="center" align="middle"> </a-space>
<a-col> </a-col>
<a-icon @click="() => commandParams.splice(index, 1)" type="minus-circle" style="color: #ff0000" />
</a-col>
</a-row>
</a-col>
</a-row>
<a-divider style="margin: 5px 0" />
</div>
<a-button type="primary" @click="() => commandParams.push({})">添加参数</a-button> <a-col :span="2">
<a-row type="flex" justify="center" align="middle">
<a-col>
<MinusCircleOutlined @click="() => commandParams.splice(index, 1)" style="color: #ff0000" />
</a-col>
</a-row>
</a-col>
</a-row>
<a-button type="primary" @click="() => commandParams.push({})">添加参数</a-button>
</a-space>
</a-form-item-rest>
</a-form-item> </a-form-item>
<a-form-item label="自动执行" name="autoExecCron"> <a-form-item label="自动执行" name="autoExecCron">
<a-auto-complete <a-auto-complete
v-model="temp.autoExecCron" v-model:value="temp.autoExecCron"
placeholder="如果需要定时自动执行则填写,cron 表达式.默认未开启秒级别,需要去修改配置文件中:[system.timerMatchSecond]" placeholder="如果需要定时自动执行则填写,cron 表达式.默认未开启秒级别,需要去修改配置文件中:[system.timerMatchSecond]"
option-label-prop="value" :options="CRON_DATA_SOURCE"
> >
<template #dataSource> <template #option="item"> {{ item.title }} {{ item.value }} </template>
<a-select-opt-group v-for="group in cronDataSource" :key="group.title">
<span #label>
{{ group.title }}
</span>
<a-select-option v-for="opt in group.children" :key="opt.title" :value="opt.value">
{{ opt.title }} {{ opt.value }}
</a-select-option>
</a-select-opt-group>
</template>
</a-auto-complete> </a-auto-complete>
</a-form-item> </a-form-item>
<a-form-item label="命令描述" name="desc"> <a-form-item label="命令描述" name="desc">
<a-input <a-textarea
v-model="temp.desc" v-model:value="temp.desc"
:maxLength="255" :maxLength="255"
type="textarea"
:rows="3" :rows="3"
style="resize: none" style="resize: none"
placeholder="命令详细描述" placeholder="命令详细描述"
@ -195,15 +204,16 @@
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model="executeCommandVisible" v-model:open="executeCommandVisible"
width="600px" width="600px"
title="执行 命令" title="执行 命令"
@ok="handleExecuteCommandOk" @ok="handleExecuteCommandOk"
:maskClosable="false" :maskClosable="false"
:confirmLoading="confirmLoading"
> >
<a-form :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }"> <a-form :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }">
<a-form-item label="命令名称" name="name"> <a-form-item label="命令名称" name="name">
<a-input v-model="temp.name" :disabled="true" placeholder="命令名称" /> <a-input v-model:value="temp.name" :disabled="true" placeholder="命令名称" />
</a-form-item> </a-form-item>
<a-form-item label="SSH节点" required> <a-form-item label="SSH节点" required>
@ -211,7 +221,7 @@
show-search show-search
option-filter-prop="children" option-filter-prop="children"
mode="multiple" mode="multiple"
v-model="chooseSsh" v-model:value="chooseSsh"
placeholder="请选择 SSH节点" placeholder="请选择 SSH节点"
> >
<a-select-option v-for="item in sshList" :key="item.id" :value="item.id"> <a-select-option v-for="item in sshList" :key="item.id" :value="item.id">
@ -228,30 +238,32 @@
: '' : ''
}`" }`"
> >
<a-row v-for="(item, index) in commandParams" :key="item.key"> <a-space direction="vertical" style="width: 100%">
<a-col :span="22"> <a-row v-for="(item, index) in commandParams" :key="item.key">
<a-input <a-col :span="22">
:addon-before="`参数${index + 1}值`" <a-input
v-model="item.value" :addon-before="`参数${index + 1}值`"
:placeholder="`参数值 ${item.desc ? ',' + item.desc : ''}`" v-model:value="item.value"
> :placeholder="`参数值 ${item.desc ? ',' + item.desc : ''}`"
<template #suffix> >
<a-tooltip v-if="item.desc" :title="item.desc"> <template v-slot:suffix>
<a-icon type="info-circle" style="color: rgba(0, 0, 0, 0.45)" /> <a-tooltip v-if="item.desc" :title="item.desc">
</a-tooltip> <InfoCircleOutlined />
</template> </a-tooltip>
</a-input> </template>
</a-col> </a-input>
</a-col>
<a-col v-if="!item.desc" :span="2"> <a-col v-if="!item.desc" :span="2">
<a-row type="flex" justify="center" align="middle"> <a-row type="flex" justify="center" align="middle">
<a-col> <a-col>
<a-icon type="minus-circle" style="color: #ff0000" @click="() => commandParams.splice(index, 1)" /> <MinusCircleOutlined style="color: #ff0000" @click="() => commandParams.splice(index, 1)" />
</a-col> </a-col>
</a-row> </a-row>
</a-col> </a-col>
</a-row> </a-row>
<a-button type="primary" @click="() => commandParams.push({})">添加参数</a-button> <a-button type="primary" @click="() => commandParams.push({})">添加参数</a-button>
</a-space>
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-modal> </a-modal>
@ -259,7 +271,7 @@
<a-modal <a-modal
destroyOnClose destroyOnClose
:width="'80vw'" :width="'80vw'"
v-model:visible="logVisible" v-model:open="logVisible"
title="执行日志" title="执行日志"
:footer="null" :footer="null"
:maskClosable="false" :maskClosable="false"
@ -269,13 +281,14 @@
<!-- 同步到其他工作空间 --> <!-- 同步到其他工作空间 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model="syncToWorkspaceVisible" :confirmLoading="confirmLoading"
v-model:open="syncToWorkspaceVisible"
title="同步到其他工作空间" title="同步到其他工作空间"
@ok="handleSyncToWorkspace" @ok="handleSyncToWorkspace"
:maskClosable="false" :maskClosable="false"
> >
<a-alert message="温馨提示" type="warning"> <a-alert message="温馨提示" type="warning" show-icon>
<template #description> <template v-slot:description>
<ul> <ul>
<li>同步机制采用<b>脚本名称</b>确定是同一个脚本</li> <li>同步机制采用<b>脚本名称</b>确定是同一个脚本</li>
<li>当目标工作空间不存在对应的 脚本 时候将自动创建一个新的 脚本</li> <li>当目标工作空间不存在对应的 脚本 时候将自动创建一个新的 脚本</li>
@ -286,7 +299,12 @@
<a-form :model="temp" :label-col="{ span: 6 }" :wrapper-col="{ span: 14 }"> <a-form :model="temp" :label-col="{ span: 6 }" :wrapper-col="{ span: 14 }">
<a-form-item> </a-form-item> <a-form-item> </a-form-item>
<a-form-item label="选择工作空间" name="workspaceId"> <a-form-item label="选择工作空间" name="workspaceId">
<a-select show-search option-filter-prop="children" v-model="temp.workspaceId" placeholder="请选择工作空间"> <a-select
show-search
option-filter-prop="children"
v-model:value="temp.workspaceId"
placeholder="请选择工作空间"
>
<a-select-option :disabled="getWorkspaceId() === item.id" v-for="item in workspaceList" :key="item.id">{{ <a-select-option :disabled="getWorkspaceId() === item.id" v-for="item in workspaceList" :key="item.id">{{
item.name item.name
}}</a-select-option> }}</a-select-option>
@ -298,7 +316,7 @@
<!-- 触发器 --> <!-- 触发器 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model:visible="triggerVisible" v-model:open="triggerVisible"
title="触发器" title="触发器"
width="50%" width="50%"
:footer="null" :footer="null"
@ -306,15 +324,15 @@
> >
<a-form ref="editTriggerForm" :rules="rules" :model="temp" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }"> <a-form ref="editTriggerForm" :rules="rules" :model="temp" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
<a-tabs default-active-key="1"> <a-tabs default-active-key="1">
<template #tabBarExtraContent> <template v-slot:tabBarExtraContent>
<a-tooltip title="重置触发器 token 信息,重置后之前的触发器 token 将失效"> <a-tooltip title="重置触发器 token 信息,重置后之前的触发器 token 将失效">
<a-button type="primary" size="small" @click="resetTrigger">重置</a-button> <a-button type="primary" size="small" @click="resetTrigger">重置</a-button>
</a-tooltip> </a-tooltip>
</template> </template>
<a-tab-pane key="1" tab="执行"> <a-tab-pane key="1" tab="执行">
<a-space style="display: block" direction="vertical" align="baseline"> <a-space direction="vertical" style="width: 100%">
<a-alert message="温馨提示" type="warning"> <a-alert message="温馨提示" type="warning">
<template #description> <template v-slot:description>
<ul> <ul>
<li>单个触发器地址中第一个随机字符串为命令脚本ID第二个随机字符串为 token</li> <li>单个触发器地址中第一个随机字符串为命令脚本ID第二个随机字符串为 token</li>
<li> <li>
@ -324,44 +342,18 @@
</ul> </ul>
</template> </template>
</a-alert> </a-alert>
<a-alert <a-alert type="info" :message="`单个触发器地址(点击可以复制)`">
v-clipboard:copy="temp.triggerUrl" <template v-slot:description>
v-clipboard:success=" <a-typography-paragraph :copyable="{ tooltip: false, text: temp.triggerUrl }">
() => { <a-tag>GET</a-tag> <span>{{ temp.triggerUrl }} </span>
tempVue.prototype.$notification.success({ message: '复制成功' }) </a-typography-paragraph>
}
"
v-clipboard:error="
() => {
tempVue.prototype.$notification.error({ message: '复制失败' })
}
"
type="info"
:message="`单个触发器地址(点击可以复制)`"
>
<template #description>
<a-tag>GET</a-tag> <span>{{ temp.triggerUrl }} </span>
<a-icon type="copy" />
</template> </template>
</a-alert> </a-alert>
<a-alert <a-alert type="info" :message="`批量触发器地址(点击可以复制)`">
v-clipboard:copy="temp.batchTriggerUrl" <template v-slot:description>
v-clipboard:success=" <a-typography-paragraph :copyable="{ tooltip: false, text: temp.batchTriggerUrl }">
() => { <a-tag>POST</a-tag> <span>{{ temp.batchTriggerUrl }} </span>
tempVue.prototype.$notification.success({ message: '复制成功' }) </a-typography-paragraph>
}
"
v-clipboard:error="
() => {
tempVue.prototype.$notification.error({ message: '复制失败' })
}
"
type="info"
:message="`批量触发器地址(点击可以复制)`"
>
<template #description>
<a-tag>POST</a-tag> <span>{{ temp.batchTriggerUrl }} </span>
<a-icon type="copy" />
</template> </template>
</a-alert> </a-alert>
</a-space> </a-space>
@ -378,16 +370,16 @@ import { CHANGE_PAGE, COMPUTED_PAGINATION, CRON_DATA_SOURCE, PAGE_DEFAULT_LIST_Q
import { getSshListAll } from '@/api/ssh' import { getSshListAll } from '@/api/ssh'
import codeEditor from '@/components/codeEditor' import codeEditor from '@/components/codeEditor'
import CommandLog from './command-view-log' import CommandLog from './command-view-log'
import { mapState } from 'pinia' import { mapGetters } from 'vuex'
import { getWorkSpaceListAll } from '@/api/workspace' import { getWorkSpaceListAll } from '@/api/workspace'
// import Vue from 'vue' import { mapState } from 'pinia'
import { useAppStore } from '@/stores/app'
export default { export default {
components: { codeEditor, CommandLog }, components: { codeEditor, CommandLog },
data() { data() {
return { return {
listQuery: Object.assign({}, PAGE_DEFAULT_LIST_QUERY), listQuery: Object.assign({}, PAGE_DEFAULT_LIST_QUERY),
cronDataSource: CRON_DATA_SOURCE, CRON_DATA_SOURCE,
commandList: [], commandList: [],
loading: false, loading: false,
editCommandVisible: false, editCommandVisible: false,
@ -402,21 +394,30 @@ export default {
command: [{ required: true, message: 'Please input command', trigger: 'blur' }] command: [{ required: true, message: 'Please input command', trigger: 'blur' }]
}, },
columns: [ columns: [
{ title: '命令名称', dataIndex: 'name', ellipsis: true, width: 200, scopedSlots: { customRender: 'name' } }, {
{ title: '命令描述', dataIndex: 'desc', ellipsis: true, width: 250, scopedSlots: { customRender: 'desc' } }, title: '命令名称',
dataIndex: 'name',
ellipsis: true,
width: 200
},
{
title: '命令描述',
dataIndex: 'desc',
ellipsis: true,
width: 250
},
{ {
title: '定时执行', title: '定时执行',
dataIndex: 'autoExecCron', dataIndex: 'autoExecCron',
ellipsis: true, ellipsis: true,
width: 120, width: 120
scopedSlots: { customRender: 'autoExecCron' }
}, },
{ {
title: '创建时间', title: '创建时间',
dataIndex: 'createTimeMillis', dataIndex: 'createTimeMillis',
ellipsis: true, ellipsis: true,
sorter: true, sorter: true,
customRender: (text) => { customRender: ({ text }) => {
return parseTime(text) return parseTime(text)
}, },
width: '170px' width: '170px'
@ -427,7 +428,7 @@ export default {
width: '170px', width: '170px',
ellipsis: true, ellipsis: true,
sorter: true, sorter: true,
customRender: (text) => { customRender: ({ text }) => {
return parseTime(text) return parseTime(text)
} }
}, },
@ -435,14 +436,13 @@ export default {
title: '最后操作人', title: '最后操作人',
dataIndex: 'modifyUser', dataIndex: 'modifyUser',
width: 120, width: 120,
ellipsis: true, ellipsis: true
scopedSlots: { customRender: 'modifyUser' }
}, },
{ {
title: '操作', title: '操作',
dataIndex: 'operation', dataIndex: 'operation',
align: 'center', align: 'center',
scopedSlots: { customRender: 'operation' },
fixed: 'right', fixed: 'right',
width: '240px' width: '240px'
} }
@ -450,11 +450,12 @@ export default {
tableSelections: [], tableSelections: [],
syncToWorkspaceVisible: false, syncToWorkspaceVisible: false,
workspaceList: [], workspaceList: [],
triggerVisible: false triggerVisible: false,
confirmLoading: false
} }
}, },
computed: { computed: {
...mapGetters(['getWorkspaceId']), ...mapState(useAppStore, ['getWorkspaceId']),
pagination() { pagination() {
return COMPUTED_PAGINATION(this.listQuery) return COMPUTED_PAGINATION(this.listQuery)
}, },
@ -474,15 +475,11 @@ export default {
methods: { methods: {
// //
handleEditCommandOk() { handleEditCommandOk() {
this.$refs['editCommandForm'].validate((valid) => { this.$refs['editCommandForm'].validate().then(() => {
if (!valid) {
return false
}
this.formLoading = true
if (this.commandParams && this.commandParams.length > 0) { if (this.commandParams && this.commandParams.length > 0) {
for (let i = 0; i < this.commandParams.length; i++) { for (let i = 0; i < this.commandParams.length; i++) {
if (!this.commandParams[i].desc) { if (!this.commandParams[i].desc) {
$notification.error({ this.$notification.error({
message: '请填写第' + (i + 1) + '个参数的描述' message: '请填写第' + (i + 1) + '个参数的描述'
}) })
return false return false
@ -493,17 +490,21 @@ export default {
this.temp.defParams = '' this.temp.defParams = ''
} }
this.temp.sshIds = this.chooseSsh.join(',') this.temp.sshIds = this.chooseSsh.join(',')
editCommand(this.temp).then((res) => { this.confirmLoading = true
this.formLoading = false editCommand(this.temp)
if (res.code === 200) { .then((res) => {
$notification.success({ if (res.code === 200) {
message: res.msg this.$notification.success({
}) message: res.msg
this.editCommandVisible = false })
this.editCommandVisible = false
this.getCommandData() this.getCommandData()
} }
}) })
.finally(() => {
this.confirmLoading = false
})
}) })
}, },
// //
@ -561,8 +562,9 @@ export default {
}, },
// //
handleDelete(row) { handleDelete(row) {
$confirm({ this.$confirm({
title: '系统提示', title: '系统提示',
zIndex: 1009,
content: '真的要删除“' + row.name + '”命令?', content: '真的要删除“' + row.name + '”命令?',
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',
@ -570,7 +572,7 @@ export default {
// //
deleteCommand(row.id).then((res) => { deleteCommand(row.id).then((res) => {
if (res.code === 200) { if (res.code === 200) {
$notification.success({ this.$notification.success({
message: res.msg message: res.msg
}) })
this.getCommandData() this.getCommandData()
@ -588,29 +590,33 @@ export default {
handleExecuteCommandOk() { handleExecuteCommandOk() {
if (!this.chooseSsh || this.chooseSsh.length <= 0) { if (!this.chooseSsh || this.chooseSsh.length <= 0) {
$notification.error({ this.$notification.error({
message: '请选择执行节点' message: '请选择执行节点'
}) })
return false return false
} }
this.confirmLoading = true
executeBatch({ executeBatch({
id: this.temp.id, id: this.temp.id,
params: JSON.stringify(this.commandParams), params: JSON.stringify(this.commandParams),
nodes: this.chooseSsh.join(',') nodes: this.chooseSsh.join(',')
}).then((res) => {
if (res.code === 200) {
$notification.success({
message: res.msg
})
this.executeCommandVisible = false
this.temp = {
commandId: this.temp.id,
batchId: res.data
}
this.logVisible = true
}
}) })
.then((res) => {
if (res.code === 200) {
this.$notification.success({
message: res.msg
})
this.executeCommandVisible = false
this.temp = {
commandId: this.temp.id,
batchId: res.data
}
this.logVisible = true
}
})
.finally(() => {
this.confirmLoading = false
})
}, },
// //
loadWorkSpaceListAll() { loadWorkSpaceListAll() {
@ -631,25 +637,30 @@ export default {
// //
handleSyncToWorkspace() { handleSyncToWorkspace() {
if (!this.temp.workspaceId) { if (!this.temp.workspaceId) {
$notification.warn({ this.$notification.warn({
message: '请选择工作空间' message: '请选择工作空间'
}) })
return false return false
} }
// //
this.confirmLoading = true
syncToWorkspace({ syncToWorkspace({
ids: this.tableSelections.join(','), ids: this.tableSelections.join(','),
toWorkspaceId: this.temp.workspaceId toWorkspaceId: this.temp.workspaceId
}).then((res) => {
if (res.code === 200) {
$notification.success({
message: res.msg
})
this.tableSelections = []
this.syncToWorkspaceVisible = false
return false
}
}) })
.then((res) => {
if (res.code === 200) {
this.$notification.success({
message: res.msg
})
this.tableSelections = []
this.syncToWorkspaceVisible = false
return false
}
})
.finally(() => {
this.confirmLoading = false
})
}, },
// //
handleTrigger(record) { handleTrigger(record) {
@ -671,7 +682,7 @@ export default {
rest: 'rest' rest: 'rest'
}).then((res) => { }).then((res) => {
if (res.code === 200) { if (res.code === 200) {
$notification.success({ this.$notification.success({
message: res.msg message: res.msg
}) })
this.fillTriggerResult(res) this.fillTriggerResult(res)
@ -687,6 +698,7 @@ export default {
} }
} }
</script> </script>
<style scoped> <style scoped>
.config-editor { .config-editor {
overflow-y: scroll; overflow-y: scroll;

View File

@ -10,17 +10,20 @@
<template #title> <template #title>
<template v-if="sshData"> <template v-if="sshData">
<a-space> <a-space>
<div>{{ sshData.name }} ({{ sshData.host }})</div> <div>
{{ sshData.name }}
<template v-if="sshData.host">({{ sshData.host }})</template>
</div>
<a-button size="small" type="primary" :disabled="!sshData.fileDirs" @click="handleFile()">文件</a-button> <a-button size="small" type="primary" :disabled="!sshData.fileDirs" @click="handleFile()">文件</a-button>
</a-space> </a-space>
</template> </template>
<template v-else>loading</template> <template v-else>loading</template>
</template> </template>
<template #extra> <template v-slot:extra>
<a href="#"></a> <a href="#"></a>
</template> </template>
<terminal v-if="sshData" :sshId="sshData.id" /> <terminal1 v-if="sshData" :sshId="sshData.id" />
<template v-else> <template v-else>
<a-result status="404" title="不能操作" sub-title="没有对应的SSH"> <a-result status="404" title="不能操作" sub-title="没有对应的SSH">
<template #extra> <template #extra>
@ -33,27 +36,23 @@
</a-card> </a-card>
</a-spin> </a-spin>
<!-- 文件管理 --> <!-- 文件管理 -->
<a-drawer <a-drawer destroyOnClose v-if="sshData" placement="right" width="90vw" :open="drawerVisible" @close="onClose">
destroyOnClose <template #title>
v-if="sshData" {{ sshData.name }}<template v-if="sshData.host"> ({{ sshData.host }}) </template>文件管理
:title="`${sshData.name} (${sshData.host}) 文件管理`" </template>
placement="right" <ssh-file v-if="drawerVisible" :sshId="sshData.id" />
width="90vw"
:visible="drawerVisible"
@close="onClose"
>
<ssh-file v-if="drawerVisible" :ssh="sshData" />
</a-drawer> </a-drawer>
</div> </div>
</template> </template>
<script> <script>
import terminal from './terminal' import terminal1 from './terminal'
import { getItem } from '@/api/ssh' import { getItem } from '@/api/ssh'
import SshFile from '@/pages/ssh/ssh-file' import SshFile from '@/pages/ssh/ssh-file'
export default { export default {
components: { components: {
terminal, terminal1,
SshFile SshFile
}, },
@ -72,7 +71,7 @@ export default {
this.loadItemData() this.loadItemData()
} }
}, },
beforeDestroy() {}, beforeUnmount() {},
methods: { methods: {
loadItemData() { loadItemData() {
getItem({ getItem({

View File

@ -0,0 +1,208 @@
<template>
<a-layout style="padding: 5px 0; background: #fff">
<a-layout-sider
width="200"
:style="{
background: '#fff',
height: `calc(100vh - 10px)`,
borderRight: '1px solid #e8e8e8',
overflowX: 'scroll'
}"
>
<a-directory-tree
v-if="treeList.length"
multiple
default-expand-all
:treeData="treeList"
:fieldNames="replaceFields"
@select="select"
>
</a-directory-tree>
<a-empty v-else></a-empty>
</a-layout-sider>
<a-layout-content :style="{ padding: '0 5px', height: `calc(100vh - 10px)` }">
<a-tabs
v-if="selectPanes.length"
v-model:value="activeKey"
type="editable-card"
hide-add
@edit="onEdit"
@change="change"
>
<template v-slot:rightExtra>
<a-button type="primary" :disabled="!activeKey" @click="changeFileVisible(activeKey, true)">
文件管理
</a-button>
</template>
<a-tab-pane
v-for="pane in selectPanes"
:key="pane.id"
:tab="pane.name"
:closable="true"
:ref="`pene-${pane.id}`"
>
<div :id="`paneDom${pane.id}`">
<div v-if="pane.open" :style="{ height: `calc(100vh - 70px) ` }">
<terminal1 :sshId="pane.id" />
</div>
<a-result v-else status="warning" title="未开启当前终端">
<template #extra>
<a-button type="primary" @click="open(pane.id)"> 打开终端 </a-button>
</template>
</a-result>
<!-- 文件管理 -->
<a-drawer
v-if="pane.openFile"
:getContainer="`#paneDom${pane.id}`"
:title="`${pane.name}文件管理`"
placement="right"
width="90vw"
:visible="pane.fileVisible"
@close="changeFileVisible(pane.id, false)"
>
<ssh-file v-if="pane.openFile" :sshId="pane.id" />
</a-drawer>
</div>
</a-tab-pane>
</a-tabs>
<a-empty v-else description="未选择ssh"></a-empty>
</a-layout-content>
</a-layout>
</template>
<script>
import { getSshListTree } from '@/api/ssh'
import terminal1 from './terminal'
import SshFile from '@/pages/ssh/ssh-file'
export default {
components: {
terminal1,
SshFile
},
data() {
return {
activeKey: '',
selectPanes: [],
treeList: [],
replaceFields: {
children: 'children',
title: 'name',
key: 'id'
}
}
},
computed: {},
created() {
this.listData()
},
methods: {
findItemById(list, id) {
// 使find
let res = list.find((item) => item.id == id)
if (res) {
return res
} else {
for (let i = 0; i < list.length; i++) {
if (list[i].children instanceof Array && list[i].children.length > 0) {
res = this.findItemById(list[i].children, id)
if (res) return res
}
}
return null
}
},
//
listData() {
getSshListTree().then((res) => {
if (res.code == 200 && res.data) {
this.treeList = res.data.children || []
try {
const cache = JSON.parse(localStorage.getItem('ssh-tabs-cache') || '{}')
const cacheIds = (cache.selectPanes || []).map((item) => item.id)
this.selectPanes =
cacheIds
.map((item) => {
return this.findItemById(this.treeList, item)
})
.filter((item) => item)
.map((item) => {
//
item.open = false
return item
}) || []
const activeKey = this.selectPanes.find((item) => item.id === cache.activeKey)
if (activeKey) {
this.activeKey = activeKey.id
} else if (this.selectPanes.length) {
this.activeKey = this.selectPanes[0].id
}
} catch (e) {
console.error(e)
}
}
})
},
// tabs
onEdit(targetKey, action) {
if (action === 'remove') {
this.selectPanes = this.selectPanes.filter((pane) => pane.id !== targetKey)
if (this.activeKey === targetKey) {
this.activeKey = this.selectPanes[0] && this.selectPanes[0].id
}
this.cache()
}
},
//
change() {
this.cache()
},
open(activeKey) {
this.selectPanes = this.selectPanes.map((item) => {
if (item.id === activeKey) {
item.open = true
}
return item
})
},
select(selectedKeys, { node }) {
if (!node.dataRef.isLeaf) {
return
}
const findPane = this.selectPanes.find((item) => item.id === node.dataRef.id)
if (findPane) {
this.activeKey = findPane.id
} else {
const data = { ...node.dataRef, open: true }
this.selectPanes.push(data)
this.activeKey = node.dataRef.id
}
this.cache()
},
cache() {
localStorage.setItem(
'ssh-tabs-cache',
JSON.stringify({
activeKey: this.activeKey,
selectPanes: this.selectPanes
})
)
},
//
changeFileVisible(activeKey, value) {
this.selectPanes = this.selectPanes.map((item) => {
if (item.id === activeKey) {
item.fileVisible = value
if (value && !item.openFile) {
item.openFile = true
}
}
return item
})
}
}
}
</script>

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="full-content"> <div>
<template v-if="useSuggestions"> <template v-if="this.useSuggestions">
<a-result <a-result
title="当前工作空间还没有SSH" title="当前工作空间还没有SSH"
sub-title="请到【系统管理】-> 【资产管理】-> 【SSH管理】添加SSH或者将已添加的SSH授权关联、分配到此工作空间" sub-title="请到【系统管理】-> 【资产管理】-> 【SSH管理】添加SSH或者将已添加的SSH授权关联、分配到此工作空间"
@ -23,19 +23,22 @@
bordered bordered
rowKey="id" rowKey="id"
:row-selection="rowSelection" :row-selection="rowSelection"
:scroll="{
x: 'max-content'
}"
> >
<template #title> <template v-slot:title>
<a-space> <a-space>
<a-input <a-input
class="search-input-item" class="search-input-item"
@pressEnter="loadData" @pressEnter="loadData"
v-model="listQuery['%name%']" v-model:value="listQuery['%name%']"
placeholder="ssh名称" placeholder="ssh名称"
/> />
<a-select <a-select
show-search show-search
option-filter-prop="children" option-filter-prop="children"
v-model="listQuery.group" v-model:value="listQuery.group"
allowClear allowClear
placeholder="分组" placeholder="分组"
class="search-input-item" class="search-input-item"
@ -49,8 +52,9 @@
<a-button type="primary" :disabled="!tableSelections || !tableSelections.length" @click="syncToWorkspaceShow" <a-button type="primary" :disabled="!tableSelections || !tableSelections.length" @click="syncToWorkspaceShow"
>工作空间同步</a-button >工作空间同步</a-button
> >
<a-button type="primary" @click="toSshTabs">管理面板</a-button>
<a-tooltip> <a-tooltip>
<template #title> <template v-slot:title>
<div> <div>
<ul> <ul>
<li>关联节点数据是异步获取有一定时间延迟</li> <li>关联节点数据是异步获取有一定时间延迟</li>
@ -59,167 +63,202 @@
</ul> </ul>
</div> </div>
</template> </template>
<question-circle-filled /> <QuestionCircleOutlined />
</a-tooltip> </a-tooltip>
</a-space> </a-space>
</template> </template>
<a-tooltip #tooltip slot-scope="text" :title="text"> {{ text }}</a-tooltip> <template #bodyCell="{ column, text, record }">
<a-tooltip <template v-if="column.tooltip">
#host <a-tooltip :title="text"> {{ text }}</a-tooltip>
slot-scope="text, record"
:title="`${record.machineSsh && record.machineSsh.host}:${record.machineSsh && record.machineSsh.port}`"
>
{{ record.machineSsh && record.machineSsh.host }}:{{ record.machineSsh && record.machineSsh.port }}
</a-tooltip>
<template #status slot-scope="text, record">
<a-tooltip :title="record.machineSsh && record.machineSsh.statusMsg">
<a-tag :color="record.machineSsh && record.machineSsh.status === 1 ? 'green' : 'red'">{{
record.machineSsh && record.machineSsh.status === 1 ? '正常' : '无法连接'
}}</a-tag>
</a-tooltip>
</template>
<a-popover title="系统信息" #osName slot-scope="text, record">
<template #content>
<p>系统名{{ record.machineSsh && record.machineSsh.osName }}</p>
<p>系统版本{{ record.machineSsh && record.machineSsh.osVersion }}</p>
<p>CPU型号{{ record.machineSsh && record.machineSsh.osCpuIdentifierName }}</p>
<p>主机名{{ record.machineSsh && record.machineSsh.hostName }}</p>
<p>开机时间{{ formatDuration(record.machineSsh && record.machineSsh.osSystemUptime) }}</p>
</template> </template>
{{ text || '未知' }} <template v-else-if="column.dataIndex === 'host'">
</a-popover> <a-tooltip
<a-tooltip :title="`${record.machineSsh && record.machineSsh.host}:${record.machineSsh && record.machineSsh.port}`"
#osOccupyMemory >
slot-scope="text, record" {{ record.machineSsh && record.machineSsh.host }}:{{ record.machineSsh && record.machineSsh.port }}
placement="topLeft"
:title="`内存使用率:${formatPercent(
record.machineSsh && record.machineSsh.osOccupyMemory
)},总内存${renderSize(record.machineSsh && record.machineSsh.osMoneyTotal)}`"
>
<span
>{{ formatPercent(record.machineSsh && record.machineSsh.osOccupyMemory) }}/{{
renderSize(record.machineSsh && record.machineSsh.osMoneyTotal)
}}</span
>
</a-tooltip>
<a-tooltip
#osOccupyCpu
slot-scope="text, record"
placement="topLeft"
:title="`CPU使用率${formatPercent2Number(record.machineSsh && record.machineSsh.osOccupyCpu)}%,CPU数${
record.machineSsh && record.machineSsh.osCpuCores
}`"
>
<span
>{{ (formatPercent2Number(record.machineSsh && record.machineSsh.osOccupyCpu) || '-') + '%' }} /
{{ record.machineSsh && record.machineSsh.osCpuCores }}</span
>
</a-tooltip>
<a-popover title="硬盘信息" #osMaxOccupyDisk slot-scope="text, record">
<template #content>
<p>硬盘总量{{ renderSize(record.machineSsh && record.machineSsh.osMoneyTotal) }}</p>
<p>硬盘最大的使用率{{ formatPercent(record.machineSsh && record.machineSsh.osMaxOccupyDisk) }}</p>
<p>使用率最大的分区{{ record.machineSsh && record.machineSsh.osMaxOccupyDiskName }}</p>
</template>
<span
>{{ formatPercent(record.machineSsh && record.machineSsh.osMaxOccupyDisk) }} /
{{ renderSize(record.machineSsh && record.machineSsh.osMoneyTotal) }}</span
>
</a-popover>
<template #nodeId slot-scope="text, record">
<template v-if="record.linkNode">
<a-tooltip placement="topLeft" :title="`节点名称:${record.linkNode.name}`">
<a-button
size="small"
style="width: 90px; padding: 0 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis"
type=""
@click="toNode(record.linkNode)"
>
{{ record.linkNode.name }}
</a-button>
</a-tooltip> </a-tooltip>
</template> </template>
<template v-else>-</template> <template v-else-if="column.dataIndex instanceof Array && column.dataIndex.includes('status')">
</template> <a-tooltip :title="record.machineSsh && record.machineSsh.statusMsg">
<template #operation slot-scope="text, record"> <a-tag
<a-space> :color="
<a-dropdown> statusMap[record.machineSsh && record.machineSsh.status] &&
<a-button size="small" type="primary" @click="handleTerminal(record, false)"> statusMap[record.machineSsh && record.machineSsh.status].color
终端<down-outlined /> "
</a-button>
<template #overlay>
<a-menu>
<a-menu-item key="1">
<a-button size="small" type="primary" icon="fullscreen" @click="handleTerminal(record, true)"
>全屏终端</a-button
>
</a-menu-item>
<a-menu-item key="2">
<router-link
target="_blank"
:to="{ path: '/full-terminal', query: { id: record.id, wid: getWorkspaceId() } }"
>
<a-button size="small" type="primary" icon="fullscreen"> 新标签终端</a-button>
</router-link>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<template v-if="record.fileDirs">
<a-button size="small" type="primary" @click="handleFile(record)">文件</a-button>
</template>
<template v-else>
<a-tooltip
placement="topLeft"
title="如果按钮不可用,请去资产管理 ssh 列表的关联中添加当前工作空间允许管理的授权文件夹"
> >
<a-button size="small" type="primary" :disabled="true">文件</a-button> {{
(statusMap[record.machineSsh && record.machineSsh.status] &&
statusMap[record.machineSsh && record.machineSsh.status].desc) ||
'未知'
}}
</a-tag>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex instanceof Array && column.dataIndex.includes('osName')">
<a-popover title="系统信息">
<template v-slot:content>
<p>系统名{{ record.machineSsh && record.machineSsh.osName }}</p>
<p>系统版本{{ record.machineSsh && record.machineSsh.osVersion }}</p>
<p>CPU型号{{ record.machineSsh && record.machineSsh.osCpuIdentifierName }}</p>
<p>主机名{{ record.machineSsh && record.machineSsh.hostName }}</p>
<p>开机时间{{ formatDuration(record.machineSsh && record.machineSsh.osSystemUptime) }}</p>
</template>
{{ text || '未知' }}
</a-popover>
</template>
<template v-else-if="column.dataIndex instanceof Array && column.dataIndex.includes('osOccupyMemory')">
<a-tooltip
placement="topLeft"
:title="`内存使用率:${formatPercent(
record.machineSsh && record.machineSsh.osOccupyMemory
)},总内存${renderSize(record.machineSsh && record.machineSsh.osMoneyTotal)}`"
>
<span
>{{ formatPercent(record.machineSsh && record.machineSsh.osOccupyMemory) }}/{{
renderSize(record.machineSsh && record.machineSsh.osMoneyTotal)
}}</span
>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex instanceof Array && column.dataIndex.includes('osOccupyCpu')">
<a-tooltip
placement="topLeft"
:title="`CPU使用率${formatPercent2Number(record.machineSsh && record.machineSsh.osOccupyCpu)}%,CPU数${
record.machineSsh && record.machineSsh.osCpuCores
}`"
>
<span
>{{ (formatPercent2Number(record.machineSsh && record.machineSsh.osOccupyCpu) || '-') + '%' }} /
{{ record.machineSsh && record.machineSsh.osCpuCores }}</span
>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex instanceof Array && column.dataIndex.includes('osMaxOccupyDisk')">
<a-popover title="硬盘信息">
<template v-slot:content>
<p>内存总量{{ renderSize(record.machineSsh && record.machineSsh.osMoneyTotal) }}</p>
<p>硬盘最大的使用率{{ formatPercent(record.machineSsh && record.machineSsh.osMaxOccupyDisk) }}</p>
<p>使用率最大的分区{{ record.machineSsh && record.machineSsh.osMaxOccupyDiskName }}</p>
</template>
<span
>{{ formatPercent(record.machineSsh && record.machineSsh.osMaxOccupyDisk) }}
/
{{ renderSize(record.machineSsh && record.machineSsh.osMoneyTotal) }}</span
>
</a-popover>
</template>
<template v-else-if="column.dataIndex === 'nodeId'">
<template v-if="record.linkNode">
<a-tooltip placement="topLeft" :title="`节点名称:${record.linkNode.name}`">
<a-button
size="small"
style="width: 90px; padding: 0 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis"
type="link"
@click="toNode(record.linkNode)"
>
{{ record.linkNode.name }}
</a-button>
</a-tooltip> </a-tooltip>
</template> </template>
<template v-else>-</template>
<a-dropdown> </template>
<a class="ant-dropdown-link" @click="(e) => e.preventDefault()"> <template v-else-if="column.dataIndex === 'operation'">
更多 <a-space>
<down-outlined /> <a-dropdown>
</a> <a-button size="small" type="primary" @click="handleTerminal(record, false)"
<template #overlay> >终端<DownOutlined
<a-menu> /></a-button>
<a-menu-item> <template v-slot:overlay>
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button> <a-menu>
</a-menu-item> <a-menu-item key="1">
<a-menu-item> <a-button size="small" type="primary" @click="handleTerminal(record, true)"
<a-button size="small" type="danger" @click="handleDelete(record)">删除</a-button> ><FullscreenOutlined />全屏终端</a-button
</a-menu-item> >
<a-menu-item> </a-menu-item>
<a-button size="small" type="primary" @click="handleViewLog(record)">终端日志</a-button> <a-menu-item key="2">
</a-menu-item> <router-link
</a-menu> target="_blank"
:to="{
path: '/full-terminal',
query: { id: record.id, wid: getWorkspaceId() }
}"
>
<a-button size="small" type="primary"> <FullscreenOutlined />新标签终端</a-button>
</router-link>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<template v-if="record.fileDirs">
<a-button size="small" type="primary" @click="handleFile(record)">文件</a-button>
</template> </template>
</a-dropdown> <template v-else>
</a-space> <a-tooltip
placement="topLeft"
title="如果按钮不可用,请去资产管理 ssh 列表的关联中添加当前工作空间允许管理的授权文件夹"
>
<a-button size="small" type="primary" :disabled="true">文件</a-button>
</a-tooltip>
</template>
<a-dropdown>
<a @click="(e) => e.preventDefault()">
更多
<DownOutlined />
</a>
<template v-slot:overlay>
<a-menu>
<a-menu-item>
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
</a-menu-item>
<a-menu-item>
<a-button size="small" type="primary" danger @click="handleDelete(record)">删除</a-button>
</a-menu-item>
<a-menu-item>
<a-button size="small" type="primary" @click="handleViewLog(record)">终端日志</a-button>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-space>
</template>
</template> </template>
</a-table> </a-table>
<!-- 编辑区 --> <!-- 编辑区 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model="editSshVisible" v-model:open="editSshVisible"
width="600px" width="600px"
title="编辑 SSH" title="编辑 SSH"
@ok="handleEditSshOk" @ok="handleEditSshOk"
:maskClosable="false" :maskClosable="false"
:confirmLoading="confirmLoading"
> >
<a-form ref="editSshForm" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }"> <a-form ref="editSshForm" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }">
<template v-if="this.getUserInfo && this.getUserInfo.systemUser">
<a-alert type="info" show-icon style="width: 100%; margin-bottom: 10px">
<template v-slot:message>
<ul>
<li>此编辑仅能编辑当前 SSH 在此工作空间的名称信息</li>
<li>如果要配置 SSH 请到系统管理-> 资产管理-> SSH 管理中去配置</li>
<li>
当前 SSH 的授权目录文件目录文件后缀禁止命令需要请到 系统管理-> 资产管理-> SSH
管理-> 操作栏中->关联按钮->对应工作空间->操作栏中->配置按钮
</li>
</ul>
</template>
</a-alert>
</template>
<a-form-item label="SSH 名称" name="name"> <a-form-item label="SSH 名称" name="name">
<a-input v-model="temp.name" :maxLength="50" placeholder="SSH 名称" /> <a-input v-model:value="temp.name" :maxLength="50" placeholder="SSH 名称" />
</a-form-item> </a-form-item>
<a-form-item label="分组名称" name="group"> <a-form-item label="分组名称" name="group">
<custom-select <custom-select
v-model="temp.group" v-model:value="temp.group"
:data="groupList" :data="groupList"
suffixIcon=""
inputPlaceholder="添加分组" inputPlaceholder="添加分组"
selectPlaceholder="选择分组名" selectPlaceholder="选择分组名"
> >
@ -231,7 +270,7 @@
<!-- 文件管理 --> <!-- 文件管理 -->
<a-drawer <a-drawer
destroyOnClose destroyOnClose
:visible="drawerVisible" :open="drawerVisible"
@close=" @close="
() => { () => {
this.drawerVisible = false this.drawerVisible = false
@ -246,29 +285,31 @@
<!-- Terminal --> <!-- Terminal -->
<a-modal <a-modal
destroyOnClose destroyOnClose
:dialogStyle="{ :style="{
maxWidth: '100vw', maxWidth: '100vw',
top: this.terminalFullscreen ? 0 : false, top: this.terminalFullscreen ? 0 : false,
paddingBottom: 0 paddingBottom: 0
}" }"
:width="terminalFullscreen ? '100vw' : '80vw'" :width="this.terminalFullscreen ? '100vw' : '80vw'"
:bodyStyle="{ :bodyStyle="{
padding: '0 10px', padding: '0 5px',
paddingTop: '10px', paddingTop: '10px',
marginRight: '10px', marginRight: '10px',
height: `${this.terminalFullscreen ? 'calc(100vh - 56px)' : '70vh'}` height: `${this.terminalFullscreen ? 'calc(100vh - 80px)' : '70vh'}`
}" }"
v-model="terminalVisible" v-model:open="terminalVisible"
:title="temp.name" :title="temp.name"
:footer="null" :footer="null"
:maskClosable="false" :maskClosable="false"
> >
<terminal v-if="terminalVisible" :sshId="temp.id" /> <div :style="`height: ${this.terminalFullscreen ? 'calc(100vh - 70px - 20px)' : 'calc(70vh - 20px)'}`">
<terminal1 v-if="terminalVisible" :sshId="temp.id" />
</div>
</a-modal> </a-modal>
<!-- 操作日志 --> <!-- 操作日志 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model="viewOperationLog" v-model:open="viewOperationLog"
title="操作日志" title="操作日志"
width="80vw" width="80vw"
:footer="null" :footer="null"
@ -279,13 +320,14 @@
<!-- 同步到其他工作空间 --> <!-- 同步到其他工作空间 -->
<a-modal <a-modal
destroyOnClose destroyOnClose
v-model="syncToWorkspaceVisible" v-model:open="syncToWorkspaceVisible"
title="同步到其他工作空间" title="同步到其他工作空间"
:confirmLoading="confirmLoading"
@ok="handleSyncToWorkspace" @ok="handleSyncToWorkspace"
:maskClosable="false" :maskClosable="false"
> >
<a-alert message="温馨提示" type="warning"> <a-alert message="温馨提示" type="warning" show-icon>
<template #description> <template v-slot:description>
<ul> <ul>
<li>同步机制采用 IP+PORT+连接方式 确定是同一个服务器</li> <li>同步机制采用 IP+PORT+连接方式 确定是同一个服务器</li>
<li>当目标工作空间不存在对应的 SSH 时候将自动创建一个新的 SSH</li> <li>当目标工作空间不存在对应的 SSH 时候将自动创建一个新的 SSH</li>
@ -296,7 +338,12 @@
<a-form :model="temp" :label-col="{ span: 6 }" :wrapper-col="{ span: 14 }"> <a-form :model="temp" :label-col="{ span: 6 }" :wrapper-col="{ span: 14 }">
<a-form-item> </a-form-item> <a-form-item> </a-form-item>
<a-form-item label="选择工作空间" name="workspaceId"> <a-form-item label="选择工作空间" name="workspaceId">
<a-select show-search option-filter-prop="children" v-model="temp.workspaceId" placeholder="请选择工作空间"> <a-select
show-search
option-filter-prop="children"
v-model:value="temp.workspaceId"
placeholder="请选择工作空间"
>
<a-select-option :disabled="getWorkspaceId() === item.id" v-for="item in workspaceList" :key="item.id">{{ <a-select-option :disabled="getWorkspaceId() === item.id" v-for="item in workspaceList" :key="item.id">{{
item.name item.name
}}</a-select-option> }}</a-select-option>
@ -306,10 +353,12 @@
</a-modal> </a-modal>
</div> </div>
</template> </template>
<script> <script>
import { deleteSsh, editSsh, getSshList, syncToWorkspace, getSshGroupAll } from '@/api/ssh' import { deleteSsh, editSsh, getSshList, syncToWorkspace, getSshGroupAll } from '@/api/ssh'
import { statusMap } from '@/api/system/assets-ssh'
import SshFile from '@/pages/ssh/ssh-file' import SshFile from '@/pages/ssh/ssh-file'
import Terminal from '@/pages/ssh/terminal' import Terminal1 from '@/pages/ssh/terminal'
import { import {
CHANGE_PAGE, CHANGE_PAGE,
COMPUTED_PAGINATION, COMPUTED_PAGINATION,
@ -323,13 +372,15 @@ import {
import { getWorkSpaceListAll } from '@/api/workspace' import { getWorkSpaceListAll } from '@/api/workspace'
import { mapState } from 'pinia' import { mapState } from 'pinia'
import { useUserStore } from '@/stores/user'
import { useAppStore } from '@/stores/app'
import OperationLog from '@/pages/system/assets/ssh/operation-log' import OperationLog from '@/pages/system/assets/ssh/operation-log'
import CustomSelect from '@/components/customSelect' import CustomSelect from '@/components/customSelect'
export default { export default {
components: { components: {
SshFile, SshFile,
Terminal, Terminal1,
OperationLog, OperationLog,
CustomSelect CustomSelect
}, },
@ -346,9 +397,7 @@ export default {
workspaceList: [], workspaceList: [],
tempNode: {}, tempNode: {},
// fileList: [], // fileList: [],
sshAgentInfo: {}, statusMap,
agentData: {},
formLoading: false,
drawerVisible: false, drawerVisible: false,
terminalVisible: false, terminalVisible: false,
@ -362,50 +411,47 @@ export default {
sorter: true, sorter: true,
width: 100, width: 100,
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'tooltip' } tooltip: true
}, },
{ {
title: 'Host', title: 'Host',
dataIndex: 'machineSsh.host', dataIndex: ['machineSsh', 'host'],
width: 100, width: 100,
ellipsis: true, ellipsis: true
scopedSlots: { customRender: 'host' }
}, },
// { title: "Port", dataIndex: "machineSsh.port", sorter: true, width: 80, ellipsis: true, scopedSlots: { customRender: "tooltip" } }, // { title: "Port", dataIndex: "machineSsh.port", sorter: true, width: 80, ellipsis: true, scopedSlots: { customRender: "tooltip" } },
{ {
title: '用户名', title: '用户名',
dataIndex: 'machineSsh.user', dataIndex: ['machineSsh', 'user'],
width: '100px', width: '100px',
ellipsis: true, ellipsis: true
scopedSlots: { customRender: 'tooltip' }
}, },
{ {
title: '系统名', title: '系统名',
dataIndex: 'machineSsh.osName', dataIndex: ['machineSsh', 'osName'],
width: 80, width: 80,
ellipsis: true, ellipsis: true
scopedSlots: { customRender: 'osName' }
}, },
// { title: "", dataIndex: "machineSsh.osVersion", sorter: true, ellipsis: true, scopedSlots: { customRender: "tooltip" } }, // { title: "", dataIndex: "machineSsh.osVersion", sorter: true, ellipsis: true, scopedSlots: { customRender: "tooltip" } },
{ {
title: 'CPU', title: 'CPU',
dataIndex: 'machineSsh.osOccupyCpu', dataIndex: ['machineSsh', 'osOccupyCpu'],
width: 80, width: 80,
ellipsis: true, ellipsis: true
scopedSlots: { customRender: 'osOccupyCpu' } // scopedSlots: { customRender: 'osOccupyCpu' }
}, },
{ {
title: '内存', title: '内存',
dataIndex: 'machineSsh.osOccupyMemory', dataIndex: ['machineSsh', 'osOccupyMemory'],
width: 80, width: 80,
ellipsis: true, ellipsis: true
scopedSlots: { customRender: 'osOccupyMemory' } // scopedSlots: { customRender: 'osOccupyMemory' }
}, },
{ {
title: '硬盘', title: '硬盘',
dataIndex: 'machineSsh.osMaxOccupyDisk', dataIndex: ['machineSsh', 'osMaxOccupyDisk'],
width: 80, width: 80,
ellipsis: true, ellipsis: true,
scopedSlots: { customRender: 'osMaxOccupyDisk' } scopedSlots: { customRender: 'osMaxOccupyDisk' }
@ -413,17 +459,16 @@ export default {
// { title: "", dataIndex: "charset", sorter: true, width: 120, ellipsis: true, scopedSlots: { customRender: "tooltip" } }, // { title: "", dataIndex: "charset", sorter: true, width: 120, ellipsis: true, scopedSlots: { customRender: "tooltip" } },
{ {
title: '连接状态', title: '连接状态',
dataIndex: 'machineSsh.status', dataIndex: ['machineSsh', 'status'],
ellipsis: true, ellipsis: true,
align: 'center', align: 'center',
width: '90px', width: '90px'
scopedSlots: { customRender: 'status' }
}, },
// { title: "", dataIndex: "machineSsh.charset", sorter: true, width: 120, ellipsis: true, scopedSlots: { customRender: "tooltip" } }, // { title: "", dataIndex: "machineSsh.charset", sorter: true, width: 120, ellipsis: true, scopedSlots: { customRender: "tooltip" } },
{ {
title: '关联节点', title: '关联节点',
dataIndex: 'nodeId', dataIndex: 'nodeId',
scopedSlots: { customRender: 'nodeId' },
width: '100px', width: '100px',
ellipsis: true ellipsis: true
}, },
@ -432,7 +477,7 @@ export default {
dataIndex: 'createTimeMillis', dataIndex: 'createTimeMillis',
ellipsis: true, ellipsis: true,
sorter: true, sorter: true,
customRender: (text) => parseTime(text), customRender: ({ text }) => parseTime(text),
width: '170px' width: '170px'
}, },
{ {
@ -440,13 +485,13 @@ export default {
dataIndex: 'modifyTimeMillis', dataIndex: 'modifyTimeMillis',
sorter: true, sorter: true,
ellipsis: true, ellipsis: true,
customRender: (text) => parseTime(text), customRender: ({ text }) => parseTime(text),
width: '170px' width: '170px'
}, },
{ {
title: '操作', title: '操作',
dataIndex: 'operation', dataIndex: 'operation',
scopedSlots: { customRender: 'operation' },
width: '200px', width: '200px',
align: 'center', align: 'center',
// ellipsis: true, // ellipsis: true,
@ -459,12 +504,13 @@ export default {
name: [{ required: true, message: '请输入 SSH 名称', trigger: 'blur' }] name: [{ required: true, message: '请输入 SSH 名称', trigger: 'blur' }]
}, },
groupList: [] groupList: [],
confirmLoading: false
} }
}, },
computed: { computed: {
...mapGetters(['getWorkspaceId', 'getUserInfo']), ...mapState(useUserStore, ['getUserInfo']),
...mapState(useAppStore, ['getWorkspaceId']),
pagination() { pagination() {
return COMPUTED_PAGINATION(this.listQuery) return COMPUTED_PAGINATION(this.listQuery)
}, },
@ -537,23 +583,25 @@ export default {
// SSH // SSH
handleEditSshOk() { handleEditSshOk() {
// //
this.$refs['editSshForm'].validate((valid) => { this.$refs['editSshForm'].validate().then(() => {
if (!valid) {
return false
}
// //
editSsh(this.temp).then((res) => { this.confirmLoading = true
if (res.code === 200) { editSsh(this.temp)
$notification.success({ .then((res) => {
message: res.msg if (res.code === 200) {
}) this.$notification.success({
//this.$refs['editSshForm'].resetFields(); message: res.msg
// this.fileList = []; })
this.editSshVisible = false //this.$refs['editSshForm'].resetFields();
this.loadData() // this.fileList = [];
this.loadGroupList() this.editSshVisible = false
} this.loadData()
}) this.loadGroupList()
}
})
.finally(() => {
this.confirmLoading = false
})
}) })
}, },
// //
@ -576,8 +624,9 @@ export default {
}, },
// //
handleDelete(record) { handleDelete(record) {
$confirm({ this.$confirm({
title: '系统提示', title: '系统提示',
zIndex: 1009,
content: '真的要删除 SSH 么?', content: '真的要删除 SSH 么?',
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',
@ -585,7 +634,7 @@ export default {
// //
deleteSsh(record.id).then((res) => { deleteSsh(record.id).then((res) => {
if (res.code === 200) { if (res.code === 200) {
$notification.success({ this.$notification.success({
message: res.msg message: res.msg
}) })
this.loadData() this.loadData()
@ -597,7 +646,6 @@ export default {
// //
toNode(node) { toNode(node) {
const newpage = this.$router.resolve({ const newpage = this.$router.resolve({
name: 'node_' + node.id,
path: '/node/list', path: '/node/list',
query: { query: {
...this.$route.query, ...this.$route.query,
@ -618,7 +666,15 @@ export default {
// }, // },
// }); // });
}, },
toSshTabs() {
const newpage = this.$router.resolve({
path: '/ssh-tabs',
query: {
wid: this.getWorkspaceId()
}
})
window.open(newpage.href, '_blank')
},
// //
changePage(pagination, filters, sorter) { changePage(pagination, filters, sorter) {
this.listQuery = CHANGE_PAGE(this.listQuery, { pagination, sorter }) this.listQuery = CHANGE_PAGE(this.listQuery, { pagination, sorter })
@ -644,27 +700,31 @@ export default {
// //
handleSyncToWorkspace() { handleSyncToWorkspace() {
if (!this.temp.workspaceId) { if (!this.temp.workspaceId) {
$notification.warn({ this.$notification.warn({
message: '请选择工作空间' message: '请选择工作空间'
}) })
return false return false
} }
// //
this.confirmLoading = true
syncToWorkspace({ syncToWorkspace({
ids: this.tableSelections.join(','), ids: this.tableSelections.join(','),
toWorkspaceId: this.temp.workspaceId toWorkspaceId: this.temp.workspaceId
}).then((res) => {
if (res.code === 200) {
$notification.success({
message: res.msg
})
this.tableSelections = []
this.syncToWorkspaceVisible = false
return false
}
}) })
.then((res) => {
if (res.code === 200) {
this.$notification.success({
message: res.msg
})
this.tableSelections = []
this.syncToWorkspaceVisible = false
return false
}
})
.finally(() => {
this.confirmLoading = false
})
} }
} }
} }
</script> </script>
<style scoped></style>

View File

@ -1,345 +0,0 @@
<template>
<div>
<!-- 数据表格 -->
<a-table
:data-source="list"
size="middle"
:loading="loading"
:columns="columns"
:pagination="pagination"
@change="
(pagination, filters, sorter) => {
this.listQuery = CHANGE_PAGE(this.listQuery, { pagination, sorter })
this.loadData()
}
"
:row-selection="rowSelection"
bordered
rowKey="id"
>
<template v-slot:title>
<a-space>
<a-space>
<a-input
allowClear
class="search-input-item"
@pressEnter="loadData"
v-model:value="listQuery['%issuerDnName%']"
placeholder="颁发者"
/>
<a-input
allowClear
class="search-input-item"
@pressEnter="loadData"
v-model:value="listQuery['%subjectDnName%']"
placeholder="主题"
/>
<a-tooltip title="按住 Ctr 或者 Alt/Option 键点击按钮快速回到第一页">
<a-button type="primary" :loading="loading" @click="loadData">搜索</a-button>
</a-tooltip>
<a-button type="primary" @click="handleAdd">导入证书</a-button>
</a-space>
</a-space>
</template>
<template v-slot:tooltip="text">
<a-tooltip placement="topLeft" :title="text">
<span>{{ text }}</span>
</a-tooltip>
</template>
<template v-slot:name="text, item">
<a-popover title="证书描述">
<template v-slot:content>
<p>描述{{ item.description }}</p>
</template>
<!-- {{ text }} -->
{{ text }}
</a-popover>
</template>
<template v-slot:fileExists="text">
<a-tag v-if="text" color="green">存在</a-tag>
<a-tag v-else color="red">丢失</a-tag>
</template>
<template v-slot:global="text">
<a-tag v-if="text === 'GLOBAL'">全局</a-tag>
<a-tag v-else>工作空间</a-tag>
</template>
</a-table>
<!-- 导入 -->
<a-modal
destroyOnClose
:zIndex="1009"
v-model:value="editCertVisible"
width="700px"
title="导入证书"
@ok="handleEditCertOk"
:maskClosable="false"
>
<a-form ref="importCertForm" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }">
<a-form-item
label="证书文件"
name="file"
help="请上传 zip 压缩包,并且包里面必须包含ca.pem、key.pem、cert.pem 三个文件"
>
<a-upload
:file-list="uploadFileList"
:remove="
() => {
uploadFileList = []
}
"
:before-upload="
(file) => {
this.uploadFileList = [file]
return false
}
"
accept=".zip"
>
<a-button><a-icon type="upload" />选择文件</a-button>
</a-upload>
</a-form-item>
</a-form>
</a-modal>
<div style="padding: 40px">
<div
:style="{
position: 'absolute',
right: 0,
bottom: 0,
width: '100%',
borderTop: '1px solid #e9e9e9',
padding: '10px 16px',
background: '#fff',
textAlign: 'right',
zIndex: 1
}"
>
<a-space>
<a-button
@click="
() => {
this.$emit('cancel')
}
"
>
取消
</a-button>
<a-button type="primary" @click="handerConfirm"> 确定 </a-button>
</a-space>
</div>
</div>
</div>
</template>
<script>
import { dockerImportTls } from '@/api/system/assets-docker'
import { certListAll } from '@/api/tools/certificate'
import { parseTime, CHANGE_PAGE, COMPUTED_PAGINATION, PAGE_DEFAULT_LIST_QUERY } from '@/utils/const'
export default {
props: {},
data() {
return {
loading: false,
listQuery: Object.assign({}, PAGE_DEFAULT_LIST_QUERY),
list: [],
uploadFileList: [],
temp: {},
editCertVisible: false,
columns: [
{
title: '序列号 (SN)',
dataIndex: 'serialNumberStr',
ellipsis: true,
width: 150,
scopedSlots: { customRender: 'name' }
},
{
title: '证书类型',
dataIndex: 'keyType',
ellipsis: true,
width: '80px',
scopedSlots: { customRender: 'tooltip' }
},
{
title: '文件状态',
dataIndex: 'fileExists',
ellipsis: true,
scopedSlots: { customRender: 'fileExists' },
width: '80px'
},
{
title: '共享',
dataIndex: 'workspaceId',
ellipsis: true,
scopedSlots: { customRender: 'global' },
width: '90px'
},
{
title: '颁发者',
dataIndex: 'issuerDnName',
ellipsis: true,
width: 200,
scopedSlots: { customRender: 'tooltip' }
},
{
title: '主题',
dataIndex: 'subjectDnName',
ellipsis: true,
width: 150,
scopedSlots: { customRender: 'tooltip' }
},
{
title: '密钥算法',
dataIndex: 'sigAlgName',
ellipsis: true,
width: 150,
scopedSlots: { customRender: 'tooltip' }
},
{
title: '算法 OID',
dataIndex: 'sigAlgOid',
ellipsis: true,
width: 150,
scopedSlots: { customRender: 'tooltip' }
},
{
title: '生效时间',
dataIndex: 'effectiveTime',
customRender: (text) => parseTime(text),
sorter: true,
width: '160px'
},
{
title: '到期时间',
dataIndex: 'expirationTime',
sorter: true,
customRender: (text) => parseTime(text),
width: '160px'
},
{
title: '版本号',
dataIndex: 'certVersion',
ellipsis: true,
width: '80px',
scopedSlots: { customRender: 'tooltip' }
},
{
title: '创建人',
dataIndex: 'createUser',
ellipsis: true,
scopedSlots: { customRender: 'modifyUser' },
width: '120px'
},
{
title: '修改人',
dataIndex: 'modifyUser',
ellipsis: true,
scopedSlots: { customRender: 'modifyUser' },
width: '120px'
},
{
title: '创建时间',
dataIndex: 'createTimeMillis',
sorter: true,
ellipsis: true,
customRender: (text) => parseTime(text),
width: '160px'
}
],
rules: {},
tableSelections: []
}
},
computed: {
pagination() {
return COMPUTED_PAGINATION(this.listQuery)
},
rowSelection() {
return {
onChange: (selectedRowKeys) => {
this.tableSelections = selectedRowKeys
},
selectedRowKeys: this.tableSelections,
type: 'radio'
}
}
},
mounted() {
this.loadData()
},
methods: {
CHANGE_PAGE,
//
loadData(pointerEvent) {
this.loading = true
this.listQuery.page = pointerEvent?.altKey || pointerEvent?.ctrlKey ? 1 : this.listQuery.page
this.loading = true
certListAll(this.listQuery).then((res) => {
if (res.code === 200) {
this.list = res.data.result
this.listQuery.total = res.data.total
}
this.loading = false
})
},
//
handleAdd() {
this.editCertVisible = true
this.uploadFileList = []
this.$refs['importCertForm']?.resetFields()
},
// Cert
handleEditCertOk() {
//
this.$refs['importCertForm'].validate((valid) => {
if (!valid) {
return false
}
if (this.uploadFileList.length === 0) {
this.$notification.error({
message: '请选择证书文件'
})
return false
}
const formData = new FormData()
formData.append('file', this.uploadFileList[0])
//
dockerImportTls(formData).then((res) => {
if (res.code === 200) {
//
this.$notification.success({
message: res.msg
})
this.editCertVisible = false
this.loadData()
}
})
})
},
//
handerConfirm() {
if (!this.tableSelections.length) {
this.$notification.warning({
message: '请选择要使用的证书'
})
return
}
const selectData = this.list.filter((item) => {
return item.id === this.tableSelections[0]
})[0]
$emit(this, 'confirm', `${selectData.serialNumberStr}:${selectData.keyType}`)
}
},
emits: ['cancel', 'confirm']
}
</script>

View File

@ -468,9 +468,11 @@
this.certificateVisible = false this.certificateVisible = false
} }
" "
:footer-style="{ textAlign: 'right' }"
> >
<certificate <certificate
v-if="certificateVisible" v-if="certificateVisible"
ref="certificate"
@confirm=" @confirm="
(certInfo) => { (certInfo) => {
this.temp = { ...this.temp, certInfo: certInfo } this.temp = { ...this.temp, certInfo: certInfo }
@ -483,6 +485,29 @@
} }
" "
></certificate> ></certificate>
<template #footer>
<a-space>
<a-button
@click="
() => {
this.chooseVisible = 0
}
"
>
取消
</a-button>
<a-button
type="primary"
@click="
() => {
this.$refs['certificate'].handerConfirm()
}
"
>
确认
</a-button>
</a-space>
</template>
</a-drawer> </a-drawer>
</div> </div>
</template> </template>
@ -507,7 +532,7 @@ import { getWorkSpaceListAll } from '@/api/workspace'
import Console from '@/pages/docker/console' import Console from '@/pages/docker/console'
import SwarmConsole from '@/pages/docker/swarm/console.vue' import SwarmConsole from '@/pages/docker/swarm/console.vue'
import certificate from './certificate.vue' import certificate from '@/pages/certificate/list.vue'
import CustomSelect from '@/components/customSelect' import CustomSelect from '@/components/customSelect'
export default { export default {

View File

@ -134,6 +134,11 @@ const children = [
name: 'file-storage-release-task', name: 'file-storage-release-task',
component: () => import('../pages/file-manager/release-task/list.vue') component: () => import('../pages/file-manager/release-task/list.vue')
}, },
{
path: '/file-manager/static-file-storage',
name: 'static-file-storage',
component: () => import('../pages/file-manager/staticFileStorage/list.vue')
},
{ {
path: '/certificate/list', path: '/certificate/list',
name: '/certificate-list', name: '/certificate-list',
@ -160,7 +165,7 @@ const management = [
{ {
path: '/system/assets/repository-list', path: '/system/assets/repository-list',
name: 'system-global-repository', name: 'system-global-repository',
component: () => import('../pages/repository/global-repository') component: () => import('../pages/repository/global-repository.vue')
}, },
{ {
path: '/user/permission-group', path: '/user/permission-group',
@ -277,6 +282,11 @@ const router = createRouter({
name: 'full-terminal', name: 'full-terminal',
component: () => import('../pages/ssh/full-terminal.vue') component: () => import('../pages/ssh/full-terminal.vue')
}, },
{
path: '/ssh-tabs',
name: 'ssh-tabs',
component: () => import('../pages/ssh/ssh-tabs.vue')
},
{ {
path: '/*', path: '/*',
name: '404', name: '404',