mirror of https://github.com/certd/certd
perf: 优化流水线页面,增加下次执行时间、查看证书显示
parent
2a19b61b7a
commit
c820315409
|
@ -16,6 +16,7 @@
|
||||||
import parser from "cron-parser";
|
import parser from "cron-parser";
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import { getCronNextTimes } from "/@/components/cron-editor/utils";
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "CronEditor",
|
name: "CronEditor",
|
||||||
});
|
});
|
||||||
|
@ -84,10 +85,10 @@ const nextTime = computed(() => {
|
||||||
if (props.modelValue == null) {
|
if (props.modelValue == null) {
|
||||||
return "请先设置正确的cron表达式";
|
return "请先设置正确的cron表达式";
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const interval = parser.parseExpression(props.modelValue);
|
const nextTimes = getCronNextTimes(props.modelValue, 2);
|
||||||
const next = interval.next().getTime();
|
return nextTimes.join(",");
|
||||||
return dayjs(next).format("YYYY-MM-DD HH:mm:ss");
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
return "请先设置正确的cron表达式";
|
return "请先设置正确的cron表达式";
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -76,7 +76,7 @@ export default {
|
||||||
.text-editable {
|
.text-editable {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
line-height: 34px;
|
line-height: 34px;
|
||||||
|
overflow: hidden;
|
||||||
span.fs-iconify {
|
span.fs-iconify {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
|
@ -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 { 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 { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
|
||||||
import { Modal, notification } from "ant-design-vue";
|
import { Modal, notification } from "ant-design-vue";
|
||||||
import { env } from "/@/utils/util.env";
|
|
||||||
import { useUserStore } from "/@/store/user";
|
import { useUserStore } from "/@/store/user";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useSettingStore } from "/@/store/settings";
|
import { useSettingStore } from "/@/store/settings";
|
||||||
import { cloneDeep } from "lodash-es";
|
import { cloneDeep } from "lodash-es";
|
||||||
import { useModal } from "/@/use/use-modal";
|
|
||||||
import CertView from "./cert-view.vue";
|
|
||||||
import { eachStages } from "./utils";
|
import { eachStages } from "./utils";
|
||||||
import { setRunnableIds, useCertPipelineCreator } from "/@/views/certd/pipeline/certd-form/use";
|
import { setRunnableIds, useCertPipelineCreator } from "/@/views/certd/pipeline/certd-form/use";
|
||||||
import { useCertUpload } from "/@/views/certd/pipeline/cert-upload/use";
|
import { useCertUpload } from "/@/views/certd/pipeline/cert-upload/use";
|
||||||
import GroupSelector from "/@/views/certd/pipeline/group/group-selector.vue";
|
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 {
|
export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -61,59 +59,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
const model = useModal();
|
const { viewCert, downloadCert } = useCertViewer();
|
||||||
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 userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
|
|
||||||
|
@ -208,6 +154,10 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
|
||||||
},
|
},
|
||||||
table: {
|
table: {
|
||||||
scroll: { x: 1500 },
|
scroll: { x: 1500 },
|
||||||
|
remove: {
|
||||||
|
confirmTitle: "确定要删除吗?",
|
||||||
|
confirmMessage: "将删除该流水线相关的所有数据,包括执行历史、证书文件、证书仓库记录等",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
tabs: {
|
tabs: {
|
||||||
name: "groupId",
|
name: "groupId",
|
||||||
|
@ -281,7 +231,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
|
||||||
type: "link",
|
type: "link",
|
||||||
icon: "ph:certificate",
|
icon: "ph:certificate",
|
||||||
async click({ row }) {
|
async click({ row }) {
|
||||||
await viewCert(row);
|
await viewCert(row.id);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
download: {
|
download: {
|
||||||
|
@ -291,7 +241,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
|
||||||
tooltip: { title: "下载证书" },
|
tooltip: { title: "下载证书" },
|
||||||
icon: "ant-design:download-outlined",
|
icon: "ant-design:download-outlined",
|
||||||
async click({ row }) {
|
async click({ row }) {
|
||||||
await downloadCert(row);
|
await downloadCert(row.id);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
remove: {
|
remove: {
|
||||||
|
|
|
@ -34,6 +34,8 @@ const pipelineOptions: PipelineOptions = {
|
||||||
stages: [],
|
stages: [],
|
||||||
triggers: [],
|
triggers: [],
|
||||||
...JSON.parse(detail.pipeline.content || "{}"),
|
...JSON.parse(detail.pipeline.content || "{}"),
|
||||||
|
type: detail.pipeline.type,
|
||||||
|
from: detail.pipeline.from,
|
||||||
},
|
},
|
||||||
} as PipelineDetail;
|
} as PipelineDetail;
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,18 +1,28 @@
|
||||||
<template>
|
<template>
|
||||||
<fs-page v-if="pipeline" class="page-pipeline-edit">
|
<fs-page v-if="pipeline" class="page-pipeline-edit">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="title">
|
<div class="title flex-1">
|
||||||
<fs-button class="back" icon="ion:chevron-back-outline" @click="goBack"></fs-button>
|
<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>
|
<text-editable v-model="pipeline.title" :hover-show="false" :disabled="!editMode"></text-editable>
|
||||||
</div>
|
</div>
|
||||||
<div class="more">
|
<div class="more flex items-center flex-1 justify-end">
|
||||||
<template v-if="editMode">
|
<div class="flex items-center hidden md:block">
|
||||||
<a-button type="primary" :loading="saveLoading" @click="save">保存</a-button>
|
<a-tag v-if="nextTriggerTimes" color="blue">下次执行时间:{{ nextTriggerTimes }}</a-tag>
|
||||||
<a-button class="ml-5" @click="cancel">取消</a-button>
|
</div>
|
||||||
</template>
|
<div class="basis-40 flex justify-end mr-10">
|
||||||
<template v-else>
|
<template v-if="editMode">
|
||||||
<a-button type="primary" @click="edit">编辑</a-button>
|
<fs-button type="primary" :loading="saveLoading" @click="save">保存</fs-button>
|
||||||
</template>
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -255,7 +265,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 { useRouter } from "vue-router";
|
||||||
import PiTaskForm from "./component/task-form/index.vue";
|
import PiTaskForm from "./component/task-form/index.vue";
|
||||||
import PiTriggerForm from "./component/trigger-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 TaskShortcuts from "./component/shortcut/task-shortcuts.vue";
|
||||||
import { eachSteps, findStep } from "../utils";
|
import { eachSteps, findStep } from "../utils";
|
||||||
import { PluginGroups } from "/@/store/plugin";
|
import { PluginGroups } from "/@/store/plugin";
|
||||||
|
import { getCronNextTimes } from "/@/components/cron-editor/utils";
|
||||||
|
import { useCertViewer } from "/@/views/certd/pipeline/use";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "PipelineEdit",
|
name: "PipelineEdit",
|
||||||
|
@ -309,6 +321,22 @@ export default defineComponent({
|
||||||
|
|
||||||
const currentHistory: Ref<any> = ref({});
|
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();
|
const router = useRouter();
|
||||||
function goBack() {
|
function goBack() {
|
||||||
router.back();
|
router.back();
|
||||||
|
@ -735,6 +763,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const edit = () => {
|
const edit = () => {
|
||||||
|
debugger;
|
||||||
pipeline.value = _.cloneDeep(currentPipeline.value);
|
pipeline.value = _.cloneDeep(currentPipeline.value);
|
||||||
currentHistory.value = null;
|
currentHistory.value = null;
|
||||||
toggleEditMode(true);
|
toggleEditMode(true);
|
||||||
|
@ -838,7 +867,12 @@ export default defineComponent({
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { viewCert, downloadCert } = useCertViewer();
|
||||||
|
const isCert = computed(() => {
|
||||||
|
return currentPipeline.value?.type?.startsWith("cert");
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
|
isCert,
|
||||||
pipeline,
|
pipeline,
|
||||||
currentHistory,
|
currentHistory,
|
||||||
histories,
|
histories,
|
||||||
|
@ -852,6 +886,9 @@ export default defineComponent({
|
||||||
...useHistory(),
|
...useHistory(),
|
||||||
...useNotification(),
|
...useNotification(),
|
||||||
...useScroll(),
|
...useScroll(),
|
||||||
|
nextTriggerTimes,
|
||||||
|
viewCert,
|
||||||
|
downloadCert,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
|
@ -110,6 +110,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async add(bean: PipelineEntity) {
|
async add(bean: PipelineEntity) {
|
||||||
|
bean.status = ResultType.none
|
||||||
await this.save(bean);
|
await this.save(bean);
|
||||||
return bean;
|
return bean;
|
||||||
}
|
}
|
||||||
|
@ -191,6 +192,9 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||||
await this.checkMaxPipelineCount(bean, pipeline, domains);
|
await this.checkMaxPipelineCount(bean, pipeline, domains);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!bean.status ){
|
||||||
|
bean.status = ResultType.none;
|
||||||
|
}
|
||||||
if (!isUpdate) {
|
if (!isUpdate) {
|
||||||
//如果是添加,先保存一下,获取到id,更新pipeline.id
|
//如果是添加,先保存一下,获取到id,更新pipeline.id
|
||||||
await this.addOrUpdate(bean);
|
await this.addOrUpdate(bean);
|
||||||
|
|
Loading…
Reference in New Issue