mirror of https://github.com/certd/certd
550 lines
15 KiB
TypeScript
550 lines
15 KiB
TypeScript
import * as api from "./api";
|
||
import { useI18n } from "vue-i18n";
|
||
import { computed, ref } from "vue";
|
||
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 { nanoid } from "nanoid";
|
||
import { message, Modal, notification } from "ant-design-vue";
|
||
import { env } from "/@/utils/util.env";
|
||
import { useUserStore } from "/@/store/modules/user";
|
||
import dayjs from "dayjs";
|
||
import { useSettingStore } from "/@/store/modules/settings";
|
||
import * as _ from "lodash-es";
|
||
import { useModal } from "/@/use/use-modal";
|
||
import CertView from "./cert-view.vue";
|
||
import { eachStages } from "./utils";
|
||
|
||
export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||
const router = useRouter();
|
||
const { t } = useI18n();
|
||
const lastResRef = ref();
|
||
|
||
function setRunnableIds(pipeline: any) {
|
||
const idMap: any = {};
|
||
function createId(oldId: any) {
|
||
if (oldId == null) {
|
||
return nanoid();
|
||
}
|
||
const newId = nanoid();
|
||
idMap[oldId] = newId;
|
||
return newId;
|
||
}
|
||
if (pipeline.stages) {
|
||
for (const stage of pipeline.stages) {
|
||
stage.id = createId(stage.id);
|
||
if (stage.tasks) {
|
||
for (const task of stage.tasks) {
|
||
task.id = createId(task.id);
|
||
if (task.steps) {
|
||
for (const step of task.steps) {
|
||
step.id = createId(step.id);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
for (const trigger of pipeline.triggers) {
|
||
trigger.id = nanoid();
|
||
}
|
||
for (const notification of pipeline.notifications) {
|
||
notification.id = nanoid();
|
||
}
|
||
|
||
let content = JSON.stringify(pipeline);
|
||
for (const key in idMap) {
|
||
content = content.replaceAll(key, idMap[key]);
|
||
}
|
||
return JSON.parse(content);
|
||
}
|
||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||
return await api.GetList(query);
|
||
};
|
||
const editRequest = async ({ form, row }: EditReq) => {
|
||
form.id = row.id;
|
||
const res = await api.UpdateObj(form);
|
||
lastResRef.value = res;
|
||
return res;
|
||
};
|
||
const delRequest = async ({ row }: DelReq) => {
|
||
return await api.DelObj(row.id);
|
||
};
|
||
|
||
const addRequest = async ({ form }: AddReq) => {
|
||
if (form.content == null) {
|
||
form.content = JSON.stringify({
|
||
title: form.title
|
||
});
|
||
} else {
|
||
//复制的流水线
|
||
delete form.status;
|
||
delete form.lastHistoryTime;
|
||
delete form.lastVars;
|
||
delete form.createTime;
|
||
delete form.id;
|
||
let pipeline = JSON.parse(form.content);
|
||
pipeline.title = form.title;
|
||
pipeline = setRunnableIds(pipeline);
|
||
form.content = JSON.stringify(pipeline);
|
||
}
|
||
|
||
const res = await api.AddObj(form);
|
||
lastResRef.value = res;
|
||
return res;
|
||
};
|
||
function addCertdPipeline() {
|
||
certdFormRef.value.open(async ({ form }: any) => {
|
||
// 添加certd pipeline
|
||
const triggers = [];
|
||
if (form.triggerCron) {
|
||
triggers.push({ title: "定时触发", type: "timer", props: { cron: form.triggerCron } });
|
||
}
|
||
const notifications = [];
|
||
if (form.emailNotify) {
|
||
notifications.push({
|
||
type: "email",
|
||
when: ["error", "turnToSuccess"],
|
||
options: {
|
||
receivers: [form.email]
|
||
}
|
||
});
|
||
}
|
||
let pipeline = {
|
||
title: form.domains[0] + "证书自动化",
|
||
stages: [
|
||
{
|
||
title: "证书申请阶段",
|
||
maxTaskCount: 1,
|
||
tasks: [
|
||
{
|
||
title: "证书申请任务",
|
||
steps: [
|
||
{
|
||
title: "申请证书",
|
||
input: {
|
||
renewDays: 35,
|
||
...form
|
||
},
|
||
strategy: {
|
||
runStrategy: 0 // 正常执行
|
||
},
|
||
type: form.certApplyPlugin
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
],
|
||
triggers,
|
||
notifications
|
||
};
|
||
pipeline = setRunnableIds(pipeline);
|
||
|
||
const id = await api.Save({
|
||
title: pipeline.title,
|
||
content: JSON.stringify(pipeline),
|
||
keepHistoryCount: 30
|
||
});
|
||
message.success("创建成功,请添加证书部署任务");
|
||
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
|
||
});
|
||
}
|
||
|
||
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>
|
||
<a href={downloadUrl} target={"_blank"}>
|
||
{file.filename}
|
||
</a>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return <div class={"mt-3"}>{children}</div>;
|
||
}
|
||
});
|
||
};
|
||
const userStore = useUserStore();
|
||
const settingStore = useSettingStore();
|
||
return {
|
||
crudOptions: {
|
||
request: {
|
||
pageRequest,
|
||
addRequest,
|
||
editRequest,
|
||
delRequest
|
||
},
|
||
actionbar: {
|
||
buttons: {
|
||
add: {
|
||
order: 5,
|
||
text: "自定义流水线"
|
||
},
|
||
addCertd: {
|
||
order: 1,
|
||
text: "创建证书流水线",
|
||
type: "primary",
|
||
click() {
|
||
addCertdPipeline();
|
||
}
|
||
}
|
||
}
|
||
},
|
||
form: {
|
||
afterSubmit({ form, res, mode }) {
|
||
if (mode === "add") {
|
||
router.push({ path: "/certd/pipeline/detail", query: { id: res.id, editMode: "true" } });
|
||
}
|
||
}
|
||
},
|
||
table: {
|
||
scroll: { x: 1500 }
|
||
},
|
||
rowHandle: {
|
||
width: 300,
|
||
fixed: "right",
|
||
buttons: {
|
||
play: {
|
||
order: -999,
|
||
title: "运行流水线",
|
||
type: "link",
|
||
icon: "ant-design:play-circle-outlined",
|
||
click({ row }) {
|
||
Modal.confirm({
|
||
title: "确认",
|
||
content: `确定要触发运行吗?`,
|
||
async onOk() {
|
||
await api.Trigger(row.id);
|
||
notification.success({ message: "管道已经开始运行" });
|
||
}
|
||
});
|
||
}
|
||
},
|
||
view: {
|
||
show: false,
|
||
click({ row }) {
|
||
router.push({ path: "/certd/pipeline/detail", query: { id: row.id, editMode: "false" } });
|
||
}
|
||
},
|
||
copy: {
|
||
click: async (context) => {
|
||
settingStore.checkPlus();
|
||
const { ui } = useUi();
|
||
// @ts-ignore
|
||
let row = context[ui.tableColumn.row];
|
||
row = _.cloneDeep(row);
|
||
row.title = row.title + "_copy";
|
||
await crudExpose.openCopy({
|
||
row: row,
|
||
index: context.index
|
||
});
|
||
},
|
||
class: "need-plus"
|
||
},
|
||
config: {
|
||
order: 1,
|
||
title: "修改流水线内容",
|
||
type: "link",
|
||
icon: "ant-design:edit-outlined",
|
||
click({ row }) {
|
||
router.push({ path: "/certd/pipeline/detail", query: { id: row.id, editMode: "true" } });
|
||
}
|
||
},
|
||
edit: {
|
||
order: 2,
|
||
title: "修改流水线运行配置",
|
||
icon: "ant-design:setting-outlined"
|
||
},
|
||
viewCert: {
|
||
order: 3,
|
||
title: "查看证书",
|
||
type: "link",
|
||
icon: "ph:certificate",
|
||
async click({ row }) {
|
||
viewCert(row);
|
||
}
|
||
},
|
||
download: {
|
||
order: 4,
|
||
type: "link",
|
||
title: "下载证书",
|
||
icon: "ant-design:download-outlined",
|
||
async click({ row }) {
|
||
downloadCert(row);
|
||
}
|
||
},
|
||
remove: {
|
||
order: 5
|
||
}
|
||
}
|
||
},
|
||
columns: {
|
||
id: {
|
||
title: "ID",
|
||
key: "id",
|
||
type: "number",
|
||
search: {
|
||
show: true
|
||
},
|
||
column: {
|
||
width: 100
|
||
},
|
||
form: {
|
||
show: false
|
||
}
|
||
},
|
||
userId: {
|
||
title: "用户Id",
|
||
type: "number",
|
||
search: {
|
||
show: computed(() => {
|
||
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
|
||
})
|
||
},
|
||
form: {
|
||
show: false
|
||
},
|
||
column: {
|
||
show: computed(() => {
|
||
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
|
||
}),
|
||
width: 100
|
||
}
|
||
},
|
||
title: {
|
||
title: "流水线名称",
|
||
type: "link",
|
||
search: {
|
||
show: true,
|
||
title: "关键字",
|
||
component: {
|
||
name: "a-input"
|
||
}
|
||
},
|
||
form: {
|
||
rules: [{ required: true, message: "此项必填" }]
|
||
},
|
||
column: {
|
||
width: 350,
|
||
sorter: true,
|
||
component: {
|
||
on: {
|
||
// 注意:必须要on前缀
|
||
onClick({ row }) {
|
||
router.push({ path: "/certd/pipeline/detail", query: { id: row.id, editMode: "false" } });
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
content: {
|
||
title: "流水线内容",
|
||
form: { show: false },
|
||
column: {
|
||
show: false
|
||
},
|
||
valueBuilder({ row }) {
|
||
if (row.content) {
|
||
row.content = JSON.parse(row.content);
|
||
const pipeline = row.content;
|
||
let stepCount = 0;
|
||
eachStages(pipeline.stages, (item, runnableType) => {
|
||
if (runnableType === "step") {
|
||
stepCount++;
|
||
}
|
||
});
|
||
row._stepCount = stepCount;
|
||
if (pipeline.triggers) {
|
||
row._triggerCount = pipeline.triggers?.length > 0 ? pipeline.triggers.length : "-";
|
||
}
|
||
}
|
||
},
|
||
valueResolve({ row }) {
|
||
if (row.content) {
|
||
row.content = JSON.stringify(row.content);
|
||
}
|
||
}
|
||
},
|
||
_triggerCount: {
|
||
title: "定时任务数",
|
||
type: "number",
|
||
column: {
|
||
align: "center",
|
||
width: 100
|
||
},
|
||
form: {
|
||
show: false
|
||
}
|
||
},
|
||
_stepCount: {
|
||
title: "部署任务数",
|
||
type: "number",
|
||
form: { show: false },
|
||
column: {
|
||
align: "center",
|
||
width: 100
|
||
}
|
||
},
|
||
lastVars: {
|
||
title: "到期剩余",
|
||
type: "number",
|
||
form: {
|
||
show: false
|
||
},
|
||
column: {
|
||
cellRender({ row }) {
|
||
if (!row.lastVars?.certExpiresTime) {
|
||
return "-";
|
||
}
|
||
const leftDays = dayjs(row.lastVars.certExpiresTime).diff(dayjs(), "day");
|
||
const color = leftDays < 20 ? "red" : "#389e0d";
|
||
const percent = (leftDays / 90) * 100;
|
||
return <a-progress percent={percent} strokeColor={color} format={(percent: number) => `${leftDays} 天`} />;
|
||
},
|
||
width: 150
|
||
}
|
||
},
|
||
status: {
|
||
title: "状态",
|
||
type: "dict-select",
|
||
search: {
|
||
show: true
|
||
},
|
||
dict: dict({
|
||
data: statusUtil.getOptions()
|
||
}),
|
||
form: {
|
||
show: false
|
||
},
|
||
column: {
|
||
sorter: true,
|
||
width: 80,
|
||
align: "center"
|
||
}
|
||
},
|
||
lastHistoryTime: {
|
||
title: "最后运行",
|
||
type: "datetime",
|
||
form: {
|
||
show: false
|
||
},
|
||
column: {
|
||
sorter: true,
|
||
width: 150,
|
||
align: "center"
|
||
}
|
||
},
|
||
disabled: {
|
||
title: "启用",
|
||
type: "dict-switch",
|
||
dict: dict({
|
||
data: [
|
||
{ value: false, label: "启用" },
|
||
{ value: true, label: "禁用" }
|
||
]
|
||
}),
|
||
form: {
|
||
value: false,
|
||
show: false
|
||
},
|
||
column: {
|
||
sorter: true,
|
||
width: 80,
|
||
align: "center",
|
||
component: {
|
||
name: "fs-dict-switch",
|
||
vModel: "checked"
|
||
},
|
||
async valueChange({ row, key, value }) {
|
||
return await api.UpdateObj({
|
||
id: row.id,
|
||
disabled: row[key]
|
||
});
|
||
}
|
||
}
|
||
},
|
||
|
||
keepHistoryCount: {
|
||
title: "历史记录保持数",
|
||
type: "number",
|
||
form: {
|
||
value: 20,
|
||
helper: "历史记录保持条数,多余的会被删除"
|
||
},
|
||
column: {
|
||
width: 130,
|
||
show: false
|
||
}
|
||
},
|
||
order: {
|
||
title: "排序号",
|
||
type: "number",
|
||
column: {
|
||
sorter: true,
|
||
align: "center",
|
||
width: 80
|
||
},
|
||
form: {
|
||
value: 0
|
||
}
|
||
},
|
||
createTime: {
|
||
title: "创建时间",
|
||
type: "datetime",
|
||
form: {
|
||
show: false
|
||
},
|
||
column: {
|
||
sorter: true,
|
||
width: 155,
|
||
align: "center"
|
||
}
|
||
},
|
||
updateTime: {
|
||
title: "更新时间",
|
||
type: "datetime",
|
||
form: {
|
||
show: false
|
||
},
|
||
column: {
|
||
width: 125,
|
||
show: false
|
||
}
|
||
}
|
||
}
|
||
}
|
||
};
|
||
}
|