mirror of https://github.com/certd/certd
perf: 支持github 新版本检查并发布通知
parent
1cae709b2b
commit
356703c83e
|
@ -13,13 +13,14 @@ import ExpiresTimeText from "./expires-time-text.vue";
|
||||||
import FileInput from "./file-input.vue";
|
import FileInput from "./file-input.vue";
|
||||||
import PemInput from "./pem-input.vue";
|
import PemInput from "./pem-input.vue";
|
||||||
import { defineAsyncComponent } from "vue";
|
import { defineAsyncComponent } from "vue";
|
||||||
|
import NotificationSelector from "../views/certd/notification/notification-selector/index.vue";
|
||||||
export default {
|
export default {
|
||||||
install(app: any) {
|
install(app: any) {
|
||||||
app.component(
|
app.component(
|
||||||
"CodeEditor",
|
"CodeEditor",
|
||||||
defineAsyncComponent(() => import("./code-editor/index.vue"))
|
defineAsyncComponent(() => import("./code-editor/index.vue"))
|
||||||
);
|
);
|
||||||
|
app.component("NotificationSelector", NotificationSelector);
|
||||||
app.component("PiContainer", PiContainer);
|
app.component("PiContainer", PiContainer);
|
||||||
app.component("TextEditable", TextEditable);
|
app.component("TextEditable", TextEditable);
|
||||||
app.component("FileInput", FileInput);
|
app.component("FileInput", FileInput);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="notification-selector">
|
<div class="notification-selector">
|
||||||
<div class="flex-o w-100">
|
<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
|
<fs-table-select
|
||||||
ref="tableSelectRef"
|
ref="tableSelectRef"
|
||||||
class="flex-0"
|
class="flex-0"
|
||||||
|
@ -21,6 +21,7 @@
|
||||||
:dialog="{ width: 960 }"
|
:dialog="{ width: 960 }"
|
||||||
:destroy-on-close="false"
|
:destroy-on-close="false"
|
||||||
height="400px"
|
height="400px"
|
||||||
|
v-bind="tableSelect"
|
||||||
@update:model-value="onChange"
|
@update:model-value="onChange"
|
||||||
@dialog-closed="doRefresh"
|
@dialog-closed="doRefresh"
|
||||||
>
|
>
|
||||||
|
@ -39,17 +40,20 @@ import { message } from "ant-design-vue";
|
||||||
import { dict } from "@fast-crud/fast-crud";
|
import { dict } from "@fast-crud/fast-crud";
|
||||||
import createCrudOptions from "../crud";
|
import createCrudOptions from "../crud";
|
||||||
import { notificationProvide } from "/@/views/certd/notification/common";
|
import { notificationProvide } from "/@/views/certd/notification/common";
|
||||||
|
import { useUserStore } from "/@/store/user";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "NotificationSelector",
|
name: "NotificationSelector",
|
||||||
});
|
});
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue?: number | string;
|
modelValue?: number | string | number[] | string[];
|
||||||
type?: string;
|
type?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
size?: string;
|
size?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
select?: any;
|
||||||
|
tableSelect?: any;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const onChange = async (value: number) => {
|
const onChange = async (value: number) => {
|
||||||
|
@ -118,9 +122,12 @@ function clear() {
|
||||||
emitValue(null);
|
emitValue(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
async function emitValue(value: any) {
|
async function emitValue(value: any) {
|
||||||
target.value = optionsDictRef.dataMap[value];
|
// target.value = optionsDictRef.dataMap[value];
|
||||||
if (value !== 0 && pipeline?.value && target && pipeline.value.userId !== target.value.userId) {
|
const userId = userStore.userInfo.id;
|
||||||
|
if (pipeline?.value && pipeline.value.userId !== userId) {
|
||||||
message.error("对不起,您不能修改他人流水线的通知");
|
message.error("对不起,您不能修改他人流水线的通知");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -134,6 +141,7 @@ watch(
|
||||||
},
|
},
|
||||||
async value => {
|
async value => {
|
||||||
await optionsDictRef.loadDict();
|
await optionsDictRef.loadDict();
|
||||||
|
//@ts-ignore
|
||||||
target.value = optionsDictRef.dataMap[value];
|
target.value = optionsDictRef.dataMap[value];
|
||||||
emit("selectedChange", target.value);
|
emit("selectedChange", target.value);
|
||||||
},
|
},
|
||||||
|
|
|
@ -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();
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./plugins/index.js";
|
||||||
|
export * from "./access.js";
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./plugin-check-release.js";
|
|
@ -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();
|
Loading…
Reference in New Issue