diff --git a/.env.example b/.env.example index d81932bd2..dc88458d8 100644 --- a/.env.example +++ b/.env.example @@ -71,3 +71,13 @@ EREPUTATION_MAPPING_DB_PATH="/path/to/erep/mapping/db" VITE_EREPUTATION_BASE_URL=http://localhost:8765 LOAD_TEST_USER_COUNT=6 + +PUBLIC_EID_WALLET_TOKEN=obtained-from-post-registry-service-/platforms/certification + +LOKI_URL=http://localhost:3100 +LOKI_USERNAME=admin +LOKI_PASSWORD=admin + +LOKI_URL=http://146.190.29.56:3100 +LOKI_USERNAME=admin +LOKI_PASSWORD=admin diff --git a/asd b/asd new file mode 100644 index 000000000..e69de29bb diff --git a/db/init-multiple-databases.sh b/db/init-multiple-databases.sh new file mode 100755 index 000000000..299ad717a --- /dev/null +++ b/db/init-multiple-databases.sh @@ -0,0 +1,39 @@ +#!/bin/bash +set -e + +# Get the list of databases from environment variable +# Default to empty if not set +POSTGRES_MULTIPLE_DATABASES=${POSTGRES_MULTIPLE_DATABASES:-} + +# If no databases specified, exit +if [ -z "$POSTGRES_MULTIPLE_DATABASES" ]; then + echo "No databases specified in POSTGRES_MULTIPLE_DATABASES" + exit 0 +fi + +echo "Creating multiple databases..." + +# Split the comma-separated list and create each database +IFS=',' read -ra DATABASES <<< "$POSTGRES_MULTIPLE_DATABASES" +for db in "${DATABASES[@]}"; do + # Trim whitespace + db=$(echo "$db" | xargs) + + if [ -n "$db" ]; then + # Check if database exists + DB_EXISTS=$(psql -v ON_ERROR_STOP=0 --username "$POSTGRES_USER" --dbname postgres -tAc "SELECT 1 FROM pg_database WHERE datname='$db'" 2>/dev/null || echo "") + + if [ "$DB_EXISTS" = "1" ]; then + echo "Database $db already exists, skipping..." + else + echo "Creating database: $db" + # Create the database directly (not inside a function) + psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname postgres <<-EOSQL + CREATE DATABASE "$db"; +EOSQL + fi + fi +done + +echo "Multiple databases created successfully!" + diff --git a/dev-docker-compose.yaml b/dev-docker-compose.yaml index f17b6dbc2..28f8b5ce0 100644 --- a/dev-docker-compose.yaml +++ b/dev-docker-compose.yaml @@ -126,6 +126,19 @@ services: networks: - metastate-network <<: *common-host-access + entrypoint: ["/bin/sh", "-c"] + command: + - | + # Remove any stale PID files before starting Neo4j + # Neo4j stores PID files in /var/lib/neo4j/run/neo4j.pid + rm -f /var/lib/neo4j/run/neo4j.pid 2>/dev/null || true + rm -f /var/lib/neo4j/data/run/neo4j.pid 2>/dev/null || true + rm -f /var/lib/neo4j/data/neo4j.pid 2>/dev/null || true + # Also clean up any other PID files + find /var/lib/neo4j -name "*.pid" -type f -delete 2>/dev/null || true + find /var/lib/neo4j/data -name "*.pid" -type f -delete 2>/dev/null || true + # Start Neo4j with the original entrypoint + exec /startup/docker-entrypoint.sh neo4j healthcheck: test: [ "CMD-SHELL", "cypher-shell -u neo4j -p ${NEO4J_PASSWORD:-neo4j} 'RETURN 1' || exit 1" ] interval: 10s @@ -557,6 +570,18 @@ services: - metastate-network <<: *common-host-access + # Loki for log aggregation + loki: + profiles: + - all + image: grafana/loki:latest + ports: + - "3100:3100" + command: -config.file=/etc/loki/local-config.yaml + networks: + - metastate-network + <<: *common-host-access + volumes: postgres_data: neo4j_data: diff --git a/infrastructure/control-panel/src/lib/components/EVaultList.svelte b/infrastructure/control-panel/src/lib/components/EVaultList.svelte index 0d141d0f4..8656c6383 100644 --- a/infrastructure/control-panel/src/lib/components/EVaultList.svelte +++ b/infrastructure/control-panel/src/lib/components/EVaultList.svelte @@ -138,7 +138,7 @@

No eVaults found

- Try refreshing or check your Kubernetes connection + Try refreshing or check your registry connection

