-
- {!this.state.challengeCreate && !this.state.editChallenge && (
-
-
} onClick={() => { this.setState({ challengeCreate: true }, this.props.history.push("/Admin/Challenges/Create")) }}>Create New Challenge
+ {!this.state.challengeCreate && !this.state.editChallenge && (
+
+
+ } onClick={() => { this.setState({ challengeCreate: true }, this.props.history.push("/Admin/Challenges/Create")) }}>Create New Challenge
+ } onClick={async () => { await this.handleRefresh(); message.success("Challenge list refreshed.") }} />
+
+ {this.state.loading && (
+
+
+
+ )}
+ {!this.state.loading && (
+
- There are no challenges created.
+ No Challenges Have Been Created.
)
}}>
@@ -329,35 +328,37 @@ class AdminChallenges extends React.Component {
)}
/>
-
-
Category Management {this.state.transferDisabled && ()}
-
-
item.key}
- pagination
- disabled={this.state.transferDisabled}
- />
-
-
)}
-
- } />
- } />
+
+ Category Management {this.state.transferDisabled && ()}
+
+ item.key}
+ pagination
+ disabled={this.state.transferDisabled}
+ />
-
+
)}
+
+
+ } />
+ } />
+
+
+
);
}
diff --git a/client/src/adminSubmissions.js b/client/src/adminSubmissions.js
index 63a45213..6337a827 100644
--- a/client/src/adminSubmissions.js
+++ b/client/src/adminSubmissions.js
@@ -1,7 +1,8 @@
import React from 'react';
-import { Layout, Table, message } from 'antd';
+import { Layout, Table, message, Button } from 'antd';
import {
FileUnknownTwoTone,
+ RedoOutlined
} from '@ant-design/icons';
import { orderBy } from "lodash";
import { Ellipsis } from 'react-spinners-css';
@@ -25,9 +26,9 @@ class AdminSubmissions extends React.Component {
this.fillTableData()
}
- fillTableData = () => {
+ fillTableData = async () => {
this.setState({ loading: true })
- fetch(window.ipAddress + "/v1/submissions", {
+ await fetch(window.ipAddress + "/v1/submissions", {
method: 'get',
headers: { 'Content-Type': 'application/json', "Authorization": localStorage.getItem("IRSCTF-token") },
}).then((results) => {
@@ -69,6 +70,10 @@ class AdminSubmissions extends React.Component {
+
+ } onClick={async () => { await this.fillTableData(); message.success("Submissions list refreshed.") }} />
+
+
{this.state.loading && (
@@ -95,7 +100,7 @@ class AdminSubmissions extends React.Component {
)}
-
+
);
}
}
diff --git a/client/src/adminUsers.js b/client/src/adminUsers.js
index bdd2d50f..07fb6b5c 100644
--- a/client/src/adminUsers.js
+++ b/client/src/adminUsers.js
@@ -7,7 +7,8 @@ import {
ClusterOutlined,
UserOutlined,
MailOutlined,
- LockOutlined
+ LockOutlined,
+ RedoOutlined
} from '@ant-design/icons';
import { Link } from 'react-router-dom';
import { Ellipsis } from 'react-spinners-css';
@@ -109,9 +110,9 @@ class AdminUsers extends React.Component {
this.fillTableData()
}
- fillTableData = () => {
+ fillTableData = async () => {
this.setState({ loading: true })
- fetch(window.ipAddress + "/v1/account/list", {
+ await fetch(window.ipAddress + "/v1/account/list", {
method: 'get',
headers: { 'Content-Type': 'application/json', "Authorization": localStorage.getItem("IRSCTF-token") },
}).then((results) => {
@@ -172,7 +173,7 @@ class AdminUsers extends React.Component {
}).then((data) => {
//console.log(data)
if (data.success === true) {
-
+
message.success({ content: "User \"" + username + "\" deleted successfully" })
this.fillTableData()
}
@@ -241,98 +242,97 @@ class AdminUsers extends React.Component {
)}
-
+ Change User Permissions }
+ visible={this.state.permissionModal}
+ onOk={this.changePermissions}
+ onCancel={() => { this.setState({ permissionModal: false }) }}
+ confirmLoading={this.state.modalLoading}
+ >
+
+
+
+
+
+ - 0 - Normal User: Has access to the basic functions and nothing else
+ - 1 - Challenge Creator User: Has the additional power of submitting new challenges, but not modifying existing ones
+ - 2 - Admin User: Has full access to the platform via the admin panel.
+
+
+
+ { this.setState({ createUserModal: false }) }}
+ confirmLoading={this.state.modalLoading}
+ >
+
+
+
+
+
+
+ } onClick={() => { this.setState({ createUserModal: true }) }}>Create New User
+ } onClick={async () => { await this.fillTableData(); message.success("Users list refreshed.") }} />
+
{!this.state.loading && (
-
-
Change User Permissions }
- visible={this.state.permissionModal}
- onOk={this.changePermissions}
- onCancel={() => { this.setState({ permissionModal: false }) }}
- confirmLoading={this.state.modalLoading}
- >
-
-
-
-
-
- - 0 - Normal User: Has access to the basic functions and nothing else
- - 1 - Challenge Creator User: Has the additional power of submitting new challenges, but not modifying existing ones
- - 2 - Admin User: Has full access to the platform via the admin panel.
-
-
-
-
{ this.setState({ createUserModal: false }) }}
- confirmLoading={this.state.modalLoading}
- >
-
-
-
-
-
-
-
} onClick={() => { this.setState({ createUserModal: true }) }}>Create New User
-
-
-
- There are no users created
-
- )
- }}>
- {
- return {text};
- }}
- />
-
-
-
- (
-
- {
- this.setState({ permissionModal: true, username: record.username, permissionChangeTo: record.type.toString() })
- }}>
-
- Change Permissions
-
-
-
- {
- confirm({
- title: 'Are you sure you want to delete the user \"' + record.username + '\"? This action is irreversible.',
- icon: ,
- onOk: (close) => { this.deleteAccount(close.bind(this), record.username) },
- onCancel: () => { },
- });
- }}>
-
- Delete Account
-
-
-
- } placement="bottomCenter">
-
-
- )}
- />
-
-
+
+
+ There are no users created
+
+ )
+ }}>
+ {
+ return {text};
+ }}
+ />
+
+
+
+ (
+
+ {
+ this.setState({ permissionModal: true, username: record.username, permissionChangeTo: record.type.toString() })
+ }}>
+
+ Change Permissions
+
+
+
+ {
+ confirm({
+ title: 'Are you sure you want to delete the user \"' + record.username + '\"? This action is irreversible.',
+ icon: ,
+ onOk: (close) => { this.deleteAccount(close.bind(this), record.username) },
+ onCancel: () => { },
+ });
+ }}>
+
+ Delete Account
+
+
+
+ } placement="bottomCenter">
+
+
+ )}
+ />
+
)}
+
);
}
diff --git a/client/src/challenges.js b/client/src/challenges.js
index 9feac3df..2e869292 100644
--- a/client/src/challenges.js
+++ b/client/src/challenges.js
@@ -92,7 +92,7 @@ class Challenges extends React.Component {
const category = this.props.match.params.category;
if (typeof category !== "undefined") {
- await this.setState({ challengeCategory: true, currentCategory: decodeURIComponent(category), currentCategoryChallenges: this.state.originalData[decodeURIComponent(category)] })
+ await this.setState({ currentCategory: decodeURIComponent(category), currentCategoryChallenges: this.state.originalData[decodeURIComponent(category)] })
this.sortDifferent({ target: { value: "Type" } })
}
diff --git a/client/src/challengesCategory.js b/client/src/challengesCategory.js
index b95dc5e7..2b3addcf 100644
--- a/client/src/challengesCategory.js
+++ b/client/src/challengesCategory.js
@@ -1,5 +1,5 @@
-import React from 'react';
-import { Layout, Card, List, message, Modal, Tag, Input, Button, Tabs, Avatar, Form, notification, Tooltip } from 'antd';
+import React, { useEffect, useRef } from 'react';
+import { Layout, Card, List, message, Modal, Tag, Input, Button, Tabs, Avatar, Form, notification, Tooltip, Popover } from 'antd';
import {
LoadingOutlined,
UnlockOutlined,
@@ -9,7 +9,8 @@ import {
FileUnknownTwoTone,
EyeInvisibleOutlined,
ExclamationCircleOutlined,
- SolutionOutlined
+ SolutionOutlined,
+ LinkOutlined
} from '@ant-design/icons';
import './App.css';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
@@ -46,6 +47,20 @@ const SubmitFlagForm = (props) => {
);
}
+const CopyLinkInput = (props) => {
+ const copyInput = useRef(null)
+
+ useEffect(() => {
+ copyInput.current.select()
+ document.execCommand('copy')
+ message.success('Challenge link copied to clipboard.')
+ })
+
+ return (
+
+ )
+}
+
class ChallengesCategory extends React.Component {
constructor(props) {
@@ -385,7 +400,7 @@ class ChallengesCategory extends React.Component {
tab={
Challenge}
key="challenge"
>
- {this.state.challengeWriteup !== "" && this.state.challengeWriteup !== "CompleteFirst" (
+ {this.state.challengeWriteup !== "" && this.state.challengeWriteup !== "CompleteFirst"(
} onClick={() => { window.open(this.state.challengeWriteup) }} />
@@ -400,7 +415,9 @@ class ChallengesCategory extends React.Component {
} />
)}
-
{this.state.viewingChallengeDetails.name}
+
+
{this.state.viewingChallengeDetails.name} } >
+
{this.state.challengeTags}
diff --git a/client/src/challengesTagSort.js b/client/src/challengesTagSort.js
index 5c5c89c1..3d4ad2ce 100644
--- a/client/src/challengesTagSort.js
+++ b/client/src/challengesTagSort.js
@@ -1,12 +1,13 @@
-import React from 'react';
-import { Layout, List, message, Modal, Tag, Input, Button, Tabs, Avatar, Form, notification, Tooltip } from 'antd';
+import React, {useEffect, useRef} from 'react';
+import { Layout, List, message, Modal, Tag, Input, Button, Tabs, Avatar, Form, notification, Tooltip, Popover } from 'antd';
import {
UnlockOutlined,
ProfileOutlined,
FlagOutlined,
SmileOutlined,
ExclamationCircleOutlined,
- SolutionOutlined
+ SolutionOutlined,
+ LinkOutlined
} from '@ant-design/icons';
import './App.css';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
@@ -43,6 +44,20 @@ const SubmitFlagForm = (props) => {
);
}
+const CopyLinkInput = (props) => {
+ const copyInput = useRef(null)
+
+ useEffect(() => {
+ copyInput.current.select()
+ document.execCommand('copy')
+ message.success('Challenge link copied to clipboard.')
+ })
+
+ return (
+
+ )
+}
+
class ChallengesTagSort extends React.Component {
constructor(props) {
@@ -75,63 +90,105 @@ class ChallengesTagSort extends React.Component {
currentSorting: "points",
tag: false,
loadingTag: false,
- challengeWriteup: ""
+ challengeWriteup: "",
+ moveTagToFront: ""
};
}
componentDidMount() {
const startup = async () => {
- await this.sortByTags()
+
const challenge = this.props.match.params.challenge;
if (typeof challenge !== "undefined") {
+ await this.sortByTags(challenge)
const solved = this.props.currentCategoryChallenges.find(element => element.name === challenge).solved
this.loadChallengeDetails(challenge, solved)
}
+ else {
+ await this.sortByTags(false)
+ }
}
startup()
}
- sortByTags() {
-
+ sortByTags(findNOpenTag) {
+ console.log(findNOpenTag)
let originalData = this.props.tagData
let tag = {}
this.setState({ loadingTag: true })
+ let moveTagToFront = ""
+ if (!findNOpenTag) {
+ for (const [key, value] of Object.entries(originalData)) {
+ let currentCat = originalData[key]
+ for (let x = 0; x < currentCat.length; x++) { //loop through each challenge
- for (const [key, value] of Object.entries(originalData)) {
- let currentCat = originalData[key]
- for (let x = 0; x < currentCat.length; x++) { //loop through each challenge
+ if ("tags" in currentCat[x]) {
+ const firstTag = currentCat[x].tags[0] //grab the first tag of each challenge as the tag it will use in categorising
+ if (firstTag.toLowerCase() in tag) {
+ tag[firstTag.toLowerCase()].push(currentCat[x]) //add current challenge to that tag's category
+ }
+ else {
+ tag[firstTag.toLowerCase()] = []
+ tag[firstTag.toLowerCase()].push(currentCat[x])
+ }
- if ("tags" in currentCat[x]) {
- const firstTag = currentCat[x].tags[0] //grab the first tag of each challenge as the tag it will use in categorising
- if (firstTag.toLowerCase() in tag) {
- tag[firstTag.toLowerCase()].push(currentCat[x]) //add current challenge to that tag's category
}
else {
- tag[firstTag.toLowerCase()] = []
- tag[firstTag.toLowerCase()].push(currentCat[x])
+ if ("Uncategorised" in tag) tag["Uncategorised"].push(currentCat[x])
+ else {
+ tag["Uncategorised"] = []
+ tag["Uncategorised"].push(currentCat[x])
+ }
}
-
}
- else {
- if ("Uncategorised" in tag) tag["Uncategorised"].push(currentCat[x])
+ }
+ moveTagToFront = Object.keys(tag)[0] //Set to first tag
+ }
+ else {
+
+ let found = false
+ for (const [key, value] of Object.entries(originalData)) {
+ let currentCat = originalData[key]
+ for (let x = 0; x < currentCat.length; x++) { //loop through each challenge
+
+ if (!found && currentCat[x].name === findNOpenTag) {
+ if ("tags" in currentCat[x]) moveTagToFront = currentCat[x].tags[0].toLowerCase()
+ else moveTagToFront = "Uncategorised"
+ found = true
+ }
+ if ("tags" in currentCat[x]) {
+ const firstTag = currentCat[x].tags[0] //grab the first tag of each challenge as the tag it will use in categorising
+ if (firstTag.toLowerCase() in tag) {
+ tag[firstTag.toLowerCase()].push(currentCat[x]) //add current challenge to that tag's category
+ }
+ else {
+ tag[firstTag.toLowerCase()] = []
+ tag[firstTag.toLowerCase()].push(currentCat[x])
+ }
+
+ }
else {
- tag["Uncategorised"] = []
- tag["Uncategorised"].push(currentCat[x])
+ if ("Uncategorised" in tag) tag["Uncategorised"].push(currentCat[x])
+ else {
+ tag["Uncategorised"] = []
+ tag["Uncategorised"].push(currentCat[x])
+ }
}
}
}
}
- for (const [key, value] of Object.entries(tag)) {
+
+ for (const [key, value] of Object.entries(tag)) { //loop through each tag and sort the challenges in each tag by points
tag[key] = orderBy(tag[key], ['points'], ['asc'])
}
//console.log(tag)
- this.setState({ tag: tag, loadingTag: false })
+ this.setState({ tag: tag, loadingTag: false, moveTagToFront: moveTagToFront })
}
@@ -402,6 +459,7 @@ class ChallengesTagSort extends React.Component {
tab={
Challenge}
key="challenge"
>
+
{this.state.challengeWriteup !== "" && this.state.challengeWriteup !== "CompleteFirst" && (
} onClick={() => { window.open(this.state.challengeWriteup) }} />
@@ -417,7 +475,10 @@ class ChallengesTagSort extends React.Component {
} />
)}
-
{this.state.viewingChallengeDetails.name}
+
+
+
{this.state.viewingChallengeDetails.name} } >
+
{this.state.challengeTags}
@@ -479,7 +540,7 @@ class ChallengesTagSort extends React.Component {
{this.state.tag && (
-
+
)}
diff --git a/client/src/challengesTagSortList.js b/client/src/challengesTagSortList.js
index 14328c7a..13bd2ccf 100644
--- a/client/src/challengesTagSortList.js
+++ b/client/src/challengesTagSortList.js
@@ -22,7 +22,7 @@ class ChallengesTagSortList extends React.Component {
}
render() {
return (
-
+
{
Object.entries(this.props.tag).map((currentCat, index) => {
const key = currentCat[0]