chore: plugin管理

pull/213/head
xiaojunnuo 2024-10-13 01:27:08 +08:00
parent 6f8fe62087
commit ccfe72a0d9
29 changed files with 729 additions and 163 deletions

View File

@ -1,18 +1,16 @@
import { ConcurrencyStrategy, NotificationWhen, Pipeline, ResultType, Runnable, RunStrategy, Stage, Step, Task } from "../dt/index.js"; import { ConcurrencyStrategy, NotificationWhen, Pipeline, ResultType, Runnable, RunStrategy, Stage, Step, Task } from "../dt/index.js";
import _ from "lodash-es";
import { RunHistory, RunnableCollection } from "./run-history.js"; import { RunHistory, RunnableCollection } from "./run-history.js";
import { AbstractTaskPlugin, PluginDefine, pluginRegistry, TaskInstanceContext, UserInfo } from "../plugin/index.js"; import { AbstractTaskPlugin, PluginDefine, pluginRegistry, TaskInstanceContext, UserInfo } from "../plugin/index.js";
import { ContextFactory, IContext } from "./context.js"; import { ContextFactory, IContext } from "./context.js";
import { IStorage } from "./storage.js"; import { IStorage } from "./storage.js";
import { logger } from "../utils/index.js"; import { createAxiosService, hashUtils, logger, utils } from "../utils/index.js";
import { Logger } from "log4js"; import { Logger } from "log4js";
import { createAxiosService } from "../utils/index.js";
import { IAccessService } from "../access/index.js"; import { IAccessService } from "../access/index.js";
import { RegistryItem } from "../registry/index.js"; import { RegistryItem } from "../registry/index.js";
import { Decorator } from "../decorator/index.js"; import { Decorator } from "../decorator/index.js";
import { ICnameProxyService, IEmailService } from "../service/index.js"; import { ICnameProxyService, IEmailService, IPluginConfigService } from "../service/index.js";
import { FileStore } from "./file-store.js"; import { FileStore } from "./file-store.js";
import { hashUtils, utils } from "../utils/index.js"; import { cloneDeep, forEach, merge } from "lodash-es";
export type ExecutorOptions = { export type ExecutorOptions = {
pipeline: Pipeline; pipeline: Pipeline;
@ -21,6 +19,7 @@ export type ExecutorOptions = {
accessService: IAccessService; accessService: IAccessService;
emailService: IEmailService; emailService: IEmailService;
cnameProxyService: ICnameProxyService; cnameProxyService: ICnameProxyService;
pluginConfigService: IPluginConfigService;
fileRootDir?: string; fileRootDir?: string;
user: UserInfo; user: UserInfo;
}; };
@ -42,7 +41,7 @@ export class Executor {
onChanged: (history: RunHistory) => Promise<void>; onChanged: (history: RunHistory) => Promise<void>;
constructor(options: ExecutorOptions) { constructor(options: ExecutorOptions) {
this.options = options; this.options = options;
this.pipeline = _.cloneDeep(options.pipeline); this.pipeline = cloneDeep(options.pipeline);
this.onChanged = async (history: RunHistory) => { this.onChanged = async (history: RunHistory) => {
await options.onChanged(history); await options.onChanged(history);
}; };
@ -219,7 +218,7 @@ export class Executor {
// @ts-ignore // @ts-ignore
const define: PluginDefine = plugin.define; const define: PluginDefine = plugin.define;
//从outputContext读取输入参数 //从outputContext读取输入参数
const input = _.cloneDeep(step.input); const input = cloneDeep(step.input);
Decorator.inject(define.input, instance, input, (item, key) => { Decorator.inject(define.input, instance, input, (item, key) => {
if (item.component?.name === "output-selector") { if (item.component?.name === "output-selector") {
const contextKey = input[key]; const contextKey = input[key];
@ -269,6 +268,7 @@ export class Executor {
accessService: this.options.accessService, accessService: this.options.accessService,
emailService: this.options.emailService, emailService: this.options.emailService,
cnameProxyService: this.options.cnameProxyService, cnameProxyService: this.options.cnameProxyService,
pluginConfigService: this.options.pluginConfigService,
pipelineContext: this.pipelineContext, pipelineContext: this.pipelineContext,
userContext: this.contextFactory.getContext("user", this.options.user.id), userContext: this.contextFactory.getContext("user", this.options.user.id),
fileStore: new FileStore({ fileStore: new FileStore({
@ -290,7 +290,7 @@ export class Executor {
this.lastStatusMap.clear(); this.lastStatusMap.clear();
} }
//输出上下文变量到output context //输出上下文变量到output context
_.forEach(define.output, (item: any, key: any) => { forEach(define.output, (item: any, key: any) => {
step.status!.output[key] = instance[key]; step.status!.output[key] = instance[key];
// const stepOutputKey = `step.${step.id}.${key}`; // const stepOutputKey = `step.${step.id}.${key}`;
// this.runtime.context[stepOutputKey] = instance[key]; // this.runtime.context[stepOutputKey] = instance[key];
@ -300,7 +300,7 @@ export class Executor {
if (Object.keys(instance._result.pipelineVars).length > 0) { if (Object.keys(instance._result.pipelineVars).length > 0) {
// 判断 pipelineVars 有值时更新 // 判断 pipelineVars 有值时更新
const vars = this.pipelineContext.getObj("vars"); const vars = this.pipelineContext.getObj("vars");
_.merge(vars, instance._result.pipelineVars); merge(vars, instance._result.pipelineVars);
await this.pipelineContext.setObj("vars", vars); await this.pipelineContext.setObj("vars", vars);
} }
} }

View File

@ -8,8 +8,8 @@ import { IContext, PluginRequestHandleReq, RunnableCollection } from "../core/in
import { ILogger, logger, utils } from "../utils/index.js"; import { ILogger, logger, utils } from "../utils/index.js";
import { HttpClient } from "../utils/index.js"; import { HttpClient } from "../utils/index.js";
import dayjs from "dayjs"; import dayjs from "dayjs";
import _ from "lodash-es";
import { IPluginConfigService } from "../service/config"; import { IPluginConfigService } from "../service/config";
import { upperFirst } from "lodash-es";
export type UserInfo = { export type UserInfo = {
role: "admin" | "user"; role: "admin" | "user";
id: any; id: any;
@ -74,7 +74,7 @@ export type TaskInstanceContext = {
//cname记录服务 //cname记录服务
cnameProxyService: ICnameProxyService; cnameProxyService: ICnameProxyService;
//插件配置服务 //插件配置服务
configService: IPluginConfigService; pluginConfigService: IPluginConfigService;
//流水线上下文 //流水线上下文
pipelineContext: IContext; pipelineContext: IContext;
//用户上下文 //用户上下文
@ -172,7 +172,7 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
let methodName = req.action; let methodName = req.action;
if (!req.action.startsWith("on")) { if (!req.action.startsWith("on")) {
methodName = `on${_.upperFirst(req.action)}`; methodName = `on${upperFirst(req.action)}`;
} }
// @ts-ignore // @ts-ignore

View File

@ -1,9 +1,9 @@
import _ from "lodash-es";
import { pluginRegistry } from "./registry.js"; import { pluginRegistry } from "./registry.js";
import { PluginDefine, TaskInputDefine, TaskOutputDefine } from "./api.js"; import { PluginDefine, TaskInputDefine, TaskOutputDefine } from "./api.js";
import { Decorator } from "../decorator/index.js"; import { Decorator } from "../decorator/index.js";
import { AUTOWIRE_KEY } from "../decorator/index.js"; import { AUTOWIRE_KEY } from "../decorator/index.js";
import "reflect-metadata"; import "reflect-metadata";
import { merge, sortBy } from "lodash-es";
// 提供一个唯一 key // 提供一个唯一 key
export const PLUGIN_CLASS_KEY = "pipeline:plugin"; export const PLUGIN_CLASS_KEY = "pipeline:plugin";
@ -42,13 +42,13 @@ export function IsTaskPlugin(define: PluginDefine): ClassDecorator {
} }
inputArray.push([key, _input]); inputArray.push([key, _input]);
} }
inputArray = _.sortBy(inputArray, (item: any) => item[1].order); inputArray = sortBy(inputArray, (item: any) => item[1].order);
const inputMap: any = {}; const inputMap: any = {};
inputArray.forEach((item: any) => { inputArray.forEach((item: any) => {
inputMap[item[0]] = item[1]; inputMap[item[0]] = item[1];
}); });
_.merge(define, { input: inputMap, autowire: autowires, output: outputs }); merge(define, { input: inputMap, autowire: autowires, output: outputs });
Reflect.defineMetadata(PLUGIN_CLASS_KEY, define, target); Reflect.defineMetadata(PLUGIN_CLASS_KEY, define, target);

View File

@ -1,9 +1,8 @@
import { FormItemProps } from "../d.ts/index.js";
export type PluginConfig = { export type PluginConfig = {
show: false; name: string;
sysInput: { disabled: boolean;
[key: string]: {}; sysSetting: {
[key: string]: any;
}; };
}; };

View File

@ -1,2 +1,3 @@
export * from "./email.js"; export * from "./email.js";
export * from "./cname.js"; export * from "./cname.js";
export * from "./config.js";

View File

@ -2,59 +2,42 @@ import { ALL, Body, Post, Query } from '@midwayjs/core';
import { BaseController } from './base-controller.js'; import { BaseController } from './base-controller.js';
export abstract class CrudController<T> extends BaseController { export abstract class CrudController<T> extends BaseController {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
abstract getService<T>(); abstract getService<T>();
@Post('/page') @Post('/page')
async page( async page(@Body(ALL) body: any) {
@Body(ALL)
body
) {
const pageRet = await this.getService().page(body?.query, body?.page, body?.sort, null); const pageRet = await this.getService().page(body?.query, body?.page, body?.sort, null);
return this.ok(pageRet); return this.ok(pageRet);
} }
@Post('/list') @Post('/list')
async list( async list(@Body(ALL) body: any) {
@Body(ALL)
body
) {
const listRet = await this.getService().list(body, null, null); const listRet = await this.getService().list(body, null, null);
return this.ok(listRet); return this.ok(listRet);
} }
@Post('/add') @Post('/add')
async add( async add(@Body(ALL) bean: any) {
@Body(ALL)
bean
) {
delete bean.id; delete bean.id;
const id = await this.getService().add(bean); const id = await this.getService().add(bean);
return this.ok(id); return this.ok(id);
} }
@Post('/info') @Post('/info')
async info( async info(@Query('id') id: number) {
@Query('id')
id
) {
const bean = await this.getService().info(id); const bean = await this.getService().info(id);
return this.ok(bean); return this.ok(bean);
} }
@Post('/update') @Post('/update')
async update( async update(@Body(ALL) bean: any) {
@Body(ALL)
bean
) {
await this.getService().update(bean); await this.getService().update(bean);
return this.ok(null); return this.ok(null);
} }
@Post('/delete') @Post('/delete')
async delete( async delete(@Query('id') id: number) {
@Query('id')
id
) {
await this.getService().delete([id]); await this.getService().delete([id]);
return this.ok(null); return this.ok(null);
} }

View File

@ -68,6 +68,34 @@ export const sysResources = [
permission: "sys:settings:view" permission: "sys:settings:view"
} }
}, },
{
title: "系统级授权",
name: "SysAccess",
path: "/sys/access",
component: "/sys/access/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:disc-outline",
permission: "sys:settings:view"
}
},
{
title: "插件管理",
name: "SysPlugin",
path: "/sys/plugin",
component: "/sys/plugin/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:extension-puzzle-outline",
permission: "sys:settings:view"
}
},
{ {
title: "账号绑定", title: "账号绑定",
name: "AccountBind", name: "AccountBind",

View File

@ -1,5 +1,4 @@
// @ts-ignore // @ts-ignore
import * as api from "/@/views/certd/access/api";
import { ref } from "vue"; import { ref } from "vue";
import { getCommonColumnDefine } from "/@/views/certd/access/common"; import { getCommonColumnDefine } from "/@/views/certd/access/common";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
@ -9,25 +8,25 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const { props, ctx } = context; const { props, ctx } = context;
const lastResRef = ref(); const lastResRef = ref();
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => { const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query); return await context.api.GetList(query);
}; };
const editRequest = async (req: EditReq) => { const editRequest = async (req: EditReq) => {
const { form, row } = req; const { form, row } = req;
form.id = row.id; form.id = row.id;
form.type = props.type; form.type = props.type;
const res = await api.UpdateObj(form); const res = await context.api.UpdateObj(form);
lastResRef.value = res; lastResRef.value = res;
return res; return res;
}; };
const delRequest = async (req: DelReq) => { const delRequest = async (req: DelReq) => {
const { row } = req; const { row } = req;
return await api.DelObj(row.id); return await context.api.DelObj(row.id);
}; };
const addRequest = async (req: AddReq) => { const addRequest = async (req: AddReq) => {
const { form } = req; const { form } = req;
form.type = props.type; form.type = props.type;
const res = await api.AddObj(form); const res = await context.api.AddObj(form);
lastResRef.value = res; lastResRef.value = res;
return res; return res;
}; };
@ -41,7 +40,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const typeRef = ref("aliyun"); const typeRef = ref("aliyun");
context.typeRef = typeRef; context.typeRef = typeRef;
const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef); const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api);
commonColumnsDefine.type.form.component.disabled = true; commonColumnsDefine.type.form.component.disabled = true;
return { return {
typeRef, typeRef,

View File

@ -8,6 +8,7 @@
import { defineComponent, onMounted, watch } from "vue"; import { defineComponent, onMounted, watch } from "vue";
import { useFs } from "@fast-crud/fast-crud"; import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud"; import createCrudOptions from "./crud";
import { createAccessApi } from "/@/views/certd/access/api";
export default defineComponent({ export default defineComponent({
name: "CertAccessModal", name: "CertAccessModal",
@ -16,11 +17,16 @@ export default defineComponent({
type: String, type: String,
default: "aliyun" default: "aliyun"
}, },
from: {
type: String, //user | sys
default: "user"
},
modelValue: {} modelValue: {}
}, },
emits: ["update:modelValue"], emits: ["update:modelValue"],
setup(props, ctx) { setup(props, ctx) {
const context: any = { props, ctx }; const api = createAccessApi(props.from === "sys" ? "/sys/access" : "/pi/access");
const context: any = { props, ctx, api };
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context }); const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
// crud // crud

View File

@ -18,9 +18,8 @@
<script> <script>
import { defineComponent, reactive, ref, watch } from "vue"; import { defineComponent, reactive, ref, watch } from "vue";
import * as api from "../api";
import CertAccessModal from "./access/index.vue"; import CertAccessModal from "./access/index.vue";
import { GetProviderDefineByAccessType } from "../api"; import { createAccessApi } from "../api";
export default defineComponent({ export default defineComponent({
name: "AccessSelector", name: "AccessSelector",
@ -41,10 +40,16 @@ export default defineComponent({
size: { size: {
type: String, type: String,
default: "middle" default: "middle"
},
from: {
type: String, //user | sys
default: "user"
} }
}, },
emits: ["update:modelValue"], emits: ["update:modelValue"],
setup(props, ctx) { setup(props, ctx) {
const api = createAccessApi(props.from === "sys" ? "/sys/access" : "/pi/access");
const target = ref({}); const target = ref({});
const selectedId = ref(); const selectedId = ref();
async function refreshTarget(value) { async function refreshTarget(value) {

View File

@ -1,57 +1,61 @@
import { request } from "/src/api/service"; import { request } from "/src/api/service";
const apiPrefix = "/pi/access";
export async function GetList(query: any) { export function createAccessApi(apiPrefix = "/pi/access") {
return {
async GetList(query: any) {
return await request({ return await request({
url: apiPrefix + "/page", url: apiPrefix + "/page",
method: "post", method: "post",
data: query data: query
}); });
} },
export async function AddObj(obj: any) { async AddObj(obj: any) {
return await request({ return await request({
url: apiPrefix + "/add", url: apiPrefix + "/add",
method: "post", method: "post",
data: obj data: obj
}); });
} },
export async function UpdateObj(obj: any) { async UpdateObj(obj: any) {
return await request({ return await request({
url: apiPrefix + "/update", url: apiPrefix + "/update",
method: "post", method: "post",
data: obj data: obj
}); });
} },
export async function DelObj(id: number) { async DelObj(id: number) {
return await request({ return await request({
url: apiPrefix + "/delete", url: apiPrefix + "/delete",
method: "post", method: "post",
params: { id } params: { id }
}); });
} },
export async function GetObj(id: number) { async GetObj(id: number) {
return await request({ return await request({
url: apiPrefix + "/info", url: apiPrefix + "/info",
method: "post", method: "post",
params: { id } params: { id }
}); });
} },
export async function GetProviderDefine(type: string) { async GetProviderDefine(type: string) {
return await request({ return await request({
url: apiPrefix + "/define", url: apiPrefix + "/define",
method: "post", method: "post",
params: { type } params: { type }
}); });
} },
export async function GetProviderDefineByAccessType(type: string) { async GetProviderDefineByAccessType(type: string) {
return await request({ return await request({
url: apiPrefix + "/defineByAccessType", url: apiPrefix + "/defineByAccessType",
method: "post", method: "post",
params: { type } params: { type }
}); });
} }
};
}

View File

@ -1,12 +1,9 @@
import { ColumnCompositionProps, dict, compute } from "@fast-crud/fast-crud"; import { ColumnCompositionProps, dict } from "@fast-crud/fast-crud";
// @ts-ignore
import * as api from "./api";
// @ts-ignore
import _ from "lodash-es";
import { computed, ref, toRef } from "vue"; import { computed, ref, toRef } from "vue";
import { useReference } from "/@/use/use-refrence"; import { useReference } from "/@/use/use-refrence";
import { forEach, get, merge, set } from "lodash-es";
export function getCommonColumnDefine(crudExpose: any, typeRef: any) { export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
const AccessTypeDictRef = dict({ const AccessTypeDictRef = dict({
url: "/pi/access/accessTypeDict" url: "/pi/access/accessTypeDict"
}); });
@ -27,20 +24,20 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any) {
} }
} }
console.log('crudBinding.value[mode + "Form"].columns', columnsRef.value); console.log('crudBinding.value[mode + "Form"].columns', columnsRef.value);
_.forEach(define.input, (value: any, mapKey: any) => { forEach(define.input, (value: any, mapKey: any) => {
const key = "access." + mapKey; const key = "access." + mapKey;
const field = { const field = {
...value, ...value,
key key
}; };
const column = _.merge({ title: key }, defaultPluginConfig, field); const column = merge({ title: key }, defaultPluginConfig, field);
//eval //eval
useReference(column); useReference(column);
//设置默认值 //设置默认值
if (column.value != null && _.get(form, key) == null) { if (column.value != null && get(form, key) == null) {
_.set(form, key, column.value); set(form, key, column.value);
} }
//字段配置赋值 //字段配置赋值
columnsRef.value[key] = column; columnsRef.value[key] = column;

View File

@ -1,12 +1,12 @@
// @ts-ignore // @ts-ignore
import * as api from "./api";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { ref } from "vue"; import { ref } from "vue";
import { getCommonColumnDefine } from "/@/views/certd/access/common"; import { getCommonColumnDefine } from "/@/views/certd/access/common";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n(); const { t } = useI18n();
const api = context.api;
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => { const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query); return await api.GetList(query);
}; };
@ -28,7 +28,7 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
}; };
const typeRef = ref(); const typeRef = ref();
const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef); const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api);
return { return {
crudOptions: { crudOptions: {
request: { request: {

View File

@ -14,11 +14,13 @@
import { defineComponent, onMounted } from "vue"; import { defineComponent, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud"; import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud"; import createCrudOptions from "./crud";
import { createAccessApi } from "/@/views/certd/access/api";
export default defineComponent({ export default defineComponent({
name: "AccessManager", name: "AccessManager",
setup() { setup() {
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} }); const api = createAccessApi("/pi/access");
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { api } });
// //
onMounted(() => { onMounted(() => {

View File

@ -0,0 +1,36 @@
<template>
<fs-page>
<template #header>
<div class="title">
系统级授权管理
<span class="sub">管理第三方系统的授权信息(系统级)</span>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
</fs-page>
</template>
<script lang="ts">
import { defineComponent, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "../../certd/access/crud";
import { createAccessApi } from "/@/views/certd/access/api";
export default defineComponent({
name: "SysAccessManager",
setup() {
const api = createAccessApi("/sys/access");
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { api } });
//
onMounted(() => {
crudExpose.doRefresh();
});
return {
crudBinding,
crudRef
};
}
});
</script>

View File

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

View File

@ -0,0 +1,180 @@
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, dict, EditReq, UserPageQuery, UserPageRes, utils } from "@fast-crud/fast-crud";
import { useUserStore } from "/src/store/modules/user";
import { useSettingStore } from "/src/store/modules/settings";
import { Modal } from "ant-design-vue";
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
}
},
name: {
title: "插件名称",
type: "text",
search: {
show: true
},
form: {
show: false
},
column: {
width: 100
}
},
title: {
title: "标题",
type: "text",
column: {
width: 300
}
},
desc: {
title: "描述",
type: "text",
column: {
width: 300
}
},
group: {
title: "分组",
type: "text",
column: {
width: 300
}
},
disabled: {
title: "禁用/启用",
type: "dict-switch",
dict: dict({
data: [
{ label: "启用", value: false, color: "success" },
{ label: "禁用", value: true, color: "error" }
]
}),
form: {
value: false
},
column: {
width: 100,
component: {
title: "点击可禁用/启用",
on: {
async click({ value, row }) {
Modal.confirm({
title: "提示",
content: `确定要${!value ? "禁用" : "启用"}吗?`,
onOk: async () => {
await api.SetDisabled(row.id, !value);
await crudExpose.doRefresh();
}
});
}
}
}
}
},
createTime: {
title: "创建时间",
type: "datetime",
form: {
show: false
},
column: {
sorter: true,
width: 160,
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 "./api";
defineOptions({
name: "SysPlugin"
});
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

@ -0,0 +1,18 @@
CREATE TABLE "pi_plugin"
(
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" varchar(100) NOT NULL,
"icon" varchar(100),
"title" varchar(200),
"desc" varchar(500),
"group" varchar(100),
"version" varchar(100),
"setting" text,
"sysSetting" text,
"content" text,
"type" strinng NOT NULL,
"disabled" boolean NOT NULL,
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
);

View File

@ -1,24 +1,24 @@
## sqlite与postgres不同点 ## sqlite与postgres不同点
1. 1.
sl: AUTOINCREAMENT sqlite: AUTOINCREAMENT
pg: GENERATED BY DEFAULT AS IDENTITY postgresql: GENERATED BY DEFAULT AS IDENTITY
2. 2.
datetime sqlite: datetime
timestamp postgresql: timestamp
3. 3.
update sqlite_sequence set seq = 1000 where name = 'sys_role' ; sqlite: update sqlite_sequence set seq = 1000 where name = 'sys_role' ;
select setval('sys_role_id_seq', 1000); postgresql: select setval('sys_role_id_seq', 1000);
4. 4.
"disabled" boolean DEFAULT (0) sqlite: "disabled" boolean DEFAULT (0)
"disabled" boolean DEFAULT (false) postgresql: "disabled" boolean DEFAULT (false)
5. 5.
last_insert_rowid() sqlite: last_insert_rowid()
LASTVAL() postgresql: LASTVAL()
6. 6.
sl: integer sqlite: integer
pg: bigint postgresql: bigint

View File

@ -12,6 +12,10 @@ export class AccessController extends CrudController<AccessService> {
@Inject() @Inject()
service: AccessService; service: AccessService;
userId() {
return this.getUserId();
}
getService(): AccessService { getService(): AccessService {
return this.service; return this.service;
} }
@ -19,36 +23,36 @@ export class AccessController extends CrudController<AccessService> {
@Post('/page', { summary: Constants.per.authOnly }) @Post('/page', { summary: Constants.per.authOnly })
async page(@Body(ALL) body) { async page(@Body(ALL) body) {
body.query = body.query ?? {}; body.query = body.query ?? {};
body.query.userId = this.getUserId(); body.query.userId = this.userId();
return await super.page(body); return await super.page(body);
} }
@Post('/list', { summary: Constants.per.authOnly }) @Post('/list', { summary: Constants.per.authOnly })
async list(@Body(ALL) body) { async list(@Body(ALL) body) {
body.userId = this.getUserId(); body.userId = this.userId();
return super.list(body); return super.list(body);
} }
@Post('/add', { summary: Constants.per.authOnly }) @Post('/add', { summary: Constants.per.authOnly })
async add(@Body(ALL) bean) { async add(@Body(ALL) bean) {
bean.userId = this.getUserId(); bean.userId = this.userId();
return super.add(bean); return super.add(bean);
} }
@Post('/update', { summary: Constants.per.authOnly }) @Post('/update', { summary: Constants.per.authOnly })
async update(@Body(ALL) bean) { async update(@Body(ALL) bean) {
await this.service.checkUserId(bean.id, this.getUserId()); await this.service.checkUserId(bean.id, this.userId());
return super.update(bean); return super.update(bean);
} }
@Post('/info', { summary: Constants.per.authOnly }) @Post('/info', { summary: Constants.per.authOnly })
async info(@Query('id') id: number) { async info(@Query('id') id: number) {
await this.service.checkUserId(id, this.getUserId()); await this.service.checkUserId(id, this.userId());
return super.info(id); return super.info(id);
} }
@Post('/delete', { summary: Constants.per.authOnly }) @Post('/delete', { summary: Constants.per.authOnly })
async delete(@Query('id') id: number) { async delete(@Query('id') id: number) {
await this.service.checkUserId(id, this.getUserId()); await this.service.checkUserId(id, this.userId());
return super.delete(id); return super.delete(id);
} }

View File

@ -1,28 +0,0 @@
import { ALL, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
import { BaseController } from '@certd/lib-server';
import { PluginService } from '../service/plugin-service.js';
import { Constants } from '@certd/lib-server';
/**
*
*/
@Provide()
@Controller('/api/pi/plugin')
export class PluginController extends BaseController {
@Inject()
service: PluginService;
@Post('/list', { summary: Constants.per.authOnly })
async list(@Query(ALL) query: any) {
query.userId = this.getUserId();
const list = this.service.getList();
return this.ok(list);
}
@Post('/groups', { summary: Constants.per.authOnly })
async groups(@Query(ALL) query: any) {
query.userId = this.getUserId();
const group = this.service.getGroups();
return this.ok(group);
}
}

View File

@ -20,6 +20,7 @@ import { UserService } from '../../sys/authority/service/user-service.js';
import { AccessGetter } from './access-getter.js'; import { AccessGetter } from './access-getter.js';
import { CnameRecordService } from '../../cname/service/cname-record-service.js'; import { CnameRecordService } from '../../cname/service/cname-record-service.js';
import { CnameProxyService } from './cname-proxy-service.js'; import { CnameProxyService } from './cname-proxy-service.js';
import { PluginConfigService } from './plugin-config-service.js';
const runningTasks: Map<string | number, Executor> = new Map(); const runningTasks: Map<string | number, Executor> = new Map();
const freeCount = 10; const freeCount = 10;
@ -45,6 +46,9 @@ export class PipelineService extends BaseService<PipelineEntity> {
@Inject() @Inject()
historyLogService: HistoryLogService; historyLogService: HistoryLogService;
@Inject()
pluginConfigService: PluginConfigService;
@Inject() @Inject()
userService: UserService; userService: UserService;
@ -356,6 +360,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
onChanged, onChanged,
accessService: accessGetter, accessService: accessGetter,
cnameProxyService, cnameProxyService,
pluginConfigService: this.pluginConfigService,
storage: new DbStorage(userId, this.storageService), storage: new DbStorage(userId, this.storageService),
emailService: this.emailService, emailService: this.emailService,
fileRootDir: this.certdConfig.fileRootDir, fileRootDir: this.certdConfig.fileRootDir,

View File

@ -0,0 +1,13 @@
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { IPluginConfigService } from '@certd/pipeline';
/**
*
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class PluginConfigService implements IPluginConfigService {
getPluginConfig(pluginName: string) {
return Promise.resolve({});
}
}

View File

@ -1,8 +1,9 @@
import { Provide, Scope, ScopeEnum } from '@midwayjs/core'; import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { pluginGroups, pluginRegistry } from '@certd/pipeline'; import { pluginGroups, pluginRegistry } from '@certd/pipeline';
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Singleton)
export class PluginService { export class BuiltInPluginService {
getList() { getList() {
const collection = pluginRegistry.storage; const collection = pluginRegistry.storage;
const list = []; const list = [];

View File

@ -0,0 +1,62 @@
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
import { AccessService } from '../../pipeline/service/access-service.js';
import { AccessController } from '../../pipeline/controller/access-controller.js';
import { checkComm } from '@certd/pipeline';
/**
*
*/
@Provide()
@Controller('/api/sys/access')
export class SysAccessController extends AccessController {
@Inject()
service2: AccessService;
getService(): AccessService {
return this.service2;
}
userId() {
checkComm();
return 0;
}
@Post('/page', { summary: 'sys:settings:view' })
async page(@Body(ALL) body: any) {
return await await super.page(body);
}
@Post('/list', { summary: 'sys:settings:view' })
async list(@Body(ALL) body: any) {
return await super.list(body);
}
@Post('/add', { summary: 'sys:settings:edit' })
async add(@Body(ALL) bean: any) {
return await super.add(bean);
}
@Post('/update', { summary: 'sys:settings:edit' })
async update(@Body(ALL) bean: any) {
return await super.update(bean);
}
@Post('/info', { summary: 'sys:settings:view' })
async info(@Query('id') id: number) {
return await super.info(id);
}
@Post('/delete', { summary: 'sys:settings:edit' })
async delete(@Query('id') id: number) {
return await super.delete(id);
}
@Post('/define', { summary: 'sys:settings:view' })
async define(@Query('type') type: string) {
return await super.define(type);
}
@Post('/accessTypeDict', { summary: 'sys:settings:view' })
async getAccessTypeDict() {
return await super.getAccessTypeDict();
}
}

View File

@ -0,0 +1,68 @@
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
import { merge } from 'lodash-es';
import { CrudController } from '@certd/lib-server';
import { PluginService } from '../service/plugin-service.js';
import { checkComm } from '@certd/pipeline';
/**
*
*/
@Provide()
@Controller('/api/sys/plugin')
export class PluginController extends CrudController<PluginService> {
@Inject()
service: PluginService;
getService() {
checkComm();
return this.service;
}
@Post('/page', { summary: 'sys:settings:view' })
async page(@Body(ALL) body: any) {
body.query = body.query ?? {};
return await super.page(body);
}
@Post('/list', { summary: 'sys:settings:view' })
async list(@Body(ALL) body: any) {
return super.list(body);
}
@Post('/add', { summary: 'sys:settings:edit' })
async add(@Body(ALL) bean: any) {
const def: any = {
isDefault: false,
disabled: false,
};
merge(bean, def);
return super.add(bean);
}
@Post('/update', { summary: 'sys:settings:edit' })
async update(@Body(ALL) bean: any) {
return super.update(bean);
}
@Post('/info', { summary: 'sys:settings:view' })
async info(@Query('id') id: number) {
return super.info(id);
}
@Post('/delete', { summary: 'sys:settings:edit' })
async delete(@Query('id') id: number) {
return super.delete(id);
}
@Post('/deleteByIds', { summary: 'sys:settings:edit' })
async deleteByIds(@Body(ALL) body: { ids: number[] }) {
const res = await this.service.delete(body.ids);
return this.ok(res);
}
@Post('/setDisabled', { summary: 'sys:settings:edit' })
async setDisabled(@Body('id') id: number, @Body('disabled') disabled: boolean) {
await this.service.setDisabled(id, disabled);
return this.ok();
}
}

View File

@ -0,0 +1,50 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity('pi_plugin')
export class PluginEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'name', comment: 'Key' })
name: string;
@Column({ name: 'icon', comment: '图标' })
icon: string;
@Column({ name: 'title', comment: '标题' })
title: string;
@Column({ name: 'group', comment: '分组' })
group: string;
@Column({ name: 'desc', comment: '描述' })
desc: string;
@Column({ comment: '配置', length: 40960 })
setting: string;
@Column({ comment: '系统配置', length: 40960 })
sysSetting: string;
@Column({ comment: '脚本', length: 40960 })
content: string;
@Column({ comment: '类型', length: 100, nullable: true })
type: string; // builtIn | custom
@Column({ comment: '启用/禁用', default: false })
disabled: boolean;
@Column({
name: 'create_time',
comment: '创建时间',
default: () => 'CURRENT_TIMESTAMP',
})
createTime: Date;
@Column({
name: 'update_time',
comment: '修改时间',
default: () => 'CURRENT_TIMESTAMP',
})
updateTime: Date;
}

View File

@ -0,0 +1,23 @@
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { BaseService } from '@certd/lib-server';
import { PluginEntity } from '../entity/plugin.js';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { checkComm } from '@certd/pipeline';
@Provide()
@Scope(ScopeEnum.Singleton)
export class PluginService extends BaseService<PluginEntity> {
@InjectEntityModel(PluginEntity)
repository: Repository<PluginEntity>;
//@ts-ignore
getRepository() {
checkComm();
return this.repository;
}
async setDisabled(id: number, disabled: boolean) {
await this.repository.update({ id }, { disabled });
}
}