Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 72 additions & 82 deletions src/GZCTF/ClientApp/src/pages/games/[id]/monitor/CheatInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ import {
Box,
Center,
Group,
Input,
Paper,
ScrollArea,
Stack,
Switch,
Table,
Text,
Title,
useMantineTheme,
Expand All @@ -30,7 +28,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'
Expand All @@ -42,6 +39,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,
Expand All @@ -64,7 +66,8 @@ interface CheatSubmissionInfo {
answer?: string
user?: string
challenge?: string
relatedTeam?: string
submitTeam?: string
ownedTeam?: string
cheatType: CheatType
}

Expand Down Expand Up @@ -114,12 +117,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)
Expand All @@ -131,48 +135,77 @@ const ToCheatTeamInfo = (cheatInfo: CheatInfoModel[]) => {
const cheatSubmissionSourceInfo: CheatSubmissionInfo = {
...cheatSubmissionInfo,
cheatType: CheatType.Submit,
relatedTeam: ownedTeam.team?.name,
}

submitTeamInfo.submissionInfo.add(cheatSubmissionSourceInfo)
}
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() ?? 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 ? 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 CheatSubmissionCardProps {
submissionInfo: CheatSubmissionInfo
}

const CheatSubmissionInfo: FC<CheatSubmissionInfoProps> = (props) => {
const CheatSubmissionCard: FC<CheatSubmissionCardProps> = (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 (
<Group justify="space-between" w="100%" gap={0}>
<Group justify="space-between" w="60%" pr="2rem">
<Group justify="left">
<Icon path={type.iconPath} size={1} color={theme.colors[type.color][6]} />
<Badge size="sm" color="indigo">
{dayjs(submissionInfo.time).locale(locale).format('SL HH:mm:ss')}
<Paper withBorder radius="md" p="xs">
<Stack gap={6}>
<Group justify="space-between" align="flex-start" gap="xs">
<Text size="sm" fw="bold" className={misc.wordBreakAll}>
{FormatCheatSubmissionSummary(submissionInfo, t('common.label.user'), t('common.label.team'))}
</Text>
<Group gap={6} justify="flex-end">
<Badge size="xs" color={type.color} variant="light" leftSection={<Icon path={type.iconPath} size={0.8} />}>
{submissionInfo.cheatType === CheatType.Submit
? t('game.label.cheat_info.submit_team')
: t('game.label.cheat_info.owned_team')}
</Badge>
<Badge size="xs" color="indigo" variant="light">
{FormatCheatTime(submissionInfo.time, locale, 'SL HH:mm:ss')}
</Badge>
</Group>
</Group>
<Group wrap="wrap" justify="space-between" align="flex-start" gap="xs">
<Badge size="xs" variant="outline" maw="100%" className={misc.wordBreakAll}>
{submissionInfo.challenge ?? t('common.label.challenge')}
</Badge>
<Text lineClamp={1} fw="bold">
{submissionInfo.relatedTeam}
<Text size="sm" ff="monospace" className={misc.wordBreakAll} ta="right">
{submissionInfo.answer ?? '-'}
</Text>
</Group>
<Text size="sm" lineClamp={1} fw="bold">
{submissionInfo.user}
</Text>
</Group>
<Stack gap={0} w="40%">
<Text fw="bold" size="xs" lineClamp={1}>
{submissionInfo.challenge}
</Text>
<Input variant="unstyled" value={submissionInfo.answer} readOnly size="xs" classNames={classes} />
</Stack>
</Group>
</Paper>
)
}

Expand Down Expand Up @@ -205,14 +238,17 @@ const CheatInfoItem: FC<CheatInfoItemProps> = (props) => {
<Title order={4} lineClamp={1} fw="bold">
{!cheatTeamInfo.name ? t('admin.placeholder.games.participation.team') : cheatTeamInfo.name}
</Title>
<Badge size="sm" variant="light" color="gray">
{cheatTeamInfo.submissionInfo.size}
</Badge>
{cheatTeamInfo?.division && (
<Badge size="sm" variant="outline">
{cheatTeamInfo.division}
</Badge>
)}
</Group>
<Text size="sm" lineClamp={1}>
{dayjs(cheatTeamInfo.lastSubmitTime).locale(locale).format('SL LTS')}
{FormatCheatTime(cheatTeamInfo.lastSubmitTime, locale, 'SL LTS')}
</Text>
</Stack>
</Group>
Expand Down Expand Up @@ -242,7 +278,7 @@ const CheatInfoItem: FC<CheatInfoItemProps> = (props) => {
{[...cheatTeamInfo.submissionInfo]
.sort((a, b) => (b.time?.unix() ?? 0) - (a.time?.unix() ?? 0))
.map((submissionInfo) => (
<CheatSubmissionInfo key={submissionInfo.time?.unix()} submissionInfo={submissionInfo} />
<CheatSubmissionCard key={GetCheatSubmissionKey(submissionInfo)} submissionInfo={submissionInfo} />
))}
</Stack>
</Accordion.Panel>
Expand Down Expand Up @@ -297,63 +333,17 @@ interface CheatInfoTableViewProps {
}

const CheatInfoTableView: FC<CheatInfoTableViewProps> = (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) => (
<Table.Tr key={`${item.submission?.time}@${i}`}>
<Table.Td ff="monospace">
<Badge size="sm" color="indigo">
{dayjs(item.submission?.time).locale(locale).format('SL HH:mm:ss')}
</Badge>
</Table.Td>
<Table.Td>
<Text size="sm" fw="bold">
{item.ownedTeam?.team?.name ?? 'Team'}
</Text>
</Table.Td>
<Table.Td>
<Badge size="sm" color="orange">
{`>>>`}
</Badge>
</Table.Td>
<Table.Td>
<Text size="sm" fw="bold">
{item.submitTeam?.team?.name ?? 'Team'}
</Text>
</Table.Td>
<Table.Td>
<Text ff="monospace" size="sm" fw="bold">
{item.submission?.user ?? 'User'}
</Text>
</Table.Td>
<Table.Td>{item.submission?.challenge ?? 'Challenge'}</Table.Td>
<Table.Td p="0" w="24vw">
<Input variant="unstyled" value={item.submission?.answer} readOnly size="sm" classNames={inputClasses} />
</Table.Td>
</Table.Tr>
))
.map((item) => {
const submissionInfo = ToCheatSubmissionInfo(item)
return <CheatSubmissionCard key={GetCheatSubmissionKey(submissionInfo)} submissionInfo={submissionInfo} />
})

return (
<Paper shadow="md" p="md">
<ScrollArea offsetScrollbars h="calc(100vh - 200px)">
<Table className={classes.table}>
<Table.Thead>
<Table.Tr>
<Table.Th w="8rem">{t('common.label.time')}</Table.Th>
<Table.Th miw="5rem">{t('game.label.cheat_info.owned_team')}</Table.Th>
<Table.Th />
<Table.Th miw="5rem">{t('game.label.cheat_info.submit_team')}</Table.Th>
<Table.Th miw="5rem">{t('game.label.cheat_info.submit_user')}</Table.Th>
<Table.Th miw="3rem">{t('common.label.challenge')}</Table.Th>
<Table.Th className={classes.mono}>{t('common.label.flag')}</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>{rows}</Table.Tbody>
</Table>
<Stack gap="sm">{rows}</Stack>
</ScrollArea>
</Paper>
)
Expand Down