Merge pull request #18 from jaypyles/16-create-a-favorites-table
feat: add favorites tab in jobs
@@ -17,6 +17,7 @@ From the table, users can download an excel sheet of the job's results, along wi
|
|||||||
- Download csv containing results
|
- Download csv containing results
|
||||||
- Rerun jobs
|
- Rerun jobs
|
||||||
- View status of queued jobs
|
- View status of queued jobs
|
||||||
|
- Favorite and view favorited jobs
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ from api.backend.job import (
|
|||||||
query,
|
query,
|
||||||
insert,
|
insert,
|
||||||
delete_jobs,
|
delete_jobs,
|
||||||
|
update_job,
|
||||||
)
|
)
|
||||||
from api.backend.models import (
|
from api.backend.models import (
|
||||||
DownloadJob,
|
DownloadJob,
|
||||||
@@ -31,6 +32,7 @@ from api.backend.models import (
|
|||||||
SubmitScrapeJob,
|
SubmitScrapeJob,
|
||||||
DeleteScrapeJobs,
|
DeleteScrapeJobs,
|
||||||
RetrieveScrapeJobs,
|
RetrieveScrapeJobs,
|
||||||
|
UpdateJobs,
|
||||||
)
|
)
|
||||||
from api.backend.auth.auth_router import auth_router
|
from api.backend.auth.auth_router import auth_router
|
||||||
import traceback
|
import traceback
|
||||||
@@ -67,6 +69,12 @@ def read_favicon():
|
|||||||
return FileResponse("dist/favicon.ico")
|
return FileResponse("dist/favicon.ico")
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/update")
|
||||||
|
async def update(update_jobs: UpdateJobs):
|
||||||
|
"""Used to update jobs"""
|
||||||
|
await update_job(update_jobs.ids, update_jobs.field, update_jobs.value)
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/submit-scrape-job")
|
@app.post("/api/submit-scrape-job")
|
||||||
async def submit_scrape_job(job: SubmitScrapeJob, background_tasks: BackgroundTasks):
|
async def submit_scrape_job(job: SubmitScrapeJob, background_tasks: BackgroundTasks):
|
||||||
LOG.info(f"Recieved job: {job}")
|
LOG.info(f"Recieved job: {job}")
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ async def query(filter: dict[str, Any]) -> list[dict[str, Any]]:
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
async def update_job(id: str, field: str, value: Any):
|
async def update_job(ids: list[str], field: str, value: Any):
|
||||||
collection = get_job_collection()
|
collection = get_job_collection()
|
||||||
result = await collection.update_one(
|
for id in ids:
|
||||||
{"id": id},
|
_ = await collection.update_one(
|
||||||
{"$set": {field: value}},
|
{"id": id},
|
||||||
)
|
{"$set": {field: value}},
|
||||||
return result.modified_count
|
)
|
||||||
|
|
||||||
|
|
||||||
async def delete_jobs(jobs: list[str]):
|
async def delete_jobs(jobs: list[str]):
|
||||||
|
|||||||
@@ -48,3 +48,9 @@ class DeleteScrapeJobs(pydantic.BaseModel):
|
|||||||
|
|
||||||
class GetStatistics(pydantic.BaseModel):
|
class GetStatistics(pydantic.BaseModel):
|
||||||
user: str
|
user: str
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateJobs(pydantic.BaseModel):
|
||||||
|
ids: list[str]
|
||||||
|
field: str
|
||||||
|
value: Any
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 140 KiB |
BIN
docs/login.png
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
@@ -1,19 +1,9 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import {
|
import {
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
IconButton,
|
IconButton,
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
Accordion,
|
|
||||||
AccordionSummary,
|
|
||||||
AccordionDetails,
|
|
||||||
Checkbox,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Button,
|
|
||||||
TextField,
|
TextField,
|
||||||
FormControl,
|
FormControl,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
@@ -23,19 +13,11 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import DeleteIcon from "@mui/icons-material/Delete";
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
import SelectAllIcon from "@mui/icons-material/SelectAll";
|
import SelectAllIcon from "@mui/icons-material/SelectAll";
|
||||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
|
||||||
import DownloadIcon from "@mui/icons-material/Download";
|
import DownloadIcon from "@mui/icons-material/Download";
|
||||||
|
import StarIcon from "@mui/icons-material/Star";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { Favorites, JobQueue } from "./jobs";
|
||||||
interface Job {
|
import { Job } from "../types";
|
||||||
id: string;
|
|
||||||
url: string;
|
|
||||||
elements: Object[];
|
|
||||||
result: Object;
|
|
||||||
time_created: Date;
|
|
||||||
status: string;
|
|
||||||
job_options: Object;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface JobTableProps {
|
interface JobTableProps {
|
||||||
jobs: Job[];
|
jobs: Job[];
|
||||||
@@ -57,6 +39,7 @@ const JobTable: React.FC<JobTableProps> = ({ jobs, fetchJobs }) => {
|
|||||||
const [allSelected, setAllSelected] = useState(false);
|
const [allSelected, setAllSelected] = useState(false);
|
||||||
const [searchQuery, setSearchQuery] = useState<string>("");
|
const [searchQuery, setSearchQuery] = useState<string>("");
|
||||||
const [searchMode, setSearchMode] = useState<string>("url");
|
const [searchMode, setSearchMode] = useState<string>("url");
|
||||||
|
const [favoriteView, setFavoriteView] = useState<boolean>(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -139,6 +122,22 @@ const JobTable: React.FC<JobTableProps> = ({ jobs, fetchJobs }) => {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const favoriteJob = async (ids: string[], field: string, value: any) => {
|
||||||
|
const postBody = {
|
||||||
|
ids: ids,
|
||||||
|
field: field,
|
||||||
|
value: value,
|
||||||
|
};
|
||||||
|
|
||||||
|
await fetch("/api/update", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(postBody),
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchJobs();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
width="100%"
|
width="100%"
|
||||||
@@ -191,6 +190,16 @@ const JobTable: React.FC<JobTableProps> = ({ jobs, fetchJobs }) => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<Tooltip title="Favorites">
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
color={favoriteView ? "warning" : "default"}
|
||||||
|
onClick={() => setFavoriteView(!favoriteView)}
|
||||||
|
>
|
||||||
|
<StarIcon />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row space-x-2 w-1/2">
|
<div className="flex flex-row space-x-2 w-1/2">
|
||||||
<TextField
|
<TextField
|
||||||
@@ -219,117 +228,23 @@ const JobTable: React.FC<JobTableProps> = ({ jobs, fetchJobs }) => {
|
|||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ overflow: "auto" }}>
|
<Box sx={{ overflow: "auto" }}>
|
||||||
<Table sx={{ tableLayout: "fixed", width: "100%" }}>
|
{!favoriteView ? (
|
||||||
<TableHead>
|
<JobQueue
|
||||||
<TableRow>
|
stateProps={{ selectedJobs, filteredJobs }}
|
||||||
<TableCell>Select</TableCell>
|
colors={COLOR_MAP}
|
||||||
<TableCell>Id</TableCell>
|
onDownload={handleDownload}
|
||||||
<TableCell>Url</TableCell>
|
onNavigate={handleNavigate}
|
||||||
<TableCell>Elements</TableCell>
|
onSelectJob={handleSelectJob}
|
||||||
<TableCell>Result</TableCell>
|
onFavorite={favoriteJob}
|
||||||
<TableCell>Time Created</TableCell>
|
></JobQueue>
|
||||||
<TableCell>Status</TableCell>
|
) : (
|
||||||
<TableCell>Actions</TableCell>
|
<Favorites
|
||||||
</TableRow>
|
stateProps={{ selectedJobs, filteredJobs }}
|
||||||
</TableHead>
|
onNavigate={handleNavigate}
|
||||||
<TableBody>
|
onSelectJob={handleSelectJob}
|
||||||
{filteredJobs.map((row, index) => (
|
onFavorite={favoriteJob}
|
||||||
<TableRow key={index}>
|
></Favorites>
|
||||||
<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}
|
|
||||||
</Box>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell sx={{ maxWidth: 200, overflow: "auto" }}>
|
|
||||||
<Box sx={{ maxHeight: 100, overflow: "auto" }}>
|
|
||||||
{row.url}
|
|
||||||
</Box>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell sx={{ maxWidth: 150, overflow: "auto" }}>
|
|
||||||
<Box sx={{ maxHeight: 100, overflow: "auto" }}>
|
|
||||||
{JSON.stringify(row.elements)}
|
|
||||||
</Box>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell
|
|
||||||
sx={{ maxWidth: 150, overflow: "auto", padding: 0 }}
|
|
||||||
>
|
|
||||||
<Accordion sx={{ margin: 0, padding: 0.5 }}>
|
|
||||||
<AccordionSummary
|
|
||||||
expandIcon={<ExpandMoreIcon />}
|
|
||||||
aria-controls="panel1a-content"
|
|
||||||
id="panel1a-header"
|
|
||||||
sx={{
|
|
||||||
minHeight: 0,
|
|
||||||
"&.Mui-expanded": { minHeight: 0 },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
maxHeight: 150,
|
|
||||||
overflow: "auto",
|
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography sx={{ fontSize: "0.875rem" }}>
|
|
||||||
Show Result
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</AccordionSummary>
|
|
||||||
<AccordionDetails sx={{ padding: 1 }}>
|
|
||||||
<Box sx={{ maxHeight: 200, overflow: "auto" }}>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: "0.875rem",
|
|
||||||
whiteSpace: "pre-wrap",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{JSON.stringify(row.result, null, 2)}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</AccordionDetails>
|
|
||||||
</Accordion>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell sx={{ maxWidth: 150, overflow: "auto" }}>
|
|
||||||
<Box sx={{ maxHeight: 100, overflow: "auto" }}>
|
|
||||||
{new Date(row.time_created).toLocaleString()}
|
|
||||||
</Box>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell sx={{ maxWidth: 150, overflow: "auto" }}>
|
|
||||||
<Box sx={{ maxHeight: 100, overflow: "auto" }}>
|
|
||||||
<Box
|
|
||||||
className="rounded-md p-2 text-center"
|
|
||||||
sx={{ bgcolor: COLOR_MAP[row.status], opactity: "50%" }}
|
|
||||||
>
|
|
||||||
{row.status}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell sx={{ maxWidth: 100, overflow: "auto" }}>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
handleDownload([row.id]);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Download
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() =>
|
|
||||||
handleNavigate(row.elements, row.url, row.job_options)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Rerun
|
|
||||||
</Button>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
102
src/components/jobs/Favorites.tsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
IconButton,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Box,
|
||||||
|
Checkbox,
|
||||||
|
Button,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { Job } from "../../types";
|
||||||
|
import StarIcon from "@mui/icons-material/Star";
|
||||||
|
|
||||||
|
interface stateProps {
|
||||||
|
selectedJobs: Set<string>;
|
||||||
|
filteredJobs: Job[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onSelectJob: (job: string) => void;
|
||||||
|
onNavigate: (elements: Object[], url: string, options: any) => void;
|
||||||
|
onFavorite: (ids: string[], field: string, value: any) => void;
|
||||||
|
stateProps: stateProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Favorites = ({
|
||||||
|
stateProps,
|
||||||
|
onSelectJob,
|
||||||
|
onNavigate,
|
||||||
|
onFavorite,
|
||||||
|
}: Props) => {
|
||||||
|
const { selectedJobs, filteredJobs } = stateProps;
|
||||||
|
const favoritedJobs = filteredJobs.filter((job) => job.favorite);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table sx={{ tableLayout: "fixed", width: "100%" }}>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Select</TableCell>
|
||||||
|
<TableCell>Id</TableCell>
|
||||||
|
<TableCell>Url</TableCell>
|
||||||
|
<TableCell>Elements</TableCell>
|
||||||
|
<TableCell>Time Created</TableCell>
|
||||||
|
<TableCell>Actions</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{favoritedJobs.map((row, index) => (
|
||||||
|
<TableRow key={index}>
|
||||||
|
<TableCell padding="checkbox">
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedJobs.has(row.id)}
|
||||||
|
onChange={() => onSelectJob(row.id)}
|
||||||
|
/>
|
||||||
|
<Tooltip title="Favorite Job">
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
color={row.favorite ? "warning" : "default"}
|
||||||
|
onClick={() => {
|
||||||
|
onFavorite([row.id], "favorite", !row.favorite);
|
||||||
|
row.favorite = !row.favorite;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StarIcon />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ maxWidth: 100, overflow: "auto" }}>
|
||||||
|
<Box sx={{ maxHeight: 100, overflow: "auto" }}>{row.id}</Box>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ maxWidth: 200, overflow: "auto" }}>
|
||||||
|
<Box sx={{ maxHeight: 100, overflow: "auto" }}>{row.url}</Box>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ maxWidth: 150, overflow: "auto" }}>
|
||||||
|
<Box sx={{ maxHeight: 100, overflow: "auto" }}>
|
||||||
|
{JSON.stringify(row.elements)}
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ maxWidth: 150, overflow: "auto" }}>
|
||||||
|
<Box sx={{ maxHeight: 100, overflow: "auto" }}>
|
||||||
|
{new Date(row.time_created).toLocaleString()}
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ maxWidth: 100, overflow: "auto" }}>
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
onNavigate(row.elements, row.url, row.job_options)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Run
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
||||||
170
src/components/jobs/JobQueue.tsx
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Accordion,
|
||||||
|
AccordionSummary,
|
||||||
|
AccordionDetails,
|
||||||
|
Checkbox,
|
||||||
|
Button,
|
||||||
|
Tooltip,
|
||||||
|
IconButton,
|
||||||
|
} from "@mui/material";
|
||||||
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||||
|
import StarIcon from "@mui/icons-material/Star";
|
||||||
|
import { Job } from "../../types";
|
||||||
|
|
||||||
|
interface stringMap {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface stateProps {
|
||||||
|
selectedJobs: Set<string>;
|
||||||
|
filteredJobs: Job[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
colors: stringMap;
|
||||||
|
onSelectJob: (job: string) => void;
|
||||||
|
onDownload: (job: string[]) => void;
|
||||||
|
onNavigate: (elements: Object[], url: string, options: any) => void;
|
||||||
|
onFavorite: (ids: string[], field: string, value: any) => void;
|
||||||
|
stateProps: stateProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const JobQueue = ({
|
||||||
|
stateProps,
|
||||||
|
colors,
|
||||||
|
onSelectJob,
|
||||||
|
onDownload,
|
||||||
|
onNavigate,
|
||||||
|
onFavorite,
|
||||||
|
}: Props) => {
|
||||||
|
const { selectedJobs, filteredJobs } = stateProps;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table sx={{ tableLayout: "fixed", width: "100%" }}>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Select</TableCell>
|
||||||
|
<TableCell>Id</TableCell>
|
||||||
|
<TableCell>Url</TableCell>
|
||||||
|
<TableCell>Elements</TableCell>
|
||||||
|
<TableCell>Result</TableCell>
|
||||||
|
<TableCell>Time Created</TableCell>
|
||||||
|
<TableCell>Status</TableCell>
|
||||||
|
<TableCell>Actions</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{filteredJobs.map((row, index) => (
|
||||||
|
<TableRow key={index}>
|
||||||
|
<TableCell padding="checkbox">
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedJobs.has(row.id)}
|
||||||
|
onChange={() => onSelectJob(row.id)}
|
||||||
|
/>
|
||||||
|
<Tooltip title="Favorite Job">
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
color={row.favorite ? "warning" : "default"}
|
||||||
|
onClick={() => {
|
||||||
|
onFavorite([row.id], "favorite", !row.favorite);
|
||||||
|
row.favorite = !row.favorite;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StarIcon />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ maxWidth: 100, overflow: "auto" }}>
|
||||||
|
<Box sx={{ maxHeight: 100, overflow: "auto" }}>{row.id}</Box>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ maxWidth: 200, overflow: "auto" }}>
|
||||||
|
<Box sx={{ maxHeight: 100, overflow: "auto" }}>{row.url}</Box>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ maxWidth: 150, overflow: "auto" }}>
|
||||||
|
<Box sx={{ maxHeight: 100, overflow: "auto" }}>
|
||||||
|
{JSON.stringify(row.elements)}
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ maxWidth: 150, overflow: "auto", padding: 0 }}>
|
||||||
|
<Accordion sx={{ margin: 0, padding: 0.5 }}>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={<ExpandMoreIcon />}
|
||||||
|
aria-controls="panel1a-content"
|
||||||
|
id="panel1a-header"
|
||||||
|
sx={{
|
||||||
|
minHeight: 0,
|
||||||
|
"&.Mui-expanded": { minHeight: 0 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
maxHeight: 150,
|
||||||
|
overflow: "auto",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography sx={{ fontSize: "0.875rem" }}>
|
||||||
|
Show Result
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails sx={{ padding: 1 }}>
|
||||||
|
<Box sx={{ maxHeight: 200, overflow: "auto" }}>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "0.875rem",
|
||||||
|
whiteSpace: "pre-wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{JSON.stringify(row.result, null, 2)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ maxWidth: 150, overflow: "auto" }}>
|
||||||
|
<Box sx={{ maxHeight: 100, overflow: "auto" }}>
|
||||||
|
{new Date(row.time_created).toLocaleString()}
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ maxWidth: 150, overflow: "auto" }}>
|
||||||
|
<Box sx={{ maxHeight: 100, overflow: "auto" }}>
|
||||||
|
<Box
|
||||||
|
className="rounded-md p-2 text-center"
|
||||||
|
sx={{ bgcolor: colors[row.status], opactity: "50%" }}
|
||||||
|
>
|
||||||
|
{row.status}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ maxWidth: 100, overflow: "auto" }}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
onDownload([row.id]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Download
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
onNavigate(row.elements, row.url, row.job_options)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Rerun
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
||||||
2
src/components/jobs/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./JobQueue";
|
||||||
|
export * from "./Favorites";
|
||||||
@@ -7,8 +7,8 @@ const Jobs = () => {
|
|||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const [jobs, setJobs] = useState([]);
|
const [jobs, setJobs] = useState([]);
|
||||||
|
|
||||||
const fetchJobs = () => {
|
const fetchJobs = async () => {
|
||||||
fetch("/api/retrieve-scrape-jobs", {
|
await fetch("/api/retrieve-scrape-jobs", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "content-type": "application/json" },
|
headers: { "content-type": "application/json" },
|
||||||
body: JSON.stringify({ user: user?.email }),
|
body: JSON.stringify({ user: user?.email }),
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from "./element";
|
export * from "./element";
|
||||||
export * from "./result";
|
export * from "./result";
|
||||||
|
export * from "./job";
|
||||||
|
|||||||
10
src/types/job.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export interface Job {
|
||||||
|
id: string;
|
||||||
|
url: string;
|
||||||
|
elements: Object[];
|
||||||
|
result: Object;
|
||||||
|
time_created: Date;
|
||||||
|
status: string;
|
||||||
|
job_options: Object;
|
||||||
|
favorite: boolean;
|
||||||
|
}
|
||||||