diff --git a/src/components/groups/assignments/create-challenge.jsx b/src/components/groups/assignments/create-challenge.jsx
index 5879ad6b..fafe46db 100644
--- a/src/components/groups/assignments/create-challenge.jsx
+++ b/src/components/groups/assignments/create-challenge.jsx
@@ -22,6 +22,7 @@ const styles = {
const auth = getAuth();
export default function Createchall(props) {
+
const pages = [
{
name: 'Create Assignment',
@@ -36,6 +37,7 @@ export default function Createchall(props) {
click: () => {},
},
];
+
const [contentPreview, setContentPreview] = useState('');
const [penalty, setPenalty] = useState([0, 0, 0]);
const [hints, setHints] = useState([
@@ -43,6 +45,7 @@ export default function Createchall(props) {
'No hints set',
'No hints set',
]);
+
const [solution, setSolution] = useState('');
const [difficulty, setDifficulty] = useState('Easy');
const [category, setCategory] = useState('forensics');
@@ -100,13 +103,15 @@ export default function Createchall(props) {
const uploadChallenge = async (fileId) => {
try {
const nConfig = newConfig.replace('\n', ' && ');
+ let new_solution = props.assignmentInfo.option !== "dynamicLab" ? solution : "";
+
const challengeInfo = {
name: newChallengeName,
category: [category],
hints,
penalty,
content: contentPreview,
- solution,
+ solution: new_solution,
difficulty,
category,
commands: nConfig,
@@ -116,7 +121,10 @@ export default function Createchall(props) {
const classCode = window.location.pathname.split('/')[2];
const assignmentInfo = props.assignmentInfo;
const url = `${process.env.NEXT_PUBLIC_API_URL}/classroom-assignments/create-new-assignment/${classCode}`;
- const data = await request(url, 'POST', { challengeInfo, assignmentInfo, username: localStorage.getItem('username') });
+ const data = await request(url, 'POST', {
+ challengeInfo, assignmentInfo,
+ username: localStorage.getItem('username')
+ });
if (data && data.success) {
toast.success('Assignment Created', {
position: 'bottom-right',
@@ -394,6 +402,8 @@ export default function Createchall(props) {
+ {
+ props.assignmentInfo && props.assignmentInfo.option !== "dynamicLab" && (
Challenge Solution
@@ -408,6 +418,8 @@ export default function Createchall(props) {
>
+ )
+ }
diff --git a/src/components/groups/assignments/createAssignment.jsx b/src/components/groups/assignments/createAssignment.jsx
index 1fed2f6e..32968ad7 100644
--- a/src/components/groups/assignments/createAssignment.jsx
+++ b/src/components/groups/assignments/createAssignment.jsx
@@ -26,6 +26,7 @@ export default function CreateGroup(props) {
const [displayExistingChallenge, setDisplayExistingChallenge] =
useState(false);
const [displayCustomChallenge, setDisplayCustomChallenge] = useState(false);
+ const [displayDynamicLab, setDisplayDynamicLab] = useState(false);
const [errMessage, setErrMessage] = useState([]);
@@ -120,6 +121,8 @@ export default function CreateGroup(props) {
setDisplayExistingChallenge(true);
} else if (selectedOption === 'customChallenge') {
setDisplayCustomChallenge(true);
+ } else if(selectedOption === 'dynamicLab') {
+ setDisplayDynamicLab(true);
} else {
setErrMessage('Please select an assignment type');
}
@@ -137,6 +140,7 @@ export default function CreateGroup(props) {
assignmentPoints,
selectedCategory,
latePenalty: parseInt(latePenalty),
+ option: selectedOption
}}
setDisplay={setDisplayExistingChallenge}
/>
@@ -153,10 +157,28 @@ export default function CreateGroup(props) {
assignmentPoints,
selectedCategory,
latePenalty: parseInt(latePenalty),
+ option: selectedOption
}}
setDisplay={setDisplayCustomChallenge}
/>
);
+ } else if(displayDynamicLab) {
+ return (
+
+ );
}
return (
@@ -408,12 +430,16 @@ export default function CreateGroup(props) {
setSelectedOption('dynamicLab')}
+ className={`cursor-pointer bg-neutral-800 px-2 py-2 text-center ${
+ selectedOption === 'dynamicLab'
+ ? 'border-2 border-blue-600'
+ : 'border-2 border-neutral-800 hover:border-neutral-900 hover:bg-neutral-900'
+ }`}
+ onClick={() => setSelectedOption('dynamicLab')}
>
- Dynamic Lab (Coming Soon)
+ Dynamic Lab
Create a simulated Cybersecurity environent graded by AI{' '}
diff --git a/src/components/home/SecondaryFeatures.jsx b/src/components/home/SecondaryFeatures.jsx
index fba12c4c..1719937d 100644
--- a/src/components/home/SecondaryFeatures.jsx
+++ b/src/components/home/SecondaryFeatures.jsx
@@ -1,6 +1,6 @@
'use client'
-import { useId } from 'react'
+import { useEffect, useId, useRef, useState } from 'react'
import Image from 'next/image'
import { Tab } from '@headlessui/react'
import clsx from 'clsx'
@@ -48,18 +48,18 @@ const features = [
'Review recorded student lab sessions.',
description:
'We automatically records lab sessions, so you can review them later.',
- image: '../../videoproof.mp4',
- icon: function InventoryIcon() {
+ image: '../../videoproof.mp4',
+ icon: function InventoryIcon() {
return (
<>
-
+
>
)
@@ -71,19 +71,19 @@ const features = [
'Cloud machines streamed to the browser.',
description:
'Easily access cloud machines from your browser, no need to install anything.',
- image: '../../terminalproof.mp4',
- icon: function ContactsIcon() {
+ image: '../../terminalproof.mp4',
+ icon: function ContactsIcon() {
return (
<>
-
+
+
-
>
)
},
@@ -123,32 +123,43 @@ function Feature({ feature, isActive, className, ...props }) {
}
function FeaturesMobile() {
+ let observer = useRef(null);
+
+ useEffect(() => {
+ if (!observer.current) {
+ observer.current = new IntersectionObserver((entries) => {
+ if (entries[0].intersectionRatio > 0.4) {
+ entries[0].target.play();
+ } else {
+ entries[0].target.pause();
+ }
+ }, { threshold: [0, 0.5] });
+ }
+ }, []);
+
return (
{features.map((feature) => (
-
-
-
-
+ muted
+ ref={(el) => setTimeout(() => { el && observer.current.observe(el) })}
+
+ autoSave='true'
+ loop
+ onLoadedMetadata={(e) => {
+ e.target.currentTime = 2; // Skip first two seconds
+ }}
+ >
+
+
+
))}
@@ -156,69 +167,126 @@ function FeaturesMobile() {
}
function FeaturesDesktop() {
+ const observer = useRef(null);
+ const selectedVideo = useRef(null);
+ const previousVideo = useRef(null);
+ const [selectedIndex, _setSelectedIndex] = useState(0);
+ const selectedIndexRef = useRef(selectedIndex);
+ const setSelectedIndex = (index) => {
+ selectedIndexRef.current = index;
+ _setSelectedIndex(index);
+ };
+
+ useEffect(() => {
+ if (!observer.current) {
+ observer.current = new IntersectionObserver((entries) => {
+ if (entries[0].intersectionRatio > 0.4) {
+ selectedVideo.current?.play();
+ } else {
+ selectedVideo.current?.pause();
+ }
+ }, { threshold: [0, 0.5] });
+ }
+ }, []);
+
+ const restartVideo = () => {
+ if (typeof window === "undefined") {
+ return;
+ }
+ const vid = document.getElementById("video-" + selectedIndexRef.current);
+ if (!vid || previousVideo.current == vid) {
+ return;
+ }
+ selectedVideo.current = vid;
+ vid.currentTime = 0;
+ if (previousVideo.current !== null) {
+ vid.play();
+ previousVideo.current.pause();
+ previousVideo.current.currentTIme = 0;
+ }
+
+ previousVideo.current = vid;
+ };
+
+ const onVideoEnd = () => {
+ setSelectedIndex((selectedIndex + 1) % features.length);
+ restartVideo();
+ }
+
return (
-
- {({ selectedIndex }) => (
- <>
-
- {features.map((feature, featureIndex) => (
-
-
- {feature.name}
-
- ),
- }}
- isActive={featureIndex === selectedIndex}
- className="relative"
- />
- ))}
-
-
-
+
setTimeout(() => el && observer.current?.observe(el) || restartVideo("video-0"))}
+ className="hidden lg:mt-20 lg:block"
+ onChange={(index) => {
+ setSelectedIndex(index);
+ restartVideo();
+ }}
+ selectedIndex={selectedIndex}
+ >
+ {() => {
+ return (
+ <>
+
{features.map((feature, featureIndex) => (
-
-
-
-
-
+ feature={{
+ ...feature,
+ name: (
+
+
+ {feature.name}
+
+ ),
+ }}
+ isActive={selectedIndex === featureIndex}
+ className="relative"
+ />
+ ))}
+
+
+
+ {features.map((feature, featureIndex) => (
+
+
+
-
-
- ))}
-
-
-
- >
- )}
+
+
+
+
+ ))}
+
+
+
+ >
+ )
+ }}
)
}
@@ -235,11 +303,11 @@ export function SecondaryFeatures() {
We're the best platform for teaching cybersecurity.
-
+
-
-
+
+
diff --git a/src/pages/assignments/student/[code].jsx b/src/pages/assignments/student/[code].jsx
index 1139192e..5f06d29e 100644
--- a/src/pages/assignments/student/[code].jsx
+++ b/src/pages/assignments/student/[code].jsx
@@ -52,9 +52,11 @@ export default function Slug() {
const [terminalPopup, setTerminalPopup] = useState(false);
const [loadingMessage, setLoadingMessage] = useState('');
const [isStudent, setIsStudent] = useState(true);
-
const [submissionId, setSubmissionId] = useState(null);
+ const [isDynamicLab, setIsDynmaicLab] = useState(false);
+ const [loadingLabStatus, setLoadingLabStatus] = useState(true);
+
function copy(tags) {
var copyText = document.getElementById(tags);
copyText.type = 'text';
@@ -106,7 +108,6 @@ export default function Slug() {
console.log(err);
}
};
-
const getAssignment = async () => {
const params = window.location.href.split('/');
@@ -122,6 +123,7 @@ export default function Slug() {
setSubmissionId(data.submissionId);
setChallengeHints(data.body.challenge.hints);
await getChallenge(data.body);
+ await checkIfDynamicLab(data.body.challenge.id);
} else {
console.log('You are not apart of this class');
router.push('/groups');
@@ -216,11 +218,24 @@ export default function Slug() {
}
}, []);
+ const checkIfDynamicLab = async (challengeId) => {
+ const url = `${baseUrl}/classroom-assignments/check-flag`;
+ const body = { flag: "", challengeId: challengeId};
+ const response = await request(url, "POST", body);
+ if(response && response.success) {
+ if(response.body.solved) {
+ setIsDynmaicLab(true);
+ }
+ }
+ setLoadingLabStatus(false);
+ };
+
const checkFlag = async () => {
const url = `${baseUrl}/classroom-assignments/check-flag`;
const body = { flag: flagInput, challengeId: challenge.id};
setLoading(true);
const response = await request(url, "POST", body);
+ console.log(response);
setLoading(false);
if(response && response.success) {
if(response.body.solved) {
@@ -275,7 +290,7 @@ export default function Slug() {
const url = `${baseUrl}/submission/create/${assignment.classroom.classCode}`;
const body = {
- solved: solved,
+ solved: isDynamicLab ? true : solved,
classroomId: assignment.classroomId,
assignmentId: params[5],
keyword: flagInput,
@@ -356,6 +371,7 @@ export default function Slug() {
function handleDataAsk() {
socketRef.current.emit('data_ask', { whoami: password });
}
+ console.log(isDynamicLab);
return (
<>
@@ -433,12 +449,15 @@ export default function Slug() {
content={challenge && challenge.content}
/>
-
- Submission Area
-
+ {
+ !loadingLabStatus && !isDynamicLab ? 'Submission Area' : 'Dynamic Lab'
+ }
+ {
+ !loadingLabStatus && !isDynamicLab && (
+
FLAG SUBMISSION
@@ -451,7 +470,7 @@ export default function Slug() {
+ )
+ }
HINTS
diff --git a/src/pages/assignments/teacher/[code].jsx b/src/pages/assignments/teacher/[code].jsx
index 0e564029..ac1a20f6 100644
--- a/src/pages/assignments/teacher/[code].jsx
+++ b/src/pages/assignments/teacher/[code].jsx
@@ -73,6 +73,9 @@ export default function id() {
const [code, setCode] = useState(0);
const [loadingMessage, setLoadingMessage] = useState('');
+ const [isDynamicLab, setIsDynmaicLab] = useState(false);
+ const [loadingLabStatus, setLoadingLabStatus] = useState(true);
+
const router = useRouter();
const parseDate = (dateString) => {
@@ -108,6 +111,7 @@ export default function id() {
setAssignment(data.body);
getChallenge(data.body);
await getSubmissions(data.body);
+ await checkIfDynamicLab(data.body.challenge.id);
} else {
console.log('You are not apart of this class');
router.push('/groups');
@@ -264,6 +268,18 @@ export default function id() {
router.replace(`/assignments/${assignment.id}/submissions/${id}?key=t`);
};
+ const checkIfDynamicLab = async (challengeId) => {
+ const url = `${baseUrl}/classroom-assignments/check-flag`;
+ const body = { flag: "", challengeId: challengeId};
+ const response = await request(url, "POST", body);
+ if(response && response.success) {
+ if(response.body.solved) {
+ setIsDynmaicLab(true);
+ }
+ }
+ setLoadingLabStatus(false);
+ };
+
const checkIfTerminalExists = async () => {
setFetchingTerminal(true);
const token = auth.currentUser.accessToken;
@@ -460,6 +476,9 @@ export default function id() {
+ {
+ !isDynamicLab && !loadingLabStatus && (
+
FLAG SUBMISSION
@@ -500,6 +519,10 @@ export default function id() {
)
)}
+
+ )
+ }
+
HINTS
{hints.map((hint, idx) => {
diff --git a/src/utils/request.js b/src/utils/request.js
index a431609e..59404822 100644
--- a/src/utils/request.js
+++ b/src/utils/request.js
@@ -1,7 +1,6 @@
const request = async (url, req_method, body) => {
try {
- //const token = getCookie();
let method = req_method.toUpperCase();
if(method === 'GET' || method === "DELETE") {
@@ -25,6 +24,13 @@ const request = async (url, req_method, body) => {
}
const response = await fetch(url, requestOptions);
const data = await response.json();
+
+ if(response.status === 401 && data.error) {
+ console.log("Unauthorized");
+ window.location.href = "/login";
+ return null;
+ }
+
return data;
} else {
return null;