mirror of https://github.com/certd/certd
feat: cert download
parent
27a4c81c6d
commit
5a51c14de5
|
@ -20,7 +20,8 @@
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"node-forge": "^1.3.1",
|
"node-forge": "^1.3.1",
|
||||||
"nodemailer": "^6.9.3",
|
"nodemailer": "^6.9.3",
|
||||||
"qs": "^6.11.2"
|
"qs": "^6.11.2",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@certd/acme-client": "^1.0.6",
|
"@certd/acme-client": "^1.0.6",
|
||||||
|
@ -33,6 +34,7 @@
|
||||||
"@types/lodash": "^4.14.194",
|
"@types/lodash": "^4.14.194",
|
||||||
"@types/mocha": "^10.0.1",
|
"@types/mocha": "^10.0.1",
|
||||||
"@types/node-forge": "^1.3.2",
|
"@types/node-forge": "^1.3.2",
|
||||||
|
"@types/uuid": "^9.0.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.59.7",
|
"@typescript-eslint/eslint-plugin": "^5.59.7",
|
||||||
"@typescript-eslint/parser": "^5.59.7",
|
"@typescript-eslint/parser": "^5.59.7",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
|
|
|
@ -29,6 +29,7 @@ export class Executor {
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
pipelineContext!: IContext;
|
pipelineContext!: IContext;
|
||||||
lastStatusMap!: RunnableCollection;
|
lastStatusMap!: RunnableCollection;
|
||||||
|
lastRuntime!: RunHistory;
|
||||||
options: ExecutorOptions;
|
options: ExecutorOptions;
|
||||||
onChanged: (history: RunHistory) => void;
|
onChanged: (history: RunHistory) => void;
|
||||||
constructor(options: ExecutorOptions) {
|
constructor(options: ExecutorOptions) {
|
||||||
|
@ -45,6 +46,7 @@ export class Executor {
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
const lastRuntime = await this.pipelineContext.getObj(`lastRuntime`);
|
const lastRuntime = await this.pipelineContext.getObj(`lastRuntime`);
|
||||||
|
this.lastRuntime = lastRuntime;
|
||||||
this.lastStatusMap = new RunnableCollection(lastRuntime?.pipeline);
|
this.lastStatusMap = new RunnableCollection(lastRuntime?.pipeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +61,9 @@ export class Executor {
|
||||||
await this.runWithHistory(this.pipeline, "pipeline", async () => {
|
await this.runWithHistory(this.pipeline, "pipeline", async () => {
|
||||||
await this.runStages(this.pipeline);
|
await this.runStages(this.pipeline);
|
||||||
});
|
});
|
||||||
|
if (this.lastRuntime && this.lastRuntime.pipeline.status?.status === ResultType.error) {
|
||||||
|
await this.notification("turnToSuccess");
|
||||||
|
}
|
||||||
await this.notification("success");
|
await this.notification("success");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await this.notification("error", e);
|
await this.notification("error", e);
|
||||||
|
@ -233,6 +238,9 @@ export class Executor {
|
||||||
} else if (when === "success") {
|
} else if (when === "success") {
|
||||||
subject = `【CertD】执行成功,${this.pipeline.title}, buildId:${this.runtime.id}`;
|
subject = `【CertD】执行成功,${this.pipeline.title}, buildId:${this.runtime.id}`;
|
||||||
content = subject;
|
content = subject;
|
||||||
|
} else if (when === "turnToSuccess") {
|
||||||
|
subject = `【CertD】执行成功(错误转成功),${this.pipeline.title}, buildId:${this.runtime.id}`;
|
||||||
|
content = subject;
|
||||||
} else if (when === "error") {
|
} else if (when === "error") {
|
||||||
subject = `【CertD】执行失败,${this.pipeline.title}, buildId:${this.runtime.id}`;
|
subject = `【CertD】执行失败,${this.pipeline.title}, buildId:${this.runtime.id}`;
|
||||||
content = `<pre>${error.message}</pre>`;
|
content = `<pre>${error.message}</pre>`;
|
||||||
|
|
|
@ -119,25 +119,25 @@ export class RunnableCollection {
|
||||||
this.collection = map;
|
this.collection = map;
|
||||||
}
|
}
|
||||||
|
|
||||||
private each<T extends Runnable>(list: T[], exec: (item: Runnable) => void) {
|
static each<T extends Runnable>(list: T[], exec: (item: Runnable) => void) {
|
||||||
list.forEach((item) => {
|
list.forEach((item) => {
|
||||||
exec(item);
|
exec(item);
|
||||||
if (item.runnableType === "pipeline") {
|
if (item.runnableType === "pipeline") {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.each<Stage>(item.stages, exec);
|
RunnableCollection.each<Stage>(item.stages, exec);
|
||||||
} else if (item.runnableType === "stage") {
|
} else if (item.runnableType === "stage") {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.each<Task>(item.tasks, exec);
|
RunnableCollection.each<Task>(item.tasks, exec);
|
||||||
} else if (item.runnableType === "task") {
|
} else if (item.runnableType === "task") {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.each<Step>(item.steps, exec);
|
RunnableCollection.each<Step>(item.steps, exec);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
private toMap(pipeline: Pipeline) {
|
public toMap(pipeline: Pipeline) {
|
||||||
const map: RunnableMap = {};
|
const map: RunnableMap = {};
|
||||||
|
|
||||||
this.each(pipeline.stages, (item) => {
|
RunnableCollection.each(pipeline.stages, (item) => {
|
||||||
map[item.id] = item;
|
map[item.id] = item;
|
||||||
});
|
});
|
||||||
return map;
|
return map;
|
||||||
|
@ -151,7 +151,7 @@ export class RunnableCollection {
|
||||||
if (!this.pipeline) {
|
if (!this.pipeline) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.each(this.pipeline.stages, (item) => {
|
RunnableCollection.each(this.pipeline.stages, (item) => {
|
||||||
item.status = undefined;
|
item.status = undefined;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@ export type Trigger = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FileItem = {
|
export type FileItem = {
|
||||||
|
id: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
|
@ -68,13 +69,12 @@ export type Runnable = {
|
||||||
default?: {
|
default?: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
files?: FileItem[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EmailOptions = {
|
export type EmailOptions = {
|
||||||
receivers: string[];
|
receivers: string[];
|
||||||
};
|
};
|
||||||
export type NotificationWhen = "error" | "success" | "start";
|
export type NotificationWhen = "error" | "success" | "turnToSuccess" | "start";
|
||||||
export type NotificationType = "email" | "url";
|
export type NotificationType = "email" | "url";
|
||||||
export type Notification = {
|
export type Notification = {
|
||||||
type: NotificationType;
|
type: NotificationType;
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { IAccessService } from "../access";
|
||||||
import { IEmailService } from "../service";
|
import { IEmailService } from "../service";
|
||||||
import { IContext } from "../core";
|
import { IContext } from "../core";
|
||||||
import { AxiosInstance } from "axios";
|
import { AxiosInstance } from "axios";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
export enum ContextScope {
|
export enum ContextScope {
|
||||||
global,
|
global,
|
||||||
pipeline,
|
pipeline,
|
||||||
|
@ -81,6 +81,7 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
|
||||||
saveFile(filename: string, file: Buffer) {
|
saveFile(filename: string, file: Buffer) {
|
||||||
const filePath = this.ctx.fileStore.writeFile(filename, file);
|
const filePath = this.ctx.fileStore.writeFile(filename, file);
|
||||||
this._result.files!.push({
|
this._result.files!.push({
|
||||||
|
id: uuidv4(),
|
||||||
filename,
|
filename,
|
||||||
path: filePath,
|
path: filePath,
|
||||||
});
|
});
|
||||||
|
|
|
@ -74,7 +74,7 @@
|
||||||
"@vue/compiler-sfc": "^3.2.45",
|
"@vue/compiler-sfc": "^3.2.45",
|
||||||
"@vue/eslint-config-typescript": "^11.0.2",
|
"@vue/eslint-config-typescript": "^11.0.2",
|
||||||
"@vue/test-utils": "^2.2.6",
|
"@vue/test-utils": "^2.2.6",
|
||||||
"autoprefixer": "^10.4.12",
|
"autoprefixer": "^10.4.14",
|
||||||
"caller-path": "^4.0.0",
|
"caller-path": "^4.0.0",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"eslint": "8.29.0",
|
"eslint": "8.29.0",
|
||||||
|
@ -89,7 +89,7 @@
|
||||||
"less": "^4.1.3",
|
"less": "^4.1.3",
|
||||||
"less-loader": "^11.0.0",
|
"less-loader": "^11.0.0",
|
||||||
"lint-staged": "^13.1.0",
|
"lint-staged": "^13.1.0",
|
||||||
"postcss": "^8.4.20",
|
"postcss": "^8.4.23",
|
||||||
"prettier": "2.8.1",
|
"prettier": "2.8.1",
|
||||||
"pretty-quick": "^3.1.3",
|
"pretty-quick": "^3.1.3",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
|
@ -98,7 +98,7 @@
|
||||||
"stylelint": "^14.16.0",
|
"stylelint": "^14.16.0",
|
||||||
"stylelint-config-prettier": "^9.0.4",
|
"stylelint-config-prettier": "^9.0.4",
|
||||||
"stylelint-order": "^5.0.0",
|
"stylelint-order": "^5.0.0",
|
||||||
"tailwindcss": "^3.2.4",
|
"tailwindcss": "^3.3.2",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "4.9.4",
|
"typescript": "4.9.4",
|
||||||
"vite": "^4.0.1",
|
"vite": "^4.0.1",
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {}
|
||||||
|
}
|
||||||
|
};
|
|
@ -11,9 +11,9 @@ import { provide, ref, nextTick } from "vue";
|
||||||
import { usePageStore } from "/src/store/modules/page";
|
import { usePageStore } from "/src/store/modules/page";
|
||||||
import { useResourceStore } from "/src/store/modules/resource";
|
import { useResourceStore } from "/src/store/modules/resource";
|
||||||
import { useSettingStore } from "/@/store/modules/settings";
|
import { useSettingStore } from "/@/store/modules/settings";
|
||||||
import 'dayjs/locale/zh-cn';
|
import "dayjs/locale/zh-cn";
|
||||||
import 'dayjs/locale/en';
|
import "dayjs/locale/en";
|
||||||
import dayjs from 'dayjs'
|
import dayjs from "dayjs";
|
||||||
export default {
|
export default {
|
||||||
name: "App",
|
name: "App",
|
||||||
setup() {
|
setup() {
|
||||||
|
@ -29,13 +29,13 @@ export default {
|
||||||
console.log("locale changed:", value);
|
console.log("locale changed:", value);
|
||||||
if (value === "zh-cn") {
|
if (value === "zh-cn") {
|
||||||
locale.value = zhCN;
|
locale.value = zhCN;
|
||||||
dayjs.locale('zh-cn');
|
dayjs.locale("zh-cn");
|
||||||
} else if (value === "en") {
|
} else if (value === "en") {
|
||||||
locale.value = enUS;
|
locale.value = enUS;
|
||||||
dayjs.locale('en');
|
dayjs.locale("en");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
localeChanged('zh-cn')
|
localeChanged("zh-cn");
|
||||||
provide("fn:router.reload", reload);
|
provide("fn:router.reload", reload);
|
||||||
provide("fn:locale.changed", localeChanged);
|
provide("fn:locale.changed", localeChanged);
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ export function errorLog(error: any) {
|
||||||
error.message = error?.response?.data?.message;
|
error.message = error?.response?.data?.message;
|
||||||
}
|
}
|
||||||
// 打印到控制台
|
// 打印到控制台
|
||||||
console.error(error);
|
console.error("errorLog", error);
|
||||||
// 显示提示
|
// 显示提示
|
||||||
uiContext.get().notification.error({ message: error.message });
|
uiContext.get().notification.error({ message: error.message });
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,7 @@ export function errorLog(error: any) {
|
||||||
*/
|
*/
|
||||||
export function errorCreate(msg: string) {
|
export function errorCreate(msg: string) {
|
||||||
const err = new Error(msg);
|
const err = new Error(msg);
|
||||||
|
console.error("errorCreate", err);
|
||||||
uiContext.get().notification.error({ message: err.message });
|
uiContext.get().notification.error({ message: err.message });
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<template xmlns:w="http://www.w3.org/1999/xhtml">
|
<template>
|
||||||
<a-layout class="fs-framework">
|
<a-layout class="fs-framework">
|
||||||
<a-layout-sider v-model:collapsed="asideCollapsed" :trigger="null" collapsible>
|
<a-layout-sider v-model:collapsed="asideCollapsed" :trigger="null" collapsible>
|
||||||
<div class="header-logo">
|
<div class="header-logo">
|
||||||
|
@ -95,7 +95,7 @@ export default {
|
||||||
}
|
}
|
||||||
onErrorCaptured((e) => {
|
onErrorCaptured((e) => {
|
||||||
console.error("ErrorCaptured:", e);
|
console.error("ErrorCaptured:", e);
|
||||||
notification.error({ message: e.message });
|
// notification.error({ message: e.message });
|
||||||
//阻止错误向上传递
|
//阻止错误向上传递
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,8 +2,6 @@ import { createApp } from "vue";
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
import Antd from "ant-design-vue";
|
import Antd from "ant-design-vue";
|
||||||
import "ant-design-vue/dist/antd.less";
|
|
||||||
// import "virtual:windi.css";
|
|
||||||
import "./style/common.less";
|
import "./style/common.less";
|
||||||
import "./mock";
|
import "./mock";
|
||||||
import i18n from "./i18n";
|
import i18n from "./i18n";
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
@import './tailwind.less';
|
||||||
|
@import "ant-design-vue/dist/antd.less";
|
||||||
@import './theme/index.less';
|
@import './theme/index.less';
|
||||||
@import './theme/default.less';
|
@import './theme/default.less';
|
||||||
@import './scroll.less';
|
@import './scroll.less';
|
||||||
|
@ -49,9 +51,7 @@ h1, h2, h3, h4, h5, h6 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.red{
|
|
||||||
color:red
|
|
||||||
}
|
|
||||||
|
|
||||||
.font12{
|
.font12{
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
@ -88,27 +88,6 @@ h1, h2, h3, h4, h5, h6 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-10{
|
|
||||||
margin-top:10px;
|
|
||||||
}
|
|
||||||
.ml-5{
|
|
||||||
margin-left:5px;
|
|
||||||
}
|
|
||||||
.ml-10{
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
.mtb-5{
|
|
||||||
margin: 5px 0 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mb-10{
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mlr-5{
|
|
||||||
margin: 0 5px 0 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gray{
|
.gray{
|
||||||
color:gray;
|
color:gray;
|
||||||
}
|
}
|
||||||
|
@ -125,10 +104,6 @@ h1, h2, h3, h4, h5, h6 {
|
||||||
color:yellow;
|
color:yellow;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ml-2{
|
|
||||||
margin-left: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.font-20{
|
.font-20{
|
||||||
font-size:20px
|
font-size:20px
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
img.ant-image-preview-img{
|
img.ant-image-preview-img{
|
||||||
display: initial;
|
display: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
|
@ -1,7 +1,9 @@
|
||||||
import { request } from "/src/api/service";
|
import { request } from "/src/api/service";
|
||||||
const apiPrefix = "/pi/pipeline";
|
|
||||||
|
|
||||||
export function GetList(query) {
|
const apiPrefix = "/pi/pipeline";
|
||||||
|
const historyApiPrefix = "/pi/history";
|
||||||
|
|
||||||
|
export function GetList(query: any) {
|
||||||
return request({
|
return request({
|
||||||
url: apiPrefix + "/page",
|
url: apiPrefix + "/page",
|
||||||
method: "post",
|
method: "post",
|
||||||
|
@ -9,7 +11,7 @@ export function GetList(query) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AddObj(obj) {
|
export function AddObj(obj: any) {
|
||||||
return request({
|
return request({
|
||||||
url: apiPrefix + "/add",
|
url: apiPrefix + "/add",
|
||||||
method: "post",
|
method: "post",
|
||||||
|
@ -17,7 +19,7 @@ export function AddObj(obj) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UpdateObj(obj) {
|
export function UpdateObj(obj: any) {
|
||||||
return request({
|
return request({
|
||||||
url: apiPrefix + "/update",
|
url: apiPrefix + "/update",
|
||||||
method: "post",
|
method: "post",
|
||||||
|
@ -25,7 +27,7 @@ export function UpdateObj(obj) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DelObj(id) {
|
export function DelObj(id: any) {
|
||||||
return request({
|
return request({
|
||||||
url: apiPrefix + "/delete",
|
url: apiPrefix + "/delete",
|
||||||
method: "post",
|
method: "post",
|
||||||
|
@ -33,7 +35,7 @@ export function DelObj(id) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GetObj(id) {
|
export function GetObj(id: any) {
|
||||||
return request({
|
return request({
|
||||||
url: apiPrefix + "/info",
|
url: apiPrefix + "/info",
|
||||||
method: "post",
|
method: "post",
|
||||||
|
@ -41,7 +43,7 @@ export function GetObj(id) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GetDetail(id) {
|
export function GetDetail(id: any) {
|
||||||
return request({
|
return request({
|
||||||
url: apiPrefix + "/detail",
|
url: apiPrefix + "/detail",
|
||||||
method: "post",
|
method: "post",
|
||||||
|
@ -49,7 +51,7 @@ export function GetDetail(id) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Save(pipelineEntity) {
|
export function Save(pipelineEntity: any) {
|
||||||
return request({
|
return request({
|
||||||
url: apiPrefix + "/save",
|
url: apiPrefix + "/save",
|
||||||
method: "post",
|
method: "post",
|
||||||
|
@ -57,10 +59,18 @@ export function Save(pipelineEntity) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Trigger(id) {
|
export function Trigger(id: any) {
|
||||||
return request({
|
return request({
|
||||||
url: apiPrefix + "/trigger",
|
url: apiPrefix + "/trigger",
|
||||||
method: "post",
|
method: "post",
|
||||||
params: { id }
|
params: { id }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function GetFiles(pipelineId: number) {
|
||||||
|
return request({
|
||||||
|
url: historyApiPrefix + "/files",
|
||||||
|
method: "post",
|
||||||
|
params: { pipelineId }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,9 @@ import { useRouter } from "vue-router";
|
||||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||||
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
|
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { message } from "ant-design-vue";
|
import { message, Modal } from "ant-design-vue";
|
||||||
|
import { env } from "/@/utils/util.env";
|
||||||
|
import { useUserStore } from "/@/store/modules/user";
|
||||||
|
|
||||||
export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -73,6 +75,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
|
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const userStore = useUserStore();
|
||||||
return {
|
return {
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
request: {
|
request: {
|
||||||
|
@ -124,6 +127,33 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
order: 2,
|
order: 2,
|
||||||
icon: "ant-design:setting-outlined"
|
icon: "ant-design:setting-outlined"
|
||||||
},
|
},
|
||||||
|
download: {
|
||||||
|
order: 3,
|
||||||
|
title: null,
|
||||||
|
type: "link",
|
||||||
|
icon: "ant-design:download-outlined",
|
||||||
|
async click({ row }) {
|
||||||
|
const files = await api.GetFiles(row.id);
|
||||||
|
Modal.success({
|
||||||
|
title: "文件下载",
|
||||||
|
content: () => {
|
||||||
|
const children = [];
|
||||||
|
for (const file of files) {
|
||||||
|
const downloadUrl = `${env.API}/pi/history/download?pipelineId=${row.id}&fileId=${file.id}`;
|
||||||
|
children.push(
|
||||||
|
<p>
|
||||||
|
<a href={downloadUrl} target={"_blank"}>
|
||||||
|
{file.filename}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div class={"mt-3"}>{children}</div>;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
remove: {
|
remove: {
|
||||||
order: 5
|
order: 5
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
</template>
|
</template>
|
||||||
<p>
|
<p>
|
||||||
<fs-date-format :model-value="runnable.status?.startTime"></fs-date-format>
|
<fs-date-format :model-value="runnable.status?.startTime"></fs-date-format>
|
||||||
<a-tag class="ml-10" :color="status.color">{{ status.label }}</a-tag>
|
<a-tag class="ml-1" :color="status.color">{{ status.label }}</a-tag>
|
||||||
|
|
||||||
<a-tag v-if="isCurrent" class="pointer" color="green" :closable="true" @close="cancel">当前</a-tag>
|
<a-tag v-if="isCurrent" class="pointer" color="green" :closable="true" @close="cancel">当前</a-tag>
|
||||||
<a-tag v-else-if="!editMode" class="pointer" color="blue" @click="view">查看</a-tag>
|
<a-tag v-else-if="!editMode" class="pointer" color="blue" @click="view">查看</a-tag>
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
options: [
|
options: [
|
||||||
{ value: 'start', label: '开始时' },
|
{ value: 'start', label: '开始时' },
|
||||||
{ value: 'success', label: '成功时' },
|
{ value: 'success', label: '成功时' },
|
||||||
|
{ value: 'turnToSuccess', label: '错误转成功时' },
|
||||||
{ value: 'error', label: '错误时' }
|
{ value: 'error', label: '错误时' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<div style="margin-top: 10px">
|
<div style="margin-top: 10px">
|
||||||
<a-button @click="formSubmit">提交表单</a-button>
|
<a-button @click="formSubmit">提交表单</a-button>
|
||||||
<a-button @click="formReset">重置表单</a-button>
|
<a-button @click="formReset">重置表单</a-button>
|
||||||
<a-button class="ml-10" @click="setFormDataTest">setFormData</a-button>
|
<a-button class="ml-1" @click="setFormDataTest">setFormData</a-button>
|
||||||
</div>
|
</div>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
purge: [],
|
darkMode: "class",
|
||||||
darkMode: false, // or 'media' or 'class'
|
important: true,
|
||||||
|
content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {}
|
extend: {}
|
||||||
},
|
},
|
||||||
variants: {
|
|
||||||
extend: {}
|
|
||||||
},
|
|
||||||
plugins: []
|
plugins: []
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,6 @@ import visualizer from "rollup-plugin-visualizer";
|
||||||
import viteCompression from "vite-plugin-compression";
|
import viteCompression from "vite-plugin-compression";
|
||||||
import PurgeIcons from "vite-plugin-purge-icons";
|
import PurgeIcons from "vite-plugin-purge-icons";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
// import WindiCSS from "vite-plugin-windicss";
|
|
||||||
// import { generateModifyVars } from "./build/modify-vars";
|
// import { generateModifyVars } from "./build/modify-vars";
|
||||||
// import { configThemePlugin } from "./build/theme-plugin";
|
// import { configThemePlugin } from "./build/theme-plugin";
|
||||||
// import OptimizationPersist from "vite-plugin-optimize-persist";
|
// import OptimizationPersist from "vite-plugin-optimize-persist";
|
||||||
|
|
|
@ -45,10 +45,10 @@
|
||||||
"@midwayjs/validate": "^3.9.0",
|
"@midwayjs/validate": "^3.9.0",
|
||||||
"cache-manager": "^3.6.3",
|
"cache-manager": "^3.6.3",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"dotenv": "^16.0.3",
|
|
||||||
"glob": "^7.2.3",
|
"glob": "^7.2.3",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
"koa-send": "^5.0.1",
|
||||||
"kubernetes-client": "^9.0.0",
|
"kubernetes-client": "^9.0.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"log4js": "^6.7.1",
|
"log4js": "^6.7.1",
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { BaseException } from './base-exception';
|
||||||
* 授权异常
|
* 授权异常
|
||||||
*/
|
*/
|
||||||
export class PermissionException extends BaseException {
|
export class PermissionException extends BaseException {
|
||||||
constructor(message) {
|
constructor(message?: string) {
|
||||||
super(
|
super(
|
||||||
'PermissionException',
|
'PermissionException',
|
||||||
Constants.res.permission.code,
|
Constants.res.permission.code,
|
||||||
|
|
|
@ -25,6 +25,11 @@ export class AuthorityMiddleware implements IWebMiddleware {
|
||||||
ctx.path,
|
ctx.path,
|
||||||
ctx.method
|
ctx.method
|
||||||
);
|
);
|
||||||
|
if (routeInfo == null) {
|
||||||
|
// 404
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const permission = routeInfo.summary;
|
const permission = routeInfo.summary;
|
||||||
if (permission == null || permission === '') {
|
if (permission == null || permission === '') {
|
||||||
ctx.status = 500;
|
ctx.status = 500;
|
||||||
|
@ -41,6 +46,14 @@ export class AuthorityMiddleware implements IWebMiddleware {
|
||||||
|
|
||||||
let token = ctx.get('Authorization') || '';
|
let token = ctx.get('Authorization') || '';
|
||||||
token = token.replace('Bearer ', '').trim();
|
token = token.replace('Bearer ', '').trim();
|
||||||
|
if (token === '') {
|
||||||
|
//尝试从cookie中获取token
|
||||||
|
token = ctx.cookies.get('token') || '';
|
||||||
|
}
|
||||||
|
if (token === '') {
|
||||||
|
//尝试从query中获取token
|
||||||
|
token = (ctx.query.token as string) || '';
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
ctx.user = jwt.verify(token, this.secret);
|
ctx.user = jwt.verify(token, this.secret);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -23,6 +23,11 @@ export class LoginController extends BaseController {
|
||||||
user
|
user
|
||||||
) {
|
) {
|
||||||
const token = await this.loginService.login(user);
|
const token = await this.loginService.login(user);
|
||||||
|
|
||||||
|
this.ctx.cookies.set('token', token.token, {
|
||||||
|
maxAge: 1000 * token.expire,
|
||||||
|
});
|
||||||
|
|
||||||
return this.ok(token);
|
return this.ok(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {
|
||||||
ALL,
|
ALL,
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
|
Get,
|
||||||
Inject,
|
Inject,
|
||||||
Post,
|
Post,
|
||||||
Provide,
|
Provide,
|
||||||
|
@ -13,7 +14,11 @@ import { HistoryService } from '../service/history-service';
|
||||||
import { HistoryLogService } from '../service/history-log-service';
|
import { HistoryLogService } from '../service/history-log-service';
|
||||||
import { HistoryEntity } from '../entity/history';
|
import { HistoryEntity } from '../entity/history';
|
||||||
import { HistoryLogEntity } from '../entity/history-log';
|
import { HistoryLogEntity } from '../entity/history-log';
|
||||||
import {Constants} from "../../../basic/constants";
|
import { Constants } from '../../../basic/constants';
|
||||||
|
import { PipelineService } from '../service/pipeline-service';
|
||||||
|
import { CommonException } from '../../../basic/exception/common-exception';
|
||||||
|
import { PermissionException } from '../../../basic/exception/permission-exception';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 证书
|
* 证书
|
||||||
|
@ -24,6 +29,8 @@ export class HistoryController extends CrudController<HistoryService> {
|
||||||
@Inject()
|
@Inject()
|
||||||
service: HistoryService;
|
service: HistoryService;
|
||||||
@Inject()
|
@Inject()
|
||||||
|
pipelineService: PipelineService;
|
||||||
|
@Inject()
|
||||||
logService: HistoryLogService;
|
logService: HistoryLogService;
|
||||||
|
|
||||||
getService() {
|
getService() {
|
||||||
|
@ -104,4 +111,51 @@ export class HistoryController extends CrudController<HistoryService> {
|
||||||
const logInfo = await this.logService.info(id);
|
const logInfo = await this.logService.info(id);
|
||||||
return this.ok(logInfo);
|
return this.ok(logInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('/files', { summary: Constants.per.authOnly })
|
||||||
|
async files(@Query('pipelineId') pipelineId, @Query('historyId') historyId) {
|
||||||
|
const files = await this.getFiles(historyId, pipelineId);
|
||||||
|
return this.ok(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getFiles(historyId, pipelineId) {
|
||||||
|
let history = null;
|
||||||
|
if (historyId != null) {
|
||||||
|
// nothing
|
||||||
|
history = await this.service.info(historyId);
|
||||||
|
} else if (pipelineId != null) {
|
||||||
|
history = await this.service.getLastHistory(pipelineId);
|
||||||
|
}
|
||||||
|
if (history == null) {
|
||||||
|
throw new CommonException('historyId is null');
|
||||||
|
}
|
||||||
|
if (history.userId !== this.ctx.user.id) {
|
||||||
|
throw new PermissionException();
|
||||||
|
}
|
||||||
|
return await this.service.getFiles(history);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/download', { summary: Constants.per.authOnly })
|
||||||
|
async download(
|
||||||
|
@Query('pipelineId') pipelineId,
|
||||||
|
@Query('historyId') historyId,
|
||||||
|
@Query('fileId') fileId
|
||||||
|
) {
|
||||||
|
const files = await this.getFiles(historyId, pipelineId);
|
||||||
|
const file = files.find(f => f.id === fileId);
|
||||||
|
if (file == null) {
|
||||||
|
throw new CommonException('file not found');
|
||||||
|
}
|
||||||
|
// koa send file
|
||||||
|
// 下载文件的名称
|
||||||
|
// const filename = file.filename;
|
||||||
|
// 要下载的文件的完整路径
|
||||||
|
const path = file.path;
|
||||||
|
|
||||||
|
// 以流的形式下载文件
|
||||||
|
this.ctx.attachment(path);
|
||||||
|
this.ctx.set('Content-Type', 'application/octet-stream');
|
||||||
|
|
||||||
|
return fs.createReadStream(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/decorator";
|
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/decorator';
|
||||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { BaseService } from '../../../basic/base-service';
|
import { BaseService } from '../../../basic/base-service';
|
||||||
|
@ -6,6 +6,7 @@ import { HistoryEntity } from '../entity/history';
|
||||||
import { PipelineEntity } from '../entity/pipeline';
|
import { PipelineEntity } from '../entity/pipeline';
|
||||||
import { HistoryDetail } from '../entity/vo/history-detail';
|
import { HistoryDetail } from '../entity/vo/history-detail';
|
||||||
import { HistoryLogService } from './history-log-service';
|
import { HistoryLogService } from './history-log-service';
|
||||||
|
import { FileItem, Pipeline, RunnableCollection } from '@certd/pipeline';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 证书申请
|
* 证书申请
|
||||||
|
@ -78,4 +79,29 @@ export class HistoryService extends BaseService<HistoryEntity> {
|
||||||
shouldDeleteCount -= deleteCountBatch;
|
shouldDeleteCount -= deleteCountBatch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getLastHistory(pipelineId: number) {
|
||||||
|
return await this.repository.findOne({
|
||||||
|
where: {
|
||||||
|
pipelineId,
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
id: 'DESC',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFiles(history: HistoryEntity) {
|
||||||
|
const status: Pipeline = JSON.parse(history.pipeline);
|
||||||
|
const files: FileItem[] = [];
|
||||||
|
RunnableCollection.each([status], runnable => {
|
||||||
|
if (runnable.runnableType !== 'step') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (runnable.status?.files != null) {
|
||||||
|
files.push(...runnable.status.files);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return files;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue