From e04ae8b365005e8499ec775cba2bfd902eb8c23d Mon Sep 17 00:00:00 2001 From: SoSweetHam Date: Wed, 26 Nov 2025 15:07:11 +0530 Subject: [PATCH 1/8] fix: local dev qol --- db/init-multiple-databases.sh | 39 ++ dev-docker-compose.yaml | 13 + .../gen/android/.idea/codeStyles/Project.xml | 123 ++++ .../.idea/codeStyles/codeStyleConfig.xml | 5 + .../src-tauri/gen/android/.idea/kotlinc.xml | 2 +- .../src/routes/(auth)/onboarding/+page.svelte | 2 + .../evault-core/src/core/http/server.ts | 596 ++++++++++-------- platforms/registry/src/index.ts | 53 +- .../src/services/HealthCheckService.ts | 37 +- .../registry/src/services/VaultService.ts | 26 +- scripts/get-eid-wallet-token.sh | 47 ++ 11 files changed, 619 insertions(+), 324 deletions(-) create mode 100755 db/init-multiple-databases.sh create mode 100644 infrastructure/eid-wallet/src-tauri/gen/android/.idea/codeStyles/Project.xml create mode 100644 infrastructure/eid-wallet/src-tauri/gen/android/.idea/codeStyles/codeStyleConfig.xml create mode 100755 scripts/get-eid-wallet-token.sh diff --git a/db/init-multiple-databases.sh b/db/init-multiple-databases.sh new file mode 100755 index 00000000..299ad717 --- /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 f17b6dbc..ec998f71 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 diff --git a/infrastructure/eid-wallet/src-tauri/gen/android/.idea/codeStyles/Project.xml b/infrastructure/eid-wallet/src-tauri/gen/android/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..7643783a --- /dev/null +++ b/infrastructure/eid-wallet/src-tauri/gen/android/.idea/codeStyles/Project.xml @@ -0,0 +1,123 @@ + + + + + + + + + + \ No newline at end of file diff --git a/infrastructure/eid-wallet/src-tauri/gen/android/.idea/codeStyles/codeStyleConfig.xml b/infrastructure/eid-wallet/src-tauri/gen/android/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..79ee123c --- /dev/null +++ b/infrastructure/eid-wallet/src-tauri/gen/android/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/infrastructure/eid-wallet/src-tauri/gen/android/.idea/kotlinc.xml b/infrastructure/eid-wallet/src-tauri/gen/android/.idea/kotlinc.xml index 4cb74572..fe63bb67 100644 --- a/infrastructure/eid-wallet/src-tauri/gen/android/.idea/kotlinc.xml +++ b/infrastructure/eid-wallet/src-tauri/gen/android/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte b/infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte index 5027dc51..6628964f 100644 --- a/infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte +++ b/infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte @@ -198,6 +198,7 @@ onMount(async () => { new URL("/entropy", PUBLIC_REGISTRY_URL).toString(), ); const registryEntropy = entropyRes.data.token; + console.log("Registry entropy:", registryEntropy); const provisionRes = await axios.post( new URL("/provision", PUBLIC_PROVISIONER_URL).toString(), @@ -208,6 +209,7 @@ onMount(async () => { publicKey: await getApplicationPublicKey(), }, ); + console.log("Provision response:", provisionRes.data); if (!provisionRes.data?.success) { throw new Error("Invalid verification code"); diff --git a/infrastructure/evault-core/src/core/http/server.ts b/infrastructure/evault-core/src/core/http/server.ts index 898901de..c4cacb63 100644 --- a/infrastructure/evault-core/src/core/http/server.ts +++ b/infrastructure/evault-core/src/core/http/server.ts @@ -1,116 +1,125 @@ -import fastify, { FastifyInstance } from "fastify"; import swagger from "@fastify/swagger"; import swaggerUi from "@fastify/swagger-ui"; -import { WatcherRequest, TypedRequest, TypedReply } from "./types"; -import { ProvisioningService, ProvisionRequest } from "../../services/ProvisioningService"; -import { DbService } from "../db/db.service"; -import * as jose from "jose"; import axios from "axios"; +import fastify, { type FastifyInstance } from "fastify"; +import * as jose from "jose"; +import type { + ProvisionRequest, + ProvisioningService, +} from "../../services/ProvisioningService"; +import type { DbService } from "../db/db.service"; +import { type TypedReply, type TypedRequest, WatcherRequest } from "./types"; interface WatcherSignatureRequest { - w3id: string; - logEntryId: string; - proof: { - signature: string; - alg: string; - kid: string; - }; + w3id: string; + logEntryId: string; + proof: { + signature: string; + alg: string; + kid: string; + }; } export async function registerHttpRoutes( - server: FastifyInstance, - evault: any, // EVault instance to access publicKey - provisioningService?: ProvisioningService, - dbService?: DbService + server: FastifyInstance, + evault: any, // EVault instance to access publicKey + provisioningService?: ProvisioningService, + dbService?: DbService, ): Promise { - // Register Swagger - await server.register(swagger, { - swagger: { - info: { - title: "eVault Core API", - description: "API documentation for eVault Core HTTP endpoints", - version: "1.0.0", - }, - tags: [ - { name: "identity", description: "Identity related endpoints" }, - { - name: "watchers", - description: "Watcher signature related endpoints", - }, - { - name: "provisioning", - description: "eVault provisioning endpoints", + // Register Swagger + await server.register(swagger, { + swagger: { + info: { + title: "eVault Core API", + description: "API documentation for eVault Core HTTP endpoints", + version: "1.0.0", + }, + tags: [ + { name: "identity", description: "Identity related endpoints" }, + { + name: "watchers", + description: "Watcher signature related endpoints", + }, + { + name: "provisioning", + description: "eVault provisioning endpoints", + }, + ], }, - ], - }, - }); + }); - await server.register(swaggerUi, { - routePrefix: "/docs", - }); + await server.register(swaggerUi, { + routePrefix: "/docs", + }); - // Whois endpoint - returns both W3ID identifier and public key - server.get( - "/whois", - { - schema: { - tags: ["identity"], - description: "Get eVault W3ID identifier and public key", - headers: { - type: "object", - required: ["X-ENAME"], - properties: { - "X-ENAME": { type: "string" }, - }, - }, - response: { - 200: { - type: "object", - properties: { - w3id: { type: "string" }, - publicKey: { type: "string", nullable: true }, - }, - }, - 400: { - type: "object", - properties: { - error: { type: "string" }, + // Whois endpoint - returns both W3ID identifier and public key + server.get( + "/whois", + { + schema: { + tags: ["identity"], + description: "Get eVault W3ID identifier and public key", + headers: { + type: "object", + required: ["X-ENAME"], + properties: { + "X-ENAME": { type: "string" }, + }, + }, + response: { + 200: { + type: "object", + properties: { + w3id: { type: "string" }, + publicKey: { type: "string", nullable: true }, + }, + }, + 400: { + type: "object", + properties: { + error: { type: "string" }, + }, + }, + }, }, - }, }, - }, - }, - async (request: TypedRequest<{}>, reply: TypedReply) => { - const eName = request.headers["x-ename"] || request.headers["X-ENAME"]; - - if (!eName || typeof eName !== "string") { - return reply.status(400).send({ error: "X-ENAME header is required" }); - } - - // Get public key from database if dbService is available - let publicKey: string | null = null; - if (dbService) { - try { - publicKey = await dbService.getPublicKey(eName); - } catch (error) { - console.error("Error getting public key from database:", error); - // Continue with null publicKey - } - } - - const result = { - w3id: eName, - publicKey: publicKey, - }; - console.log("Whois request:", result); - return result; - } - ); + async (request: TypedRequest<{}>, reply: TypedReply) => { + const eName = + request.headers["x-ename"] || request.headers["X-ENAME"]; + + if (!eName || typeof eName !== "string") { + return reply + .status(400) + .send({ error: "X-ENAME header is required" }); + } + + // Get public key from database if dbService is available + let publicKey: string | null = null; + if (dbService) { + try { + publicKey = await dbService.getPublicKey(eName); + } catch (error) { + console.error( + "Error getting public key from database:", + error, + ); + // Continue with null publicKey + } + } + + const result = { + w3id: eName, + publicKey: publicKey, + }; + console.log("Whois request:", result); + return result; + }, + ); - // Watchers signature endpoint - DISABLED: Requires W3ID functionality - // This endpoint is disabled because the eVault no longer creates/manages W3IDs - // The private key is now stored on the user's phone - /* + // Watchers signature endpoint - DISABLED: Requires W3ID functionality + // This endpoint is disabled because the eVault no longer creates/manages W3IDs + // The private key is now stored on the user's phone + /* server.post<{ Body: WatcherSignatureRequest }>( "/watchers/sign", { @@ -199,10 +208,10 @@ export async function registerHttpRoutes( ); */ - // Watchers request endpoint - DISABLED: Requires W3ID functionality - // This endpoint is disabled because the eVault no longer creates/manages W3IDs - // The private key is now stored on the user's phone - /* + // Watchers request endpoint - DISABLED: Requires W3ID functionality + // This endpoint is disabled because the eVault no longer creates/manages W3IDs + // The private key is now stored on the user's phone + /* server.post<{ Body: WatcherRequest }>( "/watchers/request", { @@ -283,181 +292,228 @@ export async function registerHttpRoutes( ); */ - // Helper function to validate JWT token - async function validateToken(authHeader: string | null): Promise { - if (!authHeader || !authHeader.startsWith("Bearer ")) { - console.error("Token validation: Missing or invalid Authorization header format"); - return null; - } - - const token = authHeader.substring(7); // Remove 'Bearer ' prefix + // Helper function to validate JWT token + async function validateToken( + authHeader: string | null, + ): Promise { + if (!authHeader || !authHeader.startsWith("Bearer ")) { + console.error( + "Token validation: Missing or invalid Authorization header format", + ); + return null; + } - try { - // Try REGISTRY_URL first, fallback to PUBLIC_REGISTRY_URL - const registryUrl = process.env.REGISTRY_URL || process.env.PUBLIC_REGISTRY_URL; - if (!registryUrl) { - console.error("Token validation: REGISTRY_URL or PUBLIC_REGISTRY_URL is not set"); - return null; - } + const token = authHeader.substring(7); // Remove 'Bearer ' prefix - const jwksUrl = new URL(`/.well-known/jwks.json`, registryUrl).toString(); - console.log(`Token validation: Fetching JWKS from ${jwksUrl}`); - - const jwksResponse = await axios.get(jwksUrl, { - timeout: 5000, - }); - - console.log(`Token validation: JWKS response keys count: ${jwksResponse.data?.keys?.length || 0}`); - - const JWKS = jose.createLocalJWKSet(jwksResponse.data); - - // Decode token header to see what kid it's using - const decodedHeader = jose.decodeProtectedHeader(token); - console.log(`Token validation: Token header - alg: ${decodedHeader.alg}, kid: ${decodedHeader.kid}`); - - const { payload } = await jose.jwtVerify(token, JWKS); - - console.log(`Token validation: Token verified successfully, payload:`, payload); - return payload; - } catch (error: any) { - console.error("Token validation failed:", error.message || error); - if (error.code) { - console.error(`Token validation error code: ${error.code}`); - } - if (error.response) { - console.error(`Token validation HTTP error: ${error.response.status} - ${error.response.statusText}`); - } - if (error.cause) { - console.error(`Token validation error cause:`, error.cause); - } - return null; + try { + // Try REGISTRY_URL first, fallback to PUBLIC_REGISTRY_URL + const registryUrl = + process.env.REGISTRY_URL || process.env.PUBLIC_REGISTRY_URL; + if (!registryUrl) { + console.error( + "Token validation: REGISTRY_URL or PUBLIC_REGISTRY_URL is not set", + ); + return null; + } + + const jwksUrl = new URL( + "/.well-known/jwks.json", + registryUrl, + ).toString(); + console.log(`Token validation: Fetching JWKS from ${jwksUrl}`); + + const jwksResponse = await axios.get(jwksUrl, { + timeout: 5000, + }); + + console.log( + `Token validation: JWKS response keys count: ${jwksResponse.data?.keys?.length || 0}`, + ); + + const JWKS = jose.createLocalJWKSet(jwksResponse.data); + + // Decode token header to see what kid it's using + const decodedHeader = jose.decodeProtectedHeader(token); + console.log( + `Token validation: Token header - alg: ${decodedHeader.alg}, kid: ${decodedHeader.kid}`, + ); + + const { payload } = await jose.jwtVerify(token, JWKS); + + console.log( + `Token validation: Token verified successfully, payload:`, + payload, + ); + return payload; + } catch (error: any) { + console.error("Token validation failed:", error.message || error); + if (error.code) { + console.error(`Token validation error code: ${error.code}`); + } + if (error.response) { + console.error( + `Token validation HTTP error: ${error.response.status} - ${error.response.statusText}`, + ); + } + if (error.cause) { + console.error(`Token validation error cause:`, error.cause); + } + return null; + } } - } - // PATCH endpoint to save public key - server.patch<{ Body: { publicKey: string } }>( - "/public-key", - { - schema: { - tags: ["identity"], - description: "Save public key for a user's eName", - headers: { - type: "object", - required: ["X-ENAME", "Authorization"], - properties: { - "X-ENAME": { type: "string" }, - "Authorization": { type: "string" }, - }, - }, - body: { - type: "object", - required: ["publicKey"], - properties: { - publicKey: { type: "string" }, - }, - }, - response: { - 200: { - type: "object", - properties: { - success: { type: "boolean" }, - message: { type: "string" }, - }, - }, - 400: { - type: "object", - properties: { - error: { type: "string" }, - }, - }, - 401: { - type: "object", - properties: { - error: { type: "string" }, + // PATCH endpoint to save public key + server.patch<{ Body: { publicKey: string } }>( + "/public-key", + { + schema: { + tags: ["identity"], + description: "Save public key for a user's eName", + headers: { + type: "object", + required: ["X-ENAME", "Authorization"], + properties: { + "X-ENAME": { type: "string" }, + Authorization: { type: "string" }, + }, + }, + body: { + type: "object", + required: ["publicKey"], + properties: { + publicKey: { type: "string" }, + }, + }, + response: { + 200: { + type: "object", + properties: { + success: { type: "boolean" }, + message: { type: "string" }, + }, + }, + 400: { + type: "object", + properties: { + error: { type: "string" }, + }, + }, + 401: { + type: "object", + properties: { + error: { type: "string" }, + }, + }, + }, }, - }, }, - }, - }, - async (request: TypedRequest<{ publicKey: string }>, reply: TypedReply) => { - const eName = request.headers["x-ename"] || request.headers["X-ENAME"]; - - if (!eName || typeof eName !== "string") { - return reply.status(400).send({ error: "X-ENAME header is required" }); - } - - const authHeader = request.headers.authorization || request.headers["Authorization"]; - const tokenPayload = await validateToken( - typeof authHeader === "string" ? authHeader : null - ); - - if (!tokenPayload) { - return reply.status(401).send({ error: "Invalid or missing authentication token" }); - } - - const { publicKey } = request.body; - if (!publicKey) { - return reply.status(400).send({ error: "publicKey is required in request body" }); - } - - if (!dbService) { - return reply.status(500).send({ error: "Database service not available" }); - } - - try { - await dbService.setPublicKey(eName, publicKey); - return { - success: true, - message: "Public key saved successfully", - }; - } catch (error) { - console.error("Error saving public key:", error); - return reply.status(500).send({ - error: error instanceof Error ? error.message : "Failed to save public key", - }); - } - } - ); + async ( + request: TypedRequest<{ publicKey: string }>, + reply: TypedReply, + ) => { + const eName = + request.headers["x-ename"] || request.headers["X-ENAME"]; + + if (!eName || typeof eName !== "string") { + return reply + .status(400) + .send({ error: "X-ENAME header is required" }); + } + + const authHeader = + request.headers.authorization || + request.headers["Authorization"]; + const tokenPayload = await validateToken( + typeof authHeader === "string" ? authHeader : null, + ); + + if (!tokenPayload) { + return reply + .status(401) + .send({ error: "Invalid or missing authentication token" }); + } + + const { publicKey } = request.body; + if (!publicKey) { + return reply + .status(400) + .send({ error: "publicKey is required in request body" }); + } + + if (!dbService) { + return reply + .status(500) + .send({ error: "Database service not available" }); + } + + try { + await dbService.setPublicKey(eName, publicKey); + return { + success: true, + message: "Public key saved successfully", + }; + } catch (error) { + console.error("Error saving public key:", error); + return reply.status(500).send({ + error: + error instanceof Error + ? error.message + : "Failed to save public key", + }); + } + }, + ); - // Provision eVault endpoint - if (provisioningService) { - server.post<{ Body: ProvisionRequest }>( - "/provision", - { - schema: { - tags: ["provisioning"], - description: "Provision a new eVault instance (logical only, no infrastructure)", - body: { - type: "object", - required: ["registryEntropy", "namespace", "verificationId", "publicKey"], - properties: { - registryEntropy: { type: "string" }, - namespace: { type: "string" }, - verificationId: { type: "string" }, - publicKey: { type: "string" }, + // Provision eVault endpoint + if (provisioningService) { + server.post<{ Body: ProvisionRequest }>( + "/provision", + { + schema: { + tags: ["provisioning"], + description: + "Provision a new eVault instance (logical only, no infrastructure)", + body: { + type: "object", + required: [ + "registryEntropy", + "namespace", + "verificationId", + "publicKey", + ], + properties: { + registryEntropy: { type: "string" }, + namespace: { type: "string" }, + verificationId: { type: "string" }, + publicKey: { type: "string" }, + }, + }, + response: { + 200: { + type: "object", + properties: { + success: { type: "boolean" }, + w3id: { type: "string" }, + uri: { type: "string" }, + message: { type: "string" }, + error: { type: "string" }, + }, + }, + }, + }, }, - }, - response: { - 200: { - type: "object", - properties: { - success: { type: "boolean" }, - w3id: { type: "string" }, - uri: { type: "string" }, - message: { type: "string" }, - error: { type: "string" }, - }, + async ( + request: TypedRequest, + reply: TypedReply, + ) => { + const result = await provisioningService.provisionEVault( + request.body, + ); + if (!result.success) { + return reply.status(500).send(result); + } + return result; }, - }, - }, - }, - async (request: TypedRequest, reply: TypedReply) => { - const result = await provisioningService.provisionEVault(request.body); - if (!result.success) { - return reply.status(500).send(result); - } - return result; - } - ); - } + ); + } } diff --git a/platforms/registry/src/index.ts b/platforms/registry/src/index.ts index 0bf76ede..04eb6c03 100644 --- a/platforms/registry/src/index.ts +++ b/platforms/registry/src/index.ts @@ -1,19 +1,22 @@ -import fastify from "fastify"; -import { generateEntropy, generatePlatformToken, getJWK } from "./jwt"; -import dotenv from "dotenv"; import path from "node:path"; +import cors from "@fastify/cors"; +import dotenv from "dotenv"; +import fastify from "fastify"; import { AppDataSource } from "./config/database"; -import { VaultService } from "./services/VaultService"; +import { generateEntropy, generatePlatformToken, getJWK } from "./jwt"; import { UriResolutionService } from "./services/UriResolutionService"; -import cors from "@fastify/cors"; +import { VaultService } from "./services/VaultService"; import fs from "node:fs"; function loadMotdJSON() { - const motdJSON = fs.readFileSync(path.resolve(__dirname, "../motd.json"), "utf8"); + const motdJSON = fs.readFileSync( + path.resolve(__dirname, "../motd.json"), + "utf8", + ); return JSON.parse(motdJSON) as { - status: "up" | "maintenance" - message: string + status: "up" | "maintenance"; + message: string; }; } @@ -41,7 +44,10 @@ const initializeDatabase = async () => { await AppDataSource.initialize(); server.log.info("Database connection initialized"); } catch (error) { - server.log.error({message: "Error during database initialization", detail: error}); + server.log.error({ + message: "Error during database initialization", + detail: error, + }); process.exit(1); } }; @@ -102,6 +108,7 @@ server.post( // Generate and return a signed JWT with entropy server.get("/entropy", async (request, reply) => { + console.log("Generating entropy"); try { const token = await generateEntropy(); return { token }; @@ -123,21 +130,19 @@ server.post("/platforms/certification", async (request, reply) => { }); server.get("/platforms", async (request, reply) => { - const platforms = [ - process.env.PUBLIC_PICTIQUE_BASE_URL, - process.env.PUBLIC_BLABSY_BASE_URL, - process.env.PUBLIC_GROUP_CHARTER_BASE_URL, - process.env.PUBLIC_CERBERUS_BASE_URL, + const platforms = [ + process.env.PUBLIC_PICTIQUE_BASE_URL, + process.env.PUBLIC_BLABSY_BASE_URL, + process.env.PUBLIC_GROUP_CHARTER_BASE_URL, + process.env.PUBLIC_CERBERUS_BASE_URL, process.env.PUBLIC_EVOTING_BASE_URL, process.env.VITE_DREAMSYNC_BASE_URL, - process.env.VITE_EREPUTATION_BASE_URL - ] + process.env.VITE_EREPUTATION_BASE_URL, + ]; - return platforms + return platforms; }); - - // Expose the JWK used for signing server.get("/.well-known/jwks.json", async (request, reply) => { try { @@ -184,11 +189,13 @@ server.get("/resolve", async (request, reply) => { server.get("/list", async (request, reply) => { try { const vaults = await vaultService.findAll(); - + // Resolve URIs for all vaults const resolvedVaults = await Promise.all( vaults.map(async (vault) => { - const resolvedUri = await uriResolutionService.resolveUri(vault.uri); + const resolvedUri = await uriResolutionService.resolveUri( + vault.uri, + ); return { ename: vault.ename, uri: resolvedUri, @@ -196,9 +203,9 @@ server.get("/list", async (request, reply) => { originalUri: vault.uri, resolved: resolvedUri !== vault.uri, }; - }) + }), ); - + return resolvedVaults; } catch (error) { server.log.error(error); diff --git a/platforms/registry/src/services/HealthCheckService.ts b/platforms/registry/src/services/HealthCheckService.ts index 55e36631..0a0cdb2c 100644 --- a/platforms/registry/src/services/HealthCheckService.ts +++ b/platforms/registry/src/services/HealthCheckService.ts @@ -1,4 +1,4 @@ -import axios from 'axios'; +import axios from "axios"; export class HealthCheckService { private readonly timeout = 5000; // 5 second timeout @@ -10,7 +10,7 @@ export class HealthCheckService { try { // Parse the URI to extract IP:PORT const { ip, port } = this.parseUri(uri); - + if (!ip || !port) { console.log(`Invalid URI format: ${uri}`); return false; @@ -18,20 +18,22 @@ export class HealthCheckService { // Construct the whois endpoint URL const whoisUrl = `http://${ip}:${port}/whois`; - + console.log(`Health checking: ${whoisUrl}`); - + // Make a request to the whois endpoint with timeout const response = await axios.get(whoisUrl, { timeout: this.timeout, - validateStatus: (status) => status < 500 // Accept any status < 500 as "reachable" + validateStatus: (status) => status < 500, // Accept any status < 500 as "reachable" }); - + console.log(`Health check passed for ${uri}: ${response.status}`); return true; - } catch (error) { - console.log(`Health check failed for ${uri}:`, error instanceof Error ? error.message : 'Unknown error'); + console.log( + `Health check failed for ${uri}:`, + error instanceof Error ? error.message : "Unknown error", + ); return false; } } @@ -43,21 +45,21 @@ export class HealthCheckService { private parseUri(uri: string): { ip: string | null; port: string | null } { try { // Remove protocol if present - const cleanUri = uri.replace(/^https?:\/\//, ''); - + const cleanUri = uri.replace(/^https?:\/\//, ""); + // Split by colon to get IP and PORT - const parts = cleanUri.split(':'); - + const parts = cleanUri.split(":"); + if (parts.length === 2) { const ip = parts[0]; const port = parts[1]; - + // Basic validation if (this.isValidIp(ip) && this.isValidPort(port)) { return { ip, port }; } } - + return { ip: null, port: null }; } catch (error) { console.error(`Error parsing URI ${uri}:`, error); @@ -70,7 +72,8 @@ export class HealthCheckService { */ private isValidIp(ip: string): boolean { // Simple regex for IP validation - const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; + const ipRegex = + /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; return ipRegex.test(ip); } @@ -78,7 +81,7 @@ export class HealthCheckService { * Basic port validation */ private isValidPort(port: string): boolean { - const portNum = parseInt(port, 10); + const portNum = Number.parseInt(port, 10); return portNum >= 1 && portNum <= 65535; } -} \ No newline at end of file +} diff --git a/platforms/registry/src/services/VaultService.ts b/platforms/registry/src/services/VaultService.ts index 899f6fcc..36886de1 100644 --- a/platforms/registry/src/services/VaultService.ts +++ b/platforms/registry/src/services/VaultService.ts @@ -1,32 +1,32 @@ -import { Repository } from "typeorm" -import { Vault} from "../entities/Vault" +import type { Repository } from "typeorm"; +import type { Vault } from "../entities/Vault"; export class VaultService { constructor(private readonly serviceRepository: Repository) {} async create(ename: string, uri: string, evault: string): Promise { - const service = this.serviceRepository.create({ ename, uri, evault }) - return await this.serviceRepository.save(service) + const service = this.serviceRepository.create({ ename, uri, evault }); + return await this.serviceRepository.save(service); } async findAll(): Promise { - return await this.serviceRepository.find() + return await this.serviceRepository.find(); } - async findOne(id: number): Promise { - return await this.serviceRepository.findOneBy({ id }) + async findOne(id: number): Promise { + return await this.serviceRepository.findOneBy({ id }); } async findByEname(ename: string): Promise { - return await this.serviceRepository.findOneBy({ ename }) + return await this.serviceRepository.findOneBy({ ename }); } - async update(id: number, data: Partial): Promise { - await this.serviceRepository.update(id, data) - return await this.findOne(id) + async update(id: number, data: Partial): Promise { + await this.serviceRepository.update(id, data); + return await this.findOne(id); } async delete(id: number): Promise { - await this.serviceRepository.delete(id) + await this.serviceRepository.delete(id); } -} \ No newline at end of file +} diff --git a/scripts/get-eid-wallet-token.sh b/scripts/get-eid-wallet-token.sh new file mode 100755 index 00000000..6f2f845b --- /dev/null +++ b/scripts/get-eid-wallet-token.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# Get the registry URL from environment or use default +REGISTRY_URL="${PUBLIC_REGISTRY_URL:-http://localhost:4321}" + +# Request platform token for eid-wallet +echo "Requesting platform token from registry at $REGISTRY_URL..." +echo "" + +RESPONSE=$(curl -s -X POST "$REGISTRY_URL/platforms/certification" \ + -H "Content-Type: application/json" \ + -d '{"platform": "eid-wallet"}') + +# Check if curl was successful +if [ $? -ne 0 ]; then + echo "Error: Failed to connect to registry at $REGISTRY_URL" + exit 1 +fi + +# Extract token using jq if available, otherwise use grep/sed +if command -v jq &> /dev/null; then + TOKEN=$(echo "$RESPONSE" | jq -r '.token') + if [ "$TOKEN" = "null" ] || [ -z "$TOKEN" ]; then + echo "Error: Failed to get token from response:" + echo "$RESPONSE" + exit 1 + fi +else + # Fallback: extract token manually + TOKEN=$(echo "$RESPONSE" | grep -o '"token":"[^"]*"' | cut -d'"' -f4) + if [ -z "$TOKEN" ]; then + echo "Error: Failed to parse token from response:" + echo "$RESPONSE" + exit 1 + fi +fi + +echo "Token obtained successfully!" +echo "" +echo "Add this to your .env file:" +echo "PUBLIC_EID_WALLET_TOKEN=\"$TOKEN\"" +echo "" +echo "Or use it directly:" +echo "$TOKEN" + + + From e7c68b0dfd3db0095cd0a887f89f4ef8c3b923d9 Mon Sep 17 00:00:00 2001 From: SoSweetHam Date: Wed, 26 Nov 2025 15:14:00 +0530 Subject: [PATCH 2/8] fix: env example --- .env.example | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.env.example b/.env.example index d81932bd..b31437a3 100644 --- a/.env.example +++ b/.env.example @@ -71,3 +71,5 @@ 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 From 9f5623468a4ee7021731aeffb43278ca33f11163 Mon Sep 17 00:00:00 2001 From: SoSweetHam Date: Wed, 26 Nov 2025 15:32:00 +0530 Subject: [PATCH 3/8] fix: eid wallet check --- .../eid-wallet/src/lib/global/controllers/evault.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/infrastructure/eid-wallet/src/lib/global/controllers/evault.ts b/infrastructure/eid-wallet/src/lib/global/controllers/evault.ts index 917f6ab4..349f9618 100644 --- a/infrastructure/eid-wallet/src/lib/global/controllers/evault.ts +++ b/infrastructure/eid-wallet/src/lib/global/controllers/evault.ts @@ -1,14 +1,14 @@ import { - PUBLIC_REGISTRY_URL, - PUBLIC_PROVISIONER_URL, PUBLIC_EID_WALLET_TOKEN, + PUBLIC_PROVISIONER_URL, + PUBLIC_REGISTRY_URL, } from "$env/static/public"; import type { Store } from "@tauri-apps/plugin-store"; import axios from "axios"; import { GraphQLClient } from "graphql-request"; import NotificationService from "../../services/NotificationService"; -import type { UserController } from "./user"; import type { KeyService } from "./key"; +import type { UserController } from "./user"; const STORE_META_ENVELOPE = ` mutation StoreMetaEnvelope($input: MetaEnvelopeInput!) { @@ -176,7 +176,7 @@ export class VaultController { }; if (authToken) { - headers["Authorization"] = `Bearer ${authToken}`; + headers.Authorization = `Bearer ${authToken}`; } await axios.patch(patchUrl, { publicKey }, { headers }); From b7fd8fc2ae6659719f63863dfa15858ab607e322 Mon Sep 17 00:00:00 2001 From: SoSweetHam Date: Sat, 29 Nov 2025 09:00:09 +0530 Subject: [PATCH 4/8] refactor:control-panel --- dev-docker-compose.yaml | 12 + .../src/lib/components/EVaultList.svelte | 36 +-- .../src/lib/services/evaultService.ts | 25 +- .../control-panel/src/lib/services/loki.ts | 43 +++ .../src/lib/services/registry.ts | 24 ++ .../control-panel/src/routes/+page.svelte | 50 +-- .../src/routes/api/evaults/+server.ts | 290 +++--------------- .../api/evaults/[evaultId]/details/+server.ts | 45 +++ .../api/evaults/[evaultId]/logs/+server.ts | 47 +++ .../[namespace]/[pod]/details/+server.ts | 41 --- .../evaults/[namespace]/[pod]/logs/+server.ts | 77 ----- .../[namespace]/[pod]/metrics/+server.ts | 182 ----------- .../[pod] => [evaultId]}/+page.svelte | 114 ++++--- 13 files changed, 345 insertions(+), 641 deletions(-) create mode 100644 infrastructure/control-panel/src/routes/api/evaults/[evaultId]/details/+server.ts create mode 100644 infrastructure/control-panel/src/routes/api/evaults/[evaultId]/logs/+server.ts delete mode 100644 infrastructure/control-panel/src/routes/api/evaults/[namespace]/[pod]/details/+server.ts delete mode 100644 infrastructure/control-panel/src/routes/api/evaults/[namespace]/[pod]/logs/+server.ts delete mode 100644 infrastructure/control-panel/src/routes/api/evaults/[namespace]/[pod]/metrics/+server.ts rename infrastructure/control-panel/src/routes/evaults/{[namespace]/[pod] => [evaultId]}/+page.svelte (51%) diff --git a/dev-docker-compose.yaml b/dev-docker-compose.yaml index f17b6dbc..f2c5173f 100644 --- a/dev-docker-compose.yaml +++ b/dev-docker-compose.yaml @@ -557,6 +557,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 0d141d0f..8656c638 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 5e11675c..216b0f48 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 34cb6f1a..361249dc 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 99b1c8b0..0b1a33f3 100644 --- a/infrastructure/control-panel/src/lib/services/registry.ts +++ b/infrastructure/control-panel/src/lib/services/registry.ts @@ -7,6 +7,14 @@ export interface Platform { uptime: string; } +export interface RegistryVault { + ename: string; + uri: string; + evault: string; + originalUri?: string; + resolved?: boolean; +} + export class RegistryService { private baseUrl: string; @@ -14,6 +22,22 @@ export class RegistryService { 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`); diff --git a/infrastructure/control-panel/src/routes/+page.svelte b/infrastructure/control-panel/src/routes/+page.svelte index 34a76626..fa5637f2 100644 --- a/infrastructure/control-panel/src/routes/+page.svelte +++ b/infrastructure/control-panel/src/routes/+page.svelte @@ -30,13 +30,14 @@ let selectedPlatforms = $state([]); // Filtered data for search - let filteredEVaults = $derived(() => { + let filteredEVaults = $derived(() => { 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,23 @@ let mappedEVaultsData = $derived(() => { const paginated = paginatedEVaults(); return paginated.map((evault) => ({ - eName: { + Name: { type: 'text', - value: evault.evaultId, + value: evault.name || evault.ename || evault.evault, className: 'cursor-pointer text-blue-600 hover:text-blue-800 hover:underline' }, - Uptime: { + eName: { type: 'text', - value: evault.age + value: evault.ename || 'N/A' }, - IP: { + Status: { type: 'text', - value: evault.ip + value: evault.status || 'Unknown' }, 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 } })); @@ -142,14 +143,16 @@ } 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)) + .map((id) => evaults.find((e) => (e.evault || e.ename || e.id) === id)) .filter(Boolean); sessionStorage.setItem('selectedEVaults', JSON.stringify(selectedEVaultData)); } @@ -189,8 +192,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 @@ -290,7 +293,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,7 +352,9 @@ 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)}`); } } @@ -403,9 +408,10 @@ onSelectionChange={handleEVaultSelectionChange} onSelectAllChange={handleSelectAllEVaults} selectedIndices={paginatedEVaults() - .map((evault, index) => - selectedEVaults.includes(evault.evaultId) ? index : -1 - ) + .map((evault, index) => { + const evaultId = evault.evault || evault.ename || evault.id; + return selectedEVaults.includes(evaultId) ? index : -1; + }) .filter((index) => index !== -1)} /> diff --git a/infrastructure/control-panel/src/routes/api/evaults/+server.ts b/infrastructure/control-panel/src/routes/api/evaults/+server.ts index 225f1557..3e9fbaae 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 00000000..13469106 --- /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 00000000..e4d4a49c --- /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 ceb226ec..00000000 --- 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 3ba0d93b..00000000 --- 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 431f3643..00000000 --- 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 1eccdfc2..09cbbf8e 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} + From c9362ae4c83c609b2fdb835ef340bafc4d7d2b71 Mon Sep 17 00:00:00 2001 From: SoSweetHam Date: Sat, 29 Nov 2025 13:44:04 +0530 Subject: [PATCH 5/8] refactor: make monitoring functioning again --- .../src/lib/services/registry.ts | 257 ++++++++++-------- .../src/routes/monitoring/+page.svelte | 28 +- infrastructure/control-panel/svelte.config.js | 18 +- infrastructure/control-panel/vite.config.ts | 33 ++- 4 files changed, 202 insertions(+), 134 deletions(-) diff --git a/infrastructure/control-panel/src/lib/services/registry.ts b/infrastructure/control-panel/src/lib/services/registry.ts index 0b1a33f3..28696b75 100644 --- a/infrastructure/control-panel/src/lib/services/registry.ts +++ b/infrastructure/control-panel/src/lib/services/registry.ts @@ -1,119 +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; + 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 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[] = 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/monitoring/+page.svelte b/infrastructure/control-panel/src/routes/monitoring/+page.svelte index 539efbd9..90529d98 100644 --- a/infrastructure/control-panel/src/routes/monitoring/+page.svelte +++ b/infrastructure/control-panel/src/routes/monitoring/+page.svelte @@ -100,8 +100,8 @@ id: `evault-${index + 1}`, position: { x: 200, y: 500 + index * 180 }, data: { - label: evault.evaultId || evault.name || 'eVault', - subLabel: evault.serviceUrl || evault.ip || 'Unknown', + label: evault.name || evault.ename || evault.evault || evault.id || 'eVault', + subLabel: evault.uri || evault.serviceUrl || 'Unknown', type: 'evault', selected: false }, @@ -506,12 +506,19 @@ const cleanW3id = w3id.replace('@', ''); console.log('Cleaned w3id:', cleanW3id); - // Since evaultId is the same as w3id (without @), prioritize that match + // Match against ename (w3id), evault, or id fields const index = selectedEVaults.findIndex((e) => { - const matches = e.evaultId === cleanW3id; + // Check if ename (w3id) matches (with or without @) + const enameMatch = e.ename && (e.ename === cleanW3id || e.ename === w3id || e.ename.replace('@', '') === cleanW3id); + // Check if evault field matches + const evaultMatch = e.evault && e.evault === cleanW3id; + // Check if id field matches + const idMatch = e.id && e.id === cleanW3id; + + const matches = enameMatch || evaultMatch || idMatch; if (matches) { - console.log('Found matching eVault by evaultId:', e); + console.log('Found matching eVault:', e); } return matches; @@ -523,12 +530,17 @@ selectedEVaultsLength: selectedEVaults.length }); - // If no match found, log all available evaultIds for debugging + // If no match found, log all available evault identifiers for debugging if (index === -1) { console.log('No match found for cleaned w3id:', cleanW3id); - console.log('Available evaultIds:'); + console.log('Available evault identifiers:'); selectedEVaults.forEach((evault, i) => { - console.log(`eVault ${i}: evaultId = "${evault.evaultId}"`); + console.log(`eVault ${i}:`, { + ename: evault.ename, + evault: evault.evault, + id: evault.id, + name: evault.name + }); }); // Return -1 to indicate no match found diff --git a/infrastructure/control-panel/svelte.config.js b/infrastructure/control-panel/svelte.config.js index 03c17f28..9b5edcd7 100644 --- a/infrastructure/control-panel/svelte.config.js +++ b/infrastructure/control-panel/svelte.config.js @@ -1,12 +1,18 @@ -import adapter from '@sveltejs/adapter-node'; -import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; +import adapter from "@sveltejs/adapter-node"; +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; /** @type {import('@sveltejs/kit').Config} */ const config = { - // Consult https://svelte.dev/docs/kit/integrations - // for more information about preprocessors - preprocess: vitePreprocess(), - kit: { adapter: adapter() } + // Consult https://svelte.dev/docs/kit/integrations + // for more information about preprocessors + preprocess: vitePreprocess(), + kit: { + adapter: adapter(), + // Load .env from the root of the monorepo (parent directory) + env: { + dir: "../../", + }, + }, }; export default config; diff --git a/infrastructure/control-panel/vite.config.ts b/infrastructure/control-panel/vite.config.ts index 8ad9f41b..da537e98 100644 --- a/infrastructure/control-panel/vite.config.ts +++ b/infrastructure/control-panel/vite.config.ts @@ -1,15 +1,24 @@ -import { sveltekit } from '@sveltejs/kit/vite'; -import tailwindcss from '@tailwindcss/vite'; -import { defineConfig } from 'vite'; +import { dirname, resolve } from "path"; +import { fileURLToPath } from "url"; +import { sveltekit } from "@sveltejs/kit/vite"; +import tailwindcss from "@tailwindcss/vite"; +import { defineConfig } from "vite"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); export default defineConfig({ - plugins: [tailwindcss(), sveltekit()], - optimizeDeps: { - exclude: ['lowdb', 'steno'] - }, - build: { - rollupOptions: { - external: ['lowdb', 'lowdb/node', 'steno'] - } - } + plugins: [tailwindcss(), sveltekit()], + // Load .env from the root of the monorepo (parent directory) + envDir: resolve(__dirname, "../.."), + // PUBLIC_ prefix is for client-side env vars, server-side vars (like LOKI_*) are loaded automatically + envPrefix: "PUBLIC_", + optimizeDeps: { + exclude: ["lowdb", "steno"], + }, + build: { + rollupOptions: { + external: ["lowdb", "lowdb/node", "steno"], + }, + }, }); From ad0e3c3c52d92c95f278af9faeebbc8552961846 Mon Sep 17 00:00:00 2001 From: SoSweetHam Date: Sat, 29 Nov 2025 13:57:25 +0530 Subject: [PATCH 6/8] fix: make eVault details visible --- .../control-panel/src/routes/+page.svelte | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/infrastructure/control-panel/src/routes/+page.svelte b/infrastructure/control-panel/src/routes/+page.svelte index fa5637f2..34c14a3c 100644 --- a/infrastructure/control-panel/src/routes/+page.svelte +++ b/infrastructure/control-panel/src/routes/+page.svelte @@ -1,13 +1,13 @@