parent
a2ca9bb7a4
commit
c5adde5509
@ -0,0 +1,49 @@
|
|||||||
|
# Ignore node_modules in the Website folder
|
||||||
|
# Compiled output
|
||||||
|
/dist
|
||||||
|
/tmp
|
||||||
|
/out-tsc
|
||||||
|
/bazel-out
|
||||||
|
|
||||||
|
# Node
|
||||||
|
CTF-Frontend/node_modules
|
||||||
|
CTF-Server/node_modules
|
||||||
|
Website/node_modules
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
.idea/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.history/*
|
||||||
|
|
||||||
|
# Miscellaneous
|
||||||
|
CTF-Frontend/.angular/cache
|
||||||
|
.sass-cache/
|
||||||
|
/connect.lock
|
||||||
|
/coverage
|
||||||
|
/libpeerconnection.log
|
||||||
|
testem.log
|
||||||
|
/typings
|
||||||
|
|
||||||
|
# System files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
#environment files
|
||||||
|
.env
|
||||||
|
|
||||||
|
#Vite
|
||||||
|
|
@ -0,0 +1,17 @@
|
|||||||
|
# Editor configuration, see https://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.ts]
|
||||||
|
quote_type = single
|
||||||
|
ij_typescript_use_double_quotes = false
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
max_line_length = off
|
||||||
|
trim_trailing_whitespace = false
|
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
|
||||||
|
"recommendations": ["angular.ng-template"]
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "ng serve",
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "npm: start",
|
||||||
|
"url": "http://localhost:4200/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ng test",
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "npm: test",
|
||||||
|
"url": "http://localhost:9876/debug.html"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "start",
|
||||||
|
"isBackground": true,
|
||||||
|
"problemMatcher": {
|
||||||
|
"owner": "typescript",
|
||||||
|
"pattern": "$tsc",
|
||||||
|
"background": {
|
||||||
|
"activeOnStart": true,
|
||||||
|
"beginsPattern": {
|
||||||
|
"regexp": "(.*?)"
|
||||||
|
},
|
||||||
|
"endsPattern": {
|
||||||
|
"regexp": "bundle generation complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "test",
|
||||||
|
"isBackground": true,
|
||||||
|
"problemMatcher": {
|
||||||
|
"owner": "typescript",
|
||||||
|
"pattern": "$tsc",
|
||||||
|
"background": {
|
||||||
|
"activeOnStart": true,
|
||||||
|
"beginsPattern": {
|
||||||
|
"regexp": "(.*?)"
|
||||||
|
},
|
||||||
|
"endsPattern": {
|
||||||
|
"regexp": "bundle generation complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
# CTFFrontend
|
||||||
|
|
||||||
|
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 19.2.3.
|
||||||
|
|
||||||
|
## Development server
|
||||||
|
|
||||||
|
To start a local development server, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng serve
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
|
||||||
|
|
||||||
|
## Code scaffolding
|
||||||
|
|
||||||
|
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng generate component component-name
|
||||||
|
```
|
||||||
|
|
||||||
|
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng generate --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To build the project run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng build
|
||||||
|
```
|
||||||
|
|
||||||
|
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running end-to-end tests
|
||||||
|
|
||||||
|
For end-to-end (e2e) testing, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
|
@ -0,0 +1,104 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"CTF-Frontend": {
|
||||||
|
"projectType": "application",
|
||||||
|
"schematics": {},
|
||||||
|
"root": "",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"prefix": "app",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-angular:application",
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist/ctf-frontend",
|
||||||
|
"index": "src/index.html",
|
||||||
|
"browser": "src/main.ts",
|
||||||
|
"polyfills": [
|
||||||
|
"zone.js"
|
||||||
|
],
|
||||||
|
"tsConfig": "tsconfig.app.json",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "public"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"src/styles.css",
|
||||||
|
"node_modules/@xterm/xterm/css/xterm.css"
|
||||||
|
],
|
||||||
|
"scripts": []
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "initial",
|
||||||
|
"maximumWarning": "500kB",
|
||||||
|
"maximumError": "1MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "4kB",
|
||||||
|
"maximumError": "8kB"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputHashing": "all"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"optimization": false,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production"
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"options":{
|
||||||
|
"proxyConfig": "./proxy.conf.json"
|
||||||
|
},
|
||||||
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"buildTarget": "CTF-Frontend:build:production"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"buildTarget": "CTF-Frontend:build:development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development"
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"builder": "@angular-devkit/build-angular:extract-i18n"
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular-devkit/build-angular:karma",
|
||||||
|
"options": {
|
||||||
|
"polyfills": [
|
||||||
|
"zone.js",
|
||||||
|
"zone.js/testing"
|
||||||
|
],
|
||||||
|
"tsConfig": "tsconfig.spec.json",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "public"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"src/styles.css",
|
||||||
|
"node_modules/@xterm/xterm/css/xterm.css"
|
||||||
|
],
|
||||||
|
"scripts": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cli": {
|
||||||
|
"analytics": false
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "ctf-frontend",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"start": "ng serve",
|
||||||
|
"build": "ng build",
|
||||||
|
"watch": "ng build --watch --configuration development",
|
||||||
|
"test": "ng test"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/common": "^19.2.0",
|
||||||
|
"@angular/compiler": "^19.2.0",
|
||||||
|
"@angular/core": "^19.2.0",
|
||||||
|
"@angular/forms": "^19.2.0",
|
||||||
|
"@angular/platform-browser": "^19.2.0",
|
||||||
|
"@angular/platform-browser-dynamic": "^19.2.0",
|
||||||
|
"@angular/router": "^19.2.0",
|
||||||
|
"@xterm/addon-attach": "^0.11.0",
|
||||||
|
"rxjs": "~7.8.0",
|
||||||
|
"socket.io-client": "^4.8.1",
|
||||||
|
"tslib": "^2.3.0",
|
||||||
|
"xterm": "^5.3.0",
|
||||||
|
"zone.js": "~0.15.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular-devkit/build-angular": "^19.2.3",
|
||||||
|
"@angular/cli": "^19.2.3",
|
||||||
|
"@angular/compiler-cli": "^19.2.0",
|
||||||
|
"@types/jasmine": "~5.1.0",
|
||||||
|
"jasmine-core": "~5.6.0",
|
||||||
|
"karma": "~6.4.0",
|
||||||
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
|
"karma-coverage": "~2.2.0",
|
||||||
|
"karma-jasmine": "~5.1.0",
|
||||||
|
"karma-jasmine-html-reporter": "~2.1.0",
|
||||||
|
"typescript": "~5.7.2"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"/api": {
|
||||||
|
"target": "http://localhost:3000",
|
||||||
|
"secure": false,
|
||||||
|
"changeOrigin": true,
|
||||||
|
"pathRewrite": {
|
||||||
|
"^/api": ""
|
||||||
|
},
|
||||||
|
"logLevel": "debug"
|
||||||
|
},
|
||||||
|
"/api/socket.io": {
|
||||||
|
"target": "http://localhost:300",
|
||||||
|
"secure": false,
|
||||||
|
"ws": true,
|
||||||
|
"changeOrigin": true,
|
||||||
|
"logLevel": "debug"
|
||||||
|
},
|
||||||
|
"/ws": {
|
||||||
|
"target": "ws://localhost:3000",
|
||||||
|
"ws": true,
|
||||||
|
"secure": false,
|
||||||
|
"changeOrigin": true
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,312 @@
|
|||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { Contest } from '../models/contest.model';
|
||||||
|
import { Flag } from '../models/flag.model';
|
||||||
|
import { Renderer2, ElementRef } from '@angular/core';
|
||||||
|
import { getEmail, updateContainer, gotoPage } from './Helpers';
|
||||||
|
import { TerminalService } from './terminal.service';
|
||||||
|
import { SocketIOService } from '../notifications/socket-io.service';
|
||||||
|
import { Socket } from 'socket.io-client';
|
||||||
|
|
||||||
|
|
||||||
|
export async function loadPage(renderer: Renderer2, el: ElementRef, ts: any): Promise<void> {
|
||||||
|
const isPractice = window.location.href.includes("Prac");
|
||||||
|
const taskbar: HTMLElement = document.getElementById('Taskbar')!;
|
||||||
|
// basics for use in multiple methods
|
||||||
|
const elements = {
|
||||||
|
flagName: el.nativeElement.querySelector('#Flag_Name') as HTMLElement,
|
||||||
|
desc: el.nativeElement.querySelector('#Desc') as HTMLElement,
|
||||||
|
contName: el.nativeElement.querySelector('#ContHeader') as HTMLElement,
|
||||||
|
contestName: el.nativeElement.querySelector('#ContName') as HTMLElement,
|
||||||
|
contDesc: el.nativeElement.querySelector('#ContDesc') as HTMLElement,
|
||||||
|
hintDesc: el.nativeElement.querySelector('#Hint_Desc') as HTMLElement,
|
||||||
|
HintButts: el.nativeElement.querySelector('#Hint_Butts') as HTMLElement
|
||||||
|
}
|
||||||
|
await LoadContest(el, elements, ts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the params to see if admin or not
|
||||||
|
export function getAdmin(): string | null {
|
||||||
|
const url: string = window.location.search;
|
||||||
|
const params = new URLSearchParams(url);
|
||||||
|
return params.get('admin');
|
||||||
|
}
|
||||||
|
|
||||||
|
// load the current active contest
|
||||||
|
async function LoadContest(el: ElementRef, elements: any, ts: any) {
|
||||||
|
const contestID = new URLSearchParams(window.location.search).get('contestID');
|
||||||
|
console.log("Contest ID:", contestID);
|
||||||
|
let contest: Contest | undefined;
|
||||||
|
if(contestID){
|
||||||
|
const data = { contestID: contestID};
|
||||||
|
const res = await fetch('api/contests/getContestByID', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
if(res.ok) {
|
||||||
|
contest = await res.json();
|
||||||
|
console.log("contest", contest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const data = { email: getEmail() };
|
||||||
|
const res = await fetch('api/contests/getActiveContest', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type' : 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
contest = await res.json();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const msg = el.nativeElement.querySelector('#DefaultMessage') as HTMLElement;
|
||||||
|
if (!contest) {
|
||||||
|
alert('No Contest is Active');
|
||||||
|
msg.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg.classList.add('hidden');
|
||||||
|
console.log('Contest:', contest);
|
||||||
|
elements.contName.innerHTML = contest.Name + ' Contest';
|
||||||
|
await LoadElements(contest, el, elements, ts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// load all the dynamic elements
|
||||||
|
async function LoadElements(contest: Contest, el: ElementRef, elements: any, ts: any) {
|
||||||
|
const data = { contest: contest.ContestID };
|
||||||
|
const res = await fetch('api/flags/getAllFlagsFromContest', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
if(!res.ok){
|
||||||
|
console.error('Error fetching flags for contest');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const flagdata: Flag[] = await res.json();
|
||||||
|
console.log("Fetched Flags:", flagdata);
|
||||||
|
const UL = el.nativeElement.querySelector('#FlagList') as HTMLElement;
|
||||||
|
if(UL)
|
||||||
|
UL.innerHTML = '';
|
||||||
|
if(flagdata.length === 0){
|
||||||
|
console.warn("no flags found for contest");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(elements.contName) elements.contName.style.display = 'none';
|
||||||
|
if(elements.contDesc) elements.contDesc.style.display = 'none';
|
||||||
|
|
||||||
|
const tmp = await getActiveFlag(getEmail(), contest.ContestID, el, elements);
|
||||||
|
if(tmp.ActiveFlag === 'ubuntu'){
|
||||||
|
elements.contName.innerHTML = contest.Name;
|
||||||
|
elements.contDesc.innerHTML = contest.Description;
|
||||||
|
elements.contName.style.display = 'block';
|
||||||
|
elements.contDesc.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitFlag = el.nativeElement.querySelector('#Submit_Flag_Stuff') as HTMLElement;
|
||||||
|
const hintStuff = el.nativeElement.querySelector('#Hint_Stuff') as HTMLElement;
|
||||||
|
// add the contest name to the list
|
||||||
|
elements.flagName.textContent = '';
|
||||||
|
elements.desc.textContent = '';
|
||||||
|
if(elements.hintButts) elements.hintButts.innerHTML = '';
|
||||||
|
if(submitFlag) submitFlag.classList.add('hidden');
|
||||||
|
hintStuff.classList.add('hidden');
|
||||||
|
|
||||||
|
flagdata.forEach(flag => {
|
||||||
|
|
||||||
|
console.log(flag);
|
||||||
|
const li = document.createElement('li');
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.textContent = flag.Name;
|
||||||
|
li.onclick = () => setNewFlag(flag, el, elements, ts);
|
||||||
|
|
||||||
|
// add to list
|
||||||
|
li.appendChild(a);
|
||||||
|
UL.appendChild(li);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the active flag of a user
|
||||||
|
async function getActiveFlag(email: string, contest: number, el: ElementRef, elements: any) {
|
||||||
|
const data = { email: email };
|
||||||
|
const res = await fetch('api/flags/getActiveFlag', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type' : 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
if (res.ok) { // get the active flag onto html
|
||||||
|
const ret = await res.json();
|
||||||
|
|
||||||
|
// go through all the flags gotten and make sure it's the right one for this contest
|
||||||
|
for (let i=0; i < ret.length; i++) {
|
||||||
|
if (contest === ret[i].ContestID) setUpFlag(ret[i], el, elements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { ActiveFlag: 'ubuntu' };
|
||||||
|
}
|
||||||
|
let currentFlagID: number | null = null;
|
||||||
|
|
||||||
|
// get the new flag the user clicked on
|
||||||
|
export async function setNewFlag(flag: Flag, el: ElementRef, elements: any, ts: any) {
|
||||||
|
currentFlagID = flag.FlagID;
|
||||||
|
console.log("The new flagID is", currentFlagID);
|
||||||
|
const submitFlagButt = el.nativeElement.querySelector('#Submit_Flag_Stuff') as HTMLElement;
|
||||||
|
const hintstuff = el.nativeElement.querySelector('#Hint_Stuff') as HTMLElement;
|
||||||
|
const resultelement = el.nativeElement.querySelector('#Submission_Result') as HTMLElement;
|
||||||
|
const input = el.nativeElement.querySelector('#Submit_Flag_Stuff input[type="text"]') as HTMLInputElement;
|
||||||
|
|
||||||
|
if(submitFlagButt) {
|
||||||
|
submitFlagButt.classList.remove('hidden');
|
||||||
|
|
||||||
|
// clear the result and the input box
|
||||||
|
if(resultelement){
|
||||||
|
resultelement.textContent = '';
|
||||||
|
resultelement.classList.remove('Incorrect', 'Correct');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(input){
|
||||||
|
input.value = '';
|
||||||
|
input.placeholder = 'NMUCTF${FLAG}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(hintstuff) hintstuff.classList.remove('hidden');
|
||||||
|
|
||||||
|
const email = getEmail();
|
||||||
|
const data = { FlagImage: flag.Image, email: email };
|
||||||
|
const res = await fetch('api/setNewActiveFlag', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type' : 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
if (res.ok) { // get the flag details onto html
|
||||||
|
const ret = await res.json();
|
||||||
|
setUpFlag(flag, el, elements);
|
||||||
|
await updateContainer(email);
|
||||||
|
console.log('Setting new Flag');
|
||||||
|
ts.ClearAndSetTerminal(); // clears the terminal and replaces with the new one for the specific flag
|
||||||
|
}
|
||||||
|
else
|
||||||
|
console.error('Failed to set new flag.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the HTML to the correct flag
|
||||||
|
function setUpFlag(flag: Flag, el: ElementRef, elements: any) {
|
||||||
|
|
||||||
|
// set up the basics
|
||||||
|
elements.flagName.innerHTML = flag.Name;
|
||||||
|
elements.desc.innerHTML = flag.Description;
|
||||||
|
if(elements.HintButts) elements.HintButts.innerHTML = '';
|
||||||
|
if(elements.hintDesc) elements.hintDesc.innerHTML = '';
|
||||||
|
|
||||||
|
// set up the hints buttons
|
||||||
|
if (flag.Hint1) {
|
||||||
|
const hint1 = document.createElement('button');
|
||||||
|
hint1.textContent = 'Hint 1';
|
||||||
|
hint1.addEventListener('click', function() {
|
||||||
|
elements.hintDesc.innerHTML = flag.Hint1;
|
||||||
|
});
|
||||||
|
elements.HintButts.appendChild(hint1);
|
||||||
|
}
|
||||||
|
if (flag.Hint2) {
|
||||||
|
const hint2 = document.createElement('button');
|
||||||
|
hint2.textContent = 'Hint 2';
|
||||||
|
hint2.addEventListener('click', function() {
|
||||||
|
elements.hintDesc.innerHTML = flag.Hint2;
|
||||||
|
});
|
||||||
|
elements.HintButts.appendChild(hint2);
|
||||||
|
}
|
||||||
|
if (flag.Hint3) {
|
||||||
|
const hint3 = document.createElement('button');
|
||||||
|
hint3.textContent = 'Hint 3';
|
||||||
|
hint3.addEventListener('click', function() {
|
||||||
|
elements.hintDesc.innerHTML = flag.Hint3;
|
||||||
|
});
|
||||||
|
elements.HintButts.appendChild(hint3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if theres at least one hint have a placeholder for user
|
||||||
|
if (flag.Hint1 || flag.Hint2 || flag.Hint3) {
|
||||||
|
elements.hintDesc.textContent = 'click to see a Hint';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
elements.hintDesc.textContent = 'there are no Hints at all good luck :('
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*async function stopContainer(){
|
||||||
|
if(container){
|
||||||
|
await container.kill();
|
||||||
|
await container.remove({force:true});
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
function getCurrentFlagID(){
|
||||||
|
return currentFlagID;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleSubmission(el: ElementRef, socket: SocketIOService){
|
||||||
|
const url = window.location.href;
|
||||||
|
if(url.includes("Prac")){
|
||||||
|
checkSubmission(true, el, socket);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
checkSubmission(false, el, socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkSubmission(isPractice: boolean, el: ElementRef, socket: SocketIOService){
|
||||||
|
const submittedFlag = el.nativeElement.querySelector('#Submit_Flag input[type="text"]').value.trim();
|
||||||
|
const email = getEmail();
|
||||||
|
const flagID = getCurrentFlagID();
|
||||||
|
console.log("FLAG ID IS", flagID);
|
||||||
|
const flagName = el.nativeElement.querySelector('#Flag_Name').textContent;
|
||||||
|
const resultelement = el.nativeElement.querySelector('#Submission_Result');
|
||||||
|
if(!submittedFlag){
|
||||||
|
resultelement.textContent = "Please enter a flag.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let res, result;
|
||||||
|
if(!isPractice){
|
||||||
|
res = await fetch('api/checkFlagSubmission', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({ email, flagID, submittedFlag })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
res = await fetch('api/checkPracSubmission', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({ email, flagID, submittedFlag })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
result = await res.json();
|
||||||
|
|
||||||
|
if(result.correct){
|
||||||
|
resultelement.classList.add('Correct');
|
||||||
|
resultelement.classList.remove('Incorrect');
|
||||||
|
resultelement.textContent = 'Correct! :)';
|
||||||
|
|
||||||
|
console.log("SENDING NOTIF", email, flagName);
|
||||||
|
/*socket.emit('submission-notification', {
|
||||||
|
email, flagName, time: new Date().toISOString()
|
||||||
|
});*/
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
resultelement.classList.add('Incorrect');
|
||||||
|
resultelement.classList.remove('Correct');
|
||||||
|
resultelement.textContent = 'Incorrect! :(';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,207 @@
|
|||||||
|
/*
|
||||||
|
Jordan Latimer
|
||||||
|
Alex Miller
|
||||||
|
|
||||||
|
Helper functions used by a variety of javascript files
|
||||||
|
*/
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { Contest } from '../models/contest.model';
|
||||||
|
import { Flag } from '../models/flag.model';
|
||||||
|
import { Terminal } from 'xterm';
|
||||||
|
import { AttachAddon } from '@xterm/addon-attach';
|
||||||
|
import { PopulateUserTables } from './User-pfp';
|
||||||
|
let term: Terminal | undefined;
|
||||||
|
// get the email of the client
|
||||||
|
export function getEmail(): string{
|
||||||
|
return sessionStorage.getItem('email') || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// got to a specific page
|
||||||
|
export function gotoPage(router: Router, page: string): void {
|
||||||
|
router.navigate([page]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logOut(router: Router){
|
||||||
|
router.navigate(['']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// popup window for a specific page with the email
|
||||||
|
export function Popup(site: string): void {
|
||||||
|
|
||||||
|
const width = 500;
|
||||||
|
const height = 500;
|
||||||
|
|
||||||
|
// get the right placement and size for any screen
|
||||||
|
const x = screenX + (window.screen.width / 2) - (width / 2);
|
||||||
|
const y = screenY + (window.screen.height / 2) - (height / 2);
|
||||||
|
// add the features and the email inside the URL and open the window
|
||||||
|
const features = `width=${width},height=${height},left=${x},top=${y}`;
|
||||||
|
window.open(site,'popup',features);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get all the table contents and add them in the respective table
|
||||||
|
export async function PopulateTable(page: number, InsertIntoTable: (data: Contest[]) => void): Promise<void> {
|
||||||
|
const data = { email: getEmail() };
|
||||||
|
const res = await fetch('api/contests/getContests', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type' : 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
const list = await res.json();
|
||||||
|
if (page === 0) InsertIntoTable(list);
|
||||||
|
else PopulateUserTables(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getContests(): Promise<Contest[]> {
|
||||||
|
const data = { email: getEmail() };
|
||||||
|
const res = await fetch('api/contests/getContests', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type' : 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
const list: Contest[] = await res.json();
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error('Failed to fetch contests');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const flagItems = document.querySelectorAll('#Flag_Sidebar li');
|
||||||
|
flagItems.forEach(item => {
|
||||||
|
item.addEventListener('click', function() {
|
||||||
|
flagItems.forEach(f => f.classList.remove('selected-flag'));
|
||||||
|
item.classList.add('selected-flag');
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function InsertIntoTable(data: Contest[], router: Router): Promise<void> {
|
||||||
|
// Past Contest Table
|
||||||
|
const PCT = document.querySelector('#Past_Contests tbody') as HTMLTableSectionElement | null;
|
||||||
|
// Active Contest Table
|
||||||
|
const ACT = document.querySelector('#Current_Contest tbody') as HTMLTableSectionElement | null;
|
||||||
|
if(!PCT || !ACT)
|
||||||
|
return;
|
||||||
|
PCT.innerHTML = '';
|
||||||
|
ACT.innerHTML = '';
|
||||||
|
|
||||||
|
// list of flags
|
||||||
|
const res2 = await fetch('api/flags/getAllFlags');
|
||||||
|
const flaglist: Flag[] = await res2.json();
|
||||||
|
|
||||||
|
const email = getEmail();
|
||||||
|
const res3 = await fetch('api/contests/getActiveContest', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({email})
|
||||||
|
})
|
||||||
|
let activeContest = await res3.json().catch(() => null);
|
||||||
|
if(activeContest && activeContest.error === 'NO CONTEST FOUND') activeContest = null;
|
||||||
|
data.forEach((contest: Contest) => {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
const name = document.createElement('td');
|
||||||
|
const flags = document.createElement('td');
|
||||||
|
const leader = document.createElement('td');
|
||||||
|
|
||||||
|
name.textContent = contest.Name;
|
||||||
|
|
||||||
|
// get the amount of flags in the table
|
||||||
|
let i = 0;
|
||||||
|
flaglist.forEach(flag => {
|
||||||
|
if (flag.ContestID === contest.ContestID) i++;
|
||||||
|
});
|
||||||
|
flags.textContent = i.toString();
|
||||||
|
|
||||||
|
// get the button for the leaderboard
|
||||||
|
const leaderbutton = document.createElement('button');
|
||||||
|
leaderbutton.textContent = 'view';
|
||||||
|
leaderbutton.addEventListener('click', function() {
|
||||||
|
// Popup('leaderboard.html' ,getEmail(), 'ContestName=' + contest.Name); Uncomment when we finish leaderboard implementation
|
||||||
|
});
|
||||||
|
leader.appendChild(leaderbutton);
|
||||||
|
|
||||||
|
row.appendChild(name);
|
||||||
|
row.appendChild(flags);
|
||||||
|
|
||||||
|
if (contest.IsActive === 1) {
|
||||||
|
const button = document.createElement('td');
|
||||||
|
const JoinButton = document.createElement('button');
|
||||||
|
JoinButton.textContent = 'Join';
|
||||||
|
JoinButton.addEventListener('click', function() {
|
||||||
|
router.navigate(['/contest-page']);
|
||||||
|
});
|
||||||
|
|
||||||
|
button.appendChild(JoinButton);
|
||||||
|
row.appendChild(button);
|
||||||
|
ACT.appendChild(row);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
row.appendChild(leader);
|
||||||
|
if(!activeContest){
|
||||||
|
const pracButtCell = document.createElement('td');
|
||||||
|
const pracButt = document.createElement('button');
|
||||||
|
pracButt.textContent = 'Practice';
|
||||||
|
pracButt.addEventListener('click', function() {
|
||||||
|
console.log("contestID", contest.ContestID);
|
||||||
|
router.navigate(['/contest-page'], { queryParams: { email: email, contestID: contest.ContestID, Prac: true } });
|
||||||
|
});
|
||||||
|
pracButtCell.appendChild(pracButt);
|
||||||
|
row.appendChild(pracButtCell);
|
||||||
|
}
|
||||||
|
PCT.appendChild(row);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(!activeContest){
|
||||||
|
const pracRow = document.querySelector('#Past_Contests thead tr');
|
||||||
|
if(pracRow){
|
||||||
|
const pracRowCell = document.createElement('th');
|
||||||
|
pracRowCell.textContent = 'Practice';
|
||||||
|
pracRow.appendChild(pracRowCell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the container
|
||||||
|
export async function updateContainer(email: string){
|
||||||
|
const res = await fetch(`api/updateContainer/${email}`, { method: 'POST'});
|
||||||
|
|
||||||
|
if(res.ok){
|
||||||
|
console.log('Container updated successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
console.log('Failed to update container');
|
||||||
|
}
|
||||||
|
|
||||||
|
// reattach the terminal web socket
|
||||||
|
export async function reattachTerminal(term: Terminal){
|
||||||
|
console.log('reattaching termnial');
|
||||||
|
|
||||||
|
if(term) {
|
||||||
|
term.dispose();
|
||||||
|
}
|
||||||
|
term = new Terminal();
|
||||||
|
const container = document.getElementById('Linux_Shell');
|
||||||
|
if(container && term) {
|
||||||
|
term.open(container);
|
||||||
|
}
|
||||||
|
const socket = new WebSocket('/ws');
|
||||||
|
socket.onopen = () => {
|
||||||
|
socket.send(JSON.stringify(getEmail()));
|
||||||
|
console.log('Websocket opened');
|
||||||
|
}
|
||||||
|
const attachAddon = new AttachAddon(socket);
|
||||||
|
term.loadAddon(attachAddon);
|
||||||
|
term.focus();
|
||||||
|
socket.onclose = () => {
|
||||||
|
console.log("socket closed");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
Alex Miller
|
||||||
|
Jordan Latimer
|
||||||
|
|
||||||
|
Client code for Profile Page for Users
|
||||||
|
*/
|
||||||
|
import { Contest } from '../models/contest.model';
|
||||||
|
import { Flag } from '../models/flag.model';
|
||||||
|
import { User } from '../models/user.model';
|
||||||
|
import { getEmail, Popup, InsertIntoTable, getContests } from './Helpers';
|
||||||
|
|
||||||
|
// sets of names to pull randomly from for random names
|
||||||
|
let Adjectives = [
|
||||||
|
'Aspiring', 'Strict','Electronic','Talented','Gifted','Wonderful',
|
||||||
|
'Amazing','Inspiring','First','Second','Third','Cowardly','True',
|
||||||
|
'Coordinated','Sneaky','Concerned','Courageous','Nice','Mean','Sharp',
|
||||||
|
'Mysterious','Magical','Tame','Well-off','Overconfident','Cautious',
|
||||||
|
'Wandering','Wrathful','Important','Jolly','Mistaken','Admirable'
|
||||||
|
]
|
||||||
|
let Animals = [
|
||||||
|
'Fox','Turtle','Penguin','Robin','Shark','Giraffe','Kangaroo','Cat','Dog',
|
||||||
|
'Fish','Bear','Lion','Hippo','Duck','Octopus','Elephant','Bunny','Rabbit',
|
||||||
|
'Mouse','Pig','Cow','Chicken','Rooster','Ladybug','Firefly','Mosquito','Snail',
|
||||||
|
'Koala','Platypus','Woodchuck','Polarbear','Rat','Bull','Newt','Deer','Fawn',
|
||||||
|
'Cheetah','Leopard','Beaver','Llama','Guineapig','Squirrel','Eagle','Moose'
|
||||||
|
]
|
||||||
|
let Colors = [
|
||||||
|
'Green', 'Blue','Aqua','Red','Purple','Pink','Gray','Black','Brown','Teal',
|
||||||
|
'Limegreen','White','Yellow','Maroon','Violet', 'Coral','Emerald','Orange',
|
||||||
|
'Darkgreen','Navyblue','Cyan','Crimson','Bronze','Gold','Silver','Amber',
|
||||||
|
'Turquoise','Indigo','Fuchsia','Magenta','Platinum','Tan','Jade','Ruby'
|
||||||
|
]
|
||||||
|
let Things = [
|
||||||
|
'Chair','Desk','Lamp','Keys','Bottle','Wall','Window','Barn','House','Door',
|
||||||
|
'Outlet','TV','Screen','Road','Lightbulb','Sidewalk','Keyboard','Paper','Plastic',
|
||||||
|
'Remote','Phone','Computer','Can','Table','Backpack','Fan','Carpet','Pen','Pencil',
|
||||||
|
'Notebook','Clock','Tree','Pants','Shorts','Flannel','Jacket','Apple','Banana','Watch',
|
||||||
|
'Apricot','Socks','Pizza','Pasty','Car','Airplane','Bus','Scissors','Rock','Water'
|
||||||
|
]
|
||||||
|
let Name = '';
|
||||||
|
|
||||||
|
// on load, populate tables and set the users name inside the input field
|
||||||
|
export async function loadTable(): Promise<string> {
|
||||||
|
let contests = await getContests();
|
||||||
|
PopulateUserTables(contests);
|
||||||
|
const data = { email: getEmail() };
|
||||||
|
const res = await fetch('api/users/getUsername', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type' : 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
})
|
||||||
|
if (res.ok) {
|
||||||
|
const ret = await res.json();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate both user tables
|
||||||
|
export async function PopulateUserTables(PCData: Contest[]): Promise<void> {
|
||||||
|
const TB = document.querySelector('#Past_Contests tbody') as HTMLElement;
|
||||||
|
TB.innerHTML = "";
|
||||||
|
|
||||||
|
// get all of the flags
|
||||||
|
const res = await fetch('api/flags/getAllFlags');
|
||||||
|
const PCData2: Flag[] = await res.json();
|
||||||
|
let totalflagamount = 0;
|
||||||
|
|
||||||
|
// insert the amount of flags per contests
|
||||||
|
PCData.forEach(contest => {
|
||||||
|
let flagamount = 0;
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
const name = document.createElement('td');
|
||||||
|
name.textContent = contest.Name;
|
||||||
|
const flags = document.createElement('td');
|
||||||
|
PCData2.forEach(flag => {
|
||||||
|
if (flag.ContestID === contest.ContestID) flagamount++;
|
||||||
|
});
|
||||||
|
flags.textContent = flagamount.toString();
|
||||||
|
totalflagamount += flagamount;
|
||||||
|
|
||||||
|
const leader = document.createElement('td');
|
||||||
|
|
||||||
|
// get the button for the leaderboard
|
||||||
|
const leaderbutton = document.createElement('button');
|
||||||
|
leaderbutton.textContent = 'view';
|
||||||
|
leaderbutton.setAttribute('id','ViewLeaderboard');
|
||||||
|
leaderbutton.addEventListener('click', function() {
|
||||||
|
//Popup('leaderboard.html' ,getEmail(), 'ContestName=' + contest.Name); Uncomment when leaderboard is implemented
|
||||||
|
});
|
||||||
|
leader.appendChild(leaderbutton);
|
||||||
|
|
||||||
|
|
||||||
|
// add to the table
|
||||||
|
row.appendChild(name);
|
||||||
|
row.appendChild(flags);
|
||||||
|
row.appendChild(leader);
|
||||||
|
|
||||||
|
TB.appendChild(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
// get the user information
|
||||||
|
const data = { email: getEmail() };
|
||||||
|
const res2 = await fetch('api/users/getUser', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type' : 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
if (res2.ok) {
|
||||||
|
const Rdata = await res2.json();
|
||||||
|
|
||||||
|
const TB2 = document.querySelector('#Record tbody') as HTMLTableSectionElement;
|
||||||
|
if(TB2)
|
||||||
|
TB2.innerHTML = '';
|
||||||
|
|
||||||
|
// create the table for the users record, flags completed, and total flags
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
const Total = document.createElement('td');
|
||||||
|
const Completed = document.createElement('td');
|
||||||
|
const record = document.createElement('td');
|
||||||
|
Total.textContent = totalflagamount.toString();
|
||||||
|
Completed.textContent = Rdata.Flags;
|
||||||
|
record.textContent = Completed.innerHTML + ' / ' + Total.innerHTML;
|
||||||
|
|
||||||
|
row.appendChild(Completed);
|
||||||
|
row.appendChild(Total);
|
||||||
|
row.appendChild(record);
|
||||||
|
|
||||||
|
console.log('TB2:', TB2);
|
||||||
|
console.log('row:', row);
|
||||||
|
TB2.appendChild(row);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error('Error getting data for Flag Users');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get a new name from name array
|
||||||
|
export function getNewName(): string {
|
||||||
|
|
||||||
|
let first = '';
|
||||||
|
let second = '';
|
||||||
|
|
||||||
|
// get what arrays to be used
|
||||||
|
let firstarr = Math.floor(Math.random() * 2);
|
||||||
|
let secondarr = Math.floor(Math.random() * 2);
|
||||||
|
|
||||||
|
// get the random array items based on the arrays used
|
||||||
|
if (firstarr === 0) first = Adjectives[Math.floor(Math.random() * Adjectives.length)];
|
||||||
|
else first = Colors[Math.floor(Math.random() * Colors.length)];
|
||||||
|
|
||||||
|
if (secondarr === 0) second = Animals[Math.floor(Math.random() * Animals.length)];
|
||||||
|
else second = Things[Math.floor(Math.random() * Things.length)];
|
||||||
|
|
||||||
|
// set up the name
|
||||||
|
Name = '';
|
||||||
|
Name += first + second;
|
||||||
|
|
||||||
|
// determine if there will be a random number after and add it
|
||||||
|
let num = Math.floor(Math.random() * 2);
|
||||||
|
if (num === 0) Name += Math.floor(Math.random() * 1001);
|
||||||
|
|
||||||
|
return Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the random name to the name of the user
|
||||||
|
export async function setNewName(NameInput: string): Promise<void> {
|
||||||
|
const name = NameInput;
|
||||||
|
const data = { name: name, email: getEmail() };
|
||||||
|
const res = await fetch('api/users/setUserName', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type' : 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { TerminalService } from './terminal.service';
|
||||||
|
|
||||||
|
describe('TerminalService', () => {
|
||||||
|
let service: TerminalService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(TerminalService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
Alex Miller
|
||||||
|
Jordan Latimer
|
||||||
|
|
||||||
|
Service file for Terminals
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Terminal } from '@xterm/xterm';
|
||||||
|
import { AttachAddon } from '@xterm/addon-attach';
|
||||||
|
import { getEmail } from './Helpers';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class TerminalService {
|
||||||
|
|
||||||
|
private term: Terminal; // terminal for container
|
||||||
|
private socket: WebSocket | null = null; // websocket attached to container
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.term = new Terminal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the terminal
|
||||||
|
getTermainal() : Terminal {
|
||||||
|
return this.term;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the terminal
|
||||||
|
setTerminal(newTerm: Terminal) : void {
|
||||||
|
this.term = newTerm;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClearTerminal() : void {
|
||||||
|
if(this.term){
|
||||||
|
this.term.write("\x1b[2J");
|
||||||
|
this.term.write("\x1b[H");
|
||||||
|
this.term.write('Loading Environment...');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create terminal
|
||||||
|
CreateTerminal(): void {
|
||||||
|
const container = document.getElementById('Linux_Shell');
|
||||||
|
if (container && this.term) {
|
||||||
|
this.term.open(container);
|
||||||
|
this.CreateWebSocket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the web socket connection
|
||||||
|
CreateWebSocket() {
|
||||||
|
this.socket = new WebSocket('/ws');
|
||||||
|
this.socket.onopen = () => {
|
||||||
|
if (this.socket) {
|
||||||
|
this.socket.send(JSON.stringify(getEmail()));
|
||||||
|
console.log('WebSocket opened');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const attachAddon = new AttachAddon(this.socket!);
|
||||||
|
this.term.loadAddon(attachAddon);
|
||||||
|
this.term.focus();
|
||||||
|
console.log('reattached attachAddon');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reattach the terminal for a new flag
|
||||||
|
reattachTerminal() {
|
||||||
|
if (this.term) {
|
||||||
|
this.term.dispose();
|
||||||
|
this.term = new Terminal();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.CreateTerminal(); // create the new terminal
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { TreeService } from './tree.service';
|
||||||
|
|
||||||
|
describe('TreeService', () => {
|
||||||
|
let service: TreeService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(TreeService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,117 @@
|
|||||||
|
import { Injectable, InputSignal, Inject } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class TreeService {
|
||||||
|
|
||||||
|
private name: string;
|
||||||
|
private contents: string;
|
||||||
|
private children: TreeService[];
|
||||||
|
private parentnode: TreeService | null;
|
||||||
|
private directory: boolean;
|
||||||
|
private nodenum: number;
|
||||||
|
|
||||||
|
constructor(@Inject(String) name: string, @Inject(String) contents: string, @Inject(Boolean) directory: boolean, @Inject(Number) nodenum: number, @Inject(TreeService) parentnode: TreeService | null) {
|
||||||
|
this.name = name;
|
||||||
|
this.contents = contents;
|
||||||
|
this.children = [];
|
||||||
|
this.parentnode = parentnode;
|
||||||
|
this.directory = directory;
|
||||||
|
this.nodenum = nodenum;
|
||||||
|
}
|
||||||
|
|
||||||
|
// adding a child node to tree
|
||||||
|
AddChild(node: TreeService) {
|
||||||
|
this.children.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// chekc if a child already has the same name
|
||||||
|
IsNameTaken(filename: string): boolean {
|
||||||
|
// check children
|
||||||
|
if (this.children.length > 0) {
|
||||||
|
for (var i=0; i < this.children.length; i++) {
|
||||||
|
let child = this.children[i];
|
||||||
|
|
||||||
|
// check the name
|
||||||
|
if (child.name === filename) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the node with the correct name and nodenum
|
||||||
|
getNodeByName(name: string, num: number): TreeService | null{
|
||||||
|
|
||||||
|
// base case
|
||||||
|
if (this.name === name && this.nodenum == num) return this;
|
||||||
|
|
||||||
|
// Traverse tree structure
|
||||||
|
if (this.children.length > 0) {
|
||||||
|
for (var i=0; i < this.children.length; i++) {
|
||||||
|
const childResult = this.children[i].getNodeByName(name,num);
|
||||||
|
if (childResult !== null && childResult.name === name && childResult.nodenum == num) {
|
||||||
|
return childResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get all the nodes in the tree with the same name
|
||||||
|
getNodesSameName(name: string, nodes: TreeService[]): TreeService[] {
|
||||||
|
|
||||||
|
// base case
|
||||||
|
if (this.name === name) nodes.push(this);
|
||||||
|
|
||||||
|
// check children
|
||||||
|
if (this.children.length > 0) {
|
||||||
|
for (var i=0; i < this.children.length; i++) {
|
||||||
|
this.children[i].getNodesSameName(name,nodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the parent references to send tree to server
|
||||||
|
DeleteParentRef() {
|
||||||
|
|
||||||
|
this.parentnode = null;
|
||||||
|
|
||||||
|
if (this.children.length > 0) {
|
||||||
|
for (var i=0; i < this.children.length; i++) {
|
||||||
|
this.children[i].DeleteParentRef();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete all children of this node
|
||||||
|
DeleteTheChildren() {
|
||||||
|
this.children = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// gets
|
||||||
|
|
||||||
|
getDir() : boolean {
|
||||||
|
return this.directory;
|
||||||
|
}
|
||||||
|
getName() : string{
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
getNodeNum() : number {
|
||||||
|
return this.nodenum;
|
||||||
|
}
|
||||||
|
getChildren() : TreeService[] {
|
||||||
|
return this.children;
|
||||||
|
}
|
||||||
|
getParent() : TreeService | null {
|
||||||
|
return this.parentnode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sets
|
||||||
|
|
||||||
|
setChildren(newchildren: TreeService[]) {
|
||||||
|
this.children = newchildren;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
html {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
h1, h3 {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#Inputs {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: max-content max-content;
|
||||||
|
grid-gap: 5px;
|
||||||
|
margin-left: 25%;
|
||||||
|
margin-top: 25%;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
#Submit {
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#submit * {
|
||||||
|
width: 15%;
|
||||||
|
height: 5%;
|
||||||
|
}
|
||||||
|
#Submit button {
|
||||||
|
background-color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hint1Text, #hint2Text, #hint3Text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show the respective hint fields based on the selected radio button */
|
||||||
|
#hint1:checked ~ #hint1Text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hint2:checked ~ #hint1Text,
|
||||||
|
#hint2:checked ~ #hint2Text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hint3:checked ~ #hint1Text,
|
||||||
|
#hint3:checked ~ #hint2Text,
|
||||||
|
#hint3:checked ~ #hint3Text{
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* for Create Image */
|
||||||
|
#body {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 25% auto auto auto auto auto 25%;
|
||||||
|
grid-template-columns: 20% 20% 20% 20% 20%;
|
||||||
|
justify-items: center;
|
||||||
|
}
|
||||||
|
#labels {
|
||||||
|
grid-row: 1/2;
|
||||||
|
grid-column: 3/4;
|
||||||
|
}
|
||||||
|
#Butts {
|
||||||
|
grid-row: 2/3;
|
||||||
|
grid-column: 3/4;
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
#TextBox {
|
||||||
|
grid-row: 3/6;
|
||||||
|
grid-column: 1/3;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
#DropBox {
|
||||||
|
grid-row: 3/6;
|
||||||
|
grid-column: 3/4;
|
||||||
|
border: 3px black dashed;
|
||||||
|
align-content: center;
|
||||||
|
justify-items: center;
|
||||||
|
}
|
||||||
|
#SendFile {
|
||||||
|
grid-row: 6/7;
|
||||||
|
grid-column: 3/4;
|
||||||
|
}
|
||||||
|
#FileTree {
|
||||||
|
grid-row: 3/6;
|
||||||
|
grid-column: 4/6;
|
||||||
|
}
|
||||||
|
#Submition {
|
||||||
|
grid-row: 7/8;
|
||||||
|
grid-column: 3/4;
|
||||||
|
}
|
||||||
|
.selected-node {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
<!--
|
||||||
|
Jordan Latimer
|
||||||
|
Alex Miller
|
||||||
|
|
||||||
|
Adding a contest screen for Admin
|
||||||
|
-->
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title> Add Contest</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1> Add Contest </h1>
|
||||||
|
|
||||||
|
<!-- form for inserting the contest -->
|
||||||
|
<form id="Inputs">
|
||||||
|
<label> Name: </label>
|
||||||
|
<input id="Name" type="text" align="middle" required>
|
||||||
|
<label> Description: </label>
|
||||||
|
<input id="Desc" type="text" align="middle" required>
|
||||||
|
</form>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<!-- submitting and closing -->
|
||||||
|
<div id="Submit">
|
||||||
|
<input id="AddContest" type="button" value="Add Contest" (click)="AddContest();">
|
||||||
|
<button onclick="self.close();"> Cancel </button>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AddContestComponent } from './add-contest.component';
|
||||||
|
|
||||||
|
describe('AddContestComponent', () => {
|
||||||
|
let component: AddContestComponent;
|
||||||
|
let fixture: ComponentFixture<AddContestComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [AddContestComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AddContestComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,43 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { getEmail } from '../Helper/Helpers';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-add-contest',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './add-contest.component.html',
|
||||||
|
styleUrl: './add-contest.component.css'
|
||||||
|
})
|
||||||
|
export class AddContestComponent {
|
||||||
|
|
||||||
|
// add a contest to the database
|
||||||
|
async AddContest() {
|
||||||
|
const nameInput = document.getElementById("Name") as HTMLInputElement;
|
||||||
|
const descInput = document.getElementById("Desc") as HTMLTextAreaElement;
|
||||||
|
const name = nameInput.value;
|
||||||
|
const desc = descInput.value;
|
||||||
|
console.log("Name entered: ", name);
|
||||||
|
console.log("Description entered: ", desc);
|
||||||
|
const isActive = 0;
|
||||||
|
console.log("EMAIL IS:", this.getEmail())
|
||||||
|
const data = {Name: name, IsActive: isActive, email: this.getEmail(), Desc: desc};
|
||||||
|
const res = await fetch('api/AddContest', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type' : 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
console.log("Success: adding contest");
|
||||||
|
window.opener.location.reload();
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("ERROR: adding contest");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getEmail(): string{
|
||||||
|
return getEmail();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
Alex Miller
|
||||||
|
Jordan Latimer
|
||||||
|
|
||||||
|
CSS for Add Flag popup window
|
||||||
|
*/
|
||||||
|
|
||||||
|
#FlagInputs {
|
||||||
|
text-align: center;
|
||||||
|
background-color: lightgreen;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#Submit {
|
||||||
|
text-align: center;
|
||||||
|
justify-items: center;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
font-weight: bold;
|
||||||
|
height: 15%;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#flagName {
|
||||||
|
background-color: lawngreen;
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
display: inline-block;
|
||||||
|
width: 170px;
|
||||||
|
margin: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
option {
|
||||||
|
text-align: center;
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
<!--
|
||||||
|
Jordan Latimer
|
||||||
|
Alex Miller
|
||||||
|
|
||||||
|
Adding a Flag for Admin
|
||||||
|
-->
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title> Add Flag</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1> Add Flag </h1>
|
||||||
|
|
||||||
|
<!-- form for adding the flag -->
|
||||||
|
<form id="FlagInputs">
|
||||||
|
<label> Name: </label>
|
||||||
|
<input id="Name" type="text" align="middle" required>
|
||||||
|
<br><br>
|
||||||
|
<label> Description: </label>
|
||||||
|
<input id="Description" required>
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<input type="radio" id="hint1" name="hintCount" value="1">
|
||||||
|
<label for="hint1">1 Hint</label>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<input type="radio" id="hint2" name="hintCount" value="2">
|
||||||
|
<label for="hint2">2 Hints</label>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<input type="radio" id="hint3" name="hintCount" value="3">
|
||||||
|
<label for="hint3">3 Hints</label>
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<div id = "hint1Text">
|
||||||
|
<label for="Hint1">Hint 1: </label>
|
||||||
|
<input id="Hint1" type="text"><br><br>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id = "hint2Text">
|
||||||
|
<label for="Hint2">Hint 2: </label>
|
||||||
|
<input id="Hint2" type="text"><br><br>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id = "hint3Text">
|
||||||
|
<label for="Hint3">Hint 3: </label>
|
||||||
|
<input id="Hint3" type="text"><br><br>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- image of the flag to create container -->
|
||||||
|
<label> Image: </label>
|
||||||
|
<select id="Images">
|
||||||
|
<option> ubuntu </option>
|
||||||
|
</select>
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<!-- path to place the flag into -->
|
||||||
|
<label> Path To File </label>
|
||||||
|
<input id="Path" type="text">
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<!-- submit button just closes the window for now -->
|
||||||
|
<div id="Submit">
|
||||||
|
<button id="flagName" type="submit" (click)="AddFlag()">Add Flag</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AddFlagComponent } from './add-flag.component';
|
||||||
|
|
||||||
|
describe('AddFlagComponent', () => {
|
||||||
|
let component: AddFlagComponent;
|
||||||
|
let fixture: ComponentFixture<AddFlagComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [AddFlagComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AddFlagComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,64 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { gotoPage } from '../Helper/Helpers';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
@Component({
|
||||||
|
selector: 'app-add-flag',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './add-flag.component.html',
|
||||||
|
styleUrl: './add-flag.component.css'
|
||||||
|
})
|
||||||
|
export class AddFlagComponent {
|
||||||
|
|
||||||
|
constructor(private router: Router){}
|
||||||
|
|
||||||
|
// add a flag to a contest
|
||||||
|
async AddFlag(){
|
||||||
|
// get contestId from URL
|
||||||
|
const contestId = parseInt(sessionStorage.getItem('selectedContestID') || '', 10);
|
||||||
|
|
||||||
|
// get all the values in the form
|
||||||
|
const flagName = document.getElementById("Name") as HTMLInputElement;
|
||||||
|
const description = document.getElementById("Description") as HTMLInputElement;
|
||||||
|
let Hint1 = document.getElementById("Hint1") as HTMLInputElement;
|
||||||
|
let Hint2 = document.getElementById("Hint2") as HTMLInputElement;
|
||||||
|
let Hint3 = document.getElementById("Hint3") as HTMLInputElement;
|
||||||
|
const image = document.getElementById('Images') as HTMLInputElement;
|
||||||
|
const path = document.getElementById('Path') as HTMLInputElement;
|
||||||
|
|
||||||
|
// required fields
|
||||||
|
if(!flagName.value){
|
||||||
|
alert("Enter a flag name.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(contestId === null || contestId === undefined){
|
||||||
|
alert("Please select a contest first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the flag
|
||||||
|
const data = {name: flagName.value, desc: description.value, contest: contestId, image: image.value, path: path.value, hint1: Hint1.value || '', hint2: Hint2.value || '', hint3: Hint3.value || ''};
|
||||||
|
try{
|
||||||
|
const response = await fetch('api/AddFlag', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json',},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
if(response.ok){
|
||||||
|
console.log("Flag added");
|
||||||
|
// reload the parent window when this window closes
|
||||||
|
window.opener.postMessage({ type: 'FLAG_ADDED', contestId: contestId }, window.origin);
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
alert("Failed to add flag.");
|
||||||
|
}
|
||||||
|
} catch(error){
|
||||||
|
console.error("Error adding flag:", error);
|
||||||
|
alert("An error occurred while adding flag.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
navtoPageCI(){
|
||||||
|
gotoPage(this.router, '/create-image');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
html {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
h1, h3 {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#Inputs {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: max-content max-content;
|
||||||
|
grid-gap: 5px;
|
||||||
|
margin-left: 25%;
|
||||||
|
margin-top: 25%;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
#Submit {
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#submit * {
|
||||||
|
width: 15%;
|
||||||
|
height: 5%;
|
||||||
|
}
|
||||||
|
#Submit button {
|
||||||
|
background-color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hint1Text, #hint2Text, #hint3Text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show the respective hint fields based on the selected radio button */
|
||||||
|
#hint1:checked ~ #hint1Text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hint2:checked ~ #hint1Text,
|
||||||
|
#hint2:checked ~ #hint2Text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hint3:checked ~ #hint1Text,
|
||||||
|
#hint3:checked ~ #hint2Text,
|
||||||
|
#hint3:checked ~ #hint3Text{
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* for Create Image */
|
||||||
|
#body {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 25% auto auto auto auto auto 25%;
|
||||||
|
grid-template-columns: 20% 20% 20% 20% 20%;
|
||||||
|
justify-items: center;
|
||||||
|
}
|
||||||
|
#labels {
|
||||||
|
grid-row: 1/2;
|
||||||
|
grid-column: 3/4;
|
||||||
|
}
|
||||||
|
#Butts {
|
||||||
|
grid-row: 2/3;
|
||||||
|
grid-column: 3/4;
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
#TextBox {
|
||||||
|
grid-row: 3/6;
|
||||||
|
grid-column: 1/3;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
#DropBox {
|
||||||
|
grid-row: 3/6;
|
||||||
|
grid-column: 3/4;
|
||||||
|
border: 3px black dashed;
|
||||||
|
align-content: center;
|
||||||
|
justify-items: center;
|
||||||
|
}
|
||||||
|
#SendFile {
|
||||||
|
grid-row: 6/7;
|
||||||
|
grid-column: 3/4;
|
||||||
|
}
|
||||||
|
#FileTree {
|
||||||
|
grid-row: 3/6;
|
||||||
|
grid-column: 4/6;
|
||||||
|
}
|
||||||
|
#Submition {
|
||||||
|
grid-row: 7/8;
|
||||||
|
grid-column: 3/4;
|
||||||
|
}
|
||||||
|
.selected-node {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
<!--
|
||||||
|
Alex Miller
|
||||||
|
Jordan Latimer
|
||||||
|
|
||||||
|
Adding a student page for Admin
|
||||||
|
-->
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title> Add Student </title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1> Add Student </h1>
|
||||||
|
|
||||||
|
<!-- form for adding a student -->
|
||||||
|
<form id="Inputs">
|
||||||
|
<label> Name: </label>
|
||||||
|
<input id="Name" type="text" [(ngModel)]="Uname" name="Uname" align="middle">
|
||||||
|
<label> Email: </label>
|
||||||
|
<input id="EM" type="text" [(ngModel)]="email" name="email" align="middle">
|
||||||
|
<label> Password: </label>
|
||||||
|
<input *ngIf="!showPass" id="PS1" type="password" [(ngModel)]="password" name="password" align="middle">
|
||||||
|
<input *ngIf="showPass" id="PS1" type="text" [(ngModel)]="password" name="password" align="middle">
|
||||||
|
<label> Confirm Password: </label>
|
||||||
|
<input *ngIf="!showPass" id="PS2" type="password" [(ngModel)]="confirmPassword" name="confirmPassword" align="middle">
|
||||||
|
<input *ngIf="showPass" id="PS2" type="text" [(ngModel)]="confirmPassword" name="confirmPassword" align="middle">
|
||||||
|
<label> Show Password </label>
|
||||||
|
<input type="checkbox" (click)="ShowPassword()" id="ShowPass">
|
||||||
|
</form>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<!-- submitting or closing-->
|
||||||
|
<div id="Submit">
|
||||||
|
<input id="AddStudent" type="submit" (click)="AddStudent()">
|
||||||
|
<button onclick="self.close();"> Cancel </button>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AddStudentComponent } from './add-student.component';
|
||||||
|
|
||||||
|
describe('AddStudentComponent', () => {
|
||||||
|
let component: AddStudentComponent;
|
||||||
|
let fixture: ComponentFixture<AddStudentComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [AddStudentComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AddStudentComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,59 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { getEmail } from '../Helper/Helpers';
|
||||||
|
import { NgIf } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-add-student',
|
||||||
|
imports: [NgIf, FormsModule],
|
||||||
|
templateUrl: './add-student.component.html',
|
||||||
|
styleUrl: './add-student.component.css'
|
||||||
|
})
|
||||||
|
export class AddStudentComponent {
|
||||||
|
Uname: string = '';
|
||||||
|
email: string = '';
|
||||||
|
password: string = '';
|
||||||
|
confirmPassword: string = '';
|
||||||
|
showPass: boolean = false;
|
||||||
|
|
||||||
|
ShowPassword() {
|
||||||
|
this.showPass = !this.showPass
|
||||||
|
}
|
||||||
|
|
||||||
|
// add a student to the database
|
||||||
|
async AddStudent() {
|
||||||
|
const Aemail = getEmail();
|
||||||
|
|
||||||
|
// password protection
|
||||||
|
if (this.password.length < 10 || this.password == "") {
|
||||||
|
alert("password must be at least 10 characters");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(this.email == Aemail){
|
||||||
|
alert("Funny guy, eh?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.password == this.confirmPassword) { // passwords match
|
||||||
|
const data = {name: this.Uname, email: this.email, Aemail: Aemail, password: this.password};
|
||||||
|
const res = await fetch('api/AddStudent', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type' : 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
console.log("Success: adding student");
|
||||||
|
window.opener.location.reload();
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("ERROR: adding student");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert("Passowrds do not match");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,141 @@
|
|||||||
|
/*
|
||||||
|
Jordan Latimer
|
||||||
|
Alex Miller
|
||||||
|
|
||||||
|
CSS for both Contest Pages
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: grid;
|
||||||
|
background-color: green;
|
||||||
|
|
||||||
|
/* columns and rows sizes */
|
||||||
|
grid-template-columns: 15% 10% auto 20%;
|
||||||
|
grid-template-rows: 10% 40% 10% auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name of the contest at the top left */
|
||||||
|
#Contest_Name {
|
||||||
|
grid-row: 1/2;
|
||||||
|
grid-column: 1/3;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 175%;
|
||||||
|
}
|
||||||
|
#Contest_Name * {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sidebar thats scrollable for different flags */
|
||||||
|
#Flag_Sidebar {
|
||||||
|
background-color: white;
|
||||||
|
grid-row: 2/5;
|
||||||
|
grid-column: 1/2;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#Flag_Sidebar ul {
|
||||||
|
list-style-type: none;
|
||||||
|
height: 80%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
background-color: white;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* details for each flag (name, description, etc) */
|
||||||
|
#Flag_Deats {
|
||||||
|
background-color: white;
|
||||||
|
grid-row: 2/3;
|
||||||
|
grid-column: 2/4;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#Notifications {
|
||||||
|
background-color: rgba(65, 145, 55, 0.511);
|
||||||
|
margin-left: 50%;
|
||||||
|
grid-row: 3/4;
|
||||||
|
grid-column: 3/5;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#Notifications.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Submitting the flag */
|
||||||
|
#Submit_Flag {
|
||||||
|
background-color: white;
|
||||||
|
grid-row: 3/4;
|
||||||
|
grid-column: 2/4;
|
||||||
|
|
||||||
|
}
|
||||||
|
#Submit_Flag_Stuff * {
|
||||||
|
height: 40%;
|
||||||
|
width: 15%;
|
||||||
|
}
|
||||||
|
#Submit_Butt {
|
||||||
|
background-color: lawngreen;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
#Submit_Flag h3 {
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.Incorrect, .Correct {
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 125%;
|
||||||
|
}
|
||||||
|
.Incorrect {
|
||||||
|
background-color: red;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.Correct {
|
||||||
|
background-color: lawngreen;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* hints for each flag */
|
||||||
|
#Hints {
|
||||||
|
background-color: white;
|
||||||
|
grid-row: 2/4;
|
||||||
|
grid-column: 4/5;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
background-color: white;
|
||||||
|
resize: none;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Linux Shell for commands */
|
||||||
|
#Linux_Shell {
|
||||||
|
grid-row: 4/5;
|
||||||
|
grid-column: 2/5;
|
||||||
|
color: lawngreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* when a flag is selected */
|
||||||
|
.selected-flag {
|
||||||
|
background-color: lightgray;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flags and Hints Labels */
|
||||||
|
.FlagHint {
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
margin: 10px;
|
||||||
|
font-size: 200%;
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
<!--
|
||||||
|
Alex Miller
|
||||||
|
Jordan Latimer
|
||||||
|
|
||||||
|
Contest Page for Admins
|
||||||
|
-->
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title> NMU CTF Contest Page </title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Bar at top for moving tabs -->
|
||||||
|
<div id="Taskbar">
|
||||||
|
<a (click)="confirmSelection()"> Profile </a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Contest name -->
|
||||||
|
<div id="Contest_Name">
|
||||||
|
<h1 id="ContHeader"></h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sidebar thats scrollable for different flags -->
|
||||||
|
<div id="Flag_Sidebar">
|
||||||
|
<h2 class="FlagHint">Flags</h2>
|
||||||
|
<ul id="FlagList">
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- details for each flag (name, description etc) -->
|
||||||
|
<div id="Flag_Deats">
|
||||||
|
<div id ="DefaultMessage">
|
||||||
|
<h2>Welcome to NMU CTF</h2>
|
||||||
|
<p>There are currently no contests active at this time</p>
|
||||||
|
</div>
|
||||||
|
<h2 id="ContName"></h2>
|
||||||
|
<p id="ContDesc"></p>
|
||||||
|
<h2 id="Flag_Name"></h2>
|
||||||
|
<p id="Desc" ></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="Submit_Flag">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- Hints for each flag -->
|
||||||
|
<div id="Hints">
|
||||||
|
<div id="Hint_Stuff">
|
||||||
|
<h2 class="FlagHint"> Hints </h2>
|
||||||
|
<div id="Hint_Butts">
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<textarea readonly rows="15" cols="30" id="Hint_Desc"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Linux Shell for commands -->
|
||||||
|
<div id="Linux_Shell"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AdminContestComponent } from './admin-contest.component';
|
||||||
|
|
||||||
|
describe('AdminContestComponent', () => {
|
||||||
|
let component: AdminContestComponent;
|
||||||
|
let fixture: ComponentFixture<AdminContestComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [AdminContestComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AdminContestComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,39 @@
|
|||||||
|
import { Component, ElementRef, Renderer2 } from '@angular/core';
|
||||||
|
import { gotoPage, getEmail } from '../Helper/Helpers';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { loadPage } from '../Helper/ContestSetup';
|
||||||
|
import { TerminalService } from '../Helper/terminal.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-admin-contest',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './admin-contest.component.html',
|
||||||
|
styleUrl: './admin-contest.component.css'
|
||||||
|
})
|
||||||
|
export class AdminContestComponent {
|
||||||
|
|
||||||
|
constructor(private renderer: Renderer2, private el: ElementRef, private router: Router, private terminalService: TerminalService){}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
|
||||||
|
// attach to terminal onload
|
||||||
|
this.terminalService.reattachTerminal();
|
||||||
|
|
||||||
|
loadPage(this.renderer, this.el, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmSelection(){
|
||||||
|
const confirmSelection_ = confirm("You will lose your current flag progress, are you sure?");
|
||||||
|
if(confirmSelection_){
|
||||||
|
gotoPage(this.router, '/admin-profile');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
console.log("Profile navigation canceled");
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear and set terminal for new flag
|
||||||
|
async ClearAndSetTerminal() {
|
||||||
|
await this.terminalService.ClearTerminal();
|
||||||
|
await this.terminalService.reattachTerminal();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
Alex Miller
|
||||||
|
Jordan Latimer
|
||||||
|
|
||||||
|
CSS for Profile Page on Admin side
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
/* columns and rows sizes */
|
||||||
|
grid-template-columns: auto auto auto auto auto;
|
||||||
|
grid-template-rows: auto auto auto auto auto auto;
|
||||||
|
|
||||||
|
background-color: lightgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
#Header {
|
||||||
|
grid-row: 1/3;
|
||||||
|
grid-column: 1/6;
|
||||||
|
margin: 50px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
.HeadButts {
|
||||||
|
height: 25px;
|
||||||
|
width: 150px;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
#ModifyContests {
|
||||||
|
background-color: blue;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#Logout {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
#ContestPage {
|
||||||
|
background-color: lawngreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Student side of the page */
|
||||||
|
#Students {
|
||||||
|
grid-row: 4/6;
|
||||||
|
grid-column: 1/3;
|
||||||
|
margin: 50px;
|
||||||
|
height: 500px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
#Student_Search input, select, button {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
.StudentButts, .FlagButts, select {
|
||||||
|
width: 23%;
|
||||||
|
height: 25px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#SortButt {
|
||||||
|
width: 10%;
|
||||||
|
background-color: blue;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#StudentsTable, #FlagsTable {
|
||||||
|
margin: 10px;
|
||||||
|
height: 75%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flag side of the page */
|
||||||
|
#Flags {
|
||||||
|
grid-row: 4/6;
|
||||||
|
grid-column: 4/6;
|
||||||
|
margin: 50px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
#ContestDropDown button, select {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Popup for deleting students */
|
||||||
|
#DialogBox {
|
||||||
|
text-align: center;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
padding: 100px;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
#DialogBox #Delete {
|
||||||
|
background-color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#DialogBox button {
|
||||||
|
height: 25px;
|
||||||
|
width: 75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* buttons */
|
||||||
|
#AddStudent {
|
||||||
|
background-color: lawngreen;
|
||||||
|
}
|
||||||
|
#EditStudent {
|
||||||
|
background-color: blue;
|
||||||
|
color: white;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
#DeleteStudent {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tooltips */
|
||||||
|
.tooltip {
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.tooltip:hover .tooltipText {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
.tooltip .tooltipText {
|
||||||
|
visibility: hidden;
|
||||||
|
height: auto;
|
||||||
|
background-color: gray;
|
||||||
|
color: black;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
padding: 5px;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* no ID stuff */
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border: 3px solid black;
|
||||||
|
border-collapse: seperate;
|
||||||
|
border-spacing: 0;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
table thead {
|
||||||
|
top: 0;
|
||||||
|
position: sticky;
|
||||||
|
position: -webkit-sticky;
|
||||||
|
z-index: 1;
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
border: 3px solid black;
|
||||||
|
text-align: center;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 80%;
|
||||||
|
height: 50%;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
display: inline;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
@ -0,0 +1,169 @@
|
|||||||
|
<!--
|
||||||
|
Alex Miller
|
||||||
|
Jordan Latimer
|
||||||
|
|
||||||
|
Profile Page for Admins
|
||||||
|
-->
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title> NMU CTF Profile Page </title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="Header">
|
||||||
|
<h1> Profile </h1>
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
This is where you can do anything and
|
||||||
|
<br>
|
||||||
|
everything with your <span style="color: white">Students</span>
|
||||||
|
or look at the current stats for <span style="color: white;">Flags</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<button class='HeadButts' id="ContestPage" (click)="navtoPageAC()">Back to Contest</button>
|
||||||
|
<button class='HeadButts' id="ModifyContests" (click)="navtoPageMC()">Modify Contests</button>
|
||||||
|
<button class='HeadButts' id="Logout" (click)="onLogout()">Logout</button>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- everything with students side of the page -->
|
||||||
|
<div id="Students">
|
||||||
|
<h1> Students </h1>
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
Here you will see a list of your <span style="color:white">Students</span>
|
||||||
|
<br>
|
||||||
|
you can search for specific students
|
||||||
|
<br>
|
||||||
|
or filter them how you would like. For searching a specific <span style="color:white;">Student</span>, make sure
|
||||||
|
<br>
|
||||||
|
<span style="color:white;">Name</span> is selected in the dropdown
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<!-- searching for students -->
|
||||||
|
<div id="Student_Search">
|
||||||
|
<input id="StudentName" type="text" placeholder="Student Name" [(ngModel)]="searchText">
|
||||||
|
<select id="Options">
|
||||||
|
<option disabled selected hidden> Sort By: </option>
|
||||||
|
<option value="Name (A->Z)"> Name (A->Z) </option>
|
||||||
|
<option value="Name (Z->A)"> Name (Z->A) </option>
|
||||||
|
<option value="Flags (H->L)"> Flags (H->L) </option>
|
||||||
|
<option value="Flags (L->H)"> Flags (L->H) </option>
|
||||||
|
</select>
|
||||||
|
<button class="StudentButts" id="SortButt" (click)="sortTable()">Sort</button>
|
||||||
|
<button class="StudentButts" id="AddStudent" (click)="openAddUserPopup()"> Add Student </button>
|
||||||
|
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
<span style="color:lawngreen;">Add Student: </span> opens a popup to add a <span style="color:white;">Student</span> to the list
|
||||||
|
<br>
|
||||||
|
<span style="color:blue;">Edit: </span> allows you to edit a students <span style="color:white;">Password</span>
|
||||||
|
<br>
|
||||||
|
<span style="color:red;">Delete: </span> Deletes a <span style="color:white;">Student</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Student Table -->
|
||||||
|
<div id="StudentsTable">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th> Student Name </th>
|
||||||
|
<th> Flags </th>
|
||||||
|
<th> Email </th>
|
||||||
|
<th> Change Password </th>
|
||||||
|
<th> Delete </th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="TBody">
|
||||||
|
<tr *ngFor="let user of users">
|
||||||
|
<td>{{ user.Name }}</td>
|
||||||
|
<td>{{ user.Flags }}</td>
|
||||||
|
<td>{{ user.Email }}</td>
|
||||||
|
<td><button id="EditStudent" (click)="openPasswordPopup(user.Email)">Edit</button></td>
|
||||||
|
<td><button id="DeleteStudent" (click)="openDeleteDialog(user.Email)">Delete</button></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- everythig with the flags side of the page -->
|
||||||
|
<div id="Flags">
|
||||||
|
<h1> Flags </h1>
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
This is where you can look at how your <span style="color:white;">Students</span>
|
||||||
|
<br>
|
||||||
|
are doing with the <span style="color:white;">Flags</span> you created
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<!-- dropdown for contests -->
|
||||||
|
<div id="ContestDropDown">
|
||||||
|
<select id="DropDown" [(ngModel)]="selectedContest">
|
||||||
|
<option disabled selected hidden> Select a Contest </option>
|
||||||
|
<option *ngFor="let contest of contests" [value] ="contest.Name">{{ contest.Name }}</option>
|
||||||
|
</select>
|
||||||
|
<button class="FlagButts" (click)="populateFlagsTable()">Get Flags</button>
|
||||||
|
<button class="FlagButts" (click)="openLeaderboardPopup(getEmail())">Leaderboard</button>
|
||||||
|
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
<span style="color:white;">Get Flags: </span> shows all the <span style="color:white;">Flags</span> for that contests and the stats
|
||||||
|
<br>
|
||||||
|
<span style="color:white;">Leaderboard: </span> popup a leaderboard for that <span style="color:white;">Contest</span> to see how each <span style="color:white;">Student</span> is doing
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- table for flags -->
|
||||||
|
<div id="FlagsTable">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th> Flag Name </th>
|
||||||
|
<th> Attempts </th>
|
||||||
|
<th> Completions </th>
|
||||||
|
<th> Ratio </th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="FlagTBody">
|
||||||
|
<tr *ngFor="let flag of flags">
|
||||||
|
<td>{{ flag.Name }}</td>
|
||||||
|
<td>{{ getAttempts(flag.FlagID )}}</td>
|
||||||
|
<td>{{ getCompletions(flag.FlagID) }}</td>
|
||||||
|
<td [ngStyle]="{ color: getRatioColor(flag.FlagID) }"> {{ getRatio(flag.FlagID) }}%</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dialog box for reassurance of deleting a student -->
|
||||||
|
<dialog id="DialogBox">
|
||||||
|
Are you sure you want to delete <strong id="emailDB">{{ emailToDelete }}</strong>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<button (click)="closeDialog()"> Cancel </button>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<button id="Delete" (click)="deleteUser()"> Delete </button>
|
||||||
|
</dialog>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AdminProfileComponent } from './admin-profile.component';
|
||||||
|
|
||||||
|
describe('AdminProfileComponent', () => {
|
||||||
|
let component: AdminProfileComponent;
|
||||||
|
let fixture: ComponentFixture<AdminProfileComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [AdminProfileComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AdminProfileComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,179 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { gotoPage, logOut, getEmail, Popup } from '../Helper/Helpers';
|
||||||
|
import { AdminProfileService } from './admin-profile.service';
|
||||||
|
import { Flag } from '../models/flag.model';
|
||||||
|
import { User } from '../models/user.model';
|
||||||
|
import { Contest } from '../models/contest.model';
|
||||||
|
import { Submission } from '../models/submission.model';
|
||||||
|
import { NgFor } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-admin-profile',
|
||||||
|
imports: [NgFor, FormsModule, CommonModule],
|
||||||
|
templateUrl: './admin-profile.component.html',
|
||||||
|
styleUrl: './admin-profile.component.css'
|
||||||
|
})
|
||||||
|
export class AdminProfileComponent implements OnInit{
|
||||||
|
users: User[] = []
|
||||||
|
displayedUsers: User[] = []
|
||||||
|
contests: Contest[] = []
|
||||||
|
subs: Submission[] = []
|
||||||
|
flags: Flag[] = []
|
||||||
|
selectedContest: string = '';
|
||||||
|
sortOption: string = '';
|
||||||
|
constructor(private router: Router, private adminProfileService: AdminProfileService){}
|
||||||
|
emailToDelete = '';
|
||||||
|
searchText: string = '';
|
||||||
|
|
||||||
|
ngOnInit(){
|
||||||
|
this.getContests();
|
||||||
|
this.populateTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async populateTable(){
|
||||||
|
try{
|
||||||
|
const email = this.getEmail();
|
||||||
|
const users = await this.adminProfileService.getAllUsers(email);
|
||||||
|
this.users = users;
|
||||||
|
} catch(error) {
|
||||||
|
console.error('Error populating user table', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async populateFlagsTable(): Promise<void> {
|
||||||
|
if(!this.selectedContest){
|
||||||
|
alert('Must select a contest');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try{
|
||||||
|
const email = getEmail();
|
||||||
|
const result = await this.adminProfileService.getContestFlagsSubs(email, this.selectedContest);
|
||||||
|
this.flags = result.flags;
|
||||||
|
this.subs = result.subs;
|
||||||
|
}catch(error){
|
||||||
|
console.error('Failed to populate flag table');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAttempts(flagId: number): number{
|
||||||
|
return this.subs.filter(sub => sub.FlagID === flagId).reduce((acc, sub) => acc + sub.Attempts, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCompletions(flagId: number): number{
|
||||||
|
return this.subs.filter(sub => sub.FlagID === flagId).reduce((acc, sub) => acc + (sub.IsCorrect ? 1 : 0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRatio(flagId: number): number{
|
||||||
|
const attempts = this.getAttempts(flagId);
|
||||||
|
const completions = this.getCompletions(flagId);
|
||||||
|
return attempts > 0 ? +(completions / attempts * 100).toFixed(2) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRatioColor(flagId: number): string{
|
||||||
|
const ratio = this.getRatio(flagId);
|
||||||
|
if(ratio >= 80) return 'lawngreen';
|
||||||
|
else if (ratio >= 60) return 'orange';
|
||||||
|
return 'red';
|
||||||
|
}
|
||||||
|
|
||||||
|
openPasswordPopup(email: string){
|
||||||
|
sessionStorage.setItem('selectedUser', email);
|
||||||
|
Popup('/edit-student');
|
||||||
|
}
|
||||||
|
|
||||||
|
openAddUserPopup(){
|
||||||
|
Popup('/add-student');
|
||||||
|
}
|
||||||
|
|
||||||
|
openLeaderboardPopup(email: string){
|
||||||
|
Popup('Leaderboard.html');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getContests(){
|
||||||
|
try {
|
||||||
|
const email = this.getEmail();
|
||||||
|
const contests = await this.adminProfileService.getContests(email);
|
||||||
|
this.contests = contests;
|
||||||
|
} catch(error){
|
||||||
|
console.error("Error getting contests", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openDeleteDialog(email: string): void {
|
||||||
|
this.emailToDelete = email;
|
||||||
|
const dialog = document.getElementById('DialogBox') as HTMLDialogElement
|
||||||
|
if(dialog){
|
||||||
|
dialog.showModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteUser(){
|
||||||
|
try{
|
||||||
|
if(!this.emailToDelete) return;
|
||||||
|
await this.adminProfileService.deleteUser(this.emailToDelete);
|
||||||
|
console.log("User deleted successfully");
|
||||||
|
this.closeDialog();
|
||||||
|
this.populateTable();
|
||||||
|
} catch(error) {
|
||||||
|
console.error("Error deleting user", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDialog(): void {
|
||||||
|
const dialog = document.getElementById('DialogBox') as HTMLDialogElement;
|
||||||
|
if(dialog)
|
||||||
|
dialog.close();
|
||||||
|
this.emailToDelete = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
sortTable(): void{
|
||||||
|
const name = this.searchText.trim().toLowerCase();
|
||||||
|
const option = this.sortOption;
|
||||||
|
let sorted = [...this.users];
|
||||||
|
|
||||||
|
if((option === 'Name (A->Z)' || option === 'Name (Z->A)') && name !== ''){
|
||||||
|
const targetIndex = sorted.findIndex(s => s.Name.toLowerCase() === name);
|
||||||
|
if(targetIndex !== -1){
|
||||||
|
const student = sorted.splice(targetIndex, 1)[0];
|
||||||
|
sorted.unshift(student);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
alert('Student is not in database');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(option === 'Name (A->Z)'){
|
||||||
|
sorted.sort((a, b) => a.Name.localeCompare(b.Name));
|
||||||
|
}
|
||||||
|
else if(option === 'Name (Z->A)'){
|
||||||
|
sorted.sort((a, b) => b.Name.localeCompare(a.Name));
|
||||||
|
}
|
||||||
|
else if(option === 'Flags (H->L)'){
|
||||||
|
sorted.sort((a, b) => b.Flags - a.Flags);
|
||||||
|
}
|
||||||
|
else if(option === 'Flags (L->H)'){
|
||||||
|
sorted.sort((b, a) => a.Flags - b.Flags);
|
||||||
|
}
|
||||||
|
this.displayedUsers = sorted;
|
||||||
|
}
|
||||||
|
|
||||||
|
navtoPageAC(): void{
|
||||||
|
gotoPage(this.router, '/admin-contest');
|
||||||
|
}
|
||||||
|
|
||||||
|
navtoPageMC(): void{
|
||||||
|
gotoPage(this.router, '/modify-contest');
|
||||||
|
}
|
||||||
|
|
||||||
|
onLogout(): void{
|
||||||
|
logOut(this.router);
|
||||||
|
}
|
||||||
|
|
||||||
|
getEmail(): string{
|
||||||
|
return getEmail();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AdminProfileService } from './admin-profile.service';
|
||||||
|
|
||||||
|
describe('AdminProfileService', () => {
|
||||||
|
let service: AdminProfileService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(AdminProfileService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,90 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
|
||||||
|
export class AdminProfileService {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
async getAllUsers(email: string): Promise<any> {
|
||||||
|
try {
|
||||||
|
const response = await fetch('api/users/getAllUsers', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ email })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch users');
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching users:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteUser(email: string): Promise<any> {
|
||||||
|
try {
|
||||||
|
const response = await fetch('api/users/DeleteStudent', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ email })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to delete user');
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting user:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getContests(email: string): Promise<any> {
|
||||||
|
try {
|
||||||
|
const response = await fetch('api/contests/getContests', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ email })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch contests');
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching contests:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getContestFlagsSubs(email: string, contest: string): Promise<any> {
|
||||||
|
try {
|
||||||
|
const response = await fetch('api/getContestFlagsSubs', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ email, contest })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch contest flags');
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching contest flags:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
<router-outlet></router-outlet>
|
@ -0,0 +1,29 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
|
describe('AppComponent', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [AppComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create the app', () => {
|
||||||
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
expect(app).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should have the 'CTF-Frontend' title`, () => {
|
||||||
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
expect(app.title).toEqual('CTF-Frontend');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render title', () => {
|
||||||
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const compiled = fixture.nativeElement as HTMLElement;
|
||||||
|
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, CTF-Frontend');
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,14 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { Router, RouterOutlet } from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
imports: [RouterOutlet],
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrl: './app.component.css'
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
title = 'CTF-Frontend';
|
||||||
|
|
||||||
|
constructor(private router: Router){}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
||||||
|
import { provideRouter } from '@angular/router';
|
||||||
|
|
||||||
|
import { routes } from './app.routes';
|
||||||
|
|
||||||
|
export const appConfig: ApplicationConfig = {
|
||||||
|
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
|
||||||
|
};
|
@ -0,0 +1,131 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { AuthGuard } from './guards/auth.guard';
|
||||||
|
import { AdminGuard } from './guards/AdminGuard.guard';
|
||||||
|
import { UserGuard } from './guards/UserGuard.guard';
|
||||||
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
|
import { LoginComponent } from './login/login.component';
|
||||||
|
import { UserMenuComponent } from './user-menu/user-menu.component';
|
||||||
|
import { UserProfileComponent } from './user-profile/user-profile.component';
|
||||||
|
import { ContestPageComponent } from './contest-page/contest-page.component';
|
||||||
|
import { UserProfileACComponent } from './user-profile-ac/user-profile-ac.component';
|
||||||
|
import { AdminContestComponent } from './admin-contest/admin-contest.component';
|
||||||
|
import { AdminProfileComponent } from './admin-profile/admin-profile.component';
|
||||||
|
import { LeaderboardComponent } from './leaderboard/leaderboard.component';
|
||||||
|
import { ModifyContestComponent } from './modify-contest/modify-contest.component';
|
||||||
|
import { AddContestComponent } from './add-contest/add-contest.component';
|
||||||
|
import { AddStudentComponent } from './add-student/add-student.component';
|
||||||
|
import { EditStudentComponent } from './edit-student/edit-student.component';
|
||||||
|
import { AddFlagComponent } from './add-flag/add-flag.component';
|
||||||
|
import { UNAUTHORIZEDComponent } from './unauthorized/unauthorized.component';
|
||||||
|
import { CreateImageComponent } from './create-image/create-image.component';
|
||||||
|
|
||||||
|
export const routes: Routes = [
|
||||||
|
{
|
||||||
|
path:'',
|
||||||
|
component: LoginComponent,
|
||||||
|
title: 'Login',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'user-menu',
|
||||||
|
component: UserMenuComponent,
|
||||||
|
title: 'User Menu',
|
||||||
|
canActivate: [AuthGuard, UserGuard]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'user-profile',
|
||||||
|
component: UserProfileComponent,
|
||||||
|
title: 'User Profile',
|
||||||
|
canActivate: [AuthGuard, UserGuard]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'contest-page',
|
||||||
|
component: ContestPageComponent,
|
||||||
|
title: 'Contest Page',
|
||||||
|
canActivate: [AuthGuard, UserGuard]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'user-profileAC',
|
||||||
|
component: UserProfileACComponent,
|
||||||
|
title: 'User Profile',
|
||||||
|
canActivate: [AuthGuard, UserGuard]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'admin-contest',
|
||||||
|
component: AdminContestComponent,
|
||||||
|
title: 'Admin Contest',
|
||||||
|
canActivate: [AuthGuard, AdminGuard]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'admin-profile',
|
||||||
|
component: AdminProfileComponent,
|
||||||
|
title: 'Admin Profile',
|
||||||
|
canActivate: [AuthGuard, AdminGuard]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'leaderboard',
|
||||||
|
component: LeaderboardComponent,
|
||||||
|
title: 'Leaderboard'
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'modify-contest',
|
||||||
|
component: ModifyContestComponent,
|
||||||
|
title: 'Modify Contest',
|
||||||
|
canActivate: [AuthGuard, AdminGuard]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'add-contest',
|
||||||
|
component: AddContestComponent,
|
||||||
|
title: 'Add Contest',
|
||||||
|
canActivate: [AuthGuard, AdminGuard]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'add-student',
|
||||||
|
component: AddStudentComponent,
|
||||||
|
title: 'Add Student',
|
||||||
|
canActivate: [AuthGuard, AdminGuard]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'edit-student',
|
||||||
|
component: EditStudentComponent,
|
||||||
|
title: 'Edit Student',
|
||||||
|
canActivate: [AuthGuard, AdminGuard]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'add-flag',
|
||||||
|
component: AddFlagComponent,
|
||||||
|
title: 'Add Flag',
|
||||||
|
canActivate: [AuthGuard, AdminGuard]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'unauthorized',
|
||||||
|
component: UNAUTHORIZEDComponent,
|
||||||
|
title: 'You think youre slick eh?'
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'create-image',
|
||||||
|
component: CreateImageComponent,
|
||||||
|
title: 'Create Image'
|
||||||
|
}
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forRoot(routes)],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
|
||||||
|
export class AppRoutingModule{}
|
@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
Jordan Latimer
|
||||||
|
Alex Miller
|
||||||
|
|
||||||
|
CSS for both Contest Pages
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: grid;
|
||||||
|
background-color: green;
|
||||||
|
|
||||||
|
/* columns and rows sizes */
|
||||||
|
grid-template-columns: 15% 10% auto 20%;
|
||||||
|
grid-template-rows: 10% 40% 10% auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name of the contest at the top left */
|
||||||
|
#Contest_Name {
|
||||||
|
grid-row: 1/2;
|
||||||
|
grid-column: 1/3;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 175%;
|
||||||
|
}
|
||||||
|
#Contest_Name * {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sidebar thats scrollable for different flags */
|
||||||
|
#Flag_Sidebar {
|
||||||
|
background-color: white;
|
||||||
|
grid-row: 2/5;
|
||||||
|
grid-column: 1/2;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#Flag_Sidebar ul {
|
||||||
|
list-style-type: none;
|
||||||
|
height: 80%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
background-color: white;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* details for each flag (name, description, etc) */
|
||||||
|
#Flag_Deats {
|
||||||
|
background-color: white;
|
||||||
|
grid-row: 2/3;
|
||||||
|
grid-column: 2/4;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notificationMessage {
|
||||||
|
background-color: rgba(65, 145, 55, 0.511);
|
||||||
|
margin-left: 50%;
|
||||||
|
grid-row: 3/4;
|
||||||
|
grid-column: 3/5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notificationMessage {
|
||||||
|
background-color: rgba(65, 145, 55, 0.511);
|
||||||
|
margin-left: 50%;
|
||||||
|
grid-row: 3/4;
|
||||||
|
grid-column: 3/5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#Notifications.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.notification-banner {
|
||||||
|
background-color: rgba(65, 145, 55, 0.511);
|
||||||
|
margin-left: 50%;
|
||||||
|
grid-row: 3/4;
|
||||||
|
grid-column: 3/5;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Submitting the flag */
|
||||||
|
#Submit_Flag {
|
||||||
|
background-color: white;
|
||||||
|
grid-row: 3/4;
|
||||||
|
grid-column: 2/4;
|
||||||
|
|
||||||
|
}
|
||||||
|
#Submit_Flag_Stuff * {
|
||||||
|
height: 40%;
|
||||||
|
width: 15%;
|
||||||
|
}
|
||||||
|
#Submit_Butt {
|
||||||
|
background-color: lawngreen;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
#Submit_Flag h3 {
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.Incorrect, .Correct {
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 125%;
|
||||||
|
}
|
||||||
|
.Incorrect {
|
||||||
|
background-color: red;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.Correct {
|
||||||
|
background-color: lawngreen;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* hints for each flag */
|
||||||
|
#Hints {
|
||||||
|
background-color: white;
|
||||||
|
grid-row: 2/4;
|
||||||
|
grid-column: 4/5;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
background-color: white;
|
||||||
|
resize: none;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Linux Shell for commands */
|
||||||
|
#Linux_Shell {
|
||||||
|
grid-row: 4/5;
|
||||||
|
grid-column: 2/5;
|
||||||
|
color: lawngreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* when a flag is selected */
|
||||||
|
.selected-flag {
|
||||||
|
background-color: lightgray;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flags and Hints Labels */
|
||||||
|
.FlagHint {
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
margin: 10px;
|
||||||
|
font-size: 200%;
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
<!--
|
||||||
|
Alex Miller
|
||||||
|
Jordan Latimer
|
||||||
|
|
||||||
|
Contest Page for Users
|
||||||
|
-->
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title> NMU CTF Contest Page </title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Bar at top for moving tabs, this is for the practice contest -->
|
||||||
|
<ng-container *ngIf="isPractice">
|
||||||
|
<a href="#" (click)="confirmSelection(1); $event.preventDefault()">Leave Practice</a>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Show if NOT Admin and NOT in Practice -->
|
||||||
|
<ng-container id="Taskbar" *ngIf="!isPractice && !isAdmin">
|
||||||
|
<a id="Current_Page" href="#" (click)="navtoPageUM(); $event.preventDefault()">Contest</a>
|
||||||
|
<a href="#" (click)="confirmSelection(2); $event.preventDefault()">Profile</a>
|
||||||
|
</ng-container>
|
||||||
|
<!-- Contest name -->
|
||||||
|
<div id="Contest_Name">
|
||||||
|
<h1 id="ContHeader"></h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sidebar thats scrollable for different flags -->
|
||||||
|
<div id="Flag_Sidebar">
|
||||||
|
<h2 class="FlagHint"> Flags </h2>
|
||||||
|
<ul id="FlagList">
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- details for each flag (name, description etc) -->
|
||||||
|
<div id="Flag_Deats">
|
||||||
|
<div id ="DefaultMessage">
|
||||||
|
<h2>Welcome to NMU CTF</h2>
|
||||||
|
<p>There are currently no contests active at this time</p>
|
||||||
|
</div>
|
||||||
|
<h2 id="ContName"></h2>
|
||||||
|
<p id="ContDesc"></p>
|
||||||
|
<h2 id="Flag_Name"></h2>
|
||||||
|
<p id="Desc"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Submitting the flag -->
|
||||||
|
<div id="Submit_Flag">
|
||||||
|
<div id="Submit_Flag_Stuff">
|
||||||
|
<h3> Submit Flag: </h3>
|
||||||
|
<input type="text" placeholder="NMUCTF${FLAG}">
|
||||||
|
<input id="Submit_Butt" type="submit" value="Submit" (click)="onSubmission()">
|
||||||
|
<p id="Submission_Result"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="notificationMessage" id="notificationMessage" class="notificationMessage">
|
||||||
|
{{ notificationMessage }}
|
||||||
|
</div>
|
||||||
|
<!-- Hints for each flag -->
|
||||||
|
<div id="Hints">
|
||||||
|
<div id="Hint_Stuff">
|
||||||
|
<h2 class="FlagHint"> Hints </h2>
|
||||||
|
<div id="Hint_Butts">
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<textarea readonly rows="15" cols="30" id="Hint_Desc"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Linux Shell for commands -->
|
||||||
|
<div id="Linux_Shell"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ContestPageComponent } from './contest-page.component';
|
||||||
|
|
||||||
|
describe('ContestPageComponent', () => {
|
||||||
|
let component: ContestPageComponent;
|
||||||
|
let fixture: ComponentFixture<ContestPageComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [ContestPageComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ContestPageComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,88 @@
|
|||||||
|
import { Component, ElementRef, OnInit, Renderer2 } from '@angular/core';
|
||||||
|
import { NgIf } from '@angular/common';
|
||||||
|
import { Router } from '@angular/router'
|
||||||
|
import { loadPage, handleSubmission, getAdmin } from '../Helper/ContestSetup';
|
||||||
|
import { getEmail, gotoPage } from '../Helper/Helpers';
|
||||||
|
import { TerminalService } from '../Helper/terminal.service';
|
||||||
|
import { SocketIOService } from '../notifications/socket-io.service';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-contest-page',
|
||||||
|
imports: [NgIf],
|
||||||
|
templateUrl: './contest-page.component.html',
|
||||||
|
styleUrl: './contest-page.component.css'
|
||||||
|
})
|
||||||
|
|
||||||
|
export class ContestPageComponent implements OnInit {
|
||||||
|
isPractice = window.location.href.includes("Prac");
|
||||||
|
isAdmin: boolean = this.isItAdmin();
|
||||||
|
notificationMessage: string | null = null;
|
||||||
|
|
||||||
|
constructor(private renderer: Renderer2,
|
||||||
|
private el: ElementRef,
|
||||||
|
private router: Router,
|
||||||
|
private terminalService: TerminalService,
|
||||||
|
private socketService: SocketIOService) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
// attach to terminal onload
|
||||||
|
this.terminalService.reattachTerminal();
|
||||||
|
//this.socketService.connect();
|
||||||
|
loadPage(this.renderer, this.el, this);
|
||||||
|
/*this.socketService.listen('submission-notification').subscribe((data: any) => {
|
||||||
|
console.log('Received notification:', data);
|
||||||
|
this.notificationMessage = `${data.email} solved ${data.flagName}!`;
|
||||||
|
console.log('Updated notif:', this.notificationMessage);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.notificationMessage = null;
|
||||||
|
}, 5000);
|
||||||
|
});*/
|
||||||
|
|
||||||
|
this.socketService.listen('test-event').subscribe((data: any) => {
|
||||||
|
console.log('Test event received:', data);
|
||||||
|
this.notificationMessage = 'Test event received';
|
||||||
|
});
|
||||||
|
this.socketService.emit('test-event', { message: 'Hello from client' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear and set terminal for new flag
|
||||||
|
async ClearAndSetTerminal() {
|
||||||
|
await this.terminalService.ClearTerminal();
|
||||||
|
await this.terminalService.reattachTerminal();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmission(): void{
|
||||||
|
handleSubmission(this.el, this.socketService);
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmSelection(num: number){
|
||||||
|
const confirmSelection_ = confirm("You will lose your current flag progress, are you sure?");
|
||||||
|
if(confirmSelection_){
|
||||||
|
const email = getEmail();
|
||||||
|
if(num === 1)
|
||||||
|
gotoPage(this.router, '/user-menu');
|
||||||
|
else
|
||||||
|
gotoPage(this.router, '/user-profileAC');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
console.log("Profile navigation canceled");
|
||||||
|
}
|
||||||
|
|
||||||
|
navtoPageA(): void {
|
||||||
|
gotoPage(this.router, '/contest-page');
|
||||||
|
}
|
||||||
|
|
||||||
|
navtoPageUM(): void{
|
||||||
|
gotoPage(this.router, '/user-menu')
|
||||||
|
}
|
||||||
|
|
||||||
|
isItAdmin(): boolean{
|
||||||
|
if(getAdmin() === "True")
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
Alex Miller
|
||||||
|
Jordan Latimer
|
||||||
|
|
||||||
|
CSS for Create image
|
||||||
|
*/
|
||||||
|
|
||||||
|
#body {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 30% 30% auto;
|
||||||
|
grid-template-rows: auto auto auto auto auto;
|
||||||
|
background-color: lightgreen;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
#HeaderDiv {
|
||||||
|
grid-row: 1/2;
|
||||||
|
grid-column: 1/4;
|
||||||
|
background-color: green;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* form stuff like image name, file name, add file etc */
|
||||||
|
#FormDiv {
|
||||||
|
grid-row: 2/3;
|
||||||
|
grid-column: 1/4;
|
||||||
|
background-color: green;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
#FormStuff {
|
||||||
|
margin: 10px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Contents of the File */
|
||||||
|
#FileContentsDiv {
|
||||||
|
grid-row: 3/6;
|
||||||
|
grid-column: 1/2;
|
||||||
|
background-color: green;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
#FileContents {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* drop box to drop files */
|
||||||
|
#DropBoxDiv {
|
||||||
|
grid-row: 3/6;
|
||||||
|
grid-column: 2/3;
|
||||||
|
background-color: green;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
#DropBox {
|
||||||
|
border: 3px black dashed;
|
||||||
|
align-content: center;
|
||||||
|
justify-items: center;
|
||||||
|
background-color: white;
|
||||||
|
margin: 10px;
|
||||||
|
height: 310px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* file tree visual */
|
||||||
|
#FileTreeDiv {
|
||||||
|
grid-row: 3/6;
|
||||||
|
grid-column: 3/4;
|
||||||
|
background-color: green;
|
||||||
|
margin: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#FileTree {
|
||||||
|
background-color: white;
|
||||||
|
margin: 10px;
|
||||||
|
height: 310px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
overflow-x: scroll;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
.Green {
|
||||||
|
background-color: lawngreen;
|
||||||
|
}
|
||||||
|
.Blue {
|
||||||
|
background-color: blue;
|
||||||
|
color: white;
|
||||||
|
height: 25%;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
font-weight: bold;
|
||||||
|
width: 10%;
|
||||||
|
height: 25%;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
<!--
|
||||||
|
Alex Miller
|
||||||
|
Jordan Latimer
|
||||||
|
|
||||||
|
Page for creating images for flags
|
||||||
|
-->
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title> Create Image </title>
|
||||||
|
</head>
|
||||||
|
<body id="body">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div id="HeaderDiv">
|
||||||
|
<h1> Create Image </h1>
|
||||||
|
<button class="Blue" (click)="navtoPageMC()">Back to Modify</button>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- File Contents -->
|
||||||
|
<div id="FileContentsDiv">
|
||||||
|
<h1> Contents of File </h1>
|
||||||
|
<div id="FileContents">
|
||||||
|
<div id="TextBox">
|
||||||
|
<textarea id="filecontents" placeholder="Enter contents of files here" cols="40" rows="20"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Form for adding files and directories -->
|
||||||
|
<div id="FormDiv">
|
||||||
|
<h1> File and Image </h1>
|
||||||
|
<div id="FormStuff">
|
||||||
|
<label> Image Name: </label>
|
||||||
|
<input id="ImageName" type="text" placeholder="image name">
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<label> Name: </label>
|
||||||
|
<input id="filename" type="text" placeholder="Folder or File name">
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<button class="Green" (click)="getFile(true)"> Add Folder</button>
|
||||||
|
<button class="Green" (click)="getFile(false)"> Add File </button>
|
||||||
|
<button (click)="DeleteFile()" style="background-color: red;"> Delete </button>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<button class="Green" (click)="CreateImage()" type="submit">Create Image</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Drop Box -->
|
||||||
|
<div id="DropBoxDiv">
|
||||||
|
<h1> Drop Box </h1>
|
||||||
|
<div id="DropBox">
|
||||||
|
<p> Drag and Drop files here or: </p>
|
||||||
|
<input type="file" id="FileInput" multiple (change)="onFileChange($event)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- File Tree -->
|
||||||
|
<div id="FileTreeDiv">
|
||||||
|
<h1> File Tree </h1>
|
||||||
|
<div id="FileTree">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CreateImageComponent } from './create-image.component';
|
||||||
|
|
||||||
|
describe('CreateImageComponent', () => {
|
||||||
|
let component: CreateImageComponent;
|
||||||
|
let fixture: ComponentFixture<CreateImageComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [CreateImageComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(CreateImageComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,314 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { TreeService } from '../Helper/tree.service';
|
||||||
|
import { getEmail, gotoPage } from '../Helper/Helpers';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-create-image',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './create-image.component.html',
|
||||||
|
styleUrl: './create-image.component.css'
|
||||||
|
})
|
||||||
|
export class CreateImageComponent {
|
||||||
|
|
||||||
|
private root: TreeService = new TreeService("Root", "", true, 1, null);
|
||||||
|
private allFiles: File[] | null = null;
|
||||||
|
|
||||||
|
constructor(private router: Router) {}
|
||||||
|
|
||||||
|
// initialize
|
||||||
|
ngOnInit() : void {
|
||||||
|
|
||||||
|
// create the root and refresh the file tree
|
||||||
|
this.RefreshFileTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshFileTree() {
|
||||||
|
|
||||||
|
const filetree = document.getElementById('FileTree') as HTMLUListElement;
|
||||||
|
filetree.innerHTML = '';
|
||||||
|
|
||||||
|
// create list and add the root
|
||||||
|
const list = document.createElement('ul');
|
||||||
|
|
||||||
|
// go through full tree
|
||||||
|
const fulllist = this.AddListItem(list, this.root);
|
||||||
|
filetree.appendChild(fulllist);
|
||||||
|
|
||||||
|
this.ClearInputs();
|
||||||
|
}
|
||||||
|
|
||||||
|
// uploading files through dropbox
|
||||||
|
onFileChange(event: any) {
|
||||||
|
const files: FileList = event.target.files;
|
||||||
|
if (files.length > 0) {
|
||||||
|
if (!this.allFiles) this.allFiles = []; // set the array up if it's null
|
||||||
|
for (let i=0; i < files.length; i++) {
|
||||||
|
this.allFiles?.push(files[i]);
|
||||||
|
|
||||||
|
// add to tree
|
||||||
|
if (this.AddFileToTree(files[i], false, "", "")) return;
|
||||||
|
}
|
||||||
|
// reset files inside dropbox?
|
||||||
|
this.RefreshFileTree();
|
||||||
|
this.ClearInputs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the file to the tree
|
||||||
|
AddFileToTree(file: File | null, dir: boolean, filename: string, contents: string) : boolean{
|
||||||
|
const elements = document.querySelectorAll('.selected-node');
|
||||||
|
|
||||||
|
// make sure a parent node is selected
|
||||||
|
if (elements.length == 0) {
|
||||||
|
alert('Must select a Directory');
|
||||||
|
this.ClearInputs();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure all the nodes selected are directories
|
||||||
|
for (let i=0; i < elements.length; i++) {
|
||||||
|
let li = elements[i] as HTMLLIElement;
|
||||||
|
if (li.firstChild?.textContent?.substring(0,4) !== "(D)-") {
|
||||||
|
alert('Must select a directory');
|
||||||
|
this.ClearInputs();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop through all the elements adding the name of the file to the tree
|
||||||
|
for (let i=0; i < elements.length; i++) {
|
||||||
|
|
||||||
|
// get the parent node selected
|
||||||
|
let li = elements[i] as HTMLLIElement;
|
||||||
|
let results = this.getNodeNumFromClass(li);
|
||||||
|
let parentname: string = (results[0]).toString();
|
||||||
|
let parentnum: number = Number(results[1]);
|
||||||
|
let node = this.root.getNodeByName(parentname,parentnum);
|
||||||
|
|
||||||
|
// if the parent node isnt a directory, dont add it
|
||||||
|
if (node?.getDir() == false) {
|
||||||
|
alert('can only add files/folders to directories');
|
||||||
|
this.ClearInputs();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the filename isnt taken already
|
||||||
|
if (!node?.IsNameTaken(filename)) {
|
||||||
|
|
||||||
|
// get the number of nodes of this name for nodenum
|
||||||
|
let nodes: TreeService[] = [];
|
||||||
|
nodes = this.root.getNodesSameName(filename, nodes);
|
||||||
|
let nodenum = nodes.length + 1;
|
||||||
|
|
||||||
|
// add the node to the tree
|
||||||
|
if (file == null) { // not using dropbox
|
||||||
|
let newFile = new TreeService(filename, contents, dir, nodenum, node);
|
||||||
|
node?.AddChild(newFile);
|
||||||
|
}
|
||||||
|
else { // using dropbox
|
||||||
|
let FileObjName : string = file?.name!;
|
||||||
|
let newFile = new TreeService(FileObjName, "", false, nodenum, node);
|
||||||
|
node?.AddChild(newFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert('That file name is already under this directory');
|
||||||
|
this.ClearInputs();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// get the file stuff through creating a new file
|
||||||
|
getFile(dir: boolean) {
|
||||||
|
let nameElement = document.getElementById('filename') as HTMLInputElement;
|
||||||
|
let filename: string = nameElement.value;
|
||||||
|
let contentsElement = document.getElementById('filecontents') as HTMLTextAreaElement;
|
||||||
|
let contents : string = contentsElement.value;
|
||||||
|
|
||||||
|
if(this.AddFileToTree(null, dir, filename, contents)) return;
|
||||||
|
this.RefreshFileTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// use recursion to go through the tree adding each child
|
||||||
|
AddListItem(list: HTMLUListElement, node: TreeService) {
|
||||||
|
|
||||||
|
// add node to current list
|
||||||
|
const nodeitem = document.createElement('li');
|
||||||
|
const spanitem = document.createElement('span');
|
||||||
|
|
||||||
|
if (node.getDir()) spanitem.textContent = '(D)-' + node.getName();
|
||||||
|
else spanitem.textContent = node.getName();
|
||||||
|
|
||||||
|
// add the name and nodenum to the class list
|
||||||
|
spanitem.classList.add(node.getName() + node.getNodeNum());
|
||||||
|
|
||||||
|
spanitem.addEventListener('click', function(nodeitem) {
|
||||||
|
const listitem = nodeitem.target as HTMLLIElement;
|
||||||
|
listitem?.classList.toggle('selected-node');
|
||||||
|
});
|
||||||
|
|
||||||
|
nodeitem.appendChild(spanitem);
|
||||||
|
// add to list
|
||||||
|
list.appendChild(nodeitem);
|
||||||
|
|
||||||
|
// if node has children
|
||||||
|
if (node.getChildren().length > 0) {
|
||||||
|
let nodechildren = node.getChildren();
|
||||||
|
const childlist = document.createElement('ul');
|
||||||
|
for (var i=0; i < nodechildren.length; i++) {
|
||||||
|
let child = nodechildren[i];
|
||||||
|
list.appendChild(this.AddListItem(childlist, child));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the whole list
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get nodenum from class list
|
||||||
|
getNodeNumFromClass(item : HTMLLIElement) {
|
||||||
|
|
||||||
|
// get the class list of the selected item
|
||||||
|
const WholeClassList = item.classList;
|
||||||
|
const namenum = WholeClassList[0];
|
||||||
|
|
||||||
|
// break up the string into the two parts
|
||||||
|
const index = namenum.search(/\d/);
|
||||||
|
const name = namenum.substring(0,index);
|
||||||
|
let num : number = Number(namenum.substring(index));
|
||||||
|
|
||||||
|
const results = [name,num];
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete a file from the tree
|
||||||
|
DeleteFile() {
|
||||||
|
|
||||||
|
const items = document.querySelectorAll('.selected-node');
|
||||||
|
|
||||||
|
// go through each item selected and remove it from parent
|
||||||
|
for (var i=0; i < items.length; i++) {
|
||||||
|
|
||||||
|
let li = items[i] as HTMLLIElement;
|
||||||
|
|
||||||
|
// get the name and num from class list
|
||||||
|
let results = this.getNodeNumFromClass(li);
|
||||||
|
let name : string = results[0].toString();
|
||||||
|
let num : number = Number(results[1]);
|
||||||
|
|
||||||
|
let node = this.root.getNodeByName(name,num);
|
||||||
|
|
||||||
|
// if root then delete everything
|
||||||
|
if (node?.getName() === 'Root') {
|
||||||
|
this.root.DeleteTheChildren();
|
||||||
|
this.RefreshFileTree();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the correct nodes
|
||||||
|
const parent = node?.getParent();
|
||||||
|
const newchildren = parent?.getChildren().filter(child => child.getName() !== node?.getName());
|
||||||
|
parent?.setChildren(newchildren!);
|
||||||
|
}
|
||||||
|
|
||||||
|
// refresh the tree visual
|
||||||
|
this.RefreshFileTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the actual Image
|
||||||
|
CreateImage() {
|
||||||
|
|
||||||
|
const imagename = this.getImageName();
|
||||||
|
if (imagename === true) return;
|
||||||
|
|
||||||
|
this.root.DeleteParentRef();
|
||||||
|
this.SendTree(imagename);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the name of the image and add the time to it
|
||||||
|
getImageName() {
|
||||||
|
const imgElement = document.getElementById('ImageName') as HTMLInputElement;
|
||||||
|
const imgname = imgElement.value;
|
||||||
|
|
||||||
|
// make sure name is included
|
||||||
|
if (imgname === undefined || imgname === null || imgname === '') {
|
||||||
|
alert('Must include a name for the image');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// docker states all image names must be lowercase, so have to check for that
|
||||||
|
if (/[A-Z]/.test(imgname) === true) {
|
||||||
|
alert('Image name must be all lowercase');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the current time to add to the name so no images have same name
|
||||||
|
let time = new Date().toLocaleTimeString([], {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// get rid of the colons and add the time to the image name
|
||||||
|
time = time.replace(/:/g,'');
|
||||||
|
return imgname + time;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the full tree structure to the server side
|
||||||
|
async SendTree(imgname : string) {
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
root: this.root, imgname: imgname, email: getEmail()
|
||||||
|
};
|
||||||
|
|
||||||
|
// create a form data to hold both JSON and the files
|
||||||
|
const formdata = new FormData();
|
||||||
|
|
||||||
|
// append each file to the form data with the key files
|
||||||
|
this.allFiles?.forEach((file : File) => {
|
||||||
|
formdata.append("files", file, file.name);
|
||||||
|
console.log(file.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
formdata.append('data', JSON.stringify(data));
|
||||||
|
|
||||||
|
// send the post request
|
||||||
|
const res = await fetch('api/AddImage', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formdata
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
// go back to modify contest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear the input fields after each addition/error
|
||||||
|
ClearInputs() {
|
||||||
|
|
||||||
|
// contents of file
|
||||||
|
let contentElement = document.getElementById('filecontents') as HTMLTextAreaElement;
|
||||||
|
contentElement.value = '';
|
||||||
|
|
||||||
|
// name of file
|
||||||
|
let filenameElement = document.getElementById('filename') as HTMLInputElement;
|
||||||
|
filenameElement.value = '';
|
||||||
|
|
||||||
|
// file input box
|
||||||
|
let fileinputElement = document.getElementById('FileInput') as HTMLInputElement;
|
||||||
|
fileinputElement.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// go back to Modify Contest
|
||||||
|
navtoPageMC() {
|
||||||
|
gotoPage(this.router, '/modify-contest');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
html {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
h1, h3 {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#Inputs {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: max-content max-content;
|
||||||
|
grid-gap: 5px;
|
||||||
|
margin-left: 25%;
|
||||||
|
margin-top: 25%;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
#Submit {
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#submit * {
|
||||||
|
width: 15%;
|
||||||
|
height: 5%;
|
||||||
|
}
|
||||||
|
#Submit button {
|
||||||
|
background-color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hint1Text, #hint2Text, #hint3Text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show the respective hint fields based on the selected radio button */
|
||||||
|
#hint1:checked ~ #hint1Text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hint2:checked ~ #hint1Text,
|
||||||
|
#hint2:checked ~ #hint2Text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hint3:checked ~ #hint1Text,
|
||||||
|
#hint3:checked ~ #hint2Text,
|
||||||
|
#hint3:checked ~ #hint3Text{
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* for Create Image */
|
||||||
|
#body {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 25% auto auto auto auto auto 25%;
|
||||||
|
grid-template-columns: 20% 20% 20% 20% 20%;
|
||||||
|
justify-items: center;
|
||||||
|
}
|
||||||
|
#labels {
|
||||||
|
grid-row: 1/2;
|
||||||
|
grid-column: 3/4;
|
||||||
|
}
|
||||||
|
#Butts {
|
||||||
|
grid-row: 2/3;
|
||||||
|
grid-column: 3/4;
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
#TextBox {
|
||||||
|
grid-row: 3/6;
|
||||||
|
grid-column: 1/3;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
#DropBox {
|
||||||
|
grid-row: 3/6;
|
||||||
|
grid-column: 3/4;
|
||||||
|
border: 3px black dashed;
|
||||||
|
align-content: center;
|
||||||
|
justify-items: center;
|
||||||
|
}
|
||||||
|
#SendFile {
|
||||||
|
grid-row: 6/7;
|
||||||
|
grid-column: 3/4;
|
||||||
|
}
|
||||||
|
#FileTree {
|
||||||
|
grid-row: 3/6;
|
||||||
|
grid-column: 4/6;
|
||||||
|
}
|
||||||
|
#Submition {
|
||||||
|
grid-row: 7/8;
|
||||||
|
grid-column: 3/4;
|
||||||
|
}
|
||||||
|
.selected-node {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
<!--
|
||||||
|
Alex Miller
|
||||||
|
Jordan Latimer
|
||||||
|
|
||||||
|
Changing passwords of users for Admin
|
||||||
|
-->
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title> Change Password </title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- everything needed for adding a student -->
|
||||||
|
<h1> Change Password </h1>
|
||||||
|
<h3 id="Email">Email: {{emailinput}}</h3>
|
||||||
|
<form id="Inputs">
|
||||||
|
<label> Change Password: </label>
|
||||||
|
<input *ngIf="!showPass" id="PS1" type="password" [(ngModel)]="newPassword" name="newPassword" align="middle">
|
||||||
|
<input *ngIf="showPass" id="PS1" type="text" [(ngModel)]="newPassword" name="newPassword" align="middle">
|
||||||
|
<label> Confirm Password: </label>
|
||||||
|
<input *ngIf="!showPass" id="PS2" type="password" [(ngModel)]="confirmPassword" name="confirmPassword" align="middle">
|
||||||
|
<input *ngIf="showPass" id="PS2" type="text" [(ngModel)]="confirmPassword" name="confirmPassword" align="middle">
|
||||||
|
<label> Show Password: </label>
|
||||||
|
<input type='checkbox' (click)='ShowPassword()' id='ShowPass'>
|
||||||
|
</form>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<!-- submitting or closing -->
|
||||||
|
<div id="Submit">
|
||||||
|
<input id="EditPass" type="submit" (click)="EditPass();">
|
||||||
|
<button onclick="self.close();"> Cancel </button>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { EditStudentComponent } from './edit-student.component';
|
||||||
|
|
||||||
|
describe('EditStudentComponent', () => {
|
||||||
|
let component: EditStudentComponent;
|
||||||
|
let fixture: ComponentFixture<EditStudentComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [EditStudentComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(EditStudentComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,51 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { NgIf } from '@angular/common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-edit-student',
|
||||||
|
imports: [FormsModule, NgIf],
|
||||||
|
templateUrl: './edit-student.component.html',
|
||||||
|
styleUrl: './edit-student.component.css'
|
||||||
|
})
|
||||||
|
export class EditStudentComponent {
|
||||||
|
emailinput: string | null = sessionStorage.getItem('selectedUser');;
|
||||||
|
showPass: boolean = false;
|
||||||
|
newPassword: string = '';
|
||||||
|
confirmPassword: string = '';
|
||||||
|
// edit the password of a student
|
||||||
|
async EditPass() {
|
||||||
|
const ps1 = this.newPassword;
|
||||||
|
const ps2 = this.confirmPassword
|
||||||
|
// password protection
|
||||||
|
if (ps1.length < 10 || ps1 == "") {
|
||||||
|
alert("password must be at least 10 characters");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps1 == ps2) { // if both passwords are equal, continue
|
||||||
|
const data = { email: this.emailinput, password: ps1};
|
||||||
|
const res = await fetch('api/UpdateStudent', {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
'Content-Type' : 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
console.log("Success: changing password");
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("ERROR: changing password");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert('Passwords do not match');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ShowPassword(){
|
||||||
|
this.showPass = !this.showPass;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CanActivate, Router } from '@angular/router';
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class AdminGuard implements CanActivate {
|
||||||
|
constructor(private router: Router) {}
|
||||||
|
|
||||||
|
canActivate(): boolean {
|
||||||
|
const role = sessionStorage.getItem('role');
|
||||||
|
if (role === 'admin') return true;
|
||||||
|
|
||||||
|
this.router.navigate(['/unauthorized']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CanActivate, Router } from '@angular/router';
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class UserGuard implements CanActivate {
|
||||||
|
constructor(private router: Router) {}
|
||||||
|
|
||||||
|
canActivate(): boolean {
|
||||||
|
const role = sessionStorage.getItem('role');
|
||||||
|
if (role === 'user') return true;
|
||||||
|
|
||||||
|
this.router.navigate(['/unauthorized']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CanActivate, Router } from '@angular/router';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AuthGuard implements CanActivate {
|
||||||
|
constructor(private router: Router) {}
|
||||||
|
|
||||||
|
canActivate(): boolean {
|
||||||
|
const email = sessionStorage.getItem('email');
|
||||||
|
if (!email) {
|
||||||
|
this.router.navigate(['/unauthorized']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
Alex Miller
|
||||||
|
Jordan Latimer
|
||||||
|
|
||||||
|
CSS for Profile Page User Side
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
/* columns and rows sizes */
|
||||||
|
grid-template-columns: auto auto auto auto auto auto;
|
||||||
|
grid-template-rows: auto auto auto auto auto;
|
||||||
|
|
||||||
|
background-color: lightgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
#Header {
|
||||||
|
grid-row: 1/2;
|
||||||
|
grid-column: 1/7;
|
||||||
|
background-color: green;
|
||||||
|
margin: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* past contests table */
|
||||||
|
#PastContests {
|
||||||
|
grid-column: 1/4;
|
||||||
|
grid-row: 2/6;
|
||||||
|
margin: 50px;
|
||||||
|
height: 500px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
#Past_Contests {
|
||||||
|
margin: 10px;
|
||||||
|
height: 75%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stuff with no ID */
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border: 3px solid black;
|
||||||
|
border-collapse: seperate;
|
||||||
|
border-spacing: 0;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
table thead {
|
||||||
|
top: 0;
|
||||||
|
position: sticky;
|
||||||
|
position: -webkit-sticky;
|
||||||
|
z-index: 1;
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
border: 3px solid black;
|
||||||
|
text-align: center;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
display: inline;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Leaderboard popup window */
|
||||||
|
#Leaderboard {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#Leaderboard h1 {
|
||||||
|
grid-row: 1/2;
|
||||||
|
grid-column: 2/5;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#Leaderboard h2 {
|
||||||
|
grid-row: 2/3;
|
||||||
|
grid-column: 3/4;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#LeaderTable {
|
||||||
|
grid-column: 2/6;
|
||||||
|
grid-row: 4/5;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Leaderboard</title>
|
||||||
|
</head>
|
||||||
|
<body id="Leaderboard">
|
||||||
|
|
||||||
|
<div id="Header">
|
||||||
|
<h1> Leaderboard For </h1>
|
||||||
|
<h2 id="ContestName"> </h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- table for leaderboard -->
|
||||||
|
<div id="LeaderTable">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th> Rank </th>
|
||||||
|
<th> Name </th>
|
||||||
|
<th> Flags </th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="LeaderTBody">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { LeaderboardComponent } from './leaderboard.component';
|
||||||
|
|
||||||
|
describe('LeaderboardComponent', () => {
|
||||||
|
let component: LeaderboardComponent;
|
||||||
|
let fixture: ComponentFixture<LeaderboardComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [LeaderboardComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(LeaderboardComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-leaderboard',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './leaderboard.component.html',
|
||||||
|
styleUrl: './leaderboard.component.css'
|
||||||
|
})
|
||||||
|
export class LeaderboardComponent {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
html, app-login html {
|
||||||
|
background-color: green;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
app-login body {
|
||||||
|
height: 92vh;
|
||||||
|
margin: 25px;
|
||||||
|
display: grid;
|
||||||
|
background-color: white;
|
||||||
|
grid-template-rows: auto auto auto;
|
||||||
|
grid-template-columns: auto auto auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#inputFields {
|
||||||
|
grid-row: 2/3;
|
||||||
|
grid-column: 2/3;
|
||||||
|
background-color: green;
|
||||||
|
text-align: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#loginform {
|
||||||
|
background-color: lightgreen;
|
||||||
|
margin: 10px;
|
||||||
|
height: 65%;
|
||||||
|
}
|
||||||
|
#loginform * {
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 400%;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
width: 12%;
|
||||||
|
height: 7%;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 100%;
|
||||||
|
background-color: lawngreen;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
<!--
|
||||||
|
Alex Miller
|
||||||
|
Jordan Latimer
|
||||||
|
|
||||||
|
Login for CTF
|
||||||
|
-->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Login</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="inputFields">
|
||||||
|
<h1>NMU CTF</h1>
|
||||||
|
<form id="loginform" (ngSubmit)="SendCreds()">
|
||||||
|
<br>
|
||||||
|
<label for="EM">Email:</label>
|
||||||
|
<input id="EM" type="text" [(ngModel)]="email" name="email" />
|
||||||
|
<br>
|
||||||
|
<label for="PS">Password:</label>
|
||||||
|
<input *ngIf="!showPass" id="PS" type="password" [(ngModel)]="password" name="password" />
|
||||||
|
<input *ngIf="showPass" id="PS" type="text" [(ngModel)]="password" name="password" />
|
||||||
|
<br>
|
||||||
|
<label for="ShowPass">Show Password</label>
|
||||||
|
<input type="checkbox" id="ShowPass" (change)="showPassword()" />
|
||||||
|
<br>
|
||||||
|
<button type="submit">Login</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { LoginComponent } from './login.component';
|
||||||
|
|
||||||
|
describe('LoginComponent', () => {
|
||||||
|
let component: LoginComponent;
|
||||||
|
let fixture: ComponentFixture<LoginComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [LoginComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(LoginComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,48 @@
|
|||||||
|
import { Component, ViewEncapsulation } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { NgIf } from '@angular/common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
imports: [FormsModule, NgIf],
|
||||||
|
templateUrl: './login.component.html',
|
||||||
|
|
||||||
|
styleUrls: ['./login.component.css'],
|
||||||
|
encapsulation: ViewEncapsulation.None
|
||||||
|
})
|
||||||
|
|
||||||
|
export class LoginComponent {
|
||||||
|
email: string = '';
|
||||||
|
password: string = '';
|
||||||
|
showPass: boolean = false;
|
||||||
|
constructor(private router: Router) {}
|
||||||
|
async SendCreds() {
|
||||||
|
const data = { email: this.email, password: this.password };
|
||||||
|
const res = await fetch('api/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
if (res.ok) { // okay response from server
|
||||||
|
const RB = await res.json();
|
||||||
|
if (RB && RB.redirectTo) { // redirect client to new webpage
|
||||||
|
sessionStorage.setItem('email', RB.email);
|
||||||
|
sessionStorage.setItem('role', RB.redirectTo === '/admin-contest' ? 'admin' : 'user');
|
||||||
|
this.router.navigate([RB.redirectTo]);
|
||||||
|
}
|
||||||
|
else { // error handling
|
||||||
|
console.error("redirect URL issue");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { // what user entered doesnt match up to db
|
||||||
|
alert("Invalid Credentials");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showPassword() {
|
||||||
|
this.showPass = !this.showPass;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
export interface Admin {
|
||||||
|
AdminID: number,
|
||||||
|
Email: string,
|
||||||
|
Password: string,
|
||||||
|
ActiveFlag: string
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
export interface Contest {
|
||||||
|
ContestID: number,
|
||||||
|
Name: string,
|
||||||
|
IsActive: number,
|
||||||
|
AdminID: number,
|
||||||
|
Description: string
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
export interface Flag{
|
||||||
|
FlagID: number,
|
||||||
|
Name: string,
|
||||||
|
Description: string,
|
||||||
|
ContestID: number,
|
||||||
|
Image: string,
|
||||||
|
Path: string,
|
||||||
|
Hint1: string,
|
||||||
|
Hint2: string,
|
||||||
|
Hint3: string
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
export interface Image{
|
||||||
|
ImageID: number,
|
||||||
|
Name: string,
|
||||||
|
AdminID: number
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
export interface Submission {
|
||||||
|
SubmissionID: number,
|
||||||
|
UserID: number,
|
||||||
|
FlagID: number,
|
||||||
|
IsCorrect: boolean,
|
||||||
|
Attempts: number
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
export interface User{
|
||||||
|
UserID: number,
|
||||||
|
Name: string,
|
||||||
|
Email: string,
|
||||||
|
Password: string,
|
||||||
|
Flags: number,
|
||||||
|
ActiveFlag: string,
|
||||||
|
AdminID: number
|
||||||
|
}
|
@ -0,0 +1,178 @@
|
|||||||
|
/*
|
||||||
|
Alex Miller
|
||||||
|
Jordan Latimer
|
||||||
|
|
||||||
|
CSS for Modifying Contests
|
||||||
|
|
||||||
|
*/
|
||||||
|
body {
|
||||||
|
background-color: lightblue;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto;
|
||||||
|
grid-template-rows: auto auto auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header stuff */
|
||||||
|
#HeaderStuff {
|
||||||
|
grid-row: 1/2;
|
||||||
|
grid-column: 1/3;
|
||||||
|
background-color: blue;
|
||||||
|
margin: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#DoneButt {
|
||||||
|
height: 25px;
|
||||||
|
width: 150px;
|
||||||
|
background-color: lawngreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Contests */
|
||||||
|
#Contests {
|
||||||
|
background-color: blue;
|
||||||
|
grid-row: 2/3;
|
||||||
|
grid-column: 1/2;
|
||||||
|
margin: 50px;
|
||||||
|
height: 500px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-contest {
|
||||||
|
background-color: lawngreen;
|
||||||
|
color: blue;
|
||||||
|
border: 2px solid lawngreen;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ContestContent, #FIContent {
|
||||||
|
background-color: white;
|
||||||
|
margin: 10px;
|
||||||
|
text-align: center;
|
||||||
|
height: 75%;
|
||||||
|
}
|
||||||
|
#ContestList, #FIList {
|
||||||
|
height: 75%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
#ActiveSpan {
|
||||||
|
color: lawngreen;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#FISpan {
|
||||||
|
color: blue;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flags and Images */
|
||||||
|
#FlagsAndImages {
|
||||||
|
background-color: blue;
|
||||||
|
grid-row: 2/3;
|
||||||
|
grid-column: 2/3;
|
||||||
|
margin: 50px;
|
||||||
|
height: 500px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Terminal area */
|
||||||
|
#Terminal {
|
||||||
|
background-color: blue;
|
||||||
|
grid-row: 3/4;
|
||||||
|
grid-column: 1/3;
|
||||||
|
margin: 50px;
|
||||||
|
height: 525px;
|
||||||
|
}
|
||||||
|
#TerminalHeader {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#TerminalHeader select, input {
|
||||||
|
margin: 10px;
|
||||||
|
width: 150px;
|
||||||
|
height: 25px;
|
||||||
|
}
|
||||||
|
#TerminalHeader input {
|
||||||
|
width: 150px;
|
||||||
|
height: 25px;
|
||||||
|
}
|
||||||
|
#Linux_Shell {
|
||||||
|
background-color: black;
|
||||||
|
height: 70%;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
#Buttons {
|
||||||
|
background-color: blue;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
font-weight: bold;
|
||||||
|
width: 20%;
|
||||||
|
height: 10%;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
.AddButt {
|
||||||
|
background-color: lawngreen;
|
||||||
|
}
|
||||||
|
.DeleteButt {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* selected items */
|
||||||
|
.selected {
|
||||||
|
background-color: lightgray;
|
||||||
|
}
|
||||||
|
.selected span {
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
.selected-flag {
|
||||||
|
background-color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tooltips */
|
||||||
|
.tooltip {
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.tooltip:hover .tooltipText {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
.tooltip .tooltipText {
|
||||||
|
visibility: hidden;
|
||||||
|
height: auto;
|
||||||
|
background-color: gray;
|
||||||
|
color: black;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dialog box for deleting flags and images */
|
||||||
|
#DialogBox {
|
||||||
|
text-align: center;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
padding: 100px;
|
||||||
|
}
|
||||||
|
#DialogBox input {
|
||||||
|
width: 50%;
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* anything else not related to any of above */
|
||||||
|
h1 {
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
height: 50%;
|
||||||
|
}
|
||||||
|
select, option {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
@ -0,0 +1,142 @@
|
|||||||
|
<!--
|
||||||
|
Jordan Latimer
|
||||||
|
Alex Miller
|
||||||
|
|
||||||
|
Modify Contest screen for Admin
|
||||||
|
-->
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
|
||||||
|
<title>Contest Menu</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- header stuff -->
|
||||||
|
<div id="HeaderStuff">
|
||||||
|
<h1> Modify Contests </h1>
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
This is where you can change anything involving <span style="color:white;">Contests</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<input type="button" id="DoneButt" (click)="navtoPageAP()" value="Back to Profile">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Contests -->
|
||||||
|
<div id="Contests">
|
||||||
|
<h1> Contests </h1>
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
select a <span style="color:white;">Contest</span> by <i>clicking</i> on one
|
||||||
|
<br>
|
||||||
|
<span style="color:lawngreen;">Add:</span> add a new contest
|
||||||
|
<br>
|
||||||
|
<span style="color:blue;">Active:</span> set a <i>selected</i> contest active and available for your students
|
||||||
|
<br>
|
||||||
|
<span style="color:red;">Delete:</span> delete a <i>selected</i> contest
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<div id="ContestContent">
|
||||||
|
|
||||||
|
<!-- buttons to manipulate contests -->
|
||||||
|
<div id="Buttons">
|
||||||
|
<input class="AddButt" type="button" (click)="addContestPopup()" value="Add">
|
||||||
|
<input type="button" (click)="setContestActive()" value="Activate">
|
||||||
|
<input class="DeleteButt" type="button" (click)="deleteContest();" value="Delete">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- list of contests -->
|
||||||
|
<div id="ContestList">
|
||||||
|
<ul id="ContestListItems">
|
||||||
|
<li *ngFor="let contest of contests" (click)="selectContest(contest.ContestID)"
|
||||||
|
[ngClass]="{'selected-contest': contest.ContestID === selectedContestId}">
|
||||||
|
{{ contest.Name }}
|
||||||
|
<span *ngIf="contest.ContestID === activeContest">⭐</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Flags and Images -->
|
||||||
|
<div id="FlagsAndImages">
|
||||||
|
<h1> Flags and Images </h1>
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
when selecting a contest, the <span style="color:white;">Flags</span> and <span style="color:white;">Images</span> of that contest will show up here.
|
||||||
|
<br>
|
||||||
|
each <span style="color:white;">Flag</span> <span style="color:red;">NEEDS</span> a <i>selected</i> contest to be <span style="color:lawngreen;">Added</span>
|
||||||
|
<br>
|
||||||
|
this is not the case for <span style="color:white;">Images</span>
|
||||||
|
<br>
|
||||||
|
Some <span style="color:white;">Flags</span> may show up with <span style="color: red;">NONE</span>
|
||||||
|
but be in different <span style="color:white;">Contests</span>
|
||||||
|
you can select <span style="color:white;">Flags</span> to delete or change by <i>clicking</i> on them.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<div id="FIContent">
|
||||||
|
|
||||||
|
<!-- buttons for flags and images -->
|
||||||
|
<div id="Buttons">
|
||||||
|
<input class="AddButt" type="button" (click)="addFlagPopup()" value="Add Flag">
|
||||||
|
<input class="DeleteButt" type="button" (click)="showDialogBox()" value="Delete">
|
||||||
|
<input class="AddButt" type="button" value="Add Image" (click)="navtoPageAI()">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- list of flags and images -->
|
||||||
|
<div id="FIList">
|
||||||
|
<ul id="FIListItems">
|
||||||
|
<li *ngFor="let flag of flagsForContest" (click)="selectFlag(flag)"
|
||||||
|
[ngClass]="{'selected-contest': flag.FlagID === selectedFlag?.FlagID}">
|
||||||
|
{{ flag.Name }} ({{flag.Image}})
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- linux shell to test images -->
|
||||||
|
<div id="Terminal">
|
||||||
|
<div id="TerminalHeader">
|
||||||
|
<h1> Test Images </h1>
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
This is where you can <i>test</i> the <span style="color:white;">Images</span> you create.
|
||||||
|
<br>
|
||||||
|
Simply select one from the dropdown and press <i>Test Image</i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<select id="Images" [(ngModel)]="selectedImage">
|
||||||
|
<option disabled selected hidden> Select an Image </option>
|
||||||
|
<option *ngFor="let image of allImages" [value] ="image.Name">{{image.Name}}</option>
|
||||||
|
</select>
|
||||||
|
<input type="button" value="Test Image" (click)="TestImage()">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- the actual shell -->
|
||||||
|
<div id="Linux_Shell">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dialog box for deleting flag or image -->
|
||||||
|
<dialog id="DialogBox" [open]="isDialogVisible">
|
||||||
|
<h3> Which would you want to Delete: </h3>
|
||||||
|
<p id="FlagImage">{{ selectedFlag?.Image }}</p>
|
||||||
|
<input type="button" value="Delete Flag" (click)="DeleteFlag()">
|
||||||
|
<input type="button" value="Delete Image" (click)="DeleteImage()">
|
||||||
|
<input type="button" value="Cancel" (click)="closeDialogBox()">
|
||||||
|
</dialog>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ModifyContestComponent } from './modify-contest.component';
|
||||||
|
|
||||||
|
describe('ModifyContestComponent', () => {
|
||||||
|
let component: ModifyContestComponent;
|
||||||
|
let fixture: ComponentFixture<ModifyContestComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [ModifyContestComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ModifyContestComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,210 @@
|
|||||||
|
import { Component, Renderer2 } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { TerminalService } from '../Helper/terminal.service';
|
||||||
|
import { getEmail, gotoPage, Popup, updateContainer } from '../Helper/Helpers';
|
||||||
|
import { ModifyContestService } from './modify-contest.service';
|
||||||
|
import { Contest } from '../models/contest.model';
|
||||||
|
import { NgFor, CommonModule } from '@angular/common';
|
||||||
|
import { Flag } from '../models/flag.model';
|
||||||
|
import { Image } from '../models/image.model';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-modify-contest',
|
||||||
|
imports: [NgFor, CommonModule, FormsModule],
|
||||||
|
templateUrl: './modify-contest.component.html',
|
||||||
|
styleUrl: './modify-contest.component.css'
|
||||||
|
})
|
||||||
|
|
||||||
|
export class ModifyContestComponent {
|
||||||
|
contests: Contest[] = [];
|
||||||
|
activeContest: number | null = null;
|
||||||
|
flagsForContest: Flag[] = [];
|
||||||
|
selectedContestId: number = 0;
|
||||||
|
selectedFlag: Flag | null = null;
|
||||||
|
allImages: Image[] = []
|
||||||
|
isDialogVisible = false;
|
||||||
|
selectedImage: string = "Select an Image";
|
||||||
|
constructor(private renderer: Renderer2, private router: Router, private modifyContestService: ModifyContestService, private terminalService: TerminalService){}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
|
||||||
|
this.terminalService.reattachTerminal();
|
||||||
|
|
||||||
|
// style class for blue border
|
||||||
|
this.renderer.addClass(document.documentElement, 'modify');
|
||||||
|
|
||||||
|
this.loadContests();
|
||||||
|
this.getImages();
|
||||||
|
|
||||||
|
window.addEventListener("message", this.handlePopupMessage.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
// set app back to default when leaving this page
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.renderer.removeClass(document.documentElement, 'modify');
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePopupMessage(event: MessageEvent): void {
|
||||||
|
if (event.origin !== window.location.origin) return;
|
||||||
|
if (event.data?.type === "FLAG_ADDED" && event.data?.contestId) {
|
||||||
|
this.modifyContestService.loadFlagsForContest(event.data.contestId)
|
||||||
|
.then(flags => this.flagsForContest = flags)
|
||||||
|
.catch(err => console.error('Error reloading flags:', err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to load contests
|
||||||
|
async loadContests(): Promise<void> {
|
||||||
|
const email = this.getEmail();
|
||||||
|
try {
|
||||||
|
const contestsData = await this.modifyContestService.loadContests(email);
|
||||||
|
this.contests = contestsData || []; // Populate contests array
|
||||||
|
this.activeContest = await this.modifyContestService.getActiveContest(this.getEmail()); //Getting the active contest
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading contests:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getActiveContest(): Promise<void> {
|
||||||
|
this.activeContest = await this.modifyContestService.getActiveContest(this.getEmail());
|
||||||
|
}
|
||||||
|
|
||||||
|
async getImages(): Promise<void>{
|
||||||
|
const email = this.getEmail();
|
||||||
|
try{
|
||||||
|
const imageData = await this.modifyContestService.getImages(email);
|
||||||
|
this.allImages = imageData || [];
|
||||||
|
}catch(error){
|
||||||
|
console.error('Error loading images');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to add a contest
|
||||||
|
addContestPopup(): void {
|
||||||
|
Popup('/add-contest');
|
||||||
|
}
|
||||||
|
|
||||||
|
addFlagPopup(): void{
|
||||||
|
if(this.selectedContestId === 0)
|
||||||
|
alert("Select a contest to add a flag!");
|
||||||
|
else{
|
||||||
|
Popup('/add-flag');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
navtoPageAI(): void{
|
||||||
|
gotoPage(this.router, '/create-image');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to set a contest as active
|
||||||
|
async setContestActive(): Promise<void> {
|
||||||
|
if (this.selectedContestId !== null) {
|
||||||
|
try {
|
||||||
|
const email = this.getEmail();
|
||||||
|
await this.modifyContestService.setContestActive(this.selectedContestId, email);
|
||||||
|
this.loadContests();
|
||||||
|
console.log('Contest set as active');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error activating contest:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteContest(): Promise<void> {
|
||||||
|
if (this.selectedContestId === 0)
|
||||||
|
alert("Select a contest to delete!");
|
||||||
|
else if(this.activeContest === this.selectedContestId)
|
||||||
|
alert("You can't delete an active contest!");
|
||||||
|
else{
|
||||||
|
try {
|
||||||
|
await this.modifyContestService.deleteContest(this.selectedContestId);
|
||||||
|
console.log('Contest deleted');
|
||||||
|
this.loadContests(); // Refresh the contest list after deletion
|
||||||
|
this.selectedContestId = 0;
|
||||||
|
this.flagsForContest = [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting contest:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectContest(contestId: number): void {
|
||||||
|
this.selectedContestId = contestId;
|
||||||
|
this.modifyContestService.loadFlagsForContest(contestId)
|
||||||
|
.then((flags) => {
|
||||||
|
this.flagsForContest = flags;
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error('Failed to load flags:', err);
|
||||||
|
})
|
||||||
|
sessionStorage.setItem('selectedContestID', this.selectedContestId.toString());
|
||||||
|
console.log('Selected contest ID:', contestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectFlag(flag: Flag): void{
|
||||||
|
this.selectedFlag = flag
|
||||||
|
}
|
||||||
|
|
||||||
|
getEmail(): string {
|
||||||
|
return getEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
navtoPageAP() {
|
||||||
|
gotoPage(this.router, '/admin-profile');
|
||||||
|
}
|
||||||
|
|
||||||
|
showDialogBox(): void{
|
||||||
|
if(!this.selectedFlag || this.selectedFlag.Name === null){
|
||||||
|
alert('You must select a flag first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('Opening dialog for flag:', this.selectedFlag);
|
||||||
|
this.isDialogVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDialogBox(): void{
|
||||||
|
this.isDialogVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async DeleteFlag(): Promise<void>{
|
||||||
|
if(!this.selectedFlag){
|
||||||
|
console.log("No flag selected");
|
||||||
|
alert("Please select a flag to delete.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try{
|
||||||
|
await this.modifyContestService.DeleteFlag(this.selectedFlag.FlagID);
|
||||||
|
this.flagsForContest = await this.modifyContestService.loadFlagsForContest(this.selectedContestId);
|
||||||
|
} catch(error){
|
||||||
|
console.error("Failed deleting flag:", error);
|
||||||
|
}
|
||||||
|
console.log("Deleted Flag");
|
||||||
|
this.isDialogVisible = false;
|
||||||
|
this.selectedFlag = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteImage(): void{
|
||||||
|
this.closeDialogBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
async TestImage(): Promise<void>{
|
||||||
|
if(this.selectedImage === "Select an Image"){
|
||||||
|
alert('you must select an Image first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try{
|
||||||
|
await this.modifyContestService.setNewActiveFlag(this.selectedImage, getEmail())
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed setting up new image", error);
|
||||||
|
}
|
||||||
|
await updateContainer(getEmail());
|
||||||
|
this.ClearAndSetTerminal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear and set terminal for new flag
|
||||||
|
async ClearAndSetTerminal() {
|
||||||
|
await this.terminalService.ClearTerminal();
|
||||||
|
await this.terminalService.reattachTerminal();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ModifyContestService } from './modify-contest.service';
|
||||||
|
|
||||||
|
describe('ModifyContestService', () => {
|
||||||
|
let service: ModifyContestService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(ModifyContestService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,232 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ModifyContestService {
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
async getImages(email: string): Promise<any> {
|
||||||
|
try {
|
||||||
|
const response = await fetch('api/getImages', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ email })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch images');
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching images:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadContests(email: string): Promise<any> {
|
||||||
|
try {
|
||||||
|
const response = await fetch('api/contests/getContests', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ email })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch contests');
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching contests:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadFlagsForContest(contestId: number): Promise<any> {
|
||||||
|
if(contestId === 0)
|
||||||
|
return [];
|
||||||
|
try {
|
||||||
|
const response = await fetch('api/flags/getAllFlagsFromContest', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ contest: contestId })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch flags');
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching flags:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addContest(data: any): Promise<any> {
|
||||||
|
try {
|
||||||
|
const response = await fetch('api/AddContest', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to add contest');
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error adding contest:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteContest(contestId: number): Promise<any> {
|
||||||
|
// Send a POST request to delete the contest by its name
|
||||||
|
let data = {contest: contestId};
|
||||||
|
await this.deleteFlagsFromContest(contestId);
|
||||||
|
let res = await fetch('api/DeleteContest', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
console.log("Success: deleting contest");
|
||||||
|
} else {
|
||||||
|
console.log("ERROR: deleting contest");
|
||||||
|
alert("Failed to delete contest.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteFlagsFromContest(contestId: number): Promise<any> {
|
||||||
|
try {
|
||||||
|
const response = await fetch('api/DeleteFlagsFromContest', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ contest: contestId })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to delete flags');
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting flags:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async AddFlag(data: any): Promise<any> {
|
||||||
|
try {
|
||||||
|
const response = await fetch('api/AddFlag', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to add flag');
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error adding flag:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async DeleteFlag(flagId: number): Promise<any> {
|
||||||
|
let data = { flag: flagId };
|
||||||
|
let res = await fetch('api/DeleteFlag', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async setNewActiveFlag(flagImage: string, Email: string):Promise<any> {
|
||||||
|
const image = flagImage;
|
||||||
|
// set up the container for the image
|
||||||
|
const data = { FlagImage: image, email: Email };
|
||||||
|
const res = await fetch('/api/setNewActiveFlag', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type' : 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async setContestActive(contestId: number, email: string): Promise<any> {
|
||||||
|
try {
|
||||||
|
const response = await fetch('api/setContestActive', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ contest: contestId, email })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to set contest active');
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error setting contest active:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteImage(imageName: string): Promise<any> {
|
||||||
|
try {
|
||||||
|
const response = await fetch('api/DeleteImageReplaceFlags', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ images: imageName })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to delete image');
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting image:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getActiveContest(Email: string): Promise<any>{
|
||||||
|
const data = { email: Email };
|
||||||
|
const res = await fetch('api/contests/getActiveContest', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type' : 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
let contest = await res.json();
|
||||||
|
return contest.ContestID;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SocketIOService } from './socket-io.service';
|
||||||
|
|
||||||
|
describe('SocketIOService', () => {
|
||||||
|
let service: SocketIOService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(SocketIOService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,48 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { io, Socket } from 'socket.io-client';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class SocketIOService {
|
||||||
|
private socket: Socket;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.socket = io("/api/socket.io", {
|
||||||
|
path: '/api/socket.io',
|
||||||
|
transports: ['websocket'], // or ['polling', 'websocket'] if you want fallback
|
||||||
|
autoConnect: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(): void {
|
||||||
|
if (!this.socket.connected) {
|
||||||
|
console.log("Attempting to connect...");
|
||||||
|
this.socket.connect();
|
||||||
|
|
||||||
|
this.socket.on("connect", () => console.log("Connected to Socket.IO!"));
|
||||||
|
this.socket.on("connect_error", (err) => console.error("Socket.IO Connection Error:", err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect(): void {
|
||||||
|
if (this.socket.connected) {
|
||||||
|
this.socket.disconnect();
|
||||||
|
console.log("Disconnected from Socket.IO.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listen<T = any>(eventName: string): Observable<T> {
|
||||||
|
return new Observable<T>((subscriber) => {
|
||||||
|
const handler = (data: T) => subscriber.next(data);
|
||||||
|
this.socket.on(eventName, handler);
|
||||||
|
|
||||||
|
return () => this.socket.off(eventName, handler); // cleanup
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(eventName: string, data: any): void {
|
||||||
|
this.socket.emit(eventName, data);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
<h1>Thought you could pull a fast one eh?</h1>
|
||||||
|
<p>I dont think so</p>
|
||||||
|
<h2>👎</h2>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UNAUTHORIZEDComponent } from './unauthorized.component';
|
||||||
|
|
||||||
|
describe('UNAUTHORIZEDComponent', () => {
|
||||||
|
let component: UNAUTHORIZEDComponent;
|
||||||
|
let fixture: ComponentFixture<UNAUTHORIZEDComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [UNAUTHORIZEDComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(UNAUTHORIZEDComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-unauthorized',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './unauthorized.component.html',
|
||||||
|
styleUrl: './unauthorized.component.css'
|
||||||
|
})
|
||||||
|
export class UNAUTHORIZEDComponent {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,160 @@
|
|||||||
|
.body {
|
||||||
|
height: 98vh;
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
/* columns and rows sizes */
|
||||||
|
grid-template-columns: auto auto auto auto auto auto;
|
||||||
|
grid-template-rows: auto auto auto auto auto;
|
||||||
|
|
||||||
|
background-color: lightgreen;
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
.bg {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
#Header {
|
||||||
|
grid-row: 1/2;
|
||||||
|
grid-column: 1/7;
|
||||||
|
background-color: green;
|
||||||
|
margin: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#Header input {
|
||||||
|
height: 25px;
|
||||||
|
width: 150px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#ContestsButt {
|
||||||
|
background-color: lawngreen;
|
||||||
|
}
|
||||||
|
#LogoutButt {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* past contests table */
|
||||||
|
#PastContests {
|
||||||
|
grid-column: 1/4;
|
||||||
|
grid-row: 2/6;
|
||||||
|
margin: 50px;
|
||||||
|
height: 500px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
#Past_Contests {
|
||||||
|
margin: 10px;
|
||||||
|
height: 75%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* record table */
|
||||||
|
#Record, #CurrentContest {
|
||||||
|
grid-column: 5/7;
|
||||||
|
grid-row: 2/3;
|
||||||
|
margin: 50px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
#RecordTable, #Current_Contest {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Usernames */
|
||||||
|
#Username {
|
||||||
|
grid-row: 3/5;
|
||||||
|
grid-column: 5/7;
|
||||||
|
background-color: green;
|
||||||
|
text-align: center;
|
||||||
|
margin: 50px;
|
||||||
|
}
|
||||||
|
#generate {
|
||||||
|
background-color: blue;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#setname {
|
||||||
|
background-color: lawngreen;
|
||||||
|
}
|
||||||
|
#generate, #setname {
|
||||||
|
height: 25px;
|
||||||
|
width: 150px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tooltips */
|
||||||
|
.tooltip {
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.tooltip:hover .tooltipText {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
.tooltip .tooltipText {
|
||||||
|
visibility: hidden;
|
||||||
|
height: auto;
|
||||||
|
background-color: gray;
|
||||||
|
color: black;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
padding: 5px;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stuff with no ID */
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border: 3px solid black;
|
||||||
|
border-collapse: seperate;
|
||||||
|
border-spacing: 0;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
table thead {
|
||||||
|
top: 0;
|
||||||
|
position: sticky;
|
||||||
|
position: -webkit-sticky;
|
||||||
|
z-index: 1;
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
border: 3px solid black;
|
||||||
|
text-align: center;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
width: 50%;
|
||||||
|
height: 50%;
|
||||||
|
background-color: lawngreen;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
display: inline;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Leaderboard popup window */
|
||||||
|
#Leaderboard {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#Leaderboard h1 {
|
||||||
|
grid-row: 1/2;
|
||||||
|
grid-column: 2/5;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#Leaderboard h2 {
|
||||||
|
grid-row: 2/3;
|
||||||
|
grid-column: 3/4;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#LeaderTable {
|
||||||
|
grid-column: 2/6;
|
||||||
|
grid-row: 4/5;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
<div class="bg">
|
||||||
|
<div class="body">
|
||||||
|
<!-- header -->
|
||||||
|
<div id="Header">
|
||||||
|
<h1> Menu </h1>
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
This is the main place for you, the <span style="color:white">User</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<input id="ContestsButt" type="button" value="Profile" (click)="navtoPage()">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- past contests -->
|
||||||
|
<div id="PastContests">
|
||||||
|
<h1> Past Contests </h1>
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
This is every past contest that's still around
|
||||||
|
<br>
|
||||||
|
you can view the leaderboard or hop in to take a look
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<!-- past contest table -->
|
||||||
|
<div id="Past_Contests">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th> Contest Name </th>
|
||||||
|
<th> Flags </th>
|
||||||
|
<th> Leaderboard </th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="UPCTbody">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- current contest-->
|
||||||
|
<div id="CurrentContest">
|
||||||
|
<h1> Current Contest </h1>
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
This is where the current contest is shown
|
||||||
|
<br>
|
||||||
|
click <span style="color:lawngreen">Join</span> to join in on the fun
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<!-- Current Contest -->
|
||||||
|
<div id="Current_Contest">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<th> Current Contest </th>
|
||||||
|
<th> Flags </th>
|
||||||
|
<th> Join </th>
|
||||||
|
</thead>
|
||||||
|
<tbody id="UCCTbody">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UserMenuComponent } from './user-menu.component';
|
||||||
|
|
||||||
|
describe('UserMenuComponent', () => {
|
||||||
|
let component: UserMenuComponent;
|
||||||
|
let fixture: ComponentFixture<UserMenuComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [UserMenuComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(UserMenuComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,24 @@
|
|||||||
|
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router'
|
||||||
|
import { Contest } from '../models/contest.model.js';
|
||||||
|
import { PopulateTable, InsertIntoTable, gotoPage, getContests } from '../Helper/Helpers.js';
|
||||||
|
@Component({
|
||||||
|
selector: 'app-user-menu',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './user-menu.component.html',
|
||||||
|
encapsulation: ViewEncapsulation.None,
|
||||||
|
styleUrl: './user-menu.component.css'
|
||||||
|
})
|
||||||
|
|
||||||
|
export class UserMenuComponent implements OnInit{
|
||||||
|
constructor(private router: Router){}
|
||||||
|
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
let data: Contest[] = await getContests();
|
||||||
|
InsertIntoTable(data, this.router);
|
||||||
|
}
|
||||||
|
|
||||||
|
navtoPage(): void {
|
||||||
|
gotoPage(this.router, '/user-profile');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
Alex Miller
|
||||||
|
Jordan Latimer
|
||||||
|
|
||||||
|
CSS for Profile Page User Side
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
height: 98vh;
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
/* columns and rows sizes */
|
||||||
|
grid-template-columns: auto auto auto auto auto auto;
|
||||||
|
grid-template-rows: auto auto auto auto auto;
|
||||||
|
|
||||||
|
background-color: lightgreen;
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
#Header {
|
||||||
|
grid-row: 1/2;
|
||||||
|
grid-column: 1/7;
|
||||||
|
background-color: green;
|
||||||
|
margin: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#Header input {
|
||||||
|
height: 25px;
|
||||||
|
width: 150px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#ContestsButt {
|
||||||
|
background-color: lawngreen;
|
||||||
|
}
|
||||||
|
#LogoutButt {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* past contests table */
|
||||||
|
#PastContests {
|
||||||
|
grid-column: 1/4;
|
||||||
|
grid-row: 2/6;
|
||||||
|
margin: 50px;
|
||||||
|
height: 500px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
#Past_Contests {
|
||||||
|
margin: 10px;
|
||||||
|
height: 75%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* record table */
|
||||||
|
#Record, #CurrentContest {
|
||||||
|
grid-column: 5/7;
|
||||||
|
grid-row: 2/3;
|
||||||
|
margin: 50px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
#RecordTable, #Current_Contest {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Usernames */
|
||||||
|
#Username {
|
||||||
|
grid-row: 3/5;
|
||||||
|
grid-column: 5/7;
|
||||||
|
background-color: green;
|
||||||
|
text-align: center;
|
||||||
|
margin: 50px;
|
||||||
|
}
|
||||||
|
#generate {
|
||||||
|
background-color: blue;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#setname {
|
||||||
|
background-color: lawngreen;
|
||||||
|
}
|
||||||
|
#generate, #setname {
|
||||||
|
height: 25px;
|
||||||
|
width: 150px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tooltips */
|
||||||
|
.tooltip {
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.tooltip:hover .tooltipText {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
.tooltip .tooltipText {
|
||||||
|
visibility: hidden;
|
||||||
|
height: auto;
|
||||||
|
background-color: gray;
|
||||||
|
color: black;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
padding: 5px;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stuff with no ID */
|
||||||
|
h1 {
|
||||||
|
display: inline;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Leaderboard popup window */
|
||||||
|
#Leaderboard {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#Leaderboard h1 {
|
||||||
|
grid-row: 1/2;
|
||||||
|
grid-column: 2/5;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#Leaderboard h2 {
|
||||||
|
grid-row: 2/3;
|
||||||
|
grid-column: 3/4;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#LeaderTable {
|
||||||
|
grid-column: 2/6;
|
||||||
|
grid-row: 4/5;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
@ -0,0 +1,124 @@
|
|||||||
|
<!--
|
||||||
|
Alex Miller
|
||||||
|
Jordan Latimer
|
||||||
|
|
||||||
|
Profile Page for Users
|
||||||
|
-->
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>NMU CTF Profile Page</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div id="Header">
|
||||||
|
<h1> Profile </h1>
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
This is everything about your profile
|
||||||
|
<br>
|
||||||
|
you can view your Past Contests, Record,
|
||||||
|
<br>
|
||||||
|
and change your username
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<input id="ContestsButt" type="button" value="Back to Contests" (click)="navtoPage()">
|
||||||
|
<input id="LogoutButt" type="button" value="Logout" (click)="onLogout()">
|
||||||
|
</div>
|
||||||
|
<!-- Past contest table -->
|
||||||
|
<div id="PastContests">
|
||||||
|
<h1> Past Contests </h1>
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
This is every past contest that's still around
|
||||||
|
<br>
|
||||||
|
you can see the <span style="color:white">Leaderboard</span> by clicking
|
||||||
|
<br>
|
||||||
|
on <span style="color:lawngreen">View</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<!-- past contest table -->
|
||||||
|
<div id="Past_Contests">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th> Contest Name </th>
|
||||||
|
<th> Flags </th>
|
||||||
|
<th> Leaderboard </th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="UPCTBody">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- chaning username -->
|
||||||
|
<div id="Username">
|
||||||
|
<h1> Edit Username</h1>
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
This is where you can change your username
|
||||||
|
<br>
|
||||||
|
simply click <span style="color:blue">Generate</span> and a random
|
||||||
|
<br>
|
||||||
|
username will be generated, keep going until you find one you like and
|
||||||
|
<br>
|
||||||
|
click <span style="color:lawngreen">Set Name</span> to set that as your username
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<!-- Name generator and logout button -->
|
||||||
|
<div id="Name">
|
||||||
|
<input type="text" id="NameInput" [(ngModel)]="NameInput" readonly>
|
||||||
|
<input id="generate" type="button" value="Generate" (click)="onGetNewName()">
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<input id="setname" type="button" value="Set Name" (click)="onSetNewName()">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- users record -->
|
||||||
|
<div id="Record">
|
||||||
|
<h1> Record </h1>
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
This is where you can view your <span style="color:white">Record</span>
|
||||||
|
<br>
|
||||||
|
to see how well you are doing with all <span style="color:white">Contests</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<!-- record table -->
|
||||||
|
<div id="RecordTable">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th> Completed </th>
|
||||||
|
<th> Total </th>
|
||||||
|
<th> Record </th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="URTbody">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UserProfileACComponent } from './user-profile-ac.component';
|
||||||
|
|
||||||
|
describe('UserProfileACComponent', () => {
|
||||||
|
let component: UserProfileACComponent;
|
||||||
|
let fixture: ComponentFixture<UserProfileACComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [UserProfileACComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(UserProfileACComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,37 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { loadTable, getNewName, setNewName } from '../Helper/User-pfp';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { gotoPage, logOut } from '../Helper/Helpers';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-user-profile-ac',
|
||||||
|
imports: [FormsModule],
|
||||||
|
templateUrl: './user-profile-ac.component.html',
|
||||||
|
styleUrl: './user-profile-ac.component.css'
|
||||||
|
})
|
||||||
|
export class UserProfileACComponent {
|
||||||
|
|
||||||
|
constructor(private router: Router){}
|
||||||
|
|
||||||
|
NameInput: string = '';
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
this.NameInput = await loadTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
navtoPage(): void {
|
||||||
|
gotoPage(this.router, '/contest-page');
|
||||||
|
}
|
||||||
|
|
||||||
|
onGetNewName(): void{
|
||||||
|
this.NameInput = getNewName();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSetNewName(): void{
|
||||||
|
setNewName(this.NameInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
onLogout(): void{
|
||||||
|
logOut(this.router);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
Alex Miller
|
||||||
|
Jordan Latimer
|
||||||
|
|
||||||
|
CSS for Profile Page User Side
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
height: 98vh;
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
/* columns and rows sizes */
|
||||||
|
grid-template-columns: auto auto auto auto auto auto;
|
||||||
|
grid-template-rows: auto auto auto auto auto;
|
||||||
|
|
||||||
|
background-color: lightgreen;
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
#Header {
|
||||||
|
grid-row: 1/2;
|
||||||
|
grid-column: 1/7;
|
||||||
|
background-color: green;
|
||||||
|
margin: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#Header input {
|
||||||
|
height: 25px;
|
||||||
|
width: 150px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#ContestsButt {
|
||||||
|
background-color: lawngreen;
|
||||||
|
}
|
||||||
|
#LogoutButt {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* past contests table */
|
||||||
|
#PastContests {
|
||||||
|
grid-column: 1/4;
|
||||||
|
grid-row: 2/6;
|
||||||
|
margin: 50px;
|
||||||
|
height: 500px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
#Past_Contests {
|
||||||
|
margin: 10px;
|
||||||
|
height: 75%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* record table */
|
||||||
|
#Record, #CurrentContest {
|
||||||
|
grid-column: 5/7;
|
||||||
|
grid-row: 2/3;
|
||||||
|
margin: 50px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
#RecordTable, #Current_Contest {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Usernames */
|
||||||
|
#Username {
|
||||||
|
grid-row: 3/5;
|
||||||
|
grid-column: 5/7;
|
||||||
|
background-color: green;
|
||||||
|
text-align: center;
|
||||||
|
margin: 50px;
|
||||||
|
}
|
||||||
|
#generate {
|
||||||
|
background-color: blue;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#setname {
|
||||||
|
background-color: lawngreen;
|
||||||
|
}
|
||||||
|
#generate, #setname {
|
||||||
|
height: 25px;
|
||||||
|
width: 150px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tooltips */
|
||||||
|
.tooltip {
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.tooltip:hover .tooltipText {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
.tooltip .tooltipText {
|
||||||
|
visibility: hidden;
|
||||||
|
height: auto;
|
||||||
|
background-color: gray;
|
||||||
|
color: black;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
padding: 5px;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stuff with no ID */
|
||||||
|
h1 {
|
||||||
|
display: inline;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Leaderboard popup window */
|
||||||
|
#Leaderboard {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#Leaderboard h1 {
|
||||||
|
grid-row: 1/2;
|
||||||
|
grid-column: 2/5;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#Leaderboard h2 {
|
||||||
|
grid-row: 2/3;
|
||||||
|
grid-column: 3/4;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#LeaderTable {
|
||||||
|
grid-column: 2/6;
|
||||||
|
grid-row: 4/5;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
<!--
|
||||||
|
Alex Miller
|
||||||
|
Jordan Latimer
|
||||||
|
|
||||||
|
Profile Page before entering a contest for User
|
||||||
|
-->
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>NMU CTF User Profile Page</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div id="Header">
|
||||||
|
<h1> Profile </h1>
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
This is everything about your profile
|
||||||
|
<br>
|
||||||
|
you can view your Past Contests, Record,
|
||||||
|
<br>
|
||||||
|
and change your username
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<input id="ContestsButt" type="button" value="Back to Menu" (click)="navtoPage()">
|
||||||
|
<input id="LogoutButt" type="button" value="Logout" (click)="onLogout()">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- past contests -->
|
||||||
|
<div id="PastContests">
|
||||||
|
<h1> Past Contests </h1>
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
This is every past contest that's still around
|
||||||
|
<br>
|
||||||
|
you can see the <span style="color:white">Leaderboard</span> by clicking
|
||||||
|
<br>
|
||||||
|
on <span style="color:lawngreen">View</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<!-- past contest table -->
|
||||||
|
<div id="Past_Contests">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th> Contest Name </th>
|
||||||
|
<th> Flags </th>
|
||||||
|
<th> Leaderboard </th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="UPCTBody">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- usernames -->
|
||||||
|
<div id="Username">
|
||||||
|
<h1> Edit Username </h1>
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
This is where you can change your username
|
||||||
|
<br>
|
||||||
|
simply click <span style="color:blue">Generate</span> and a random
|
||||||
|
<br>
|
||||||
|
username will be generated, keep going until you find one you like and
|
||||||
|
<br>
|
||||||
|
click <span style="color:lawngreen">Set Name</span> to set that as your username
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<!-- Name generator and logout button -->
|
||||||
|
<div id="Name">
|
||||||
|
<input type="text" id="NameInput" [(ngModel)]="NameInput" readonly>
|
||||||
|
<input id="generate" type="button" value="Generate" (click)="onGetNewName()">
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<input id="setname" type="button" value="Set Name" (click)="onSetNewName()">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- record -->
|
||||||
|
<div id="Record">
|
||||||
|
<h1> Record </h1>
|
||||||
|
<div class="tooltip">
|
||||||
|
?
|
||||||
|
<span class="tooltipText">
|
||||||
|
This is where you can view your <span style="color:white">Record</span>
|
||||||
|
<br>
|
||||||
|
to see how well you are doing with all <span style="color:white">Contests</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<!-- record table -->
|
||||||
|
<div id="RecordTable">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th> Completed </th>
|
||||||
|
<th> Total </th>
|
||||||
|
<th> Record </th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="URTbody">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UserProfileComponent } from './user-profile.component';
|
||||||
|
|
||||||
|
describe('UserProfileComponent', () => {
|
||||||
|
let component: UserProfileComponent;
|
||||||
|
let fixture: ComponentFixture<UserProfileComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [UserProfileComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(UserProfileComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,38 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { loadTable } from '../Helper/User-pfp';
|
||||||
|
import { gotoPage, logOut } from '../Helper/Helpers';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { getNewName, setNewName } from '../Helper/User-pfp';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-user-profile',
|
||||||
|
imports: [FormsModule],
|
||||||
|
templateUrl: './user-profile.component.html',
|
||||||
|
styleUrl: './user-profile.component.css'
|
||||||
|
})
|
||||||
|
export class UserProfileComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor(private router: Router){}
|
||||||
|
|
||||||
|
NameInput: string = '';
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
this.NameInput = await loadTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
navtoPage(): void {
|
||||||
|
gotoPage(this.router, '/user-menu');
|
||||||
|
}
|
||||||
|
|
||||||
|
onGetNewName(): void{
|
||||||
|
this.NameInput = getNewName();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSetNewName(): void{
|
||||||
|
setNewName(this.NameInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
onLogout(): void{
|
||||||
|
logOut(this.router);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" class="default">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>CTFFrontend</title>
|
||||||
|
<base href="/" />
|
||||||
|
<meta
|
||||||
|
http-equiv="Content-Security-Policy"
|
||||||
|
content="default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; connect-src 'self' ws://localhost:3000;"
|
||||||
|
/>
|
||||||
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<app-root></app-root>
|
||||||
|
</body>
|
||||||
|
</html>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue