Skip to content

Commit

Permalink
add: welcome animationd done + tabbed home screen
Browse files Browse the repository at this point in the history
  • Loading branch information
domysh committed Jun 19, 2024
1 parent 567e39a commit 2606dfa
Show file tree
Hide file tree
Showing 16 changed files with 577 additions and 148 deletions.
Binary file modified frontend/bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@tiptap/starter-kit": "^2.3.2",
"dayjs": "^1.11.11",
"embla-carousel-react": "^8.0.4",
"framer-motion": "^11.2.10",
"immer": "^10.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
Binary file removed frontend/public/xfarm-animation.webm
Binary file not shown.
1 change: 1 addition & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export default function App() {
<Box className='center-flex' style={{ width: "100%", height:80 }} >
<span>Made with ❤️ and 🚩 by <a href="https://pwnzer0tt1.it" target='_blank'>Pwnzer0tt1</a></span>
</Box>
<Divider />
</Container>

</AppShell.Main>
Expand Down
4 changes: 1 addition & 3 deletions frontend/src/components/ChartView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { hashedColor } from "@/utils"
import { flagsStatsQuery, statusQuery } from "@/utils/queries"
import { getDateSmallFormatted } from "@/utils/time"
import { AreaChart, ChartData } from "@mantine/charts"
import { Space, Title } from "@mantine/core"
import { Space } from "@mantine/core"
import { useMemo } from "react"


Expand Down Expand Up @@ -30,8 +30,6 @@ export const ChartView = () => {
}, [stats.isFetching])
return <>
<Space h="lg" />
<Title order={1}>Chart</Title>
<Space h="xl" />
<Space h="md" />
<AreaChart
h={300}
Expand Down
161 changes: 27 additions & 134 deletions frontend/src/components/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,17 @@
import { flagsQuery, flagsStatsQuery, statusQuery, useClientSolver, useExploitSolver, useServiceSolverByExploitId, useTeamSolver } from "@/utils/queries";
import { useSettingsStore } from "@/utils/stores";
import { Alert, Box, Button, Loader, MantineStyleProp, Pagination, ScrollArea, Space, Table } from "@mantine/core";
import { Fragment, useMemo, useState } from "react";
import { DonutChart } from '@mantine/charts';
import { FaKeyboard } from "react-icons/fa";
import { TbReload } from "react-icons/tb";
import { useQueryClient } from "@tanstack/react-query";
import { notifications } from "@mantine/notifications";
import { StatusIcon } from "./StatusIcon";
import { getDateFormatted, secondDurationToString } from "@/utils/time";
import { statusQuery } from "@/utils/queries";
import { Alert, Box, ScrollArea, Space, Tabs } from "@mantine/core";
import { Fragment } from "react";
import { MdErrorOutline } from "react-icons/md";
import { AiOutlineInfoCircle } from "react-icons/ai";
import { AiOutlineWarning } from "react-icons/ai";
import { ManualSubmissionModal } from "./ManualSubmissionModal";
import { ChartView } from "./ChartView";
import { IoFlagSharp } from "react-icons/io5";
import { FlagsScreen } from "./Pages/FlagsScreen";
import { PiSwordBold } from "react-icons/pi";
import { MdGroups } from "react-icons/md";

