Compare commits

...

11 Commits

Author SHA1 Message Date
xiaojunnuo
fbf4959463 v1.22.9 2024-08-05 16:10:39 +08:00
xiaojunnuo
02bb0be06a chore: 2024-08-05 16:07:28 +08:00
xiaojunnuo
87e440ee2a perf: 优化定时任务 2024-08-05 16:00:04 +08:00
xiaojunnuo
2182dce07c chore: 修复pipelineid为空被注册任务 2024-08-05 15:08:24 +08:00
xiaojunnuo
3f0a10007c build: trigger build image 2024-08-05 13:20:09 +08:00
xiaojunnuo
67934cdebd v1.22.8 2024-08-05 13:19:57 +08:00
xiaojunnuo
6765a48706 chore: 2024-08-05 13:04:36 +08:00
xiaojunnuo
b4252033d5 perf: 优化pipeline删除时,删除其他history 2024-08-05 12:57:13 +08:00
xiaojunnuo
f78ae93eed perf: 修复删除历史记录没有删除log的bug,新增history管理页面,演示站点启动时不自动启动非管理员用户的定时任务 2024-08-05 12:49:44 +08:00
xiaojunnuo
0227155ab4 chore: 2024-08-04 22:46:08 +08:00
xiaojunnuo
330b84de33 build: trigger build image 2024-08-04 22:33:08 +08:00
46 changed files with 762 additions and 126 deletions

View File

@@ -3,6 +3,19 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.9](https://github.com/certd/certd/compare/v1.22.8...v1.22.9) (2024-08-05)
### Performance Improvements
* 优化定时任务 ([87e440e](https://github.com/certd/certd/commit/87e440ee2a8b10dc571ce619f28bc83c1e5eb147))
## [1.22.8](https://github.com/certd/certd/compare/v1.22.7...v1.22.8) (2024-08-05)
### Performance Improvements
* 修复删除历史记录没有删除log的bug新增history管理页面演示站点启动时不自动启动非管理员用户的定时任务 ([f78ae93](https://github.com/certd/certd/commit/f78ae93eedfe214008c3d071ca3d77c962137a64))
* 优化pipeline删除时删除其他history ([b425203](https://github.com/certd/certd/commit/b4252033d56a9ad950f3e204ff021497c3978015))
## [1.22.7](https://github.com/certd/certd/compare/v1.22.6...v1.22.7) (2024-08-04)
### Bug Fixes

View File

@@ -1 +1 @@
02:55
13:20

8
doc/server/free.md Normal file
View File

@@ -0,0 +1,8 @@
# 免费服务器部署
## 1. 注册koyeb账号
https://app.koyeb.com/
## 2. 创建应用

View File

@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.22.7"
"version": "1.22.9"
}

View File

@@ -14,7 +14,8 @@
"i-all": "lerna link && lerna exec npm install ",
"publish": "npm run prepublishOnly1 && lerna publish --conventional-commits --create-release github && npm run afterpublishOnly",
"afterpublishOnly": "time /t >build.trigger && git add ./build.trigger && git commit -m \"build: trigger build image\" && TIMEOUT /T 10 && git push",
"prepublishOnly1": "npm run check && npm run before-build && lerna run build ",
"prepublishOnly1": "npm run check && lerna run build ",
"prepublishOnly2": "npm run check && npm run before-build && lerna run build ",
"before-build": "cd ./packages/core/pipeline && time /t >build.md && git add ./build.md && git commit -m \"build: prepare to build\"",
"deploy1": "node --experimental-json-modules deploy.js ",
"check": "node --experimental-json-modules publish-check.js",

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.8](https://github.com/certd/certd/compare/v1.22.7...v1.22.8) (2024-08-05)
### Performance Improvements
* 优化pipeline删除时删除其他history ([b425203](https://github.com/certd/certd/commit/b4252033d56a9ad950f3e204ff021497c3978015))
## [1.22.7](https://github.com/certd/certd/compare/v1.22.6...v1.22.7) (2024-08-04)
**Note:** Version bump only for package @certd/pipeline

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.22.7",
"version": "1.22.8",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",

View File

@@ -18,7 +18,9 @@ export interface IFileStore {
export class FileStore {
rootDir: string;
// pipelineId
scope: string;
// historyId
parent: string;
constructor(options?: FileStoreOptions) {
this.rootDir = fileUtils.getFileRootDir(options?.rootDir);

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.8](https://github.com/certd/certd/compare/v1.22.7...v1.22.8) (2024-08-05)
**Note:** Version bump only for package @certd/lib-k8s
## [1.22.7](https://github.com/certd/certd/compare/v1.22.6...v1.22.7) (2024-08-04)
**Note:** Version bump only for package @certd/lib-k8s

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.22.7",
"version": "1.22.8",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -17,7 +17,7 @@
"shelljs": "^0.8.5"
},
"devDependencies": {
"@certd/pipeline": "^1.22.7",
"@certd/pipeline": "^1.22.8",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.0.1",

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.8](https://github.com/certd/certd/compare/v1.22.7...v1.22.8) (2024-08-05)
**Note:** Version bump only for package @certd/plugin-cert
## [1.22.7](https://github.com/certd/certd/compare/v1.22.6...v1.22.7) (2024-08-04)
**Note:** Version bump only for package @certd/plugin-cert

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.22.7",
"version": "1.22.8",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -14,7 +14,7 @@
},
"dependencies": {
"@certd/acme-client": "^1.22.6",
"@certd/pipeline": "^1.22.7",
"@certd/pipeline": "^1.22.8",
"jszip": "^3.10.1",
"node-forge": "^0.10.0",
"psl": "^1.9.0"

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.9](https://github.com/certd/certd/compare/v1.22.8...v1.22.9) (2024-08-05)
### Performance Improvements
* 优化定时任务 ([87e440e](https://github.com/certd/certd/commit/87e440ee2a8b10dc571ce619f28bc83c1e5eb147))
## [1.22.8](https://github.com/certd/certd/compare/v1.22.7...v1.22.8) (2024-08-05)
### Performance Improvements
* 修复删除历史记录没有删除log的bug新增history管理页面演示站点启动时不自动启动非管理员用户的定时任务 ([f78ae93](https://github.com/certd/certd/commit/f78ae93eedfe214008c3d071ca3d77c962137a64))
## [1.22.7](https://github.com/certd/certd/compare/v1.22.6...v1.22.7) (2024-08-04)
**Note:** Version bump only for package @certd/ui-client

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.22.7",
"version": "1.22.9",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -57,7 +57,7 @@
"vuedraggable": "^2.24.3"
},
"devDependencies": {
"@certd/pipeline": "^1.22.7",
"@certd/pipeline": "^1.22.8",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",

View File

@@ -1,9 +1,9 @@
import { request } from "../service";
export type SysPublicSetting = {
registerEnabled:boolean
}
registerEnabled: boolean;
managerOtherUserPipeline: boolean;
};
export async function getSysPublicSettings(): Promise<SysPublicSetting> {
return await request({

View File

@@ -18,6 +18,7 @@ export interface UserInfoRes {
id: string | number;
username: string;
nickName: string;
roles: number[];
}
export interface LoginRes {

View File

@@ -27,6 +27,15 @@ export const certdResources = [
isMenu: false
}
},
{
title: "执行历史记录",
name: "pipelineHistory",
path: "/certd/history",
component: "/certd/history/index.vue",
meta: {
icon: "ion:timer-outline"
}
},
{
title: "授权管理",
name: "access",

View File

@@ -4,9 +4,8 @@ import _ from "lodash-es";
// @ts-ignore
import { LocalStorage } from "/src/utils/util.storage";
import * as basicApi from "/@/api/modules/api.basic";
import { SysPublicSetting } from "/@/api/modules/api.basic";
import * as basicApi from '/@/api/modules/api.basic'
import _ from "lodash-es";
export type ThemeToken = {
token: {
@@ -21,7 +20,7 @@ export type ThemeConfig = {
export interface SettingState {
themeConfig?: ThemeConfig;
themeToken: ThemeToken;
sysPublic?: SysPublicSetting
sysPublic?: SysPublicSetting;
}
const defaultThemeConfig = {
@@ -38,21 +37,22 @@ export const useSettingStore = defineStore({
algorithm: theme.defaultAlgorithm
},
sysPublic: {
registerEnabled: false
registerEnabled: false,
managerOtherUserPipeline: false
}
}),
getters: {
getThemeConfig(): any {
return this.themeConfig || _.merge({}, defaultThemeConfig, LocalStorage.get(SETTING_THEME_KEY) || {});
},
getSysPublic():SysPublicSetting{
return this.sysPublic
getSysPublic(): SysPublicSetting {
return this.sysPublic;
}
},
actions: {
async loadSysSettings(){
const settings = await basicApi.getSysPublicSettings()
_.merge(this.sysPublic,settings)
async loadSysSettings() {
const settings = await basicApi.getSysPublicSettings();
_.merge(this.sysPublic, settings);
},
persistThemeConfig() {
LocalStorage.set(SETTING_THEME_KEY, this.getThemeConfig);
@@ -92,7 +92,7 @@ export const useSettingStore = defineStore({
},
async init() {
await this.setThemeConfig(this.getThemeConfig);
await this.loadSysSettings()
await this.loadSysSettings();
}
}
});

View File

@@ -34,6 +34,9 @@ export const useUserStore = defineStore({
},
getToken(): string {
return this.token || LocalStorage.get(TOKEN_KEY);
},
isAdmin(): boolean {
return this.getUserInfo?.id === 1;
}
},
actions: {

View File

@@ -0,0 +1,59 @@
import { request } from "/src/api/service";
const apiPrefix = "/pi/history";
export function GetList(query: any) {
return request({
url: apiPrefix + "/page",
method: "post",
data: query
});
}
export function AddObj(obj: any) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj: any) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id: any) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id: any) {
return request({
url: apiPrefix + "/info",
method: "post",
params: { id }
});
}
export function GetDetail(id: any) {
return request({
url: apiPrefix + "/detail",
method: "post",
params: { id }
});
}
export function DeleteBatch(ids: any[]) {
return request({
url: apiPrefix + "/deleteByIds",
method: "post",
data: { ids }
});
}

View File

@@ -0,0 +1,154 @@
import * as api from "./api";
import { useI18n } from "vue-i18n";
import { computed, Ref, ref } from "vue";
import { useRouter } from "vue-router";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes, utils } from "@fast-crud/fast-crud";
import { useUserStore } from "/@/store/modules/user";
import { useSettingStore } from "/@/store/modules/settings";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
const { t } = useI18n();
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }: EditReq) => {
form.id = row.id;
const res = await api.UpdateObj(form);
return res;
};
const delRequest = async ({ row }: DelReq) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }: AddReq) => {
form.content = JSON.stringify({
title: form.title
});
const res = await api.AddObj(form);
return res;
};
const userStore = useUserStore();
const settingStore = useSettingStore();
const selectedRowKeys: Ref<any[]> = ref([]);
context.selectedRowKeys = selectedRowKeys;
return {
crudOptions: {
settings: {
plugins: {
//这里使用行选择插件生成行选择crudOptions配置最终会与crudOptions合并
rowSelection: {
enabled: true,
order: -2,
before: true,
// handle: (pluginProps,useCrudProps)=>CrudOptions,
props: {
multiple: true,
crossPage: true,
selectedRowKeys
}
}
}
},
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
actionbar: {
buttons: {
add: {
show: false
}
}
},
rowHandle: {
minWidth: 200,
fixed: "right",
buttons: {
edit: {
show: false
}
}
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 100
},
form: {
show: false
}
},
userId: {
title: "用户Id",
type: "number",
search: {
show: computed(() => {
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
})
},
form: {
show: false
},
column: {
show: computed(() => {
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
})
}
},
pipelineId: {
title: "流水线Id",
type: "number",
search: {
show: true
},
form: {
show: false
}
},
pipelineTitle: {
title: "流水线名称",
type: "link",
search: {
show: true,
component: {
name: "a-input"
}
},
column: {
width: 200
}
},
createTime: {
title: "创建时间",
type: "datetime",
form: {
show: false
},
column: {
sorter: true,
width: 125,
align: "center"
}
},
updateTime: {
title: "更新时间",
type: "datetime",
form: {
show: false
},
column: {
show: true
}
}
}
}
};
}

View File

@@ -0,0 +1,51 @@
<template>
<fs-page class="page-cert">
<template #header>
<div class="title">流水线执行记录</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #pagination-left>
<a-tooltip title="批量删除">
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
</a-tooltip>
</template>
</fs-crud>
</fs-page>
</template>
<script lang="ts" setup>
import { onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "/@/views/certd/history/api";
defineOptions({
name: "PipelineHistory"
});
const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions });
const selectedRowKeys = context.selectedRowKeys;
const handleBatchDelete = () => {
if (selectedRowKeys.value?.length > 0) {
Modal.confirm({
title: "确认",
content: `确定要批量删除这${selectedRowKeys.value.length}条记录吗`,
async onOk() {
await DeleteBatch(selectedRowKeys.value);
message.info("删除成功");
crudExpose.doRefresh();
selectedRowKeys.value = [];
}
});
} else {
message.error("请先勾选记录");
}
};
// 页面打开后获取列表数据
onMounted(() => {
crudExpose.doRefresh();
});
</script>
<style lang="less"></style>

View File

@@ -1,6 +1,6 @@
import * as api from "./api";
import { useI18n } from "vue-i18n";
import { ref } from "vue";
import { computed, ref } from "vue";
import { useRouter } from "vue-router";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
@@ -9,6 +9,7 @@ import { message, Modal } from "ant-design-vue";
import { env } from "/@/utils/util.env";
import { useUserStore } from "/@/store/modules/user";
import dayjs from "dayjs";
import { useSettingStore } from "/@/store/modules/settings";
export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
@@ -94,6 +95,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
});
}
const userStore = useUserStore();
const settingStore = useSettingStore();
return {
crudOptions: {
request: {
@@ -185,6 +187,9 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
title: "ID",
key: "id",
type: "number",
search: {
show: true
},
column: {
width: 50
},
@@ -192,6 +197,23 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
show: false
}
},
userId: {
title: "用户Id",
type: "number",
search: {
show: computed(() => {
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
})
},
form: {
show: false
},
column: {
show: computed(() => {
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
})
}
},
title: {
title: "流水线名称",
type: "link",
@@ -214,6 +236,31 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
}
}
},
content: {
title: "定时任务数量",
type: "number",
column: {
cellRender({ value }) {
if (value && value.triggers) {
return value.triggers?.length > 0 ? value.triggers.length : "-";
}
return "-";
}
},
valueBuilder({ row }) {
if (row.content) {
row.content = JSON.parse(row.content);
}
},
valueResolve({ row }) {
if (row.content) {
row.content = JSON.stringify(row.content);
}
},
form: {
show: false
}
},
lastVars: {
title: "到期剩余",
type: "number",

View File

@@ -1,6 +1,15 @@
<template>
<div class="main">
<a-form ref="formRef" class="user-layout-login" name="custom-validation" :model="formState" :rules="rules" v-bind="layout" @finish="handleFinish" @finishFailed="handleFinishFailed">
<a-form
ref="formRef"
class="user-layout-login"
name="custom-validation"
:model="formState"
:rules="rules"
v-bind="layout"
@finish="handleFinish"
@finish-failed="handleFinishFailed"
>
<!-- <div class="login-title">登录</div>-->
<a-tabs :active-key="formState.loginType" :tab-bar-style="{ textAlign: 'center', borderBottom: 'unset' }">
<a-tab-pane key="password" tab="用户名密码登录">
@@ -55,7 +64,13 @@
</a-input>
</a-col>
<a-col class="gutter-row" :span="8">
<a-button class="getCaptcha" tabindex="-1" :disabled="smsSendBtnDisabled" @click="sendSmsCode" v-text="smsTime <= 0 ? '发送' : smsTime + ' s'"></a-button>
<a-button
class="getCaptcha"
tabindex="-1"
:disabled="smsSendBtnDisabled"
@click="sendSmsCode"
v-text="smsTime <= 0 ? '发送' : smsTime + ' s'"
></a-button>
</a-col>
</a-row>
</a-form-item>
@@ -75,13 +90,13 @@
import { defineComponent, reactive, ref, toRaw, computed } from "vue";
import { useUserStore } from "/src/store/modules/user";
import { useSettingStore } from "/@/store/modules/settings";
import {utils} from "@fast-crud/fast-crud";
import { utils } from "@fast-crud/fast-crud";
export default defineComponent({
name: "LoginPage",
setup() {
const loading = ref(false);
const userStore = useUserStore();
const settingStore = useSettingStore()
const settingStore = useSettingStore();
const formRef = ref();
const formState = reactive({
username: "",
@@ -168,7 +183,7 @@ export default defineComponent({
function sendSmsCode() {
//api.sendSmsCode();
}
const sysPublicSettings = settingStore.getSysPublic
const sysPublicSettings = settingStore.getSysPublic;
return {
loading,
formState,

View File

@@ -1,6 +1,16 @@
<template>
<div class="main">
<a-form ref="formRef" class="user-layout-register" name="custom-validation" :model="formState" :rules="rules" v-bind="layout" @finish="handleFinish" @finishFailed="handleFinishFailed">
<a-form
ref="formRef"
class="user-layout-register"
name="custom-validation"
:model="formState"
:rules="rules"
v-bind="layout"
:label-col="{ span: 5 }"
@finish="handleFinish"
@finish-failed="handleFinishFailed"
>
<a-tabs :tab-bar-style="{ textAlign: 'center', borderBottom: 'unset' }">
<a-tab-pane key="register" tab="用户注册"> </a-tab-pane>
</a-tabs>
@@ -39,7 +49,7 @@
<script lang="ts">
import { defineComponent, reactive, ref, toRaw } from "vue";
import { useUserStore } from "/src/store/modules/user";
import {utils} from "@fast-crud/fast-crud";
import { utils } from "@fast-crud/fast-crud";
export default defineComponent({
name: "RegisterPage",
setup() {

View File

@@ -4,8 +4,7 @@ const apiPrefix = "/sys/settings";
export const SettingKeys = {
SysPublic: "sys.public",
SysPrivate: "sys.private",
SysPrivate: "sys.private"
};
export async function SettingsGet(key: string) {
return await request({
@@ -17,22 +16,28 @@ export async function SettingsGet(key: string) {
});
}
export async function SettingsSave(key: string,setting: any) {
export async function SettingsSave(key: string, setting: any) {
await request({
url: apiPrefix + "/save",
method: "post",
data: {
key,
setting: JSON.stringify(setting),
setting: JSON.stringify(setting)
}
});
}
export async function PublicSettingsSave(setting: any) {
await request({
url: apiPrefix + "/savePublicSettings",
method: "post",
data: setting
});
}
}
export async function stopOtherUserTimer() {
await request({
url: apiPrefix + "/stopOtherUserTimer",
method: "post"
});
}

View File

@@ -4,14 +4,31 @@
<div class="title">系统设置</div>
</template>
<div class="sys-settings-form">
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" @finish="onFinish" @finish-failed="onFinishFailed">
<a-form
:model="formState"
name="basic"
:label-col="{ span: 8 }"
:wrapper-col="{ span: 16 }"
autocomplete="off"
@finish="onFinish"
@finish-failed="onFinishFailed"
>
<a-form-item label="开启自助注册" name="registerEnabled">
<a-switch v-model:checked="formState.registerEnabled" />
</a-form-item>
<a-form-item label="管理其他用户流水线" name="managerOtherUserPipeline">
<a-switch v-model:checked="formState.managerOtherUserPipeline" />
</a-form-item>
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
<a-button type="primary" html-type="submit">保存</a-button>
</a-form-item>
</a-form>
<!-- <a-descriptions label="系统维护操作">-->
<!-- <a-descriptions-item label="自动化任务">-->
<!-- <a-button @click="stopOtherUserTimer">停止所有其他用户的定时任务</a-button>-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
</div>
</fs-page>
</template>
@@ -25,11 +42,10 @@ import { useSettingStore } from "/@/store/modules/settings";
interface FormState {
registerEnabled: boolean;
}
const formState = reactive<Partial<FormState>>({
registerEnabled:false
registerEnabled: false
});
async function loadSysPublicSettings() {
@@ -39,11 +55,11 @@ async function loadSysPublicSettings() {
}
loadSysPublicSettings();
const settingsStore= useSettingStore()
const settingsStore = useSettingStore();
const onFinish = async (form: any) => {
console.log("Success:", form);
await api.PublicSettingsSave(form);
await settingsStore.loadSysSettings()
await settingsStore.loadSysSettings();
notification.success({
message: "保存成功"
});
@@ -53,6 +69,12 @@ const onFinishFailed = (errorInfo: any) => {
// console.log("Failed:", errorInfo);
};
async function stopOtherUserTimer() {
await api.stopOtherUserTimer();
notification.success({
message: "停止成功"
});
}
</script>
<style lang="less">

View File

@@ -3,6 +3,19 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.9](https://github.com/certd/certd/compare/v1.22.8...v1.22.9) (2024-08-05)
### Performance Improvements
* 优化定时任务 ([87e440e](https://github.com/certd/certd/commit/87e440ee2a8b10dc571ce619f28bc83c1e5eb147))
## [1.22.8](https://github.com/certd/certd/compare/v1.22.7...v1.22.8) (2024-08-05)
### Performance Improvements
* 修复删除历史记录没有删除log的bug新增history管理页面演示站点启动时不自动启动非管理员用户的定时任务 ([f78ae93](https://github.com/certd/certd/commit/f78ae93eedfe214008c3d071ca3d77c962137a64))
* 优化pipeline删除时删除其他history ([b425203](https://github.com/certd/certd/commit/b4252033d56a9ad950f3e204ff021497c3978015))
## [1.22.7](https://github.com/certd/certd/compare/v1.22.6...v1.22.7) (2024-08-04)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-server",
"version": "1.22.7",
"version": "1.22.9",
"description": "fast-server base midway",
"private": true,
"type": "module",
@@ -23,10 +23,10 @@
"@alicloud/pop-core": "^1.7.10",
"@certd/acme-client": "^1.22.6",
"@certd/lib-huawei": "^1.22.1",
"@certd/lib-k8s": "^1.22.7",
"@certd/lib-k8s": "^1.22.8",
"@certd/midway-flyway-js": "^1.22.6",
"@certd/pipeline": "^1.22.7",
"@certd/plugin-cert": "^1.22.7",
"@certd/pipeline": "^1.22.8",
"@certd/plugin-cert": "^1.22.8",
"@koa/cors": "^3.4.3",
"@midwayjs/bootstrap": "^3.16.2",
"@midwayjs/cache": "^3.14.0",

View File

@@ -10,7 +10,7 @@ export abstract class BaseController {
* 成功返回
* @param data 返回数据
*/
ok(data: any) {
ok(data?: any) {
const res = {
...Constants.res.success,
data: undefined,

View File

@@ -2,6 +2,7 @@ import fs from 'fs';
import yaml from 'js-yaml';
import * as _ from 'lodash-es';
import { nanoid } from 'nanoid';
import path from 'path';
const KEYS_FILE = './data/keys.yaml';
export class Keys {
jwtKey: string = nanoid();
@@ -19,6 +20,12 @@ export class Keys {
}
save() {
const parent = path.dirname(KEYS_FILE);
if (!fs.existsSync(parent)) {
fs.mkdirSync(parent, {
recursive: true,
});
}
fs.writeFileSync(KEYS_FILE, yaml.dump(this));
}
}

View File

@@ -1,10 +1,9 @@
import { Config, Inject, Provide } from '@midwayjs/core';
import { Config, Inject, MidwayWebRouterService, Provide } from '@midwayjs/core';
import { IMidwayKoaContext, IWebMiddleware, NextFunction } from '@midwayjs/koa';
import jwt from 'jsonwebtoken';
import { Constants } from '../basic/constants.js';
import { MidwayWebRouterService } from '@midwayjs/core';
import { RoleService } from '../modules/authority/service/role-service.js';
import { logger } from '../utils/logger.js';
import { AuthService } from '../modules/authority/service/auth-service.js';
/**
* 权限校验
@@ -16,7 +15,7 @@ export class AuthorityMiddleware implements IWebMiddleware {
@Inject()
webRouterService: MidwayWebRouterService;
@Inject()
roleService: RoleService;
authService: AuthService;
resolve() {
return async (ctx: IMidwayKoaContext, next: NextFunction) => {
@@ -59,11 +58,8 @@ export class AuthorityMiddleware implements IWebMiddleware {
}
if (permission !== Constants.per.authOnly) {
//如果不是仅校验登录,还需要校验是否拥有权限
const roleIds: number[] = ctx.user.roles;
const permissions = await this.roleService.getCachedPermissionSetByRoleIds(roleIds);
if (!permissions.has(permission)) {
const pass = await this.authService.checkPermission(ctx, permission);
if (!pass) {
logger.info('not permission: ', ctx.req.url);
ctx.status = 401;
ctx.body = Constants.res.permission;

View File

@@ -0,0 +1,38 @@
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { RoleService } from './role-service.js';
import { BaseService } from '../../../basic/base-service.js';
/**
* 权限校验
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class AuthService {
@Inject()
roleService: RoleService;
async checkPermission(ctx: any, permission: string) {
//如果不是仅校验登录,还需要校验是否拥有权限
const roleIds: number[] = ctx.user.roles;
const permissions = await this.roleService.getCachedPermissionSetByRoleIds(roleIds);
if (!permissions.has(permission)) {
return false;
}
return true;
}
async isAdmin(ctx: any) {
const roleIds: number[] = ctx.user.roles;
if (roleIds.includes(1)) {
return true;
}
}
async checkEntityUserId(ctx: any, service: BaseService<any>, id: any = 0, userKey = 'userId') {
const isAdmin = await this.isAdmin(ctx);
if (isAdmin) {
return true;
}
await service.checkUserId(id, ctx.user.id, userKey);
}
}

View File

@@ -1,6 +1,6 @@
import { Autoload, Config, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core';
import { PipelineService } from '../service/pipeline-service.js';
import { logger } from '../../../utils/logger.js';
import { PipelineService } from '../pipeline/service/pipeline-service.js';
import { logger } from '../../utils/logger.js';
@Autoload()
@Scope(ScopeEnum.Singleton)
@@ -8,6 +8,9 @@ export class AutoRegisterCron {
@Inject()
pipelineService: PipelineService;
@Config('preview.enabled')
private preview: boolean;
// @Inject()
// echoPlugin: EchoPlugin;
@Config('cron.immediateTriggerOnce')
@@ -16,7 +19,7 @@ export class AutoRegisterCron {
@Init()
async init() {
logger.info('加载定时trigger开始');
await this.pipelineService.onStartup(this.immediateTriggerOnce);
await this.pipelineService.onStartup(this.immediateTriggerOnce, this.preview);
// logger.info(this.echoPlugin, this.echoPlugin.test);
// logger.info('加载定时trigger完成');
//

View File

@@ -11,6 +11,8 @@ import { CommonException } from '../../../basic/exception/common-exception.js';
import { PermissionException } from '../../../basic/exception/permission-exception.js';
import * as fs from 'fs';
import { logger } from '../../../utils/logger.js';
import { AuthService } from '../../authority/service/auth-service.js';
import { SysSettingsService } from '../../system/service/sys-settings-service.js';
/**
* 证书
@@ -25,19 +27,35 @@ export class HistoryController extends CrudController<HistoryService> {
@Inject()
logService: HistoryLogService;
@Inject()
authService: AuthService;
@Inject()
sysSettingsService: SysSettingsService;
getService() {
return this.service;
}
@Post('/page', { summary: Constants.per.authOnly })
async page(@Body(ALL) body) {
body.query.userId = this.ctx.user.id;
return super.page(body);
const isAdmin = await this.authService.isAdmin(this.ctx);
const publicSettings = await this.sysSettingsService.getPublicSettings();
if (!(publicSettings.managerOtherUserPipeline && isAdmin)) {
body.query.userId = this.ctx.user.id;
}
const res = await super.page(body);
return res;
}
@Post('/list', { summary: Constants.per.authOnly })
async list(@Body(ALL) body) {
body.userId = this.ctx.user.id;
const isAdmin = await this.authService.isAdmin(this.ctx);
if (!isAdmin) {
body.userId = this.ctx.user.id;
}
if (body.pipelineId == null) {
return this.ok([]);
}
@@ -56,7 +74,7 @@ export class HistoryController extends CrudController<HistoryService> {
@Post('/update', { summary: Constants.per.authOnly })
async update(@Body(ALL) bean) {
await this.service.checkUserId(bean.id, this.ctx.user.id);
await this.authService.checkEntityUserId(this.ctx, this.getService(), bean.id);
return super.update(bean);
}
@@ -64,7 +82,7 @@ export class HistoryController extends CrudController<HistoryService> {
async save(@Body(ALL) bean: HistoryEntity) {
bean.userId = this.ctx.user.id;
if (bean.id > 0) {
await this.service.checkUserId(bean.id, this.ctx.user.id);
await this.authService.checkEntityUserId(this.ctx, this.getService(), bean.id);
}
await this.service.save(bean);
return this.ok(bean.id);
@@ -74,7 +92,7 @@ export class HistoryController extends CrudController<HistoryService> {
async saveLog(@Body(ALL) bean: HistoryLogEntity) {
bean.userId = this.ctx.user.id;
if (bean.id > 0) {
await this.service.checkUserId(bean.id, this.ctx.user.id);
await this.authService.checkEntityUserId(this.ctx, this.getService(), bean.id);
}
await this.logService.save(bean);
return this.ok(bean.id);
@@ -82,26 +100,37 @@ export class HistoryController extends CrudController<HistoryService> {
@Post('/delete', { summary: Constants.per.authOnly })
async delete(@Query('id') id) {
await this.service.checkUserId(id, this.ctx.user.id);
return super.delete(id);
await this.authService.checkEntityUserId(this.ctx, this.getService(), id);
await super.delete(id);
return this.ok();
}
@Post('/deleteByIds', { summary: Constants.per.authOnly })
async deleteByIds(@Body(ALL) body) {
await this.authService.checkEntityUserId(this.ctx, this.getService(), body.ids);
const isAdmin = await this.authService.isAdmin(this.ctx);
const userId = isAdmin ? null : this.ctx.user.id;
await this.getService().deleteByIds(body.ids, userId);
return this.ok();
}
@Post('/detail', { summary: Constants.per.authOnly })
async detail(@Query('id') id) {
await this.service.checkUserId(id, this.ctx.user.id);
await this.authService.checkEntityUserId(this.ctx, this.getService(), id);
const detail = await this.service.detail(id);
return this.ok(detail);
}
@Post('/logs', { summary: Constants.per.authOnly })
async logs(@Query('id') id) {
await this.logService.checkUserId(id, this.ctx.user.id);
await this.authService.checkEntityUserId(this.ctx, this.logService, id);
const logInfo = await this.logService.info(id);
return this.ok(logInfo);
}
@Post('/files', { summary: Constants.per.authOnly })
async files(@Query('pipelineId') pipelineId, @Query('historyId') historyId) {
await this.authService.checkEntityUserId(this.ctx, this.service, historyId);
const files = await this.getFiles(historyId, pipelineId);
return this.ok(files);
}
@@ -125,6 +154,7 @@ export class HistoryController extends CrudController<HistoryService> {
@Get('/download', { summary: Constants.per.authOnly })
async download(@Query('pipelineId') pipelineId, @Query('historyId') historyId, @Query('fileId') fileId) {
await this.authService.checkEntityUserId(this.ctx, this.service, historyId);
const files = await this.getFiles(historyId, pipelineId);
const file = files.find(f => f.id === fileId);
if (file == null) {

View File

@@ -4,6 +4,8 @@ import { PipelineService } from '../service/pipeline-service.js';
import { PipelineEntity } from '../entity/pipeline.js';
import { Constants } from '../../../basic/constants.js';
import { HistoryService } from '../service/history-service.js';
import { AuthService } from '../../authority/service/auth-service.js';
import { SysSettingsService } from '../../system/service/sys-settings-service.js';
/**
* 证书
@@ -15,6 +17,10 @@ export class PipelineController extends CrudController<PipelineService> {
service: PipelineService;
@Inject()
historyService: HistoryService;
@Inject()
authService: AuthService;
@Inject()
sysSettingsService: SysSettingsService;
getService() {
return this.service;
@@ -22,7 +28,11 @@ export class PipelineController extends CrudController<PipelineService> {
@Post('/page', { summary: Constants.per.authOnly })
async page(@Body(ALL) body) {
body.query.userId = this.ctx.user.id;
const isAdmin = await this.authService.isAdmin(this.ctx);
const publicSettings = await this.sysSettingsService.getPublicSettings();
if (!(publicSettings.managerOtherUserPipeline && isAdmin)) {
body.query.userId = this.ctx.user.id;
}
const title = body.query.title;
delete body.query.title;
@@ -47,7 +57,7 @@ export class PipelineController extends CrudController<PipelineService> {
@Post('/update', { summary: Constants.per.authOnly })
async update(@Body(ALL) bean) {
await this.service.checkUserId(bean.id, this.ctx.user.id);
await this.authService.checkEntityUserId(this.ctx, this.getService(), bean.id);
return super.update(bean);
}
@@ -55,7 +65,7 @@ export class PipelineController extends CrudController<PipelineService> {
async save(@Body(ALL) bean: PipelineEntity) {
bean.userId = this.ctx.user.id;
if (bean.id > 0) {
await this.service.checkUserId(bean.id, this.ctx.user.id);
await this.authService.checkEntityUserId(this.ctx, this.getService(), bean.id);
}
await this.service.save(bean);
return this.ok(bean.id);
@@ -63,28 +73,28 @@ export class PipelineController extends CrudController<PipelineService> {
@Post('/delete', { summary: Constants.per.authOnly })
async delete(@Query('id') id) {
await this.service.checkUserId(id, this.ctx.user.id);
await this.authService.checkEntityUserId(this.ctx, this.getService(), id);
await this.service.delete(id);
return this.ok({});
}
@Post('/detail', { summary: Constants.per.authOnly })
async detail(@Query('id') id) {
await this.service.checkUserId(id, this.ctx.user.id);
await this.authService.checkEntityUserId(this.ctx, this.getService(), id);
const detail = await this.service.detail(id);
return this.ok(detail);
}
@Post('/trigger', { summary: Constants.per.authOnly })
async trigger(@Query('id') id) {
await this.service.checkUserId(id, this.ctx.user.id);
await this.authService.checkEntityUserId(this.ctx, this.getService(), id);
await this.service.trigger(id);
return this.ok({});
}
@Post('/cancel', { summary: Constants.per.authOnly })
async cancel(@Query('historyId') historyId) {
await this.historyService.checkUserId(historyId, this.ctx.user.id);
await this.authService.checkEntityUserId(this.ctx, this.historyService, historyId);
await this.service.cancel(historyId);
return this.ok({});
}

View File

@@ -35,4 +35,13 @@ export class HistoryEntity {
default: () => 'CURRENT_TIMESTAMP',
})
updateTime: Date;
pipelineTitle: string;
fillPipelineTitle() {
if (this.pipeline) {
const pipeline = JSON.parse(this.pipeline);
this.pipelineTitle = pipeline.title;
}
}
}

View File

@@ -1,6 +1,6 @@
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { In, Repository } from 'typeorm';
import { BaseService } from '../../../basic/base-service.js';
import { HistoryLogEntity } from '../entity/history-log.js';
@@ -24,4 +24,15 @@ export class HistoryLogService extends BaseService<HistoryLogEntity> {
await this.add(bean);
}
}
async deleteByHistoryIds(numbers: number[]) {
if (numbers.length === 0) {
return;
}
await this.repository.delete({ historyId: In(numbers) });
}
async deleteByPipelineId(id: number) {
await this.repository.delete({ pipelineId: id });
}
}

View File

@@ -1,6 +1,6 @@
import { Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { In, Repository } from 'typeorm';
import { BaseService } from '../../../basic/base-service.js';
import { HistoryEntity } from '../entity/history.js';
import { PipelineEntity } from '../entity/pipeline.js';
@@ -28,6 +28,14 @@ export class HistoryService extends BaseService<HistoryEntity> {
return this.repository;
}
async page(query, page, sort, buildQuery) {
const res = await super.page(query, page, sort, buildQuery);
for (const item of res.records) {
item.fillPipelineTitle();
}
return res;
}
async save(bean: HistoryEntity) {
if (bean.id > 0) {
await this.update(bean);
@@ -51,7 +59,7 @@ export class HistoryService extends BaseService<HistoryEntity> {
};
const { id } = await this.add(bean);
//清除大于pipeline.keepHistoryCount的历史记录
this.clear(pipeline.id, pipeline.keepHistoryCount);
await this.clear(pipeline.id, pipeline.keepHistoryCount);
return id;
}
@@ -85,7 +93,6 @@ export class HistoryService extends BaseService<HistoryEntity> {
skip: 0,
take: deleteCountBatch,
});
await this.repository.remove(list);
for (const historyEntity of list) {
const id = historyEntity.id;
@@ -95,6 +102,9 @@ export class HistoryService extends BaseService<HistoryEntity> {
logger.error('删除文件失败', e);
}
}
await this.repository.remove(list);
await this.logService.deleteByHistoryIds(list.map(item => item.id));
shouldDeleteCount -= deleteCountBatch;
}
@@ -124,4 +134,29 @@ export class HistoryService extends BaseService<HistoryEntity> {
});
return files;
}
async deleteByIds(ids: number[], userId: number) {
await this.repository.delete({
id: In(ids),
userId,
});
await this.logService.deleteByHistoryIds(ids);
}
async deleteByPipelineId(id: number) {
await this.repository.delete({
pipelineId: id,
});
try {
const fileStore = new FileStore({
rootDir: this.certdConfig.fileRootDir,
scope: id + '',
parent: '0',
});
fileStore.deleteByParent(id + '', '');
} catch (e) {
logger.error('删除文件失败', e);
}
}
}

View File

@@ -73,6 +73,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
const info = await this.info(pipelineId);
if (info && !info.disabled) {
const pipeline = JSON.parse(info.content);
// 手动触发不要await
this.registerTriggers(pipeline);
}
}
@@ -87,24 +88,21 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
async update(bean: PipelineEntity) {
await this.clearTriggers(bean.id);
//更新非trigger部分
await super.update(bean);
await this.registerTriggerById(bean.id);
}
async save(bean: PipelineEntity) {
await this.clearTriggers(bean.id);
const pipeline = JSON.parse(bean.content);
bean.title = pipeline.title;
if (bean.content) {
const pipeline = JSON.parse(bean.content);
bean.title = pipeline.title;
}
await this.addOrUpdate(bean);
await this.registerTriggerById(bean.id);
}
/**
* 应用启动后初始加载记录
*/
async onStartup(immediateTriggerOnce: boolean) {
logger.info('加载定时trigger开始');
async foreachPipeline(callback: (pipeline: PipelineEntity) => void) {
const idEntityList = await this.repository.find({
select: {
id: true,
@@ -135,14 +133,35 @@ export class PipelineService extends BaseService<PipelineEntity> {
});
for (const entity of list) {
const pipeline = JSON.parse(entity.content ?? '{}');
try {
await this.registerTriggers(pipeline, immediateTriggerOnce);
} catch (e) {
logger.error('加载定时trigger失败', e);
}
await callback(entity);
}
}
}
async stopOtherUserPipeline(userId: number) {
await this.foreachPipeline(async entity => {
if (entity.userId !== userId) {
await this.clearTriggers(entity.id);
}
});
}
/**
* 应用启动后初始加载记录
*/
async onStartup(immediateTriggerOnce: boolean, preview: boolean) {
logger.info('加载定时trigger开始');
await this.foreachPipeline(async entity => {
if (preview && entity.userId !== 1) {
return;
}
const pipeline = JSON.parse(entity.content ?? '{}');
try {
await this.registerTriggers(pipeline, immediateTriggerOnce);
} catch (e) {
logger.error('加载定时trigger失败', e);
}
});
logger.info('定时器数量:', this.cron.getTaskSize());
}
@@ -156,7 +175,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
if (immediateTriggerOnce) {
await this.trigger(pipeline.id);
await sleep(1000);
await sleep(200);
}
}
@@ -181,6 +200,8 @@ export class PipelineService extends BaseService<PipelineEntity> {
// const storage = new DbStorage(pipeline.userId, this.storageService);
// await storage.remove(pipeline.id);
await super.delete([id]);
await this.historyService.deleteByPipelineId(id);
await this.historyLogService.deleteByPipelineId(id);
}
async clearTriggers(id: number) {
@@ -205,6 +226,11 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
registerCron(pipelineId, trigger) {
if (pipelineId == null) {
logger.warn('pipelineId为空无法注册定时任务');
return;
}
let cron = trigger.props?.cron;
if (cron == null) {
return;
@@ -213,15 +239,20 @@ export class PipelineService extends BaseService<PipelineEntity> {
if (cron.startsWith('*')) {
cron = '0' + cron.substring(1, cron.length);
}
const name = this.buildCronKey(pipelineId, trigger.id);
const triggerId = trigger.id;
const name = this.buildCronKey(pipelineId, triggerId);
this.cron.remove(name);
this.cron.register({
name,
cron,
job: async () => {
logger.info('定时任务触发:', pipelineId, trigger.id);
logger.info('定时任务触发:', pipelineId, triggerId);
if (pipelineId == null) {
logger.warn('pipelineId为空,无法执行');
return;
}
try {
await this.run(pipelineId, trigger.id);
await this.run(pipelineId, triggerId);
} catch (e) {
logger.error('定时job执行失败', e);
}

View File

@@ -22,6 +22,7 @@ export class CronConfiguration {
...this.config,
});
container.registerObject('cron', this.cron);
this.cron.start();
this.logger.info('cron started');
}
}

View File

@@ -17,56 +17,63 @@ export class CronTask {
name: string;
stoped = false;
timeoutId: any;
nextTime: any;
constructor(req: CronTaskReq, logger: ILogger) {
this.cron = req.cron;
this.job = req.job;
this.name = req.name;
this.logger = logger;
this.start();
this.genNextTime();
}
start() {
genNextTime() {
if (!this.cron) {
return;
return null;
}
if (this.stoped) {
return;
return null;
}
const interval = parser.parseExpression(this.cron);
const next = interval.next().getTime();
const now = Date.now();
const delay = next - now;
this.timeoutId = setTimeout(async () => {
try {
if (this.stoped) {
return;
}
await this.job();
} catch (e) {
this.logger.error(`[cron] job error : [${this.name}]`, e);
}
this.start();
}, delay);
this.logger.info(`[cron] [${this.name}], cron:${this.cron}, next run :${new Date(next).toLocaleString()}`);
this.nextTime = next;
return next;
}
stop() {
this.stoped = true;
clearTimeout(this.timeoutId);
}
}
export class Cron {
logger: ILogger;
immediateTriggerOnce: boolean;
bucket: Record<string, CronTask> = {};
queue: CronTask[] = [];
constructor(opts: any) {
this.logger = opts.logger;
this.immediateTriggerOnce = opts.immediateTriggerOnce;
}
start() {
this.logger.info('[cron] start');
this.queue.forEach(task => {
task.genNextTime();
});
setInterval(() => {
const now = new Date().getTime();
for (const task of this.queue) {
if (task.nextTime <= now) {
task.job().catch(e => {
this.logger.error(`job execute error : [${task.name}]`, e);
});
task.genNextTime();
}
}
}, 1000 * 60);
}
register(req: CronTaskReq) {
if (!req.cron) {
this.logger.info(`[cron] register once : [${req.name}]`);
@@ -78,21 +85,22 @@ export class Cron {
this.logger.info(`[cron] register cron : [${req.name}] ,${req.cron}`);
const task = new CronTask(req, this.logger);
this.bucket[task.name] = task;
this.queue.push(task);
this.logger.info('当前定时任务数量:', this.getTaskSize());
}
remove(taskName: string) {
this.logger.info(`[cron] remove : [${taskName}]`);
const task = this.bucket[taskName];
if (task) {
task.stop();
delete this.bucket[taskName];
const index = this.queue.findIndex(item => item.name === taskName);
if (index !== -1) {
this.queue[index].stop();
this.queue.splice(index, 1);
}
this.logger.info('当前定时任务数量:', this.getTaskSize());
}
getTaskSize() {
const tasks = Object.keys(this.bucket);
const tasks = Object.keys(this.queue);
return tasks.length;
}
}

View File

@@ -4,6 +4,7 @@ import { SysSettingsService } from '../service/sys-settings-service.js';
import { SysSettingsEntity } from '../entity/sys-settings.js';
import { SysPublicSettings } from '../service/models.js';
import * as _ from 'lodash-es';
import { PipelineService } from '../../pipeline/service/pipeline-service.js';
/**
*/
@@ -12,6 +13,8 @@ import * as _ from 'lodash-es';
export class SysSettingsController extends CrudController<SysSettingsService> {
@Inject()
service: SysSettingsService;
@Inject()
pipelineService: PipelineService;
getService() {
return this.service;
@@ -73,4 +76,9 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
await this.service.savePublicSettings(setting);
return this.ok({});
}
@Post('/stopOtherUserTimer', { summary: 'sys:settings:edit' })
async stopOtherUserTimer(@Body(ALL) body) {
await this.pipelineService.stopOtherUserPipeline(1);
return this.ok({});
}
}

View File

@@ -12,6 +12,7 @@ export class SysPublicSettings extends BaseSettings {
static __title__ = '系统公共设置';
static __access__ = 'public';
registerEnabled = false;
managerOtherUserPipeline = false;
}
export class SysPrivateSettings extends BaseSettings {

View File

@@ -75,6 +75,10 @@ export class AsyncSsh2Client {
}
async exec(script: string) {
if (!script) {
this.logger.info('script 为空,取消执行');
return;
}
return new Promise((resolve, reject) => {
this.logger.info(`执行命令:[${this.connConf.host}][exec]: ` + script);
this.conn.exec(script, (err: Error, stream: any) => {
@@ -97,6 +101,10 @@ export class AsyncSsh2Client {
data += out;
this.logger.info(`[${this.connConf.host}][info]: ` + out.trimEnd());
})
.on('error', (err: any) => {
reject(err);
this.logger.error(err);
})
.stderr.on('data', (ret: Buffer) => {
const err = this.convert(ret);
data += err;

View File

@@ -30,6 +30,7 @@ export class HostShellExecutePlugin extends AbstractTaskPlugin {
name: 'a-textarea',
vModel: 'value',
},
required: true,
})
script!: string;