perf: 优化流水线页面,增加下次执行时间、查看证书显示

pull/436/head
xiaojunnuo 2025-06-07 01:19:37 +08:00
parent 2a19b61b7a
commit c820315409
8 changed files with 146 additions and 72 deletions

View File

@ -16,6 +16,7 @@
import parser from "cron-parser";
import { computed, ref } from "vue";
import dayjs from "dayjs";
import { getCronNextTimes } from "/@/components/cron-editor/utils";
defineOptions({
name: "CronEditor",
});
@ -84,10 +85,10 @@ const nextTime = computed(() => {
if (props.modelValue == null) {
return "请先设置正确的cron表达式";
}
try {
const interval = parser.parseExpression(props.modelValue);
const next = interval.next().getTime();
return dayjs(next).format("YYYY-MM-DD HH:mm:ss");
const nextTimes = getCronNextTimes(props.modelValue, 2);
return nextTimes.join("");
} catch (e) {
console.log(e);
return "请先设置正确的cron表达式";

View File

@ -0,0 +1,15 @@
import parser from "cron-parser";
import dayjs from "dayjs";
export function getCronNextTimes(cron: string, count: number = 1) {
if (cron == null) {
return [];
}
const nextTimes = [];
const interval = parser.parseExpression(cron);
for (let i = 0; i < count; i++) {
const next = interval.next().getTime();
nextTimes.push(dayjs(next).format("YYYY-MM-DD HH:mm:ss"));
}
return nextTimes;
}

View File

@ -76,7 +76,7 @@ export default {
.text-editable {
flex: 1;
line-height: 34px;
overflow: hidden;
span.fs-iconify {
display: inline-flex;
justify-content: center;

View File

@ -5,17 +5,15 @@ import { useRouter } from "vue-router";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, useUi } from "@fast-crud/fast-crud";
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
import { Modal, notification } from "ant-design-vue";
import { env } from "/@/utils/util.env";
import { useUserStore } from "/@/store/user";
import dayjs from "dayjs";
import { useSettingStore } from "/@/store/settings";
import { cloneDeep } from "lodash-es";
import { useModal } from "/@/use/use-modal";
import CertView from "./cert-view.vue";
import { eachStages } from "./utils";
import { setRunnableIds, useCertPipelineCreator } from "/@/views/certd/pipeline/certd-form/use";
import { useCertUpload } from "/@/views/certd/pipeline/cert-upload/use";
import GroupSelector from "/@/views/certd/pipeline/group/group-selector.vue";
import { useCertViewer } from "/@/views/certd/pipeline/use";
export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
@ -61,59 +59,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
return res;
};
const model = useModal();
const viewCert = async (row: any) => {
const cert = await api.GetCert(row.id);
if (!cert) {
notification.error({ message: "请先运行一次流水线" });
return;
}
model.success({
title: "查看证书",
maskClosable: true,
okText: "关闭",
width: 800,
content: () => {
return <CertView cert={cert}></CertView>;
},
});
};
const downloadCert = async (row: any) => {
const files = await api.GetFiles(row.id);
model.success({
title: "点击链接下载",
maskClosable: true,
okText: "关闭",
content: () => {
const children = [];
for (const file of files) {
const downloadUrl = `${env.API}/pi/history/download?pipelineId=${row.id}&fileId=${file.id}`;
children.push(
<div>
<div class={"flex-o m-5"}>
<fs-icon icon={"ant-design:cloud-download-outlined"} class={"mr-5 fs-16"}></fs-icon>
<a href={downloadUrl} target={"_blank"}>
{file.filename}
</a>
</div>
</div>
);
}
if (children.length === 0) {
return <div></div>;
}
return (
<div class={"mt-3"}>
<div> {children}</div>
</div>
);
},
});
};
const { viewCert, downloadCert } = useCertViewer();
const userStore = useUserStore();
const settingStore = useSettingStore();
@ -208,6 +154,10 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
},
table: {
scroll: { x: 1500 },
remove: {
confirmTitle: "确定要删除吗?",
confirmMessage: "将删除该流水线相关的所有数据,包括执行历史、证书文件、证书仓库记录等",
},
},
tabs: {
name: "groupId",
@ -281,7 +231,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
type: "link",
icon: "ph:certificate",
async click({ row }) {
await viewCert(row);
await viewCert(row.id);
},
},
download: {
@ -291,7 +241,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
tooltip: { title: "下载证书" },
icon: "ant-design:download-outlined",
async click({ row }) {
await downloadCert(row);
await downloadCert(row.id);
},
},
remove: {

View File

@ -34,6 +34,8 @@ const pipelineOptions: PipelineOptions = {
stages: [],
triggers: [],
...JSON.parse(detail.pipeline.content || "{}"),
type: detail.pipeline.type,
from: detail.pipeline.from,
},
} as PipelineDetail;
},

View File

@ -1,18 +1,28 @@
<template>
<fs-page v-if="pipeline" class="page-pipeline-edit">
<template #header>
<div class="title">
<div class="title flex-1">
<fs-button class="back" icon="ion:chevron-back-outline" @click="goBack"></fs-button>
<text-editable v-model="pipeline.title" :hover-show="false" :disabled="!editMode"></text-editable>
</div>
<div class="more">
<template v-if="editMode">
<a-button type="primary" :loading="saveLoading" @click="save"></a-button>
<a-button class="ml-5" @click="cancel"></a-button>
</template>
<template v-else>
<a-button type="primary" @click="edit"></a-button>
</template>
<div class="more flex items-center flex-1 justify-end">
<div class="flex items-center hidden md:block">
<a-tag v-if="nextTriggerTimes" color="blue">{{ nextTriggerTimes }}</a-tag>
</div>
<div class="basis-40 flex justify-end mr-10">
<template v-if="editMode">
<fs-button type="primary" :loading="saveLoading" @click="save"></fs-button>
<fs-button class="ml-5" @click="cancel"></fs-button>
</template>
<template v-else>
<fs-button icon="ant-design:edit-outlined" type="primary" @click="edit"></fs-button>
</template>
</div>
<div v-if="isCert" class="flex items-center hidden md:block">
<fs-button icon="ant-design:eye-outlined" class="mr-5" type="primary" text="查看证书" @click="viewCert(pipeline.id)"> </fs-button>
<fs-button icon="ant-design:download-outlined" class="mr-5" type="primary" text="下载证书" @click="downloadCert(pipeline.id)"> </fs-button>
</div>
</div>
</template>
@ -255,7 +265,7 @@
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, provide, Ref, ref, watch } from "vue";
import { defineComponent, onMounted, onUnmounted, provide, Ref, ref, watch, computed } from "vue";
import { useRouter } from "vue-router";
import PiTaskForm from "./component/task-form/index.vue";
import PiTriggerForm from "./component/trigger-form/index.vue";
@ -275,6 +285,8 @@ import { useUserStore } from "/@/store/user";
import TaskShortcuts from "./component/shortcut/task-shortcuts.vue";
import { eachSteps, findStep } from "../utils";
import { PluginGroups } from "/@/store/plugin";
import { getCronNextTimes } from "/@/components/cron-editor/utils";
import { useCertViewer } from "/@/views/certd/pipeline/use";
export default defineComponent({
name: "PipelineEdit",
@ -309,6 +321,22 @@ export default defineComponent({
const currentHistory: Ref<any> = ref({});
const nextTriggerTimes = computed(() => {
const triggers = pipeline.value.triggers;
if (!triggers || triggers.length === 0) {
return null;
}
let nextTimes: any = [];
for (const item of triggers) {
if (!item.props?.cron) {
continue;
}
const ret = getCronNextTimes(item.props?.cron, 1);
nextTimes.push(...ret);
}
return nextTimes.join("");
});
const router = useRouter();
function goBack() {
router.back();
@ -735,6 +763,7 @@ export default defineComponent({
}
};
const edit = () => {
debugger;
pipeline.value = _.cloneDeep(currentPipeline.value);
currentHistory.value = null;
toggleEditMode(true);
@ -838,7 +867,12 @@ export default defineComponent({
};
});
const { viewCert, downloadCert } = useCertViewer();
const isCert = computed(() => {
return currentPipeline.value?.type?.startsWith("cert");
});
return {
isCert,
pipeline,
currentHistory,
histories,
@ -852,6 +886,9 @@ export default defineComponent({
...useHistory(),
...useNotification(),
...useScroll(),
nextTriggerTimes,
viewCert,
downloadCert,
};
},
});

View File

@ -0,0 +1,65 @@
import * as api from "/@/views/certd/pipeline/api";
import { notification } from "ant-design-vue";
import CertView from "/@/views/certd/pipeline/cert-view.vue";
import { env } from "/@/utils/util.env";
import { useModal } from "/@/use/use-modal";
export function useCertViewer() {
const model = useModal();
const viewCert = async (id: number) => {
const cert = await api.GetCert(id);
if (!cert) {
notification.error({ message: "请先运行一次流水线" });
return;
}
model.success({
title: "查看证书",
maskClosable: true,
okText: "关闭",
width: 800,
content: () => {
return <CertView cert={cert}></CertView>;
},
});
};
const downloadCert = async (id: any) => {
const files = await api.GetFiles(id);
model.success({
title: "点击链接下载",
maskClosable: true,
okText: "关闭",
content: () => {
const children = [];
for (const file of files) {
const downloadUrl = `${env.API}/pi/history/download?pipelineId=${id}&fileId=${file.id}`;
children.push(
<div>
<div class={"flex-o m-5"}>
<fs-icon icon={"ant-design:cloud-download-outlined"} class={"mr-5 fs-16"}></fs-icon>
<a href={downloadUrl} target={"_blank"}>
{file.filename}
</a>
</div>
</div>
);
}
if (children.length === 0) {
return <div></div>;
}
return (
<div class={"mt-3"}>
<div> {children}</div>
</div>
);
},
});
};
return {
viewCert,
downloadCert,
};
}

View File

@ -110,6 +110,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
async add(bean: PipelineEntity) {
bean.status = ResultType.none
await this.save(bean);
return bean;
}
@ -191,6 +192,9 @@ export class PipelineService extends BaseService<PipelineEntity> {
await this.checkMaxPipelineCount(bean, pipeline, domains);
}
if (!bean.status ){
bean.status = ResultType.none;
}
if (!isUpdate) {
//如果是添加先保存一下获取到id更新pipeline.id
await this.addOrUpdate(bean);