-
Notifications
You must be signed in to change notification settings - Fork 183
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support viewing and deleting all indexed web pages and local files
- Loading branch information
Showing
16 changed files
with
303 additions
and
102 deletions.
There are no files selected for viewing
35 changes: 4 additions & 31 deletions
35
...p/[locale]/(dashboard)/dashboard/page.tsx → ...app/[locale]/(dashboard)/indexes/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
'use client'; | ||
|
||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; | ||
import { formatDateTime } from '@/lib/utils'; | ||
import { ScoredURL } from '@/lib/types'; | ||
import { Loader2, Send, Trash2 } from 'lucide-react'; | ||
import { useState } from 'react'; | ||
import { toast } from 'sonner'; | ||
import { getIndexedUrls } from '@/lib/store/indexes'; | ||
import { Pagination, PaginationContent, PaginationItem, PaginationNext, PaginationPrevious } from '@/components/ui/pagination'; | ||
|
||
interface IndexesTableProps { | ||
userId: string; | ||
initialUrls: ScoredURL[]; | ||
totalCount: number; | ||
} | ||
|
||
export function IndexesTable({ userId, initialUrls, totalCount }: IndexesTableProps) { | ||
const [urls, setUrls] = useState<ScoredURL[]>(initialUrls); | ||
const [currentPage, setCurrentPage] = useState(1); | ||
const [isLoading, setIsLoading] = useState(false); | ||
const PAGE_SIZE = 20; | ||
const totalPages = Math.ceil(totalCount / PAGE_SIZE); | ||
|
||
const fetchPageData = async (page: number) => { | ||
setIsLoading(true); | ||
try { | ||
const offset = (page - 1) * PAGE_SIZE; | ||
const result = await getIndexedUrls(userId, offset, PAGE_SIZE); | ||
setUrls(result); | ||
} catch (error) { | ||
console.error('Error fetching page data:', error); | ||
toast.error('Failed to load data'); | ||
} finally { | ||
setIsLoading(false); | ||
} | ||
}; | ||
|
||
const handlePageChange = async (newPage: number) => { | ||
if (newPage < 1 || newPage > totalPages) return; | ||
setCurrentPage(newPage); | ||
await fetchPageData(newPage); | ||
}; | ||
|
||
const handleVisit = (url: ScoredURL) => { | ||
window.open(url.value, '_blank'); | ||
}; | ||
|
||
const [deletingId, setDeletingId] = useState<string | null>(null); | ||
const handleDelete = async (url: ScoredURL) => { | ||
try { | ||
if (deletingId) return; | ||
setDeletingId(url.value); | ||
const response = await fetch('/api/delete-url', { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify({ url: url.value, isSuccess: true }), | ||
}); | ||
|
||
if (!response.ok) { | ||
throw new Error('Delete failed'); | ||
} | ||
|
||
const data = await response.json(); | ||
setUrls((prevUrls) => prevUrls.filter((u) => u.value !== url.value)); | ||
toast.success('Successfully Deleted'); | ||
} catch (error) { | ||
console.error('Error:', error); | ||
toast.error('Failed to delete'); | ||
} finally { | ||
setDeletingId(null); | ||
} | ||
}; | ||
|
||
return ( | ||
<div className="space-y-4"> | ||
<Table> | ||
<TableHeader> | ||
<TableRow> | ||
<TableHead>Url</TableHead> | ||
<TableHead>Indexed Date</TableHead> | ||
<TableHead>Visit</TableHead> | ||
<TableHead>Delete</TableHead> | ||
</TableRow> | ||
</TableHeader> | ||
<TableBody> | ||
{urls.map((url, index) => ( | ||
<TableRow key={index}> | ||
<TableCell> | ||
<div className="font-medium">{url.value}</div> | ||
</TableCell> | ||
<TableCell>{formatDateTime(url.score)}</TableCell> | ||
<TableCell> | ||
<button onClick={() => handleVisit(url)} title="visit" aria-label="Visit link"> | ||
<Send size={24} /> | ||
</button> | ||
</TableCell> | ||
<TableCell> | ||
<button onClick={() => handleDelete(url)} title="Delete" aria-label="Delete"> | ||
{deletingId === url.value ? ( | ||
<div className="animate-spin"> | ||
<Loader2 size={24} /> | ||
</div> | ||
) : ( | ||
<Trash2 size={24} /> | ||
)} | ||
</button> | ||
</TableCell> | ||
</TableRow> | ||
))} | ||
</TableBody> | ||
</Table> | ||
|
||
{totalPages > 1 && ( | ||
<Pagination> | ||
<PaginationContent> | ||
<PaginationItem className="cursor-pointer hover:bg-accent hover:text-accent-foreground"> | ||
<PaginationPrevious onClick={() => handlePageChange(currentPage - 1)} /> | ||
</PaginationItem> | ||
<PaginationItem className="flex items-center"> | ||
<span>{`Page ${currentPage} of ${totalPages}`}</span> | ||
</PaginationItem> | ||
<PaginationItem className="cursor-pointer hover:bg-accent hover:text-accent-foreground"> | ||
<PaginationNext onClick={() => handlePageChange(currentPage + 1)} /> | ||
</PaginationItem> | ||
</PaginationContent> | ||
</Pagination> | ||
)} | ||
</div> | ||
); | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import 'server-only'; | ||
|
||
import { API_TOKEN, VECTOR_HOST } from '@/lib/env'; | ||
import { log } from '@/lib/log'; | ||
|
||
export async function selectDetail(userId: string, offset: number = 0, limit: number = 20) { | ||
try { | ||
const response = await fetch(`${VECTOR_HOST}/api/detail/search`, { | ||
method: 'POST', | ||
headers: { | ||
'Authorization': `${API_TOKEN}`, | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify({ userId: userId, offset: offset, selectFields: ['url', 'create_time'] }), | ||
}); | ||
if (!response.ok) { | ||
console.error(`rselectDetail! Status: ${response.status}, StatusText: ${response.statusText}`); | ||
throw new Error(`selectDetail! Status: ${response.status}, StatusText: ${response.statusText}`); | ||
} | ||
|
||
return response.json(); | ||
} catch (error) { | ||
console.error(`remove url Error! ${error} for user ${userId}`); | ||
log({ | ||
service: 'index-url', | ||
action: `error-remove-url`, | ||
error: `${error}`, | ||
userId: userId, | ||
}); | ||
throw error; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
'use server'; | ||
|
||
import { redisDB, URLS_KEY } from '@/lib/db'; | ||
import { ScoredURL } from '@/lib/types'; | ||
|
||
export async function getIndexedUrls(userId: string, offset: number, limit: number): Promise<ScoredURL[]> { | ||
const urls = await redisDB.zrange(URLS_KEY + userId, offset, offset + limit - 1, { | ||
rev: true, | ||
withScores: true, | ||
}); | ||
|
||
const scoredURLs: ScoredURL[] = []; | ||
for (let i = 0; i < urls.length; i += 2) { | ||
scoredURLs.push({ | ||
value: urls[i] as string, | ||
score: urls[i + 1] as number, | ||
}); | ||
} | ||
|
||
return scoredURLs; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.