mirror of
https://github.com/certd/certd.git
synced 2026-04-27 23:37:29 +08:00
perf: 支持webhook触发流水线,新增触发类型图标显示
This commit is contained in:
@@ -352,6 +352,7 @@ export default {
|
||||
editSchedule: "修改定时",
|
||||
timerTrigger: "定时触发",
|
||||
schedule: "定时",
|
||||
webhook: "Webhook",
|
||||
selectCron: "请选择定时Cron",
|
||||
batchEditSchedule: "批量修改定时",
|
||||
editTrigger: "编辑触发器",
|
||||
|
||||
@@ -38,6 +38,8 @@ const pipelineOptions: PipelineOptions = {
|
||||
from: detail.pipeline.from,
|
||||
},
|
||||
validTime: detail.pipeline.validTime,
|
||||
webhookKey: detail.pipeline.webhookKey,
|
||||
id: detail.pipeline.id,
|
||||
} as PipelineDetail;
|
||||
},
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
<template #header>
|
||||
<div class="title">{{ t("certd.myPipelines") }}</div>
|
||||
</template>
|
||||
<a-alert v-if="settingStore.sysPublic.notice" type="warning" show-icon>
|
||||
<!-- <a-alert v-if="settingStore.sysPublic.notice" type="warning" show-icon>
|
||||
<template #message>
|
||||
{{ settingStore.sysPublic.notice }}
|
||||
</template>
|
||||
</a-alert>
|
||||
</a-alert> -->
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<div v-if="selectedRowKeys.length > 0" class="batch-actions">
|
||||
<div class="batch-actions-inner">
|
||||
|
||||
+6
@@ -4,6 +4,7 @@
|
||||
<fs-icon v-bind="status" :color="status.iconColor || status.color" />
|
||||
</template>
|
||||
<p class="flex items-center">
|
||||
<TriggerIcon class="mr-2" :trigger-type="runnable.triggerType"></TriggerIcon>
|
||||
<fs-date-format :model-value="runnable.createTime"></fs-date-format>
|
||||
<a-tag class="ml-5" :color="status.color" :closable="status.value === 'start'" @close="cancelTask">
|
||||
{{ status.label }}
|
||||
@@ -19,8 +20,12 @@ import { defineComponent, ref, provide, Ref, watch, computed } from "vue";
|
||||
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
|
||||
import * as api from "../../api";
|
||||
import { Modal, notification } from "ant-design-vue";
|
||||
import TriggerIcon from "./trigger-icon.vue";
|
||||
export default defineComponent({
|
||||
name: "PiHistoryTimelineItem",
|
||||
components: {
|
||||
TriggerIcon,
|
||||
},
|
||||
props: {
|
||||
runnable: {
|
||||
type: Object,
|
||||
@@ -69,6 +74,7 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
cancel,
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { request } from "/src/api/service";
|
||||
|
||||
const apiPrefix = "/pi/pipeline";
|
||||
|
||||
export async function RefreshWebhookKey(form: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/refreshWebhookKey",
|
||||
method: "post",
|
||||
data: form,
|
||||
});
|
||||
}
|
||||
+50
-34
@@ -13,6 +13,25 @@
|
||||
<template v-if="currentTrigger">
|
||||
<pi-container>
|
||||
<a-form ref="triggerFormRef" class="trigger-form" :model="currentTrigger" :label-col="labelCol" :wrapper-col="wrapperCol">
|
||||
<fs-form-item
|
||||
:model-value="currentTrigger.type"
|
||||
:item="{
|
||||
title: t('certd.type'),
|
||||
key: 'type',
|
||||
value: 'timer',
|
||||
component: {
|
||||
name: 'a-select',
|
||||
vModel: 'value',
|
||||
disabled: !editMode,
|
||||
options: [
|
||||
{ value: 'timer', label: t('certd.schedule') },
|
||||
{ value: 'webhook', label: t('certd.webhook') },
|
||||
],
|
||||
},
|
||||
rules: [{ required: true, message: t('certd.requiredField') }],
|
||||
}"
|
||||
@update:model-value="typeValueChange($event)"
|
||||
/>
|
||||
<fs-form-item
|
||||
v-model="currentTrigger.title"
|
||||
:item="{
|
||||
@@ -27,36 +46,8 @@
|
||||
}"
|
||||
/>
|
||||
|
||||
<fs-form-item
|
||||
v-model="currentTrigger.type"
|
||||
:item="{
|
||||
title: t('certd.type'),
|
||||
key: 'type',
|
||||
value: 'timer',
|
||||
component: {
|
||||
name: 'a-select',
|
||||
vModel: 'value',
|
||||
disabled: !editMode,
|
||||
options: [{ value: 'timer', label: t('certd.schedule') }],
|
||||
},
|
||||
rules: [{ required: true, message: t('certd.requiredField') }],
|
||||
}"
|
||||
/>
|
||||
|
||||
<fs-form-item
|
||||
v-model="currentTrigger.props.cron"
|
||||
:item="{
|
||||
title: t('certd.cronForm.title'),
|
||||
key: 'props.cron',
|
||||
component: {
|
||||
disabled: !editMode,
|
||||
name: 'cron-editor',
|
||||
vModel: 'modelValue',
|
||||
},
|
||||
helper: t('certd.cronForm.helper'),
|
||||
rules: [{ required: true, message: t('certd.cronForm.required') }],
|
||||
}"
|
||||
/>
|
||||
<timer-form v-if="currentTrigger.type === 'timer'" :edit-mode="editMode" />
|
||||
<webhook-form v-if="currentTrigger.type === 'webhook'" :edit-mode="editMode" />
|
||||
</a-form>
|
||||
|
||||
<template #footer>
|
||||
@@ -70,13 +61,19 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { message, Modal } from "ant-design-vue";
|
||||
import { inject, ref } from "vue";
|
||||
import { Modal } from "ant-design-vue";
|
||||
import * as _ from "lodash-es";
|
||||
import { useI18n } from "/src/locales/";
|
||||
import { nanoid } from "nanoid";
|
||||
import { ref, provide } from "vue";
|
||||
import { useI18n } from "/src/locales/";
|
||||
import TimerForm from "./timer-form.vue";
|
||||
import WebhookForm from "./webhook-form.vue";
|
||||
export default {
|
||||
name: "PiTriggerForm",
|
||||
components: {
|
||||
TimerForm,
|
||||
WebhookForm,
|
||||
},
|
||||
props: {
|
||||
editMode: {
|
||||
type: Boolean,
|
||||
@@ -94,6 +91,7 @@ export default {
|
||||
const mode = ref("add");
|
||||
const callback = ref();
|
||||
const currentTrigger = ref({ title: undefined, input: {} });
|
||||
provide("trigger:get", () => currentTrigger);
|
||||
const currentPlugin = ref({});
|
||||
const triggerFormRef = ref(null);
|
||||
const triggerDrawerVisible = ref(false);
|
||||
@@ -125,9 +123,13 @@ export default {
|
||||
triggerDrawerShow();
|
||||
};
|
||||
|
||||
const triggerAdd = emit => {
|
||||
const triggerAdd = (triggerType, emit) => {
|
||||
mode.value = "add";
|
||||
const trigger = { id: nanoid(), title: t("certd.timerTrigger"), type: "timer", props: {} };
|
||||
if (triggerType === "webhook") {
|
||||
trigger.type = "webhook";
|
||||
trigger.title = "Webhook触发";
|
||||
}
|
||||
triggerOpen(trigger, emit);
|
||||
};
|
||||
|
||||
@@ -168,6 +170,19 @@ export default {
|
||||
const blankFn = () => {
|
||||
return {};
|
||||
};
|
||||
|
||||
function typeValueChange(value) {
|
||||
if (value === "webhook") {
|
||||
if (currentTrigger.value.title === "定时触发" || !currentTrigger.value.title) {
|
||||
currentTrigger.value.title = "Webhook触发";
|
||||
}
|
||||
} else if (value === "timer") {
|
||||
if (currentTrigger.value.title === "Webhook触发" || !currentTrigger.value.title) {
|
||||
currentTrigger.value.title = "定时触发";
|
||||
}
|
||||
}
|
||||
currentTrigger.value.type = value;
|
||||
}
|
||||
return {
|
||||
triggerFormRef,
|
||||
mode,
|
||||
@@ -183,6 +198,7 @@ export default {
|
||||
triggerDelete,
|
||||
rules,
|
||||
blankFn,
|
||||
typeValueChange,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div>
|
||||
<fs-form-item
|
||||
v-model="trigger.props.cron"
|
||||
:item="{
|
||||
title: t('certd.cronForm.title'),
|
||||
key: 'props.cron',
|
||||
component: {
|
||||
disabled: !editMode,
|
||||
name: 'cron-editor',
|
||||
vModel: 'modelValue',
|
||||
},
|
||||
helper: t('certd.cronForm.helper'),
|
||||
rules: [{ required: true, message: t('certd.cronForm.required') }],
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="tsx" setup>
|
||||
import { inject } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
defineOptions({
|
||||
name: "TimerForm",
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
editMode: boolean;
|
||||
}>();
|
||||
const { t } = useI18n();
|
||||
const triggerGetter: any = inject("trigger:get");
|
||||
const trigger = triggerGetter();
|
||||
</script>
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-form-item label="Webhook URL">
|
||||
<div class="flex flex-col h-8 border-dashed border-2 rounded-md p-1">
|
||||
<fs-copyable :model-value="webhookUrl" class="flex-1 overflow-hidden"></fs-copyable>
|
||||
</div>
|
||||
<a-button class="mt-2" type="primary" size="small" @click="refreshWebhookKey">重新生成</a-button>
|
||||
<div class="helper">支持post和get请求</div>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="tsx" setup>
|
||||
import { Modal } from "ant-design-vue";
|
||||
import { computed, inject, onMounted } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import * as api from "./api";
|
||||
defineOptions({
|
||||
name: "WebhookForm",
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
editMode: boolean;
|
||||
}>();
|
||||
const { t } = useI18n();
|
||||
const pipelineDetailGetter: any = inject("pipelineDetail:get");
|
||||
const webhookUrl = computed(() => {
|
||||
const detailRef = pipelineDetailGetter();
|
||||
return `${window.location.origin}/api/webhook/${detailRef?.value?.webhookKey}`;
|
||||
});
|
||||
onMounted(() => {
|
||||
const detailRef = pipelineDetailGetter();
|
||||
if (!detailRef.value) {
|
||||
return;
|
||||
}
|
||||
if (!detailRef.value.webhookKey) {
|
||||
doRefreshWebhookKey();
|
||||
}
|
||||
});
|
||||
|
||||
const refreshWebhookKey = () => {
|
||||
Modal.confirm({
|
||||
title: "确认重新生成Webhook URL吗?",
|
||||
okText: "确认",
|
||||
okType: "danger",
|
||||
onOk: async () => {
|
||||
await doRefreshWebhookKey();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
async function doRefreshWebhookKey() {
|
||||
const detailRef = pipelineDetailGetter();
|
||||
const res = await api.RefreshWebhookKey({
|
||||
id: detailRef.value.id,
|
||||
});
|
||||
detailRef.value.webhookKey = res.webhookKey;
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<fs-icon v-bind="triggerTypeIconBind" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
|
||||
defineOptions({
|
||||
name: "TriggerIcon",
|
||||
});
|
||||
const props = defineProps({
|
||||
triggerType: {
|
||||
type: String,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
const triggerTypeIconBind = computed(() => {
|
||||
const triggerType = props.triggerType;
|
||||
let icon = "hugeicons:tap-05:#ff9900";
|
||||
let title = "手动触发";
|
||||
if (triggerType === "webhook") {
|
||||
icon = "mdi:link-variant:blue";
|
||||
title = "Webhook 触发";
|
||||
} else if (triggerType === "timer") {
|
||||
icon = "mdi:schedule:green";
|
||||
title = "定时器触发";
|
||||
}
|
||||
return {
|
||||
icon,
|
||||
title,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
@@ -28,10 +28,10 @@
|
||||
未设置触发源,不会自动执行
|
||||
</span>
|
||||
</a-tag>
|
||||
<a-tag v-if="pipelineEntity.validTime > 0 && settingStore.sysPublic.pipelineValidTimeEnabled && settingStore.isPlus" :color="pipelineEntity.validTime > Date.now() ? 'green' : 'red'">
|
||||
<a-tag v-if="pipelineDetail.validTime > 0 && settingStore.sysPublic.pipelineValidTimeEnabled && settingStore.isPlus" :color="pipelineDetail.validTime > Date.now() ? 'green' : 'red'">
|
||||
<span class="flex">
|
||||
<fs-icon icon="ion:time-outline"></fs-icon>
|
||||
<span v-if="pipelineEntity.validTime > Date.now()"> 有效期:<FsTimeHumanize :model-value="pipelineEntity.validTime" :options="{ units: ['d'] }" format="YYYY-MM-DD"></FsTimeHumanize> </span>
|
||||
<span v-if="pipelineDetail.validTime > Date.now()"> 有效期:<FsTimeHumanize :model-value="pipelineDetail.validTime" :options="{ units: ['d'] }" format="YYYY-MM-DD"></FsTimeHumanize> </span>
|
||||
<span v-else> 已过期 </span>
|
||||
</span>
|
||||
</a-tag>
|
||||
@@ -76,7 +76,7 @@
|
||||
</div>
|
||||
<div class="task">
|
||||
<a-button shape="round" @click="triggerEdit(trigger, index)">
|
||||
<fs-icon icon="ion:time"></fs-icon>
|
||||
<TriggerIcon class="mr-2" :trigger-type="trigger.type"></TriggerIcon>
|
||||
{{ trigger.title }}
|
||||
</a-button>
|
||||
</div>
|
||||
@@ -93,6 +93,17 @@
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="editMode" class="task-container is-add">
|
||||
<div class="line line-right">
|
||||
<div class="flow-line"></div>
|
||||
</div>
|
||||
<div class="task">
|
||||
<a-button shape="round" type="dashed" @click="triggerAdd('webhook')">
|
||||
<fs-icon icon="ion:add-circle-outline"></fs-icon>
|
||||
触发源(Webhook)
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -126,8 +137,8 @@
|
||||
<a-popover title="步骤" :trigger="editMode ? 'none' : 'hover'">
|
||||
<!-- :open="true"-->
|
||||
<template #content>
|
||||
<div v-for="(item, index) of task.steps" :key="item.id" class="flex-o w-100">
|
||||
<span class="ellipsis flex-1 step-title" :class="{ disabled: item.disabled, deleted: item.disabled }"> {{ index + 1 }}. {{ item.title }} </span>
|
||||
<div v-for="(item, stepIndex) of task.steps" :key="item.id" class="flex-o w-100">
|
||||
<span class="ellipsis flex-1 step-title" :class="{ disabled: item.disabled, deleted: item.disabled }"> {{ stepIndex + 1 }}. {{ item.title }} </span>
|
||||
<pi-status-show v-if="!editMode" :status="item.status?.result"></pi-status-show>
|
||||
<a-tooltip title="强制重新执行此步骤">
|
||||
<fs-icon v-if="!editMode" class="pointer color-blue ml-2" style="font-size: 16px" title="强制重新执行此步骤" icon="icon-park-outline:replay-music" @click="run(item.id)"></fs-icon>
|
||||
@@ -310,6 +321,7 @@ import { usePluginStore } from "/@/store/plugin";
|
||||
import { getCronNextTimes } from "/@/components/cron-editor/utils";
|
||||
import { useCertViewer } from "/@/views/certd/pipeline/use";
|
||||
import { useI18n } from "/@/locales";
|
||||
import TriggerIcon from "./component/trigger-icon.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "PipelineEdit",
|
||||
@@ -324,6 +336,7 @@ export default defineComponent({
|
||||
PiNotificationForm,
|
||||
VDraggable,
|
||||
TaskShortcuts,
|
||||
TriggerIcon,
|
||||
},
|
||||
props: {
|
||||
pipelineId: {
|
||||
@@ -351,7 +364,7 @@ export default defineComponent({
|
||||
//右侧选中的pipeline
|
||||
const currentPipeline: Ref<any> = ref({});
|
||||
const pipeline: Ref<any> = ref({});
|
||||
const pipelineEntity: Ref<any> = ref({});
|
||||
const pipelineDetail: Ref<any> = ref({});
|
||||
const histories: Ref<RunHistory[]> = ref([]);
|
||||
|
||||
const currentHistory: Ref<any> = ref({});
|
||||
@@ -400,7 +413,7 @@ export default defineComponent({
|
||||
currentPipeline.value = currentHistory.value.pipeline;
|
||||
};
|
||||
|
||||
async function loadHistoryList(reload = false, historyId: number) {
|
||||
async function loadHistoryList(reload = false, historyId: number = null) {
|
||||
if (props.editMode) {
|
||||
return;
|
||||
}
|
||||
@@ -499,7 +512,7 @@ export default defineComponent({
|
||||
return;
|
||||
}
|
||||
const detail: PipelineDetail = await props.options.getPipelineDetail({ pipelineId: value });
|
||||
pipelineEntity.value = detail;
|
||||
pipelineDetail.value = detail;
|
||||
currentPipeline.value = merge(
|
||||
{
|
||||
title: "新管道流程",
|
||||
@@ -523,6 +536,7 @@ export default defineComponent({
|
||||
};
|
||||
fetchPlugins();
|
||||
|
||||
provide("pipelineDetail:get", () => pipelineDetail);
|
||||
provide("pipeline", pipeline);
|
||||
provide("getPluginGroups", () => {
|
||||
return pluginStore.group;
|
||||
@@ -631,8 +645,8 @@ export default defineComponent({
|
||||
|
||||
function useTrigger() {
|
||||
const triggerFormRef: Ref<any> = ref(null);
|
||||
const triggerAdd = () => {
|
||||
triggerFormRef.value.triggerAdd((type: string, value: any) => {
|
||||
const triggerAdd = (triggerType = "timer") => {
|
||||
triggerFormRef.value.triggerAdd(triggerType, (type: string, value: any) => {
|
||||
if (type === "save") {
|
||||
pipeline.value.triggers.push(value);
|
||||
}
|
||||
@@ -988,7 +1002,7 @@ export default defineComponent({
|
||||
nextTriggerTimes,
|
||||
viewCert,
|
||||
downloadCert,
|
||||
pipelineEntity,
|
||||
pipelineDetail,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { PluginGroups } from "/@/store/plugin";
|
||||
export type PipelineDetail = {
|
||||
pipeline: Pipeline;
|
||||
validTime?: number;
|
||||
webhookKey?: string;
|
||||
};
|
||||
|
||||
export type RunHistory = {
|
||||
|
||||
Reference in New Issue
Block a user