Cancel queued search

Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
This commit is contained in:
Hadi
2025-10-04 19:19:19 +02:00
parent e7a9f9a0fe
commit ad789c73bd
4 changed files with 78 additions and 7 deletions

View File

@@ -118,6 +118,44 @@ func routes(s *server.Server, cache *map[string]*search.Result, searchQueue chan
c.JSON(http.StatusOK, r) c.JSON(http.StatusOK, r)
}) })
s.Router.POST("/search/cancel/:id", func(c *gin.Context) {
id := c.Param("id")
s.Mu.Lock()
r, exists := (*cache)[id]
s.Mu.Unlock()
if !exists {
c.JSON(http.StatusNotFound, gin.H{"Error": "Search not found"})
return
}
if r.Status == "queued" {
newQueue := make(chan string, cap(searchQueue))
for {
select {
case queuedId := <-searchQueue:
if queuedId != id {
newQueue <- queuedId
}
default:
goto Done
}
}
Done:
searchQueue = newQueue
s.Mu.Lock()
r.Status = "cancelled"
s.Mu.Unlock()
c.JSON(http.StatusOK, gin.H{"Status": "Cancelled"})
return
}
c.JSON(http.StatusBadRequest, gin.H{"Error": "Cannot cancel running or finished search"})
})
s.Router.GET("/dataleak/sample", func(c *gin.Context) { s.Router.GET("/dataleak/sample", func(c *gin.Context) {
path := c.Query("path") path := c.Query("path")
if path == "" { if path == "" {

View File

@@ -27,7 +27,7 @@ type Query struct {
type Result struct { type Result struct {
Id string Id string
Date time.Time Date time.Time
Status string // "queued", "pending", "completed", "error" Status string // "queued", "pending", "completed", "error", "cancelled"
Query Query Query Query
ResultsCount int // Total number of results found across all services ResultsCount int // Total number of results found across all services
@@ -44,6 +44,8 @@ func Search(s *server.Server, q Query, r *Result, mu *sync.RWMutex) {
r.ResultsCount = 0 r.ResultsCount = 0
mu.Unlock() mu.Unlock()
time.Sleep(20 * time.Second) // To ensure the status update is sent to the client before starting the search
wg.Add(3) wg.Add(3)
go func() { go func() {
if !q.Datawells { if !q.Datawells {

View File

@@ -1,8 +1,11 @@
<script lang="ts"> <script lang="ts">
import { serverUrl, serverPassword } from "$src/lib/stores/server";
import type { History } from "$src/lib/types"; import type { History } from "$src/lib/types";
import { formatDate } from "$src/lib/utils"; import { formatDate } from "$src/lib/utils";
import { Search } from "@lucide/svelte"; import { Search } from "@lucide/svelte";
import { navigate, p } from "sv-router/generated"; import axios from "axios";
import { navigate } from "sv-router/generated";
import { toast } from "svelte-sonner";
let { history, perPage = 5 }: { history: History; perPage?: number } = let { history, perPage = 5 }: { history: History; perPage?: number } =
$props(); $props();
@@ -48,6 +51,26 @@
page++; page++;
} }
} }
function cancel(id: string) {
axios
.post(
`${$serverUrl}/search/cancel/${id}`,
{},
{ headers: { "X-Password": $serverPassword } },
)
.then(() => {
toast.success("Search cancelled");
history = history.map((item) =>
item.Id === id ? { ...item, Status: "cancelled" } : item,
);
filter = filter;
})
.catch((e) => {
toast.error("Failed to cancel search");
console.log("Failed to cancel search", e);
});
}
</script> </script>
<div class="my-4 flex flex-col gap-2"> <div class="my-4 flex flex-col gap-2">
@@ -88,12 +111,20 @@
class="badge badge-xs" class="badge badge-xs"
class:badge-success={item.Status === "completed"} class:badge-success={item.Status === "completed"}
class:badge-warning={item.Status === "pending"} class:badge-warning={item.Status === "pending"}
class:badge-neutral={item.Status === "queued"} class:badge-neutral={item.Status === "queued" ||
item.Status === "cancelled"}
class:badge-error={item.Status === "error"} class:badge-error={item.Status === "error"}
> >
{item.Status} {item.Status}
</div></td </div>
>
{#if item.Status === "queued"}
<button
class="btn btn-xs size-4 btn-square btn-soft"
onclick={() => cancel(item.Id)}>x</button
>
{/if}
</td>
<td>{formatDate(item.Date)}</td> <td>{formatDate(item.Date)}</td>
<td <td
onclick={() => { onclick={() => {

View File

@@ -36,7 +36,7 @@ export type GravatarResult = {
export type Result = { export type Result = {
Id: string; Id: string;
Status: "queued" | "pending" | "completed" | "error"; Status: "queued" | "pending" | "completed" | "error" | "cancelled";
Date: string; Date: string;
Query: Query; Query: Query;
ResultsCount: number; ResultsCount: number;