{:else} @@ -155,7 +155,7 @@ - Namespace + eName (w3id) - Age - - - Service URL + URI @@ -180,38 +175,35 @@ - {evault.name} + {evault.name || evault.ename || evault.evault} - {evault.namespace} + {evault.ename || 'N/A'} - {evault.status} + {evault.status || 'Unknown'} - {evault.age || 'Unknown'} - - - {#if evault.serviceUrl} + {#if evault.uri || evault.serviceUrl} - {evault.serviceUrl} + {evault.uri || evault.serviceUrl} {:else} - No external access + No URI available {/if} diff --git a/infrastructure/control-panel/src/lib/services/evaultService.ts b/infrastructure/control-panel/src/lib/services/evaultService.ts index 5e11675c8..216b0f481 100644 --- a/infrastructure/control-panel/src/lib/services/evaultService.ts +++ b/infrastructure/control-panel/src/lib/services/evaultService.ts @@ -75,16 +75,15 @@ export class EVaultService { } /** - * Get logs for a specific eVault pod + * Get logs for a specific eVault by evaultId */ - /** - * Get logs for a specific eVault pod - */ - static async getEVaultLogs(namespace: string, podName: string): Promise { + static async getEVaultLogs(evaultId: string, tail?: number): Promise { try { - const response = await fetch( - `/api/evaults/${encodeURIComponent(namespace)}/${encodeURIComponent(podName)}/logs` - ); + const url = new URL(`/api/evaults/${encodeURIComponent(evaultId)}/logs`, window.location.origin); + if (tail) { + url.searchParams.set('tail', tail.toString()); + } + const response = await fetch(url.toString()); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } @@ -97,20 +96,20 @@ export class EVaultService { } /** - * Get metrics for a specific eVault pod + * Get details for a specific eVault by evaultId */ - static async getEVaultMetrics(namespace: string, podName: string): Promise { + static async getEVaultDetails(evaultId: string): Promise { try { const response = await fetch( - `/api/evaults/${encodeURIComponent(namespace)}/${encodeURIComponent(podName)}/metrics` + `/api/evaults/${encodeURIComponent(evaultId)}/details` ); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); - return data.metrics || {}; + return data.evault || {}; } catch (error) { - console.error('Failed to fetch eVault metrics:', error); + console.error('Failed to fetch eVault details:', error); throw error; } } diff --git a/infrastructure/control-panel/src/lib/services/loki.ts b/infrastructure/control-panel/src/lib/services/loki.ts index 34cb6f1a1..361249dc2 100644 --- a/infrastructure/control-panel/src/lib/services/loki.ts +++ b/infrastructure/control-panel/src/lib/services/loki.ts @@ -98,6 +98,49 @@ export class LokiService { .filter((event): event is FlowEvent => event !== null); } + /** + * Query logs for a specific evault by identifier + * Supports querying by evault field or ename (w3id) in log labels + */ + async getEVaultLogs( + evaultId: string, + ename?: string, + limit: number = 100, + start?: string, + end?: string + ): Promise { + // Try multiple query patterns to find logs for this evault + // First try by evault field, then by ename/w3id + const queries = [ + `{evault="${evaultId}"}`, + ...(ename ? [`{ename="${ename}"}`, `{w3id="${ename}"}`] : []) + ]; + + const allLogs: LogEntry[] = []; + + // Try each query pattern + for (const query of queries) { + try { + const logs = await this.queryLogs(query, start, end); + allLogs.push(...logs); + } catch (error) { + console.log(`Query ${query} failed, trying next pattern`); + } + } + + // Remove duplicates and sort by timestamp + const uniqueLogs = Array.from( + new Map(allLogs.map((log) => [`${log.timestamp}-${log.line}`, log])).values() + ).sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()); + + // Extract log lines and limit to requested number + const logLines = uniqueLogs + .map((log) => log.line) + .slice(-limit); // Get last N lines + + return logLines; + } + parseLogEntry(log: LogEntry): FlowEvent | null { try { // Parse the JSON log line diff --git a/infrastructure/control-panel/src/lib/services/registry.ts b/infrastructure/control-panel/src/lib/services/registry.ts index 99b1c8b0f..28696b759 100644 --- a/infrastructure/control-panel/src/lib/services/registry.ts +++ b/infrastructure/control-panel/src/lib/services/registry.ts @@ -1,95 +1,160 @@ -import { env } from '$env/dynamic/public'; +import { env } from "$env/dynamic/public"; export interface Platform { - name: string; - url: string; - status: 'Active' | 'Inactive'; - uptime: string; + name: string; + url: string; + status: "Active" | "Inactive"; + uptime: string; +} + +export interface RegistryVault { + ename: string; + uri: string; + evault: string; + originalUri?: string; + resolved?: boolean; } export class RegistryService { - private baseUrl: string; - - constructor() { - this.baseUrl = env.PUBLIC_REGISTRY_URL || 'https://registry.staging.metastate.foundation'; - } - - async getPlatforms(): Promise { - try { - const response = await fetch(`${this.baseUrl}/platforms`); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const platformUrls: string[] = await response.json(); - - // Convert URLs to platform objects with friendly names - const platforms = platformUrls.map((url) => { - // Ensure URL has protocol - const urlWithProtocol = - url.startsWith('http://') || url.startsWith('https://') ? url : `http://${url}`; - const urlObj = new URL(urlWithProtocol); - const hostname = urlObj.hostname; - const port = urlObj.port; - const protocol = urlObj.protocol; - - // Extract platform name from hostname - let name = hostname.split('.')[0]; - - // Capitalize and format the name - if (name === 'pictique') name = 'Pictique'; - else if (name === 'blabsy') name = 'Blabsy'; - else if (name === 'charter') name = 'Group Charter'; - else if (name === 'cerberus') name = 'Cerberus'; - else if (name === 'evoting') name = 'eVoting'; - else name = name.charAt(0).toUpperCase() + name.slice(1); - - // Build the full URL with protocol and port - const fullUrl = port ? `${hostname}:${port}` : hostname; - const displayUrl = `${protocol}//${fullUrl}`; - - return { - name, - url: displayUrl, - status: 'Active' as const, - uptime: '24h' - }; - }); - - return platforms; - } catch (error) { - console.error('Error fetching platforms from registry:', error); - - // Return fallback platforms if registry is unavailable - return [ - { - name: 'Blabsy', - url: 'http://192.168.0.235:4444', - status: 'Active', - uptime: '24h' - }, - { - name: 'Pictique', - url: 'http://192.168.0.235:1111', - status: 'Active', - uptime: '24h' - }, - { - name: 'Group Charter', - url: 'http://192.168.0.235:5555', - status: 'Active', - uptime: '24h' - }, - { - name: 'Cerberus', - url: 'http://192.168.0.235:6666', - status: 'Active', - uptime: '24h' - } - ]; - } - } + private baseUrl: string; + + constructor() { + this.baseUrl = + env.PUBLIC_REGISTRY_URL || + "https://registry.staging.metastate.foundation"; + } + + async getEVaults(): Promise { + try { + const response = await fetch(`${this.baseUrl}/list`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const vaults: RegistryVault[] = await response.json(); + return vaults; + } catch (error) { + console.error("Error fetching evaults from registry:", error); + return []; + } + } + + async getPlatforms(): Promise { + try { + const response = await fetch(`${this.baseUrl}/platforms`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const platformUrls: (string | null | undefined)[] = + await response.json(); + + // Filter out null/undefined values and convert URLs to platform objects + const platforms = platformUrls + .filter( + (url): url is string => url != null && url.trim() !== "", + ) + .map((url) => { + // Use the original URL from the registry (it already has the correct format) + let displayUrl = url.trim(); + + // Ensure URL has protocol if it doesn't + if ( + !displayUrl.startsWith("http://") && + !displayUrl.startsWith("https://") + ) { + displayUrl = `http://${displayUrl}`; + } + + // Parse URL to extract platform name + let name = "Unknown"; + try { + const urlObj = new URL(displayUrl); + const hostname = urlObj.hostname; + // Extract platform name from hostname (remove port if present) + const hostnameWithoutPort = hostname.split(":")[0]; + const namePart = hostnameWithoutPort.split(".")[0]; + + // Capitalize and format the name + if (namePart === "pictique") name = "Pictique"; + else if (namePart === "blabsy") name = "Blabsy"; + else if (namePart === "charter") name = "Group Charter"; + else if (namePart === "cerberus") name = "Cerberus"; + else if (namePart === "evoting") name = "eVoting"; + else if (namePart === "dreamsync") name = "DreamSync"; + else if (namePart === "ereputation") + name = "eReputation"; + else + name = + namePart.charAt(0).toUpperCase() + + namePart.slice(1); + } catch { + // If URL parsing fails, try to extract name from the URL string + const match = displayUrl.match( + /(?:https?:\/\/)?([^:./]+)/, + ); + if (match) { + const namePart = match[1].toLowerCase(); + if (namePart === "pictique") name = "Pictique"; + else if (namePart === "blabsy") name = "Blabsy"; + else if (namePart === "charter") + name = "Group Charter"; + else if (namePart === "cerberus") name = "Cerberus"; + else if (namePart === "evoting") name = "eVoting"; + else if (namePart === "dreamsync") + name = "DreamSync"; + else if (namePart === "ereputation") + name = "eReputation"; + else + name = + namePart.charAt(0).toUpperCase() + + namePart.slice(1); + } + } + + return { + name, + url: displayUrl, + status: "Active" as const, + uptime: "24h", + }; + }); + + return platforms; + } catch (error) { + console.error("Error fetching platforms from registry:", error); + + // Return fallback platforms if registry is unavailable + return [ + { + name: "Blabsy", + url: "http://192.168.0.235:4444", + status: "Active", + uptime: "24h", + }, + { + name: "Pictique", + url: "http://192.168.0.235:1111", + status: "Active", + uptime: "24h", + }, + { + name: "Group Charter", + url: "http://192.168.0.235:5555", + status: "Active", + uptime: "24h", + }, + { + name: "Cerberus", + url: "http://192.168.0.235:6666", + status: "Active", + uptime: "24h", + }, + ]; + } + } } export const registryService = new RegistryService(); diff --git a/infrastructure/control-panel/src/routes/+layout.svelte b/infrastructure/control-panel/src/routes/+layout.svelte index 6d243a278..b2226b9a1 100644 --- a/infrastructure/control-panel/src/routes/+layout.svelte +++ b/infrastructure/control-panel/src/routes/+layout.svelte @@ -31,7 +31,7 @@ size="sm" class="whitespace-nowrap" variant="solid" - callback={() => { + callback={async () => { // Get selected items from the current page const evaultsData = sessionStorage.getItem('selectedEVaults'); const platformsData = sessionStorage.getItem('selectedPlatforms'); @@ -47,6 +47,37 @@ return; } + // Fetch full objects and store them for monitoring page + try { + // Fetch evaults if we have IDs + if (evaultsData) { + const evaultIds = JSON.parse(evaultsData); + if (Array.isArray(evaultIds) && evaultIds.length > 0 && typeof evaultIds[0] === 'string') { + const { EVaultService } = await import('$lib/services/evaultService'); + const allEVaults = await EVaultService.getEVaults(); + const evaultObjects = evaultIds + .map((id: string) => allEVaults.find((e: any) => (e.evault || e.ename || e.id) === id)) + .filter(Boolean); + sessionStorage.setItem('selectedEVaultsData', JSON.stringify(evaultObjects)); + } + } + + // Fetch platforms if we have URLs + if (platformsData) { + const platformUrls = JSON.parse(platformsData); + if (Array.isArray(platformUrls) && platformUrls.length > 0 && typeof platformUrls[0] === 'string') { + const { registryService } = await import('$lib/services/registry'); + const allPlatforms = await registryService.getPlatforms(); + const platformObjects = platformUrls + .map((url: string) => allPlatforms.find((p: any) => p.url === url)) + .filter(Boolean); + sessionStorage.setItem('selectedPlatformsData', JSON.stringify(platformObjects)); + } + } + } catch (error) { + console.error('Error fetching data for monitoring:', error); + } + // Navigate to monitoring goto('/monitoring'); }}>Start Monitoring + import { goto } from '$app/navigation'; import { TableCard, TableCardHeader } from '$lib/fragments'; - import { Table } from '$lib/ui'; import { EVaultService } from '$lib/services/evaultService'; import { registryService } from '$lib/services/registry'; - import type { EVault } from './api/evaults/+server'; import type { Platform } from '$lib/services/registry'; - import { onMount } from 'svelte'; + import { Table } from '$lib/ui'; import { RefreshCw } from 'lucide-svelte'; - import { goto } from '$app/navigation'; + import { onMount } from 'svelte'; + import type { EVault } from './api/evaults/+server'; let evaultsSearchValue = $state(''); let platformsSearchQuery = $state(''); @@ -34,9 +34,10 @@ if (!evaultsSearchValue.trim()) return evaults; return evaults.filter( (evault) => - evault.name.toLowerCase().includes(evaultsSearchValue.toLowerCase()) || - evault.evaultId.toLowerCase().includes(evaultsSearchValue.toLowerCase()) || - evault.namespace.toLowerCase().includes(evaultsSearchValue.toLowerCase()) + evault.name?.toLowerCase().includes(evaultsSearchValue.toLowerCase()) || + evault.ename?.toLowerCase().includes(evaultsSearchValue.toLowerCase()) || + evault.evault?.toLowerCase().includes(evaultsSearchValue.toLowerCase()) || + evault.id?.toLowerCase().includes(evaultsSearchValue.toLowerCase()) ); }); @@ -70,23 +71,19 @@ let mappedEVaultsData = $derived(() => { const paginated = paginatedEVaults(); return paginated.map((evault) => ({ - eName: { + eVault: { type: 'text', - value: evault.evaultId, + value: evault.evault || evault.id || 'N/A', className: 'cursor-pointer text-blue-600 hover:text-blue-800 hover:underline' }, - Uptime: { - type: 'text', - value: evault.age - }, - IP: { + eName: { type: 'text', - value: evault.ip + value: evault.ename || 'N/A' }, URI: { type: 'link', - value: evault.serviceUrl || 'N/A', - link: evault.serviceUrl || '#', + value: evault.uri || evault.serviceUrl || 'N/A', + link: evault.uri || evault.serviceUrl || '#', external: true } })); @@ -116,6 +113,22 @@ })); }); + // Derived values for selected indices to ensure reactivity + const selectedEVaultIndices = $derived( + paginatedEVaults() + .map((evault, index) => { + const evaultId = evault.evault || evault.ename || evault.id; + return selectedEVaults.includes(evaultId) ? index : -1; + }) + .filter((index) => index !== -1) + ); + + const selectedPlatformIndices = $derived( + filteredPlatforms() + .map((platform, index) => (selectedPlatforms.includes(platform.url) ? index : -1)) + .filter((index) => index !== -1) + ); + const handlePreviousPage = async () => { if (currentPage > 1) { currentPage--; @@ -142,16 +155,15 @@ } if (checked) { - selectedEVaults = [...selectedEVaults, selectedEVault.evaultId]; + const evaultId = selectedEVault.evault || selectedEVault.ename || selectedEVault.id; + selectedEVaults = [...selectedEVaults, evaultId]; } else { - selectedEVaults = selectedEVaults.filter((id) => id !== selectedEVault.evaultId); + const evaultId = selectedEVault.evault || selectedEVault.ename || selectedEVault.id; + selectedEVaults = selectedEVaults.filter((id) => id !== evaultId); } - // Store selections immediately in sessionStorage - const selectedEVaultData = selectedEVaults - .map((id) => evaults.find((e) => e.evaultId === id)) - .filter(Boolean); - sessionStorage.setItem('selectedEVaults', JSON.stringify(selectedEVaultData)); + // Store selections immediately in sessionStorage (store as simple array of IDs) + sessionStorage.setItem('selectedEVaults', JSON.stringify(selectedEVaults)); } // Handle platform selection changes @@ -168,16 +180,13 @@ } if (checked) { - selectedPlatforms = [...selectedPlatforms, selectedPlatform.name]; + selectedPlatforms = [...selectedPlatforms, selectedPlatform.url]; } else { - selectedPlatforms = selectedPlatforms.filter((name) => name !== selectedPlatform.name); + selectedPlatforms = selectedPlatforms.filter((url) => url !== selectedPlatform.url); } - // Store selections immediately in sessionStorage - const selectedPlatformData = selectedPlatforms - .map((name) => platforms.find((p) => p.name === name)) - .filter(Boolean); - sessionStorage.setItem('selectedPlatforms', JSON.stringify(selectedPlatformData)); + // Store selections immediately in sessionStorage (store as simple array of URLs) + sessionStorage.setItem('selectedPlatforms', JSON.stringify(selectedPlatforms)); } // Handle select all eVaults @@ -189,8 +198,8 @@ console.log('filtered eVaults length:', filtered.length); if (checked) { - // Select all filtered eVaults by their evaultId - selectedEVaults = filtered.map((evault) => evault.evaultId); + // Select all filtered eVaults by their ID (evault or ename) + selectedEVaults = filtered.map((evault) => evault.evault || evault.ename || evault.id); console.log('✅ Selected all filtered eVaults, selectedEVaults:', selectedEVaults); } else { // Deselect all eVaults @@ -198,12 +207,9 @@ console.log('❌ Deselected all eVaults, selectedEVaults:', selectedEVaults); } - // Store selections immediately in sessionStorage - const selectedEVaultData = selectedEVaults - .map((id) => evaults.find((e) => e.evaultId === id)) - .filter(Boolean); - sessionStorage.setItem('selectedEVaults', JSON.stringify(selectedEVaultData)); - console.log('💾 Stored in sessionStorage:', selectedEVaultData); + // Store selections immediately in sessionStorage (store as simple array of IDs) + sessionStorage.setItem('selectedEVaults', JSON.stringify(selectedEVaults)); + console.log('💾 Stored in sessionStorage:', selectedEVaults); } // Handle select all platforms @@ -215,8 +221,8 @@ console.log('filtered platforms length:', filtered.length); if (checked) { - // Select all filtered platforms by their name - selectedPlatforms = filtered.map((platform) => platform.name); + // Select all filtered platforms by their URL + selectedPlatforms = filtered.map((platform) => platform.url); console.log( '✅ Selected all filtered platforms, selectedPlatforms:', selectedPlatforms @@ -227,38 +233,42 @@ console.log('❌ Deselected all platforms, selectedPlatforms:', selectedPlatforms); } - // Store selections immediately in sessionStorage - const selectedPlatformData = selectedPlatforms - .map((name) => platforms.find((p) => p.name === name)) - .filter(Boolean); - sessionStorage.setItem('selectedPlatforms', JSON.stringify(selectedPlatformData)); - console.log('💾 Stored in sessionStorage:', selectedPlatformData); + // Store selections immediately in sessionStorage (store as simple array of URLs) + sessionStorage.setItem('selectedPlatforms', JSON.stringify(selectedPlatforms)); + console.log('💾 Stored in sessionStorage:', selectedPlatforms); } // Clear eVault selection function clearEVaultSelection() { selectedEVaults = []; sessionStorage.removeItem('selectedEVaults'); + sessionStorage.removeItem('selectedEVaultsData'); } // Clear platform selection function clearPlatformSelection() { selectedPlatforms = []; sessionStorage.removeItem('selectedPlatforms'); + sessionStorage.removeItem('selectedPlatformsData'); } // Navigate to monitoring with selected items function goToMonitoring() { + // Convert IDs/URLs to full objects for monitoring page const selectedEVaultData = selectedEVaults - .map((id) => evaults.find((e) => e.evaultId === id)) + .map((id) => evaults.find((e) => (e.evault || e.ename || e.id) === id)) .filter(Boolean); const selectedPlatformData = selectedPlatforms - .map((name) => platforms.find((p) => p.name === name)) + .map((url) => platforms.find((p) => p.url === url)) .filter(Boolean); - // Store selected data in sessionStorage to pass to monitoring page - sessionStorage.setItem('selectedEVaults', JSON.stringify(selectedEVaultData)); - sessionStorage.setItem('selectedPlatforms', JSON.stringify(selectedPlatformData)); + // Store full objects in sessionStorage for monitoring page (it expects objects) + // But also keep the simple arrays for persistence when returning + sessionStorage.setItem('selectedEVaultsData', JSON.stringify(selectedEVaultData)); + sessionStorage.setItem('selectedPlatformsData', JSON.stringify(selectedPlatformData)); + // Keep simple arrays for persistence + sessionStorage.setItem('selectedEVaults', JSON.stringify(selectedEVaults)); + sessionStorage.setItem('selectedPlatforms', JSON.stringify(selectedPlatforms)); goto('/monitoring'); } @@ -290,7 +300,7 @@ const mapped = { eName: { type: 'text', - value: evault.evaultId, + value: evault.evault || evault.ename || evault.id, className: 'cursor-pointer text-blue-600 hover:text-blue-800 hover:underline' }, @@ -349,10 +359,99 @@ const paginated = paginatedEVaults(); const evault = paginated[index]; if (evault) { - goto(`/monitoring/${evault.namespace}/${evault.name}`); + // Use evault ID (evault field or ename) for navigation + const evaultId = evault.evault || evault.ename || evault.id; + goto(`/evaults/${encodeURIComponent(evaultId)}`); } } + // Restore selections from sessionStorage + // Use an effect to restore selections when data is loaded + let hasRestoredSelections = $state(false); + $effect(() => { + // Only restore once when both datasets are loaded + // The flag prevents multiple restorations, and will reset on component remount + if ( + !isLoading && + !platformsLoading && + evaults.length >= 0 && + platforms.length >= 0 && + !hasRestoredSelections + ) { + hasRestoredSelections = true; + + // Restore eVault selections - try simple array first, fallback to object array for backwards compatibility + const storedEVaults = sessionStorage.getItem('selectedEVaults'); + if (storedEVaults && evaults.length > 0) { + try { + const storedData = JSON.parse(storedEVaults); + + // Check if it's a simple array of IDs or array of objects + if (Array.isArray(storedData) && storedData.length > 0) { + if (typeof storedData[0] === 'string') { + // Simple array of IDs - filter to only include IDs that exist in current evaults + selectedEVaults = storedData.filter((id: string) => + evaults.some((e) => (e.evault || e.ename || e.id) === id) + ); + } else { + // Array of objects (backwards compatibility) - extract IDs + selectedEVaults = storedData + .map((stored: any) => { + const match = evaults.find( + (e) => + (stored.evault && e.evault === stored.evault) || + (stored.ename && e.ename === stored.ename) || + (stored.id && e.id === stored.id) || + (stored.serviceUrl && + e.serviceUrl === stored.serviceUrl) + ); + return match ? match.evault || match.ename || match.id : null; + }) + .filter((id: string | null): id is string => id !== null); + } + } + console.log('✅ Restored eVault selections:', selectedEVaults); + } catch (error) { + console.error('Error restoring eVault selections:', error); + } + } + + // Restore platform selections - try simple array first, fallback to object array for backwards compatibility + const storedPlatforms = sessionStorage.getItem('selectedPlatforms'); + if (storedPlatforms && platforms.length > 0) { + try { + const storedData = JSON.parse(storedPlatforms); + + // Check if it's a simple array of URLs or array of objects + if (Array.isArray(storedData) && storedData.length > 0) { + if (typeof storedData[0] === 'string') { + // Simple array of URLs - filter to only include URLs that exist in current platforms + selectedPlatforms = storedData.filter((url: string) => + platforms.some((p) => p.url === url) + ); + } else { + // Array of objects (backwards compatibility) - extract URLs + selectedPlatforms = storedData + .map((stored: any) => { + // Try to match by URL first, then by name for backwards compatibility + const match = platforms.find( + (p) => + p.url === stored.url || + (stored.name && p.name === stored.name) + ); + return match ? match.url : null; + }) + .filter((url: string | null): url is string => url !== null); + } + } + console.log('✅ Restored platform selections:', selectedPlatforms); + } catch (error) { + console.error('Error restoring platform selections:', error); + } + } + } + }); + onMount(() => { fetchEVaults(); fetchPlatforms(); @@ -402,11 +501,7 @@ handleSelectedRow={handleEVaultRowClick} onSelectionChange={handleEVaultSelectionChange} onSelectAllChange={handleSelectAllEVaults} - selectedIndices={paginatedEVaults() - .map((evault, index) => - selectedEVaults.includes(evault.evaultId) ? index : -1 - ) - .filter((index) => index !== -1)} + selectedIndices={selectedEVaultIndices} /> @@ -478,11 +573,7 @@ withPagination={false} onSelectionChange={handlePlatformSelectionChange} onSelectAllChange={handleSelectAllPlatforms} - selectedIndices={filteredPlatforms() - .map((platform, index) => - selectedPlatforms.includes(platform.name) ? index : -1 - ) - .filter((index) => index !== -1)} + selectedIndices={selectedPlatformIndices} /> {/if} diff --git a/infrastructure/control-panel/src/routes/api/evaults/+server.ts b/infrastructure/control-panel/src/routes/api/evaults/+server.ts index 225f1557c..3e9fbaae3 100644 --- a/infrastructure/control-panel/src/routes/api/evaults/+server.ts +++ b/infrastructure/control-panel/src/routes/api/evaults/+server.ts @@ -1,260 +1,56 @@ import { json } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit'; -import { promisify } from 'util'; -import { exec } from 'child_process'; - -const execAsync = promisify(exec); +import { registryService } from '$lib/services/registry'; export interface EVault { - id: string; - name: string; - namespace: string; - status: string; - age: string; - ready: string; - restarts: number; - image: string; - ip: string; - node: string; - evaultId: string; - serviceUrl?: string; - podName?: string; // Add pod name for logs access + id: string; // evault identifier (evault field from registry) + name: string; // display name (ename or evault) + ename: string; // w3id identifier + uri: string; // resolved service URI + evault: string; // evault identifier + status: string; // derived from health check + serviceUrl?: string; // same as uri for display } export const GET: RequestHandler = async () => { try { - // Get external IP from Kubernetes nodes - let externalIP = 'localhost'; - try { - // First try to get external IP from nodes - const { stdout: nodesOutput } = await execAsync('kubectl get nodes -o json'); - const nodes = JSON.parse(nodesOutput); - - // Look for external IP in node addresses - for (const node of nodes.items) { - if (node.status && node.status.addresses) { - for (const address of node.status.addresses) { - if (address.type === 'ExternalIP' && address.address) { - externalIP = address.address; - console.log('Found external IP from node:', externalIP); - break; - } - } - if (externalIP !== 'localhost') break; - } - } - - // If no external IP found, try to get internal IP - if (externalIP === 'localhost') { - for (const node of nodes.items) { - if (node.status && node.status.addresses) { - for (const address of node.status.addresses) { - if (address.type === 'InternalIP' && address.address) { - // Check if it's not a localhost/127.0.0.1 address - if ( - !address.address.startsWith('127.') && - address.address !== 'localhost' - ) { - externalIP = address.address; - console.log('Found internal IP from node:', externalIP); - break; - } - } - } - if (externalIP !== 'localhost') break; - } - } - } - - // If still no IP found, try minikube ip as fallback - if (externalIP === 'localhost') { - const { stdout: minikubeIPOutput } = await execAsync( - 'minikube ip 2>/dev/null || echo ""' - ); - if (minikubeIPOutput.trim() && minikubeIPOutput.trim() !== 'localhost') { - externalIP = minikubeIPOutput.trim(); - console.log('Using minikube IP:', externalIP); - } - } - - // If still no IP, try to get the host IP from kubectl config - if (externalIP === 'localhost') { - const { stdout: configOutput } = await execAsync( - 'kubectl config view --minify -o json' - ); - const config = JSON.parse(configOutput); - if ( - config.clusters && - config.clusters[0] && - config.clusters[0].cluster && - config.clusters[0].cluster.server - ) { - const serverUrl = config.clusters[0].cluster.server; - const urlMatch = serverUrl.match(/https?:\/\/([^:]+):/); - if ( - urlMatch && - urlMatch[1] && - urlMatch[1] !== 'localhost' && - urlMatch[1] !== '127.0.0.1' - ) { - externalIP = urlMatch[1]; - console.log('Using IP from kubectl config:', externalIP); - } - } - } - } catch (ipError) { - console.log('Could not get external IP, using localhost:', ipError); - } - - console.log('Using IP for services:', externalIP); - - // Get all namespaces - const { stdout: namespacesOutput } = await execAsync('kubectl get namespaces -o json'); - const namespaces = JSON.parse(namespacesOutput); - - // Filter for eVault namespaces - const evaultNamespaces = namespaces.items - .filter((ns: any) => ns.metadata.name.startsWith('evault-')) - .map((ns: any) => ns.metadata.name); - - console.log('Found eVault namespaces:', evaultNamespaces); - - let allEVaults: EVault[] = []; - - // Get services and pods from each eVault namespace - for (const namespace of evaultNamespaces) { - try { - // Get services in this namespace as JSON - const { stdout: servicesOutput } = await execAsync( - `kubectl get services -n ${namespace} -o json` - ); - const services = JSON.parse(servicesOutput); - - // Get pods in this namespace as JSON - const { stdout: podsOutput } = await execAsync( - `kubectl get pods -n ${namespace} -o json` - ); - const pods = JSON.parse(podsOutput); - - console.log(`=== SERVICES FOR ${namespace} ===`); - console.log(JSON.stringify(services, null, 2)); - console.log(`=== PODS FOR ${namespace} ===`); - console.log(JSON.stringify(pods, null, 2)); - console.log(`=== END DATA ===`); - - if (services.items && services.items.length > 0) { - for (const service of services.items) { - const serviceName = service.metadata.name; - const serviceType = service.spec.type; - const ports = service.spec.ports; - - console.log(`Service: ${serviceName}, Type: ${serviceType}, Ports:`, ports); - - // Find NodePort for NodePort services - let nodePort = null; - if (serviceType === 'NodePort' && ports) { - for (const port of ports) { - if (port.nodePort) { - nodePort = port.nodePort; - break; - } - } - } - - console.log(`NodePort: ${nodePort}`); - - // Get pod data for this service - let podData = { - status: 'Unknown', - age: 'N/A', - ready: '0/0', - restarts: 0, - image: 'N/A', - ip: 'N/A', - node: 'N/A', - podName: 'N/A' - }; - - if (pods.items && pods.items.length > 0) { - // Find pod that matches this service (usually same name or has service label) - const matchingPod = pods.items.find( - (pod: any) => - pod.metadata.name.includes( - serviceName.replace('-service', '') - ) || pod.metadata.labels?.app === 'evault' - ); - - if (matchingPod) { - const pod = matchingPod; - const readyCount = - pod.status.containerStatuses?.filter((cs: any) => cs.ready) - .length || 0; - const totalCount = pod.status.containerStatuses?.length || 0; - const restarts = - pod.status.containerStatuses?.reduce( - (sum: number, cs: any) => sum + (cs.restartCount || 0), - 0 - ) || 0; - - // Calculate age - const creationTime = new Date(pod.metadata.creationTimestamp); - const now = new Date(); - const ageMs = now.getTime() - creationTime.getTime(); - const ageDays = Math.floor(ageMs / (1000 * 60 * 60 * 24)); - const ageHours = Math.floor( - (ageMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60) - ); - const age = - ageDays > 0 ? `${ageDays}d${ageHours}h` : `${ageHours}h`; - - podData = { - status: pod.status.phase || 'Unknown', - age: age, - ready: `${readyCount}/${totalCount}`, - restarts: restarts, - image: pod.spec.containers?.[0]?.image || 'N/A', - ip: pod.status.podIP || 'N/A', - node: pod.spec.nodeName || 'N/A', - podName: pod.metadata.name || 'N/A' - }; - } - } - - // Extract the eVault ID from the namespace - const evaultId = namespace.replace('evault-', ''); - - // Generate service URL - let serviceUrl = ''; - if (nodePort) { - serviceUrl = `http://${externalIP}:${nodePort}`; - } - - console.log(`Service URL: ${serviceUrl}`); - - allEVaults.push({ - id: serviceName, - name: serviceName, - namespace: namespace, - status: podData.status, - age: podData.age, - ready: podData.ready, - restarts: podData.restarts, - image: podData.image, - ip: podData.ip, - node: podData.node, - evaultId: evaultId, - serviceUrl: serviceUrl, - podName: podData.podName - }); - } + // Fetch all evaults from registry + const registryVaults = await registryService.getEVaults(); + + // Transform registry vaults to EVault format + const evaults: EVault[] = await Promise.all( + registryVaults.map(async (vault) => { + // Use evault identifier as the primary ID, fallback to ename + const evaultId = vault.evault || vault.ename; + + // Determine display name (prefer ename, fallback to evault) + const displayName = vault.ename || vault.evault || 'Unknown'; + + // Check health status by attempting to fetch from URI + let status = 'Unknown'; + try { + const healthResponse = await fetch(`${vault.uri}/health`, { + signal: AbortSignal.timeout(2000) // 2 second timeout + }); + status = healthResponse.ok ? 'Active' : 'Inactive'; + } catch { + status = 'Inactive'; } - } catch (namespaceError) { - console.log(`Error accessing namespace ${namespace}:`, namespaceError); - } - } - console.log(`Total eVaults found: ${allEVaults.length}`); - return json({ evaults: allEVaults }); + return { + id: evaultId, + name: displayName, + ename: vault.ename, + uri: vault.uri, + evault: vault.evault, + status: status, + serviceUrl: vault.uri + }; + }) + ); + + console.log(`Total eVaults found: ${evaults.length}`); + return json({ evaults }); } catch (error) { console.error('Error fetching eVaults:', error); return json({ error: 'Failed to fetch eVaults', evaults: [] }, { status: 500 }); diff --git a/infrastructure/control-panel/src/routes/api/evaults/[evaultId]/details/+server.ts b/infrastructure/control-panel/src/routes/api/evaults/[evaultId]/details/+server.ts new file mode 100644 index 000000000..13469106f --- /dev/null +++ b/infrastructure/control-panel/src/routes/api/evaults/[evaultId]/details/+server.ts @@ -0,0 +1,45 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { registryService } from '$lib/services/registry'; + +export const GET: RequestHandler = async ({ params }) => { + const { evaultId } = params; + + try { + // Get evault information from registry + const evaults = await registryService.getEVaults(); + const evault = evaults.find( + (v) => v.evault === evaultId || v.ename === evaultId + ); + + if (!evault) { + return json({ error: `eVault '${evaultId}' not found in registry.` }, { status: 404 }); + } + + // Check health status + let healthStatus = 'Unknown'; + try { + const healthResponse = await fetch(`${evault.uri}/health`, { + signal: AbortSignal.timeout(2000) // 2 second timeout + }); + healthStatus = healthResponse.ok ? 'Healthy' : 'Unhealthy'; + } catch { + healthStatus = 'Unreachable'; + } + + return json({ + evault: { + ename: evault.ename, + uri: evault.uri, + evault: evault.evault, + originalUri: evault.originalUri, + resolved: evault.resolved, + healthStatus + } + }); + } catch (error) { + console.error('Error fetching evault details:', error); + return json({ error: 'Failed to fetch evault details' }, { status: 500 }); + } +}; + diff --git a/infrastructure/control-panel/src/routes/api/evaults/[evaultId]/logs/+server.ts b/infrastructure/control-panel/src/routes/api/evaults/[evaultId]/logs/+server.ts new file mode 100644 index 000000000..e4d4a49ce --- /dev/null +++ b/infrastructure/control-panel/src/routes/api/evaults/[evaultId]/logs/+server.ts @@ -0,0 +1,47 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { lokiService } from '$lib/services/loki'; +import { registryService } from '$lib/services/registry'; + +export const GET: RequestHandler = async ({ params, url }) => { + const { evaultId } = params; + const tail = parseInt(url.searchParams.get('tail') || '100', 10); + + try { + // Get evault information from registry to find ename + const evaults = await registryService.getEVaults(); + const evault = evaults.find( + (v) => v.evault === evaultId || v.ename === evaultId + ); + + if (!evault) { + return json( + { + error: `eVault '${evaultId}' not found in registry.`, + logs: [] + }, + { status: 404 } + ); + } + + // Query Loki for logs using evault identifier + const logs = await lokiService.getEVaultLogs( + evault.evault || evaultId, + evault.ename, + tail + ); + + return json({ logs }); + } catch (error: any) { + console.error('Error fetching logs:', error); + + return json( + { + error: 'Failed to fetch logs. Please check if the eVault is still running and Loki is accessible.', + logs: [] + }, + { status: 500 } + ); + } +}; + diff --git a/infrastructure/control-panel/src/routes/api/evaults/[namespace]/[pod]/details/+server.ts b/infrastructure/control-panel/src/routes/api/evaults/[namespace]/[pod]/details/+server.ts deleted file mode 100644 index ceb226ec2..000000000 --- a/infrastructure/control-panel/src/routes/api/evaults/[namespace]/[pod]/details/+server.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { exec } from 'child_process'; -import { promisify } from 'util'; -import { json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; - -const execAsync = promisify(exec); - -export const GET: RequestHandler = async ({ params }) => { - const { namespace, pod } = params; - - try { - // Get detailed pod information - const { stdout: podInfo } = await execAsync(`kubectl describe pod -n ${namespace} ${pod}`); - - // Get pod YAML - const { stdout: podYaml } = await execAsync( - `kubectl get pod -n ${namespace} ${pod} -o yaml` - ); - - // Get pod metrics if available - let metrics = null; - try { - const { stdout: metricsOutput } = await execAsync( - `kubectl top pod -n ${namespace} ${pod}` - ); - metrics = metricsOutput.trim(); - } catch (metricsError) { - // Metrics might not be available - console.log('Metrics not available:', metricsError); - } - - return json({ - podInfo: podInfo.trim(), - podYaml: podYaml.trim(), - metrics - }); - } catch (error) { - console.error('Error fetching pod details:', error); - return json({ error: 'Failed to fetch pod details' }, { status: 500 }); - } -}; diff --git a/infrastructure/control-panel/src/routes/api/evaults/[namespace]/[pod]/logs/+server.ts b/infrastructure/control-panel/src/routes/api/evaults/[namespace]/[pod]/logs/+server.ts deleted file mode 100644 index 3ba0d93ba..000000000 --- a/infrastructure/control-panel/src/routes/api/evaults/[namespace]/[pod]/logs/+server.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { exec } from 'child_process'; -import { promisify } from 'util'; -import { json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; - -const execAsync = promisify(exec); - -export const GET: RequestHandler = async ({ params, url }) => { - const { namespace, pod } = params; - const tail = url.searchParams.get('tail') || '100'; - - try { - // First check if the namespace exists - try { - await execAsync(`kubectl get namespace ${namespace}`); - } catch (namespaceError: any) { - if (namespaceError.stderr?.includes('not found')) { - return json( - { - error: `Namespace '${namespace}' not found. The eVault may have been deleted or terminated.`, - logs: [] - }, - { status: 404 } - ); - } - throw namespaceError; - } - - // Then check if the pod exists - try { - await execAsync(`kubectl get pod ${pod} -n ${namespace}`); - } catch (podError: any) { - if (podError.stderr?.includes('not found')) { - return json( - { - error: `Pod '${pod}' not found in namespace '${namespace}'. The pod may have been deleted or terminated.`, - logs: [] - }, - { status: 404 } - ); - } - throw podError; - } - - // If both exist, fetch the logs - const { stdout } = await execAsync( - `kubectl logs -n ${namespace} ${pod} -c evault --tail=${tail}` - ); - const logs = stdout - .trim() - .split('\n') - .filter((line) => line.trim()); - - return json({ logs }); - } catch (error: any) { - console.error('Error fetching logs:', error); - - // Handle specific kubectl errors - if (error.stderr?.includes('not found')) { - return json( - { - error: 'Resource not found. The eVault or pod may have been deleted.', - logs: [] - }, - { status: 404 } - ); - } - - return json( - { - error: 'Failed to fetch logs. Please check if the eVault is still running.', - logs: [] - }, - { status: 500 } - ); - } -}; diff --git a/infrastructure/control-panel/src/routes/api/evaults/[namespace]/[pod]/metrics/+server.ts b/infrastructure/control-panel/src/routes/api/evaults/[namespace]/[pod]/metrics/+server.ts deleted file mode 100644 index 431f3643e..000000000 --- a/infrastructure/control-panel/src/routes/api/evaults/[namespace]/[pod]/metrics/+server.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { exec } from 'child_process'; -import { promisify } from 'util'; -import { json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; - -const execAsync = promisify(exec); - -export const GET: RequestHandler = async ({ params }) => { - const { namespace, pod } = params; - - console.log('Metrics API called with namespace:', namespace, 'pod:', pod); - - try { - // Get pod resource usage (this might fail if metrics server not enabled) - console.log('Running kubectl top pod...'); - let topOutput = ''; - try { - const { stdout } = await execAsync(`kubectl top pod ${pod} -n ${namespace}`); - topOutput = stdout; - } catch (topError) { - console.log('kubectl top pod failed (metrics server not available):', topError); - topOutput = 'No metrics available'; - } - console.log('kubectl top pod output:', topOutput); - - // Get pod status details - console.log('Running kubectl describe pod...'); - const { stdout: describeOutput } = await execAsync( - `kubectl describe pod ${pod} -n ${namespace} 2>/dev/null || echo "No pod details available"` - ); - console.log('kubectl describe pod output length:', describeOutput?.length || 0); - - // Get container logs count (last 100 lines) - console.log('Running kubectl logs...'); - const { stdout: logsOutput } = await execAsync( - `kubectl logs -n ${namespace} ${pod} -c evault --tail=100 2>/dev/null || echo ""` - ); - console.log('kubectl logs output length:', logsOutput?.length || 0); - - const logLines = logsOutput - .trim() - .split('\n') - .filter((line) => line.trim()); - - // Parse top output for CPU and Memory - let cpu = 'N/A'; - let memory = 'N/A'; - if ( - topOutput && - !topOutput.includes('No metrics available') && - !topOutput.includes('Metrics API not available') - ) { - console.log('Parsing top output...'); - const lines = topOutput.trim().split('\n'); - console.log('Top output lines:', lines); - if (lines.length > 1) { - const podLine = lines[1]; // First line after header - console.log('Pod line:', podLine); - const parts = podLine.split(/\s+/); - console.log('Pod line parts:', parts); - if (parts.length >= 4) { - cpu = parts[2] || 'N/A'; - memory = parts[3] || 'N/A'; - console.log('Extracted CPU:', cpu, 'Memory:', memory); - } - } - } - - console.log('Final CPU:', cpu, 'Memory:', memory); - - // Parse describe output for events and conditions - const events: string[] = []; - const conditions: string[] = []; - - if (describeOutput && !describeOutput.includes('No pod details available')) { - const lines = describeOutput.split('\n'); - let inEvents = false; - let inConditions = false; - - for (const line of lines) { - if (line.includes('Events:')) { - inEvents = true; - inConditions = false; - continue; - } - if (line.includes('Conditions:')) { - inConditions = true; - inEvents = false; - continue; - } - if (line.includes('Volumes:') || line.includes('QoS Class:')) { - inEvents = false; - inConditions = false; - continue; - } - - if (inEvents && line.trim() && !line.startsWith(' ')) { - // Handle case where Events shows "" - if (line.trim() === '') { - events.push('No recent events'); - } else { - events.push(line.trim()); - } - } - if (inConditions && line.trim() && !line.startsWith(' ')) { - conditions.push(line.trim()); - } - } - } - - // Calculate basic stats - const totalLogLines = logLines.length; - const errorLogs = logLines.filter( - (line) => - line.toLowerCase().includes('error') || - line.toLowerCase().includes('fail') || - line.toLowerCase().includes('exception') - ).length; - const warningLogs = logLines.filter( - (line) => line.toLowerCase().includes('warn') || line.toLowerCase().includes('warning') - ).length; - - // Get additional pod info for alternative metrics - let podAge = 'N/A'; - let podStatus = 'Unknown'; - try { - const { stdout: getPodOutput } = await execAsync( - `kubectl get pod ${pod} -n ${namespace} -o json` - ); - const podInfo = JSON.parse(getPodOutput); - podAge = podInfo.metadata?.creationTimestamp - ? Math.floor( - (Date.now() - new Date(podInfo.metadata.creationTimestamp).getTime()) / - (1000 * 60 * 60 * 24) - ) + 'd' - : 'N/A'; - podStatus = podInfo.status?.phase || 'Unknown'; - } catch (podError) { - console.log('Failed to get pod info:', podError); - } - - const metrics = { - resources: { - cpu, - memory, - note: topOutput.includes('Metrics API not available') - ? 'Metrics server not enabled' - : undefined - }, - logs: { - totalLines: totalLogLines, - errorCount: errorLogs, - warningCount: warningLogs, - lastUpdate: new Date().toISOString() - }, - status: { - events: events.length > 0 ? events : ['No recent events'], - conditions: conditions.length > 0 ? conditions : ['No conditions available'], - podAge, - podStatus - } - }; - - return json(metrics); - } catch (error) { - console.error('Error fetching metrics:', error); - return json( - { - error: 'Failed to fetch metrics', - resources: { cpu: 'N/A', memory: 'N/A' }, - logs: { - totalLines: 0, - errorCount: 0, - warningCount: 0, - lastUpdate: new Date().toISOString() - }, - status: { events: [], conditions: [] } - }, - { status: 500 } - ); - } -}; diff --git a/infrastructure/control-panel/src/routes/evaults/[namespace]/[pod]/+page.svelte b/infrastructure/control-panel/src/routes/evaults/[evaultId]/+page.svelte similarity index 51% rename from infrastructure/control-panel/src/routes/evaults/[namespace]/[pod]/+page.svelte rename to infrastructure/control-panel/src/routes/evaults/[evaultId]/+page.svelte index 1eccdfc2b..09cbbf8ee 100644 --- a/infrastructure/control-panel/src/routes/evaults/[namespace]/[pod]/+page.svelte +++ b/infrastructure/control-panel/src/routes/evaults/[evaultId]/+page.svelte @@ -2,7 +2,7 @@ import { page } from '$app/stores'; import { EVaultService } from '$lib/services/evaultService'; import { onMount } from 'svelte'; - import type { EVault } from '../../../api/evaults/+server'; + import type { EVault } from '../../api/evaults/+server'; let evault: EVault | null = null; let logs: string[] = []; @@ -11,12 +11,11 @@ let error = $state(null); let selectedTab = $state('logs'); - const namespace = $page.params.namespace; - const podName = $page.params.pod; + const evaultId = $page.params.evaultId; const fetchEVaultDetails = async () => { - if (!namespace || !podName) { - error = 'Invalid namespace or pod name'; + if (!evaultId) { + error = 'Invalid evault ID'; return; } @@ -24,9 +23,20 @@ isLoading = true; error = null; + // First get the evault info to display + const evaults = await EVaultService.getEVaults(); + evault = evaults.find((e) => e.id === evaultId || e.evault === evaultId) || null; + + if (!evault) { + error = 'eVault not found'; + isLoading = false; + return; + } + + // Fetch logs and details in parallel const [logsData, detailsData] = await Promise.all([ - EVaultService.getEVaultLogs(namespace, podName), - EVaultService.getEVaultMetrics(namespace, podName) + EVaultService.getEVaultLogs(evaultId, 100), + EVaultService.getEVaultDetails(evaultId) ]); logs = logsData; @@ -51,11 +61,16 @@

- eVault: {podName || 'Unknown'} + eVault: {evault?.name || evaultId || 'Unknown'}

- Namespace: {namespace || 'Unknown'} + ID: {evaultId || 'Unknown'}

+ {#if evault} +

+ URI: {evault.uri} +

+ {/if}
{#if isLoading} @@ -92,14 +107,6 @@ > Details -
@@ -107,7 +114,7 @@ {#if selectedTab === 'logs'}
-

Pod Logs

+

eVault Logs

{:else if selectedTab === 'details'}
-

Pod Details

+

eVault Details

-
{details?.podInfo ||
-							'No details available'}
+ {#if details} +
+
+
eName (w3id):
+
{details.ename || 'N/A'}
+
+
+
eVault ID:
+
{details.evault || 'N/A'}
+
+
+
URI:
+
{details.uri || 'N/A'}
+
+
+
Health Status:
+
+ + {details.healthStatus || 'Unknown'} + +
+
+ {#if details.originalUri && details.originalUri !== details.uri} +
+
Original URI:
+
{details.originalUri}
+
+
+
Resolved:
+
{details.resolved ? 'Yes' : 'No'}
+
+ {/if} +
+ {:else} +

No details available

+ {/if}
- {:else if selectedTab === 'metrics'} -
-

Pod Metrics

- {#if details?.metrics} -
-
{details.metrics}
-
- {:else} -
- Metrics not available. Make sure metrics-server is installed and running. -
- {/if} -
{/if} {/if} + diff --git a/infrastructure/control-panel/src/routes/monitoring/+page.svelte b/infrastructure/control-panel/src/routes/monitoring/+page.svelte index 539efbd9d..3ad8e02c9 100644 --- a/infrastructure/control-panel/src/routes/monitoring/+page.svelte +++ b/infrastructure/control-panel/src/routes/monitoring/+page.svelte @@ -1,13 +1,17 @@