perf: 支持github 新版本检查并发布通知

pull/409/head
xiaojunnuo 2025-05-29 00:08:10 +08:00
parent 1cae709b2b
commit 356703c83e
6 changed files with 255 additions and 5 deletions

View File

@ -13,13 +13,14 @@ import ExpiresTimeText from "./expires-time-text.vue";
import FileInput from "./file-input.vue";
import PemInput from "./pem-input.vue";
import { defineAsyncComponent } from "vue";
import NotificationSelector from "../views/certd/notification/notification-selector/index.vue";
export default {
install(app: any) {
app.component(
"CodeEditor",
defineAsyncComponent(() => import("./code-editor/index.vue"))
);
app.component("NotificationSelector", NotificationSelector);
app.component("PiContainer", PiContainer);
app.component("TextEditable", TextEditable);
app.component("FileInput", FileInput);

View File

@ -1,7 +1,7 @@
<template>
<div class="notification-selector">
<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-dict-select class="flex-1" :value="modelValue" :dict="optionsDictRef" :disabled="disabled" :render-label="renderLabel" :slots="selectSlots" :allow-clear="true" v-bind="select" @update:value="onChange" />
<fs-table-select
ref="tableSelectRef"
class="flex-0"
@ -21,6 +21,7 @@
:dialog="{ width: 960 }"
:destroy-on-close="false"
height="400px"
v-bind="tableSelect"
@update:model-value="onChange"
@dialog-closed="doRefresh"
>
@ -39,17 +40,20 @@ import { message } from "ant-design-vue";
import { dict } from "@fast-crud/fast-crud";
import createCrudOptions from "../crud";
import { notificationProvide } from "/@/views/certd/notification/common";
import { useUserStore } from "/@/store/user";
defineOptions({
name: "NotificationSelector",
});
const props = defineProps<{
modelValue?: number | string;
modelValue?: number | string | number[] | string[];
type?: string;
placeholder?: string;
size?: string;
disabled?: boolean;
select?: any;
tableSelect?: any;
}>();
const onChange = async (value: number) => {
@ -118,9 +122,12 @@ function clear() {
emitValue(null);
}
const userStore = useUserStore();
async function emitValue(value: any) {
target.value = optionsDictRef.dataMap[value];
if (value !== 0 && pipeline?.value && target && pipeline.value.userId !== target.value.userId) {
// target.value = optionsDictRef.dataMap[value];
const userId = userStore.userInfo.id;
if (pipeline?.value && pipeline.value.userId !== userId) {
message.error("对不起,您不能修改他人流水线的通知");
return;
}
@ -134,6 +141,7 @@ watch(
},
async value => {
await optionsDictRef.loadDict();
//@ts-ignore
target.value = optionsDictRef.dataMap[value];
emit("selectedChange", target.value);
},

View File

@ -0,0 +1,139 @@
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
import { HttpRequestConfig } from "@certd/basic";
/**
*/
@IsAccess({
name: "github",
title: "Github授权",
desc: "",
icon: "ion:logo-github"
})
export class GithubAccess extends BaseAccess {
@AccessInput({
title: "接口地址",
component: {
placeholder: "可以使用反向代理地址",
component: {
name: "a-input",
vModel: "value"
}
},
helper:"默认值https://api.github.com",
encrypt: false,
required: false
})
endpoint!: string;
@AccessInput({
title: "GithubToken",
component: {
placeholder: "GithubToken",
component: {
name: "a-input",
vModel: "value"
}
},
helper:"支持匿名访问的接口可以不填",
encrypt: true,
required: false
})
githubToken!: string;
@AccessInput({
title: "HttpProxy",
component: {
placeholder: "http://192.168.x.x:10811",
component: {
name: "a-input",
vModel: "value"
}
},
encrypt: false,
required: false
})
httpProxy!: string;
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
},
helper: "点击测试接口是否正常"
})
testRequest = true;
async onTestRequest() {
await this.getRelease({repoName:"certd/certd"})
return "ok"
}
async getRelease(req:{repoName:string}){
const url = `/repos/${req.repoName}/releases/latest`;
return await this.doRequest({
url,
method: "GET",
data:{}
});
}
async doRequest(req:HttpRequestConfig){
/**
* async function getLatestRelease() {
* const { REPO_OWNER, REPO_NAME, API_URL, TOKEN } = CONFIG.GITHUB;
* const url = `${API_URL}/${REPO_OWNER}/${REPO_NAME}/releases/latest`;
*
* try {
* const response = await axios.get(url, {
* headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
* });
*
* return {
* tag_name: response.data.tag_name,
* name: response.data.name || '无标题',
* body: response.data.body || '无描述内容',
* html_url: response.data.html_url,
* published_at: new Date(response.data.published_at).toLocaleString(),
* assets: response.data.assets.map(a => ({
* name: a.name,
* download_url: a.browser_download_url
* }))
* };
* } catch (error) {
* if (error.response?.status === 404) {
* return { success: false, error: '仓库未找到或没有Release' };
* }
* return { success: false, error: `请求失败: ${error.message}` };
* }
* }
*/
const headers:any = {}
if(this.githubToken){
headers.Authorization = `token ${this.githubToken}`
}
const baseURL= this.endpoint || "https://api.github.com";
const res = await this.ctx.http.request({
url: req.url,
baseURL,
method: req.method || "POST",
data: req.data,
headers,
httpProxy: this.httpProxy||undefined,
});
if (res) {
return res;
}
throw new Error(res.message || res);
}
}
new GithubAccess();

View File

@ -0,0 +1,2 @@
export * from "./plugins/index.js";
export * from "./access.js";

View File

@ -0,0 +1 @@
export * from "./plugin-check-release.js";

View File

@ -0,0 +1,99 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from "@certd/pipeline";
import { GithubAccess } from "../access.js";
@IsTaskPlugin({
//命名规范,插件类型+功能就是目录plugin-demo中的demo大写字母开头驼峰命名
name: "GithubCheckRelease",
title: "Github-检查Release版本",
desc:"检查最新Release版本并推送消息",
icon: "ion:logo-github",
//插件分组
group: pluginGroups.other.key,
needPlus: false,
default: {
//默认值配置照抄即可
strategy: {
runStrategy: RunStrategy.AlwaysRun
}
}
})
//类名规范跟上面插件名称name一致
export class GithubCheckRelease extends AbstractTaskPlugin {
//授权选择框
@TaskInput({
title: "Github授权",
component: {
name: "access-selector",
type: "github" //固定授权类型
},
required: true //必填
})
accessId!: string;
@TaskInput({
title: "仓库名称",
helper:"owner/name比如 certd/certd",
required:true,
})
repoName!: string;
@TaskInput({
title: "通知渠道",
component:{
name:"notification-selector",
select:{
mode:"tags"
}
},
required:true,
})
notificationIds!: number[];
@TaskOutput({
title: "最后版本",
})
lastVersion?: string;
//插件实例化时执行的方法
async onInstance() {
}
//插件执行方法
async execute(): Promise<void> {
const access = await this.getAccess<GithubAccess>(this.accessId);
const res = await access.getRelease({repoName:this.repoName})
const lastVersion = this.ctx.lastStatus?.status?.output?.lastVersion;
if(res.tag_name == null || res.tag_name ==lastVersion){
this.logger.info(`暂无更新,${res.tag_name}`);
return
}
//有更新
this.logger.info(`有更新,${lastVersion}->${res.tag_name}`)
this.lastVersion = res.tag_name;
//发送通知
for (const notificationId of this.notificationIds) {
await this.ctx.notificationService.send({
id: notificationId,
useDefault: false,
useEmail:false,
logger: this.logger,
body: {
title: `${this.repoName} 新版本 ${this.lastVersion} 发布`,
content: `${res.body}`,
url: `https://github.com/${this.repoName}/releases/tag/${this.lastVersion}`,
}
})
}
}
}
new GithubCheckRelease();