mirror of https://github.com/certd/certd
perf: 通知选择器优化
parent
68a503796c
commit
2c0cbdd29e
|
@ -98,10 +98,22 @@ export function createAxiosService({ logger }: { logger: Logger }) {
|
|||
config.timeout = 15000;
|
||||
}
|
||||
let agents = defaultAgents;
|
||||
if (config.skipSslVerify) {
|
||||
logger.info('跳过SSL验证');
|
||||
agents = createAgent({ rejectUnauthorized: false } as any);
|
||||
if (config.skipSslVerify || config.httpProxy) {
|
||||
let rejectUnauthorized = true;
|
||||
if (config.skipSslVerify) {
|
||||
logger.info('跳过SSL验证');
|
||||
rejectUnauthorized = false;
|
||||
}
|
||||
const proxy: any = {};
|
||||
if (config.httpProxy) {
|
||||
logger.info('使用自定义http代理:', config.httpProxy);
|
||||
proxy.httpProxy = config.httpProxy;
|
||||
proxy.httpsProxy = config.httpProxy;
|
||||
}
|
||||
|
||||
agents = createAgent({ rejectUnauthorized, ...proxy } as any);
|
||||
}
|
||||
|
||||
delete config.skipSslVerify;
|
||||
config.httpsAgent = agents.httpsAgent;
|
||||
config.httpAgent = agents.httpAgent;
|
||||
|
@ -200,6 +212,7 @@ export type HttpRequestConfig<D = any> = {
|
|||
skipCheckRes?: boolean;
|
||||
logParams?: boolean;
|
||||
logRes?: boolean;
|
||||
httpProxy?: string;
|
||||
} & AxiosRequestConfig<D>;
|
||||
export type HttpClient = {
|
||||
request<D = any, R = any>(config: HttpRequestConfig<D>): Promise<HttpClientResponse<R>>;
|
||||
|
|
|
@ -388,6 +388,7 @@ export class Executor {
|
|||
if (!notification.when.includes(when)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (notification.type === "email") {
|
||||
try {
|
||||
await this.options.emailService?.send({
|
||||
|
@ -401,7 +402,16 @@ export class Executor {
|
|||
} else {
|
||||
try {
|
||||
//构建notification插件,发送通知
|
||||
const notifyConfig = await this.options.notificationService.getById(notification.notificationId);
|
||||
let notifyConfig: any;
|
||||
if (notification.notificationId === 0) {
|
||||
notifyConfig = await this.options.notificationService.getDefault();
|
||||
} else {
|
||||
notifyConfig = await this.options.notificationService.getById(notification.notificationId);
|
||||
}
|
||||
if (notifyConfig == null) {
|
||||
throw new Error(`通知配置<id:${notification.notificationId}>不存在`);
|
||||
}
|
||||
|
||||
const notificationPlugin = notificationRegistry.get(notifyConfig.type);
|
||||
const notificationCls: any = notificationPlugin.target;
|
||||
const notificationSender = new notificationCls();
|
||||
|
|
|
@ -77,6 +77,9 @@ h1, h2, h3, h4, h5, h6 {
|
|||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
.flex-0 {
|
||||
flex: 0;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
display: flex;
|
||||
|
|
|
@ -43,6 +43,13 @@ export function createApi() {
|
|||
});
|
||||
},
|
||||
|
||||
async GetOptions(id: number) {
|
||||
return await request({
|
||||
url: apiPrefix + "/options",
|
||||
method: "post"
|
||||
});
|
||||
},
|
||||
|
||||
async SetDefault(id: number) {
|
||||
return await request({
|
||||
url: apiPrefix + "/setDefault",
|
||||
|
@ -66,6 +73,13 @@ export function createApi() {
|
|||
});
|
||||
},
|
||||
|
||||
async GetDefineTypes() {
|
||||
return await request({
|
||||
url: apiPrefix + "/getTypeDict",
|
||||
method: "post"
|
||||
});
|
||||
},
|
||||
|
||||
async GetProviderDefine(type: string) {
|
||||
return await request({
|
||||
url: apiPrefix + "/define",
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
// @ts-ignore
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { ref } from "vue";
|
||||
import { getCommonColumnDefine } from "./common";
|
||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
|
||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { createApi } from "/@/views/certd/notification/api";
|
||||
const api = createApi();
|
||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const { t } = useI18n();
|
||||
const api = context.api;
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
|
|
|
@ -1,162 +1,156 @@
|
|||
<template>
|
||||
<div class="notification-selector">
|
||||
<span v-if="target?.name" class="mr-5 cd-flex-inline">
|
||||
<a-tag class="mr-5" color="green">{{ target.name }}</a-tag>
|
||||
<fs-icon class="cd-icon-button" icon="ion:close-circle-outline" @click="clear"></fs-icon>
|
||||
</span>
|
||||
<span v-else class="mlr-5 text-gray">{{ placeholder }}</span>
|
||||
<a-button class="ml-5" :disabled="disabled" :size="size" @click="chooseForm.open">选择</a-button>
|
||||
<a-form-item-rest v-if="chooseForm.show">
|
||||
<a-modal v-model:open="chooseForm.show" title="选择通知渠道" width="905px" @ok="chooseForm.ok">
|
||||
<div style="height: 400px; position: relative">
|
||||
<cert-notification-modal v-model="selectedId"></cert-notification-modal>
|
||||
</div>
|
||||
</a-modal>
|
||||
</a-form-item-rest>
|
||||
<div class="flex-o w-100">
|
||||
<fs-dict-select
|
||||
class="flex-1"
|
||||
:value="modelValue"
|
||||
:dict="optionsDictRef"
|
||||
:disabled="disabled"
|
||||
:render-label="renderLabel"
|
||||
:slots="selectSlots"
|
||||
:allow-clear="true"
|
||||
@update:value="onChange"
|
||||
/>
|
||||
<fs-table-select
|
||||
ref="tableSelectRef"
|
||||
class="flex-0"
|
||||
:model-value="modelValue"
|
||||
:dict="optionsDictRef"
|
||||
:create-crud-options="createCrudOptions"
|
||||
:crud-options-override="{
|
||||
search: { show: false },
|
||||
table: {
|
||||
scroll: {
|
||||
x: 540
|
||||
}
|
||||
}
|
||||
}"
|
||||
:show-current="false"
|
||||
:show-select="false"
|
||||
:dialog="{ width: 960 }"
|
||||
:destroy-on-close="false"
|
||||
@update:model-value="onChange"
|
||||
>
|
||||
<template #default="scope">
|
||||
<fs-button class="ml-5" :disabled="disabled" :size="size" type="primary" icon="ant-design:edit-outlined" @click="scope.open"></fs-button>
|
||||
</template>
|
||||
</fs-table-select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, reactive, ref, watch, inject } from "vue";
|
||||
import CertNotificationModal from "./modal/index.vue";
|
||||
<script lang="tsx" setup>
|
||||
import { inject, ref, Ref, watch } from "vue";
|
||||
import { createApi } from "../api";
|
||||
import { message } from "ant-design-vue";
|
||||
import { dict } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "../crud";
|
||||
|
||||
export default defineComponent({
|
||||
name: "NotificationSelector",
|
||||
components: { CertNotificationModal },
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [Number, String],
|
||||
default: null
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请选择"
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: "middle"
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
useDefault: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ["update:modelValue", "selectedChange", "change"],
|
||||
setup(props, ctx) {
|
||||
const api = createApi();
|
||||
defineOptions({
|
||||
name: "NotificationSelector"
|
||||
});
|
||||
|
||||
const target = ref({});
|
||||
const selectedId = ref();
|
||||
async function refreshTarget(value) {
|
||||
selectedId.value = value;
|
||||
if (value > 0) {
|
||||
target.value = await api.GetSimpleInfo(value);
|
||||
}
|
||||
}
|
||||
const props = defineProps<{
|
||||
modelValue?: number | string;
|
||||
type?: string;
|
||||
placeholder?: string;
|
||||
size?: string;
|
||||
disabled?: boolean;
|
||||
}>();
|
||||
|
||||
async function loadDefault() {
|
||||
const defId = await api.GetDefaultId();
|
||||
if (defId) {
|
||||
await emitValue(defId);
|
||||
}
|
||||
}
|
||||
const onChange = async (value: number) => {
|
||||
await emitValue(value);
|
||||
};
|
||||
|
||||
loadDefault();
|
||||
const emit = defineEmits(["update:modelValue", "selectedChange", "change"]);
|
||||
|
||||
function clear() {
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
emitValue(null);
|
||||
}
|
||||
const api = createApi();
|
||||
|
||||
async function emitValue(value) {
|
||||
if (pipeline?.value && target?.value && pipeline.value.userId !== target.value.userId) {
|
||||
message.error("对不起,您不能修改他人流水线的通知");
|
||||
return;
|
||||
}
|
||||
if (value == null) {
|
||||
selectedId.value = "";
|
||||
target.value = null;
|
||||
} else {
|
||||
selectedId.value = value;
|
||||
await refreshTarget(selectedId.value);
|
||||
}
|
||||
ctx.emit("change", selectedId.value);
|
||||
ctx.emit("update:modelValue", selectedId.value);
|
||||
ctx.emit("selectedChange", target.value);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => {
|
||||
return props.modelValue;
|
||||
},
|
||||
async (value) => {
|
||||
selectedId.value = null;
|
||||
target.value = null;
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
await refreshTarget(value);
|
||||
},
|
||||
// const types = ref({});
|
||||
// async function loadNotificationTypes() {
|
||||
// const types = await api.GetDefineTypes();
|
||||
// const map: any = {};
|
||||
// for (const item of types) {
|
||||
// map[item.type] = item;
|
||||
// }
|
||||
// types.value = map;
|
||||
// }
|
||||
// loadNotificationTypes();
|
||||
const tableSelectRef = ref();
|
||||
const optionsDictRef = dict({
|
||||
url: "/pi/notification/options",
|
||||
value: "id",
|
||||
label: "name",
|
||||
onReady: ({ dict }) => {
|
||||
const data = [
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
const providerDefine = ref({});
|
||||
|
||||
async function refreshProviderDefine(type) {
|
||||
providerDefine.value = await api.GetProviderDefine(type);
|
||||
}
|
||||
// watch(
|
||||
// () => {
|
||||
// return props.type;
|
||||
// },
|
||||
// async (value) => {
|
||||
// await refreshProviderDefine(value);
|
||||
// },
|
||||
// {
|
||||
// immediate: true
|
||||
// }
|
||||
// );
|
||||
|
||||
//当不在pipeline中编辑时,可能为空
|
||||
const pipeline = inject("pipeline", null);
|
||||
|
||||
const chooseForm = reactive({
|
||||
show: false,
|
||||
open() {
|
||||
chooseForm.show = true;
|
||||
id: 0,
|
||||
name: "使用默认通知",
|
||||
icon: "ion:notifications"
|
||||
},
|
||||
ok: () => {
|
||||
console.log("choose ok:", selectedId.value);
|
||||
emitValue(selectedId.value);
|
||||
chooseForm.show = false;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
clear,
|
||||
target,
|
||||
selectedId,
|
||||
providerDefine,
|
||||
chooseForm
|
||||
};
|
||||
...dict.data
|
||||
];
|
||||
dict.setData(data);
|
||||
}
|
||||
});
|
||||
const renderLabel = (option: any) => {
|
||||
return <span>{option.name}</span>;
|
||||
};
|
||||
|
||||
async function openTableSelectDialog(e: any) {
|
||||
e.preventDefault();
|
||||
await tableSelectRef.value.open();
|
||||
await tableSelectRef.value.crudExpose.openAdd({});
|
||||
}
|
||||
const selectSlots = ref({
|
||||
dropdownRender({ menuNode }: any) {
|
||||
const res = [];
|
||||
res.push(menuNode);
|
||||
res.push(<a-divider style="margin: 4px 0" />);
|
||||
res.push(<a-space style="padding: 4px 8px" />);
|
||||
res.push(<fs-button class="w-100" type="text" icon="plus-outlined" text="新建通知渠道" onClick={openTableSelectDialog}></fs-button>);
|
||||
return res;
|
||||
}
|
||||
});
|
||||
|
||||
const target: Ref<any> = ref({});
|
||||
|
||||
function clear() {
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
emitValue(null);
|
||||
}
|
||||
|
||||
async function emitValue(value: any) {
|
||||
const target = optionsDictRef.dataMap[value];
|
||||
if (value !== 0 && pipeline?.value && target && pipeline.value.userId !== target.userId) {
|
||||
message.error("对不起,您不能修改他人流水线的通知");
|
||||
return;
|
||||
}
|
||||
emit("change", value);
|
||||
emit("update:modelValue", value);
|
||||
emit("selectedChange", target);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => {
|
||||
return props.modelValue;
|
||||
},
|
||||
async (value) => {
|
||||
await optionsDictRef.loadDict();
|
||||
target.value = optionsDictRef.dataMap[value];
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
//当不在pipeline中编辑时,可能为空
|
||||
const pipeline = inject("pipeline", null);
|
||||
</script>
|
||||
<style lang="less">
|
||||
.notification-selector {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -107,6 +107,7 @@ export default function (certPluginGroup: PluginGroup, formWrapperRef: any): Cre
|
|||
title: "失败通知",
|
||||
type: "text",
|
||||
form: {
|
||||
value: 0,
|
||||
component: {
|
||||
name: NotificationSelector,
|
||||
vModel: "modelValue",
|
||||
|
|
|
@ -140,4 +140,17 @@ export class NotificationController extends CrudController<NotificationService>
|
|||
const res = await this.service.setDefault(id, this.getUserId());
|
||||
return this.ok(res);
|
||||
}
|
||||
|
||||
@Post('/options', { summary: Constants.per.authOnly })
|
||||
async options() {
|
||||
const res = await this.service.list({
|
||||
query: {
|
||||
userId: this.getUserId(),
|
||||
},
|
||||
});
|
||||
for (const item of res) {
|
||||
delete item.setting;
|
||||
}
|
||||
return this.ok(res);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,27 @@ export class DiscordNotification extends BaseNotification {
|
|||
})
|
||||
mentionedList!: string[];
|
||||
|
||||
@NotificationInput({
|
||||
title: '代理',
|
||||
component: {
|
||||
placeholder: 'http://xxxxx:xx',
|
||||
},
|
||||
helper: '使用https_proxy',
|
||||
required: false,
|
||||
})
|
||||
httpsProxy = '';
|
||||
|
||||
@NotificationInput({
|
||||
title: '忽略证书校验',
|
||||
value: false,
|
||||
component: {
|
||||
name: 'a-switch',
|
||||
vModel: 'checked',
|
||||
},
|
||||
required: false,
|
||||
})
|
||||
skipSslVerify: boolean;
|
||||
|
||||
async send(body: NotificationBody) {
|
||||
if (!this.webhook) {
|
||||
throw new Error('Webhook URL 不能为空');
|
||||
|
@ -49,6 +70,8 @@ export class DiscordNotification extends BaseNotification {
|
|||
url: this.webhook,
|
||||
method: 'POST',
|
||||
data: json,
|
||||
httpProxy: this.httpsProxy,
|
||||
skipSslVerify: this.skipSslVerify,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,27 @@ export class SlackNotification extends BaseNotification {
|
|||
})
|
||||
webhook = '';
|
||||
|
||||
@NotificationInput({
|
||||
title: '代理',
|
||||
component: {
|
||||
placeholder: 'http://xxxxx:xx',
|
||||
},
|
||||
helper: '使用https_proxy',
|
||||
required: false,
|
||||
})
|
||||
httpsProxy = '';
|
||||
|
||||
@NotificationInput({
|
||||
title: '忽略证书校验',
|
||||
value: false,
|
||||
component: {
|
||||
name: 'a-switch',
|
||||
vModel: 'checked',
|
||||
},
|
||||
required: false,
|
||||
})
|
||||
skipSslVerify: boolean;
|
||||
|
||||
async send(body: NotificationBody) {
|
||||
if (!this.webhook) {
|
||||
throw new Error('token不能为空');
|
||||
|
@ -28,6 +49,8 @@ export class SlackNotification extends BaseNotification {
|
|||
data: {
|
||||
text: `${body.title}\n${body.content}\n[查看详情](${body.url})`,
|
||||
},
|
||||
httpProxy: this.httpsProxy,
|
||||
skipSslVerify: this.skipSslVerify,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,16 @@ import { BaseNotification, IsNotification, NotificationBody, NotificationInput }
|
|||
needPlus: true,
|
||||
})
|
||||
export class TelegramNotification extends BaseNotification {
|
||||
@NotificationInput({
|
||||
title: 'URL',
|
||||
value: 'https://api.telegram.org',
|
||||
component: {
|
||||
placeholder: 'https://api.telegram.org',
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
endpoint = 'https://api.telegram.org';
|
||||
|
||||
@NotificationInput({
|
||||
title: 'Bot Token',
|
||||
component: {
|
||||
|
@ -27,6 +37,27 @@ export class TelegramNotification extends BaseNotification {
|
|||
})
|
||||
chatId = '';
|
||||
|
||||
@NotificationInput({
|
||||
title: '代理',
|
||||
component: {
|
||||
placeholder: 'http://xxxxx:xx',
|
||||
},
|
||||
helper: '使用https_proxy',
|
||||
required: false,
|
||||
})
|
||||
httpsProxy = '';
|
||||
|
||||
@NotificationInput({
|
||||
title: '忽略证书校验',
|
||||
value: false,
|
||||
component: {
|
||||
name: 'a-switch',
|
||||
vModel: 'checked',
|
||||
},
|
||||
required: false,
|
||||
})
|
||||
skipSslVerify: boolean;
|
||||
|
||||
async send(body: NotificationBody) {
|
||||
if (!this.botToken || !this.chatId) {
|
||||
throw new Error('Bot Token 和聊天ID不能为空');
|
||||
|
@ -47,6 +78,8 @@ export class TelegramNotification extends BaseNotification {
|
|||
text: messageContent,
|
||||
parse_mode: 'MarkdownV2', // 或使用 'HTML' 取决于需要的格式
|
||||
},
|
||||
httpProxy: this.httpsProxy,
|
||||
skipSslVerify: this.skipSslVerify,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue