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}
-
- {: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 @@