From be74411080ad64b1b9ca73109ec963714fa9fdd7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Mar 2026 18:57:00 +0000 Subject: [PATCH 1/4] Initial plan From 82086d2b283d23cf30e52934862b8ce4dc45c15d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Mar 2026 19:02:42 +0000 Subject: [PATCH 2/4] feat: clarify cheating detection incident details Co-authored-by: hez2010 <14960345+hez2010@users.noreply.github.com> --- .../pages/games/[id]/monitor/CheatInfo.tsx | 177 ++++++++++-------- 1 file changed, 95 insertions(+), 82 deletions(-) diff --git a/src/GZCTF/ClientApp/src/pages/games/[id]/monitor/CheatInfo.tsx b/src/GZCTF/ClientApp/src/pages/games/[id]/monitor/CheatInfo.tsx index 1dc0660dc..a58fae41f 100644 --- a/src/GZCTF/ClientApp/src/pages/games/[id]/monitor/CheatInfo.tsx +++ b/src/GZCTF/ClientApp/src/pages/games/[id]/monitor/CheatInfo.tsx @@ -5,12 +5,11 @@ import { Box, Center, Group, - Input, Paper, ScrollArea, + SimpleGrid, Stack, Switch, - Table, Text, Title, useMantineTheme, @@ -30,7 +29,6 @@ import { SwitchLabel } from '@Components/admin/SwitchLabel' import { useLanguage } from '@Utils/I18n' import { showErrorMsg } from '@Utils/Shared' import { useParticipationStatusMap } from '@Utils/Shared' -import { useDisplayInputStyles } from '@Utils/ThemeOverride' import { OnceSWRConfig } from '@Hooks/useConfig' import { useUserRole } from '@Hooks/useUser' import api, { CheatInfoModel, ParticipationEditModel, ParticipationStatus, Role } from '@Api' @@ -64,7 +62,8 @@ interface CheatSubmissionInfo { answer?: string user?: string challenge?: string - relatedTeam?: string + submitTeam?: string + ownedTeam?: string cheatType: CheatType } @@ -114,12 +113,13 @@ const ToCheatTeamInfo = (cheatInfo: CheatInfoModel[]) => { } const cheatSubmissionInfo: CheatSubmissionInfo = { - time: time, + time, answer: submission.answer, user: submission.user, challenge: submission.challenge, + submitTeam: submitTeam.team?.name, + ownedTeam: ownedTeam.team?.name, cheatType: CheatType.Owned, - relatedTeam: submitTeam.team?.name, } ownedTeamInfo.submissionInfo.add(cheatSubmissionInfo) @@ -131,7 +131,6 @@ const ToCheatTeamInfo = (cheatInfo: CheatInfoModel[]) => { const cheatSubmissionSourceInfo: CheatSubmissionInfo = { ...cheatSubmissionInfo, cheatType: CheatType.Submit, - relatedTeam: ownedTeam.team?.name, } submitTeamInfo.submissionInfo.add(cheatSubmissionSourceInfo) @@ -139,40 +138,97 @@ const ToCheatTeamInfo = (cheatInfo: CheatInfoModel[]) => { return cheatTeamInfo } -interface CheatSubmissionInfoProps { +const ToCheatSubmissionInfo = (info: CheatInfoModel, cheatType: CheatType = CheatType.Submit): CheatSubmissionInfo => ({ + time: info.submission?.time ? dayjs(info.submission.time) : undefined, + answer: info.submission?.answer, + user: info.submission?.user, + challenge: info.submission?.challenge, + submitTeam: info.submitTeam?.team?.name, + ownedTeam: info.ownedTeam?.team?.name, + cheatType, +}) + +const GetCheatSubmissionKey = (submissionInfo: CheatSubmissionInfo) => + [ + submissionInfo.cheatType, + submissionInfo.time?.unix() ?? 'time', + submissionInfo.submitTeam ?? 'submit-team', + submissionInfo.ownedTeam ?? 'owned-team', + submissionInfo.user ?? 'user', + submissionInfo.challenge ?? 'challenge', + submissionInfo.answer ?? 'answer', + ].join(':') + +const FormatCheatTime = (time: dayjs.Dayjs | undefined, locale: string, format: string) => + time ? dayjs(time).locale(locale).format(format) : '-' + +interface CheatDetailItemProps { + label: string + value?: string +} + +const CheatDetailItem: FC = ({ label, value }) => ( + + + {label} + + + {value ?? '-'} + + +) + +interface CheatSubmissionCardProps { submissionInfo: CheatSubmissionInfo } -const CheatSubmissionInfo: FC = (props) => { +const CheatSubmissionCard: FC = (props) => { const { submissionInfo } = props const theme = useMantineTheme() const type = CheatTypeMap.get(submissionInfo.cheatType)! - const { classes } = useDisplayInputStyles({ ff: 'monospace' }) + const { t } = useTranslation() const { locale } = useLanguage() return ( - - - - - - {dayjs(submissionInfo.time).locale(locale).format('SL HH:mm:ss')} - - - {submissionInfo.relatedTeam} + + + + + + + + {submissionInfo.cheatType === CheatType.Submit + ? t('game.label.cheat_info.submit_team') + : t('game.label.cheat_info.owned_team')} + + + {FormatCheatTime(submissionInfo.time, locale, 'SL HH:mm:ss')} + + + + {`${submissionInfo.user ?? t('common.label.user')} @ ${submissionInfo.submitTeam ?? t('common.label.team')} → ${submissionInfo.ownedTeam ?? t('common.label.team')}`} + + + + {submissionInfo.challenge ?? t('common.label.challenge')} - - {submissionInfo.user} - - - - - {submissionInfo.challenge} - - + + + + + + + + + {t('common.label.flag')} + + + {submissionInfo.answer ?? '-'} + + - + ) } @@ -205,6 +261,9 @@ const CheatInfoItem: FC = (props) => { {!cheatTeamInfo.name ? t('admin.placeholder.games.participation.team') : cheatTeamInfo.name} + + {cheatTeamInfo.submissionInfo.size} + {cheatTeamInfo?.division && ( {cheatTeamInfo.division} @@ -212,7 +271,7 @@ const CheatInfoItem: FC = (props) => { )} - {dayjs(cheatTeamInfo.lastSubmitTime).locale(locale).format('SL LTS')} + {FormatCheatTime(cheatTeamInfo.lastSubmitTime, locale, 'SL LTS')} @@ -242,7 +301,7 @@ const CheatInfoItem: FC = (props) => { {[...cheatTeamInfo.submissionInfo] .sort((a, b) => (b.time?.unix() ?? 0) - (a.time?.unix() ?? 0)) .map((submissionInfo) => ( - + ))} @@ -297,63 +356,17 @@ interface CheatInfoTableViewProps { } const CheatInfoTableView: FC = (props) => { - const { classes: inputClasses } = useDisplayInputStyles({ ff: 'monospace' }) - const { t } = useTranslation() - const { locale } = useLanguage() - const rows = props.cheatInfo .sort((a, b) => (dayjs(b.submission?.time).unix() ?? 0) - (dayjs(a.submission?.time).unix() ?? 0)) - .map((item, i) => ( - - - - {dayjs(item.submission?.time).locale(locale).format('SL HH:mm:ss')} - - - - - {item.ownedTeam?.team?.name ?? 'Team'} - - - - - {`>>>`} - - - - - {item.submitTeam?.team?.name ?? 'Team'} - - - - - {item.submission?.user ?? 'User'} - - - {item.submission?.challenge ?? 'Challenge'} - - - - - )) + .map((item, i) => { + const submissionInfo = ToCheatSubmissionInfo(item) + return + }) return ( - - - - {t('common.label.time')} - {t('game.label.cheat_info.owned_team')} - - {t('game.label.cheat_info.submit_team')} - {t('game.label.cheat_info.submit_user')} - {t('common.label.challenge')} - {t('common.label.flag')} - - - {rows} -
+ {rows}
) From 8582514736eeea55498621371816e3cd345130c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Mar 2026 19:05:10 +0000 Subject: [PATCH 3/4] refactor: polish cheating incident card rendering Co-authored-by: hez2010 <14960345+hez2010@users.noreply.github.com> --- .../pages/games/[id]/monitor/CheatInfo.tsx | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/GZCTF/ClientApp/src/pages/games/[id]/monitor/CheatInfo.tsx b/src/GZCTF/ClientApp/src/pages/games/[id]/monitor/CheatInfo.tsx index a58fae41f..30c7578bf 100644 --- a/src/GZCTF/ClientApp/src/pages/games/[id]/monitor/CheatInfo.tsx +++ b/src/GZCTF/ClientApp/src/pages/games/[id]/monitor/CheatInfo.tsx @@ -40,6 +40,11 @@ enum CheatType { Owned = 'Owned', } +const CHEAT_INFO_MISSING_VALUE = '__missing__' +const CHEAT_SUBMISSION_SEPARATOR = '|' +const CHEAT_SUBMISSION_AT = '@' +const CHEAT_SUBMISSION_ARROW = '→' + const CheatTypeMap = new Map([ [ CheatType.Submit, @@ -151,16 +156,18 @@ const ToCheatSubmissionInfo = (info: CheatInfoModel, cheatType: CheatType = Chea const GetCheatSubmissionKey = (submissionInfo: CheatSubmissionInfo) => [ submissionInfo.cheatType, - submissionInfo.time?.unix() ?? 'time', - submissionInfo.submitTeam ?? 'submit-team', - submissionInfo.ownedTeam ?? 'owned-team', - submissionInfo.user ?? 'user', - submissionInfo.challenge ?? 'challenge', - submissionInfo.answer ?? 'answer', - ].join(':') + submissionInfo.time?.unix() ?? CHEAT_INFO_MISSING_VALUE, + submissionInfo.submitTeam ?? CHEAT_INFO_MISSING_VALUE, + submissionInfo.ownedTeam ?? CHEAT_INFO_MISSING_VALUE, + submissionInfo.user ?? CHEAT_INFO_MISSING_VALUE, + submissionInfo.challenge ?? CHEAT_INFO_MISSING_VALUE, + ].join(CHEAT_SUBMISSION_SEPARATOR) const FormatCheatTime = (time: dayjs.Dayjs | undefined, locale: string, format: string) => - time ? dayjs(time).locale(locale).format(format) : '-' + time ? time.locale(locale).format(format) : '-' + +const FormatCheatSubmissionSummary = (submissionInfo: CheatSubmissionInfo, userLabel: string, teamLabel: string) => + `${submissionInfo.user ?? userLabel} ${CHEAT_SUBMISSION_AT} ${submissionInfo.submitTeam ?? teamLabel} ${CHEAT_SUBMISSION_ARROW} ${submissionInfo.ownedTeam ?? teamLabel}` interface CheatDetailItemProps { label: string @@ -206,7 +213,7 @@ const CheatSubmissionCard: FC = (props) => { - {`${submissionInfo.user ?? t('common.label.user')} @ ${submissionInfo.submitTeam ?? t('common.label.team')} → ${submissionInfo.ownedTeam ?? t('common.label.team')}`} + {FormatCheatSubmissionSummary(submissionInfo, t('common.label.user'), t('common.label.team'))} @@ -358,9 +365,9 @@ interface CheatInfoTableViewProps { const CheatInfoTableView: FC = (props) => { const rows = props.cheatInfo .sort((a, b) => (dayjs(b.submission?.time).unix() ?? 0) - (dayjs(a.submission?.time).unix() ?? 0)) - .map((item, i) => { + .map((item) => { const submissionInfo = ToCheatSubmissionInfo(item) - return + return }) return ( From 2b508cd5a0802c822ba9150808a42231f8eed85f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Mar 2026 19:15:32 +0000 Subject: [PATCH 4/4] refactor: compact cheating incident cards Co-authored-by: hez2010 <14960345+hez2010@users.noreply.github.com> --- .../pages/games/[id]/monitor/CheatInfo.tsx | 70 ++++++------------- 1 file changed, 20 insertions(+), 50 deletions(-) diff --git a/src/GZCTF/ClientApp/src/pages/games/[id]/monitor/CheatInfo.tsx b/src/GZCTF/ClientApp/src/pages/games/[id]/monitor/CheatInfo.tsx index 30c7578bf..8f4c4d6a9 100644 --- a/src/GZCTF/ClientApp/src/pages/games/[id]/monitor/CheatInfo.tsx +++ b/src/GZCTF/ClientApp/src/pages/games/[id]/monitor/CheatInfo.tsx @@ -7,7 +7,6 @@ import { Group, Paper, ScrollArea, - SimpleGrid, Stack, Switch, Text, @@ -169,71 +168,42 @@ const FormatCheatTime = (time: dayjs.Dayjs | undefined, locale: string, format: const FormatCheatSubmissionSummary = (submissionInfo: CheatSubmissionInfo, userLabel: string, teamLabel: string) => `${submissionInfo.user ?? userLabel} ${CHEAT_SUBMISSION_AT} ${submissionInfo.submitTeam ?? teamLabel} ${CHEAT_SUBMISSION_ARROW} ${submissionInfo.ownedTeam ?? teamLabel}` -interface CheatDetailItemProps { - label: string - value?: string -} - -const CheatDetailItem: FC = ({ label, value }) => ( - - - {label} - - - {value ?? '-'} - - -) - interface CheatSubmissionCardProps { submissionInfo: CheatSubmissionInfo } const CheatSubmissionCard: FC = (props) => { const { submissionInfo } = props - const theme = useMantineTheme() const type = CheatTypeMap.get(submissionInfo.cheatType)! const { t } = useTranslation() const { locale } = useLanguage() return ( - - + + - - - - - {submissionInfo.cheatType === CheatType.Submit - ? t('game.label.cheat_info.submit_team') - : t('game.label.cheat_info.owned_team')} - - - {FormatCheatTime(submissionInfo.time, locale, 'SL HH:mm:ss')} - - - - {FormatCheatSubmissionSummary(submissionInfo, t('common.label.user'), t('common.label.team'))} - - - - {submissionInfo.challenge ?? t('common.label.challenge')} + + {FormatCheatSubmissionSummary(submissionInfo, t('common.label.user'), t('common.label.team'))} + + }> + {submissionInfo.cheatType === CheatType.Submit + ? t('game.label.cheat_info.submit_team') + : t('game.label.cheat_info.owned_team')} + + + {FormatCheatTime(submissionInfo.time, locale, 'SL HH:mm:ss')} + + - - - - - - - - - {t('common.label.flag')} - - + + + {submissionInfo.challenge ?? t('common.label.challenge')} + + {submissionInfo.answer ?? '-'} - + )