Commit 9ae40825 authored by BangNSK's avatar BangNSK

feat(frontend): connect to deploy api and implement users/logs pages

- Update API_BASE_URL to http://171.247.68.96:4011/api/v1
- Implement Users page: CRUD (list, create, update, delete) with pagination and search
- Implement Logs page: audit logs with userId/action filter, expandable details, pagination
- Support URL_LIST mode in Dashboard create job form (textarea for batch URLs)
- Fix unused mimeType param in JobDetail to pass TypeScript strict check
parent dd2ea5d4
...@@ -30,6 +30,7 @@ export const Dashboard: React.FC = () => { ...@@ -30,6 +30,7 @@ export const Dashboard: React.FC = () => {
const [mode, setMode] = useState<'SCRAPE' | 'CRAWL' | 'SITEMAP' | 'URL_LIST'>('SCRAPE'); const [mode, setMode] = useState<'SCRAPE' | 'CRAWL' | 'SITEMAP' | 'URL_LIST'>('SCRAPE');
const [maxPages, setMaxPages] = useState<number>(20); const [maxPages, setMaxPages] = useState<number>(20);
const [maxDepth, setMaxDepth] = useState<number>(1); const [maxDepth, setMaxDepth] = useState<number>(1);
const [urlsList, setUrlsList] = useState(''); // URL_LIST mode: danh sách URL mỗi dòng 1 URL
const [createError, setCreateError] = useState(''); const [createError, setCreateError] = useState('');
const [creating, setCreating] = useState(false); const [creating, setCreating] = useState(false);
...@@ -39,7 +40,7 @@ export const Dashboard: React.FC = () => { ...@@ -39,7 +40,7 @@ export const Dashboard: React.FC = () => {
try { try {
const response = await api.get('/crawl-jobs'); const response = await api.get('/crawl-jobs');
if (response.data.success) { if (response.data.success) {
setJobs(response.data.data.items || []); setJobs(response.data.data.jobs || response.data.data.items || []);
} }
} catch (error) { } catch (error) {
console.error('Error fetching jobs', error); console.error('Error fetching jobs', error);
...@@ -81,11 +82,16 @@ export const Dashboard: React.FC = () => { ...@@ -81,11 +82,16 @@ export const Dashboard: React.FC = () => {
setCreating(true); setCreating(true);
try { try {
const isUrlListMode = mode === 'URL_LIST';
const parsedUrls = isUrlListMode
? urlsList.split('\n').map((u) => u.trim()).filter(Boolean)
: undefined;
const response = await api.post('/crawl-jobs', { const response = await api.post('/crawl-jobs', {
startUrl, ...(isUrlListMode ? { urls: parsedUrls } : { startUrl }),
mode, mode,
maxPages: Number(maxPages), maxPages: Number(maxPages),
maxDepth: Number(maxDepth), maxDepth: isUrlListMode ? undefined : Number(maxDepth),
}); });
if (response.data.success) { if (response.data.success) {
...@@ -95,6 +101,7 @@ export const Dashboard: React.FC = () => { ...@@ -95,6 +101,7 @@ export const Dashboard: React.FC = () => {
setMode('SCRAPE'); setMode('SCRAPE');
setMaxPages(20); setMaxPages(20);
setMaxDepth(1); setMaxDepth(1);
setUrlsList('');
fetchJobs(true); fetchJobs(true);
} }
} catch (error: any) { } catch (error: any) {
...@@ -270,19 +277,38 @@ export const Dashboard: React.FC = () => { ...@@ -270,19 +277,38 @@ export const Dashboard: React.FC = () => {
)} )}
<form onSubmit={handleCreateJob} className="space-y-4"> <form onSubmit={handleCreateJob} className="space-y-4">
<div> {mode === 'URL_LIST' ? (
<label className="block text-sm font-medium text-slate-700 mb-1"> <div>
Đường dẫn bắt đầu (Start URL) * <label className="block text-sm font-medium text-slate-700 mb-1">
</label> Danh sách URL (mỗi dòng 1 URL) *
<input </label>
type="url" <textarea
required required
placeholder="https://example.com" rows={5}
value={startUrl} placeholder={`https://example.com/page1\nhttps://example.com/page2\nhttps://example.com/page3`}
onChange={(e) => setStartUrl(e.target.value)} value={urlsList}
className="block w-full rounded-lg border border-slate-300 bg-white py-2 px-3 text-slate-900 placeholder-slate-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm" onChange={(e) => setUrlsList(e.target.value)}
/> className="block w-full rounded-lg border border-slate-300 bg-white py-2 px-3 text-slate-900 placeholder-slate-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm font-mono text-xs"
</div> />
<p className="text-xs text-slate-400 mt-1">
Tối đa 1000 URL. Mỗi dòng một URL hợp lệ.
</p>
</div>
) : (
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">
Đường dẫn bắt đầu (Start URL) *
</label>
<input
type="url"
required
placeholder="https://example.com"
value={startUrl}
onChange={(e) => setStartUrl(e.target.value)}
className="block w-full rounded-lg border border-slate-300 bg-white py-2 px-3 text-slate-900 placeholder-slate-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm"
/>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>
......
...@@ -158,7 +158,7 @@ export const JobDetail: React.FC = () => { ...@@ -158,7 +158,7 @@ export const JobDetail: React.FC = () => {
} }
}; };
const handleDownload = async (exportId: string, fileName: string, mimeType: string) => { const handleDownload = async (exportId: string, fileName: string, _mimeType: string) => {
try { try {
console.log(`Downloading export ${exportId}, requesting blob...`); console.log(`Downloading export ${exportId}, requesting blob...`);
const res = await api.get(`/exports/${exportId}/download`, { responseType: 'blob' }); const res = await api.get(`/exports/${exportId}/download`, { responseType: 'blob' });
......
This diff is collapsed.
This diff is collapsed.
import axios from 'axios'; import axios from 'axios';
const API_BASE_URL = 'http://localhost:3000/api/v1'; const API_BASE_URL = 'http://171.247.68.96:4011/api/v1';
export const api = axios.create({ export const api = axios.create({
baseURL: API_BASE_URL, baseURL: API_BASE_URL,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment