From 759b7712307687620c9e9b81209a764bd60c12c8 Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Mon, 25 Aug 2025 03:07:44 +0530 Subject: [PATCH 1/3] fix: evoting groups --- platforms/eVoting/src/app/(app)/page.tsx | 14 ++++ .../src/controllers/PollController.ts | 5 +- .../evoting-api/src/services/PollService.ts | 61 ++++++++++++++- .../evoting-api/src/services/VoteService.ts | 75 +++++++++++++++++-- 4 files changed, 144 insertions(+), 11 deletions(-) diff --git a/platforms/eVoting/src/app/(app)/page.tsx b/platforms/eVoting/src/app/(app)/page.tsx index ca624241..f7208db9 100644 --- a/platforms/eVoting/src/app/(app)/page.tsx +++ b/platforms/eVoting/src/app/(app)/page.tsx @@ -153,6 +153,11 @@ export default function Home() { > Visibility {getSortIcon("visibility")} + + Group + handleSort("status")} @@ -196,6 +201,15 @@ export default function Home() { )} + + {poll.group ? ( + + {poll.group.name} + + ) : ( + No group + )} + {isActive ? "Active" : "Ended"} diff --git a/platforms/evoting-api/src/controllers/PollController.ts b/platforms/evoting-api/src/controllers/PollController.ts index 4c199ac3..963ef693 100644 --- a/platforms/evoting-api/src/controllers/PollController.ts +++ b/platforms/evoting-api/src/controllers/PollController.ts @@ -35,11 +35,12 @@ export class PollController { getPollById = async (req: Request, res: Response) => { try { const { id } = req.params; + const userId = (req as any).user?.id; // Get user ID from auth middleware - const poll = await this.pollService.getPollById(id); + const poll = await this.pollService.getPollByIdWithAccessCheck(id, userId); if (!poll) { - return res.status(404).json({ error: "Poll not found" }); + return res.status(404).json({ error: "Poll not found or access denied" }); } res.json(poll); diff --git a/platforms/evoting-api/src/services/PollService.ts b/platforms/evoting-api/src/services/PollService.ts index a4acfa61..eea383d2 100644 --- a/platforms/evoting-api/src/services/PollService.ts +++ b/platforms/evoting-api/src/services/PollService.ts @@ -52,10 +52,24 @@ export class PollService { } }); + // Get group information for polls that have groupId + const pollsWithGroups = await Promise.all( + allPolls.map(async (poll) => { + if (poll.groupId) { + const group = await this.groupRepository.findOne({ + where: { id: poll.groupId }, + select: ['id', 'name', 'description'] + }); + return { ...poll, group }; + } + return poll; + }) + ); + // Filter polls based on user's group memberships - let filteredPolls = allPolls; + let filteredPolls = pollsWithGroups; if (userId && userGroupIds.length > 0) { - filteredPolls = allPolls.filter(poll => { + filteredPolls = filteredPolls.filter(poll => { // Show polls that: // 1. Have no groupId (public polls) // 2. Belong to groups where user is a member/admin/participant @@ -66,7 +80,7 @@ export class PollService { }); } else if (userId) { // If user has no group memberships, only show their own polls and public polls - filteredPolls = allPolls.filter(poll => !poll.groupId || poll.creatorId === userId); + filteredPolls = filteredPolls.filter(poll => !poll.groupId || poll.creatorId === userId); } // Custom sorting based on sortField and sortDirection @@ -140,6 +154,47 @@ export class PollService { }); } + /** + * Get poll by ID with group information and check if user can access it + */ + async getPollByIdWithAccessCheck(id: string, userId?: string): Promise { + const poll = await this.pollRepository.findOne({ + where: { id }, + relations: ["creator"] + }); + + if (!poll) { + return null; + } + + // If poll has no group, it's public - anyone can access + if (!poll.groupId) { + return poll; + } + + // If no userId provided, don't show group polls + if (!userId) { + return null; + } + + // Check if user is a member, admin, or participant of the group + const group = await this.groupRepository + .createQueryBuilder('group') + .leftJoin('group.members', 'member') + .leftJoin('group.admins', 'admin') + .leftJoin('group.participants', 'participant') + .where('group.id = :groupId', { groupId: poll.groupId }) + .andWhere('(member.id = :userId OR admin.id = :userId OR participant.id = :userId)', { userId }) + .getOne(); + + // If user is not in the group, don't show the poll + if (!group) { + return null; + } + + return poll; + } + async createPoll(pollData: { title: string; mode: string; diff --git a/platforms/evoting-api/src/services/VoteService.ts b/platforms/evoting-api/src/services/VoteService.ts index ae68262d..448026b0 100644 --- a/platforms/evoting-api/src/services/VoteService.ts +++ b/platforms/evoting-api/src/services/VoteService.ts @@ -3,12 +3,14 @@ import { AppDataSource } from "../database/data-source"; import { Vote, VoteDataByMode, NormalVoteData, PointVoteData, RankVoteData } from "../database/entities/Vote"; import { Poll } from "../database/entities/Poll"; import { User } from "../database/entities/User"; +import { Group } from "../database/entities/Group"; import { VotingSystem, VoteData } from 'blindvote'; export class VoteService { private voteRepository: Repository; private pollRepository: Repository; private userRepository: Repository; + private groupRepository: Repository; // Store VotingSystem instances per poll private votingSystems = new Map(); @@ -17,11 +19,50 @@ export class VoteService { this.voteRepository = AppDataSource.getRepository(Vote); this.pollRepository = AppDataSource.getRepository(Poll); this.userRepository = AppDataSource.getRepository(User); + this.groupRepository = AppDataSource.getRepository(Group); + } + + /** + * Check if a user can vote on a poll based on group membership + */ + private async canUserVote(pollId: string, userId: string): Promise { + const poll = await this.pollRepository.findOne({ + where: { id: pollId }, + relations: ['creator'] + }); + + if (!poll) { + throw new Error('Poll not found'); + } + + // If poll has no group, it's a public poll - anyone can vote + if (!poll.groupId) { + return true; + } + + // If poll has a group, check if user is a member, admin, or participant + const group = await this.groupRepository + .createQueryBuilder('group') + .leftJoin('group.members', 'member') + .leftJoin('group.admins', 'admin') + .leftJoin('group.participants', 'participant') + .where('group.id = :groupId', { groupId: poll.groupId }) + .andWhere('(member.id = :userId OR admin.id = :userId OR participant.id = :userId)', { userId }) + .getOne(); + + if (!group) { + throw new Error('User is not a member, admin, or participant of the group associated with this poll'); + } + + return true; } // ===== NON-BLIND VOTING METHODS (for normal/point/rank modes) ===== async createVote(pollId: string, userId: string, voteData: VoteData, mode: "normal" | "point" | "rank" = "normal"): Promise { + // First check if user can vote on this poll + await this.canUserVote(pollId, userId); + const poll = await this.pollRepository.findOne({ where: { id: pollId } }); @@ -313,6 +354,15 @@ export class VoteService { async submitBlindVote(pollId: string, voterId: string, voteData: any) { try { + // First check if user can vote on this poll (group membership check) + const user = await this.userRepository.findOne({ where: { ename: voterId } }); + if (!user) { + throw new Error(`User with ename ${voterId} not found. User must exist before submitting blind vote.`); + } + + // Check group membership using the existing method + await this.canUserVote(pollId, user.id); + const votingSystem = this.getVotingSystemForPoll(pollId); // Get poll to find options @@ -374,12 +424,7 @@ export class VoteService { revealed: false }; - // For blind voting, look up the user by their ename (W3ID) - const user = await this.userRepository.findOne({ where: { ename: voterId } }); - if (!user) { - throw new Error(`User with ename ${voterId} not found. User must exist before submitting blind vote.`); - } - + // User is already fetched from the group membership check above const vote = this.voteRepository.create({ poll: { id: pollId }, user: { id: user.id }, @@ -515,6 +560,15 @@ export class VoteService { async registerBlindVoteVoter(pollId: string, voterId: string) { try { + // First check if user can vote on this poll (group membership check) + const user = await this.userRepository.findOne({ where: { ename: voterId } }); + if (!user) { + throw new Error(`User with ename ${voterId} not found. User must exist before registering for blind voting.`); + } + + // Check group membership using the existing method + await this.canUserVote(pollId, user.id); + const votingSystem = this.getVotingSystemForPoll(pollId); // Get poll details @@ -561,6 +615,15 @@ export class VoteService { // Generate vote data for a voter (used by eID wallet) async generateVoteData(pollId: string, voterId: string, chosenOptionId: string) { try { + // First check if user can vote on this poll (group membership check) + const user = await this.userRepository.findOne({ where: { ename: voterId } }); + if (!user) { + throw new Error(`User with ename ${voterId} not found. User must exist before generating vote data.`); + } + + // Check group membership using the existing method + await this.canUserVote(pollId, user.id); + const votingSystem = this.getVotingSystemForPoll(pollId); // Get poll details From 720bb06b492ced5f47f79498f48b46c63b834272 Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Mon, 25 Aug 2025 03:16:22 +0530 Subject: [PATCH 2/3] chore: fix evault uris --- infrastructure/control-panel/package.json | 2 +- .../src/lib/components/EVaultList.svelte | 253 ++++++++++++------ .../src/lib/services/cacheService.ts | 125 +++++---- .../src/lib/services/evaultService.ts | 199 +++++--------- .../src/routes/api/evaults/+server.ts | 74 ++++- pnpm-lock.yaml | 148 ++++------ 6 files changed, 417 insertions(+), 384 deletions(-) diff --git a/infrastructure/control-panel/package.json b/infrastructure/control-panel/package.json index 6f2f7a16..69874414 100644 --- a/infrastructure/control-panel/package.json +++ b/infrastructure/control-panel/package.json @@ -50,7 +50,7 @@ "flowbite": "^3.1.2", "flowbite-svelte": "^1.10.7", "flowbite-svelte-icons": "^2.2.1", - "lowdb": "^9.1.0", + "lowdb": "^7.0.1", "lucide-svelte": "^0.539.0", "tailwind-merge": "^3.0.2" } diff --git a/infrastructure/control-panel/src/lib/components/EVaultList.svelte b/infrastructure/control-panel/src/lib/components/EVaultList.svelte index 46dee510..0fdcca5c 100644 --- a/infrastructure/control-panel/src/lib/components/EVaultList.svelte +++ b/infrastructure/control-panel/src/lib/components/EVaultList.svelte @@ -4,140 +4,219 @@ import type { EVault } from '../../routes/api/evaults/+server'; let evaults: EVault[] = []; - let loading = false; - let cacheStatus: any = null; - let lastRefresh = ''; + let loading = true; + let error: string | null = null; + let cacheStatus: { lastUpdated: number; isStale: boolean; itemCount: number }; - // Load eVaults on component mount onMount(async () => { - await loadEVaults(); - // Get cache status for debugging - cacheStatus = EVaultService.getCacheStatus(); + try { + await loadEVaults(); + cacheStatus = EVaultService.getCacheStatus(); + } catch (err) { + error = 'Failed to load eVaults'; + console.error(err); + } finally { + loading = false; + } }); async function loadEVaults() { - loading = true; try { - // This will return cached data immediately and refresh in background evaults = await EVaultService.getEVaults(); - lastRefresh = new Date().toLocaleTimeString(); - } catch (error) { - console.error('Failed to load eVaults:', error); - } finally { - loading = false; + cacheStatus = EVaultService.getCacheStatus(); + } catch (err) { + console.error('Error loading eVaults:', err); + throw err; } } async function forceRefresh() { - loading = true; try { - // Force refresh and get fresh data + loading = true; evaults = await EVaultService.forceRefresh(); - lastRefresh = new Date().toLocaleTimeString(); - // Update cache status cacheStatus = EVaultService.getCacheStatus(); - } catch (error) { - console.error('Failed to force refresh:', error); + } catch (err) { + error = 'Failed to refresh eVaults'; + console.error(err); } finally { loading = false; } } async function clearCache() { - await EVaultService.clearCache(); - cacheStatus = EVaultService.getCacheStatus(); - // Reload data - await loadEVaults(); + try { + await EVaultService.clearCache(); + evaults = []; + cacheStatus = EVaultService.getCacheStatus(); + } catch (err) { + console.error('Error clearing cache:', err); + } + } + + function formatTimestamp(timestamp: number): string { + if (timestamp === 0) return 'Never'; + return new Date(timestamp).toLocaleString(); }
-
-

eVault Control Panel

-
+
+

eVault Management

+ + +
+

Cache Status

+
+
+ Last Updated: + {formatTimestamp(cacheStatus?.lastUpdated || 0)} +
+
+ Status: + + {cacheStatus?.isStale ? 'Stale' : 'Fresh'} + +
+
+ Items: + {cacheStatus?.itemCount || 0} +
+
+
+ + +
+ +
- - {#if cacheStatus} -
-

Cache Status

-
-
- Last Updated:
- {new Date(cacheStatus.lastUpdated).toLocaleString()} -
-
- Status:
- - {cacheStatus.isStale ? 'Stale' : 'Fresh'} - -
-
- Cached Items:
- {cacheStatus.count} eVaults -
-
+ + {#if error} +
+ {error}
{/if} - - {#if lastRefresh} -
- Last refresh: {lastRefresh} + + {#if loading} +
+
+

Loading eVaults...

- {/if} - - -
- {#if evaults.length === 0} -
- {loading ? 'Loading eVaults...' : 'No eVaults found'} -
- {:else} - {#each evaults as evault} -
-

{evault.name || 'Unnamed eVault'}

-
- Namespace: - {evault.namespace}
- Pod: - {evault.podName}
- Status: - +
+

No eVaults found

+

+ Try refreshing or check your Kubernetes connection +

+
+ {:else} + +
+ + + + + + + + + + + + {#each evaults as evault} + + + + + + + + {/each} + +
- {evault.status} - - - - {/each} - {/if} - + Name + + Namespace + + Status + + Age + + Service URL +
+ {evault.name} + + {evault.namespace} + + + {evault.status} + + + {evault.age || 'Unknown'} + + {#if evault.serviceUrl} + + {evault.serviceUrl} + + {:else} + No external access + {/if} +
+
+ {/if}
diff --git a/infrastructure/control-panel/src/lib/services/cacheService.ts b/infrastructure/control-panel/src/lib/services/cacheService.ts index 1f48faf4..cb977013 100644 --- a/infrastructure/control-panel/src/lib/services/cacheService.ts +++ b/infrastructure/control-panel/src/lib/services/cacheService.ts @@ -20,95 +20,116 @@ const defaultData: CacheData = { }; class CacheService { - private db: Low; + private db: Low | null = null; private isInitialized = false; constructor() { - // Initialize LowDB with JSON file adapter - const adapter = new JSONFile(CACHE_FILE); - this.db = new Low(adapter, defaultData); + // Only initialize on the server side + if (typeof window === 'undefined') { + this.init(); + } } - /** - * Initialize the cache service - */ - async init(): Promise { + private async init() { if (this.isInitialized) return; try { + // Initialize LowDB with JSON file adapter + const adapter = new JSONFile(CACHE_FILE); + this.db = new Low(adapter, defaultData); + + // Load existing data or create default await this.db.read(); + if (!this.db.data) { + this.db.data = defaultData; + await this.db.write(); + } + this.isInitialized = true; - console.log('Cache service initialized'); } catch (error) { - console.warn('Cache file not found, using default data'); - this.db.data = defaultData; - await this.db.write(); - this.isInitialized = true; + console.error('Failed to initialize cache service:', error); } } - /** - * Get cached eVaults (fast, returns immediately) - */ async getCachedEVaults(): Promise { + if (typeof window !== 'undefined') { + // In browser, return empty array - caching only works on server + return []; + } + await this.init(); - return this.db.data.evaults; + return this.db?.data?.evaults || []; } - /** - * Check if cache is stale (older than 5 minutes) - */ - isCacheStale(): boolean { - const fiveMinutesAgo = Date.now() - (5 * 60 * 1000); - return this.db.data.lastUpdated < fiveMinutesAgo; + async isCacheStale(): Promise { + if (typeof window !== 'undefined') { + return true; // Always stale in browser + } + + await this.init(); + const lastUpdated = this.db?.data?.lastUpdated || 0; + const now = Date.now(); + const fiveMinutes = 5 * 60 * 1000; // 5 minutes in milliseconds + + return (now - lastUpdated) > fiveMinutes; } - /** - * Update cache with fresh data - */ async updateCache(evaults: EVault[]): Promise { - await this.init(); - - this.db.data = { - evaults, - lastUpdated: Date.now(), - isStale: false - }; + if (typeof window !== 'undefined') { + return; // No-op in browser + } - await this.db.write(); - console.log(`Cache updated with ${evaults.length} eVaults`); + await this.init(); + if (this.db) { + this.db.data = { + evaults, + lastUpdated: Date.now(), + isStale: false + }; + await this.db.write(); + } } - /** - * Mark cache as stale (force refresh on next request) - */ async markStale(): Promise { + if (typeof window !== 'undefined') { + return; // No-op in browser + } + await this.init(); - this.db.data.isStale = true; - await this.db.write(); + if (this.db && this.db.data) { + this.db.data.isStale = true; + await this.db.write(); + } } - /** - * Get cache status - */ - getCacheStatus(): { lastUpdated: number; isStale: boolean; count: number } { + getCacheStatus(): { lastUpdated: number; isStale: boolean; itemCount: number } { + if (typeof window !== 'undefined') { + return { lastUpdated: 0, isStale: true, itemCount: 0 }; + } + + if (!this.db?.data) { + return { lastUpdated: 0, isStale: true, itemCount: 0 }; + } + return { lastUpdated: this.db.data.lastUpdated, isStale: this.db.data.isStale, - count: this.db.data.evaults.length + itemCount: this.db.data.evaults.length }; } - /** - * Clear cache - */ async clearCache(): Promise { + if (typeof window !== 'undefined') { + return; // No-op in browser + } + await this.init(); - this.db.data = defaultData; - await this.db.write(); - console.log('Cache cleared'); + if (this.db) { + this.db.data = defaultData; + await this.db.write(); + } } } -// Export singleton instance +// Export a singleton instance export const cacheService = new CacheService(); diff --git a/infrastructure/control-panel/src/lib/services/evaultService.ts b/infrastructure/control-panel/src/lib/services/evaultService.ts index f0371283..7a3be91b 100644 --- a/infrastructure/control-panel/src/lib/services/evaultService.ts +++ b/infrastructure/control-panel/src/lib/services/evaultService.ts @@ -2,144 +2,73 @@ import type { EVault } from '../../routes/api/evaults/+server'; import { cacheService } from './cacheService'; export class EVaultService { - /** - * Get eVaults with stale-while-revalidate pattern: - * 1. Return cached data immediately (fast) - * 2. Fetch fresh data in background - * 3. Update cache for next request - */ - static async getEVaults(): Promise { - try { - // 1. Get cached data immediately (fast response) - const cachedEVaults = await cacheService.getCachedEVaults(); - - // 2. Check if we need to refresh in background - if (cacheService.isCacheStale()) { - // Fire and forget - fetch fresh data in background - this.refreshCacheInBackground(); - } - - // 3. Return cached data immediately - return cachedEVaults; - } catch (error) { - console.error('Error getting cached eVaults:', error); - // Fallback to direct API call if cache fails - return this.fetchEVaultsDirectly(); - } - } + /** + * Get eVaults with stale-while-revalidate caching + * Returns cached data immediately, refreshes in background if stale + */ + static async getEVaults(): Promise { + // Check if cache is stale + const isStale = await cacheService.isCacheStale(); + + if (isStale) { + // Cache is stale, refresh in background + this.refreshCacheInBackground(); + } + + // Return cached data immediately (even if stale) + return await cacheService.getCachedEVaults(); + } - /** - * Fetch fresh eVaults from API and update cache - * This runs in the background to avoid blocking the UI - */ - private static async refreshCacheInBackground(): Promise { - try { - console.log('🔄 Refreshing eVault cache in background...'); - const freshEVaults = await this.fetchEVaultsDirectly(); - await cacheService.updateCache(freshEVaults); - console.log('✅ Cache refreshed successfully'); - } catch (error) { - console.error('❌ Failed to refresh cache:', error); - // Mark cache as stale so we try again next time - await cacheService.markStale(); - } - } + /** + * Force refresh the cache with fresh data + */ + static async forceRefresh(): Promise { + const evaults = await this.fetchEVaultsDirectly(); + await cacheService.updateCache(evaults); + return evaults; + } - /** - * Direct API call to fetch eVaults (fallback method) - */ - private static async fetchEVaultsDirectly(): Promise { - try { - const response = await fetch('/api/evaults'); - if (!response.ok) { - throw new Error('Failed to fetch eVaults'); - } - const data = await response.json(); - return data.evaults || []; - } catch (error) { - console.error('Error fetching eVaults directly:', error); - return []; - } - } + /** + * Get cache status information + */ + static getCacheStatus() { + return cacheService.getCacheStatus(); + } - /** - * Force refresh cache and return fresh data - * Useful for manual refresh buttons - */ - static async forceRefresh(): Promise { - try { - console.log('🔄 Force refreshing eVault cache...'); - const freshEVaults = await this.fetchEVaultsDirectly(); - await cacheService.updateCache(freshEVaults); - return freshEVaults; - } catch (error) { - console.error('Error force refreshing eVaults:', error); - // Return cached data as fallback - return await cacheService.getCachedEVaults(); - } - } + /** + * Clear the cache + */ + static async clearCache(): Promise { + await cacheService.clearCache(); + } - /** - * Get cache status for debugging/monitoring - */ - static getCacheStatus() { - return cacheService.getCacheStatus(); - } + /** + * Refresh cache in background (non-blocking) + */ + private static async refreshCacheInBackground(): Promise { + try { + const evaults = await this.fetchEVaultsDirectly(); + await cacheService.updateCache(evaults); + } catch (error) { + console.error('Background cache refresh failed:', error); + // Mark cache as stale so next request will try again + await cacheService.markStale(); + } + } - /** - * Clear cache (useful for troubleshooting) - */ - static async clearCache(): Promise { - await cacheService.clearCache(); - } - - static async getEVaultLogs( - namespace: string, - podName: string, - tail: number = 100 - ): Promise { - try { - const response = await fetch( - `/api/evaults/${encodeURIComponent(namespace)}/${encodeURIComponent(podName)}/logs?tail=${tail}` - ); - if (!response.ok) { - throw new Error('Failed to fetch logs'); - } - const data = await response.json(); - return data.logs || []; - } catch (error) { - console.error('Error fetching logs:', error); - return []; - } - } - - static async getEVaultDetails(namespace: string, podName: string): Promise { - try { - const response = await fetch( - `/api/evaults/${encodeURIComponent(namespace)}/${encodeURIComponent(podName)}/details` - ); - if (!response.ok) { - throw new Error('Failed to fetch eVault details'); - } - return await response.json(); - } catch (error) { - console.error('Error fetching eVault details:', error); - return null; - } - } - - static async getEVaultMetrics(namespace: string, podName: string): Promise { - try { - const response = await fetch( - `/api/evaults/${encodeURIComponent(namespace)}/${encodeURIComponent(podName)}/metrics` - ); - if (!response.ok) { - throw new Error('Failed to fetch metrics'); - } - return await response.json(); - } catch (error) { - console.error('Error fetching metrics:', error); - return null; - } - } + /** + * Fetch eVaults directly from Kubernetes API + */ + private static async fetchEVaultsDirectly(): Promise { + try { + const response = await fetch('/api/evaults'); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); + } catch (error) { + console.error('Failed to fetch eVaults:', error); + throw error; + } + } } diff --git a/infrastructure/control-panel/src/routes/api/evaults/+server.ts b/infrastructure/control-panel/src/routes/api/evaults/+server.ts index 126983e8..e2f0b749 100644 --- a/infrastructure/control-panel/src/routes/api/evaults/+server.ts +++ b/infrastructure/control-panel/src/routes/api/evaults/+server.ts @@ -23,20 +23,74 @@ export interface EVault { export const GET: RequestHandler = async () => { try { - // Get minikube IP for NodePort services - let minikubeIP = 'localhost'; + // Get external IP from Kubernetes nodes + let externalIP = 'localhost'; try { - const { stdout: minikubeIPOutput } = await execAsync( - 'minikube ip 2>/dev/null || echo "localhost"' - ); - if (minikubeIPOutput.trim()) { - minikubeIP = minikubeIPOutput.trim(); + // 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 minikube IP, using localhost'); + console.log('Could not get external IP, using localhost:', ipError); } - console.log('Using IP:', minikubeIP); + console.log('Using IP for services:', externalIP); // Get all namespaces const { stdout: namespacesOutput } = await execAsync('kubectl get namespaces -o json'); @@ -156,7 +210,7 @@ export const GET: RequestHandler = async () => { // Generate service URL let serviceUrl = ''; if (nodePort) { - serviceUrl = `http://${minikubeIP}:${nodePort}`; + serviceUrl = `http://${externalIP}:${nodePort}`; } console.log(`Service URL: ${serviceUrl}`); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 41b8b679..6325d8f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,6 +75,9 @@ importers: flowbite-svelte-icons: specifier: ^2.2.1 version: 2.2.1(svelte@5.33.1) + lowdb: + specifier: ^7.0.1 + version: 7.0.1 lucide-svelte: specifier: ^0.539.0 version: 0.539.0(svelte@5.33.1) @@ -316,7 +319,7 @@ importers: version: 5.33.1 svelte-check: specifier: ^4.0.0 - version: 4.2.1(picomatch@4.0.3)(svelte@5.33.1)(typescript@5.6.3) + version: 4.2.1(picomatch@4.0.2)(svelte@5.33.1)(typescript@5.6.3) svelte-gestures: specifier: ^5.1.3 version: 5.1.4 @@ -557,10 +560,10 @@ importers: version: 8.57.1 jest: specifier: ^29.5.0 - version: 29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.2.0)(typescript@5.8.3)) + version: 29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0) ts-jest: specifier: ^29.1.0 - version: 29.3.4(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.2.0)(typescript@5.8.3)))(typescript@5.8.3) + version: 29.3.4(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0))(typescript@5.8.3) typescript: specifier: ^5.0.4 version: 5.8.3 @@ -9394,6 +9397,10 @@ packages: loupe@3.2.0: resolution: {integrity: sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==} + lowdb@7.0.1: + resolution: {integrity: sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==} + engines: {node: '>=18'} + lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} @@ -11348,6 +11355,10 @@ packages: steed@1.1.3: resolution: {integrity: sha512-EUkci0FAUiE4IvGTSKcDJIQ/eRUP2JJb56+fvZ4sdnguLTqIdKjSxUe138poW8mkvKWXW2sFPrgTsxqoISnmoA==} + steno@4.0.2: + resolution: {integrity: sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==} + engines: {node: '>=18'} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -14586,41 +14597,6 @@ snapshots: - supports-color - ts-node - '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.2.0)(typescript@5.8.3))': - dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.17.50 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.17.50)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.2.0)(typescript@5.8.3)) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node - '@jest/environment@28.1.3': dependencies: '@jest/fake-timers': 28.1.3 @@ -19630,13 +19606,13 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.2.0)(typescript@5.8.3)): + create-jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.2.0)(typescript@5.8.3)) + jest-config: 29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -20341,8 +20317,8 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.27.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.27.0(jiti@2.4.2)) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.27.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.27.0(jiti@2.4.2)) eslint-plugin-react: 7.37.5(eslint@9.27.0(jiti@2.4.2)) eslint-plugin-react-hooks: 5.2.0(eslint@9.27.0(jiti@2.4.2)) @@ -20377,33 +20353,33 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.21.0): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1(supports-color@5.5.0) - eslint: 8.21.0 + eslint: 9.27.0(jiti@2.4.2) get-tsconfig: 4.10.1 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.13 unrs-resolver: 1.7.11 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.21.0)(typescript@4.7.4))(eslint-import-resolver-typescript@3.10.1)(eslint@8.21.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.27.0(jiti@2.4.2)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.21.0): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1(supports-color@5.5.0) - eslint: 9.27.0(jiti@2.4.2) + eslint: 8.21.0 get-tsconfig: 4.10.1 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.13 unrs-resolver: 1.7.11 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.27.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.21.0)(typescript@4.7.4))(eslint-import-resolver-typescript@3.10.1)(eslint@8.21.0) transitivePeerDependencies: - supports-color @@ -20429,14 +20405,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.27.0(jiti@2.4.2)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.27.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.27.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color @@ -20498,7 +20474,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.27.0(jiti@2.4.2)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -20509,7 +20485,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.27.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.27.0(jiti@2.4.2)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -22300,16 +22276,16 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.2.0)(typescript@5.8.3)): + jest-cli@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.2.0)(typescript@5.8.3)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.50)(typescript@5.8.3)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.2.0)(typescript@5.8.3)) + create-jest: 29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.2.0)(typescript@5.8.3)) + jest-config: 29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -22410,38 +22386,7 @@ snapshots: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@20.17.50)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.2.0)(typescript@5.8.3)): - dependencies: - '@babel/core': 7.27.1 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.27.1) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0(babel-plugin-macros@3.1.0) - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 20.17.50 - ts-node: 10.9.2(@types/node@24.2.0)(typescript@5.8.3) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-config@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.2.0)(typescript@5.8.3)): + jest-config@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0): dependencies: '@babel/core': 7.27.1 '@jest/test-sequencer': 29.7.0 @@ -22467,7 +22412,6 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 24.2.0 - ts-node: 10.9.2(@types/node@24.2.0)(typescript@5.8.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -22942,12 +22886,12 @@ snapshots: - supports-color - ts-node - jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.2.0)(typescript@5.8.3)): + jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.2.0)(typescript@5.8.3)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.50)(typescript@5.8.3)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.2.0)(typescript@5.8.3)) + jest-cli: 29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -23333,6 +23277,10 @@ snapshots: loupe@3.2.0: {} + lowdb@7.0.1: + dependencies: + steno: 4.0.2 + lower-case@2.0.2: dependencies: tslib: 2.8.1 @@ -25828,6 +25776,8 @@ snapshots: fastseries: 1.7.2 reusify: 1.1.0 + steno@4.0.2: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -26087,7 +26037,7 @@ snapshots: svelte: 5.33.1 zimmerframe: 1.1.2 - svelte-check@4.2.1(picomatch@4.0.2)(svelte@5.33.1)(typescript@5.8.3): + svelte-check@4.2.1(picomatch@4.0.2)(svelte@5.33.1)(typescript@5.6.3): dependencies: '@jridgewell/trace-mapping': 0.3.25 chokidar: 4.0.3 @@ -26095,19 +26045,19 @@ snapshots: picocolors: 1.1.1 sade: 1.8.1 svelte: 5.33.1 - typescript: 5.8.3 + typescript: 5.6.3 transitivePeerDependencies: - picomatch - svelte-check@4.2.1(picomatch@4.0.3)(svelte@5.33.1)(typescript@5.6.3): + svelte-check@4.2.1(picomatch@4.0.2)(svelte@5.33.1)(typescript@5.8.3): dependencies: '@jridgewell/trace-mapping': 0.3.25 chokidar: 4.0.3 - fdir: 6.4.4(picomatch@4.0.3) + fdir: 6.4.4(picomatch@4.0.2) picocolors: 1.1.1 sade: 1.8.1 svelte: 5.33.1 - typescript: 5.6.3 + typescript: 5.8.3 transitivePeerDependencies: - picomatch @@ -26504,12 +26454,12 @@ snapshots: '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.27.1) - ts-jest@29.3.4(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.2.0)(typescript@5.8.3)))(typescript@5.8.3): + ts-jest@29.3.4(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0))(typescript@5.8.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.2.0)(typescript@5.8.3)) + jest: 29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 From 10c3522668d3969c241d466a8b6fc3c08850a762 Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Mon, 25 Aug 2025 03:23:36 +0530 Subject: [PATCH 3/3] chore: fix evault visibility --- .../control-panel/src/lib/components/EVaultList.svelte | 9 ++++++++- .../control-panel/src/lib/services/cacheService.ts | 5 +++-- .../control-panel/src/lib/services/evaultService.ts | 7 ++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/infrastructure/control-panel/src/lib/components/EVaultList.svelte b/infrastructure/control-panel/src/lib/components/EVaultList.svelte index 0fdcca5c..dd9c8ce3 100644 --- a/infrastructure/control-panel/src/lib/components/EVaultList.svelte +++ b/infrastructure/control-panel/src/lib/components/EVaultList.svelte @@ -9,20 +9,27 @@ let cacheStatus: { lastUpdated: number; isStale: boolean; itemCount: number }; onMount(async () => { + console.log('Component mounted, starting to load eVaults...'); try { + loading = true; + console.log('Loading state set to true'); await loadEVaults(); cacheStatus = EVaultService.getCacheStatus(); + console.log('Loaded eVaults:', evaults.length, 'items'); } catch (err) { error = 'Failed to load eVaults'; - console.error(err); + console.error('Error in onMount:', err); } finally { loading = false; + console.log('Loading state set to false'); } }); async function loadEVaults() { + console.log('loadEVaults called'); try { evaults = await EVaultService.getEVaults(); + console.log('EVaultService returned:', evaults.length, 'items'); cacheStatus = EVaultService.getCacheStatus(); } catch (err) { console.error('Error loading eVaults:', err); diff --git a/infrastructure/control-panel/src/lib/services/cacheService.ts b/infrastructure/control-panel/src/lib/services/cacheService.ts index cb977013..38b2bf65 100644 --- a/infrastructure/control-panel/src/lib/services/cacheService.ts +++ b/infrastructure/control-panel/src/lib/services/cacheService.ts @@ -53,8 +53,9 @@ class CacheService { async getCachedEVaults(): Promise { if (typeof window !== 'undefined') { - // In browser, return empty array - caching only works on server - return []; + // In browser, return null to indicate we need to fetch from server + // This prevents showing "No eVaults found" prematurely + return null as any; } await this.init(); diff --git a/infrastructure/control-panel/src/lib/services/evaultService.ts b/infrastructure/control-panel/src/lib/services/evaultService.ts index 7a3be91b..c332f14c 100644 --- a/infrastructure/control-panel/src/lib/services/evaultService.ts +++ b/infrastructure/control-panel/src/lib/services/evaultService.ts @@ -7,7 +7,12 @@ export class EVaultService { * Returns cached data immediately, refreshes in background if stale */ static async getEVaults(): Promise { - // Check if cache is stale + // In browser, always fetch from server since caching doesn't work here + if (typeof window !== 'undefined') { + return this.fetchEVaultsDirectly(); + } + + // On server, use caching const isStale = await cacheService.isCacheStale(); if (isStale) {