init
This commit is contained in:
104
front/src/lib/components/index/AnimatedBeam.svelte
Normal file
104
front/src/lib/components/index/AnimatedBeam.svelte
Normal file
@@ -0,0 +1,104 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { onMount, tick } from "svelte";
|
||||
|
||||
let {
|
||||
containerRef = $bindable(),
|
||||
class:className = "",
|
||||
fromRef = $bindable(),
|
||||
toRef = $bindable(),
|
||||
curvature = 0,
|
||||
reverse = false, // Include the reverse prop
|
||||
pathColor = "gray",
|
||||
pathWidth = 2,
|
||||
pathOpacity = 0.2,
|
||||
startXOffset = 0,
|
||||
startYOffset = 0,
|
||||
endXOffset = 0,
|
||||
endYOffset = 0,
|
||||
}= $props();
|
||||
|
||||
|
||||
let id = crypto.randomUUID().slice(0, 8);
|
||||
let pathD = $state("");
|
||||
let svgDimensions = { width: 0, height: 0 };
|
||||
|
||||
let updatePath = () => {
|
||||
if (!containerRef || !fromRef || !toRef) {
|
||||
return;
|
||||
}
|
||||
let containerRect = containerRef?.getBoundingClientRect();
|
||||
let rectA = fromRef?.getBoundingClientRect();
|
||||
let rectB = toRef?.getBoundingClientRect();
|
||||
|
||||
let svgWidth = containerRect.width;
|
||||
let svgHeight = containerRect.height;
|
||||
svgDimensions.width = svgWidth;
|
||||
svgDimensions.height = svgHeight;
|
||||
|
||||
let startX =
|
||||
rectA.left - containerRect.left + rectA.width / 2 + startXOffset;
|
||||
let startY =
|
||||
rectA.top - containerRect.top + rectA.height / 2 + startYOffset;
|
||||
let endX = rectB.left - containerRect.left + rectB.width / 2 + endXOffset;
|
||||
let endY = rectB.top - containerRect.top + rectB.height / 2 + endYOffset;
|
||||
|
||||
let controlY = startY - curvature;
|
||||
let d = `M ${startX},${startY} Q ${
|
||||
(startX + endX) / 2
|
||||
},${controlY} ${endX},${endY}`;
|
||||
pathD = d;
|
||||
};
|
||||
onMount(async () => {
|
||||
await tick().then(() => {
|
||||
updatePath();
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
// For all entries, recalculate the path
|
||||
for (let entry of entries) {
|
||||
updatePath();
|
||||
}
|
||||
});
|
||||
|
||||
// Observe the container element
|
||||
if (containerRef) {
|
||||
resizeObserver.observe(containerRef);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<svg
|
||||
fill="none"
|
||||
width={svgDimensions.width}
|
||||
height={svgDimensions.height}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class={cn(
|
||||
"pointer-events-none absolute left-0 top-0 transform-gpu stroke-2 animate-pulse",
|
||||
className,
|
||||
)}
|
||||
viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`}
|
||||
>
|
||||
<path
|
||||
d={pathD}
|
||||
stroke={pathColor}
|
||||
stroke-width={pathWidth}
|
||||
stroke-opacity={pathOpacity}
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d={pathD}
|
||||
stroke-width={pathWidth}
|
||||
stroke={`url(#${id})`}
|
||||
stroke-opacity="1"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient {id} gradientUnits="userSpaceOnUse" class="transform-gpu">
|
||||
<stop class="[stop-color:var(--color-primary)]" stop-opacity="0"></stop>
|
||||
<stop class="[stop-color:var(--color-primary)]"></stop>
|
||||
<stop offset="32.5%" class="[stop-color:var(--color-primary)]"></stop>
|
||||
<stop offset="100%" class="[stop-color:var(--color-primary)]" stop-opacity="0"
|
||||
></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
125
front/src/lib/components/index/AnimatedBeamMultiple.svelte
Normal file
125
front/src/lib/components/index/AnimatedBeamMultiple.svelte
Normal file
@@ -0,0 +1,125 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { Github, IdCard, Key, User } from "@lucide/svelte";
|
||||
import AnimatedBeam from "./AnimatedBeam.svelte";
|
||||
import Circle from "./Circle.svelte";
|
||||
let containerRef = $state();
|
||||
let div1Ref = $state();
|
||||
let div2Ref = $state();
|
||||
let div3Ref = $state();
|
||||
let div4Ref = $state();
|
||||
let div5Ref = $state();
|
||||
let div6Ref = $state();
|
||||
let div7Ref = $state();
|
||||
let className: any = $state("");
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={containerRef}
|
||||
class={cn("relative flex w-full items-center justify-center ", className)}
|
||||
>
|
||||
<div
|
||||
class="flex h-full w-full flex-row justify-between gap-10 max-w-lg items-center"
|
||||
>
|
||||
<div class="flex flex-col justify-center gap-2">
|
||||
<!-- Div 1 -->
|
||||
<Circle bind:ref={div1Ref}>
|
||||
<div class="tooltip" data-tip="Leak of user's informations">
|
||||
<User />
|
||||
</div>
|
||||
</Circle>
|
||||
<!-- Div 2 -->
|
||||
<Circle bind:ref={div2Ref}>
|
||||
<div class="tooltip" data-tip="Leak of user's passwords">
|
||||
<Key />
|
||||
</div>
|
||||
</Circle>
|
||||
<!-- Div 3 -->
|
||||
<Circle bind:ref={div3Ref}>
|
||||
<div class="tooltip" data-tip="Leak of personal informations">
|
||||
<IdCard />
|
||||
</div>
|
||||
</Circle>
|
||||
<!-- Div 4 -->
|
||||
<Circle bind:ref={div4Ref}>
|
||||
<div class="tooltip" data-tip="Github recon">
|
||||
<Github />
|
||||
</div>
|
||||
</Circle>
|
||||
<!-- Div 5 -->
|
||||
<Circle bind:ref={div5Ref}>
|
||||
<div class="tooltip" data-tip="Google hunt">
|
||||
<svg
|
||||
width="16"
|
||||
viewBox="0 0 256 262"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
><path
|
||||
d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"
|
||||
fill="#fff"
|
||||
/><path
|
||||
d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"
|
||||
fill="#fff"
|
||||
/><path
|
||||
d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782"
|
||||
fill="#fff"
|
||||
/><path
|
||||
d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"
|
||||
fill="#fff"
|
||||
/></svg
|
||||
>
|
||||
</div>
|
||||
</Circle>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center">
|
||||
<!-- Div 6 -->
|
||||
<Circle bind:ref={div6Ref}>
|
||||
<div class="tooltip" data-tip="Your eleakxir backend">
|
||||
<svg
|
||||
width={24}
|
||||
viewBox="0 0 141 205"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class={cn("fill-primary", className)}
|
||||
>
|
||||
<path
|
||||
d="M69.7444 0C84.49 25.1637 100.559 49.4708 117.95 72.9219C126.451 85.0149 133.113 98.1035 137.934 112.188C144.168 135.735 140.195 157.355 126.014 177.046C109.938 196.591 89.1945 205.53 63.7844 203.865C36.717 200.867 17.4935 187.019 6.11353 162.321C-1.9191 142.522 -2.03583 122.655 5.76294 102.722C9.71019 93.7604 14.3849 85.2295 19.7864 77.1289C35.0593 56.0531 49.5504 34.4338 63.259 12.2705C65.6086 8.27249 67.7699 4.18207 69.7444 0ZM100.596 81.3359C102.957 92.649 102.198 103.751 98.3176 114.642C93.9276 124.99 87.7338 134.105 79.7366 141.987C77.6951 144.434 75.8254 147.005 74.1272 149.7C70.5033 155.43 68.5745 161.682 68.342 168.456C68.1692 175.079 70.6236 180.455 75.7043 184.583C89.1062 183.345 100.267 177.678 109.186 167.58C123.101 149.518 125.672 129.885 116.899 108.682C112.25 99.0223 106.815 89.9071 100.596 81.3359ZM70.095 36.4609C59.5617 53.4793 48.4018 70.0738 36.6145 86.2441C30.2619 94.8335 25.2366 104.183 21.5393 114.291C15.2642 135.159 19.2377 153.74 33.4592 170.034C39.7381 176.533 47.2756 180.798 56.0715 182.83C55.7506 182.699 55.4583 182.524 55.1956 182.305C40.6021 163.32 39.6671 143.687 52.3909 123.406C58.721 114.622 64.9149 105.74 70.9719 96.7617C72.559 94.0549 73.9614 91.2501 75.179 88.3477C81.8825 70.1929 80.1876 52.8971 70.095 36.4609Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</Circle>
|
||||
</div>
|
||||
<div class="flex flec-col justify-center">
|
||||
<!-- Div 7 -->
|
||||
<Circle bind:ref={div7Ref}>
|
||||
<div class="tooltip" data-tip="This web client">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-monitor-icon lucide-monitor"
|
||||
><rect width="20" height="14" x="2" y="3" rx="2" /><line
|
||||
x1="8"
|
||||
x2="16"
|
||||
y1="21"
|
||||
y2="21"
|
||||
/><line x1="12" x2="12" y1="17" y2="21" /></svg
|
||||
>
|
||||
</div>
|
||||
</Circle>
|
||||
</div>
|
||||
</div>
|
||||
<AnimatedBeam bind:containerRef bind:fromRef={div1Ref} bind:toRef={div6Ref} />
|
||||
<AnimatedBeam bind:containerRef bind:fromRef={div2Ref} bind:toRef={div6Ref} />
|
||||
<AnimatedBeam bind:containerRef bind:fromRef={div3Ref} bind:toRef={div6Ref} />
|
||||
<AnimatedBeam bind:containerRef bind:fromRef={div4Ref} bind:toRef={div6Ref} />
|
||||
<AnimatedBeam bind:containerRef bind:fromRef={div5Ref} bind:toRef={div6Ref} />
|
||||
<AnimatedBeam bind:containerRef bind:fromRef={div6Ref} bind:toRef={div7Ref} />
|
||||
</div>
|
||||
19
front/src/lib/components/index/Circle.svelte
Normal file
19
front/src/lib/components/index/Circle.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
let { children, label ="", ref=$bindable()} = $props();
|
||||
let className: any = $state("");
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div class="tooltip z-10" data-tip={label}>
|
||||
<div
|
||||
bind:this={ref}
|
||||
class={cn(
|
||||
"bg-base-100 hover:bg-base-200 border-base-200 z-10 flex h-12 w-12 items-center justify-center rounded-full border-2 p-3 transition-all duration-200 cursor-pointer",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{@render children()}
|
||||
</div>
|
||||
</div>
|
||||
115
front/src/lib/components/index/search/datawells.svelte
Normal file
115
front/src/lib/components/index/search/datawells.svelte
Normal file
@@ -0,0 +1,115 @@
|
||||
<script lang="ts">
|
||||
import type { Dataleak } from "$src/lib/types";
|
||||
import { Replace, Search } from "@lucide/svelte";
|
||||
|
||||
let {
|
||||
dataleaks,
|
||||
perPage = 5,
|
||||
showColumns = false,
|
||||
}: {
|
||||
dataleaks: Dataleak[];
|
||||
perPage?: number;
|
||||
showColumns?: boolean;
|
||||
} = $props();
|
||||
|
||||
let page = $state(1);
|
||||
let filter = $state("");
|
||||
let filteredDataleaks = $state<Dataleak[]>(dataleaks);
|
||||
let paginatedDataleaks = $state<Dataleak[]>([]);
|
||||
let totalPages = $state(0);
|
||||
|
||||
$effect(() => {
|
||||
if (filter.trim() === "") {
|
||||
filteredDataleaks = dataleaks;
|
||||
} else {
|
||||
const lowerFilter = filter.toLowerCase();
|
||||
filteredDataleaks = dataleaks.filter((item) =>
|
||||
item.Name.toLowerCase().includes(lowerFilter),
|
||||
);
|
||||
}
|
||||
page = 1;
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (filteredDataleaks) {
|
||||
totalPages = Math.ceil(filteredDataleaks.length / perPage);
|
||||
const start = (page - 1) * perPage;
|
||||
const end = start + perPage;
|
||||
paginatedDataleaks = filteredDataleaks.slice(start, end);
|
||||
if (page > totalPages) {
|
||||
page = totalPages > 0 ? totalPages : 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function previousPage() {
|
||||
if (page > 1) {
|
||||
page--;
|
||||
}
|
||||
}
|
||||
|
||||
function nextPage() {
|
||||
if (page < totalPages) {
|
||||
page++;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="my-4 flex flex-col gap-2">
|
||||
<label class="input input-xs w-full">
|
||||
<Search size={12} />
|
||||
<input class="grow" placeholder="Filter" bind:value={filter} />
|
||||
</label>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Number of rows</th>
|
||||
{#if showColumns}
|
||||
<th>Columns</th>
|
||||
{/if}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#if paginatedDataleaks.length > 0}
|
||||
{#each paginatedDataleaks as item}
|
||||
<tr class="hover:bg-base-300">
|
||||
<th>
|
||||
{item.Name}
|
||||
</th>
|
||||
<td>{item.Length.toLocaleString("fr")}</td>
|
||||
{#if showColumns}
|
||||
<td class="capitalize">
|
||||
{item.Columns.map((col) => col.replace(/_/g, " ")).join(", ")}
|
||||
</td>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
{:else}
|
||||
<tr class="hover:bg-base-300">
|
||||
<td colspan="2" class="text-center leading-9"
|
||||
><span class="text-3xl">(·.·)</span><br />No data wells found</td
|
||||
>
|
||||
</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{#if totalPages > 1}
|
||||
<div class="join m-auto mt-5">
|
||||
<button class="join-item btn" onclick={previousPage} disabled={page === 1}
|
||||
>«</button
|
||||
>
|
||||
<button class="join-item btn">Page {page} / {totalPages}</button>
|
||||
<button
|
||||
class="join-item btn"
|
||||
onclick={nextPage}
|
||||
disabled={page === totalPages}>»</button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
130
front/src/lib/components/index/search/history.svelte
Normal file
130
front/src/lib/components/index/search/history.svelte
Normal file
@@ -0,0 +1,130 @@
|
||||
<script lang="ts">
|
||||
import type { History } from "$src/lib/types";
|
||||
import { formatDate } from "$src/lib/utils";
|
||||
import { Search } from "@lucide/svelte";
|
||||
import { navigate, p } from "sv-router/generated";
|
||||
|
||||
let { history, perPage = 5 }: { history: History; perPage?: number } =
|
||||
$props();
|
||||
|
||||
let page = $state(1);
|
||||
let filter = $state("");
|
||||
let filteredHistory = $state<History>(history);
|
||||
let paginatedHistory = $state<History>([]);
|
||||
let totalPages = $state(0);
|
||||
|
||||
$effect(() => {
|
||||
if (filter.trim() === "") {
|
||||
filteredHistory = history;
|
||||
} else {
|
||||
const lowerFilter = filter.toLowerCase();
|
||||
filteredHistory = history.filter((item) =>
|
||||
item.Query.Text.toLowerCase().includes(lowerFilter),
|
||||
);
|
||||
}
|
||||
page = 1;
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (filteredHistory) {
|
||||
totalPages = Math.ceil(filteredHistory.length / perPage);
|
||||
const start = (page - 1) * perPage;
|
||||
const end = start + perPage;
|
||||
paginatedHistory = filteredHistory.slice(start, end);
|
||||
if (page > totalPages) {
|
||||
page = totalPages > 0 ? totalPages : 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function previousPage() {
|
||||
if (page > 1) {
|
||||
page--;
|
||||
}
|
||||
}
|
||||
|
||||
function nextPage() {
|
||||
if (page < totalPages) {
|
||||
page++;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="my-4 flex flex-col gap-2">
|
||||
<label class="input input-xs w-full">
|
||||
<Search size={12} />
|
||||
<input class="grow" placeholder="Filter" bind:value={filter} />
|
||||
</label>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Query</th>
|
||||
<th>Results</th>
|
||||
<th>Status</th>
|
||||
<th>Date</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#if paginatedHistory.length > 0}
|
||||
{#each paginatedHistory as item}
|
||||
<tr class="hover:bg-base-300">
|
||||
<th>
|
||||
<button
|
||||
onclick={() => {
|
||||
navigate(`/search/:id`, { params: { id: item.Id } });
|
||||
}}
|
||||
class="btn btn-link p-0 no-underline text-base-content"
|
||||
>
|
||||
{item.Query.Text}
|
||||
</button>
|
||||
</th>
|
||||
<td>{item.Results}</td>
|
||||
<td
|
||||
><div
|
||||
class="badge badge-xs"
|
||||
class:badge-success={item.Status === "completed"}
|
||||
class:badge-warning={item.Status === "pending"}
|
||||
>
|
||||
{item.Status}
|
||||
</div></td
|
||||
>
|
||||
<td>{formatDate(item.Date)}</td>
|
||||
<td
|
||||
onclick={() => {
|
||||
navigate(`/search/:id`, { params: { id: item.Id } });
|
||||
}}
|
||||
><button class="btn btn-xs btn-square"
|
||||
><Search size={11} /></button
|
||||
></td
|
||||
>
|
||||
</tr>
|
||||
{/each}
|
||||
{:else}
|
||||
<tr class="hover:bg-base-300">
|
||||
<td colspan="5" class="text-center leading-9"
|
||||
><span class="text-3xl">(·.·)</span><br />No history found</td
|
||||
>
|
||||
</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{#if totalPages > 1}
|
||||
<div class="join m-auto mt-5">
|
||||
<button class="join-item btn" onclick={previousPage} disabled={page === 1}
|
||||
>«</button
|
||||
>
|
||||
<button class="join-item btn">Page {page} / {totalPages}</button>
|
||||
<button
|
||||
class="join-item btn"
|
||||
onclick={nextPage}
|
||||
disabled={page === totalPages}>»</button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
46
front/src/lib/components/index/search/howToSearch.svelte
Normal file
46
front/src/lib/components/index/search/howToSearch.svelte
Normal file
@@ -0,0 +1,46 @@
|
||||
<script lang="ts"></script>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
Eleakxir's search engine is designed to be both fast and flexible, letting
|
||||
you find what you need in multiple ways.
|
||||
</p>
|
||||
|
||||
<h3 class="h3 mt-4 mb-2">Search Modes</h3>
|
||||
|
||||
<p>
|
||||
<span class="text-primary font-semibold">All:</span> This is the default mode.
|
||||
It searches for your query across a set of standard columns like email, username,
|
||||
and phone number. This is the fastest and most efficient way to find a specific
|
||||
user or account.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="text-primary font-semibold">Specific column:</span>
|
||||
This mode lets you choose a specific column to search within, such as email or
|
||||
username. It's useful when you know exactly where the data you're looking for
|
||||
is stored.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="text-primary font-semibold">Full Text:</span> This mode combines
|
||||
all available columns into a single, large text field and searches within it.
|
||||
It's great for finding data that might be in an unexpected column, but it's way
|
||||
slower.
|
||||
</p>
|
||||
|
||||
<h3 class="h3 mt-4 mb-2">Query Matching</h3>
|
||||
<p>
|
||||
<span class="text-primary font-semibold">Standard Search:</span> By default,
|
||||
Eleakxir uses a "fuzzy" search. This means it will find results where your search
|
||||
terms are part of a larger string. For example, searching for 1234 would find
|
||||
john.doe@1234.com.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="text-primary font-semibold">Exact Match:</span> When you enable
|
||||
"Exact Match," the search will only return results where the data in a column
|
||||
is an exact match for your search term. This is useful for finding specific,
|
||||
unique values.
|
||||
</p>
|
||||
</div>
|
||||
313
front/src/lib/components/index/search/id/githubResult.svelte
Normal file
313
front/src/lib/components/index/search/id/githubResult.svelte
Normal file
@@ -0,0 +1,313 @@
|
||||
<script lang="ts">
|
||||
import Accordion from "$src/lib/components/accordion.svelte";
|
||||
import Table from "$src/lib/components/table.svelte";
|
||||
import type { GithubResult } from "$src/lib/types";
|
||||
import { FlattenObject } from "$src/lib/utils";
|
||||
import {
|
||||
Building,
|
||||
ExternalLink,
|
||||
GitCommitVertical,
|
||||
Handshake,
|
||||
Key,
|
||||
Mail,
|
||||
UserRoundPen,
|
||||
} from "@lucide/svelte";
|
||||
const { githubResult }: { githubResult: GithubResult } = $props();
|
||||
</script>
|
||||
|
||||
{#if githubResult.UsernameResult}
|
||||
<div class="w-full">
|
||||
<div class="flex flex-wrap gap-5">
|
||||
<div class="avatar">
|
||||
<div class="w-24 h-24 rounded-xl">
|
||||
<img
|
||||
src={githubResult.UsernameResult.User.AvatarURL}
|
||||
alt="Avatar of {githubResult.UsernameResult.User.Username}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col">
|
||||
<h3 class="h3">{githubResult.UsernameResult.User.Name}</h3>
|
||||
<p class="text-base-content/60">
|
||||
@{githubResult.UsernameResult.User.Username}
|
||||
</p>
|
||||
</div>
|
||||
<p class="max-w-sm">{githubResult.UsernameResult.User.Bio}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card-border border-neutral shadow my-8">
|
||||
<div class="grid">
|
||||
<Table
|
||||
row={{
|
||||
publicRepos: githubResult.UsernameResult.User.PublicRepos,
|
||||
followers: githubResult.UsernameResult.User.Followers,
|
||||
following: githubResult.UsernameResult.User.Following,
|
||||
createdAt: new Date(
|
||||
githubResult.UsernameResult.User.CreatedAt,
|
||||
).toLocaleDateString(),
|
||||
email: githubResult.UsernameResult.User.Email,
|
||||
location: githubResult.UsernameResult.User.Location,
|
||||
company: githubResult.UsernameResult.User.Company,
|
||||
url:
|
||||
"https://github.com/" + githubResult.UsernameResult.User.Username,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#if githubResult.UsernameResult.Socials && githubResult.UsernameResult.Socials.length > 0}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">Social Links</h4>
|
||||
<ul class="flex gap-4 flex-col mt-4 mb-6">
|
||||
{#each githubResult.UsernameResult.Socials as social}
|
||||
<a href={social.URL} target="_blank" rel="noopener noreferrer">
|
||||
<div class="badge bg-base-300">
|
||||
<ExternalLink size={12} />
|
||||
{social.URL}
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.UsernameResult.CloseFriends && githubResult.UsernameResult.CloseFriends.length > 0}
|
||||
<div class="mt-4">
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
<Accordion
|
||||
icon={Handshake}
|
||||
title={"Close Friends"}
|
||||
subtitle={ githubResult.UsernameResult.CloseFriends.length + " close friends found"}
|
||||
>
|
||||
<Table
|
||||
row={githubResult.UsernameResult.CloseFriends}
|
||||
/>
|
||||
</Accordion>
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.UsernameResult.Orgs && githubResult.UsernameResult.Orgs.length > 0}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">Organizations</h4>
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
<Accordion
|
||||
icon={Building}
|
||||
title="Organizations"
|
||||
subtitle={"Found " + githubResult.UsernameResult.Orgs.length + " organizations"}
|
||||
>
|
||||
<Table
|
||||
row={githubResult.UsernameResult.Orgs}
|
||||
/>
|
||||
</Accordion>
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.UsernameResult.Commits && githubResult.UsernameResult.Commits.length > 0}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">Commits</h4>
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
{#each githubResult.UsernameResult.Commits as commit}
|
||||
<Accordion
|
||||
icon={GitCommitVertical}
|
||||
title={commit.Name + " <" + commit.Email + ">"}
|
||||
subtitle={"Occurrences: " + commit.Occurrences}
|
||||
>
|
||||
<Table
|
||||
row={{
|
||||
name: commit.Name,
|
||||
email: commit.Email,
|
||||
url: "https://github.com/" + commit.FirstFoundIn,
|
||||
occurrences: commit.Occurrences,
|
||||
}}
|
||||
/>
|
||||
</Accordion>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.UsernameResult.SshKeys && githubResult.UsernameResult.SshKeys.length > 0}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">SSH Keys</h4>
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
{#each githubResult.UsernameResult.SshKeys as key}
|
||||
<Accordion
|
||||
icon={Key}
|
||||
title={"Created At: " +
|
||||
new Date(key.CreatedAt).toLocaleDateString()}
|
||||
subtitle={"Last Used: " +
|
||||
(key.LastUsed !== "0001-01-01 00:00:00 +0000 UTC"
|
||||
? new Date(key.LastUsed).toLocaleDateString()
|
||||
: "Never")}
|
||||
>
|
||||
<pre class="overflow-x-auto p-2 bg-base-200 rounded"><code
|
||||
class="break-all">{key.Key}</code
|
||||
></pre>
|
||||
</Accordion>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.UsernameResult.SshSigningKeys && githubResult.UsernameResult.SshSigningKeys.length > 0}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">SSH Signing Keys</h4>
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
{#each githubResult.UsernameResult.SshSigningKeys as key}
|
||||
<Accordion
|
||||
icon={Key}
|
||||
title={key.Title}
|
||||
subtitle={"Created At: " + key.CreatedAt}
|
||||
>
|
||||
<pre class="overflow-x-auto p-2 bg-base-200 rounded"><code
|
||||
class="break-all">{key.Key}</code
|
||||
></pre>
|
||||
</Accordion>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.UsernameResult.GpgKeys && githubResult.UsernameResult.GpgKeys.length > 0}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">GPG Keys</h4>
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
{#each githubResult.UsernameResult.GpgKeys as key}
|
||||
<Accordion
|
||||
icon={Key}
|
||||
title={key.Emails && key.Emails.length > 0 ? key.Emails[0].Email : key.KeyID}
|
||||
subtitle={"Created At: " + key.CreatedAt}
|
||||
>
|
||||
<Table
|
||||
row={FlattenObject(key)}
|
||||
/>
|
||||
</Accordion>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.UsernameResult.DeepScan}
|
||||
{#if githubResult.UsernameResult.DeepScan.Authors && githubResult.UsernameResult.DeepScan.Authors.length > 0}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">Deep scan authors</h4>
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
<Accordion
|
||||
icon={UserRoundPen}
|
||||
title="Authors"
|
||||
subtitle={"Found " + githubResult.UsernameResult.DeepScan.Authors.length + " authors"
|
||||
}
|
||||
>
|
||||
<Table
|
||||
row={githubResult.UsernameResult.DeepScan.Authors}
|
||||
/>
|
||||
</Accordion>
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.UsernameResult.DeepScan.Emails && githubResult.UsernameResult.DeepScan.Emails.length > 0}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">Deep scan emails</h4>
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
<Accordion
|
||||
icon={Mail}
|
||||
title="Emails"
|
||||
subtitle={"Found " + githubResult.UsernameResult.DeepScan.Emails.length + " emails"
|
||||
}
|
||||
>
|
||||
<Table
|
||||
row={githubResult.UsernameResult.DeepScan.Emails}
|
||||
/>
|
||||
</Accordion>
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.UsernameResult.DeepScan.Secrets && githubResult.UsernameResult.DeepScan.Secrets.length > 0}
|
||||
{@const flattenedSecrets = githubResult.UsernameResult.DeepScan.Secrets.map(FlattenObject)}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">Deep scan secrets</h4>
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
<Accordion
|
||||
icon={Mail}
|
||||
title="Secrets"
|
||||
subtitle={"Found " + githubResult.UsernameResult.DeepScan.Secrets.length + " secrets"
|
||||
}
|
||||
>
|
||||
<Table
|
||||
row={flattenedSecrets}
|
||||
/>
|
||||
</Accordion>
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{:else if githubResult.EmailResult}
|
||||
<div class="w-full">
|
||||
{#if githubResult.EmailResult.Spoofing}
|
||||
<h4 class="h4 mb-4">From spoofing</h4>
|
||||
<div class="flex flex-wrap gap-5">
|
||||
<div class="avatar">
|
||||
<div class="w-24 h-24 rounded-xl">
|
||||
<img
|
||||
src={githubResult.EmailResult.Spoofing.AvatarURL}
|
||||
alt="Avatar of {githubResult.EmailResult.Spoofing.Username}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<h4 class="h4">@{githubResult.EmailResult.Spoofing.Username}</h4>
|
||||
{#if githubResult.EmailResult.Spoofing.Name}
|
||||
<p>
|
||||
<strong>Name:</strong>
|
||||
{githubResult.EmailResult.Spoofing.Name}
|
||||
</p>
|
||||
{/if}
|
||||
{#if githubResult.EmailResult.Spoofing.Email}
|
||||
<p>
|
||||
<strong>Public email:</strong>
|
||||
{githubResult.EmailResult.Spoofing.Email}
|
||||
</p>
|
||||
{/if}
|
||||
{#if githubResult.EmailResult.Target}
|
||||
<p class="break-all">
|
||||
<strong>Primary email:</strong>
|
||||
{githubResult.EmailResult.Target}
|
||||
</p>
|
||||
{/if}
|
||||
<a
|
||||
href={githubResult.EmailResult.Spoofing.Url}
|
||||
class="link link-primary flex gap-2 items-center"
|
||||
target="_blank"
|
||||
>
|
||||
{githubResult.EmailResult.Spoofing.Url}
|
||||
<ExternalLink size={12} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.EmailResult.Commits}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">Commits</h4>
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
{#each githubResult.EmailResult.Commits as commit}
|
||||
<Accordion
|
||||
icon={GitCommitVertical}
|
||||
title={commit.Username && commit.Username !== ""
|
||||
? commit.Name + " (@" + commit.Username + ")"
|
||||
: commit.Name}
|
||||
subtitle={"Occurrences: " + commit.Occurrences}
|
||||
>
|
||||
<Table
|
||||
row={{
|
||||
name: commit.Name,
|
||||
username: commit.Username,
|
||||
email: commit.Email,
|
||||
first_found_in: commit.FirstFoundIn,
|
||||
occurrences: commit.Occurrences,
|
||||
}}
|
||||
/>
|
||||
</Accordion>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
100
front/src/lib/components/index/search/id/row.svelte
Normal file
100
front/src/lib/components/index/search/id/row.svelte
Normal file
@@ -0,0 +1,100 @@
|
||||
<script lang="ts">
|
||||
import Table from "$src/lib/components/table.svelte";
|
||||
import { ChevronDown, ChevronUp, Database, Key, Mail } from "@lucide/svelte";
|
||||
|
||||
const { row }: { row: Record<string, string> } = $props();
|
||||
|
||||
let isOpen = $state<boolean>(false);
|
||||
|
||||
function getDomain(dataleakName: string) {
|
||||
const firstPart = dataleakName.split(" ")[0].toLowerCase();
|
||||
const domainRegex =
|
||||
/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/;
|
||||
if (domainRegex.test(firstPart)) {
|
||||
return firstPart;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getHighlightedContent(row: Record<string, string>): string {
|
||||
const prioritizedKeys = [
|
||||
"email",
|
||||
"username",
|
||||
"full_name",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"phone",
|
||||
"password",
|
||||
"address",
|
||||
];
|
||||
|
||||
for (const key of prioritizedKeys) {
|
||||
if (row[key]) {
|
||||
return row[key];
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in row) {
|
||||
if (row[key]) {
|
||||
return row[key];
|
||||
}
|
||||
}
|
||||
|
||||
return "No content";
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="list-row hover:bg-base-300/75 text-left"
|
||||
class:bg-base-300={isOpen}
|
||||
class:rounded-b-none={isOpen}
|
||||
onclick={() => {
|
||||
isOpen = !isOpen;
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
{#if getDomain(row["source"])}
|
||||
<img
|
||||
src="https://icons.duckduckgo.com/ip3/{getDomain(row['source'])}.ico"
|
||||
class="size-10 rounded-box bg-neutral"
|
||||
alt="Favicon de {getDomain(row['source'])}"
|
||||
/>
|
||||
{:else if row["password"] !== null}
|
||||
<div
|
||||
class="size-10 rounded-box bg-neutral items-center justify-center flex"
|
||||
>
|
||||
<Key />
|
||||
</div>
|
||||
{:else if row["email"] !== null}
|
||||
<div
|
||||
class="size-10 rounded-box bg-neutral items-center justify-center flex"
|
||||
>
|
||||
<Mail />
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="size-10 rounded-box bg-neutral items-center justify-center flex"
|
||||
>
|
||||
<Database />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<div>{getHighlightedContent(row)}</div>
|
||||
<div class="text-xs uppercase font-semibold opacity-60">
|
||||
{row["source"]}
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn btn-square btn-ghost">
|
||||
{#if isOpen}
|
||||
<ChevronUp size={12} />
|
||||
{:else}
|
||||
<ChevronDown size={12} />
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{#if isOpen}
|
||||
<li class="list-row flex bg-base-200 rounded-t-none mb-2">
|
||||
<Table {row} />
|
||||
</li>
|
||||
{/if}
|
||||
72
front/src/lib/components/index/search/id/rows.svelte
Normal file
72
front/src/lib/components/index/search/id/rows.svelte
Normal file
@@ -0,0 +1,72 @@
|
||||
<script lang="ts">
|
||||
import type { Result } from "$src/lib/types";
|
||||
import Row from "./row.svelte";
|
||||
|
||||
const { result }: { result: Result } = $props();
|
||||
|
||||
let page = $state(1);
|
||||
let totalPages = $state(0);
|
||||
const perPage = 20;
|
||||
|
||||
let paginated = $state<Record<string, string>[]>([]);
|
||||
|
||||
$effect(() => {
|
||||
if (result && result.LeakResult.Rows) {
|
||||
totalPages = Math.ceil(result.LeakResult.Rows.length / perPage);
|
||||
const start = (page - 1) * perPage;
|
||||
const end = start + perPage;
|
||||
paginated = result.LeakResult.Rows.slice(start, end);
|
||||
if (page > totalPages) {
|
||||
page = totalPages > 0 ? totalPages : 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function goToFirstPage() {
|
||||
page = 1;
|
||||
top.scrollIntoView();
|
||||
}
|
||||
|
||||
function previousPage() {
|
||||
if (page > 1) {
|
||||
page--;
|
||||
top.scrollIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
function nextPage() {
|
||||
if (page < totalPages) {
|
||||
page++;
|
||||
top.scrollIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
let top: any = $state();
|
||||
</script>
|
||||
|
||||
<div bind:this={top} class="absolute -mt-[100px]"></div>
|
||||
{#if result}
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
{#each paginated as row (row)}
|
||||
<Row {row} />
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
{#if totalPages > 1}
|
||||
<div class="join m-auto mt-5">
|
||||
<button class="join-item btn" onclick={previousPage} disabled={page === 1}
|
||||
>«</button
|
||||
>
|
||||
<button class="join-item btn" onclick={goToFirstPage}
|
||||
>Page {page} / {totalPages}</button
|
||||
>
|
||||
<button
|
||||
class="join-item btn"
|
||||
onclick={nextPage}
|
||||
disabled={page === totalPages}>»</button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
No result
|
||||
{/if}
|
||||
54
front/src/lib/components/index/search/id/stats.svelte
Normal file
54
front/src/lib/components/index/search/id/stats.svelte
Normal file
@@ -0,0 +1,54 @@
|
||||
<script lang="ts">
|
||||
import type { Result } from "$src/lib/types";
|
||||
import { formatDate } from "$src/lib/utils";
|
||||
import { BadgeInfo, Clock, File } from "@lucide/svelte";
|
||||
|
||||
const { result }: { result: Result } = $props();
|
||||
|
||||
let nresult = $state(0);
|
||||
$effect(() => {
|
||||
const r = [
|
||||
result.LeakResult.Rows?.length | 0,
|
||||
result.GithubResult.EmailResult?.Commits?.length | 0,
|
||||
result.GithubResult.EmailResult?.Spoofing ? 1 : 0,
|
||||
result.GithubResult.UsernameResult?.Commits?.length | 0,
|
||||
];
|
||||
nresult = r.reduce((a, b) => a + b, 0);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="stats stats-vertical md:stats-horizontal">
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-secondary">
|
||||
<File />
|
||||
</div>
|
||||
<div class="stat-title">Results</div>
|
||||
<div class="stat-value" class:animate-pulse={result.Status === "pending"}>
|
||||
{nresult.toLocaleString("fr")}
|
||||
{#if result.Status === "pending"}
|
||||
<span class="loading loading-dots loading-xs ml-2"></span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-secondary">
|
||||
<Clock />
|
||||
</div>
|
||||
<div class="stat-title">Date</div>
|
||||
<div class="stat-value">
|
||||
{formatDate(result.Date)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-secondary">
|
||||
<BadgeInfo />
|
||||
</div>
|
||||
<div class="stat-title">Status</div>
|
||||
<div class="stat-value" class:animate-pulse={result.Status === "pending"}>
|
||||
{result.Status}
|
||||
{#if result.Status === "pending"}
|
||||
<span class="loading loading-dots loading-xs ml-2"></span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
104
front/src/lib/components/index/search/searchbar.svelte
Normal file
104
front/src/lib/components/index/search/searchbar.svelte
Normal file
@@ -0,0 +1,104 @@
|
||||
<script lang="ts">
|
||||
import { serverPassword, serverUrl } from "$src/lib/stores/server";
|
||||
import { cn } from "$src/lib/utils";
|
||||
import { Equal, EqualNot, Search } from "@lucide/svelte";
|
||||
import axios from "axios";
|
||||
import { navigate } from "sv-router/generated";
|
||||
import { toast } from "svelte-sonner";
|
||||
|
||||
const {
|
||||
initialQuery = "",
|
||||
initialFilter = "all",
|
||||
initialExactMatch = false,
|
||||
}: {
|
||||
initialQuery?: string;
|
||||
initialFilter?: string;
|
||||
initialExactMatch?: boolean;
|
||||
} = $props();
|
||||
|
||||
let filters = [
|
||||
"all",
|
||||
"username",
|
||||
"email",
|
||||
"name",
|
||||
"phone",
|
||||
"url",
|
||||
"password",
|
||||
"password hash",
|
||||
"full_text",
|
||||
];
|
||||
let activeFilter = $state<string>(initialFilter);
|
||||
let query = $state<string>(initialQuery);
|
||||
let exactMatch = $state<boolean>(initialExactMatch);
|
||||
|
||||
function NewSearch() {
|
||||
axios
|
||||
.post(
|
||||
`${$serverUrl}/search`,
|
||||
{ Text: query, Column: activeFilter, ExactMatch: exactMatch },
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Password": $serverPassword,
|
||||
},
|
||||
},
|
||||
)
|
||||
.then((r) => {
|
||||
const id = r.data.Id;
|
||||
window.location.href = `/search/${id}`;
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e.response.data.Error !== undefined) {
|
||||
toast.error(e.response.data.Error);
|
||||
} else {
|
||||
toast.error("An error occurred");
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex gap-5 flex-col">
|
||||
<div
|
||||
class="flex gap-3 justify-start items-center w-full overflow-y-hidden overflow-x-auto"
|
||||
>
|
||||
{#each filters as filter}
|
||||
<button
|
||||
class={cn(
|
||||
"btn btn-md capitalize",
|
||||
activeFilter === filter
|
||||
? "btn-primary"
|
||||
: "btn-ghost btn-neutral text-base-content/80 hover:text-neutral-content",
|
||||
)}
|
||||
onclick={() => (activeFilter = filter)}>{filter.replace("_", " ")}</button
|
||||
>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<form
|
||||
class="join w-full"
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
NewSearch();
|
||||
}}
|
||||
>
|
||||
<label class="grow input input-xl input-primary join-item w-full">
|
||||
<Search size={16} />
|
||||
<input
|
||||
class="grow input-xl"
|
||||
type="text"
|
||||
bind:value={query}
|
||||
placeholder="Search..."
|
||||
required
|
||||
/>
|
||||
|
||||
<div class="tooltip" data-tip="Exact Match">
|
||||
<label class="toggle text-base-content toggle-xs">
|
||||
<input type="checkbox" bind:checked={exactMatch} />
|
||||
<EqualNot aria-label="disable" size={12} />
|
||||
<Equal aria-label="enabled" size={12} />
|
||||
</label>
|
||||
</div>
|
||||
</label>
|
||||
<button class="btn btn-primary btn-xl join-item">Search</button>
|
||||
</form>
|
||||
</div>
|
||||
83
front/src/lib/components/index/search/services.svelte
Normal file
83
front/src/lib/components/index/search/services.svelte
Normal file
@@ -0,0 +1,83 @@
|
||||
<script lang="ts">
|
||||
import type { Server } from "$src/lib/types";
|
||||
|
||||
let { serverInfo }: { serverInfo: Server } = $props();
|
||||
</script>
|
||||
|
||||
<div class="my-4">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Service</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="hover:bg-base-300">
|
||||
<th> Data wells lookup </th>
|
||||
<td>
|
||||
{#if serverInfo.Dataleaks.length !== 0}
|
||||
<div class="inline-grid *:[grid-area:1/1] mr-2">
|
||||
<div class="status status-success"></div>
|
||||
<div class="status status-success"></div>
|
||||
</div>
|
||||
Active
|
||||
{:else}
|
||||
<div class="inline-grid *:[grid-area:1/1] mr-2">
|
||||
<div class="status status-error animate-ping"></div>
|
||||
<div class="status status-error"></div>
|
||||
</div>
|
||||
Inactive
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-base-300">
|
||||
<th class="flex flex-wrap gap-2 items-center">
|
||||
Github recon
|
||||
{#if serverInfo.Settings.GithubTokenLoaded === true}
|
||||
<div class="badge badge-xs badge-neutral">Token</div>
|
||||
{/if}
|
||||
{#if serverInfo.Settings.GithubDeepMode === true}
|
||||
<div class="badge badge-xs badge-neutral">Deep Mode</div>
|
||||
{/if}
|
||||
</th>
|
||||
<td>
|
||||
{#if serverInfo.Settings.GithubRecon === true}
|
||||
<div class="inline-grid *:[grid-area:1/1] mr-2">
|
||||
<div class="status status-success"></div>
|
||||
<div class="status status-success"></div>
|
||||
</div>
|
||||
Active
|
||||
{:else}
|
||||
<div class="inline-grid *:[grid-area:1/1] mr-2">
|
||||
<div class="status status-error animate-ping"></div>
|
||||
<div class="status status-error"></div>
|
||||
</div>
|
||||
Inactive
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-base-300">
|
||||
<th>Google hunt</th>
|
||||
<td>
|
||||
{#if serverInfo.Settings.GithubRecon === true}
|
||||
<div class="inline-grid *:[grid-area:1/1] mr-2">
|
||||
<div class="status status-success"></div>
|
||||
<div class="status status-success"></div>
|
||||
</div>
|
||||
Active
|
||||
{:else}
|
||||
<div class="inline-grid *:[grid-area:1/1] mr-2">
|
||||
<div class="status status-error animate-ping"></div>
|
||||
<div class="status status-error"></div>
|
||||
</div>
|
||||
Inactive
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
46
front/src/lib/components/index/search/stats.svelte
Normal file
46
front/src/lib/components/index/search/stats.svelte
Normal file
@@ -0,0 +1,46 @@
|
||||
<script lang="ts">
|
||||
import type { Server } from "$src/lib/types";
|
||||
import { Database, File, Save } from "@lucide/svelte";
|
||||
|
||||
const { serverInfo }: { serverInfo: Server | null } = $props();
|
||||
|
||||
function mbToGb(mb: number): number {
|
||||
return Math.round((mb / 1024) * 100) / 100;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="stats stats-vertical md:stats-horizontal">
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-secondary">
|
||||
<File />
|
||||
</div>
|
||||
<div class="stat-title">Rows available</div>
|
||||
<div class="stat-value">
|
||||
{serverInfo?.TotalRows
|
||||
? serverInfo.TotalRows.toLocaleString("fr")
|
||||
: "-- --- --- ---"}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-secondary">
|
||||
<Database />
|
||||
</div>
|
||||
<div class="stat-title">Data wells available</div>
|
||||
<div class="stat-value">
|
||||
{serverInfo?.TotalDataleaks
|
||||
? serverInfo.TotalDataleaks.toLocaleString("fr")
|
||||
: "---"}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-secondary">
|
||||
<Save />
|
||||
</div>
|
||||
<div class="stat-title">Storage used</div>
|
||||
<div class="stat-value">
|
||||
{serverInfo?.TotalSize
|
||||
? mbToGb(serverInfo.TotalSize).toLocaleString("fr") + " Gb"
|
||||
: "--- Gb"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user