Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab88c56320 |
54
app/components/Navigation.tsx
Normal file
54
app/components/Navigation.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { usePathname } from 'next/navigation'
|
||||||
|
import { useAuth } from '../contexts/AuthContext'
|
||||||
|
|
||||||
|
export default function Navigation() {
|
||||||
|
const { isAuthenticated, logout } = useAuth()
|
||||||
|
const pathname = usePathname()
|
||||||
|
|
||||||
|
if (pathname === '/login' || pathname === '/dashboard') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className="bg-white shadow-sm dark:bg-black">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex justify-between items-center h-16">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Link href="/" className="text-xl font-bold text-black dark:text-zinc-50">
|
||||||
|
Agent Manager
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
{isAuthenticated ? (
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<Link
|
||||||
|
href="/dashboard"
|
||||||
|
className="rounded-md bg-blue-500 px-4 py-2 text-white transition-colors hover:bg-blue-600"
|
||||||
|
>
|
||||||
|
Dashboard
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
onClick={logout}
|
||||||
|
className="rounded-md bg-red-500 px-4 py-2 text-white transition-colors hover:bg-red-600"
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
href="/login"
|
||||||
|
className="rounded-md bg-green-500 px-4 py-2 text-white transition-colors hover:bg-green-600"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
||||||
57
app/contexts/AuthContext.tsx
Normal file
57
app/contexts/AuthContext.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import React, { createContext, useContext, useState } from 'react'
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
username: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AuthContextType {
|
||||||
|
user: User | null
|
||||||
|
login: (username: string, password: string) => boolean
|
||||||
|
logout: () => void
|
||||||
|
isAuthenticated: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthContext = createContext<AuthContextType | undefined>(undefined)
|
||||||
|
|
||||||
|
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const [user, setUser] = useState<User | null>(() => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const savedUser = localStorage.getItem('user')
|
||||||
|
return savedUser ? JSON.parse(savedUser) : null
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
const login = (username: string, password: string): boolean => {
|
||||||
|
if (username === 'admin' && password === 'Mallory122907Fucku2u2') {
|
||||||
|
const userData = { username }
|
||||||
|
setUser(userData)
|
||||||
|
localStorage.setItem('user', JSON.stringify(userData))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
setUser(null)
|
||||||
|
localStorage.removeItem('user')
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAuthenticated = !!user
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider value={{ user, login, logout, isAuthenticated }}>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAuth() {
|
||||||
|
const context = useContext(AuthContext)
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useAuth must be used within an AuthProvider')
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
185
app/dashboard/page.tsx
Normal file
185
app/dashboard/page.tsx
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useAuth } from './contexts/AuthContext'
|
||||||
|
|
||||||
|
interface Agent {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
status: 'active' | 'inactive' | 'error'
|
||||||
|
lastRun: string
|
||||||
|
dataCollected: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DashboardPage() {
|
||||||
|
const { user, logout } = useAuth()
|
||||||
|
const [agents, setAgents] = useState<Agent[]>([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'News Collector',
|
||||||
|
status: 'active',
|
||||||
|
lastRun: '2024-01-03 10:30:00',
|
||||||
|
dataCollected: 1250
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
name: 'Social Media Monitor',
|
||||||
|
status: 'inactive',
|
||||||
|
lastRun: '2024-01-02 15:45:00',
|
||||||
|
dataCollected: 890
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
name: 'Market Data Scraper',
|
||||||
|
status: 'error',
|
||||||
|
lastRun: '2024-01-01 09:00:00',
|
||||||
|
dataCollected: 567
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const toggleAgentStatus = (id: string) => {
|
||||||
|
setAgents(prev => prev.map(agent => {
|
||||||
|
if (agent.id === id) {
|
||||||
|
const newStatus = agent.status === 'active' ? 'inactive' : 'active'
|
||||||
|
return { ...agent, status: newStatus }
|
||||||
|
}
|
||||||
|
return agent
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusColor = (status: Agent['status']) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'active': return 'text-green-500'
|
||||||
|
case 'inactive': return 'text-gray-500'
|
||||||
|
case 'error': return 'text-red-500'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusBg = (status: Agent['status']) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'active': return 'bg-green-100 dark:bg-green-900'
|
||||||
|
case 'inactive': return 'bg-gray-100 dark:bg-gray-900'
|
||||||
|
case 'error': return 'bg-red-100 dark:bg-red-900'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-zinc-50 font-sans dark:bg-black">
|
||||||
|
<header className="bg-white shadow-sm dark:bg-black">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex justify-between items-center py-4">
|
||||||
|
<h1 className="text-2xl font-bold text-black dark:text-zinc-50">
|
||||||
|
Agent Dashboard
|
||||||
|
</h1>
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<span className="text-black dark:text-zinc-50">
|
||||||
|
Welcome, {user?.username}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={logout}
|
||||||
|
className="rounded-md bg-red-500 px-4 py-2 text-white transition-colors hover:bg-red-600"
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
|
<div className="mb-8">
|
||||||
|
<h2 className="text-xl font-semibold text-black dark:text-zinc-50 mb-4">
|
||||||
|
Information Collection Agents
|
||||||
|
</h2>
|
||||||
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{agents.map((agent) => (
|
||||||
|
<div
|
||||||
|
key={agent.id}
|
||||||
|
className="rounded-lg bg-white p-6 shadow-md dark:bg-black"
|
||||||
|
>
|
||||||
|
<div className="flex justify-between items-start mb-4">
|
||||||
|
<h3 className="text-lg font-medium text-black dark:text-zinc-50">
|
||||||
|
{agent.name}
|
||||||
|
</h3>
|
||||||
|
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getStatusBg(agent.status)} ${getStatusColor(agent.status)}`}>
|
||||||
|
{agent.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2 text-sm">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-gray-600 dark:text-gray-400">Last Run:</span>
|
||||||
|
<span className="text-black dark:text-zinc-50">{agent.lastRun}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-gray-600 dark:text-gray-400">Data Collected:</span>
|
||||||
|
<span className="text-black dark:text-zinc-50">{agent.dataCollected}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 flex space-x-2">
|
||||||
|
<button
|
||||||
|
onClick={() => toggleAgentStatus(agent.id)}
|
||||||
|
className={`flex-1 rounded-md px-3 py-2 text-sm font-medium transition-colors ${
|
||||||
|
agent.status === 'active'
|
||||||
|
? 'bg-red-500 text-white hover:bg-red-600'
|
||||||
|
: 'bg-green-500 text-white hover:bg-green-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{agent.status === 'active' ? 'Stop' : 'Start'}
|
||||||
|
</button>
|
||||||
|
<button className="flex-1 rounded-md bg-blue-500 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-600">
|
||||||
|
Configure
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-6 md:grid-cols-2">
|
||||||
|
<div className="rounded-lg bg-white p-6 shadow-md dark:bg-black">
|
||||||
|
<h3 className="text-lg font-medium text-black dark:text-zinc-50 mb-4">
|
||||||
|
Quick Actions
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<button className="w-full rounded-md bg-blue-500 px-4 py-2 text-white transition-colors hover:bg-blue-600">
|
||||||
|
Add New Agent
|
||||||
|
</button>
|
||||||
|
<button className="w-full rounded-md bg-green-500 px-4 py-2 text-white transition-colors hover:bg-green-600">
|
||||||
|
Run All Agents
|
||||||
|
</button>
|
||||||
|
<button className="w-full rounded-md bg-gray-500 px-4 py-2 text-white transition-colors hover:bg-gray-600">
|
||||||
|
Export Data
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg bg-white p-6 shadow-md dark:bg-black">
|
||||||
|
<h3 className="text-lg font-medium text-black dark:text-zinc-50 mb-4">
|
||||||
|
System Overview
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-3 text-sm">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-gray-600 dark:text-gray-400">Total Agents:</span>
|
||||||
|
<span className="text-black dark:text-zinc-50">{agents.length}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-gray-600 dark:text-gray-400">Active Agents:</span>
|
||||||
|
<span className="text-green-500">
|
||||||
|
{agents.filter(a => a.status === 'active').length}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-gray-600 dark:text-gray-400">Total Data Points:</span>
|
||||||
|
<span className="text-black dark:text-zinc-50">
|
||||||
|
{agents.reduce((sum, a) => sum + a.dataCollected, 0)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
import { AuthProvider } from "./contexts/AuthContext";
|
||||||
|
import Navigation from "./components/Navigation";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
variable: "--font-geist-sans",
|
variable: "--font-geist-sans",
|
||||||
@@ -27,7 +29,10 @@ export default function RootLayout({
|
|||||||
<body
|
<body
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
>
|
>
|
||||||
{children}
|
<AuthProvider>
|
||||||
|
<Navigation />
|
||||||
|
{children}
|
||||||
|
</AuthProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
69
app/login/page.tsx
Normal file
69
app/login/page.tsx
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { useAuth } from './contexts/AuthContext'
|
||||||
|
|
||||||
|
export default function LoginPage() {
|
||||||
|
const [username, setUsername] = useState('')
|
||||||
|
const [password, setPassword] = useState('')
|
||||||
|
const [error, setError] = useState('')
|
||||||
|
const { login } = useAuth()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
setError('')
|
||||||
|
|
||||||
|
if (login(username, password)) {
|
||||||
|
router.push('/dashboard')
|
||||||
|
} else {
|
||||||
|
setError('Invalid credentials')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||||
|
<div className="w-full max-w-md rounded-lg bg-white p-8 shadow-lg dark:bg-black">
|
||||||
|
<h2 className="mb-6 text-center text-2xl font-bold text-black dark:text-zinc-50">
|
||||||
|
Login
|
||||||
|
</h2>
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="mb-2 block text-sm font-medium text-black dark:text-zinc-50">
|
||||||
|
Username
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
className="w-full rounded-md border border-gray-300 px-3 py-2 text-black focus:outline-none focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-zinc-50"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="mb-2 block text-sm font-medium text-black dark:text-zinc-50">
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
className="w-full rounded-md border border-gray-300 px-3 py-2 text-black focus:outline-none focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-zinc-50"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{error && (
|
||||||
|
<p className="text-center text-sm text-red-500">{error}</p>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full rounded-md bg-blue-500 px-4 py-2 text-white transition-colors hover:bg-blue-600"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
67
app/page.tsx
67
app/page.tsx
@@ -1,65 +1,40 @@
|
|||||||
import Image from "next/image";
|
'use client'
|
||||||
|
|
||||||
|
import { useAuth } from './contexts/AuthContext'
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
const { isAuthenticated } = useAuth()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isAuthenticated) {
|
||||||
|
router.push('/dashboard')
|
||||||
|
}
|
||||||
|
}, [isAuthenticated, router])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||||
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||||
<Image
|
|
||||||
className="dark:invert"
|
|
||||||
src="/next.svg"
|
|
||||||
alt="Next.js logo"
|
|
||||||
width={100}
|
|
||||||
height={20}
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
||||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
||||||
To get started, edit the page.tsx file.
|
Agent Management System
|
||||||
</h1>
|
</h1>
|
||||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
||||||
Looking for a starting point or more instructions? Head over to{" "}
|
Manage your information collection agents with our powerful dashboard.
|
||||||
<a
|
Please login to access the agent management features.
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
|
||||||
>
|
|
||||||
Templates
|
|
||||||
</a>{" "}
|
|
||||||
or the{" "}
|
|
||||||
<a
|
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
|
||||||
>
|
|
||||||
Learning
|
|
||||||
</a>{" "}
|
|
||||||
center.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
||||||
<a
|
<a
|
||||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-blue-500 px-5 text-white transition-colors hover:bg-blue-600 md:w-[158px]"
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
href="/login"
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
>
|
||||||
<Image
|
Login to Dashboard
|
||||||
className="dark:invert"
|
|
||||||
src="/vercel.svg"
|
|
||||||
alt="Vercel logomark"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Deploy Now
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
|
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Documentation
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
6113
package-lock.json
generated
Normal file
6113
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user