diff --git a/Admin.tsx b/Admin.tsx new file mode 100644 index 0000000..506ad98 --- /dev/null +++ b/Admin.tsx @@ -0,0 +1,482 @@ + +import React, { useState, useEffect, useRef, useMemo } from 'react'; +import { X, Edit3, Trash2, Shield, ShieldCheck, ShieldAlert, Skull, Newspaper, Download, Upload, Database, Save, History, Plus, Globe, User, ShieldX, UserMinus, UserCheck } from 'lucide-react'; +import { useCTF } from './CTFContext'; +import { Challenge, Team, BlogPost, Difficulty, ChallengeFile } from './types'; +import { Button, Countdown, CategoryIcon } from './UIComponents'; +import { CATEGORIES, DIFFICULTIES } from './constants'; + +export const Admin: React.FC = () => { + const { state, toggleCtf, resetScores, upsertChallenge, deleteChallenge, deleteAllChallenges, updateTeam, deleteTeam, createBlogPost, updateBlogPost, deleteBlogPost, updateConfig, exportChallenges, importChallenges, backupDatabase, restoreDatabase, refreshState } = useCTF(); + + const [editingChallenge, setEditingChallenge] = useState | null>(null); + const [editingTeam, setEditingTeam] = useState & { newPassword?: string } | null>(null); + const [editingBlogPost, setEditingBlogPost] = useState | null>(null); + const [newFiles, setNewFiles] = useState([]); + const [currentFiles, setCurrentFiles] = useState([]); + const [localConf, setLocalConf] = useState>({}); + const initialLoadDone = useRef(false); + + // Sorted data for display + const sortedChallenges = useMemo(() => { + return [...state.challenges].sort((a, b) => a.title.localeCompare(b.title)); + }, [state.challenges]); + + const sortedOperators = useMemo(() => { + return state.teams + .filter(t => t.id !== 'admin-0') + .sort((a, b) => a.name.localeCompare(b.name)); + }, [state.teams]); + + useEffect(() => { + if (Object.keys(state.config).length > 0 && !initialLoadDone.current) { + setLocalConf({ ...state.config }); + initialLoadDone.current = true; + } + }, [state.config]); + + const importChalRef = useRef(null); + const restoreDbRef = useRef(null); + + const handleConfigSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + const fd = new FormData(); + Object.entries(localConf).forEach(([k, v]) => { + if (v !== undefined && v !== null && v !== 'NaN') fd.append(k, String(v)); + }); + const logoInput = (e.target as any).logo; + const bgInput = (e.target as any).bgImage; + if (logoInput && logoInput.files[0]) fd.append('logo', logoInput.files[0]); + if (bgInput && bgInput.files[0]) fd.append('bgImage', bgInput.files[0]); + try { + await updateConfig(fd); + await refreshState(); + initialLoadDone.current = false; + alert('CONFIGURATION_SAVED_SUCCESSFULLY'); + } catch (err) { alert('SAVE_FAILED_CHECK_CONSOLE'); } + }; + + const toUTCDisplay = (msStr: string) => { + if (!msStr || msStr === 'undefined' || msStr === 'NaN') return ""; + const ms = parseInt(msStr); + if (isNaN(ms)) return ""; + const d = new Date(ms); + return d.toISOString().slice(0, 16); + }; + + const fromUTCDisplay = (isoStr: string) => { + if (!isoStr) return ""; + const d = new Date(isoStr + ":00Z"); + const ms = d.getTime(); + return isNaN(ms) ? "" : ms.toString(); + }; + + const eventStartTime = parseInt(state.config.eventStartTime || "0"); + const isBeforeStart = Date.now() < eventStartTime; + + const handleDifficultyChange = (val: Difficulty) => { + if (!editingChallenge) return; + const points = val === 'Low' ? 100 : val === 'Medium' ? 200 : 300; + setEditingChallenge({ + ...editingChallenge, + difficulty: val, + initialPoints: points, + minimumPoints: Math.floor(points / 2) + }); + }; + + const handleInitialPointsChange = (p: number) => { + if (!editingChallenge) return; + setEditingChallenge({ + ...editingChallenge, + initialPoints: p, + minimumPoints: Math.floor(p / 2) + }); + }; + + const quickToggleAdmin = async (team: Team) => { + if (team.id === 'admin-0') return; + await updateTeam(team.id, { ...team, isAdmin: !team.isAdmin }); + }; + + const quickToggleStatus = async (team: Team) => { + if (team.id === 'admin-0') return; + await updateTeam(team.id, { ...team, isDisabled: !team.isDisabled }); + }; + + return ( +
+
+

ADMIN_CONSOLE

+
+ {isBeforeStart && ( +
+ +
+ )} + +
+
+
+
+
+

GENERAL_CONFIG

+
+
+
+ + setLocalConf({...localConf, conferenceName: e.target.value})} /> +
+
+
+ + { const val = fromUTCDisplay(e.target.value); if (val) setLocalConf({...localConf, eventStartTime: val}); }} /> +
+
+ + { const val = fromUTCDisplay(e.target.value); if (val) setLocalConf({...localConf, eventEndTime: val}); }} /> +
+
+
+ + setLocalConf({...localConf, dockerIp: e.target.value})} /> +
+
+ +