mirror of
https://github.com/jaypyles/Scraperr.git
synced 2025-12-12 02:35:43 +00:00
feat: add delete and select all button
This commit is contained in:
4
.github/workflows/docker-image.yml
vendored
4
.github/workflows/docker-image.yml
vendored
@@ -2,8 +2,8 @@ name: ci
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
pull_request:
|
||||
branches: ["master"]
|
||||
# pull_request:
|
||||
# branches: ["master"]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -12,8 +12,13 @@ from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
# LOCAL
|
||||
from api.backend.job import query, insert
|
||||
from api.backend.models import DownloadJob, SubmitScrapeJob, RetrieveScrapeJobs
|
||||
from api.backend.job import query, insert, delete_jobs
|
||||
from api.backend.models import (
|
||||
DownloadJob,
|
||||
SubmitScrapeJob,
|
||||
DeleteScrapeJobs,
|
||||
RetrieveScrapeJobs,
|
||||
)
|
||||
from api.backend.scraping import scrape
|
||||
from api.backend.auth.auth_router import auth_router
|
||||
|
||||
@@ -116,3 +121,13 @@ async def download(download_job: DownloadJob):
|
||||
except Exception as e:
|
||||
LOG.error(f"Exception occurred: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
@app.post("/api/delete-scrape-jobs")
|
||||
async def delete(delete_scrape_jobs: DeleteScrapeJobs):
|
||||
result = await delete_jobs(delete_scrape_jobs.ids)
|
||||
return (
|
||||
JSONResponse(content={"message": "Jobs successfully deleted."})
|
||||
if result
|
||||
else JSONResponse({"error": "Jobs not deleted."})
|
||||
)
|
||||
|
||||
@@ -24,3 +24,11 @@ async def query(filter: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
results.append(document)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
async def delete_jobs(jobs: list[str]):
|
||||
collection = get_job_collection()
|
||||
result = await collection.delete_many({"id": {"$in": jobs}})
|
||||
LOG.info(f"RESULT: {result.deleted_count} documents deleted")
|
||||
|
||||
return True if result.deleted_count > 0 else False
|
||||
|
||||
@@ -32,3 +32,7 @@ class RetrieveScrapeJobs(pydantic.BaseModel):
|
||||
|
||||
class DownloadJob(pydantic.BaseModel):
|
||||
id: str
|
||||
|
||||
|
||||
class DeleteScrapeJobs(pydantic.BaseModel):
|
||||
ids: list[str]
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Button,
|
||||
IconButton,
|
||||
Box,
|
||||
Typography,
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
Checkbox,
|
||||
Tooltip,
|
||||
Button,
|
||||
} from "@mui/material";
|
||||
import { useRouter } from "next/router";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import SelectAllIcon from "@mui/icons-material/SelectAll";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
interface Job {
|
||||
id: string;
|
||||
@@ -25,13 +30,15 @@ interface Job {
|
||||
|
||||
interface JobTableProps {
|
||||
jobs: Job[];
|
||||
fetchJobs: () => void;
|
||||
}
|
||||
|
||||
const JobTable: React.FC<JobTableProps> = ({ jobs }) => {
|
||||
const JobTable: React.FC<JobTableProps> = ({ jobs, fetchJobs }) => {
|
||||
const [selectedJobs, setSelectedJobs] = useState<Set<string>>(new Set());
|
||||
const [allSelected, setAllSelected] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
const handleDownload = async (id: string) => {
|
||||
console.log(id);
|
||||
const response = await fetch("/api/download", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
@@ -63,6 +70,41 @@ const JobTable: React.FC<JobTableProps> = ({ jobs }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectJob = (id: string) => {
|
||||
setSelectedJobs((prevSelected) => {
|
||||
const newSelected = new Set(prevSelected);
|
||||
if (newSelected.has(id)) {
|
||||
newSelected.delete(id);
|
||||
} else {
|
||||
newSelected.add(id);
|
||||
}
|
||||
return newSelected;
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectAll = () => {
|
||||
if (allSelected) {
|
||||
setSelectedJobs(new Set());
|
||||
} else {
|
||||
const allJobIds = new Set(jobs.map((job) => job.id));
|
||||
setSelectedJobs(allJobIds);
|
||||
}
|
||||
setAllSelected(!allSelected);
|
||||
};
|
||||
|
||||
const handleDeleteSelected = async () => {
|
||||
const response = await fetch("/api/delete-scrape-jobs", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ ids: Array.from(selectedJobs) }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
fetchJobs();
|
||||
setSelectedJobs(new Set());
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
width="100%"
|
||||
@@ -79,13 +121,37 @@ const JobTable: React.FC<JobTableProps> = ({ jobs }) => {
|
||||
bgcolor="background.default"
|
||||
overflow="auto"
|
||||
>
|
||||
<Typography variant="h4" gutterBottom sx={{ mt: 3 }}>
|
||||
Scrape Jobs
|
||||
</Typography>
|
||||
<Box
|
||||
className="flex flex-row w-3/4 items-center p-2"
|
||||
bgcolor="background.paper"
|
||||
>
|
||||
<Typography className="mr-2" variant="body1">
|
||||
Scrape Jobs
|
||||
</Typography>
|
||||
<Tooltip title="Select All">
|
||||
<span>
|
||||
<IconButton color="primary" onClick={handleSelectAll}>
|
||||
<SelectAllIcon />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip title="Delete Selected">
|
||||
<span>
|
||||
<IconButton
|
||||
color="secondary"
|
||||
onClick={handleDeleteSelected}
|
||||
disabled={selectedJobs.size === 0}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box sx={{ overflow: "auto", width: "75%" }}>
|
||||
<Table sx={{ tableLayout: "fixed", width: "100%" }}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Select</TableCell>
|
||||
<TableCell>Id</TableCell>
|
||||
<TableCell>Url</TableCell>
|
||||
<TableCell>Elements</TableCell>
|
||||
@@ -97,6 +163,12 @@ const JobTable: React.FC<JobTableProps> = ({ jobs }) => {
|
||||
<TableBody>
|
||||
{jobs.map((row, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
checked={selectedJobs.has(row.id)}
|
||||
onChange={() => handleSelectJob(row.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell sx={{ maxWidth: 100, overflow: "auto" }}>
|
||||
<Box sx={{ maxHeight: 100, overflow: "auto" }}>
|
||||
{row.id}
|
||||
|
||||
@@ -7,18 +7,22 @@ const Jobs = () => {
|
||||
const { user } = useAuth();
|
||||
const [jobs, setJobs] = useState([]);
|
||||
|
||||
const fetchJobs = () => {
|
||||
fetch("/api/retrieve-scrape-jobs", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ user: user?.email }),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => setJobs(data))
|
||||
.catch((error) => {
|
||||
console.error("Error fetching jobs:", error);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
fetch("/api/retrieve-scrape-jobs", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ user: user?.email }),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => setJobs(data))
|
||||
.catch((error) => {
|
||||
console.error("Error fetching jobs:", error);
|
||||
});
|
||||
fetchJobs();
|
||||
} else {
|
||||
setJobs([]);
|
||||
}
|
||||
@@ -27,7 +31,7 @@ const Jobs = () => {
|
||||
return (
|
||||
<>
|
||||
{user ? (
|
||||
<JobTable jobs={jobs} />
|
||||
<JobTable jobs={jobs} fetchJobs={fetchJobs} />
|
||||
) : (
|
||||
<Box
|
||||
bgcolor="background.default"
|
||||
|
||||
@@ -108,6 +108,10 @@ const darkTheme = createTheme({
|
||||
h5: {
|
||||
color: "#ffffff",
|
||||
},
|
||||
body1: {
|
||||
...commonThemeOptions.typography.body1,
|
||||
color: "#ffffff",
|
||||
},
|
||||
},
|
||||
components: {
|
||||
...commonThemeOptions.components,
|
||||
|
||||
Reference in New Issue
Block a user