diff options
| -rw-r--r-- | frontend/components/history/HistoryView.tsx | 14 | ||||
| -rw-r--r-- | frontend/lib/drive_server.ts | 66 | ||||
| -rw-r--r-- | frontend/lib/drive_types.ts | 51 |
3 files changed, 115 insertions, 16 deletions
diff --git a/frontend/components/history/HistoryView.tsx b/frontend/components/history/HistoryView.tsx index b01e4c6..d9d3dc2 100644 --- a/frontend/components/history/HistoryView.tsx +++ b/frontend/components/history/HistoryView.tsx | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | import { DriveLogEntry } from "@/lib/drive_types" | 1 | import { DriveLogEntry, isCreateFileEntry, isRenameEntry } from "@/lib/drive_types" |
| 2 | import { DriveHeader } from "@/components/drive/DriveHeader" | 2 | import { DriveHeader } from "@/components/drive/DriveHeader" |
| 3 | import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" | 3 | import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" |
| 4 | import { Button } from "@/components/ui/button" | 4 | import { Button } from "@/components/ui/button" |
| @@ -102,7 +102,7 @@ export function HistoryView({ entries, currentPage, hasNextPage, hasPrevPage, to | |||
| 102 | </TableRow> | 102 | </TableRow> |
| 103 | ) : ( | 103 | ) : ( |
| 104 | entries.map((entry) => ( | 104 | entries.map((entry) => ( |
| 105 | <TableRow key={entry.log_id} className="hover:bg-muted/50"> | 105 | <TableRow key={entry.revision} className="hover:bg-muted/50"> |
| 106 | <TableCell className="font-mono text-sm"> | 106 | <TableCell className="font-mono text-sm"> |
| 107 | {formatDateTime(entry.timestamp)} | 107 | {formatDateTime(entry.timestamp)} |
| 108 | </TableCell> | 108 | </TableCell> |
| @@ -115,19 +115,23 @@ export function HistoryView({ entries, currentPage, hasNextPage, hasPrevPage, to | |||
| 115 | {entry.email} | 115 | {entry.email} |
| 116 | </TableCell> | 116 | </TableCell> |
| 117 | <TableCell className="font-mono text-sm max-w-md truncate"> | 117 | <TableCell className="font-mono text-sm max-w-md truncate"> |
| 118 | {entry.action === "create_file" && entry.blob_id !== "-" ? ( | 118 | {isCreateFileEntry(entry) ? ( |
| 119 | <Link | 119 | <Link |
| 120 | href={`/blob/${entry.blob_id}?filename=${encodeURIComponent(entry.path.split('/').pop() || 'download')}`} | 120 | href={`/blob/${entry.blob_id}?filename=${encodeURIComponent(entry.path.split('/').pop() || 'download')}`} |
| 121 | className="text-blue-600 hover:text-blue-800 hover:underline" | 121 | className="text-blue-600 hover:text-blue-800 hover:underline" |
| 122 | > | 122 | > |
| 123 | {entry.path} | 123 | {entry.path} |
| 124 | </Link> | 124 | </Link> |
| 125 | ) : isRenameEntry(entry) ? ( | ||
| 126 | <span> | ||
| 127 | {entry.old_path} → {entry.new_path} | ||
| 128 | </span> | ||
| 125 | ) : ( | 129 | ) : ( |
| 126 | entry.path | 130 | 'path' in entry ? entry.path : '' |
| 127 | )} | 131 | )} |
| 128 | </TableCell> | 132 | </TableCell> |
| 129 | <TableCell> | 133 | <TableCell> |
| 130 | {formatFileSize(entry.size)} | 134 | {isCreateFileEntry(entry) ? formatFileSize(entry.size) : '-'} |
| 131 | </TableCell> | 135 | </TableCell> |
| 132 | </TableRow> | 136 | </TableRow> |
| 133 | )) | 137 | )) |
diff --git a/frontend/lib/drive_server.ts b/frontend/lib/drive_server.ts index 8af0356..e7c88e8 100644 --- a/frontend/lib/drive_server.ts +++ b/frontend/lib/drive_server.ts | |||
| @@ -139,23 +139,73 @@ export async function Drive_log(): Promise<DriveLogEntry[]> { | |||
| 139 | } | 139 | } |
| 140 | 140 | ||
| 141 | const stdout = result.stdout | 141 | const stdout = result.stdout |
| 142 | const entries = [] | 142 | const entries: DriveLogEntry[] = [] |
| 143 | for (const line of stdout.split('\n')) { | 143 | for (const line of stdout.split('\n')) { |
| 144 | if (line.trim() === "") | 144 | if (line.trim() === "") |
| 145 | continue; | 145 | continue; |
| 146 | 146 | ||
| 147 | const parts = line.split('\t'); | 147 | const parts = line.split('\t'); |
| 148 | const timestamp = parseInt(parts[0]); | 148 | const timestamp = parseInt(parts[0]); |
| 149 | const log_id = parseInt(parts[1]); | 149 | const revision = parseInt(parts[1]); |
| 150 | const email = parts[2]; | 150 | const email = parts[2]; |
| 151 | const action = parts[3]; | 151 | const action = parts[3]; |
| 152 | const path = parts[4]; | ||
| 153 | const blob_id = parts[5]; | ||
| 154 | const size = parseInt(parts[6]); | ||
| 155 | 152 | ||
| 156 | entries.push({ | 153 | // Parse based on action type to create the correct discriminated union |
| 157 | timestamp, log_id, email, action, path, blob_id, size | 154 | switch (action) { |
| 158 | } as DriveLogEntry); | 155 | case "create_file": { |
| 156 | const path = parts[4]; | ||
| 157 | const blob_id = parts[5]; | ||
| 158 | const size = parseInt(parts[6]); | ||
| 159 | entries.push({ | ||
| 160 | timestamp, | ||
| 161 | revision, | ||
| 162 | email, | ||
| 163 | action: "create_file", | ||
| 164 | path, | ||
| 165 | blob_id, | ||
| 166 | size | ||
| 167 | }); | ||
| 168 | break; | ||
| 169 | } | ||
| 170 | case "create_dir": { | ||
| 171 | const path = parts[4]; | ||
| 172 | entries.push({ | ||
| 173 | timestamp, | ||
| 174 | revision, | ||
| 175 | email, | ||
| 176 | action: "create_dir", | ||
| 177 | path | ||
| 178 | }); | ||
| 179 | break; | ||
| 180 | } | ||
| 181 | case "remove": { | ||
| 182 | const path = parts[4]; | ||
| 183 | entries.push({ | ||
| 184 | timestamp, | ||
| 185 | revision, | ||
| 186 | email, | ||
| 187 | action: "remove", | ||
| 188 | path | ||
| 189 | }); | ||
| 190 | break; | ||
| 191 | } | ||
| 192 | case "rename": { | ||
| 193 | const old_path = parts[4]; | ||
| 194 | const new_path = parts[5]; | ||
| 195 | entries.push({ | ||
| 196 | timestamp, | ||
| 197 | revision, | ||
| 198 | email, | ||
| 199 | action: "rename", | ||
| 200 | old_path, | ||
| 201 | new_path | ||
| 202 | }); | ||
| 203 | break; | ||
| 204 | } | ||
| 205 | default: | ||
| 206 | console.warn(`Unknown log action: ${action}`, parts); | ||
| 207 | continue; | ||
| 208 | } | ||
| 159 | } | 209 | } |
| 160 | return entries; | 210 | return entries; |
| 161 | } \ No newline at end of file | 211 | } \ No newline at end of file |
diff --git a/frontend/lib/drive_types.ts b/frontend/lib/drive_types.ts index 9220d82..a168a72 100644 --- a/frontend/lib/drive_types.ts +++ b/frontend/lib/drive_types.ts | |||
| @@ -22,12 +22,57 @@ export interface DriveTreeResponse { | |||
| 22 | root: DriveTreeNode[] | 22 | root: DriveTreeNode[] |
| 23 | } | 23 | } |
| 24 | 24 | ||
| 25 | export interface DriveLogEntry { | 25 | // Base interface for all log entries with common fields |
| 26 | export interface DriveLogEntryBase { | ||
| 26 | timestamp: number | 27 | timestamp: number |
| 27 | log_id: number | 28 | revision: number // Changed from log_id to match Rust OperationHeader |
| 28 | email: string | 29 | email: string |
| 29 | action: string | 30 | } |
| 31 | |||
| 32 | // Specific log entry types based on Rust OperationData variants | ||
| 33 | export interface DriveLogEntryCreateFile extends DriveLogEntryBase { | ||
| 34 | action: "create_file" | ||
| 30 | path: string | 35 | path: string |
| 31 | blob_id: string | 36 | blob_id: string |
| 32 | size: number | 37 | size: number |
| 33 | } | 38 | } |
| 39 | |||
| 40 | export interface DriveLogEntryCreateDir extends DriveLogEntryBase { | ||
| 41 | action: "create_dir" | ||
| 42 | path: string | ||
| 43 | } | ||
| 44 | |||
| 45 | export interface DriveLogEntryRemove extends DriveLogEntryBase { | ||
| 46 | action: "remove" | ||
| 47 | path: string | ||
| 48 | } | ||
| 49 | |||
| 50 | export interface DriveLogEntryRename extends DriveLogEntryBase { | ||
| 51 | action: "rename" | ||
| 52 | old_path: string | ||
| 53 | new_path: string | ||
| 54 | } | ||
| 55 | |||
| 56 | // Discriminated union of all possible log entry types | ||
| 57 | export type DriveLogEntry = | ||
| 58 | | DriveLogEntryCreateFile | ||
| 59 | | DriveLogEntryCreateDir | ||
| 60 | | DriveLogEntryRemove | ||
| 61 | | DriveLogEntryRename | ||
| 62 | |||
| 63 | // Type guards for working with discriminated unions | ||
| 64 | export function isCreateFileEntry(entry: DriveLogEntry): entry is DriveLogEntryCreateFile { | ||
| 65 | return entry.action === "create_file" | ||
| 66 | } | ||
| 67 | |||
| 68 | export function isCreateDirEntry(entry: DriveLogEntry): entry is DriveLogEntryCreateDir { | ||
| 69 | return entry.action === "create_dir" | ||
| 70 | } | ||
| 71 | |||
| 72 | export function isRemoveEntry(entry: DriveLogEntry): entry is DriveLogEntryRemove { | ||
| 73 | return entry.action === "remove" | ||
| 74 | } | ||
| 75 | |||
| 76 | export function isRenameEntry(entry: DriveLogEntry): entry is DriveLogEntryRename { | ||
| 77 | return entry.action === "rename" | ||
| 78 | } | ||
