diff --git a/asf-cloud-server/testarena/backend/app/main.py b/asf-cloud-server/testarena/backend/app/main.py index 44545fc..44d5a45 100644 --- a/asf-cloud-server/testarena/backend/app/main.py +++ b/asf-cloud-server/testarena/backend/app/main.py @@ -1,4 +1,5 @@ from fastapi import FastAPI, Depends, HTTPException, status, WebSocket, WebSocketDisconnect, BackgroundTasks +from pydantic import BaseModel from fastapi.middleware.cors import CORSMiddleware from sqlalchemy.orm import Session from typing import List @@ -33,17 +34,20 @@ def login_for_access_token(form_data: schemas.UserCreate, db: Session = Depends( ) return {"access_token": access_token, "token_type": "bearer"} +class PasswordResetRequest(BaseModel): + username: str + new_password: str + @app.post("/auth/reset-password") def reset_password( - username: str, - new_password: str, + request: PasswordResetRequest, current_user: models.User = Depends(auth.get_current_admin_user), db: Session = Depends(get_db) ): - user = crud.get_user_by_username(db, username) + user = crud.get_user_by_username(db, request.username) if not user: raise HTTPException(status_code=404, detail="User not found") - user.hashed_password = auth.get_password_hash(new_password) + user.hashed_password = auth.get_password_hash(request.new_password) db.commit() return {"message": "Password reset successfully"} diff --git a/asf-cloud-server/testarena/backend/scripts/reset_admin.py b/asf-cloud-server/testarena/backend/scripts/reset_admin.py new file mode 100644 index 0000000..e88c81b --- /dev/null +++ b/asf-cloud-server/testarena/backend/scripts/reset_admin.py @@ -0,0 +1,34 @@ +import sys +import os + +# Add the parent directory to sys.path to import app modules +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from app import database, crud, schemas, auth, models + +def reset_admin(): + db = database.SessionLocal() + try: + username = "admin" + password = "admin123" + + user = crud.get_user_by_username(db, username) + if user: + print(f"Updating password for {username}...") + user.hashed_password = auth.get_password_hash(password) + db.commit() + print("Password updated successfully.") + else: + print(f"Creating user {username}...") + user_in = schemas.UserCreate(username=username, password=password, role=models.UserRole.admin) + hashed_password = auth.get_password_hash(password) + crud.create_user(db, user_in, hashed_password) + print("User created successfully.") + + except Exception as e: + print(f"Error: {e}") + finally: + db.close() + +if __name__ == "__main__": + reset_admin() diff --git a/asf-cloud-server/testarena/frontend/public/logo.png b/asf-cloud-server/testarena/frontend/public/logo.png new file mode 100644 index 0000000..73e6fb6 Binary files /dev/null and b/asf-cloud-server/testarena/frontend/public/logo.png differ diff --git a/asf-cloud-server/testarena/frontend/src/pages/Dashboard.tsx b/asf-cloud-server/testarena/frontend/src/pages/Dashboard.tsx index 5ec07f8..45d137d 100644 --- a/asf-cloud-server/testarena/frontend/src/pages/Dashboard.tsx +++ b/asf-cloud-server/testarena/frontend/src/pages/Dashboard.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { useAuth } from '../context/AuthContext'; import api from '../api'; import { useNavigate } from 'react-router-dom'; -import { CheckCircle, XCircle, Clock, AlertCircle, Plus, LogOut } from 'lucide-react'; +import { CheckCircle, XCircle, Clock, AlertCircle, Plus, LogOut, Search, Filter, Menu } from 'lucide-react'; import clsx from 'clsx'; interface Job { @@ -18,7 +18,7 @@ interface Job { } const Dashboard: React.FC = () => { - const { logout } = useAuth(); + const { logout, user } = useAuth(); const [jobs, setJobs] = useState([]); const [selectedJob, setSelectedJob] = useState(null); const navigate = useNavigate(); @@ -63,7 +63,7 @@ const Dashboard: React.FC = () => { const handleAbort = async (jobId: number) => { try { await api.post(`/jobs/${jobId}/abort`); - fetchJobs(); // Refresh to ensure sync + fetchJobs(); } catch (error) { console.error("Failed to abort job", error); } @@ -71,120 +71,171 @@ const Dashboard: React.FC = () => { const getStatusIcon = (status: string) => { switch (status) { - case 'passed': return ; - case 'failed': return ; - case 'running': return ; - case 'aborted': return ; - default: return ; + case 'passed': return ; + case 'failed': return ; + case 'running': return ; + case 'aborted': return ; + default: return ; } }; return ( -
- {/* Sidebar */} -
-
-

TestArena

-
- -
-
- {jobs.map(job => ( -
setSelectedJob(job)} - className={clsx( - "p-4 border-b border-gray-100 cursor-pointer hover:bg-gray-50 flex items-center justify-between", - selectedJob?.id === job.id && "bg-blue-50 border-l-4 border-l-accent" - )} - > -
-
#{job.id} {job.branch_name}
-
{new Date(job.created_at).toLocaleString()}
-
-
{getStatusIcon(job.status)}
-
- ))} + + + {/* Sub-header / Filters */} +
+
+
+ FILTERS +
+
+ + + +
+
+ +
{/* Main Content */} -
- {selectedJob ? ( -
-
-

Job #{selectedJob.id} Details

- {selectedJob.status === 'running' && ( - - )} -
+
-
-
-
Status
-
- {getStatusIcon(selectedJob.status)} - {selectedJob.status} + {/* Left Panel: Job List */} +
+
+ TEST REQUESTS +
+
+ + + + + + + + + + {jobs.map(job => ( + setSelectedJob(job)} + className={clsx( + "cursor-pointer border-b border-gray-100 hover:bg-blue-50 transition-colors", + selectedJob?.id === job.id && "bg-blue-50 border-l-4 border-l-accent" + )} + > + + + + + ))} + +
IDSTATUSBRANCH
#{job.id}{getStatusIcon(job.status)}{job.branch_name}
+
+
+ + {/* Right Panel: Job Details */} +
+
+ TEST INFO +
+ + {selectedJob ? ( +
+
+
+

Test Request #{selectedJob.id}

+

Submitted on {new Date(selectedJob.created_at).toLocaleString()}

+
+
+ {selectedJob.status === 'running' && ( + + )} + {selectedJob.result_path && ( + + View Results + + )}
-
-
Duration
-
{selectedJob.duration || '-'}
-
-
-
Branch
-
{selectedJob.branch_name}
-
-
-
Environment
-
{selectedJob.environment}
-
-
-
Test Mode
-
{selectedJob.test_mode}
-
-
-
-

Selected Scenarios

-
- {selectedJob.scenarios.map(s => ( - - {s} +
+
+ Status: + + {getStatusIcon(selectedJob.status)} {selectedJob.status} - ))} +
+
+ Duration: + {selectedJob.duration || '-'} +
+
+ Branch: + {selectedJob.branch_name} +
+
+ Environment: + {selectedJob.environment} +
+
+ Test Mode: + {selectedJob.test_mode} +
+
+ +
+

Selected Scenarios

+
+ {selectedJob.scenarios.map(s => ( + + {s} + + ))} +
+ ) : ( +
+ Select a job from the list to view details +
+ )} +
- {selectedJob.result_path && ( - - )} -
- ) : ( -
- Select a job to view details -
- )}
); diff --git a/asf-cloud-server/testarena/frontend/src/pages/Login.tsx b/asf-cloud-server/testarena/frontend/src/pages/Login.tsx index d1a811e..ccbf77c 100644 --- a/asf-cloud-server/testarena/frontend/src/pages/Login.tsx +++ b/asf-cloud-server/testarena/frontend/src/pages/Login.tsx @@ -27,40 +27,45 @@ const Login: React.FC = () => { }; return ( -
-
-

ASF TestArena

- {error &&
{error}
} +
+
+
+ Logo +
+

Sign In

+ {error &&
{error}
}
- + setUsername(e.target.value)} required + placeholder="Enter your username" />
- + setPassword(e.target.value)} required + placeholder="Enter your password" />
diff --git a/asf-cloud-server/testarena/frontend/tailwind.config.js b/asf-cloud-server/testarena/frontend/tailwind.config.js index d20fb18..6d68676 100644 --- a/asf-cloud-server/testarena/frontend/tailwind.config.js +++ b/asf-cloud-server/testarena/frontend/tailwind.config.js @@ -7,9 +7,13 @@ export default { theme: { extend: { colors: { - primary: '#0f172a', // Slate 900 - secondary: '#334155', // Slate 700 - accent: '#3b82f6', // Blue 500 + primary: '#1e40af', // Deep Blue (Header) + secondary: '#f3f4f6', // Light Gray (Background) + accent: '#3b82f6', // Bright Blue (Buttons/Highlights) + 'accent-hover': '#2563eb', + surface: '#ffffff', // White (Cards) + text: '#1f2937', // Dark Gray (Text) + 'text-muted': '#6b7280', // Light Gray (Subtext) } }, },