diff options
Diffstat (limited to 'frontend')
| -rw-r--r-- | frontend/history-view.tsx | 237 | ||||
| -rw-r--r-- | frontend/lib/utils.ts | 8 |
2 files changed, 98 insertions, 147 deletions
diff --git a/frontend/history-view.tsx b/frontend/history-view.tsx index 8cec793..2faa1c0 100644 --- a/frontend/history-view.tsx +++ b/frontend/history-view.tsx | |||
| @@ -1,131 +1,15 @@ | |||
| 1 | "use client" | 1 | "use client" |
| 2 | 2 | ||
| 3 | import { useState, useEffect } from "react" | ||
| 3 | import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" | 4 | import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" |
| 4 | import { Badge } from "@/components/ui/badge" | 5 | import { Badge } from "@/components/ui/badge" |
| 5 | import { FileText, Folder, Trash2, Edit } from "lucide-react" | 6 | import { FileText, Folder, Trash2, Edit, Plus } from "lucide-react" |
| 7 | import { DriveLogEntry } from "@/lib/drive_types" | ||
| 8 | import { formatFileSize } from "@/lib/utils" | ||
| 6 | 9 | ||
| 7 | interface HistoryEntry { | ||
| 8 | id: string | ||
| 9 | type: "file_create" | "file_remove" | "dir_create" | "rename" | ||
| 10 | fileName: string | ||
| 11 | userEmail: string | ||
| 12 | timestamp: string | ||
| 13 | details?: string | ||
| 14 | } | ||
| 15 | |||
| 16 | const mockHistoryData: HistoryEntry[] = [ | ||
| 17 | { | ||
| 18 | id: "h1", | ||
| 19 | type: "file_create", | ||
| 20 | fileName: "Database Backup and Migration Scripts - Production Environment.sql", | ||
| 21 | userEmail: "[email protected]", | ||
| 22 | timestamp: "2024-01-16T09:30:00Z", | ||
| 23 | }, | ||
| 24 | { | ||
| 25 | id: "h2", | ||
| 26 | type: "rename", | ||
| 27 | fileName: "Configuration Files and System Settings - Development Environment Setup.txt", | ||
| 28 | userEmail: "[email protected]", | ||
| 29 | timestamp: "2024-01-16T09:25:00Z", | ||
| 30 | details: "Renamed from 'config.txt'", | ||
| 31 | }, | ||
| 32 | { | ||
| 33 | id: "h3", | ||
| 34 | type: "file_create", | ||
| 35 | fileName: "E-commerce Platform with React and Node.js - Full Stack Implementation.zip", | ||
| 36 | userEmail: "[email protected]", | ||
| 37 | timestamp: "2024-01-16T08:35:00Z", | ||
| 38 | }, | ||
| 39 | { | ||
| 40 | id: "h4", | ||
| 41 | type: "dir_create", | ||
| 42 | fileName: "Web Applications and Frontend Projects", | ||
| 43 | userEmail: "[email protected]", | ||
| 44 | timestamp: "2024-01-16T08:30:00Z", | ||
| 45 | }, | ||
| 46 | { | ||
| 47 | id: "h5", | ||
| 48 | type: "file_create", | ||
| 49 | fileName: "Dashboard Analytics Tool with Real-time Data Visualization Components.zip", | ||
| 50 | userEmail: "[email protected]", | ||
| 51 | timestamp: "2024-01-15T22:10:00Z", | ||
| 52 | }, | ||
| 53 | { | ||
| 54 | id: "h6", | ||
| 55 | type: "file_remove", | ||
| 56 | fileName: "old_backup_file.sql", | ||
| 57 | userEmail: "[email protected]", | ||
| 58 | timestamp: "2024-01-15T20:45:00Z", | ||
| 59 | }, | ||
| 60 | { | ||
| 61 | id: "h7", | ||
| 62 | type: "rename", | ||
| 63 | fileName: "Mobile App Development and Cross-Platform Solutions.zip", | ||
| 64 | userEmail: "[email protected]", | ||
| 65 | timestamp: "2024-01-14T13:25:00Z", | ||
| 66 | details: "Renamed from 'mobile_app_v1.zip'", | ||
| 67 | }, | ||
| 68 | { | ||
| 69 | id: "h8", | ||
| 70 | type: "file_create", | ||
| 71 | fileName: "Corporate Training Videos and Educational Content Series - Complete Collection.mp4", | ||
| 72 | userEmail: "[email protected]", | ||
| 73 | timestamp: "2024-01-13T14:15:00Z", | ||
| 74 | }, | ||
| 75 | { | ||
| 76 | id: "h9", | ||
| 77 | type: "dir_create", | ||
| 78 | fileName: "Video Content and Multimedia Projects", | ||
| 79 | userEmail: "[email protected]", | ||
| 80 | timestamp: "2024-01-13T14:00:00Z", | ||
| 81 | }, | ||
| 82 | { | ||
| 83 | id: "h10", | ||
| 84 | type: "file_remove", | ||
| 85 | fileName: "temp_presentation.pptx", | ||
| 86 | userEmail: "[email protected]", | ||
| 87 | timestamp: "2024-01-13T11:30:00Z", | ||
| 88 | }, | ||
| 89 | { | ||
| 90 | id: "h11", | ||
| 91 | type: "file_create", | ||
| 92 | fileName: "Professional Headshots and Corporate Event Photography - High Resolution.png", | ||
| 93 | userEmail: "[email protected]", | ||
| 94 | timestamp: "2024-01-13T11:15:00Z", | ||
| 95 | }, | ||
| 96 | { | ||
| 97 | id: "h12", | ||
| 98 | type: "rename", | ||
| 99 | fileName: "Travel and Vacation Photos Collection", | ||
| 100 | userEmail: "[email protected]", | ||
| 101 | timestamp: "2024-01-12T20:50:00Z", | ||
| 102 | details: "Renamed from 'Vacation Photos'", | ||
| 103 | }, | ||
| 104 | { | ||
| 105 | id: "h13", | ||
| 106 | type: "dir_create", | ||
| 107 | fileName: "Photography and Visual Content", | ||
| 108 | userEmail: "[email protected]", | ||
| 109 | timestamp: "2024-01-12T16:00:00Z", | ||
| 110 | }, | ||
| 111 | { | ||
| 112 | id: "h14", | ||
| 113 | type: "file_create", | ||
| 114 | fileName: "Professional Resume and Cover Letter Templates - Updated 2024.docx", | ||
| 115 | userEmail: "[email protected]", | ||
| 116 | timestamp: "2024-01-12T12:00:00Z", | ||
| 117 | }, | ||
| 118 | { | ||
| 119 | id: "h15", | ||
| 120 | type: "file_remove", | ||
| 121 | fileName: "draft_document.docx", | ||
| 122 | userEmail: "[email protected]", | ||
| 123 | timestamp: "2024-01-11T16:20:00Z", | ||
| 124 | }, | ||
| 125 | ] | ||
| 126 | 10 | ||
| 127 | function formatTimestamp(timestamp: string): string { | 11 | function formatTimestamp(timestamp: number): string { |
| 128 | const date = new Date(timestamp) | 12 | const date = new Date(timestamp * 1000) // Convert Unix timestamp to milliseconds |
| 129 | return date.toLocaleString("en-US", { | 13 | return date.toLocaleString("en-US", { |
| 130 | year: "numeric", | 14 | year: "numeric", |
| 131 | month: "short", | 15 | month: "short", |
| @@ -136,49 +20,106 @@ function formatTimestamp(timestamp: string): string { | |||
| 136 | }) | 20 | }) |
| 137 | } | 21 | } |
| 138 | 22 | ||
| 139 | function getActionIcon(type: HistoryEntry["type"]) { | 23 | function getActionIcon(action: string) { |
| 140 | switch (type) { | 24 | switch (action) { |
| 141 | case "file_create": | 25 | case "create_file": |
| 142 | return <FileText className="h-4 w-4 text-green-600" /> | 26 | return <FileText className="h-4 w-4 text-green-600" /> |
| 143 | case "dir_create": | 27 | case "create_dir": |
| 144 | return <Folder className="h-4 w-4 text-blue-600" /> | 28 | return <Folder className="h-4 w-4 text-blue-600" /> |
| 145 | case "file_remove": | 29 | case "remove": |
| 146 | return <Trash2 className="h-4 w-4 text-red-600" /> | 30 | return <Trash2 className="h-4 w-4 text-red-600" /> |
| 147 | case "rename": | 31 | default: |
| 148 | return <Edit className="h-4 w-4 text-orange-600" /> | 32 | return <Plus className="h-4 w-4 text-gray-600" /> |
| 149 | } | 33 | } |
| 150 | } | 34 | } |
| 151 | 35 | ||
| 152 | function getActionBadge(type: HistoryEntry["type"]) { | 36 | function getActionBadge(action: string) { |
| 153 | switch (type) { | 37 | switch (action) { |
| 154 | case "file_create": | 38 | case "create_file": |
| 155 | return ( | 39 | return ( |
| 156 | <Badge variant="outline" className="text-green-700 border-green-300 bg-green-50"> | 40 | <Badge variant="outline" className="text-green-700 border-green-300 bg-green-50"> |
| 157 | File Created | 41 | File Created |
| 158 | </Badge> | 42 | </Badge> |
| 159 | ) | 43 | ) |
| 160 | case "dir_create": | 44 | case "create_dir": |
| 161 | return ( | 45 | return ( |
| 162 | <Badge variant="outline" className="text-blue-700 border-blue-300 bg-blue-50"> | 46 | <Badge variant="outline" className="text-blue-700 border-blue-300 bg-blue-50"> |
| 163 | Directory Created | 47 | Directory Created |
| 164 | </Badge> | 48 | </Badge> |
| 165 | ) | 49 | ) |
| 166 | case "file_remove": | 50 | case "remove": |
| 167 | return ( | 51 | return ( |
| 168 | <Badge variant="outline" className="text-red-700 border-red-300 bg-red-50"> | 52 | <Badge variant="outline" className="text-red-700 border-red-300 bg-red-50"> |
| 169 | File Removed | 53 | File/Directory Removed |
| 170 | </Badge> | 54 | </Badge> |
| 171 | ) | 55 | ) |
| 172 | case "rename": | 56 | default: |
| 173 | return ( | 57 | return ( |
| 174 | <Badge variant="outline" className="text-orange-700 border-orange-300 bg-orange-50"> | 58 | <Badge variant="outline" className="text-gray-700 border-gray-300 bg-gray-50"> |
| 175 | Renamed | 59 | {action} |
| 176 | </Badge> | 60 | </Badge> |
| 177 | ) | 61 | ) |
| 178 | } | 62 | } |
| 179 | } | 63 | } |
| 180 | 64 | ||
| 181 | export default function HistoryView() { | 65 | export default function HistoryView() { |
| 66 | const [logEntries, setLogEntries] = useState<DriveLogEntry[]>([]) | ||
| 67 | const [loading, setLoading] = useState(true) | ||
| 68 | const [error, setError] = useState<string | null>(null) | ||
| 69 | |||
| 70 | useEffect(() => { | ||
| 71 | async function fetchLogEntries() { | ||
| 72 | try { | ||
| 73 | setLoading(true) | ||
| 74 | const response = await fetch('/api/log') | ||
| 75 | if (!response.ok) { | ||
| 76 | throw new Error('Failed to fetch log entries') | ||
| 77 | } | ||
| 78 | const data: DriveLogEntry[] = await response.json() | ||
| 79 | // Reverse to show latest entries first | ||
| 80 | setLogEntries(data.reverse()) | ||
| 81 | } catch (err) { | ||
| 82 | setError(err instanceof Error ? err.message : 'Unknown error') | ||
| 83 | } finally { | ||
| 84 | setLoading(false) | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | fetchLogEntries() | ||
| 89 | }, []) | ||
| 90 | |||
| 91 | if (loading) { | ||
| 92 | return ( | ||
| 93 | <div className="space-y-6"> | ||
| 94 | <div className="flex items-center justify-between"> | ||
| 95 | <div> | ||
| 96 | <h2 className="text-xl font-semibold">Activity History</h2> | ||
| 97 | <p className="text-sm text-muted-foreground">Recent filesystem modifications and changes</p> | ||
| 98 | </div> | ||
| 99 | </div> | ||
| 100 | <div className="text-center py-8"> | ||
| 101 | <p>Loading history...</p> | ||
| 102 | </div> | ||
| 103 | </div> | ||
| 104 | ) | ||
| 105 | } | ||
| 106 | |||
| 107 | if (error) { | ||
| 108 | return ( | ||
| 109 | <div className="space-y-6"> | ||
| 110 | <div className="flex items-center justify-between"> | ||
| 111 | <div> | ||
| 112 | <h2 className="text-xl font-semibold">Activity History</h2> | ||
| 113 | <p className="text-sm text-muted-foreground">Recent filesystem modifications and changes</p> | ||
| 114 | </div> | ||
| 115 | </div> | ||
| 116 | <div className="text-center py-8 text-red-600"> | ||
| 117 | <p>Error: {error}</p> | ||
| 118 | </div> | ||
| 119 | </div> | ||
| 120 | ) | ||
| 121 | } | ||
| 122 | |||
| 182 | return ( | 123 | return ( |
| 183 | <div className="space-y-6"> | 124 | <div className="space-y-6"> |
| 184 | {/* History Header */} | 125 | {/* History Header */} |
| @@ -187,7 +128,7 @@ export default function HistoryView() { | |||
| 187 | <h2 className="text-xl font-semibold">Activity History</h2> | 128 | <h2 className="text-xl font-semibold">Activity History</h2> |
| 188 | <p className="text-sm text-muted-foreground">Recent filesystem modifications and changes</p> | 129 | <p className="text-sm text-muted-foreground">Recent filesystem modifications and changes</p> |
| 189 | </div> | 130 | </div> |
| 190 | <Badge variant="secondary">{mockHistoryData.length} total entries</Badge> | 131 | <Badge variant="secondary">{logEntries.length} total entries</Badge> |
| 191 | </div> | 132 | </div> |
| 192 | 133 | ||
| 193 | {/* History Table */} | 134 | {/* History Table */} |
| @@ -200,25 +141,27 @@ export default function HistoryView() { | |||
| 200 | <TableHead>File/Directory Name</TableHead> | 141 | <TableHead>File/Directory Name</TableHead> |
| 201 | <TableHead>User</TableHead> | 142 | <TableHead>User</TableHead> |
| 202 | <TableHead>Timestamp</TableHead> | 143 | <TableHead>Timestamp</TableHead> |
| 203 | <TableHead>Details</TableHead> | 144 | <TableHead>Size</TableHead> |
| 204 | </TableRow> | 145 | </TableRow> |
| 205 | </TableHeader> | 146 | </TableHeader> |
| 206 | <TableBody> | 147 | <TableBody> |
| 207 | {mockHistoryData.map((entry) => ( | 148 | {logEntries.map((entry) => ( |
| 208 | <TableRow key={entry.id} className="hover:bg-muted/50"> | 149 | <TableRow key={`${entry.log_id}`} className="hover:bg-muted/50"> |
| 209 | <TableCell>{getActionIcon(entry.type)}</TableCell> | 150 | <TableCell>{getActionIcon(entry.action)}</TableCell> |
| 210 | <TableCell>{getActionBadge(entry.type)}</TableCell> | 151 | <TableCell>{getActionBadge(entry.action)}</TableCell> |
| 211 | <TableCell className="font-medium"> | 152 | <TableCell className="font-medium"> |
| 212 | <span className="break-words">{entry.fileName}</span> | 153 | <span className="break-words">{entry.path}</span> |
| 213 | </TableCell> | 154 | </TableCell> |
| 214 | <TableCell> | 155 | <TableCell> |
| 215 | <span className="text-sm font-mono">{entry.userEmail}</span> | 156 | <span className="text-sm font-mono">{entry.email}</span> |
| 216 | </TableCell> | 157 | </TableCell> |
| 217 | <TableCell> | 158 | <TableCell> |
| 218 | <span className="text-sm">{formatTimestamp(entry.timestamp)}</span> | 159 | <span className="text-sm">{formatTimestamp(entry.timestamp)}</span> |
| 219 | </TableCell> | 160 | </TableCell> |
| 220 | <TableCell> | 161 | <TableCell> |
| 221 | <span className="text-sm text-muted-foreground">{entry.details || "—"}</span> | 162 | <span className="text-sm text-muted-foreground"> |
| 163 | {entry.size ? formatFileSize(entry.size) : "—"} | ||
| 164 | </span> | ||
| 222 | </TableCell> | 165 | </TableCell> |
| 223 | </TableRow> | 166 | </TableRow> |
| 224 | ))} | 167 | ))} |
diff --git a/frontend/lib/utils.ts b/frontend/lib/utils.ts index bd0c391..200e3e7 100644 --- a/frontend/lib/utils.ts +++ b/frontend/lib/utils.ts | |||
| @@ -4,3 +4,11 @@ import { twMerge } from "tailwind-merge" | |||
| 4 | export function cn(...inputs: ClassValue[]) { | 4 | export function cn(...inputs: ClassValue[]) { |
| 5 | return twMerge(clsx(inputs)) | 5 | return twMerge(clsx(inputs)) |
| 6 | } | 6 | } |
| 7 | |||
| 8 | export function formatFileSize(bytes: number): string { | ||
| 9 | if (bytes === 0) return "0 Bytes" | ||
| 10 | const k = 1024 | ||
| 11 | const sizes = ["Bytes", "KB", "MB", "GB"] | ||
| 12 | const i = Math.floor(Math.log(bytes) / Math.log(k)) | ||
| 13 | return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i] | ||
| 14 | } | ||
