diff --git a/.env.example b/.env.example
index d81932bd..dc88458d 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 00000000..e69de29b
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..28f8b5ce 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 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..28696b75 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/+page.svelte b/infrastructure/control-panel/src/routes/+page.svelte
index 34a76626..34c14a3c 100644
--- a/infrastructure/control-panel/src/routes/+page.svelte
+++ b/infrastructure/control-panel/src/routes/+page.svelte
@@ -1,13 +1,13 @@