export const HomePage = () => {

const [page, setPage] = useState<number>(1)
const [bulkPageSize, tablePageSize] = useSettingsStore((state) => [state.pageSizeRequest, state.tablePageSize])
const pagesForBulk = (bulkPageSize/tablePageSize)
const bulkedPage = Math.ceil(page/pagesForBulk)
const queryClient = useQueryClient()
const getTeamName = useTeamSolver()
const getServiceName = useServiceSolverByExploitId()
const getExploitName = useExploitSolver()
const getClientName = useClientSolver()
const [manualSubmissionModal, setManualSubmissionModal] = useState<boolean>(false)

const flagsStats = flagsStatsQuery()
const status = statusQuery()
const flags = flagsQuery(bulkedPage)
const totFlags = flagsStats.data?.globals.flags.tot??0
const totalPages = Math.ceil(totFlags/tablePageSize)
const thStyle: MantineStyleProp = { fontWeight: "bolder", fontSize: "130%", textTransform: "uppercase" }

const tableData = useMemo(() => {
const offsetArray = ((page-1)%pagesForBulk)*tablePageSize

return (flags.data?.items??[]).slice(offsetArray,offsetArray+tablePageSize).map((item) => {
const executionTime = (item.attack.start_time && item.attack.end_time)?secondDurationToString((new Date(item.attack.end_time).getTime()-new Date(item.attack.start_time).getTime())/1000):"unknown execution time"
return <Table.Tr key={item.id}>
<Table.Td>{item.id}</Table.Td>
<Table.Td><span style={{fontWeight: "bolder"}}>{item.flag}</span></Table.Td> {/* Insert click to attack execution details */}
<Table.Td><Box style={{fontWeight: "bolder"}}>{getServiceName(item.attack.exploit)}</Box>using {getExploitName(item.attack.exploit)} exploit</Table.Td>
<Table.Td><span style={{fontWeight: "bolder"}}>{getTeamName(item.attack.target)}</span></Table.Td>
<Table.Td>time: {executionTime}<br />by {getClientName(item.attack.executed_by)}</Table.Td>
<Table.Td>{item.status_text??"No response from submitter"}<br />Submitted At: {item.last_submission_at?getDateFormatted(item.last_submission_at):"never"}</Table.Td> {/* item.submit_attempts + item.last_submission_at -> Status include number of tries if != 1 and last submission if failed */}
<Table.Td><StatusIcon status={item.status} /></Table.Td>
</Table.Tr>
})
}, [flags.isFetching, page])

const messages = (status.data?.messages??[]).map((msg, i) => {
const lvl = msg.level??"warning"
const title = msg.title??"Unknown message"
Expand All @@ -71,93 +31,26 @@ export const HomePage = () => {
return <Box style={{
width: "100%",
}}>
{messages}
<ChartView />
<Space h="xl" hiddenFrom="md" />
<Box className="center-flex-col">
<Box className="center-flex" style={{width:"100%", flexWrap: "wrap" }}>
<Pagination total={totalPages} color="red" radius="md" value={page} onChange={setPage} />
<Box hiddenFrom="md" style={{flexBasis: "100%", height:30}} />
<Space w="lg" visibleFrom="md"/>
<Box className="center-flex">
<Button
leftSection={<FaKeyboard size={20} />}
variant="gradient"
gradient={{ from: 'red', to: 'grape', deg: 90 }}
onClick={() => setManualSubmissionModal(true)}
>
Manual Submission
</Button>
<Space w="md" />
<Button
leftSection={<TbReload size={20} />}
variant="gradient"
gradient={{ from: 'blue', to: 'teal', deg: 90 }}
onClick={() => {
queryClient.refetchQueries({ queryKey:["flags"] })
notifications.show({
title: "Fresh data arrived 🌱",
message: "Flag data has been refreshed!",
color: "green",
autoClose: 3000
})
}}
loading={flags.isLoading}
>
Refresh
</Button>
</Box>
<Box hiddenFrom="md" style={{flexBasis: "100%", height:20}} />

<Box style={{flex:1, flexGrow:1}} visibleFrom="md"/>

<Box className="center-flex-col">
<Space visibleFrom="md" h="lg" />
<DonutChart
data={[
{ value: flagsStats.data?.globals.flags.ok??0, color: 'lime', name: "Accepted" },
{ value: flagsStats.data?.globals.flags.timeout??0, color: 'yellow', name: "Expired" },
{ value: flagsStats.data?.globals.flags.invalid??0, color: 'red', name: "Rejected"},
{ value: flagsStats.data?.globals.flags.wait??0, color: 'indigo', name: "Queued"},
]}
startAngle={0}
endAngle={180}
paddingAngle={1}
size={160}
thickness={35}
withTooltip
tooltipDataSource="all"
mx="auto"
withLabelsLine
withLabels
style={{ marginBottom: -75 }}
chartLabel={`${totFlags} Flags`}
/>
</Box>
</Box>
{flags.isLoading?<Loader />:null}
<Space h="md" />
<Table style={{zIndex:1}}>
<Table.Thead>
<Table.Tr>
<Table.Th style={thStyle}>ID</Table.Th>
<Table.Th style={thStyle}>Flag</Table.Th>
<Table.Th style={thStyle}>Service</Table.Th>
<Table.Th style={thStyle}>Team</Table.Th>
<Table.Th style={thStyle}>Execution</Table.Th>
<Table.Th style={thStyle}>Response</Table.Th>
<Table.Th style={thStyle} className="center-flex">Status</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>{tableData}</Table.Tbody>
</Table>
{messages?<>{messages}<Space h="lg" /></>:null}
<Tabs variant="pills" radius="xl" orientation="horizontal" defaultValue="gallery">
<Tabs.List>
<Tabs.Tab value="flags" leftSection={<IoFlagSharp />} color="red"> Flags </Tabs.Tab>
<Tabs.Tab value="attacks" leftSection={<PiSwordBold />} color="green"> Attacks </Tabs.Tab>
<Tabs.Tab value="teams" leftSection={<MdGroups />}> Teams </Tabs.Tab>

</Tabs.List>
<Space h="md" />
{flags.isLoading?<Loader />:null}
{totalPages==0?"No flags found!":null}
<Space h="xl" />
<Pagination total={totalPages} color="red" radius="md" value={page} onChange={setPage} />
</Box>
<ManualSubmissionModal opened={manualSubmissionModal} close={() => setManualSubmissionModal(false)} />
<Space h="xl" /><Space h="xl" />
<Tabs.Panel value="flags">
<FlagsScreen />
</Tabs.Panel>
<Tabs.Panel value="attacks">
TODO
</Tabs.Panel>
<Tabs.Panel value="teams">
TODO
</Tabs.Panel>
</Tabs>
<Space h="xl" />
<Space h="xl" />
</Box>
}
1 change: 0 additions & 1 deletion frontend/src/components/LoginProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ export const LoginProvider = ({ children }: { children:any }) => {
}}>
<Container>
<WelcomeTitle description="A password is required to access the platform!" />
<Space h="xl" />
<form
style={{
width: "100%"
Expand Down
152 changes: 152 additions & 0 deletions frontend/src/components/Pages/FlagsScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { flagsQuery, flagsStatsQuery, useClientSolver, useExploitSolver, useServiceSolverByExploitId, useTeamSolver } from "@/utils/queries";
import { useSettingsStore } from "@/utils/stores";
import { Box, Button, Loader, MantineStyleProp, Pagination, Space, Table } from "@mantine/core";
import { useMemo, useState } from "react";
import { DonutChart } from '@mantine/charts';
import { FaKeyboard } from "react-icons/fa";
import { TbReload } from "react-icons/tb";
import { useQueryClient } from "@tanstack/react-query";
import { notifications } from "@mantine/notifications";
import { StatusIcon } from "@/components/StatusIcon";
import { getDateFormatted, secondDurationToString } from "@/utils/time";
import { ManualSubmissionModal } from "@/components/ManualSubmissionModal";
import { ChartView } from "@/components/ChartView";

export const FlagsScreen = () => {

const [page, setPage] = useState<number>(1)
const [bulkPageSize, tablePageSize] = useSettingsStore((state) => [state.pageSizeRequest, state.tablePageSize])
const pagesForBulk = (bulkPageSize/tablePageSize)
const bulkedPage = Math.ceil(page/pagesForBulk)
const queryClient = useQueryClient()
const getTeamName = useTeamSolver()
const getServiceName = useServiceSolverByExploitId()
const getExploitName = useExploitSolver()
const getClientName = useClientSolver()
const [manualSubmissionModal, setManualSubmissionModal] = useState<boolean>(false)

const flagsStats = flagsStatsQuery()
const flags = flagsQuery(bulkedPage)
const totFlags = flagsStats.data?.globals.flags.tot??0
const totalPages = Math.ceil(totFlags/tablePageSize)
const thStyle: MantineStyleProp = { fontWeight: "bolder", fontSize: "130%", textTransform: "uppercase" }

const tableData = useMemo(() => {
const offsetArray = ((page-1)%pagesForBulk)*tablePageSize

return (flags.data?.items??[]).slice(offsetArray,offsetArray+tablePageSize).map((item) => {
const executionTime = (item.attack.start_time && item.attack.end_time)?secondDurationToString((new Date(item.attack.end_time).getTime()-new Date(item.attack.start_time).getTime())/1000):"unknown execution time"
return <Table.Tr key={item.id}>
<Table.Td>{item.id}</Table.Td>
<Table.Td><span style={{fontWeight: "bolder"}}>{item.flag}</span></Table.Td> {/* Insert click to attack execution details */}
<Table.Td><Box style={{fontWeight: "bolder"}}>{getServiceName(item.attack.exploit)}</Box>using {getExploitName(item.attack.exploit)} exploit</Table.Td>
<Table.Td><span style={{fontWeight: "bolder"}}>{getTeamName(item.attack.target)}</span></Table.Td>
<Table.Td>time: {executionTime}<br />by {getClientName(item.attack.executed_by)}</Table.Td>
<Table.Td>{item.status_text??"No response from submitter"}<br />Submitted At: {item.last_submission_at?getDateFormatted(item.last_submission_at):"never"}</Table.Td> {/* item.submit_attempts + item.last_submission_at -> Status include number of tries if != 1 and last submission if failed */}
<Table.Td><StatusIcon status={item.status} /></Table.Td>
</Table.Tr>
})
}, [flags.isFetching, page])

const flags_tot_stats = {
ok: flagsStats.data?.globals.flags.ok??0,
timeout: flagsStats.data?.globals.flags.timeout??0,
invalid: flagsStats.data?.globals.flags.invalid??0,
wait: flagsStats.data?.globals.flags.wait??0
}

return <Box style={{
width: "100%",
}}>
<ChartView />
<Space h="xl" hiddenFrom="md" />
<Box className="center-flex-col">
<Box className="center-flex" style={{width:"100%", flexWrap: "wrap" }}>
<Pagination total={totalPages} color="red" radius="md" value={page} onChange={setPage} />
<Box hiddenFrom="md" style={{flexBasis: "100%", height:30}} />
<Space w="lg" visibleFrom="md"/>
<Box className="center-flex">
<Button
leftSection={<FaKeyboard size={20} />}
variant="gradient"
gradient={{ from: 'red', to: 'grape', deg: 90 }}
onClick={() => setManualSubmissionModal(true)}
>
Manual Submission
</Button>
<Space w="md" />
<Button
leftSection={<TbReload size={20} />}
variant="gradient"
gradient={{ from: 'blue', to: 'teal', deg: 90 }}
onClick={() => {
queryClient.refetchQueries({ queryKey:["flags"] })
notifications.show({
title: "Fresh data arrived 🌱",
message: "Flag data has been refreshed!",
color: "green",
autoClose: 3000
})
}}
loading={flags.isLoading}
>
Refresh
</Button>
</Box>
<Box hiddenFrom="md" style={{flexBasis: "100%", height:20}} />

<Box style={{flex:1, flexGrow:1}} visibleFrom="md"/>

<Box className="center-flex-col">
<Space visibleFrom="md" h="lg" />
<DonutChart
data={totFlags>0?[
{ value: flags_tot_stats.ok, color: 'lime', name: "Accepted" },
{ value: flags_tot_stats.timeout, color: 'yellow', name: "Expired" },
{ value: flags_tot_stats.invalid, color: 'red', name: "Rejected"},
{ value: flags_tot_stats.wait, color: 'indigo', name: "Queued"},
]:[
{ value: 1, color: 'gray', name: "No flags" },
]}

startAngle={0}
endAngle={180}
paddingAngle={1}
size={160}
thickness={35}
withTooltip={totFlags>0}
tooltipDataSource="all"
mx="auto"
withLabelsLine
withLabels={totFlags>0}
style={{ marginBottom: -75 }}
chartLabel={totFlags>0?`${totFlags} Flags`:"No flags"}
/>
</Box>
</Box>
{flags.isLoading?<Loader />:null}
<Space h="md" />
<Table style={{zIndex:1}}>
<Table.Thead>
<Table.Tr>
<Table.Th style={thStyle}>ID</Table.Th>
<Table.Th style={thStyle}>Flag</Table.Th>
<Table.Th style={thStyle}>Service</Table.Th>
<Table.Th style={thStyle}>Team</Table.Th>
<Table.Th style={thStyle}>Execution</Table.Th>
<Table.Th style={thStyle}>Response</Table.Th>
<Table.Th style={thStyle} className="center-flex">Status</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>{tableData}</Table.Tbody>
</Table>
<Space h="md" />
{flags.isLoading?<Loader />:null}
{totalPages==0?"No flags found!":null}
<Space h="xl" />
<Pagination total={totalPages} color="red" radius="md" value={page} onChange={setPage} />
</Box>
<ManualSubmissionModal opened={manualSubmissionModal} close={() => setManualSubmissionModal(false)} />
<Space h="xl" /><Space h="xl" />
</Box>
}
1 change: 0 additions & 1 deletion frontend/src/components/SetupScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export const SetupScreen = () => {
return <Container>
<Space h="xl" />
<WelcomeTitle description="Exploit farm is in setup mode ⚙️, insert all the configurations here" />
<Space h="xl" />
<Box style={{ textAlign: "center", fontWeight:"bolder" }}>
You need an auto-setup script to run!! In the future will be available a setup wizard on this page.
</Box>
Expand Down
Loading

0 comments on commit 2606dfa

Please sign in to comment.