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 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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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