From 17dd77cc96c397c87c408beb88a75aff324c8f72 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Wed, 4 Mar 2026 23:15:48 +0800 Subject: [PATCH] chore: project userid fixed -1 --- .../lib-server/src/basic/base-controller.ts | 2 +- .../libs/lib-server/src/basic/base-service.ts | 4 +-- .../src/user/access/entity/access.ts | 5 +--- .../src/locales/langs/en-US/certd.ts | 4 +++ .../src/locales/langs/zh-CN/certd.ts | 5 +++- .../src/router/source/modules/certd.ts | 30 +++++++++---------- .../access/access-selector/access/crud.tsx | 2 +- .../src/views/certd/access/crud.tsx | 2 +- .../src/views/sys/enterprise/project/crud.tsx | 26 ++++------------ .../db/migration/v10038__admin_mode.sql | 3 +- .../sys/account/account-controller.ts | 6 ++++ .../controller/sys/addon/addon-controller.ts | 2 +- .../sys/enterprise/project-controller.ts | 2 +- .../mine/service/two-factor-service.ts | 2 +- .../monitor/service/site-info-service.ts | 14 ++++----- .../monitor/service/site-ip-service.ts | 2 +- .../pipeline/service/history-service.ts | 6 ++-- .../pipeline/service/pipeline-service.ts | 30 ++++++++++++++----- .../modules/sys/enterprise/entity/project.ts | 4 +++ .../service/project-member-service.ts | 2 +- .../sys/enterprise/service/project-service.ts | 16 +++++++--- 21 files changed, 97 insertions(+), 72 deletions(-) diff --git a/packages/libs/lib-server/src/basic/base-controller.ts b/packages/libs/lib-server/src/basic/base-controller.ts index 23816ec09..7b58cd6c2 100644 --- a/packages/libs/lib-server/src/basic/base-controller.ts +++ b/packages/libs/lib-server/src/basic/base-controller.ts @@ -83,7 +83,7 @@ export abstract class BaseController { let userId = this.getUserId() const projectId = await this.getProjectId(permission) if(projectId){ - userId = 0 + userId = -1 // 企业管理模式下,用户id固定-1 } return { projectId,userId diff --git a/packages/libs/lib-server/src/basic/base-service.ts b/packages/libs/lib-server/src/basic/base-service.ts index a9a26046b..00e9e5fd4 100644 --- a/packages/libs/lib-server/src/basic/base-service.ts +++ b/packages/libs/lib-server/src/basic/base-service.ts @@ -258,12 +258,12 @@ export abstract class BaseService { export function checkUserProjectParam(userId: number, projectId: number) { if (projectId != null ){ - if( userId !==0) { + if( userId !==-1) { throw new ValidateException('userId projectId 错误'); } return true }else{ - if( userId > 0) { + if( userId != null) { return true } throw new ValidateException('userId不能为空'); diff --git a/packages/libs/lib-server/src/user/access/entity/access.ts b/packages/libs/lib-server/src/user/access/entity/access.ts index 87968bc5f..fa8349370 100644 --- a/packages/libs/lib-server/src/user/access/entity/access.ts +++ b/packages/libs/lib-server/src/user/access/entity/access.ts @@ -8,7 +8,7 @@ export class AccessEntity { @PrimaryGeneratedColumn() id: number; @Column({ name: 'user_id', comment: '用户id' }) - userId: number; + userId: number; // 0为系统级别, -1为企业,大于1为用户 @Column({ comment: '名称', length: 100 }) name: string; @@ -24,9 +24,6 @@ export class AccessEntity { @Column({ name: 'project_id', comment: '项目id' }) projectId: number; - @Column({ comment: '权限等级', length: 100 }) - level: string; // user common system - @Column({ name: 'create_time', comment: '创建时间', diff --git a/packages/ui/certd-client/src/locales/langs/en-US/certd.ts b/packages/ui/certd-client/src/locales/langs/en-US/certd.ts index cee33d466..4c8394bfe 100644 --- a/packages/ui/certd-client/src/locales/langs/en-US/certd.ts +++ b/packages/ui/certd-client/src/locales/langs/en-US/certd.ts @@ -220,6 +220,7 @@ export default { myProjectDetail: "Project Detail", projectJoin: "Join Project", currentProject: "Current Project", + projectMemberManager: "Project Member", }, certificateRepo: { title: "Certificate Repository", @@ -822,6 +823,9 @@ export default { admin: "Admin", }, projectMemberStatus: "Member Status", + + isSystem: "Is System Project", + isSystemHelper: "System-level projects allow running admin plugins", }, project: { noProjectJoined: "You haven't joined any projects yet", diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts index e60b96e65..030759015 100644 --- a/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts +++ b/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts @@ -220,12 +220,12 @@ export default { netTest: "网络测试", enterpriseManager: "企业管理设置", projectManager: "项目管理", - projectDetail: "项目详情", enterpriseSetting: "企业设置", myProjectManager: "我的项目", myProjectDetail: "项目详情", projectJoin: "加入项目", currentProject: "当前项目", + projectMemberManager: "项目成员管理", }, certificateRepo: { title: "证书仓库", @@ -838,6 +838,9 @@ export default { admin: "管理员", }, projectMemberStatus: "成员状态", + + isSystem: "是否系统项目", + isSystemHelper: "系统级项目允许运行管理员插件", }, project: { noProjectJoined: "您还没有加入任何项目", diff --git a/packages/ui/certd-client/src/router/source/modules/certd.ts b/packages/ui/certd-client/src/router/source/modules/certd.ts index 936733a8b..e38745825 100644 --- a/packages/ui/certd-client/src/router/source/modules/certd.ts +++ b/packages/ui/certd-client/src/router/source/modules/certd.ts @@ -29,21 +29,6 @@ export const certdResources = [ auth: true, }, }, - { - title: "certd.sysResources.currentProject", - name: "CurrentProject", - path: "/certd/project/detail", - component: "/certd/project/detail/index.vue", - meta: { - show: () => { - const projectStore = useProjectStore(); - return projectStore.isEnterprise; - }, - isMenu: true, - icon: "ion:apps", - auth: true, - }, - }, { title: "certd.sysResources.projectJoin", name: "ProjectJoin", @@ -278,6 +263,21 @@ export const certdResources = [ isMenu: false, }, }, + { + title: "certd.sysResources.projectMemberManager", + name: "ProjectMemberManager", + path: "/certd/project/detail", + component: "/certd/project/detail/index.vue", + meta: { + show: () => { + const projectStore = useProjectStore(); + return projectStore.isEnterprise; + }, + isMenu: true, + icon: "ion:apps", + auth: true, + }, + }, ], }, { diff --git a/packages/ui/certd-client/src/views/certd/access/access-selector/access/crud.tsx b/packages/ui/certd-client/src/views/certd/access/access-selector/access/crud.tsx index 821514155..880361bb3 100644 --- a/packages/ui/certd-client/src/views/certd/access/access-selector/access/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/access/access-selector/access/crud.tsx @@ -143,7 +143,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat order: 10, }, valueBuilder: ({ row, key, value }) => { - row[key] = row.userId > 0 ? "user" : "sys"; + row[key] = row.userId != 0 ? "user" : "sys"; }, }, ...commonColumnsDefine, diff --git a/packages/ui/certd-client/src/views/certd/access/crud.tsx b/packages/ui/certd-client/src/views/certd/access/crud.tsx index d15dd61a9..f3be184f6 100644 --- a/packages/ui/certd-client/src/views/certd/access/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/access/crud.tsx @@ -121,7 +121,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat order: 10, }, valueBuilder: ({ row, key, value }) => { - row[key] = row.userId > 0 ? "user" : "sys"; + row[key] = row.userId != 0 ? "user" : "sys"; }, }, ...commonColumnsDefine, diff --git a/packages/ui/certd-client/src/views/sys/enterprise/project/crud.tsx b/packages/ui/certd-client/src/views/sys/enterprise/project/crud.tsx index b509f8dc7..a3c6b7645 100644 --- a/packages/ui/certd-client/src/views/sys/enterprise/project/crud.tsx +++ b/packages/ui/certd-client/src/views/sys/enterprise/project/crud.tsx @@ -90,35 +90,21 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat }, }, }, - disabled: { - title: t("certd.disabled"), + isSystem: { + title: t("certd.ent.isSystem"), type: "dict-switch", dict: dict({ data: [ - { label: t("certd.enabled"), value: false, color: "success" }, - { label: t("certd.disabledLabel"), value: true, color: "error" }, + { label: t("common.yes"), value: true, color: "success" }, + { label: t("common.no"), value: false, color: "error" }, ], }), form: { - value: false, + value: true, + helper: t("certd.ent.isSystemHelper"), }, column: { width: 100, - component: { - title: t("certd.clickToToggle"), - on: { - async click({ value, row }) { - Modal.confirm({ - title: t("certd.prompt"), - content: t("certd.confirmToggleStatus", { action: !value ? t("certd.disable") : t("certd.enable") }), - onOk: async () => { - await api.SetDisabled(row.id, !value); - await crudExpose.doRefresh(); - }, - }); - }, - }, - }, }, }, adminId: { diff --git a/packages/ui/certd-server/db/migration/v10038__admin_mode.sql b/packages/ui/certd-server/db/migration/v10038__admin_mode.sql index 8e7a88f9f..4111cae61 100644 --- a/packages/ui/certd-server/db/migration/v10038__admin_mode.sql +++ b/packages/ui/certd-server/db/migration/v10038__admin_mode.sql @@ -6,6 +6,7 @@ CREATE TABLE "cd_project" "name" varchar(512) NOT NULL, "admin_id" integer NOT NULL, "disabled" boolean NOT NULL DEFAULT (false), + "is_system" boolean NOT NULL DEFAULT (false), "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP) ); @@ -13,7 +14,7 @@ CREATE TABLE "cd_project" CREATE INDEX "index_project_user_id" ON "cd_project" ("user_id"); CREATE INDEX "index_project_admin_id" ON "cd_project" ("admin_id"); -INSERT INTO cd_project (id, user_id, "admin_id", "name", "disabled") VALUES (1, 0, 1,'default', false); +INSERT INTO cd_project (id, user_id, "admin_id", "name", "disabled") VALUES (1, -1, 1,'default', false,false); ALTER TABLE cd_cert_info ADD COLUMN project_id integer; CREATE INDEX "index_cert_project_id" ON "cd_cert_info" ("project_id"); diff --git a/packages/ui/certd-server/src/controller/sys/account/account-controller.ts b/packages/ui/certd-server/src/controller/sys/account/account-controller.ts index cd55986a8..3af8074d1 100644 --- a/packages/ui/certd-server/src/controller/sys/account/account-controller.ts +++ b/packages/ui/certd-server/src/controller/sys/account/account-controller.ts @@ -21,12 +21,18 @@ export class BasicController extends BaseController { @Post('/preBindUser', { summary: 'sys:settings:edit' }) public async preBindUser(@Body(ALL) body: PreBindUserReq) { // 设置缓存内容 + if (body.userId == null || body.userId <= 0) { + throw new Error("用户ID不能为空"); + } await this.plusService.userPreBind(body.userId); return this.ok({}); } @Post('/bindUser', { summary: 'sys:settings:edit' }) public async bindUser(@Body(ALL) body: BindUserReq) { + if (body.userId == null || body.userId <= 0) { + throw new Error("用户ID不能为空"); + } const installInfo: SysInstallInfo = await this.sysSettingsService.getSetting(SysInstallInfo); installInfo.bindUserId = body.userId; await this.sysSettingsService.saveSetting(installInfo); diff --git a/packages/ui/certd-server/src/controller/sys/addon/addon-controller.ts b/packages/ui/certd-server/src/controller/sys/addon/addon-controller.ts index 804460142..a42aa240f 100644 --- a/packages/ui/certd-server/src/controller/sys/addon/addon-controller.ts +++ b/packages/ui/certd-server/src/controller/sys/addon/addon-controller.ts @@ -14,7 +14,7 @@ export class SysAddonController extends AddonController { async getProjectUserId(permission:string){ return { - projectId:null,userId:0 + projectId:null,userId:0 //0为系统级别 } } getUserId() { diff --git a/packages/ui/certd-server/src/controller/sys/enterprise/project-controller.ts b/packages/ui/certd-server/src/controller/sys/enterprise/project-controller.ts index 3066a31fc..73ae7c689 100644 --- a/packages/ui/certd-server/src/controller/sys/enterprise/project-controller.ts +++ b/packages/ui/certd-server/src/controller/sys/enterprise/project-controller.ts @@ -40,7 +40,7 @@ export class SysProjectController extends CrudController { bean.userId = this.getUserId(); return super.add({ ...bean, - userId:0, + userId:-1, //企业用户id固定为-1 adminId: bean.userId, }); } diff --git a/packages/ui/certd-server/src/modules/mine/service/two-factor-service.ts b/packages/ui/certd-server/src/modules/mine/service/two-factor-service.ts index 134c58da9..4f327ba64 100644 --- a/packages/ui/certd-server/src/modules/mine/service/two-factor-service.ts +++ b/packages/ui/certd-server/src/modules/mine/service/two-factor-service.ts @@ -63,7 +63,7 @@ export class TwoFactorService { } async offAuthenticator(userId:number) { - if (!userId) { + if (!userId || userId <= 0) { throw new Error("userId is required"); } diff --git a/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts b/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts index e6840f121..f92c2c3d4 100644 --- a/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts +++ b/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts @@ -358,7 +358,7 @@ export class SiteInfoService extends BaseService { async checkList(sites: SiteInfoEntity[],isCommon: boolean) { const cache = {} const getFromCache = async (userId: number,projectId?: number) =>{ - const key = `${userId}-${projectId??""}` + const key = `${userId}_${projectId??""}` if (cache[key]) { return cache[key]; } @@ -424,7 +424,7 @@ export class SiteInfoService extends BaseService { if (!req.text) { throw new Error("text is required"); } - if (!req.userId) { + if (req.userId == null) { throw new Error("userId is required"); } @@ -479,7 +479,7 @@ export class SiteInfoService extends BaseService { } clearSiteMonitorJob(userId: number,projectId?: number) { - this.cron.remove(`siteMonitor-${userId}-${projectId||""}`); + this.cron.remove(`siteMonitor_${userId}_${projectId||""}`); } async registerSiteMonitorJob(userId?: number,projectId?: number) { @@ -502,7 +502,7 @@ export class SiteInfoService extends BaseService { } //注册个人的 或项目的 this.cron.register({ - name: `siteMonitor-${userId}-${projectId||""}`, + name: `siteMonitor_${userId}_${projectId||""}`, cron: setting.cron, job: () => this.triggerJobOnce(userId,projectId), }); @@ -511,9 +511,9 @@ export class SiteInfoService extends BaseService { } async triggerJobOnce(userId?:number,projectId?:number) { - logger.info(`站点证书检查开始执行[${userId??'所有用户'}-${projectId??'所有项目'}]`); + logger.info(`站点证书检查开始执行[${userId??'所有用户'}_${projectId??'所有项目'}]`); const query:any = { disabled: false }; - if(userId){ + if(userId!=null){ query.userId = userId; if(projectId){ query.projectId = projectId; @@ -541,7 +541,7 @@ export class SiteInfoService extends BaseService { await this.checkList(records,isCommon); } - logger.info(`站点证书检查完成[${userId??'所有用户'}-${projectId??'所有项目'}]`); + logger.info(`站点证书检查完成[${userId??'所有用户'}_${projectId??'所有项目'}]`); } async batchDelete(ids: number[], userId: number,projectId?:number): Promise { diff --git a/packages/ui/certd-server/src/modules/monitor/service/site-ip-service.ts b/packages/ui/certd-server/src/modules/monitor/service/site-ip-service.ts index 1045ac1ae..6b67d5c80 100644 --- a/packages/ui/certd-server/src/modules/monitor/service/site-ip-service.ts +++ b/packages/ui/certd-server/src/modules/monitor/service/site-ip-service.ts @@ -43,7 +43,7 @@ export class SiteIpService extends BaseService { } async add(data: SiteIpEntity) { - if (!data.userId) { + if (data.userId == null) { throw new Error("userId is required"); } data.disabled = false; diff --git a/packages/ui/certd-server/src/modules/pipeline/service/history-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/history-service.ts index 4cc1a1b2d..352582482 100644 --- a/packages/ui/certd-server/src/modules/pipeline/service/history-service.ts +++ b/packages/ui/certd-server/src/modules/pipeline/service/history-service.ts @@ -188,11 +188,11 @@ export class HistoryService extends BaseService { const where: any = { createTime: MoreThan(todayEnd.add(-param.days, 'day').toDate()), }; - if (param.userId > 0) { - where.userId = param.userId; - } + if (param.projectId > 0) { where.projectId = param.projectId; + }else if (param.userId > 0) { + where.userId = param.userId; } const result = await this.getRepository() .createQueryBuilder('main') diff --git a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts index 7eddef03f..6d9926187 100644 --- a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts +++ b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts @@ -50,6 +50,7 @@ import { nanoid } from "nanoid"; import { set } from "lodash-es"; import { executorQueue } from "@certd/lib-server"; import parser from "cron-parser"; +import { ProjectService } from "../../sys/enterprise/service/project-service.js"; const runningTasks: Map = new Map(); @@ -107,6 +108,9 @@ export class PipelineService extends BaseService { @Inject() certInfoService: CertInfoService; + @Inject() + projectService: ProjectService; + //@ts-ignore getRepository() { return this.repository; @@ -252,6 +256,8 @@ export class PipelineService extends BaseService { //修改 old = await this.info(bean.id); bean.order = old.order; + bean.userId = old.userId; + bean.projectId = old.projectId; } if (!old || !old.webhookKey) { bean.webhookKey = await this.genWebhookKey(); @@ -262,6 +268,8 @@ export class PipelineService extends BaseService { const pipeline = JSON.parse(bean.content || "{}"); RunnableCollection.initPipelineRunnableType(pipeline); + pipeline.userId = bean.userId; + pipeline.projectId = bean.projectId; let domains = []; if (pipeline.stages) { RunnableCollection.each(pipeline.stages, (runnable: any) => { @@ -295,8 +303,8 @@ export class PipelineService extends BaseService { } else if (bean.type === "cert_auto") { fromType = "auto"; } - const userId = pipeline.userId || bean.userId; - const projectId = pipeline.projectId ?? bean.projectId ??null; + const userId = bean.userId; + const projectId = bean.projectId ??null; await this.certInfoService.updateDomains(pipeline.id, userId, projectId , domains, fromType); return { ...bean, @@ -672,9 +680,12 @@ export class PipelineService extends BaseService { }; const userId = entity.userId; - const historyId = await this.historyService.start(entity, triggerType); + const projectId = entity.projectId; let userIsAdmin = false - if(userId){ + + if (projectId && projectId>0) { + userIsAdmin = await this.projectService.isAdmin(projectId); + }else if(userId>0){ userIsAdmin = await this.userService.isAdmin(userId); } const user: UserInfo = { @@ -682,7 +693,7 @@ export class PipelineService extends BaseService { role: userIsAdmin ? "admin" : "user" }; - + const historyId = await this.historyService.start(entity, triggerType); const sysInfo: SysInfo = {}; if (isComm()) { const siteInfo = await this.sysSettingsService.getSetting(SysSiteInfo); @@ -690,7 +701,8 @@ export class PipelineService extends BaseService { } const taskServiceGetter = this.taskServiceBuilder.create({ - userId + userId, + projectId }); const accessGetter = await taskServiceGetter.get("accessService"); const notificationGetter = await taskServiceGetter.get("notificationService"); @@ -920,7 +932,7 @@ export class PipelineService extends BaseService { throw new NeedVIPException("此功能需要升级专业版"); } for (const id of ids) { - if (userId) { + if (userId && userId > 0) { await this.checkUserId(id, userId); } if(projectId){ @@ -1104,6 +1116,10 @@ export class PipelineService extends BaseService { private async checkUserStatus(userId: number) { + if(isEnterprise()){ + //企业模式不检查用户状态,都允许运行流水线 + return + } const userEntity = await this.userService.info(userId); if (userEntity == null) { throw new Error("用户不存在"); diff --git a/packages/ui/certd-server/src/modules/sys/enterprise/entity/project.ts b/packages/ui/certd-server/src/modules/sys/enterprise/entity/project.ts index 52c12f015..78897dcee 100644 --- a/packages/ui/certd-server/src/modules/sys/enterprise/entity/project.ts +++ b/packages/ui/certd-server/src/modules/sys/enterprise/entity/project.ts @@ -19,6 +19,10 @@ export class ProjectEntity { @Column({ name: 'disabled', comment: '禁用' }) disabled: boolean; + @Column({ name: 'is_system', comment: '是否系统项目' }) + isSystem: boolean; //系统项目内的流水线允许运行管理员级别的插件 + + @Column({ name: 'create_time', comment: '创建时间', diff --git a/packages/ui/certd-server/src/modules/sys/enterprise/service/project-member-service.ts b/packages/ui/certd-server/src/modules/sys/enterprise/service/project-member-service.ts index 6d86d87ff..e61dcd7d3 100644 --- a/packages/ui/certd-server/src/modules/sys/enterprise/service/project-member-service.ts +++ b/packages/ui/certd-server/src/modules/sys/enterprise/service/project-member-service.ts @@ -23,7 +23,7 @@ export class ProjectMemberService extends BaseService { if (!projectId) { throw new Error('项目ID不能为空'); } - if (!userId) { + if (!userId || userId <= 0) { throw new Error('用户ID不能为空'); } const exist = await this.repository.findOne({ diff --git a/packages/ui/certd-server/src/modules/sys/enterprise/service/project-service.ts b/packages/ui/certd-server/src/modules/sys/enterprise/service/project-service.ts index 17dadb9b2..e594020e4 100644 --- a/packages/ui/certd-server/src/modules/sys/enterprise/service/project-service.ts +++ b/packages/ui/certd-server/src/modules/sys/enterprise/service/project-service.ts @@ -11,9 +11,12 @@ const projectCache = new LRUCache({ ttl: 1000 * 60 * 10, }); +const ENTERPRISE_USER_ID = -1 //企业模式下 企业userId 固定为-1 + @Provide() @Scope(ScopeEnum.Request, { allowDowngrade: true }) export class ProjectService extends BaseService { + @InjectEntityModel(ProjectEntity) repository: Repository; @@ -36,7 +39,7 @@ export class ProjectService extends BaseService { const exist = await this.repository.findOne({ where: { name, - userId: 0, + userId: ENTERPRISE_USER_ID, }, }); if (exist) { @@ -57,7 +60,7 @@ export class ProjectService extends BaseService { async setDisabled(id: number, disabled: boolean) { await this.repository.update({ id, - userId: 0, + userId: ENTERPRISE_USER_ID, }, { disabled, }); @@ -69,7 +72,7 @@ export class ProjectService extends BaseService { const projectIds = memberList.map(item => item.projectId); const projectList = await this.repository.createQueryBuilder('project') .where(' project.disabled = false') - .where(' project.userId = :userId', { userId: 0 }) + .where(' project.userId = :userId', { userId: ENTERPRISE_USER_ID }) .where(' project.id IN (:...projectIds) or project.adminId = :userId', { projectIds, userId }) .getMany(); @@ -93,7 +96,7 @@ export class ProjectService extends BaseService { let projectList: any = await this.find({ where: { disabled: false, - userId: 0, + userId: ENTERPRISE_USER_ID, }, }) const projectMemberItemList: ProjectMemberItem[] = projectList @@ -269,4 +272,9 @@ export class ProjectService extends BaseService { }) } + async isAdmin(projectId: number): Promise { + const project = await this.info(projectId); + return project?.isSystem ?? false; + } + }