From ae172e45ba12345fa2c4395026168428155b02bd Mon Sep 17 00:00:00 2001 From: GZTime Date: Sun, 18 Sep 2022 20:45:53 +0800 Subject: [PATCH] feat: PrivilegedContainer for challenge --- GZCTF/ClientApp/src/Api.ts | 1365 +++-------------- GZCTF/ClientApp/src/App.tsx | 2 +- .../src/components/admin/FlagEditPanel.tsx | 6 +- GZCTF/ClientApp/src/pages/account/Confirm.tsx | 2 +- GZCTF/ClientApp/src/pages/account/Verify.tsx | 7 +- .../games/[id]/challenges/[chalId]/Index.tsx | 32 +- .../ClientApp/src/pages/games/[id]/Index.tsx | 2 +- GZCTF/Models/Internal/ContainerConfig.cs | 5 + GZCTF/Repositories/InstanceRepository.cs | 3 +- GZCTF/Services/DockerService.cs | 5 +- GZCTF/Services/K8sService.cs | 4 + 11 files changed, 275 insertions(+), 1158 deletions(-) diff --git a/GZCTF/ClientApp/src/Api.ts b/GZCTF/ClientApp/src/Api.ts index abda677f8..006a8e9be 100644 --- a/GZCTF/ClientApp/src/Api.ts +++ b/GZCTF/ClientApp/src/Api.ts @@ -12,213 +12,98 @@ import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, ResponseType } from 'axios' import useSWR, { mutate, MutatorOptions, SWRConfiguration } from 'swr' -/** - * 请求响应 - */ export interface RequestResponseOfRegisterStatus { - /** 响应信息 */ title?: string - - /** 数据 */ data?: RegisterStatus - /** - * 状态码 - * @format int32 - */ + /** @format int32 */ status?: number } -/** - * 登录响应状态 - */ export enum RegisterStatus { LoggedIn = 'LoggedIn', AdminConfirmationRequired = 'AdminConfirmationRequired', EmailConfirmationRequired = 'EmailConfirmationRequired', } -/** - * 请求响应 - */ export interface RequestResponse { - /** 响应信息 */ title?: string - /** - * 状态码 - * @format int32 - */ + /** @format int32 */ status?: number } -/** - * 注册账号 - */ export interface RegisterModel { - /** 用户名 */ userName: string - - /** 密码 */ password: string - /** - * 邮箱 - * @format email - */ + /** @format email */ email: string - - /** Google Recaptcha Token */ gToken?: string | null } -/** - * 找回账号 - */ export interface RecoveryModel { - /** - * 用户邮箱 - * @format email - */ + /** @format email */ email: string - - /** Google Recaptcha Token */ gToken?: string | null } -/** - * 账号密码重置 - */ export interface PasswordResetModel { - /** 密码 */ password: string - - /** 邮箱 */ email: string - - /** 邮箱接收到的Base64格式Token */ rToken: string } -/** - * 账号验证 - */ export interface AccountVerifyModel { - /** 邮箱接收到的Base64格式Token */ token: string - - /** 用户邮箱的Base64格式 */ email: string } -/** - * 登录 - */ export interface LoginModel { - /** 用户名或邮箱 */ userName: string - - /** 密码 */ password: string } -/** - * 基本账号信息更改 - */ export interface ProfileUpdateModel { - /** 用户名 */ userName?: string | null - - /** 描述 */ bio?: string | null - /** - * 手机号 - * @format phone - */ + /** @format phone */ phone?: string | null - - /** 真实姓名 */ realName?: string | null - - /** 学工号 */ stdNumber?: string | null } -/** - * 密码更改 - */ export interface PasswordChangeModel { - /** 旧密码 */ old: string - - /** 新密码 */ new: string } -/** - * 请求响应 - */ export interface RequestResponseOfBoolean { - /** 响应信息 */ title?: string - - /** 数据 */ data?: boolean - /** - * 状态码 - * @format int32 - */ + /** @format int32 */ status?: number } -/** - * 邮箱更改 - */ export interface MailChangeModel { - /** - * 新邮箱 - * @format email - */ + /** @format email */ newMail: string } -/** - * 基本账号信息 - */ export interface ProfileUserInfoModel { - /** 用户ID */ userId?: string | null - - /** 用户名 */ userName?: string | null - - /** 邮箱 */ email?: string | null - - /** 签名 */ bio?: string | null - - /** 手机号码 */ phone?: string | null - - /** 真实姓名 */ realName?: string | null - - /** 学工号 */ stdNumber?: string | null - - /** 头像链接 */ avatar?: string | null - - /** 用户角色 */ role?: Role | null } -/** - * 用户权限枚举 - */ export enum Role { Banned = 'Banned', User = 'User', @@ -226,197 +111,83 @@ export enum Role { Admin = 'Admin', } -/** - * 全局配置更新对象 - */ export interface ConfigEditModel { - /** 用户策略 */ accountPolicy?: AccountPolicy | null - - /** 全局配置项 */ globalConfig?: GlobalConfig | null } -/** - * 账户策略 - */ export interface AccountPolicy { - /** 允许用户注册 */ allowRegister?: boolean - - /** 注册时直接激活账户 */ activeOnRegister?: boolean - - /** 使用谷歌验证码校验 */ useGoogleRecaptcha?: boolean - - /** 注册、更换邮箱、找回密码需要邮件确认 */ emailConfirmationRequired?: boolean - - /** 邮箱后缀域名,以逗号分割 */ emailDomainList?: string } -/** - * 全局设置 - */ export interface GlobalConfig { - /** 平台前缀名称 */ title?: string } -/** - * 用户信息(Admin) - */ export interface UserInfoModel { - /** 用户ID */ id?: string | null - - /** 用户名 */ userName?: string | null - - /** 真实姓名 */ realName?: string | null - - /** 学号 */ stdNumber?: string | null - - /** 联系电话 */ phone?: string | null - - /** 签名 */ bio?: string | null - /** - * 注册时间 - * @format date-time - */ + /** @format date-time */ registerTimeUTC?: string - /** - * 用户最近访问时间 - * @format date-time - */ + /** @format date-time */ lastVisitedUTC?: string - - /** 用户最近访问IP */ ip?: string - - /** 邮箱 */ email?: string | null - - /** 头像链接 */ avatar?: string | null - - /** 用户角色 */ role?: Role | null - - /** 用户是否通过邮箱验证(可登录) */ emailConfirmed?: boolean | null } -/** - * 队伍信息 - */ export interface TeamInfoModel { - /** - * 队伍 Id - * @format int32 - */ + /** @format int32 */ id?: number - - /** 队伍名称 */ name?: string | null - - /** 队伍签名 */ bio?: string | null - - /** 头像链接 */ avatar?: string | null - - /** 是否锁定 */ locked?: boolean - - /** 队伍成员 */ members?: TeamUserInfoModel[] | null } -/** - * 队员信息 - */ export interface TeamUserInfoModel { - /** 用户ID */ id?: string | null - - /** 用户名 */ userName?: string | null - - /** 签名 */ bio?: string | null - - /** 头像链接 */ avatar?: string | null - - /** 是否是队长 */ captain?: boolean } -/** - * 用户信息更改(Admin) - */ export interface UpdateUserInfoModel { - /** 用户名 */ userName?: string | null - /** - * 邮箱 - * @format email - */ + /** @format email */ email?: string | null - - /** 签名 */ bio?: string | null - /** - * 手机号码 - * @format phone - */ + /** @format phone */ phone?: string | null - - /** 真实姓名 */ realName?: string | null - - /** 学工号 */ stdNumber?: string | null - - /** 用户是否通过邮箱验证(可登录) */ emailConfirmed?: boolean | null - - /** 用户角色 */ role?: Role | null } -/** - * 日志信息(Admin) - */ export interface LogMessageModel { - /** - * 日志时间 - * @format date-time - */ + /** @format date-time */ time?: string - - /** 用户名 */ name?: string | null level?: string | null - - /** IP地址 */ ip?: string | null - - /** 日志信息 */ msg?: string | null - - /** 任务状态 */ status?: string | null } @@ -429,10 +200,7 @@ export enum ParticipationStatus { } export interface LocalFile { - /** 文件哈希 */ hash?: string - - /** 文件名 */ name: string } @@ -446,150 +214,65 @@ export interface ProblemDetails { instance?: string | null } -/** - * 文章对象(Edit) - */ export interface PostEditModel { - /** 通知标题 */ title: string - - /** 文章总结 */ summary?: string - - /** 文章内容 */ content?: string - - /** 文章标签 */ tags?: string[] - - /** 是否置顶 */ isPinned?: boolean } -/** - * 文章详细内容 - */ export interface PostDetailModel { - /** 文章 Id */ id: string - - /** 通知标题 */ title: string - - /** 文章总结 */ summary: string - - /** 文章内容 */ content: string - - /** 是否置顶 */ isPinned: boolean - - /** 文章标签 */ tags?: string[] | null - - /** 作者头像 */ autherAvatar?: string | null - - /** 作者名称 */ autherName?: string | null - /** - * 发布时间 - * @format date-time - */ + /** @format date-time */ time: string } -/** - * 比赛信息(Edit) - */ export interface GameInfoModel { - /** - * 比赛 Id - * @format int32 - */ + /** @format int32 */ id?: number - - /** 比赛标题 */ title: string - - /** 是否隐藏 */ hidden?: boolean - - /** 比赛描述 */ summary?: string - - /** 比赛详细介绍 */ content?: string - - /** 报名队伍免审核 */ acceptWithoutReview?: boolean - - /** 比赛邀请码 */ inviteCode?: string | null - - /** 参赛所属单位列表 */ organizations?: string[] | null - /** - * 队员数量限制, 0 为无上限 - * @format int32 - */ + /** @format int32 */ teamMemberCountLimit?: number - /** - * 队伍同时开启的容器数量限制 - * @format int32 - */ + /** @format int32 */ containerCountLimit?: number - - /** 比赛头图 */ poster?: string | null - - /** 比赛签名公钥 */ publicKey?: string - - /** 比赛是否为练习模式(比赛结束够依然可以访问) */ practiceMode?: boolean - /** - * 开始时间 - * @format date-time - */ + /** @format date-time */ start: string - /** - * 结束时间 - * @format date-time - */ + /** @format date-time */ end: string } -/** -* 比赛通知,会发往客户端。 -信息涵盖一二三血通知、提示发布通知、题目开启通知等 -*/ export interface GameNotice { /** @format int32 */ id: number - - /** 通知类型 */ type: NoticeType - - /** 通知内容 */ content: string - /** - * 发布时间 - * @format date-time - */ + /** @format date-time */ time: string } -/** - * 比赛公告类型 - */ export enum NoticeType { Normal = 'Normal', FirstBlood = 'FirstBlood', @@ -599,108 +282,53 @@ export enum NoticeType { NewChallenge = 'NewChallenge', } -/** - * 比赛通知(Edit) - */ export interface GameNoticeModel { - /** 通知内容 */ content: string } -/** - * 题目详细信息(Edit) - */ export interface ChallengeEditDetailModel { - /** - * 题目Id - * @format int32 - */ + /** @format int32 */ id?: number - - /** 题目名称 */ title: string - - /** 题目内容 */ content?: string - - /** 题目标签 */ tag: ChallengeTag - - /** 题目类型 */ type: ChallengeType - - /** 题目提示 */ hints?: string[] - - /** Flag 模版,用于根据 Token 和题目、比赛信息生成 Flag */ flagTemplate?: string | null - - /** 是否启用题目 */ isEnabled: boolean - - /** 镜像名称与标签 */ containerImage: string - /** - * 运行内存限制 (MB) - * @format int32 - */ + /** @format int32 */ memoryLimit: number - /** - * CPU 运行数量限制 - * @format int32 - */ + /** @format int32 */ cpuCount: number - /** - * 镜像暴露端口 - * @format int32 - */ + /** @format int32 */ containerExposePort: number + privilegedContainer?: boolean | null - /** - * 初始分数 - * @format int32 - */ + /** @format int32 */ originalScore: number /** - * 最低分数比例 * @format double * @min 0 * @max 1 */ minScoreRate: number - /** - * 难度系数 - * @format double - */ + /** @format double */ difficulty: number - /** - * 通过人数 - * @format int32 - */ + /** @format int32 */ acceptedCount: number - - /** 统一文件名(仅用于动态附件) */ fileName?: string | null - - /** 题目附件(动态附件存放于 FlagInfoModel) */ attachment?: Attachment | null - - /** 测试容器 */ testContainer?: ContainerInfoModel | null - - /** 题目 Flag 信息 */ flags: FlagInfoModel[] } -/** - * 题目标签 - */ export enum ChallengeTag { Misc = 'Misc', Crypto = 'Crypto', @@ -724,23 +352,12 @@ export enum ChallengeType { export interface Attachment { /** @format int32 */ id: number - - /** 附件类型 */ type: FileType - - /** Flag 对应附件 (远程文件) */ remoteUrl?: string | null - /** - * 本地文件 Id - * @format int32 - */ + /** @format int32 */ localFileId?: number | null - - /** Flag 对应文件(本地文件) */ localFile?: LocalFile | null - - /** 文件默认 Url */ url?: string | null } @@ -751,193 +368,94 @@ export enum FileType { } export interface ContainerInfoModel { - /** 容器状态 */ status?: ContainerStatus - /** - * 容器创建时间 - * @format date-time - */ + /** @format date-time */ startedAt?: string - /** - * 容器期望终止时间 - * @format date-time - */ + /** @format date-time */ expectStopAt?: string - - /** 题目入口 */ entry?: string } -/** - * 容器状态 - */ export enum ContainerStatus { Pending = 'Pending', Running = 'Running', Destroyed = 'Destroyed', } -/** - * Flag 信息(Edit) - */ export interface FlagInfoModel { - /** - * Flag Id - * @format int32 - */ + /** @format int32 */ id?: number - - /** Flag文本 */ flag?: string - - /** Flag 对应附件 */ attachment?: Attachment | null } -/** - * 基础题目信息(Edit) - */ export interface ChallengeInfoModel { - /** - * 题目Id - * @format int32 - */ + /** @format int32 */ id?: number - - /** 题目名称 */ title: string - - /** 题目标签 */ tag?: ChallengeTag - - /** 题目类型 */ type?: ChallengeType - - /** 是否启用题目 */ isEnabled?: boolean - /** - * 题目分值 - * @format int32 - */ + /** @format int32 */ score?: number - /** - * 最低分值 - * @format int32 - */ + /** @format int32 */ minScore?: number - /** - * 最初分值 - * @format int32 - */ + /** @format int32 */ originalScore?: number } -/** - * 题目更新信息(Edit) - */ export interface ChallengeUpdateModel { - /** 题目名称 */ title?: string | null - - /** 题目内容 */ content?: string | null - - /** Flag 模版,用于根据 Token 和题目、比赛信息生成 Flag */ flagTemplate?: string | null - - /** 题目标签 */ tag?: ChallengeTag | null - - /** 题目提示 */ hints?: string[] | null - - /** 是否启用题目 */ isEnabled?: boolean | null - - /** 镜像名称与标签 */ containerImage?: string | null - /** - * 运行内存限制 (MB) - * @format int32 - */ + /** @format int32 */ memoryLimit?: number | null - /** - * CPU 运行数量限制 - * @format int32 - */ + /** @format int32 */ cpuCount?: number | null - /** - * 镜像暴露端口 - * @format int32 - */ + /** @format int32 */ containerExposePort?: number | null + privilegedContainer?: boolean | null - /** - * 初始分数 - * @format int32 - */ + /** @format int32 */ originalScore?: number | null /** - * 最低分数比例 * @format double * @min 0 * @max 1 */ minScoreRate?: number | null - /** - * 难度系数 - * @format double - */ + /** @format double */ difficulty?: number | null - - /** 统一文件名 */ fileName?: string | null } -/** - * 新建附件信息(Edit) - */ export interface AttachmentCreateModel { - /** 附件类型 */ attachmentType?: FileType - - /** 文件哈希(本地文件) */ fileHash?: string | null - - /** 文件 Url(远程文件) */ remoteUrl?: string | null } -/** - * 新建 Flag 信息(Edit) - */ export interface FlagCreateModel { - /** Flag文本 */ flag: string - - /** 附件类型 */ attachmentType?: FileType - - /** 文件哈希(本地文件) */ fileHash?: string | null - - /** 文件 Url(远程文件) */ remoteUrl?: string | null } -/** - * 任务执行状态 - */ export enum TaskStatus { Success = 'Success', Fail = 'Fail', @@ -948,238 +466,113 @@ export enum TaskStatus { Pending = 'Pending', } -/** - * 比赛基本信息,不包含详细介绍与当前队伍报名状态 - */ export interface BasicGameInfoModel { /** @format int32 */ id?: number - - /** 比赛标题 */ title?: string - - /** 比赛描述 */ summary?: string - - /** 头图 */ poster?: string | null - /** - * 队员数量限制 - * @format int32 - */ + /** @format int32 */ limit?: number - /** - * 开始时间 - * @format date-time - */ + /** @format date-time */ start?: string - /** - * 结束时间 - * @format date-time - */ + /** @format date-time */ end?: string } -/** - * 比赛详细信息,包含详细介绍与当前队伍报名状态 - */ export interface GameDetailModel { /** @format int32 */ id?: number - - /** 比赛标题 */ title?: string - - /** 比赛描述 */ summary?: string - - /** 比赛详细介绍 */ content?: string - - /** 是否为隐藏比赛 */ hidden?: boolean - - /** 参赛所属单位列表 */ organizations?: string[] | null - - /** 是否需要邀请码 */ inviteCodeRequired?: boolean - - /** 比赛头图 */ poster?: string | null - /** - * 队员数量限制 - * @format int32 - */ + /** @format int32 */ limit?: number - /** - * 报名参赛队伍数量 - * @format int32 - */ + /** @format int32 */ teamCount?: number - - /** 当前报名的组织 */ organization?: string | null - - /** 参赛队伍名称 */ teamName?: string | null - - /** 比赛是否为练习模式(比赛结束够依然可以访问) */ practiceMode?: boolean - - /** 队伍参与状态 */ status?: ParticipationStatus - /** - * 开始时间 - * @format date-time - */ + /** @format date-time */ start?: string - /** - * 结束时间 - * @format date-time - */ + /** @format date-time */ end?: string } export interface GameJoinModel { - /** - * 参赛队伍 Id - * @format int32 - */ + /** @format int32 */ teamId?: number - - /** 参赛单位 */ organization?: string | null - - /** 参赛邀请码 */ inviteCode?: string | null } -/** - * 排行榜 - */ export interface ScoreboardModel { - /** - * 更新时间 - * @format date-time - */ + /** @format date-time */ updateTimeUTC?: string - - /** 参赛组织 */ organizations?: string[] | null - - /** 前十名的时间线 */ timeLine?: TopTimeLine[] - - /** 队伍信息 */ items?: ScoreboardItem[] - - /** 题目信息 */ challenges?: Record } export interface TopTimeLine { - /** - * 队伍Id - * @format int32 - */ + /** @format int32 */ id?: number - - /** 队伍名称 */ name?: string - - /** 时间线 */ items?: TimeLine[] } export interface TimeLine { - /** - * 时间 - * @format date-time - */ + /** @format date-time */ time?: string - /** - * 得分 - * @format int32 - */ + /** @format int32 */ score?: number } export interface ScoreboardItem { - /** - * 队伍Id - * @format int32 - */ + /** @format int32 */ id?: number - - /** 队伍名称 */ name?: string - - /** 参赛所属组织 */ organization?: string | null - - /** 队伍头像 */ avatar?: string | null - /** - * 分数 - * @format int32 - */ + /** @format int32 */ score?: number - /** - * 排名 - * @format int32 - */ + /** @format int32 */ rank?: number - /** - * 已解出的题目数量 - * @format int32 - */ + /** @format int32 */ solvedCount?: number - - /** 题目情况列表 */ challenges?: ChallengeItem[] } export interface ChallengeItem { - /** - * 题目 Id - * @format int32 - */ + /** @format int32 */ id?: number - /** - * 题目分值 - * @format int32 - */ + /** @format int32 */ score?: number - - /** 未解出、一血、二血、三血或者其他 */ type?: SubmissionType - - /** 解题用户名 */ userName?: string | null - /** - * 题目提交的时间,为了计算时间线 - * @format date-time - */ + /** @format date-time */ time?: string | null } -/** - * 提交类型 - */ export enum SubmissionType { Unaccepted = 'Unaccepted', FirstBlood = 'FirstBlood', @@ -1189,81 +582,39 @@ export enum SubmissionType { } export interface ChallengeInfo { - /** - * 题目Id - * @format int32 - */ + /** @format int32 */ id?: number - - /** 题目名称 */ title?: string - - /** 题目标签 */ tag?: ChallengeTag - /** - * 题目分值 - * @format int32 - */ + /** @format int32 */ score?: number - /** - * 解出队伍数量 - * @format int32 - */ + /** @format int32 */ solved?: number - - /** 题目三血 */ bloods?: (Blood | null)[] } export interface Blood { - /** - * 队伍Id - * @format int32 - */ + /** @format int32 */ id?: number - - /** 队伍名称 */ name?: string - - /** 队伍头像 */ avatar?: string | null - /** - * 获得此血的时间 - * @format date-time - */ + /** @format date-time */ submitTimeUTC?: string | null } -/** -* 比赛事件,记录但不会发往客户端。 -信息涵盖Flag提交信息、容器启动关闭信息、作弊信息、题目分数变更信息 -*/ export interface GameEvent { - /** 事件类型 */ type: EventType - - /** 事件内容 */ content: string - /** - * 发布时间 - * @format date-time - */ + /** @format date-time */ time: string - - /** 相关用户名 */ user?: string - - /** 相关队伍名 */ team?: string } -/** - * 比赛事件类型 - */ export enum EventType { Normal = 'Normal', ContainerStart = 'ContainerStart', @@ -1273,28 +624,15 @@ export enum EventType { } export interface Submission { - /** 提交的答案字符串 */ answer?: string - - /** 提交的答案状态 */ status?: AnswerResult - /** - * 答案提交的时间 - * @format date-time - */ + /** @format date-time */ time?: string - - /** 提交用户 */ user?: string - - /** 提交队伍 */ team?: string } -/** - * 判定结果 - */ export enum AnswerResult { FlagSubmitted = 'FlagSubmitted', Accepted = 'Accepted', @@ -1304,170 +642,74 @@ export enum AnswerResult { } export interface GameTeamDetailModel { - /** 积分榜信息 */ rank?: ScoreboardItem | null - - /** 队伍 Token */ teamToken: string } -/** - * 比赛参与对象,用于审核查看(Admin) - */ export interface ParticipationInfoModel { - /** - * 参与对象 Id - * @format int32 - */ + /** @format int32 */ id?: number - - /** 参与队伍 */ team?: TeamWithDetailedUserInfo - - /** 注册的成员 */ registeredMembers?: string[] - - /** 参赛所属组织 */ organization?: string | null - - /** 参与状态 */ status?: ParticipationStatus } -/** - * 比赛队伍详细信息,用于审核查看(Admin) - */ export interface TeamWithDetailedUserInfo { - /** - * 队伍 Id - * @format int32 - */ + /** @format int32 */ id?: number - - /** 队伍名称 */ name?: string | null - - /** 队伍签名 */ bio?: string | null - - /** 头像链接 */ avatar?: string | null - - /** 是否锁定 */ locked?: boolean - - /** 队长 Id */ captainId?: string - - /** 队伍成员 */ members?: ProfileUserInfoModel[] | null } -/** - * 题目详细信息 - */ export interface ChallengeDetailModel { - /** - * 题目 Id - * @format int32 - */ + /** @format int32 */ id?: number - - /** 题目名称 */ title?: string - - /** 题目内容 */ content?: string - - /** 题目标签 */ tag?: ChallengeTag - - /** 题目提示 */ hints?: string[] | null - /** - * 题目当前分值 - * @format int32 - */ + /** @format int32 */ score?: number - - /** 题目类型 */ type?: ChallengeType - - /** Flag 上下文 */ context?: ClientFlagContext } export interface ClientFlagContext { - /** - * 题目实例的关闭时间 - * @format date-time - */ + /** @format date-time */ closeTime?: string | null - - /** 题目实例的连接方式 */ instanceEntry?: string | null - - /** 附件 Url */ url?: string | null } -/** - * flag 提交 - */ export interface FlagSubmitModel { - /** - * flag 内容 - * fix: 防止前端的意外提交 (number/float/null) 可能被错误转换 - */ flag: string } -/** - * 文章信息 - */ export interface PostInfoModel { - /** 文章 Id */ id: string - - /** 文章标题 */ title: string - - /** 文章总结 */ summary: string - - /** 是否置顶 */ isPinned: boolean - - /** 文章标签 */ tags?: string[] | null - - /** 作者头像 */ autherAvatar?: string | null - - /** 作者名称 */ autherName?: string | null - /** - * 更新时间 - * @format date-time - */ + /** @format date-time */ time: string } -/** - * 队伍信息更新 - */ export interface TeamUpdateModel { - /** 队伍名称 */ name?: string | null - - /** 队伍签名 */ bio?: string | null } export interface TeamTransferModel { - /** 新队长 Id */ newCaptainId: string } @@ -1620,11 +862,10 @@ export class HttpClient { export class Api extends HttpClient { account = { /** - * @description 使用此接口注册新用户,Dev环境下不校验 GToken,邮件URL:/verify + * No description * * @tags Account * @name AccountRegister - * @summary 用户注册接口 * @request POST:/api/account/register */ accountRegister: (data: RegisterModel, params: RequestParams = {}) => @@ -1638,11 +879,10 @@ export class Api extends HttpClient @@ -1656,11 +896,10 @@ export class Api extends HttpClient @@ -1673,11 +912,10 @@ export class Api extends HttpClient @@ -1690,11 +928,10 @@ export class Api extends HttpClient @@ -1707,11 +944,10 @@ export class Api extends HttpClient @@ -1722,11 +958,10 @@ export class Api extends HttpClient @@ -1739,11 +974,10 @@ export class Api extends HttpClient @@ -1756,11 +990,10 @@ export class Api extends HttpClient @@ -1774,11 +1007,10 @@ export class Api extends HttpClient @@ -1791,11 +1023,10 @@ export class Api extends HttpClient @@ -1806,11 +1037,10 @@ export class Api extends HttpClient @@ -1820,11 +1050,10 @@ export class Api extends HttpClient extends HttpClient mutate(`/api/account/profile`, data, options), /** - * @description 使用此接口更新用户头像,需要User权限 + * No description * * @tags Account * @name AccountAvatar - * @summary 更新用户头像接口 * @request PUT:/api/account/avatar */ accountAvatar: (data: { file?: File }, params: RequestParams = {}) => @@ -1852,11 +1080,10 @@ export class Api extends HttpClient @@ -1867,22 +1094,20 @@ export class Api extends HttpClient useSWR(doFetch ? `/api/admin/config` : null, options), /** - * @description 使用此接口获取全局设置,需要Admin权限 + * No description * * @tags Admin * @name AdminGetConfigs - * @summary 获取配置 * @request GET:/api/admin/config */ mutateAdminGetConfigs: ( @@ -1891,11 +1116,10 @@ export class Api extends HttpClient mutate(`/api/admin/config`, data, options), /** - * @description 使用此接口更改全局设置,需要Admin权限 + * No description * * @tags Admin * @name AdminUpdateConfigs - * @summary 更改配置 * @request PUT:/api/admin/config */ adminUpdateConfigs: (data: ConfigEditModel, params: RequestParams = {}) => @@ -1908,11 +1132,10 @@ export class Api extends HttpClient @@ -1924,11 +1147,10 @@ export class Api extends HttpClient extends HttpClient extends HttpClient mutate([`/api/admin/users`, query], data, options), /** - * @description 使用此接口搜索用户,需要Admin权限 + * No description * * @tags Admin * @name AdminSearchUsers - * @summary 搜索用户 * @request POST:/api/admin/users/search */ adminSearchUsers: (query?: { hint?: string }, params: RequestParams = {}) => @@ -1973,11 +1193,10 @@ export class Api extends HttpClient @@ -1989,11 +1208,10 @@ export class Api extends HttpClient extends HttpClient extends HttpClient mutate([`/api/admin/teams`, query], data, options), /** - * @description 使用此接口搜索队伍,需要Admin权限 + * No description * * @tags Admin * @name AdminSearchTeams - * @summary 搜索队伍 * @request POST:/api/admin/teams/search */ adminSearchTeams: (query?: { hint?: string }, params: RequestParams = {}) => @@ -2038,11 +1254,10 @@ export class Api extends HttpClient @@ -2055,11 +1270,10 @@ export class Api extends HttpClient @@ -2071,11 +1285,10 @@ export class Api extends HttpClient @@ -2086,11 +1299,10 @@ export class Api extends HttpClient @@ -2100,11 +1312,10 @@ export class Api extends HttpClient extends HttpClient mutate(`/api/admin/users/${userid}`, data, options), /** - * @description 使用此接口重置用户密码,需要Admin权限 + * No description * * @tags Admin * @name AdminResetPassword - * @summary 重置用户密码 * @request DELETE:/api/admin/users/{userid}/password */ adminResetPassword: (userid: string, params: RequestParams = {}) => @@ -2130,11 +1340,10 @@ export class Api extends HttpClient @@ -2146,11 +1355,10 @@ export class Api extends HttpClient extends HttpClient extends HttpClient extends HttpClient mutate([`/api/admin/logs`, query], data, options), /** - * @description 使用此接口更新队伍参与状态,审核申请,需要Admin权限 + * No description * * @tags Admin * @name AdminParticipation - * @summary 更新参与状态 * @request PUT:/api/admin/participation/{id}/{status} */ adminParticipation: (id: number, status: ParticipationStatus, params: RequestParams = {}) => @@ -2212,11 +1417,10 @@ export class Api extends HttpClient @@ -2228,11 +1432,10 @@ export class Api extends HttpClient extends HttpClient(doFetch ? [`/api/admin/files`, query] : null, options), /** - * @description 使用此接口获取全部日志,需要Admin权限 + * No description * * @tags Admin * @name AdminFiles - * @summary 获取全部文件 * @request GET:/api/admin/files */ mutateAdminFiles: ( @@ -2258,11 +1460,10 @@ export class Api extends HttpClient @@ -2272,11 +1473,10 @@ export class Api extends HttpClient extends HttpClient useSWR(doFetch ? `/assets/${hash}/${filename}` : null, options), /** - * @description 根据哈希获取文件,不匹配文件名 + * No description * * @tags Assets * @name AssetsGetFile - * @summary 获取文件接口 * @request GET:/assets/{hash}/{filename} */ mutateAssetsGetFile: ( @@ -2302,11 +1501,10 @@ export class Api extends HttpClient mutate(`/assets/${hash}/${filename}`, data, options), /** - * @description 上传一个或多个文件 + * No description * * @tags Assets * @name AssetsUpload - * @summary 上传文件接口 * @request POST:/api/assets */ assetsUpload: ( @@ -2325,11 +1523,10 @@ export class Api extends HttpClient @@ -2341,11 +1538,10 @@ export class Api extends HttpClient @@ -2359,11 +1555,10 @@ export class Api extends HttpClient @@ -2377,11 +1572,10 @@ export class Api extends HttpClient @@ -2392,11 +1586,10 @@ export class Api extends HttpClient @@ -2410,11 +1603,10 @@ export class Api extends HttpClient @@ -2426,11 +1618,10 @@ export class Api extends HttpClient extends HttpClient extends HttpClient mutate([`/api/edit/games`, query], data, options), /** - * @description 获取比赛,需要管理员权限 + * No description * * @tags Edit * @name EditGetGame - * @summary 获取比赛 * @request GET:/api/edit/games/{id} */ editGetGame: (id: number, params: RequestParams = {}) => @@ -2473,22 +1662,20 @@ export class Api extends HttpClient useSWR(doFetch ? `/api/edit/games/${id}` : null, options), /** - * @description 获取比赛,需要管理员权限 + * No description * * @tags Edit * @name EditGetGame - * @summary 获取比赛 * @request GET:/api/edit/games/{id} */ mutateEditGetGame: ( @@ -2498,11 +1685,10 @@ export class Api extends HttpClient mutate(`/api/edit/games/${id}`, data, options), /** - * @description 修改比赛,需要管理员权限 + * No description * * @tags Edit * @name EditUpdateGame - * @summary 修改比赛 * @request PUT:/api/edit/games/{id} */ editUpdateGame: (id: number, data: GameInfoModel, params: RequestParams = {}) => @@ -2516,11 +1702,10 @@ export class Api extends HttpClient @@ -2532,11 +1717,10 @@ export class Api extends HttpClient @@ -2550,11 +1734,10 @@ export class Api extends HttpClient @@ -2568,11 +1751,10 @@ export class Api extends HttpClient @@ -2583,11 +1765,10 @@ export class Api extends HttpClient @@ -2597,11 +1778,10 @@ export class Api extends HttpClient extends HttpClient mutate(`/api/edit/games/${id}/posts`, data, options), /** - * @description 更新比赛文章,需要管理员权限 + * No description * * @tags Edit * @name EditUpdateGameNotice - * @summary 更新比赛文章 * @request PUT:/api/edit/games/{id}/posts/{noticeId} */ editUpdateGameNotice: ( @@ -2634,11 +1813,10 @@ export class Api extends HttpClient @@ -2649,11 +1827,10 @@ export class Api extends HttpClient @@ -2667,11 +1844,10 @@ export class Api extends HttpClient @@ -2682,11 +1858,10 @@ export class Api extends HttpClient @@ -2696,11 +1871,10 @@ export class Api extends HttpClient extends HttpClient mutate(`/api/edit/games/${id}/challenges`, data, options), /** - * @description 获取比赛题目,需要管理员权限 + * No description * * @tags Edit * @name EditGetGameChallenge - * @summary 获取比赛题目 * @request GET:/api/edit/games/{id}/challenges/{cId} */ editGetGameChallenge: (id: number, cId: number, params: RequestParams = {}) => @@ -2725,11 +1898,10 @@ export class Api extends HttpClient extends HttpClient extends HttpClient mutate(`/api/edit/games/${id}/challenges/${cId}`, data, options), /** - * @description 修改比赛题目,需要管理员权限 + * No description * * @tags Edit * @name EditUpdateGameChallenge - * @summary 修改比赛题目信息,Flags 不受更改,使用 Flag 相关 API 修改 * @request PUT:/api/edit/games/{id}/challenges/{cId} */ editUpdateGameChallenge: ( @@ -2782,11 +1952,10 @@ export class Api extends HttpClient @@ -2797,11 +1966,10 @@ export class Api extends HttpClient @@ -2813,11 +1981,10 @@ export class Api extends HttpClient @@ -2828,11 +1995,10 @@ export class Api extends HttpClient extends HttpClient @@ -2868,11 +2033,10 @@ export class Api extends HttpClient @@ -2885,11 +2049,10 @@ export class Api extends HttpClient @@ -2900,22 +2063,20 @@ export class Api extends HttpClient useSWR(doFetch ? `/api/game` : null, options), /** - * @description 获取最近十个比赛 + * No description * * @tags Game * @name GameGamesAll - * @summary 获取最新的比赛 * @request GET:/api/game */ mutateGameGamesAll: ( @@ -2924,11 +2085,10 @@ export class Api extends HttpClient mutate(`/api/game`, data, options), /** - * @description 获取比赛的详细信息 + * No description * * @tags Game * @name GameGames - * @summary 获取比赛详细信息 * @request GET:/api/game/{id} */ gameGames: (id: number, params: RequestParams = {}) => @@ -2939,22 +2099,20 @@ export class Api extends HttpClient useSWR(doFetch ? `/api/game/${id}` : null, options), /** - * @description 获取比赛的详细信息 + * No description * * @tags Game * @name GameGames - * @summary 获取比赛详细信息 * @request GET:/api/game/{id} */ mutateGameGames: ( @@ -2964,11 +2122,10 @@ export class Api extends HttpClient mutate(`/api/game/${id}`, data, options), /** - * @description 加入一场比赛,需要User权限 + * No description * * @tags Game * @name GameJoinGame - * @summary 加入一个比赛 * @request POST:/api/game/{id} */ gameJoinGame: (id: number, data: GameJoinModel, params: RequestParams = {}) => @@ -2981,11 +2138,10 @@ export class Api extends HttpClient @@ -2996,11 +2152,10 @@ export class Api extends HttpClient @@ -3011,11 +2166,10 @@ export class Api extends HttpClient @@ -3025,11 +2179,10 @@ export class Api extends HttpClient extends HttpClient mutate(`/api/game/${id}/scoreboard`, data, options), /** - * @description 获取比赛通知数据 + * No description * * @tags Game * @name GameNotices - * @summary 获取比赛通知 * @request GET:/api/game/{id}/notices */ gameNotices: ( @@ -3059,11 +2211,10 @@ export class Api extends HttpClient extends HttpClient extends HttpClient mutate([`/api/game/${id}/notices`, query], data, options), /** - * @description 获取比赛事件数据,需要Monitor权限 + * No description * * @tags Game * @name GameEvents - * @summary 获取比赛事件 * @request GET:/api/game/{id}/events */ gameEvents: ( @@ -3113,11 +2262,10 @@ export class Api extends HttpClient extends HttpClient extends HttpClient mutate([`/api/game/${id}/events`, query], data, options), /** - * @description 获取比赛提交数据,需要Monitor权限 + * No description * * @tags Game * @name GameSubmissions - * @summary 获取比赛提交 * @request GET:/api/game/{id}/submissions */ gameSubmissions: ( @@ -3167,11 +2313,10 @@ export class Api extends HttpClient extends HttpClient extends HttpClient mutate([`/api/game/${id}/submissions`, query], data, options), /** - * @description 获取比赛的全部题目,需要User权限,需要当前激活队伍已经报名 + * No description * * @tags Game * @name GameChallenges - * @summary 获取全部比赛题目信息 * @request GET:/api/game/{id}/challenges */ gameChallenges: (id: number, params: RequestParams = {}) => @@ -3216,11 +2359,10 @@ export class Api extends HttpClient @@ -3230,11 +2372,10 @@ export class Api extends HttpClient extends HttpClient mutate>(`/api/game/${id}/challenges`, data, options), /** - * @description 获取当前队伍的比赛信息,需要User权限,需要当前激活队伍已经报名 + * No description * * @tags Game * @name GameMyTeam - * @summary 获取当前队伍比赛信息 * @request GET:/api/game/{id}/myteam */ gameMyTeam: (id: number, params: RequestParams = {}) => @@ -3259,11 +2399,10 @@ export class Api extends HttpClient @@ -3273,11 +2412,10 @@ export class Api extends HttpClient extends HttpClient mutate(`/api/game/${id}/myteam`, data, options), /** - * @description 获取比赛的全部题目参与信息,需要Admin权限 + * No description * * @tags Game * @name GameParticipations - * @summary 获取全部比赛参与信息 * @request GET:/api/game/{id}/participations */ gameParticipations: (id: number, params: RequestParams = {}) => @@ -3302,11 +2439,10 @@ export class Api extends HttpClient @@ -3316,11 +2452,10 @@ export class Api extends HttpClient extends HttpClient mutate(`/api/game/${id}/participations`, data, options), /** - * @description 下载比赛积分榜,需要Monitor权限 + * No description * * @tags Game * @name GameScoreboardSheet - * @summary 下载比赛积分榜 * @request GET:/api/game/{id}/scoreboardsheet */ gameScoreboardSheet: (id: number, params: RequestParams = {}) => @@ -3344,22 +2478,20 @@ export class Api extends HttpClient useSWR(doFetch ? `/api/game/${id}/scoreboardsheet` : null, options), /** - * @description 下载比赛积分榜,需要Monitor权限 + * No description * * @tags Game * @name GameScoreboardSheet - * @summary 下载比赛积分榜 * @request GET:/api/game/{id}/scoreboardsheet */ mutateGameScoreboardSheet: ( @@ -3369,11 +2501,10 @@ export class Api extends HttpClient mutate(`/api/game/${id}/scoreboardsheet`, data, options), /** - * @description 获取比赛题目信息,需要User权限,需要当前激活队伍已经报名 + * No description * * @tags Game * @name GameGetChallenge - * @summary 获取比赛题目信息 * @request GET:/api/game/{id}/challenges/{challengeId} */ gameGetChallenge: (id: number, challengeId: number, params: RequestParams = {}) => @@ -3384,11 +2515,10 @@ export class Api extends HttpClient extends HttpClient extends HttpClient mutate(`/api/game/${id}/challenges/${challengeId}`, data, options), /** - * @description 提交 flag,需要User权限,需要当前激活队伍已经报名 + * No description * * @tags Game * @name GameSubmit - * @summary 提交 flag * @request POST:/api/game/{id}/challenges/{challengeId} */ gameSubmit: ( @@ -3441,11 +2569,10 @@ export class Api extends HttpClient @@ -3456,11 +2583,10 @@ export class Api extends HttpClient extends HttpClient extends HttpClient @@ -3513,11 +2637,10 @@ export class Api extends HttpClient @@ -3528,11 +2651,10 @@ export class Api extends HttpClient @@ -3545,11 +2667,10 @@ export class Api extends HttpClient @@ -3560,22 +2681,20 @@ export class Api extends HttpClient useSWR(doFetch ? `/api/posts/latest` : null, options), /** - * @description 获取最新文章 + * No description * * @tags Info * @name InfoGetLatestPosts - * @summary 获取最新文章 * @request GET:/api/posts/latest */ mutateInfoGetLatestPosts: ( @@ -3584,11 +2703,10 @@ export class Api extends HttpClient mutate(`/api/posts/latest`, data, options), /** - * @description 获取全部文章 + * No description * * @tags Info * @name InfoGetPosts - * @summary 获取全部文章 * @request GET:/api/posts */ infoGetPosts: (params: RequestParams = {}) => @@ -3599,22 +2717,20 @@ export class Api extends HttpClient useSWR(doFetch ? `/api/posts` : null, options), /** - * @description 获取全部文章 + * No description * * @tags Info * @name InfoGetPosts - * @summary 获取全部文章 * @request GET:/api/posts */ mutateInfoGetPosts: ( @@ -3623,11 +2739,10 @@ export class Api extends HttpClient mutate(`/api/posts`, data, options), /** - * @description 获取文章详情 + * No description * * @tags Info * @name InfoGetPost - * @summary 获取文章详情 * @request GET:/api/posts/{id} */ infoGetPost: (id: string, params: RequestParams = {}) => @@ -3638,22 +2753,20 @@ export class Api extends HttpClient useSWR(doFetch ? `/api/posts/${id}` : null, options), /** - * @description 获取文章详情 + * No description * * @tags Info * @name InfoGetPost - * @summary 获取文章详情 * @request GET:/api/posts/{id} */ mutateInfoGetPost: ( @@ -3663,11 +2776,10 @@ export class Api extends HttpClient mutate(`/api/posts/${id}`, data, options), /** - * @description 获取全局设置 + * No description * * @tags Info * @name InfoGetGlobalConfig - * @summary 获取全局设置 * @request GET:/api/config */ infoGetGlobalConfig: (params: RequestParams = {}) => @@ -3678,22 +2790,20 @@ export class Api extends HttpClient useSWR(doFetch ? `/api/config` : null, options), /** - * @description 获取全局设置 + * No description * * @tags Info * @name InfoGetGlobalConfig - * @summary 获取全局设置 * @request GET:/api/config */ mutateInfoGetGlobalConfig: ( @@ -3702,11 +2812,10 @@ export class Api extends HttpClient mutate(`/api/config`, data, options), /** - * @description 获取 Recaptcha SiteKey + * No description * * @tags Info * @name InfoGetRecaptchaSiteKey - * @summary 获取 Recaptcha SiteKey * @request GET:/api/sitekey */ infoGetRecaptchaSiteKey: (params: RequestParams = {}) => @@ -3717,22 +2826,20 @@ export class Api extends HttpClient useSWR(doFetch ? `/api/sitekey` : null, options), /** - * @description 获取 Recaptcha SiteKey + * No description * * @tags Info * @name InfoGetRecaptchaSiteKey - * @summary 获取 Recaptcha SiteKey * @request GET:/api/sitekey */ mutateInfoGetRecaptchaSiteKey: (data?: string | Promise, options?: MutatorOptions) => @@ -3740,11 +2847,10 @@ export class Api extends HttpClient @@ -3755,22 +2861,20 @@ export class Api extends HttpClient useSWR(doFetch ? `/api/team/${id}` : null, options), /** - * @description 根据 id 获取一个队伍的基本信息 + * No description * * @tags Team * @name TeamGetBasicInfo - * @summary 获取队伍信息 * @request GET:/api/team/{id} */ mutateTeamGetBasicInfo: ( @@ -3780,11 +2884,10 @@ export class Api extends HttpClient mutate(`/api/team/${id}`, data, options), /** - * @description 队伍信息更改接口,需要为队伍创建者 + * No description * * @tags Team * @name TeamUpdateTeam - * @summary 更改队伍信息 * @request PUT:/api/team/{id} */ teamUpdateTeam: (id: number, data: TeamUpdateModel, params: RequestParams = {}) => @@ -3798,11 +2901,10 @@ export class Api extends HttpClient @@ -3814,11 +2916,10 @@ export class Api extends HttpClient @@ -3829,22 +2930,20 @@ export class Api extends HttpClient useSWR(doFetch ? `/api/team` : null, options), /** - * @description 根据用户获取一个队伍的基本信息 + * No description * * @tags Team * @name TeamGetTeamsInfo - * @summary 获取当前自己队伍信息 * @request GET:/api/team */ mutateTeamGetTeamsInfo: ( @@ -3853,11 +2952,10 @@ export class Api extends HttpClient mutate(`/api/team`, data, options), /** - * @description 用户创建队伍接口,每个用户只能创建一个队伍 + * No description * * @tags Team * @name TeamCreateTeam - * @summary 创建队伍 * @request POST:/api/team */ teamCreateTeam: (data: TeamUpdateModel, params: RequestParams = {}) => @@ -3871,11 +2969,10 @@ export class Api extends HttpClient @@ -3889,11 +2986,10 @@ export class Api extends HttpClient @@ -3904,33 +3000,30 @@ export class Api extends HttpClient useSWR(doFetch ? `/api/team/${id}/invite` : null, options), /** - * @description 获取队伍邀请信息,需要为队伍创建者 + * No description * * @tags Team * @name TeamInviteCode - * @summary 获取邀请信息 * @request GET:/api/team/{id}/invite */ mutateTeamInviteCode: (id: number, data?: string | Promise, options?: MutatorOptions) => mutate(`/api/team/${id}/invite`, data, options), /** - * @description 更新邀请 Token 的接口,需要为队伍创建者 + * No description * * @tags Team * @name TeamUpdateInviteToken - * @summary 更新邀请 Token * @request PUT:/api/team/{id}/invite */ teamUpdateInviteToken: (id: number, params: RequestParams = {}) => @@ -3942,11 +3035,10 @@ export class Api extends HttpClient @@ -3958,11 +3050,10 @@ export class Api extends HttpClient @@ -3975,11 +3066,10 @@ export class Api extends HttpClient @@ -3990,11 +3080,10 @@ export class Api extends HttpClient diff --git a/GZCTF/ClientApp/src/App.tsx b/GZCTF/ClientApp/src/App.tsx index 11604c79c..e6817555e 100644 --- a/GZCTF/ClientApp/src/App.tsx +++ b/GZCTF/ClientApp/src/App.tsx @@ -14,7 +14,7 @@ import { useLocalStorage } from '@mantine/hooks' import { ModalsProvider } from '@mantine/modals' import { NotificationsProvider } from '@mantine/notifications' import { ThemeOverride } from '@Utils/ThemeOverride' -import { fetcher } from "@Api"; +import { fetcher } from '@Api' export const App: FC = () => { const [colorScheme, setColorScheme] = useLocalStorage({ diff --git a/GZCTF/ClientApp/src/components/admin/FlagEditPanel.tsx b/GZCTF/ClientApp/src/components/admin/FlagEditPanel.tsx index 4963670ae..5e6181789 100644 --- a/GZCTF/ClientApp/src/components/admin/FlagEditPanel.tsx +++ b/GZCTF/ClientApp/src/components/admin/FlagEditPanel.tsx @@ -9,11 +9,11 @@ import { SimpleGrid, Input, } from '@mantine/core' +import { useClipboard } from '@mantine/hooks' +import { showNotification } from '@mantine/notifications' import { mdiCheck, mdiDeleteOutline } from '@mdi/js' import { Icon } from '@mdi/react' import { Attachment, FlagInfoModel } from '@Api' -import { useClipboard } from '@mantine/hooks' -import { showNotification } from '@mantine/notifications' interface FlagCardProps { flag: FlagInfoModel @@ -50,7 +50,7 @@ const FlagCard: FC = ({ flag, onDelete, unifiedAttachment }) => { }, wrapper: { width: '100%', - } + }, }} /> diff --git a/GZCTF/ClientApp/src/pages/account/Confirm.tsx b/GZCTF/ClientApp/src/pages/account/Confirm.tsx index 0a0b6c439..d60fc2b45 100644 --- a/GZCTF/ClientApp/src/pages/account/Confirm.tsx +++ b/GZCTF/ClientApp/src/pages/account/Confirm.tsx @@ -14,7 +14,7 @@ const Confirm: FC = () => { const sp = new URLSearchParams(location.search) const token = sp.get('token') const email = sp.get('email') - const runOnce = useRef(false); + const runOnce = useRef(false) usePageTitle('邮箱验证') diff --git a/GZCTF/ClientApp/src/pages/account/Verify.tsx b/GZCTF/ClientApp/src/pages/account/Verify.tsx index b1be1b227..613d357a8 100644 --- a/GZCTF/ClientApp/src/pages/account/Verify.tsx +++ b/GZCTF/ClientApp/src/pages/account/Verify.tsx @@ -14,12 +14,12 @@ const Verify: FC = () => { const token = sp.get('token') const email = sp.get('email') const navigate = useNavigate() - const runOnce = useRef(false); + const runOnce = useRef(false) usePageTitle('账户验证') useEffect(() => { - if (token && email && !runOnce.current ) { + if (token && email && !runOnce.current) { runOnce.current = true api.account .accountVerify({ token, email }) @@ -40,7 +40,8 @@ const Verify: FC = () => { icon: , disallowClose: true, }) - }).finally(() => { + }) + .finally(() => { navigate('/account/login') }) } diff --git a/GZCTF/ClientApp/src/pages/admin/games/[id]/challenges/[chalId]/Index.tsx b/GZCTF/ClientApp/src/pages/admin/games/[id]/challenges/[chalId]/Index.tsx index 681495712..a86d0ed2a 100644 --- a/GZCTF/ClientApp/src/pages/admin/games/[id]/challenges/[chalId]/Index.tsx +++ b/GZCTF/ClientApp/src/pages/admin/games/[id]/challenges/[chalId]/Index.tsx @@ -14,6 +14,7 @@ import { TextInput, Grid, Code, + Switch, } from '@mantine/core' import { useClipboard } from '@mantine/hooks' import { useModals } from '@mantine/modals' @@ -28,6 +29,7 @@ import { import { Icon } from '@mdi/react' import HintList from '@Components/HintList' import ScoreFunc from '@Components/admin/ScoreFunc' +import { SwitchLabel } from '@Components/admin/SwitchLabel' import WithGameEditTab from '@Components/admin/WithGameEditTab' import { showErrorNotification } from '@Utils/ApiErrorHandler' import { @@ -139,7 +141,10 @@ const GameChallengeEdit: FC = () => { if (!challenge?.testContainer) { if ( challenge.containerImage !== challengeInfo.containerImage || - challenge.containerExposePort !== challengeInfo.containerExposePort + challenge.containerExposePort !== challengeInfo.containerExposePort || + challenge.memoryLimit !== challengeInfo.memoryLimit || + challenge.cpuCount !== challengeInfo.cpuCount || + challenge.privilegedContainer !== challengeInfo.privilegedContainer ) onUpdate(challengeInfo)?.then(onCreateTestContainer) else onCreateTestContainer() @@ -353,8 +358,8 @@ const GameChallengeEdit: FC = () => { /> )} {(type === ChallengeType.StaticContainer || type === ChallengeType.DynamicContainer) && ( - - + + { } /> - + {challenge?.testContainer ? ( ({ backgroundColor: 'transparent', fontSize: theme.fontSizes.sm, - fontWeight: 'bold' + fontWeight: 'bold', })} onClick={() => clipBoard.copy(challenge?.testContainer?.entry ?? '')} > @@ -395,7 +400,7 @@ const GameChallengeEdit: FC = () => { )} - + { onChange={(e) => setChallengeInfo({ ...challengeInfo, containerExposePort: e })} /> - + { onChange={(e) => setChallengeInfo({ ...challengeInfo, cpuCount: e })} /> - + { onChange={(e) => setChallengeInfo({ ...challengeInfo, memoryLimit: e })} /> + + + setChallengeInfo({ ...challengeInfo, privilegedContainer: e.target.checked }) + } + /> + )} diff --git a/GZCTF/ClientApp/src/pages/games/[id]/Index.tsx b/GZCTF/ClientApp/src/pages/games/[id]/Index.tsx index 6dab7b622..f4abd9158 100644 --- a/GZCTF/ClientApp/src/pages/games/[id]/Index.tsx +++ b/GZCTF/ClientApp/src/pages/games/[id]/Index.tsx @@ -307,7 +307,7 @@ const GameDetail: FC = () => { 你已经以队伍 "{game?.teamName}" 成员身份成功报名,请耐心等待比赛开始。 )} - + public string? Flag { get; set; } = string.Empty; + /// + /// 是否为特权容器 + /// + public bool PrivilegedContainer { get; set; } = false; + /// /// 内存限制(MB) /// diff --git a/GZCTF/Repositories/InstanceRepository.cs b/GZCTF/Repositories/InstanceRepository.cs index 51f8623bd..548b02da5 100644 --- a/GZCTF/Repositories/InstanceRepository.cs +++ b/GZCTF/Repositories/InstanceRepository.cs @@ -119,12 +119,13 @@ public async Task> CreateContainer(Instance instance, Team await context.Entry(instance).Reference(e => e.FlagContext).LoadAsync(token); var container = await service.CreateContainer(new ContainerConfig() { - CPUCount = instance.Challenge.CPUCount ?? 1, TeamId = team.Id.ToString(), UserId = userId, Flag = instance.FlagContext?.Flag, // static challenge has no specific flag Image = instance.Challenge.ContainerImage, + CPUCount = instance.Challenge.CPUCount ?? 1, MemoryLimit = instance.Challenge.MemoryLimit ?? 64, + PrivilegedContainer = instance.Challenge.PrivilegedContainer ?? false, ExposedPort = instance.Challenge.ContainerExposePort ?? throw new ArgumentException("创建容器时遇到无效的端口"), }, token); diff --git a/GZCTF/Services/DockerService.cs b/GZCTF/Services/DockerService.cs index b85acf495..d577ebc6a 100644 --- a/GZCTF/Services/DockerService.cs +++ b/GZCTF/Services/DockerService.cs @@ -94,7 +94,8 @@ private CreateContainerParameters GetCreateContainerParameters(ContainerConfig c { PublishAllPorts = true, Memory = config.MemoryLimit * 1024 * 1024, - CPUCount = 1 + CPUCount = 1, + Privileged = config.PrivilegedContainer } }; @@ -128,7 +129,7 @@ private ServiceCreateParameters GetServiceCreateParameters(ContainerConfig confi { MemoryBytes = config.MemoryLimit * 1024 * 1024, NanoCPUs = config.CPUCount * 10_0000_0000, - } + }, }, } } diff --git a/GZCTF/Services/K8sService.cs b/GZCTF/Services/K8sService.cs index 20663f28f..25535cc63 100644 --- a/GZCTF/Services/K8sService.cs +++ b/GZCTF/Services/K8sService.cs @@ -112,6 +112,10 @@ public K8sService(IOptions _registry, ILogger logger Name = name, Image = config.Image, ImagePullPolicy = "Always", + SecurityContext = new() + { + Privileged = config.PrivilegedContainer + }, Env = config.Flag is null ? new List() : new[] { new V1EnvVar("GZCTF_FLAG", config.Flag)