Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,30 @@
title,
searchValue = $bindable(''),
rightTitle,
placeholder
}: { title: string; searchValue: string; rightTitle: string; placeholder: string } = $props();
placeholder,
showClearSelection = false,
onClearSelection = () => {}
}: {
title: string;
searchValue: string;
rightTitle: string;
placeholder: string;
showClearSelection?: boolean;
onClearSelection?: () => void;
} = $props();
</script>

<header class="mb-4 flex w-full items-center justify-between px-4">
<div class="flex items-center gap-4">
<p class="text-black-700 font-semibold whitespace-nowrap">{title}</p>
<p class="text-black-700 whitespace-nowrap font-semibold">{title}</p>
<div class="relative w-full">
<input
type="text"
{placeholder}
bind:value={searchValue}
class="bg-black-100 text-black-700 placeholder:text-black-700 w-full rounded-[8px] border border-gray-300 px-4 py-1.5 pr-10 text-xs outline-0"
/>
<span class="text-black-700 absolute top-1/2 right-3 h-4 w-4 -translate-y-1/2">
<span class="text-black-700 absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2">
<HugeiconsIcon
icon={Search01FreeIcons}
size="16px"
Expand All @@ -31,6 +40,14 @@
</div>

<div class="flex items-center gap-2">
{#if showClearSelection}
<button
onclick={onClearSelection}
class="rounded bg-red-100 px-3 py-1 text-xs font-medium text-red-700 transition-colors hover:bg-red-200"
>
Clear Selection
</button>
{/if}
<span class="bg-secondary inline-block h-2 w-2 rounded-full"></span>
<p class="text-black-700 text-sm">{rightTitle}</p>
</div>
Expand Down
14 changes: 13 additions & 1 deletion infrastructure/control-panel/src/lib/ui/Table/Table.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
sortBy?: string;
sortOrder?: 'asc' | 'desc';
onSelectionChange?: (index: number, checked: boolean) => void;
onSelectAllChange?: (checked: boolean) => void;
}

let {
Expand All @@ -90,6 +91,7 @@
sortBy,
sortOrder,
onSelectionChange,
onSelectAllChange,
...restProps
}: ITableProps = $props();

Expand All @@ -100,7 +102,17 @@

function toggleCheckAll(checked: boolean) {
checkAll = checked;
checkItems = checkItems.map(() => checked);

// Call the onSelectAllChange callback if provided
if (onSelectAllChange) {
onSelectAllChange(checked);
}

// Update all individual checkboxes without calling onSelectionChange for each
// since onSelectAllChange already handles the bulk selection
for (let i = 0; i < tableData.length; i++) {
checkItems[i] = checked;
}
}

function toggleCheckItem(i: number, checked: boolean) {
Expand Down
56 changes: 54 additions & 2 deletions infrastructure/control-panel/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,48 @@
sessionStorage.setItem('selectedPlatforms', JSON.stringify(selectedPlatformData));
}

// Handle select all eVaults
function handleSelectAllEVaults(checked: boolean) {
if (checked) {
// Select all eVaults
selectedEVaults = Array.from({ length: evaults.length }, (_, i) => i);
} else {
// Deselect all eVaults
selectedEVaults = [];
}

// Store selections immediately in sessionStorage
const selectedEVaultData = selectedEVaults.map((i) => evaults[i]);
sessionStorage.setItem('selectedEVaults', JSON.stringify(selectedEVaultData));
}

// Handle select all platforms
function handleSelectAllPlatforms(checked: boolean) {
if (checked) {
// Select all platforms
selectedPlatforms = Array.from({ length: platforms.length }, (_, i) => i);
} else {
// Deselect all platforms
selectedPlatforms = [];
}

// Store selections immediately in sessionStorage
const selectedPlatformData = selectedPlatforms.map((i) => platforms[i]);
sessionStorage.setItem('selectedPlatforms', JSON.stringify(selectedPlatformData));
}

// Clear eVault selection
function clearEVaultSelection() {
selectedEVaults = [];
sessionStorage.removeItem('selectedEVaults');
}

// Clear platform selection
function clearPlatformSelection() {
selectedPlatforms = [];
sessionStorage.removeItem('selectedPlatforms');
}

// Navigate to monitoring with selected items
function goToMonitoring() {
const selectedEVaultData = selectedEVaults.map((i) => evaults[i]);
Expand Down Expand Up @@ -185,7 +227,11 @@
title="eVaults"
placeholder="Search eVaults"
bind:searchValue={evaultsSearchValue}
rightTitle="Monitoring all eVault pods across Kubernetes clusters"
rightTitle={selectedEVaults.length > 0
? `${selectedEVaults.length} eVault${selectedEVaults.length === 1 ? '' : 's'} selected`
: 'Monitoring all eVault pods across Kubernetes clusters'}
showClearSelection={selectedEVaults.length > 0}
onClearSelection={clearEVaultSelection}
/>

{#if isLoading}
Expand Down Expand Up @@ -215,6 +261,7 @@
{handleNextPage}
handleSelectedRow={handleEVaultRowClick}
onSelectionChange={handleEVaultSelectionChange}
onSelectAllChange={handleSelectAllEVaults}
/>
{/if}
</TableCard>
Expand All @@ -224,7 +271,11 @@
title="Platforms"
placeholder="Search Platforms"
bind:searchValue={platformsSearchQuery}
rightTitle="No platform selected. Select a platform to monitor logs"
rightTitle={selectedPlatforms.length > 0
? `${selectedPlatforms.length} platform${selectedPlatforms.length === 1 ? '' : 's'} selected`
: 'No platform selected. Select a platform to monitor logs'}
showClearSelection={selectedPlatforms.length > 0}
onClearSelection={clearPlatformSelection}
/>
{#if platformsLoading}
<div class="flex justify-center py-8">
Expand Down Expand Up @@ -252,6 +303,7 @@
{handlePreviousPage}
{handleNextPage}
onSelectionChange={handlePlatformSelectionChange}
onSelectAllChange={handleSelectAllPlatforms}
/>
{/if}
</TableCard>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,15 @@ export class WebhookController {
axios.post(new URL("blabsy", process.env.ANCHR_URL).toString(), req.body)
}

if (adapter.lockedIds.includes(id)) return;
console.log("processing -- not skipped");
// Early duplicate check
if (adapter.lockedIds.includes(id)) {
console.log(`Webhook skipped - ID ${id} already locked`);
return res.status(200).json({ success: true, skipped: true });
}

console.log(`Processing webhook for ID: ${id}`);

// Lock the global ID immediately to prevent duplicates
adapter.addToLockedIds(id);

const mapping = Object.values(adapter.mapping).find(
Expand All @@ -105,16 +112,17 @@ export class WebhookController {
const local = await adapter.fromGlobal({ data, mapping });

console.log(local);
//

// Get the local ID from the mapping database
const localId = await adapter.mappingDb.getLocalId(id);

if (localId) {
console.log("LOCAL, updating");
console.log(`LOCAL, updating - ID: ${id}, LocalID: ${localId}`);
// Lock local ID early to prevent duplicate processing
adapter.addToLockedIds(localId);
await this.updateRecord(tableName, localId, local.data);
} else {
console.log("NOT LOCAL, creating");
console.log(`NOT LOCAL, creating - ID: ${id}`);
await this.createRecord(tableName, local.data, req.body.id);
}

Expand Down
31 changes: 30 additions & 1 deletion platforms/blabsy-w3ds-auth-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import path from "path";
import { AuthController } from "./controllers/AuthController";
import { initializeApp, cert, applicationDefault } from "firebase-admin/app";
import { Web3Adapter } from "./web3adapter";
import { WebhookController } from "./controllers/WebhookController";
import { WebhookController, adapter } from "./controllers/WebhookController";

config({ path: path.resolve(__dirname, "../../../.env") });

Expand Down Expand Up @@ -47,6 +47,35 @@ app.post("/api/auth", authController.login);
app.get("/api/auth/sessions/:id", authController.sseStream);
app.post("/api/webhook", webhookController.handleWebhook);

// Debug endpoints for monitoring duplicate prevention
app.get("/api/debug/watcher-stats", (req, res) => {
try {
const stats = web3Adapter.getWatcherStats();
res.json({ success: true, stats });
} catch (error) {
res.status(500).json({ success: false, error: "Failed to get watcher stats" });
}
});

app.post("/api/debug/clear-processed-ids", (req, res) => {
try {
web3Adapter.clearAllProcessedIds();
res.json({ success: true, message: "All processed IDs cleared" });
} catch (error) {
res.status(500).json({ success: false, error: "Failed to clear processed IDs" });
}
});

app.get("/api/debug/locked-ids", (req, res) => {
try {
// Access locked IDs from the exported adapter
const lockedIds = adapter.lockedIds || [];
res.json({ success: true, lockedIds, count: lockedIds.length });
} catch (error) {
res.status(500).json({ success: false, error: "Failed to get locked IDs" });
}
});

// Graceful shutdown
process.on("SIGTERM", async () => {
console.log("SIGTERM received. Shutting down...");
Expand Down
19 changes: 19 additions & 0 deletions platforms/blabsy-w3ds-auth-api/src/web3adapter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,23 @@ export class Web3Adapter {
console.log(`Successfully restarted watcher for ${collectionName}`);
}
}

// Method to get stats from all watchers
getWatcherStats(): Record<string, { processed: number; processing: number }> {
const stats: Record<string, { processed: number; processing: number }> = {};
for (const [name, watcher] of this.watchers.entries()) {
stats[name] = watcher.getStats();
}
return stats;
}

// Method to clear processed IDs from all watchers
clearAllProcessedIds(): void {
console.log("Clearing processed IDs from all watchers...");
for (const [name, watcher] of this.watchers.entries()) {
console.log(`Clearing processed IDs for ${name}...`);
watcher.clearProcessedIds();
}
console.log("All processed IDs cleared");
}
}
Loading
Loading