- Prevented admin challenge solves from creating score records
- Added operator solves list to the Admin panel profile - Allowed deletion of specific operator solves from the Admin panel - Enhanced operator solves list with alphabetical sorting, difficulty colors, and point values - Added rank medal icons to operator solves in the Admin panel
This commit is contained in:
59
Admin.tsx
59
Admin.tsx
@@ -1,13 +1,14 @@
|
||||
|
||||
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 { X, Edit3, Trash2, Shield, ShieldCheck, ShieldAlert, Skull, Newspaper, Download, Upload, Database, Save, History, Plus, Globe, User, ShieldX, UserMinus, UserCheck, Medal, CheckCircle2 } 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';
|
||||
import { calculateChallengeValue, getFirstBloodBonusFactor } from './services/scoring';
|
||||
|
||||
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 { state, toggleCtf, resetScores, upsertChallenge, deleteChallenge, deleteAllChallenges, updateTeam, deleteTeam, deleteSolve, createBlogPost, updateBlogPost, deleteBlogPost, updateConfig, exportChallenges, importChallenges, backupDatabase, restoreDatabase, refreshState } = useCTF();
|
||||
|
||||
const [editingChallenge, setEditingChallenge] = useState<Partial<Challenge> | null>(null);
|
||||
const [editingTeam, setEditingTeam] = useState<Partial<Team> & { newPassword?: string } | null>(null);
|
||||
@@ -460,6 +461,60 @@ export const Admin: React.FC = () => {
|
||||
</div>
|
||||
<Button type="submit" className="w-full py-4 uppercase">Update Identity</Button>
|
||||
</form>
|
||||
<div className="mt-8 border-t-2 border-[#333] pt-6">
|
||||
<h4 className="text-xl font-black italic text-[#bf00ff] mb-4 uppercase">SOLVES</h4>
|
||||
<div className="space-y-2 max-h-48 overflow-y-auto pr-2">
|
||||
{state.solves.filter(s => s.teamId === editingTeam.id).length === 0 ? (
|
||||
<p className="text-slate-500 italic text-sm font-black">NO_SOLVES_FOUND</p>
|
||||
) : (
|
||||
state.solves
|
||||
.filter(s => s.teamId === editingTeam.id)
|
||||
.map(solve => ({
|
||||
solve,
|
||||
challenge: state.challenges.find(c => c.id === solve.challengeId)
|
||||
}))
|
||||
.sort((a, b) => (a.challenge?.title || '').localeCompare(b.challenge?.title || ''))
|
||||
.map(({ solve, challenge }) => {
|
||||
if (!challenge) return null;
|
||||
|
||||
const diffColor = challenge.difficulty === 'Low' ? 'text-[#00ff00]' : challenge.difficulty === 'Medium' ? 'text-[#ffaa00]' : 'text-[#ff0000]';
|
||||
|
||||
const challengeSolves = state.solves
|
||||
.filter(s => s.challengeId === challenge.id)
|
||||
.sort((a, b) => a.timestamp - b.timestamp);
|
||||
const rank = challengeSolves.findIndex(s => s.teamId === editingTeam.id);
|
||||
|
||||
const baseValue = calculateChallengeValue(
|
||||
challenge.initialPoints,
|
||||
challenge.minimumPoints || 0,
|
||||
challenge.decaySolves || 1,
|
||||
(challenge.solves || []).length
|
||||
);
|
||||
const bonus = Math.floor(challenge.initialPoints * getFirstBloodBonusFactor(rank));
|
||||
const totalPoints = baseValue + bonus;
|
||||
|
||||
return (
|
||||
<div key={solve.challengeId} className="flex justify-between items-center bg-white/5 border border-[#333] p-3 text-sm font-bold">
|
||||
<div className="flex items-center gap-2 truncate flex-1">
|
||||
{rank === 0 ? (
|
||||
<Medal size={16} className="text-[#ffaa00]" />
|
||||
) : rank === 1 ? (
|
||||
<Medal size={16} className="text-slate-400" />
|
||||
) : rank === 2 ? (
|
||||
<Medal size={16} className="text-[#cd7f32]" />
|
||||
) : (
|
||||
<CheckCircle2 size={14} className="text-[#00ff00]" />
|
||||
)}
|
||||
<span className={`truncate ${diffColor}`}>{challenge.title}</span>
|
||||
</div>
|
||||
<span className="text-white text-xs mr-4">{totalPoints} PTS</span>
|
||||
<button type="button" onClick={() => deleteSolve(editingTeam.id as string, solve.challengeId)} className="text-red-500 hover:text-red-400 transition-colors" title="Delete Solve"><Trash2 size={16}/></button>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user