Skip to content

Commit 570904e

Browse files
authored
Feat/group evault visualization and limitations (#325)
* chore: restrict eVoting to chartered groups * fix: provision * chore: add migrations * chore: add ename migrations * chore: store ename of group * group-evaults * chore: fix group-evault rendering * feat: eVault visualization on control-panel * feat: cp quick refresh logic
1 parent 54482b8 commit 570904e

File tree

78 files changed

+2545
-55
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+2545
-55
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,6 @@ yarn-error.log*
4040
# Misc
4141
.DS_Store
4242
*.pem
43+
44+
45+
evault-cache.json

infrastructure/control-panel/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"flowbite": "^3.1.2",
5151
"flowbite-svelte": "^1.10.7",
5252
"flowbite-svelte-icons": "^2.2.1",
53+
"lowdb": "^9.1.0",
5354
"lucide-svelte": "^0.539.0",
5455
"tailwind-merge": "^3.0.2"
5556
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<script lang="ts">
2+
import { onMount } from 'svelte';
3+
import { EVaultService } from '../services/evaultService';
4+
import type { EVault } from '../../routes/api/evaults/+server';
5+
6+
let evaults: EVault[] = [];
7+
let loading = false;
8+
let cacheStatus: any = null;
9+
let lastRefresh = '';
10+
11+
// Load eVaults on component mount
12+
onMount(async () => {
13+
await loadEVaults();
14+
// Get cache status for debugging
15+
cacheStatus = EVaultService.getCacheStatus();
16+
});
17+
18+
async function loadEVaults() {
19+
loading = true;
20+
try {
21+
// This will return cached data immediately and refresh in background
22+
evaults = await EVaultService.getEVaults();
23+
lastRefresh = new Date().toLocaleTimeString();
24+
} catch (error) {
25+
console.error('Failed to load eVaults:', error);
26+
} finally {
27+
loading = false;
28+
}
29+
}
30+
31+
async function forceRefresh() {
32+
loading = true;
33+
try {
34+
// Force refresh and get fresh data
35+
evaults = await EVaultService.forceRefresh();
36+
lastRefresh = new Date().toLocaleTimeString();
37+
// Update cache status
38+
cacheStatus = EVaultService.getCacheStatus();
39+
} catch (error) {
40+
console.error('Failed to force refresh:', error);
41+
} finally {
42+
loading = false;
43+
}
44+
}
45+
46+
async function clearCache() {
47+
await EVaultService.clearCache();
48+
cacheStatus = EVaultService.getCacheStatus();
49+
// Reload data
50+
await loadEVaults();
51+
}
52+
</script>
53+
54+
<div class="p-6">
55+
<div class="mb-6 flex items-center justify-between">
56+
<h1 class="text-2xl font-bold">eVault Control Panel</h1>
57+
<div class="flex gap-2">
58+
<button
59+
on:click={loadEVaults}
60+
disabled={loading}
61+
class="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 disabled:opacity-50"
62+
>
63+
{loading ? 'Loading...' : 'Refresh'}
64+
</button>
65+
<button
66+
on:click={forceRefresh}
67+
disabled={loading}
68+
class="rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600 disabled:opacity-50"
69+
>
70+
Force Refresh
71+
</button>
72+
<button
73+
on:click={clearCache}
74+
class="rounded bg-red-500 px-4 py-2 text-white hover:bg-red-600"
75+
>
76+
Clear Cache
77+
</button>
78+
</div>
79+
</div>
80+
81+
<!-- Cache Status -->
82+
{#if cacheStatus}
83+
<div class="mb-6 rounded-lg bg-gray-100 p-4">
84+
<h3 class="mb-2 font-semibold">Cache Status</h3>
85+
<div class="grid grid-cols-3 gap-4 text-sm">
86+
<div>
87+
<strong>Last Updated:</strong><br />
88+
{new Date(cacheStatus.lastUpdated).toLocaleString()}
89+
</div>
90+
<div>
91+
<strong>Status:</strong><br />
92+
<span
93+
class="rounded px-2 py-1 text-xs {cacheStatus.isStale
94+
? 'bg-yellow-200 text-yellow-800'
95+
: 'bg-green-200 text-green-800'}"
96+
>
97+
{cacheStatus.isStale ? 'Stale' : 'Fresh'}
98+
</span>
99+
</div>
100+
<div>
101+
<strong>Cached Items:</strong><br />
102+
{cacheStatus.count} eVaults
103+
</div>
104+
</div>
105+
</div>
106+
{/if}
107+
108+
<!-- Last Refresh Info -->
109+
{#if lastRefresh}
110+
<div class="mb-4 text-sm text-gray-600">
111+
Last refresh: {lastRefresh}
112+
</div>
113+
{/if}
114+
115+
<!-- eVault List -->
116+
<div class="grid gap-4">
117+
{#if evaults.length === 0}
118+
<div class="py-8 text-center text-gray-500">
119+
{loading ? 'Loading eVaults...' : 'No eVaults found'}
120+
</div>
121+
{:else}
122+
{#each evaults as evault}
123+
<div class="rounded-lg border p-4 transition-shadow hover:shadow-md">
124+
<h3 class="text-lg font-semibold">{evault.name || 'Unnamed eVault'}</h3>
125+
<div class="mt-2 text-sm text-gray-600">
126+
<strong>Namespace:</strong>
127+
{evault.namespace}<br />
128+
<strong>Pod:</strong>
129+
{evault.podName}<br />
130+
<strong>Status:</strong>
131+
<span
132+
class="rounded px-2 py-1 text-xs {evault.status === 'Running'
133+
? 'bg-green-200 text-green-800'
134+
: 'bg-red-200 text-red-800'}"
135+
>
136+
{evault.status}
137+
</span>
138+
</div>
139+
</div>
140+
{/each}
141+
{/if}
142+
</div>
143+
</div>
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { Low } from 'lowdb'
2+
import { JSONFile } from 'lowdb/node'
3+
import type { EVault } from '../../routes/api/evaults/+server';
4+
5+
// Define the cache data structure
6+
interface CacheData {
7+
evaults: EVault[];
8+
lastUpdated: number;
9+
isStale: boolean;
10+
}
11+
12+
// Cache file path (will be created in the project root)
13+
const CACHE_FILE = './evault-cache.json';
14+
15+
// Default cache data
16+
const defaultData: CacheData = {
17+
evaults: [],
18+
lastUpdated: 0,
19+
isStale: true
20+
};
21+
22+
class CacheService {
23+
private db: Low<CacheData>;
24+
private isInitialized = false;
25+
26+
constructor() {
27+
// Initialize LowDB with JSON file adapter
28+
const adapter = new JSONFile<CacheData>(CACHE_FILE);
29+
this.db = new Low(adapter, defaultData);
30+
}
31+
32+
/**
33+
* Initialize the cache service
34+
*/
35+
async init(): Promise<void> {
36+
if (this.isInitialized) return;
37+
38+
try {
39+
await this.db.read();
40+
this.isInitialized = true;
41+
console.log('Cache service initialized');
42+
} catch (error) {
43+
console.warn('Cache file not found, using default data');
44+
this.db.data = defaultData;
45+
await this.db.write();
46+
this.isInitialized = true;
47+
}
48+
}
49+
50+
/**
51+
* Get cached eVaults (fast, returns immediately)
52+
*/
53+
async getCachedEVaults(): Promise<EVault[]> {
54+
await this.init();
55+
return this.db.data.evaults;
56+
}
57+
58+
/**
59+
* Check if cache is stale (older than 5 minutes)
60+
*/
61+
isCacheStale(): boolean {
62+
const fiveMinutesAgo = Date.now() - (5 * 60 * 1000);
63+
return this.db.data.lastUpdated < fiveMinutesAgo;
64+
}
65+
66+
/**
67+
* Update cache with fresh data
68+
*/
69+
async updateCache(evaults: EVault[]): Promise<void> {
70+
await this.init();
71+
72+
this.db.data = {
73+
evaults,
74+
lastUpdated: Date.now(),
75+
isStale: false
76+
};
77+
78+
await this.db.write();
79+
console.log(`Cache updated with ${evaults.length} eVaults`);
80+
}
81+
82+
/**
83+
* Mark cache as stale (force refresh on next request)
84+
*/
85+
async markStale(): Promise<void> {
86+
await this.init();
87+
this.db.data.isStale = true;
88+
await this.db.write();
89+
}
90+
91+
/**
92+
* Get cache status
93+
*/
94+
getCacheStatus(): { lastUpdated: number; isStale: boolean; count: number } {
95+
return {
96+
lastUpdated: this.db.data.lastUpdated,
97+
isStale: this.db.data.isStale,
98+
count: this.db.data.evaults.length
99+
};
100+
}
101+
102+
/**
103+
* Clear cache
104+
*/
105+
async clearCache(): Promise<void> {
106+
await this.init();
107+
this.db.data = defaultData;
108+
await this.db.write();
109+
console.log('Cache cleared');
110+
}
111+
}
112+
113+
// Export singleton instance
114+
export const cacheService = new CacheService();

infrastructure/control-panel/src/lib/services/evaultService.ts

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,54 @@
11
import type { EVault } from '../../routes/api/evaults/+server';
2+
import { cacheService } from './cacheService';
23

34
export class EVaultService {
5+
/**
6+
* Get eVaults with stale-while-revalidate pattern:
7+
* 1. Return cached data immediately (fast)
8+
* 2. Fetch fresh data in background
9+
* 3. Update cache for next request
10+
*/
411
static async getEVaults(): Promise<EVault[]> {
12+
try {
13+
// 1. Get cached data immediately (fast response)
14+
const cachedEVaults = await cacheService.getCachedEVaults();
15+
16+
// 2. Check if we need to refresh in background
17+
if (cacheService.isCacheStale()) {
18+
// Fire and forget - fetch fresh data in background
19+
this.refreshCacheInBackground();
20+
}
21+
22+
// 3. Return cached data immediately
23+
return cachedEVaults;
24+
} catch (error) {
25+
console.error('Error getting cached eVaults:', error);
26+
// Fallback to direct API call if cache fails
27+
return this.fetchEVaultsDirectly();
28+
}
29+
}
30+
31+
/**
32+
* Fetch fresh eVaults from API and update cache
33+
* This runs in the background to avoid blocking the UI
34+
*/
35+
private static async refreshCacheInBackground(): Promise<void> {
36+
try {
37+
console.log('🔄 Refreshing eVault cache in background...');
38+
const freshEVaults = await this.fetchEVaultsDirectly();
39+
await cacheService.updateCache(freshEVaults);
40+
console.log('✅ Cache refreshed successfully');
41+
} catch (error) {
42+
console.error('❌ Failed to refresh cache:', error);
43+
// Mark cache as stale so we try again next time
44+
await cacheService.markStale();
45+
}
46+
}
47+
48+
/**
49+
* Direct API call to fetch eVaults (fallback method)
50+
*/
51+
private static async fetchEVaultsDirectly(): Promise<EVault[]> {
552
try {
653
const response = await fetch('/api/evaults');
754
if (!response.ok) {
@@ -10,11 +57,42 @@ export class EVaultService {
1057
const data = await response.json();
1158
return data.evaults || [];
1259
} catch (error) {
13-
console.error('Error fetching eVaults:', error);
60+
console.error('Error fetching eVaults directly:', error);
1461
return [];
1562
}
1663
}
1764

65+
/**
66+
* Force refresh cache and return fresh data
67+
* Useful for manual refresh buttons
68+
*/
69+
static async forceRefresh(): Promise<EVault[]> {
70+
try {
71+
console.log('🔄 Force refreshing eVault cache...');
72+
const freshEVaults = await this.fetchEVaultsDirectly();
73+
await cacheService.updateCache(freshEVaults);
74+
return freshEVaults;
75+
} catch (error) {
76+
console.error('Error force refreshing eVaults:', error);
77+
// Return cached data as fallback
78+
return await cacheService.getCachedEVaults();
79+
}
80+
}
81+
82+
/**
83+
* Get cache status for debugging/monitoring
84+
*/
85+
static getCacheStatus() {
86+
return cacheService.getCacheStatus();
87+
}
88+
89+
/**
90+
* Clear cache (useful for troubleshooting)
91+
*/
92+
static async clearCache(): Promise<void> {
93+
await cacheService.clearCache();
94+
}
95+
1896
static async getEVaultLogs(
1997
namespace: string,
2098
podName: string,
@@ -60,7 +138,7 @@ export class EVaultService {
60138
}
61139
return await response.json();
62140
} catch (error) {
63-
console.error('Error fetching eVault metrics:', error);
141+
console.error('Error fetching metrics:', error);
64142
return null;
65143
}
66144
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./mapping.db";
2+
//# sourceMappingURL=index.d.ts.map

infrastructure/web3-adapter/src/db/index.d.ts.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)