Skip to content

Commit

Permalink
fix: create instance
Browse files Browse the repository at this point in the history
  • Loading branch information
GZTimeWalker committed Aug 4, 2022
1 parent bfa8cea commit e20299e
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 30 deletions.
4 changes: 2 additions & 2 deletions GZCTF/ClientApp/src/components/ChallengeCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ interface ChallengeCardProps {
onClick?: () => void
}

const ChallengeCard: FC<ChallengeCardProps> = ({ challenge, solved, onClick }) => {
const ChallengeCard: FC<ChallengeCardProps> = ({ challenge, onClick }) => {
const tagData = ChallengeTagLabelMap.get(challenge.tag!)
const { classes, theme, cx } = useStyles()
const { theme } = useStyles()

return (
<Card
Expand Down
176 changes: 152 additions & 24 deletions GZCTF/ClientApp/src/components/ChallengeDetailModal.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,90 @@
import dayjs from 'dayjs'
import duration from 'dayjs/plugin/duration'
import { marked } from 'marked'
import { FC, useState } from 'react'
import { FC, useEffect, useState } from 'react'
import {
ActionIcon,
Button,
Card,
Code,
Divider,
Group,
LoadingOverlay,
Modal,
ModalProps,
Popover,
Stack,
Text,
TextInput,
Title,
TypographyStylesProvider,
} from '@mantine/core'
import { useClipboard, useDisclosure, useInterval } from '@mantine/hooks'
import { mdiDownload, mdiLightbulbOnOutline } from '@mdi/js'
import { Icon } from '@mdi/react'
import api from '../Api'
import api, { ChallengeType } from '../Api'
import { useTypographyStyles } from '../utils/ThemeOverride'
import { ChallengeTagItemProps } from './ChallengeItem'
import { mdiLightbulbOnOutline } from '@mdi/js'

interface ChallengeDetailModalProps extends ModalProps {
gameId: number
tagData: ChallengeTagItemProps
title: string
score: number
challengeId: number | null
challengeId: number
}

dayjs.extend(duration)

const Countdown: FC<{ time: string }> = ({ time }) => {
const end = dayjs(time)
const [now, setNow] = useState(dayjs())
const interval = useInterval(() => setNow(dayjs()), 1000)

const countdown = dayjs.duration(end.diff(now))

useEffect(() => {
if (dayjs() < end) {
interval.start()
return interval.stop
}
}, [])

return (
<Card style={{ width: '5rem', textAlign: 'center', padding: '0px 4px' }}>
<Text size="sm" style={{ fontWeight: 700 }}>
{countdown.asSeconds() > 0 ? countdown.format('hh : mm : ss') : '00:00:00'}
</Text>
</Card>
)
}

const ChallengeDetailModal: FC<ChallengeDetailModalProps> = (props) => {
const { gameId, challengeId, tagData, title, score, ...modalProps } = props
const [downloadOpened, { close: downloadClose, open: downloadOpen }] = useDisclosure(false)

const [disabled] = useState(false)

const { data: challenge } = api.game.useGameGetChallenge(gameId, challengeId ?? 0, {
const { data: challenge } = api.game.useGameGetChallenge(gameId, challengeId, {
refreshInterval: 0,
revalidateOnFocus: false,
})

const instanceCloseTime = dayjs(challenge?.context?.closeTime ?? 0)
const instanceLeft = instanceCloseTime.diff(dayjs(), 'minute')

const isDynamic =
challenge?.type === ChallengeType.StaticContainer ||
challenge?.type === ChallengeType.DynamicContainer
const { classes, theme } = useTypographyStyles()
const clipBoard = useClipboard()

const onCreateContainer = () => {
if(challengeId) {
api.game.gameCreateContainer(gameId, challengeId)
.then((res) => {
console.log(res.data)
})
}
}

return (
<Modal
Expand All @@ -59,31 +108,110 @@ const ChallengeDetailModal: FC<ChallengeDetailModalProps> = (props) => {
</Group>
}
>
<Stack>
<Stack spacing="sm">
<Divider
size="sm"
variant="dashed"
color={tagData?.color}
labelPosition="center"
label={tagData && <Icon path={tagData.icon} size={1} />}
/>
<TypographyStylesProvider className={classes.root} style={{ minHeight: '4rem' }}>
<div dangerouslySetInnerHTML={{ __html: marked(challenge?.content ?? '') }} />
</TypographyStylesProvider>
{challenge && challenge.hints && (
<Stack spacing={2}>
{challenge.hints.split(';').map((hint) => (
<Group spacing="xs" align="flex-start" noWrap>
<Icon path={mdiLightbulbOnOutline} size={0.8} color={theme.colors.yellow[5]}/>
<Text key={hint} size="sm" style={{ maxWidth: 'calc(100% - 2rem)' }}>{hint}</Text>
<Stack style={{ position: 'relative', minHeight: '5rem' }}>
<LoadingOverlay visible={!challenge} />
<Group grow noWrap position="right" align="flex-start" spacing={2}>
<TypographyStylesProvider className={classes.root} style={{ minHeight: '4rem' }}>
{challenge?.context?.url && (
<Popover
opened={downloadOpened}
position="left"
width="5rem"
styles={{
dropdown: {
padding: '5px',
},
}}
>
<Popover.Target>
<ActionIcon
onMouseEnter={downloadOpen}
onMouseLeave={downloadClose}
onClick={() => window.open(challenge.context?.url ?? '#')}
sx={(theme) => ({
...theme.fn.hover({
color: theme.colors[tagData.color][5],
}),
float: 'right',
})}
>
<Icon path={mdiDownload} size={1} />
</ActionIcon>
</Popover.Target>
<Popover.Dropdown>
<Text size="sm" align="center">
下载附件
</Text>
</Popover.Dropdown>
</Popover>
)}
<div dangerouslySetInnerHTML={{ __html: marked(challenge?.content ?? '') }} />
</TypographyStylesProvider>
</Group>
{isDynamic && !challenge?.context?.instanceEntry && (
<Group position="center" spacing={2}>
<Button onClick={onCreateContainer}>启动实例</Button>
</Group>
)}
{isDynamic && challenge?.context?.instanceEntry && (
<Stack align="center">
<Group>
<Text size="sm" weight={600}>
实例访问入口:
<Code
style={{
backgroundColor: 'transparent',
fontSize: theme.fontSizes.sm,
}}
onClick={() => clipBoard.copy(challenge.context?.instanceEntry ?? '')}
>
127.0.0.1:25532
</Code>
</Text>
<Countdown time={challenge?.context?.closeTime ?? '0'} />
</Group>
))}
</Stack>
)}
<Group grow style={{ margin: 'auto', width: '100%' }}>
<Button fullWidth disabled={disabled}></Button>
<Button fullWidth disabled={disabled}></Button>
</Group>
<Group position="right">
<Button color="orange" disabled={instanceLeft > 10}>
延长时间
</Button>
<Button color="red">销毁实例</Button>
</Group>
</Stack>
)}
{challenge?.hints && (
<Stack spacing={2}>
{challenge.hints.split(';').map((hint) => (
<Group spacing="xs" align="flex-start" noWrap>
<Icon path={mdiLightbulbOnOutline} size={0.8} color={theme.colors.yellow[5]} />
<Text key={hint} size="sm" style={{ maxWidth: 'calc(100% - 2rem)' }}>
{hint}
</Text>
</Group>
))}
</Stack>
)}
</Stack>
<Divider size="sm" variant="dashed" color={tagData.color} />
<TextInput
placeholder="flag{...}"
styles={{
rightSection:{
width: 'auto'
},
input: {
fontFamily: theme.fontFamilyMonospace,
},
}}
rightSection={<Button>提交 flag</Button>}
/>
</Stack>
</Modal>
)
Expand Down
4 changes: 2 additions & 2 deletions GZCTF/ClientApp/src/components/ChallengePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@ const ChallengePanel: FC = () => {
opened={detailOpened}
onClose={() => setDetailOpened(false)}
withCloseButton={false}
size="30%"
size="40%"
centered
gameId={numId}
tagData={ChallengeTagLabelMap.get(challenge?.tag as ChallengeTag ?? ChallengeTag.Misc)!}
title={challenge?.title ?? ''}
score={challenge?.score ?? 0}
challengeId={challenge?.id ?? null}
challengeId={challenge?.id ?? allChallenges.at(0)?.id ?? -1}
/>
</Group>
)
Expand Down
3 changes: 2 additions & 1 deletion GZCTF/ClientApp/src/pages/games/[id]/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ const useStyles = createStyles((theme) => ({
paddingTop: '1rem',
},
banner: {
width: '40%',
maxWidth: '40%',
width: '20vw',

[theme.fn.smallerThan('sm')]: {
display: 'none',
Expand Down
11 changes: 10 additions & 1 deletion GZCTF/Repositories/InstanceRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public InstanceRepository(AppDbContext _context,
public async Task<Instance?> GetInstance(Participation team, int challengeId, CancellationToken token = default)
{
var instance = await context.Instances
.Include(i => i.FlagContext)
.Where(e => e.ChallengeId == challengeId && e.Participation == team)
.SingleOrDefaultAsync(token);

Expand Down Expand Up @@ -65,10 +66,12 @@ public InstanceRepository(AppDbContext _context,
{
instance.FlagContext = new()
{
Challenge = challenge,
Flag = $"flag{Guid.NewGuid():B}",
IsOccupied = true
};
}
await context.AddAsync(instance);
}

instance.IsLoaded = true;
Expand Down Expand Up @@ -118,8 +121,14 @@ public async Task<TaskResult<Container>> CreateContainer(Instance instance, Canc
MemoryLimit = instance.Challenge.MemoryLimit ?? 64,
ExposedPort = instance.Challenge.ContainerExposePort ?? throw new ArgumentException("创建容器时遇到无效的端口"),
}, token);
instance.Container = container;

if (container is null)
{
logger.SystemLog($"为题目 {instance.Challenge.Title} 启动容器实例失败", TaskStatus.Fail, LogLevel.Warning);
return new TaskResult<Container>(TaskStatus.Fail);
}

instance.Container = container;
await context.SaveChangesAsync(token);
}

Expand Down

0 comments on commit e20299e

Please sign in to comment.