升级前端剩余功能

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)
### 🐞 解决BUG、优化功能

View File

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

View File

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

View File

@ -168,5 +168,6 @@ declare module 'vue' {
UserOutlined: typeof import('@ant-design/icons-vue')['UserOutlined']
ViewPre: typeof import('./../components/logView/view-pre.vue')['default']
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]"
: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>
</a-auto-complete>
</a-form-item>
@ -1096,7 +1084,6 @@
确认
</a-button>
</a-space>
<!-- </div> -->
</template>
</a-drawer>
<!-- 选择脚本 -->
@ -1104,17 +1091,19 @@
destroyOnClose
:title="`选择脚本`"
placement="right"
:visible="chooseScriptVisible != 0"
width="50vw"
:open="chooseScriptVisible != 0"
width="70vw"
:zIndex="1009"
@close="
() => {
this.chooseScriptVisible = 0
}
"
:footer-style="{ textAlign: 'right' }"
>
<scriptPage
v-if="chooseScriptVisible"
ref="scriptPage"
:choose="this.chooseScriptVisible === 1 ? 'checkbox' : 'radio'"
:choose-val="
this.chooseScriptVisible === 1
@ -1140,13 +1129,36 @@
}
"
></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-modal
destroyOnClose
width="50vw"
v-model:value="viewScriptTemplVisible"
v-model:open="viewScriptTemplVisible"
title="构建命令示例"
:footer="null"
:maskClosable="false"
@ -1160,15 +1172,15 @@
>
<a-collapse-panel v-for="(group, index) in buildScipts" :key="`${index}`" :header="group.title">
<a-list size="small" bordered :data-source="group.children">
<template v-slot:renderItem="opt">
<template #renderItem="{ item }">
<a-list-item>
<a-space>
{{ opt.title }}
{{ item.title }}
<SwapOutlined
@click="
() => {
temp = { ...temp, script: opt.value }
temp = { ...temp, script: item.value }
viewScriptTemplVisible = false
}
"

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,5 @@
<template>
<div class="full-content">
<!-- <div ref="filter" class="filter"> -->
<!-- <a-button type="primary" @click="handleFilter">刷新</a-button> -->
<!-- </div> -->
<div>
<!-- 数据表格 -->
<a-table
size="middle"
@ -11,17 +8,19 @@
:pagination="pagination"
@change="changePage"
bordered
:rowKey="(record, index) => index"
:scroll="{
x: 'max-content'
}"
>
<template #title>
<template v-slot:title>
<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>
<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>
<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">{{
item
}}</a-select-option>
@ -37,91 +36,110 @@
</a-tooltip>
</a-space>
</template>
<a-tooltip #outGivingId slot-scope="text" placement="topLeft" :title="text">
<span>{{ text }}</span>
</a-tooltip>
<template #bodyCell="{ column, text, record, index }">
<template v-if="column.dataIndex === 'outGivingId'">
<a-tooltip placement="topLeft" :title="text">
<span>{{ text }}</span>
</a-tooltip>
</template>
<a-tooltip #nodeName slot-scope="text, record" placement="topLeft" :title="text">
<span>{{
nodeList.filter((item) => item.id === record.nodeId) &&
nodeList.filter((item) => item.id === record.nodeId)[0] &&
nodeList.filter((item) => item.id === record.nodeId)[0].name
}}</span>
</a-tooltip>
<a-tooltip #projectId slot-scope="text" placement="topLeft" :title="text">
<span>{{ text }}</span>
</a-tooltip>
<a-tooltip
#outGivingResultMsg
slot-scope="text, item"
placement="topLeft"
:title="readJsonStrField(item.result, 'msg')"
>
<span
>{{ readJsonStrField(item.result, 'code') }}-{{ readJsonStrField(item.result, 'msg') || item.result }}</span
>
</a-tooltip>
<a-tooltip
#outGivingResultTime
slot-scope="text, item"
placement="topLeft"
:title="readJsonStrField(item.result, 'upload_duration')"
>
<span>{{ readJsonStrField(item.result, 'upload_duration') }}</span>
</a-tooltip>
<a-tooltip
#outGivingResultSize
slot-scope="text, item"
placement="topLeft"
:title="readJsonStrField(item.result, 'upload_file_size')"
>
{{ readJsonStrField(item.result, 'upload_file_size') }}
</a-tooltip>
<a-tooltip
#outGivingResultMsgData
slot-scope="text, item"
placement="topLeft"
:title="`${readJsonStrField(item.result, 'data')}`"
>
<template v-if="item.fileSize"> {{ Math.floor((item.progressSize / item.fileSize) * 100) }}% </template>
{{ readJsonStrField(item.result, 'data') }}
</a-tooltip>
<a-tooltip #status slot-scope="text">
<!-- {{ dispatchStatusMap[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>
</a-tooltip>
<template #operation slot-scope="text, record">
<a-button type="primary" size="small" @click="handleDetail(record)">详情</a-button>
<template v-else-if="column.dataIndex === 'nodeName'">
<a-tooltip
placement="topLeft"
:title="
nodeList.filter((item) => item.id === record.nodeId) &&
nodeList.filter((item) => item.id === record.nodeId)[0] &&
nodeList.filter((item) => item.id === record.nodeId)[0].name
"
>
<span>{{
nodeList.filter((item) => item.id === record.nodeId) &&
nodeList.filter((item) => item.id === record.nodeId)[0] &&
nodeList.filter((item) => item.id === record.nodeId)[0].name
}}</span>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'projectId'">
<a-tooltip placement="topLeft" :title="text">
<span>{{ text }}</span>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'mode'">
<a-tooltip placement="topLeft" :title="`${dispatchMode[text] || ''} 关联数据:${record.modeData || ''}`">
<span>{{ dispatchMode[text] || '' }}</span>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'outGivingResultMsg'">
<a-tooltip placement="topLeft" :title="readJsonStrField(record.result, 'msg')">
<span
>{{ readJsonStrField(record.result, 'code') }}-{{
readJsonStrField(record.result, 'msg') || record.result
}}</span
>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'outGivingResultTime'">
<a-tooltip placement="topLeft" :title="readJsonStrField(record.result, 'upload_duration')">
<span>{{ readJsonStrField(record.result, 'upload_duration') }}</span>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'outGivingResultSize'">
<a-tooltip placement="topLeft" :title="readJsonStrField(record.result, 'upload_file_size')">
{{ readJsonStrField(record.result, 'upload_file_size') }}
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'outGivingResultMsgData'">
<a-tooltip placement="topLeft" :title="`${readJsonStrField(record.result, 'data')}`">
<template v-if="record.fileSize">
{{ Math.floor((record.progressSize / record.fileSize) * 100) }}%
</template>
{{ readJsonStrField(record.result, 'data') }}
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'status'">
<!-- {{ dispatchStatusMap[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>
<template v-else-if="column.dataIndex === 'operation'">
<a-button type="primary" size="small" @click="handleDetail(record)">详情</a-button>
</template>
</template>
</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 #renderItem slot-scope="item">
<a-list-item-meta :description="item.description">
<h4 #title>{{ item.title }}</h4>
</a-list-item-meta>
</a-list-item>
<template #renderItem="{ item }">
<a-list-item>
<a-list-item-meta :description="item.description">
<template v-slot:title>
<h4>{{ item.title }}</h4>
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
</a-modal>
</div>
</template>
<script>
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'
export default {
data() {
return {
loading: false,
dispatchMode,
loading: true,
list: [],
nodeList: [],
dispatchList: [],
@ -135,40 +153,54 @@ export default {
{
title: '分发项目 ID',
dataIndex: 'outGivingId',
ellipsis: true,
scopedSlots: { customRender: 'outGivingId' }
width: 100,
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: '分发结果',
dataIndex: 'outGivingResultMsg',
ellipsis: true,
scopedSlots: { customRender: 'outGivingResultMsg' }
width: 200
},
{
title: '分发状态消息',
dataIndex: 'outGivingResultMsgData',
ellipsis: true,
scopedSlots: { customRender: 'outGivingResultMsgData' }
width: 100
},
{
title: '分发耗时',
dataIndex: 'outGivingResultTime',
width: '120px',
scopedSlots: { customRender: 'outGivingResultTime' }
width: '120px'
},
{
title: '文件大小',
dataIndex: 'outGivingResultSize',
width: '100px',
scopedSlots: { customRender: 'outGivingResultSize' }
width: '100px'
},
{
title: '开始时间',
dataIndex: 'startTime',
customRender: (text) => {
customRender: ({ text }) => {
return parseTime(text)
},
sorter: true,
@ -178,7 +210,7 @@ export default {
title: '结束时间',
dataIndex: 'endTime',
sorter: true,
customRender: (text) => {
customRender: ({ text }) => {
return parseTime(text)
},
width: '170px'
@ -187,11 +219,17 @@ export default {
title: '操作人',
dataIndex: 'modifyUser',
ellipsis: true,
scopedSlots: { customRender: 'modifyUser' },
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>
<style scoped></style>

View File

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

View File

@ -1,7 +1,7 @@
<template>
<div>
<!-- 布局 -->
<a-layout class="file-layout node-full-content">
<a-layout class="file-layout">
<!-- 目录树 -->
<a-layout-sider theme="light" class="sider" width="25%">
<div class="dir-container">
@ -24,7 +24,7 @@
</div>
<a-directory-tree
:replace-fields="treeReplaceFields"
:fieldNames="treeReplaceFields"
@select="nodeClick"
:loadData="onTreeData"
:treeData="treeList"
@ -45,7 +45,7 @@
<a-input
placeholder="关键词,支持正则"
:style="`width: 250px`"
v-model="temp.cacheData.keyword"
v-model:value="temp.cacheData.keyword"
@pressEnter="sendSearchLog"
>
</a-input>
@ -55,7 +55,7 @@
显示前N行
<a-input-number
id="inputNumber"
v-model="temp.cacheData.beforeCount"
v-model:value="temp.cacheData.beforeCount"
:min="0"
:max="1000"
@pressEnter="sendSearchLog"
@ -65,22 +65,22 @@
显示后N行
<a-input-number
id="inputNumber"
v-model="temp.cacheData.afterCount"
v-model:value="temp.cacheData.afterCount"
:min="0"
:max="1000"
@pressEnter="sendSearchLog"
/>
</div>
<a-popover title="正则语法参考">
<template #content>
<template v-slot:content>
<ul>
<li><b>^.*\d+.*$</b> - 匹配包含数字的行</li>
<li><b>.*(a|b).*</b> - 匹配包含 a 或者 b 的行</li>
<li><b>.*(异常).*</b> - 匹配包含 异常 的行</li>
</ul>
</template>
<a-button type="link" style="padding: 0" icon="unordered-list"
><span style="margin-left: 2px">语法参考</span></a-button
<a-button type="link" style="padding: 0"
><UnorderedListOutlined /><span style="margin-left: 2px">语法参考</span></a-button
>
</a-popover>
</a-space>
@ -111,7 +111,7 @@
文件前N行
<a-input-number
id="inputNumber"
v-model="temp.cacheData.head"
v-model:value="temp.cacheData.head"
:min="0"
:max="1000"
@pressEnter="sendSearchLog"
@ -121,14 +121,14 @@
文件后N行
<a-input-number
id="inputNumber"
v-model="temp.cacheData.tail"
v-model:value="temp.cacheData.tail"
:min="0"
:max="1000"
@pressEnter="sendSearchLog"
/>
</div>
<a-popover title="搜索配置参考">
<template #content>
<template v-slot:content>
<ul>
<li><b>从尾搜索文件前0行文件后3行</b> - 在文件最后 3 行中搜索</li>
<li><b>从头搜索文件前0行文件后3行</b> - 在文件第 3 - 2147483647 行中搜索</li>
@ -139,18 +139,18 @@
<li><b>从头搜索文件前20行文件后3行</b> - 在文件第 3 - 20 行中搜索</li>
</ul>
</template>
<a-button type="link" style="padding: 0" icon="unordered-list"
><span style="margin-left: 2px">搜索参考</span></a-button
<a-button type="link" style="padding: 0"
><UnorderedListOutlined /><span style="margin-left: 2px">搜索参考</span></a-button
>
</a-popover>
</a-space>
</a-space>
</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">
<a-tab-pane forceRender v-if="nodeName[item.nodeId]" :key="`${item.nodeId},${item.projectId}`">
<template #tab>
<a-tab-pane forceRender v-if="nodeName[item.nodeId]">
<template v-slot:tab>
{{ nodeName[item.nodeId] && nodeName[item.nodeId].name }}
{{
nodeProjectList[item.nodeId] &&
@ -175,12 +175,15 @@
</a-layout>
</div>
</template>
<script>
import { getNodeListAll, getProjectListAll } from '@/api/node'
import { getFileList } from '@/api/node-project'
import { itemGroupBy } from '@/utils/const'
import { getWebSocketUrl } from '@/api/config'
import { mapState } from 'pinia'
import { useUserStore } from '@/stores/user'
import { useAppStore } from '@/stores/app'
import viewPre from '@/components/logView/view-pre'
import { updateCache } from '@/api/log-read'
@ -210,7 +213,8 @@ export default {
}
},
computed: {
...mapGetters(['getLongTermToken', 'getWorkspaceId']),
...mapState(useUserStore, ['getLongTermToken']),
...mapState(useAppStore, ['getWorkspaceId']),
selectPath() {
if (!Object.keys(this.tempNode).length) {
return ''
@ -255,7 +259,7 @@ export default {
'/socket/console',
`userId=${this.getLongTermToken}&id=${itemProjectData.id}&nodeId=${
item.nodeId
}&type=console&copyId=&workspaceId=${this.getWorkspaceId()}`
}&type=console&workspaceId=${this.getWorkspaceId()}`
)
const domId = `pre-dom-${item.nodeId},${item.projectId}`
this.socketCache = { ...this.socketCache, [domId]: {} }
@ -263,7 +267,11 @@ export default {
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
// console.log(cacheData);
// websocketserver
window.onbeforeunload = () => {
this.close()
}
},
beforeDestroy() {
Object.keys(this.socketCache).forEach((item) => {
clearInterval(this.socketCache[item].heart)
})
},
destroyed() {
Object.keys(this.socketCache).forEach((item) => {
clearInterval(this.socketCache[item].heart)
})
beforeUnmount() {
this.close()
},
methods: {
close() {
Object.keys(this.socketCache).forEach((item) => {
clearInterval(this.socketCache[item].heart)
this.socketCache[item].socket?.close()
})
},
initWebSocket(id, url) {
let socket
if (!socket || socket.readyState !== socket.OPEN || socket.readyState !== socket.CONNECTING) {
socket = new WebSocket(url)
}
const socket = new WebSocket(url)
socket.onerror = (err) => {
console.error(err)
$notification.error({
this.$notification.error({
key: 'log-read-error',
message: 'web socket 错误,请检查是否开启 ws 代理'
})
@ -307,9 +315,9 @@ export default {
socket.onclose = (err) => {
//onclose
console.error(err)
$notification.info({
this.$notification.info({
key: 'log-read-close',
message: '会话已经关闭'
message: '会话已经关闭[tail-log]'
})
clearInterval(this.socketCache[id].heart)
}
@ -377,7 +385,11 @@ export default {
nodeChange(value) {
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.loadFileData()
//
@ -392,14 +404,17 @@ export default {
if (node.dataRef.textFileEdit) {
this.tempFileNode = node.dataRef
// 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.$emit('changeTitle', this.selectFilePath)
//
this.sendSearchLog()
} else {
//
$message.error('当前文件不可读,需要配置可读文件白名单')
this.$message.error('当前文件不可读,需要配置可读文件授权')
}
}
},
@ -485,7 +500,8 @@ export default {
}
//
}
}
},
emits: ['changeTitle']
}
</script>
@ -502,13 +518,10 @@ export default {
.file-content {
height: calc(100vh - 80px);
overflow-y: auto;
/* margin: 10px 10px 0; */
padding: 0 10px;
background-color: #fff;
}
.log-filter {
/* margin-top: -22px; */
/* margin-bottom: 10px; */
padding: 0 10px 10px;
line-height: 0;
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>
<div>
<!-- 嵌套表格 -->
<a-table
:loading="childLoading"
:columns="childColumns"
size="middle"
:bordered="true"
:data-source="list"
:pagination="false"
rowKey="id_no"
<a-drawer
destroyOnClose
:title="`查看 ${name} 状态`"
placement="right"
width="85vw"
:open="true"
@close="
() => {
$emit('close')
}
"
>
<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>
<a-tabs v-model:value="tabKey" tab-position="left">
<a-tab-pane key="1" tab="状态">
<!-- 嵌套表格 -->
<a-table
:loading="childLoading"
:columns="childColumns"
size="middle"
:bordered="true"
:data-source="list"
:pagination="false"
rowKey="id_no"
: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>状态描述{{ data.statusMsg || '-' }}</div>
<a-button type="primary" :loading="childLoading" @click="loadData">刷新</a-button>
<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-tab-pane>
</a-tabs>
</a-drawer>
<!-- 项目文件组件 -->
<a-drawer
@ -134,7 +205,7 @@
:title="drawerTitle"
placement="right"
width="85vw"
:visible="drawerFileVisible"
:open="drawerFileVisible"
@close="onFileClose"
>
<file
@ -152,7 +223,7 @@
:title="drawerTitle"
placement="right"
width="85vw"
:visible="drawerConsoleVisible"
:open="drawerConsoleVisible"
@close="onConsoleClose"
>
<console
@ -169,7 +240,7 @@
:title="drawerTitle"
placement="right"
width="85vw"
:visible="drawerReadFileVisible"
:open="drawerReadFileVisible"
@close="onReadFileClose"
>
<file-read
@ -183,8 +254,15 @@
</a-drawer>
</div>
</template>
<script>
import { getDispatchProject, dispatchStatusMap, statusMap } from '@/api/dispatch'
import {
getDispatchProject,
dispatchStatusMap,
statusMap,
removeProject,
saveDispatchProjectConfig
} from '@/api/dispatch'
import { getNodeListAll } from '@/api/node'
import { getRuningProjectInfo } from '@/api/node-project'
import {
@ -199,25 +277,30 @@ import {
import File from '@/pages/node/node-layout/project/project-file'
import Console from '@/pages/node/node-layout/project/project-console'
import FileRead from '@/pages/node/node-layout/project/project-file-read'
import draggable from 'vuedraggable-es'
export default {
components: {
File,
Console,
FileRead
FileRead,
draggable
},
props: {
id: {
type: String
},
name: {
type: String
}
},
data() {
return {
loading: false,
childLoading: false,
childLoading: true,
statusMap,
dispatchStatusMap,
list: [],
tabKey: '1',
data: {},
drawerTitle: '',
drawerFileVisible: false,
@ -226,82 +309,79 @@ export default {
nodeNameMap: {},
childColumns: [
{ title: '节点名称', dataIndex: 'nodeId', width: 120, ellipsis: true, scopedSlots: { customRender: 'nodeId' } },
{
title: '节点名称',
dataIndex: 'nodeId',
width: 120,
ellipsis: true
},
{
title: '项目名称',
dataIndex: 'projectName',
width: 120,
ellipsis: true,
scopedSlots: { customRender: 'projectName' }
ellipsis: true
},
{
title: '项目状态',
dataIndex: 'projectStatus',
width: 120,
ellipsis: true,
scopedSlots: { customRender: 'projectStatus' }
ellipsis: true
},
{
title: '进程/端口',
dataIndex: 'projectPid',
width: '120px',
ellipsis: true,
scopedSlots: { customRender: 'projectPid' }
ellipsis: true
},
{
title: '分发状态',
dataIndex: 'outGivingStatus',
width: '120px',
scopedSlots: { customRender: 'outGivingStatus' }
width: '120px'
},
{
title: '分发结果',
dataIndex: 'outGivingResultMsg',
ellipsis: true,
width: 120,
scopedSlots: { customRender: 'outGivingResultMsg' }
width: 120
},
{
title: '分发状态消息',
dataIndex: 'outGivingResultMsgData',
ellipsis: true,
width: 120,
scopedSlots: { customRender: 'outGivingResultMsgData' }
width: 120
},
{
title: '分发耗时',
dataIndex: 'outGivingResultTime',
width: '120px',
scopedSlots: { customRender: 'outGivingResultTime' }
width: '120px'
},
{
title: '文件大小',
dataIndex: 'outGivingResultSize',
width: '100px',
scopedSlots: { customRender: 'outGivingResultSize' }
width: '100px'
},
{
title: '最后分发时间',
dataIndex: 'lastTime',
width: '170px',
ellipsis: true,
customRender: (text) => parseTime(text)
customRender: ({ text }) => parseTime(text)
},
{
title: '操作',
dataIndex: 'child-operation',
fixed: 'right',
scopedSlots: { customRender: 'child-operation' },
width: '140px',
align: 'center'
}
],
countdownTime: Date.now(),
refreshInterval: 5
refreshInterval: 5,
temp: {}
}
},
computed: {},
watch: {},
created() {
@ -337,6 +417,13 @@ export default {
},
//
silenceLoadData() {
if (this.tabKey !== '1') {
//
//
this.countdownTime = Date.now() + this.refreshInterval * 1000
return
}
this.childLoading = true
this.handleReloadById().then(() => {
//
this.countdownTime = Date.now() + this.refreshInterval * 1000
@ -350,7 +437,10 @@ export default {
if (res.code === 200 && res.data) {
let projectList =
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 || {}
let oldProjectList = this.list
@ -363,13 +453,14 @@ export default {
const nodeProjects = itemGroupBy(projectList, 'nodeId')
this.getRuningProjectInfo(nodeProjects)
}
this.childLoading = false
resolve()
})
.catch(() => {
resolve()
})
.finally(() => {
//
this.childLoading = false
resolve()
})
})
},
@ -419,7 +510,12 @@ export default {
} else {
this.list = this.list.map((element) => {
if (element.nodeId === data.type) {
return { ...element, projectStatus: false, projectPid: '-', errorMsg: res2.msg }
return {
...element,
projectStatus: false,
projectPid: '-',
errorMsg: res2.msg
}
}
return element
})
@ -430,7 +526,12 @@ export default {
.catch(() => {
this.list = this.list.map((element) => {
if (element.nodeId === data.type) {
return { ...element, projectStatus: false, projectPid: '-', errorMsg: '网络异常' }
return {
...element,
projectStatus: false,
projectPid: '-',
errorMsg: '网络异常'
}
}
return element
})
@ -496,25 +597,86 @@ export default {
}
})
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>
<style scoped>
/deep/ .ant-progress-text {
width: auto;
}
/* .replica-btn-del {
position: absolute;
right: 0;
top: 74px;
} */
/deep/ .ant-statistic div {
display: inline-block;
}
/deep/ .ant-statistic-content-value,
/deep/ .ant-statistic-content {
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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,17 +10,20 @@
<template #title>
<template v-if="sshData">
<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-space>
</template>
<template v-else>loading</template>
</template>
<template #extra>
<template v-slot:extra>
<a href="#"></a>
</template>
<terminal v-if="sshData" :sshId="sshData.id" />
<terminal1 v-if="sshData" :sshId="sshData.id" />
<template v-else>
<a-result status="404" title="不能操作" sub-title="没有对应的SSH">
<template #extra>
@ -33,27 +36,23 @@
</a-card>
</a-spin>
<!-- 文件管理 -->
<a-drawer
destroyOnClose
v-if="sshData"
:title="`${sshData.name} (${sshData.host}) 文件管理`"
placement="right"
width="90vw"
:visible="drawerVisible"
@close="onClose"
>
<ssh-file v-if="drawerVisible" :ssh="sshData" />
<a-drawer destroyOnClose v-if="sshData" placement="right" width="90vw" :open="drawerVisible" @close="onClose">
<template #title>
{{ sshData.name }}<template v-if="sshData.host"> ({{ sshData.host }}) </template>文件管理
</template>
<ssh-file v-if="drawerVisible" :sshId="sshData.id" />
</a-drawer>
</div>
</template>
<script>
import terminal from './terminal'
import terminal1 from './terminal'
import { getItem } from '@/api/ssh'
import SshFile from '@/pages/ssh/ssh-file'
export default {
components: {
terminal,
terminal1,
SshFile
},
@ -72,7 +71,7 @@ export default {
this.loadItemData()
}
},
beforeDestroy() {},
beforeUnmount() {},
methods: {
loadItemData() {
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>
<div class="full-content">
<template v-if="useSuggestions">
<div>
<template v-if="this.useSuggestions">
<a-result
title="当前工作空间还没有SSH"
sub-title="请到【系统管理】-> 【资产管理】-> 【SSH管理】添加SSH或者将已添加的SSH授权关联、分配到此工作空间"
@ -23,19 +23,22 @@
bordered
rowKey="id"
:row-selection="rowSelection"
:scroll="{
x: 'max-content'
}"
>
<template #title>
<template v-slot:title>
<a-space>
<a-input
class="search-input-item"
@pressEnter="loadData"
v-model="listQuery['%name%']"
v-model:value="listQuery['%name%']"
placeholder="ssh名称"
/>
<a-select
show-search
option-filter-prop="children"
v-model="listQuery.group"
v-model:value="listQuery.group"
allowClear
placeholder="分组"
class="search-input-item"
@ -49,8 +52,9 @@
<a-button type="primary" :disabled="!tableSelections || !tableSelections.length" @click="syncToWorkspaceShow"
>工作空间同步</a-button
>
<a-button type="primary" @click="toSshTabs">管理面板</a-button>
<a-tooltip>
<template #title>
<template v-slot:title>
<div>
<ul>
<li>关联节点数据是异步获取有一定时间延迟</li>
@ -59,167 +63,202 @@
</ul>
</div>
</template>
<question-circle-filled />
<QuestionCircleOutlined />
</a-tooltip>
</a-space>
</template>
<a-tooltip #tooltip slot-scope="text" :title="text"> {{ text }}</a-tooltip>
<a-tooltip
#host
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 #bodyCell="{ column, text, record }">
<template v-if="column.tooltip">
<a-tooltip :title="text"> {{ text }}</a-tooltip>
</template>
{{ text || '未知' }}
</a-popover>
<a-tooltip
#osOccupyMemory
slot-scope="text, record"
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>
<template v-else-if="column.dataIndex === 'host'">
<a-tooltip
:title="`${record.machineSsh && record.machineSsh.host}:${record.machineSsh && record.machineSsh.port}`"
>
{{ record.machineSsh && record.machineSsh.host }}:{{ record.machineSsh && record.machineSsh.port }}
</a-tooltip>
</template>
<template v-else>-</template>
</template>
<template #operation slot-scope="text, record">
<a-space>
<a-dropdown>
<a-button size="small" type="primary" @click="handleTerminal(record, false)">
终端<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 列表的关联中添加当前工作空间允许管理的授权文件夹"
<template v-else-if="column.dataIndex instanceof Array && column.dataIndex.includes('status')">
<a-tooltip :title="record.machineSsh && record.machineSsh.statusMsg">
<a-tag
:color="
statusMap[record.machineSsh && record.machineSsh.status] &&
statusMap[record.machineSsh && record.machineSsh.status].color
"
>
<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>
</template>
<a-dropdown>
<a class="ant-dropdown-link" @click="(e) => e.preventDefault()">
更多
<down-outlined />
</a>
<template #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="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 v-else>-</template>
</template>
<template v-else-if="column.dataIndex === 'operation'">
<a-space>
<a-dropdown>
<a-button size="small" type="primary" @click="handleTerminal(record, false)"
>终端<DownOutlined
/></a-button>
<template v-slot:overlay>
<a-menu>
<a-menu-item key="1">
<a-button size="small" type="primary" @click="handleTerminal(record, true)"
><FullscreenOutlined />全屏终端</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"> <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>
</a-dropdown>
</a-space>
<template v-else>
<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>
</a-table>
<!-- 编辑区 -->
<a-modal
destroyOnClose
v-model="editSshVisible"
v-model:open="editSshVisible"
width="600px"
title="编辑 SSH"
@ok="handleEditSshOk"
:maskClosable="false"
:confirmLoading="confirmLoading"
>
<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-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 label="分组名称" name="group">
<custom-select
v-model="temp.group"
v-model:value="temp.group"
:data="groupList"
suffixIcon=""
inputPlaceholder="添加分组"
selectPlaceholder="选择分组名"
>
@ -231,7 +270,7 @@
<!-- 文件管理 -->
<a-drawer
destroyOnClose
:visible="drawerVisible"
:open="drawerVisible"
@close="
() => {
this.drawerVisible = false
@ -246,29 +285,31 @@
<!-- Terminal -->
<a-modal
destroyOnClose
:dialogStyle="{
:style="{
maxWidth: '100vw',
top: this.terminalFullscreen ? 0 : false,
paddingBottom: 0
}"
:width="terminalFullscreen ? '100vw' : '80vw'"
:width="this.terminalFullscreen ? '100vw' : '80vw'"
:bodyStyle="{
padding: '0 10px',
padding: '0 5px',
paddingTop: '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"
:footer="null"
: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
destroyOnClose
v-model="viewOperationLog"
v-model:open="viewOperationLog"
title="操作日志"
width="80vw"
:footer="null"
@ -279,13 +320,14 @@
<!-- 同步到其他工作空间 -->
<a-modal
destroyOnClose
v-model="syncToWorkspaceVisible"
v-model:open="syncToWorkspaceVisible"
title="同步到其他工作空间"
:confirmLoading="confirmLoading"
@ok="handleSyncToWorkspace"
:maskClosable="false"
>
<a-alert message="温馨提示" type="warning">
<template #description>
<a-alert message="温馨提示" type="warning" show-icon>
<template v-slot:description>
<ul>
<li>同步机制采用 IP+PORT+连接方式 确定是同一个服务器</li>
<li>当目标工作空间不存在对应的 SSH 时候将自动创建一个新的 SSH</li>
@ -296,7 +338,12 @@
<a-form :model="temp" :label-col="{ span: 6 }" :wrapper-col="{ span: 14 }">
<a-form-item> </a-form-item>
<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">{{
item.name
}}</a-select-option>
@ -306,10 +353,12 @@
</a-modal>
</div>
</template>
<script>
import { deleteSsh, editSsh, getSshList, syncToWorkspace, getSshGroupAll } from '@/api/ssh'
import { statusMap } from '@/api/system/assets-ssh'
import SshFile from '@/pages/ssh/ssh-file'
import Terminal from '@/pages/ssh/terminal'
import Terminal1 from '@/pages/ssh/terminal'
import {
CHANGE_PAGE,
COMPUTED_PAGINATION,
@ -323,13 +372,15 @@ import {
import { getWorkSpaceListAll } from '@/api/workspace'
import { mapState } from 'pinia'
import { useUserStore } from '@/stores/user'
import { useAppStore } from '@/stores/app'
import OperationLog from '@/pages/system/assets/ssh/operation-log'
import CustomSelect from '@/components/customSelect'
export default {
components: {
SshFile,
Terminal,
Terminal1,
OperationLog,
CustomSelect
},
@ -346,9 +397,7 @@ export default {
workspaceList: [],
tempNode: {},
// fileList: [],
sshAgentInfo: {},
agentData: {},
formLoading: false,
statusMap,
drawerVisible: false,
terminalVisible: false,
@ -362,50 +411,47 @@ export default {
sorter: true,
width: 100,
ellipsis: true,
scopedSlots: { customRender: 'tooltip' }
tooltip: true
},
{
title: 'Host',
dataIndex: 'machineSsh.host',
dataIndex: ['machineSsh', 'host'],
width: 100,
ellipsis: true,
scopedSlots: { customRender: 'host' }
ellipsis: true
},
// { title: "Port", dataIndex: "machineSsh.port", sorter: true, width: 80, ellipsis: true, scopedSlots: { customRender: "tooltip" } },
{
title: '用户名',
dataIndex: 'machineSsh.user',
dataIndex: ['machineSsh', 'user'],
width: '100px',
ellipsis: true,
scopedSlots: { customRender: 'tooltip' }
ellipsis: true
},
{
title: '系统名',
dataIndex: 'machineSsh.osName',
dataIndex: ['machineSsh', 'osName'],
width: 80,
ellipsis: true,
scopedSlots: { customRender: 'osName' }
ellipsis: true
},
// { title: "", dataIndex: "machineSsh.osVersion", sorter: true, ellipsis: true, scopedSlots: { customRender: "tooltip" } },
{
title: 'CPU',
dataIndex: 'machineSsh.osOccupyCpu',
dataIndex: ['machineSsh', 'osOccupyCpu'],
width: 80,
ellipsis: true,
scopedSlots: { customRender: 'osOccupyCpu' }
ellipsis: true
// scopedSlots: { customRender: 'osOccupyCpu' }
},
{
title: '内存',
dataIndex: 'machineSsh.osOccupyMemory',
dataIndex: ['machineSsh', 'osOccupyMemory'],
width: 80,
ellipsis: true,
scopedSlots: { customRender: 'osOccupyMemory' }
ellipsis: true
// scopedSlots: { customRender: 'osOccupyMemory' }
},
{
title: '硬盘',
dataIndex: 'machineSsh.osMaxOccupyDisk',
dataIndex: ['machineSsh', 'osMaxOccupyDisk'],
width: 80,
ellipsis: true,
scopedSlots: { customRender: 'osMaxOccupyDisk' }
@ -413,17 +459,16 @@ export default {
// { title: "", dataIndex: "charset", sorter: true, width: 120, ellipsis: true, scopedSlots: { customRender: "tooltip" } },
{
title: '连接状态',
dataIndex: 'machineSsh.status',
dataIndex: ['machineSsh', 'status'],
ellipsis: true,
align: 'center',
width: '90px',
scopedSlots: { customRender: 'status' }
width: '90px'
},
// { title: "", dataIndex: "machineSsh.charset", sorter: true, width: 120, ellipsis: true, scopedSlots: { customRender: "tooltip" } },
{
title: '关联节点',
dataIndex: 'nodeId',
scopedSlots: { customRender: 'nodeId' },
width: '100px',
ellipsis: true
},
@ -432,7 +477,7 @@ export default {
dataIndex: 'createTimeMillis',
ellipsis: true,
sorter: true,
customRender: (text) => parseTime(text),
customRender: ({ text }) => parseTime(text),
width: '170px'
},
{
@ -440,13 +485,13 @@ export default {
dataIndex: 'modifyTimeMillis',
sorter: true,
ellipsis: true,
customRender: (text) => parseTime(text),
customRender: ({ text }) => parseTime(text),
width: '170px'
},
{
title: '操作',
dataIndex: 'operation',
scopedSlots: { customRender: 'operation' },
width: '200px',
align: 'center',
// ellipsis: true,
@ -459,12 +504,13 @@ export default {
name: [{ required: true, message: '请输入 SSH 名称', trigger: 'blur' }]
},
groupList: []
groupList: [],
confirmLoading: false
}
},
computed: {
...mapGetters(['getWorkspaceId', 'getUserInfo']),
...mapState(useUserStore, ['getUserInfo']),
...mapState(useAppStore, ['getWorkspaceId']),
pagination() {
return COMPUTED_PAGINATION(this.listQuery)
},
@ -537,23 +583,25 @@ export default {
// SSH
handleEditSshOk() {
//
this.$refs['editSshForm'].validate((valid) => {
if (!valid) {
return false
}
this.$refs['editSshForm'].validate().then(() => {
//
editSsh(this.temp).then((res) => {
if (res.code === 200) {
$notification.success({
message: res.msg
})
//this.$refs['editSshForm'].resetFields();
// this.fileList = [];
this.editSshVisible = false
this.loadData()
this.loadGroupList()
}
})
this.confirmLoading = true
editSsh(this.temp)
.then((res) => {
if (res.code === 200) {
this.$notification.success({
message: res.msg
})
//this.$refs['editSshForm'].resetFields();
// this.fileList = [];
this.editSshVisible = false
this.loadData()
this.loadGroupList()
}
})
.finally(() => {
this.confirmLoading = false
})
})
},
//
@ -576,8 +624,9 @@ export default {
},
//
handleDelete(record) {
$confirm({
this.$confirm({
title: '系统提示',
zIndex: 1009,
content: '真的要删除 SSH 么?',
okText: '确认',
cancelText: '取消',
@ -585,7 +634,7 @@ export default {
//
deleteSsh(record.id).then((res) => {
if (res.code === 200) {
$notification.success({
this.$notification.success({
message: res.msg
})
this.loadData()
@ -597,7 +646,6 @@ export default {
//
toNode(node) {
const newpage = this.$router.resolve({
name: 'node_' + node.id,
path: '/node/list',
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) {
this.listQuery = CHANGE_PAGE(this.listQuery, { pagination, sorter })
@ -644,27 +700,31 @@ export default {
//
handleSyncToWorkspace() {
if (!this.temp.workspaceId) {
$notification.warn({
this.$notification.warn({
message: '请选择工作空间'
})
return false
}
//
this.confirmLoading = true
syncToWorkspace({
ids: this.tableSelections.join(','),
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>
<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
}
"
:footer-style="{ textAlign: 'right' }"
>
<certificate
v-if="certificateVisible"
ref="certificate"
@confirm="
(certInfo) => {
this.temp = { ...this.temp, certInfo: certInfo }
@ -483,6 +485,29 @@
}
"
></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>
</div>
</template>
@ -507,7 +532,7 @@ import { getWorkSpaceListAll } from '@/api/workspace'
import Console from '@/pages/docker/console'
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'
export default {

View File

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