-
âī¸ Settings
-
@@ -160,19 +150,13 @@ const formatDate = (timestamp: number | null): string => {
-
diff --git a/src/config.ts b/src/config.ts
index 63dd6a5..ae68439 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -1,14 +1,14 @@
// Application configuration
export interface AppConfig {
cache: {
- maxSizeMB: number; // Maximum cache size in megabytes
- maxAge: number; // Maximum age of cache entries in milliseconds
- enabled: boolean;
- };
+ maxSizeMB: number // Maximum cache size in megabytes
+ maxAge: number // Maximum age of cache entries in milliseconds
+ enabled: boolean
+ }
search: {
- maxDepth: number; // Maximum recursion depth for path search
- maxResults: number; // Maximum number of results to return
- };
+ maxDepth: number // Maximum recursion depth for path search
+ maxResults: number // Maximum number of results to return
+ }
}
// Default configuration
@@ -22,27 +22,26 @@ export const defaultConfig: AppConfig = {
maxDepth: 10,
maxResults: 1000,
},
-};
+}
// Load configuration from localStorage
export function loadConfig(): AppConfig {
try {
- const saved = localStorage.getItem('vaultGuiConfig');
+ const saved = localStorage.getItem('vaultGuiConfig')
if (saved) {
- return { ...defaultConfig, ...JSON.parse(saved) };
+ return { ...defaultConfig, ...JSON.parse(saved) }
}
} catch (error) {
- console.error('Failed to load config:', error);
+ console.error('Failed to load config:', error)
}
- return defaultConfig;
+ return defaultConfig
}
// Save configuration to localStorage
export function saveConfig(config: AppConfig): void {
try {
- localStorage.setItem('vaultGuiConfig', JSON.stringify(config));
+ localStorage.setItem('vaultGuiConfig', JSON.stringify(config))
} catch (error) {
- console.error('Failed to save config:', error);
+ console.error('Failed to save config:', error)
}
}
-
diff --git a/src/env.d.ts b/src/env.d.ts
index 230001c..323c78a 100644
--- a/src/env.d.ts
+++ b/src/env.d.ts
@@ -1,8 +1,7 @@
///
declare module '*.vue' {
- import type { DefineComponent } from 'vue'
- const component: DefineComponent<{}, {}, any>
- export default component
+ import type { DefineComponent } from 'vue'
+ const component: DefineComponent<{}, {}, any>
+ export default component
}
-
diff --git a/src/main.ts b/src/main.ts
index 216546d..2425c0f 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -3,4 +3,3 @@ import './style.css'
import App from './App.vue'
createApp(App).mount('#app')
-
diff --git a/src/services/vaultApi.ts b/src/services/vaultApi.ts
index 85f87f3..7a30145 100644
--- a/src/services/vaultApi.ts
+++ b/src/services/vaultApi.ts
@@ -1,18 +1,18 @@
-import { VaultServer, VaultCredentials, MountPoint } from '../types';
-import { vaultCache } from '../utils/cache';
-import { loadConfig } from '../config';
-import { VaultClient, VaultError } from './vaultClient';
+import { VaultServer, VaultCredentials, MountPoint } from '../types'
+import { vaultCache } from '../utils/cache'
+import { loadConfig } from '../config'
+import { VaultClient, VaultError } from './vaultClient'
export interface SearchResult {
- path: string;
- isDirectory: boolean;
- depth: number;
- mountPoint?: string;
+ path: string
+ isDirectory: boolean
+ depth: number
+ mountPoint?: string
}
/**
* High-level Vault API service with caching
- *
+ *
* This service wraps the VaultClient and adds caching functionality
* to prevent excessive API calls and improve performance.
*/
@@ -20,97 +20,82 @@ class VaultApiService {
/**
* Create a VaultClient instance for the given server and credentials
*/
- private createClient(
- server: VaultServer,
- credentials: VaultCredentials
- ): VaultClient {
+ private createClient(server: VaultServer, credentials: VaultCredentials): VaultClient {
return new VaultClient({
server,
credentials,
timeout: 30000,
retries: 2,
kvVersion: 2, // KV v2 is enforced
- });
+ })
}
/**
* Generate a cache key for a given operation
*/
- private getCacheKey(
- server: VaultServer,
- path: string,
- operation: string
- ): string {
- return `${server.id}:${operation}:${path}`;
+ private getCacheKey(server: VaultServer, path: string, operation: string): string {
+ return `${server.id}:${operation}:${path}`
}
/**
* List secrets at a given path with caching
*/
- async listSecrets(
- server: VaultServer,
- credentials: VaultCredentials,
- path: string
- ): Promise
{
- const cacheKey = this.getCacheKey(server, path, 'list');
+ async listSecrets(server: VaultServer, credentials: VaultCredentials, path: string): Promise {
+ const cacheKey = this.getCacheKey(server, path, 'list')
// Check cache first
- const cached = vaultCache.get(cacheKey);
+ const cached = vaultCache.get(cacheKey)
if (cached) {
- console.log(`â Cache hit for list: ${path}`);
- return cached;
+ console.log(`â Cache hit for list: ${path}`)
+ return cached
}
- console.log(`⥠API call for list: ${path}`);
+ console.log(`⥠API call for list: ${path}`)
try {
- const client = this.createClient(server, credentials);
- const keys = await client.list(path);
+ const client = this.createClient(server, credentials)
+ const keys = await client.list(path)
// Cache the result
- vaultCache.set(cacheKey, keys);
+ vaultCache.set(cacheKey, keys)
- return keys;
+ return keys
} catch (error) {
if (error instanceof VaultError) {
- console.error(`Vault error listing ${path}:`, error.message);
+ console.error(`Vault error listing ${path}:`, error.message)
if (error.errors) {
- console.error('Details:', error.errors);
+ console.error('Details:', error.errors)
}
} else {
- console.error(`Error listing secrets at ${path}:`, error);
+ console.error(`Error listing secrets at ${path}:`, error)
}
- return [];
+ return []
}
}
/**
* Read a secret from Vault (NO CACHING - secrets are never cached for security)
*/
- async readSecret(
- server: VaultServer,
- credentials: VaultCredentials,
- path: string
- ): Promise | null> {
- console.log(`⥠API call for read (no cache): ${path}`);
+ async readSecret(server: VaultServer, credentials: VaultCredentials, path: string): Promise | null> {
+ console.log(`⥠API call for read (no cache): ${path}`)
try {
- const client = this.createClient(server, credentials);
- const secretData = await client.read>(path);
+ const client = this.createClient(server, credentials)
+ const secretData = await client.read>(path)
// SECURITY: Never cache secret data - always fetch fresh
- return secretData;
+ return secretData
} catch (error) {
if (error instanceof VaultError) {
- console.error(`Vault error reading ${path}:`, error.message);
+ console.error(`Vault error reading ${path}:`, error.message)
if (error.errors) {
- console.error('Details:', error.errors);
+ console.error('Details:', error.errors)
}
// Re-throw to let the caller handle it
- throw error;
+ throw error
} else {
- console.error(`Error reading secret at ${path}:`, error);
- throw new VaultError('Failed to read secret');
+ console.error(`Error reading secret at ${path}:`, error)
+ throw new VaultError('Failed to read secret')
}
}
}
@@ -118,29 +103,25 @@ class VaultApiService {
/**
* Read metadata for a secret (KV v2 only)
*/
- async readSecretMetadata(
- server: VaultServer,
- credentials: VaultCredentials,
- path: string
- ): Promise {
- console.log(`⥠API call for metadata (no cache): ${path}`);
+ async readSecretMetadata(server: VaultServer, credentials: VaultCredentials, path: string): Promise {
+ console.log(`⥠API call for metadata (no cache): ${path}`)
try {
- const client = this.createClient(server, credentials);
- const metadata = await client.readMetadata(path);
+ const client = this.createClient(server, credentials)
+ const metadata = await client.readMetadata(path)
- return metadata;
+ return metadata
} catch (error) {
if (error instanceof VaultError) {
- console.error(`Vault error reading metadata ${path}:`, error.message);
+ console.error(`Vault error reading metadata ${path}:`, error.message)
if (error.errors) {
- console.error('Details:', error.errors);
+ console.error('Details:', error.errors)
}
// Re-throw to let the caller handle it
- throw error;
+ throw error
} else {
- console.error(`Error reading metadata at ${path}:`, error);
- throw new VaultError('Failed to read metadata');
+ console.error(`Error reading metadata at ${path}:`, error)
+ throw new VaultError('Failed to read metadata')
}
}
}
@@ -148,30 +129,25 @@ class VaultApiService {
/**
* Write a secret to Vault (no caching)
*/
- async writeSecret(
- server: VaultServer,
- credentials: VaultCredentials,
- path: string,
- data: Record
- ): Promise {
- console.log(`⥠API call for write: ${path}`);
+ async writeSecret(server: VaultServer, credentials: VaultCredentials, path: string, data: Record): Promise {
+ console.log(`⥠API call for write: ${path}`)
try {
- const client = this.createClient(server, credentials);
- await client.write(path, data);
+ const client = this.createClient(server, credentials)
+ await client.write(path, data)
// Invalidate cache for this path
- const cacheKey = this.getCacheKey(server, path, 'read');
- vaultCache.delete(cacheKey);
+ const cacheKey = this.getCacheKey(server, path, 'read')
+ vaultCache.delete(cacheKey)
- console.log(`â Secret written successfully: ${path}`);
+ console.log(`â Secret written successfully: ${path}`)
} catch (error) {
if (error instanceof VaultError) {
- console.error(`Vault error writing ${path}:`, error.message);
- throw error;
+ console.error(`Vault error writing ${path}:`, error.message)
+ throw error
} else {
- console.error(`Error writing secret at ${path}:`, error);
- throw new VaultError('Failed to write secret');
+ console.error(`Error writing secret at ${path}:`, error)
+ throw new VaultError('Failed to write secret')
}
}
}
@@ -179,29 +155,25 @@ class VaultApiService {
/**
* Delete a secret from Vault (no caching)
*/
- async deleteSecret(
- server: VaultServer,
- credentials: VaultCredentials,
- path: string
- ): Promise {
- console.log(`⥠API call for delete: ${path}`);
+ async deleteSecret(server: VaultServer, credentials: VaultCredentials, path: string): Promise {
+ console.log(`⥠API call for delete: ${path}`)
try {
- const client = this.createClient(server, credentials);
- await client.delete(path);
+ const client = this.createClient(server, credentials)
+ await client.delete(path)
// Invalidate cache for this path
- const cacheKey = this.getCacheKey(server, path, 'read');
- vaultCache.delete(cacheKey);
+ const cacheKey = this.getCacheKey(server, path, 'read')
+ vaultCache.delete(cacheKey)
- console.log(`â Secret deleted successfully: ${path}`);
+ console.log(`â Secret deleted successfully: ${path}`)
} catch (error) {
if (error instanceof VaultError) {
- console.error(`Vault error deleting ${path}:`, error.message);
- throw error;
+ console.error(`Vault error deleting ${path}:`, error.message)
+ throw error
} else {
- console.error(`Error deleting secret at ${path}:`, error);
- throw new VaultError('Failed to delete secret');
+ console.error(`Error deleting secret at ${path}:`, error)
+ throw new VaultError('Failed to delete secret')
}
}
}
@@ -209,20 +181,17 @@ class VaultApiService {
/**
* Verify login and get available mount points
*/
- async verifyLoginAndGetMounts(
- server: VaultServer,
- credentials: VaultCredentials
- ): Promise {
- console.log('⥠Verifying login and fetching mount points...');
+ async verifyLoginAndGetMounts(server: VaultServer, credentials: VaultCredentials): Promise {
+ console.log('⥠Verifying login and fetching mount points...')
try {
- const client = this.createClient(server, credentials);
- const mounts = await client.listMounts();
+ const client = this.createClient(server, credentials)
+ const mounts = await client.listMounts()
- console.log('đ Raw mount points from API:', mounts);
+ console.log('đ Raw mount points from API:', mounts)
// Convert to array and filter for KV secret engines
- const mountPoints: MountPoint[] = [];
+ const mountPoints: MountPoint[] = []
for (const [path, mount] of Object.entries(mounts)) {
// Only include KV secret engines
@@ -234,18 +203,21 @@ class VaultApiService {
accessor: mount.accessor,
config: mount.config,
options: mount.options || {},
- });
+ })
}
}
- console.log(`â Found ${mountPoints.length} KV mount point(s):`, mountPoints.map(m => `${m.path} (v${m.options?.version || '1'})`));
- return mountPoints;
+ console.log(
+ `â Found ${mountPoints.length} KV mount point(s):`,
+ mountPoints.map(m => `${m.path} (v${m.options?.version || '1'})`)
+ )
+ return mountPoints
} catch (error) {
if (error instanceof VaultError) {
- console.error('â Login verification failed:', error.message);
- throw error;
+ console.error('â Login verification failed:', error.message)
+ throw error
}
- throw new VaultError('Failed to verify login');
+ throw new VaultError('Failed to verify login')
}
}
@@ -260,23 +232,23 @@ class VaultApiService {
currentDepth: number = 0,
mountPoint?: string
): Promise {
- const config = loadConfig();
+ const config = loadConfig()
// Check depth limit
if (currentDepth >= config.search.maxDepth) {
- console.warn(`â Max depth ${config.search.maxDepth} reached at ${basePath}`);
- return [];
+ console.warn(`â Max depth ${config.search.maxDepth} reached at ${basePath}`)
+ return []
}
- const results: SearchResult[] = [];
+ const results: SearchResult[] = []
try {
// List items at current path
- const items = await this.listSecrets(server, credentials, basePath);
+ const items = await this.listSecrets(server, credentials, basePath)
for (const item of items) {
- const fullPath = basePath ? `${basePath}${item}` : item;
- const isDirectory = item.endsWith('/');
+ const fullPath = basePath ? `${basePath}${item}` : item
+ const isDirectory = item.endsWith('/')
// Check if this path matches the search term
if (fullPath.toLowerCase().includes(searchTerm.toLowerCase())) {
@@ -285,88 +257,65 @@ class VaultApiService {
isDirectory,
depth: currentDepth,
mountPoint,
- });
+ })
// Stop if we've reached max results
if (results.length >= config.search.maxResults) {
- console.warn(
- `â Max results ${config.search.maxResults} reached`
- );
- return results;
+ console.warn(`â Max results ${config.search.maxResults} reached`)
+ return results
}
}
// If it's a directory, recursively search it
if (isDirectory) {
- const subResults = await this.searchPaths(
- server,
- credentials,
- fullPath,
- searchTerm,
- currentDepth + 1,
- mountPoint
- );
- results.push(...subResults);
+ const subResults = await this.searchPaths(server, credentials, fullPath, searchTerm, currentDepth + 1, mountPoint)
+ results.push(...subResults)
// Stop if we've reached max results
if (results.length >= config.search.maxResults) {
- console.warn(
- `â Max results ${config.search.maxResults} reached`
- );
- return results.slice(0, config.search.maxResults);
+ console.warn(`â Max results ${config.search.maxResults} reached`)
+ return results.slice(0, config.search.maxResults)
}
}
}
} catch (error) {
- console.error(`Error searching path ${basePath}:`, error);
+ console.error(`Error searching path ${basePath}:`, error)
}
- return results;
+ return results
}
/**
* Search across all mount points
*/
- async searchAllMounts(
- server: VaultServer,
- credentials: VaultCredentials,
- mountPoints: MountPoint[],
- searchTerm: string
- ): Promise {
- console.log(`đ Searching across ${mountPoints.length} mount point(s)...`);
+ async searchAllMounts(server: VaultServer, credentials: VaultCredentials, mountPoints: MountPoint[], searchTerm: string): Promise {
+ console.log(`đ Searching across ${mountPoints.length} mount point(s)...`)
- const allResults: SearchResult[] = [];
- const config = loadConfig();
+ const allResults: SearchResult[] = []
+ const config = loadConfig()
for (const mount of mountPoints) {
- console.log(` â Searching in ${mount.path}/`);
+ console.log(` â Searching in ${mount.path}/`)
try {
// Search this mount point (KV v2 enforced)
- const results = await this.searchPaths(
- server,
- credentials,
- `${mount.path}/`,
- searchTerm,
- 0,
- mount.path
- );
+ const results = await this.searchPaths(server, credentials, `${mount.path}/`, searchTerm, 0, mount.path)
- allResults.push(...results);
+ allResults.push(...results)
// Stop if we've hit the global max results
if (allResults.length >= config.search.maxResults) {
- console.warn(`â Max results ${config.search.maxResults} reached`);
- return allResults.slice(0, config.search.maxResults);
+ console.warn(`â Max results ${config.search.maxResults} reached`)
+ return allResults.slice(0, config.search.maxResults)
}
} catch (error) {
- console.error(` â Error searching ${mount.path}:`, error);
+ console.error(` â Error searching ${mount.path}:`, error)
// Continue with other mount points even if one fails
}
}
- console.log(`â Found ${allResults.length} total result(s) across all mounts`);
- return allResults;
+ console.log(`â Found ${allResults.length} total result(s) across all mounts`)
+ return allResults
}
/**
@@ -376,72 +325,58 @@ class VaultApiService {
try {
const client = this.createClient(server, {
serverId: server.id,
- authMethod: 'token'
- });
- const health = await client.health();
- console.log('â Vault server health:', health);
- return health.initialized && !health.sealed;
+ authMethod: 'token',
+ })
+ const health = await client.health()
+ console.log('â Vault server health:', health)
+ return health.initialized && !health.sealed
} catch (error) {
- console.error('â Failed to connect to Vault:', error);
- return false;
+ console.error('â Failed to connect to Vault:', error)
+ return false
}
}
/**
* Authenticate with username/password
*/
- async loginUserpass(
- server: VaultServer,
- username: string,
- password: string
- ): Promise {
+ async loginUserpass(server: VaultServer, username: string, password: string): Promise {
const client = this.createClient(server, {
serverId: server.id,
authMethod: 'userpass',
- });
- return await client.loginUserpass(username, password);
+ })
+ return await client.loginUserpass(username, password)
}
/**
* Authenticate with LDAP
*/
- async loginLdap(
- server: VaultServer,
- username: string,
- password: string
- ): Promise {
+ async loginLdap(server: VaultServer, username: string, password: string): Promise {
const client = this.createClient(server, {
serverId: server.id,
authMethod: 'ldap',
- });
- return await client.loginLdap(username, password);
+ })
+ return await client.loginLdap(username, password)
}
/**
* Get current token information
*/
- async getTokenInfo(
- server: VaultServer,
- credentials: VaultCredentials
- ): Promise {
- const client = this.createClient(server, credentials);
- return await client.tokenLookupSelf();
+ async getTokenInfo(server: VaultServer, credentials: VaultCredentials): Promise {
+ const client = this.createClient(server, credentials)
+ return await client.tokenLookupSelf()
}
/**
* Revoke current token (logout)
*/
- async logout(
- server: VaultServer,
- credentials: VaultCredentials
- ): Promise {
- const client = this.createClient(server, credentials);
- await client.tokenRevokeSelf();
+ async logout(server: VaultServer, credentials: VaultCredentials): Promise {
+ const client = this.createClient(server, credentials)
+ await client.tokenRevokeSelf()
}
}
// Export singleton instance
-export const vaultApi = new VaultApiService();
+export const vaultApi = new VaultApiService()
// Export VaultError for error handling
-export { VaultError };
+export { VaultError }
diff --git a/src/services/vaultClient.ts b/src/services/vaultClient.ts
index 5766edc..5995183 100644
--- a/src/services/vaultClient.ts
+++ b/src/services/vaultClient.ts
@@ -1,445 +1,427 @@
-import { VaultServer, VaultCredentials } from '../types';
+import { VaultServer, VaultCredentials } from '../types'
/**
* Configuration options for VaultClient
*/
export interface VaultClientOptions {
- server: VaultServer;
- credentials: VaultCredentials;
- timeout?: number;
- retries?: number;
- kvVersion?: 1 | 2; // KV secret engine version
+ server: VaultServer
+ credentials: VaultCredentials
+ timeout?: number
+ retries?: number
+ kvVersion?: 1 | 2 // KV secret engine version
}
/**
* Vault API error with additional context
*/
export class VaultError extends Error {
- constructor(
- message: string,
- public statusCode?: number,
- public errors?: string[]
- ) {
- super(message);
- this.name = 'VaultError';
- }
+ constructor(
+ message: string,
+ public statusCode?: number,
+ public errors?: string[]
+ ) {
+ super(message)
+ this.name = 'VaultError'
+ }
}
/**
* Browser-compatible HashiCorp Vault client
- *
+ *
* This client provides a clean interface to the Vault HTTP API
* with proper error handling, authentication, and type safety.
* Supports both KV v1 and KV v2 secret engines.
*/
export class VaultClient {
- private baseUrl: string;
- private token?: string;
- private timeout: number;
- private retries: number;
- private kvVersion: 1 | 2;
+ private baseUrl: string
+ private token?: string
+ private timeout: number
+ private retries: number
+ private kvVersion: 1 | 2
- constructor(options: VaultClientOptions) {
- this.baseUrl = options.server.url.replace(/\/$/, ''); // Remove trailing slash
- this.token = options.credentials.token;
- this.timeout = options.timeout || 30000; // 30 seconds default
- this.retries = options.retries || 2;
- this.kvVersion = options.kvVersion || 2; // Default to KV v2 (most common)
+ constructor(options: VaultClientOptions) {
+ this.baseUrl = options.server.url.replace(/\/$/, '') // Remove trailing slash
+ this.token = options.credentials.token
+ this.timeout = options.timeout || 30000 // 30 seconds default
+ this.retries = options.retries || 2
+ this.kvVersion = options.kvVersion || 2 // Default to KV v2 (most common)
+ }
+
+ /**
+ * Transform a path based on KV version
+ * KV v2 uses /data/ for reads/writes and /metadata/ for lists
+ */
+ private transformPath(path: string, operation: 'data' | 'metadata' | 'none' = 'none'): string {
+ const normalized = path.replace(/^\/+/, '').replace(/\/+$/, '')
+
+ if (this.kvVersion === 1) {
+ return normalized
}
- /**
- * Transform a path based on KV version
- * KV v2 uses /data/ for reads/writes and /metadata/ for lists
- */
- private transformPath(path: string, operation: 'data' | 'metadata' | 'none' = 'none'): string {
- const normalized = path.replace(/^\/+/, '').replace(/\/+$/, '');
-
- if (this.kvVersion === 1) {
- return normalized;
- }
-
- // KV v2 path transformation
- // Check if path already has /data/ or /metadata/
- if (normalized.includes('/data/') || normalized.includes('/metadata/')) {
- return normalized;
- }
-
- // For KV v2, transform the path
- const parts = normalized.split('/');
- const mount = parts[0]; // e.g., "secret"
- const rest = parts.slice(1).join('/');
-
- if (operation === 'data') {
- return `${mount}/data/${rest}`;
- } else if (operation === 'metadata') {
- return `${mount}/metadata/${rest}`;
- }
-
- return normalized;
+ // KV v2 path transformation
+ // Check if path already has /data/ or /metadata/
+ if (normalized.includes('/data/') || normalized.includes('/metadata/')) {
+ return normalized
}
- /**
- * Make an HTTP request to the Vault API
- */
- private async request(
- path: string,
- options: RequestInit = {}
- ): Promise {
- const url = `${this.baseUrl}/v1/${path.replace(/^\//, '')}`;
+ // For KV v2, transform the path
+ const parts = normalized.split('/')
+ const mount = parts[0] // e.g., "secret"
+ const rest = parts.slice(1).join('/')
- const headers: HeadersInit = {
- 'Content-Type': 'application/json',
- ...options.headers,
- };
+ if (operation === 'data') {
+ return `${mount}/data/${rest}`
+ } else if (operation === 'metadata') {
+ return `${mount}/metadata/${rest}`
+ }
- // Add authentication token if available
- if (this.token) {
- headers['X-Vault-Token'] = this.token;
- }
+ return normalized
+ }
- const controller = new AbortController();
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
+ /**
+ * Make an HTTP request to the Vault API
+ */
+ private async request(path: string, options: RequestInit = {}): Promise {
+ const url = `${this.baseUrl}/v1/${path.replace(/^\//, '')}`
+ const headers: HeadersInit = {
+ 'Content-Type': 'application/json',
+ ...options.headers,
+ }
+
+ // Add authentication token if available
+ if (this.token) {
+ headers['X-Vault-Token'] = this.token
+ }
+
+ const controller = new AbortController()
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout)
+
+ try {
+ const response = await fetch(url, {
+ ...options,
+ headers,
+ signal: controller.signal,
+ })
+
+ clearTimeout(timeoutId)
+
+ // Handle non-OK responses
+ if (!response.ok) {
+ let errorData: { errors?: string[] } = {}
try {
- const response = await fetch(url, {
- ...options,
- headers,
- signal: controller.signal,
- });
-
- clearTimeout(timeoutId);
-
- // Handle non-OK responses
- if (!response.ok) {
- let errorData: { errors?: string[] } = {};
- try {
- errorData = await response.json();
- } catch {
- // Response might not be JSON
- }
-
- throw new VaultError(
- `Vault API error: ${response.statusText}`,
- response.status,
- errorData.errors
- );
- }
-
- // Handle empty responses (e.g., 204 No Content)
- if (response.status === 204 || response.headers.get('content-length') === '0') {
- return null as T;
- }
-
- return await response.json();
- } catch (error) {
- clearTimeout(timeoutId);
-
- if (error instanceof VaultError) {
- throw error;
- }
-
- if (error instanceof Error) {
- if (error.name === 'AbortError') {
- throw new VaultError('Request timeout');
- }
- throw new VaultError(`Network error: ${error.message}`);
- }
-
- throw new VaultError('Unknown error occurred');
- }
- }
-
- /**
- * Make a request with automatic retries
- */
- private async requestWithRetry(
- path: string,
- options: RequestInit = {},
- attempt = 0
- ): Promise {
- try {
- return await this.request(path, options);
- } catch (error) {
- // Only retry on network errors, not on 4xx client errors
- if (
- attempt < this.retries &&
- error instanceof VaultError &&
- (!error.statusCode || error.statusCode >= 500)
- ) {
- // Exponential backoff
- const delay = Math.pow(2, attempt) * 1000;
- await new Promise(resolve => setTimeout(resolve, delay));
- return this.requestWithRetry(path, options, attempt + 1);
- }
- throw error;
- }
- }
-
- /**
- * List secrets at a given path
- *
- * For KV v2, this uses the /metadata/ endpoint
- * For KV v1, this uses the path directly
- */
- async list(path: string): Promise {
- const normalizedPath = this.transformPath(path, 'metadata');
-
- // Ensure path ends with / for LIST operations
- const listPath = normalizedPath.endsWith('/') ? normalizedPath : `${normalizedPath}/`;
-
- const response = await this.requestWithRetry<{ data: { keys: string[] } }>(
- `${listPath}?list=true`,
- { method: 'LIST' }
- );
- return response?.data?.keys || [];
- }
-
- /**
- * Read a secret from Vault
- *
- * For KV v2, this uses the /data/ endpoint
- * For KV v1, this uses the path directly
- */
- async read>(path: string): Promise {
- const normalizedPath = this.transformPath(path, 'data');
-
- if (this.kvVersion === 2) {
- // KV v2 returns { data: { data: {...}, metadata: {...} } }
- const response = await this.requestWithRetry<{
- data: { data: T; metadata?: unknown };
- }>(normalizedPath, { method: 'GET' });
- return response?.data?.data || null;
- } else {
- // KV v1 returns { data: {...} }
- const response = await this.requestWithRetry<{ data: T }>(
- normalizedPath,
- { method: 'GET' }
- );
- return response?.data || null;
- }
- }
-
- /**
- * Write a secret to Vault
- *
- * For KV v2, this uses the /data/ endpoint
- * For KV v1, this uses the path directly
- */
- async write>(
- path: string,
- data: T
- ): Promise {
- const normalizedPath = this.transformPath(path, 'data');
-
- const body = this.kvVersion === 2 ? { data } : data;
-
- await this.requestWithRetry(normalizedPath, {
- method: 'POST',
- body: JSON.stringify(body),
- });
- }
-
- /**
- * Delete a secret from Vault
- *
- * For KV v2, this uses the /data/ endpoint (soft delete)
- * For KV v1, this uses the path directly (hard delete)
- */
- async delete(path: string): Promise {
- const normalizedPath = this.transformPath(path, 'data');
- await this.requestWithRetry(normalizedPath, {
- method: 'DELETE',
- });
- }
-
- /**
- * Read secret metadata (KV v2 only)
- * Returns version history, created time, etc.
- */
- async readMetadata(path: string): Promise<{
- versions: Record;
- current_version: number;
- oldest_version: number;
- created_time: string;
- updated_time: string;
- } | null> {
- if (this.kvVersion !== 2) {
- throw new VaultError('Metadata is only available in KV v2');
- }
-
- const normalizedPath = this.transformPath(path, 'metadata');
- const response = await this.requestWithRetry<{
- data: {
- versions: Record;
- current_version: number;
- oldest_version: number;
- created_time: string;
- updated_time: string;
- };
- }>(normalizedPath, { method: 'GET' });
-
- return response?.data || null;
- }
-
- /**
- * Get health status of Vault server
- */
- async health(): Promise<{
- initialized: boolean;
- sealed: boolean;
- standby: boolean;
- version: string;
- }> {
- // Health endpoint doesn't require authentication
- const url = `${this.baseUrl}/v1/sys/health`;
- const response = await fetch(url);
- return response.json();
- }
-
- /**
- * Authenticate with username/password
- */
- async loginUserpass(username: string, password: string): Promise {
- const response = await this.request<{
- auth: { client_token: string };
- }>('auth/userpass/login/' + username, {
- method: 'POST',
- body: JSON.stringify({ password }),
- });
-
- this.token = response.auth.client_token;
- return this.token;
- }
-
- /**
- * Authenticate with LDAP
- */
- async loginLdap(username: string, password: string): Promise {
- const response = await this.request<{
- auth: { client_token: string };
- }>('auth/ldap/login/' + username, {
- method: 'POST',
- body: JSON.stringify({ password }),
- });
-
- this.token = response.auth.client_token;
- return this.token;
- }
-
- /**
- * Lookup current token info
- */
- async tokenLookupSelf(): Promise<{
- data: {
- accessor: string;
- creation_time: number;
- creation_ttl: number;
- display_name: string;
- entity_id: string;
- expire_time: string | null;
- explicit_max_ttl: number;
- id: string;
- issue_time: string;
- meta: Record;
- num_uses: number;
- orphan: boolean;
- path: string;
- policies: string[];
- renewable: boolean;
- ttl: number;
- type: string;
- };
- }> {
- return this.requestWithRetry('auth/token/lookup-self', {
- method: 'GET',
- });
- }
-
- /**
- * Revoke current token (logout)
- */
- async tokenRevokeSelf(): Promise {
- await this.requestWithRetry('auth/token/revoke-self', {
- method: 'POST',
- });
- this.token = undefined;
- }
-
- /**
- * List all secret engine mount points
- * This also verifies the token is valid
- */
- async listMounts(): Promise<{
- [key: string]: {
- type: string;
- description: string;
- accessor: string;
- config: {
- default_lease_ttl: number;
- max_lease_ttl: number;
- };
- options: {
- version?: string;
- } | null;
- };
- }> {
- const response = await this.requestWithRetry<{
- data: {
- auth?: {
- [key: string]: {
- type: string;
- description: string;
- accessor: string;
- config: Record;
- options: Record | null;
- };
- };
- secret?: {
- [key: string]: {
- type: string;
- description: string;
- accessor: string;
- config: {
- default_lease_ttl: number;
- max_lease_ttl: number;
- };
- options: {
- version?: string;
- } | null;
- };
- };
- };
- }>('sys/internal/ui/mounts', { method: 'GET' });
-
- // Return only the secret engines (not auth methods)
- return response?.data?.secret || {};
- }
-
- /**
- * Detect KV version for a mount point
- */
- async detectKvVersion(mountPath: string): Promise<1 | 2> {
- try {
- const response = await this.requestWithRetry<{
- data: {
- options: { version?: string };
- type: string;
- };
- }>(`sys/internal/ui/mounts/${mountPath}`, { method: 'GET' });
-
- const version = response?.data?.options?.version;
- return version === '2' ? 2 : 1;
+ errorData = await response.json()
} catch {
- // If detection fails, assume v2 (most common)
- return 2;
+ // Response might not be JSON
}
+
+ throw new VaultError(`Vault API error: ${response.statusText}`, response.status, errorData.errors)
+ }
+
+ // Handle empty responses (e.g., 204 No Content)
+ if (response.status === 204 || response.headers.get('content-length') === '0') {
+ return null as T
+ }
+
+ return await response.json()
+ } catch (error) {
+ clearTimeout(timeoutId)
+
+ if (error instanceof VaultError) {
+ throw error
+ }
+
+ if (error instanceof Error) {
+ if (error.name === 'AbortError') {
+ throw new VaultError('Request timeout')
+ }
+ throw new VaultError(`Network error: ${error.message}`)
+ }
+
+ throw new VaultError('Unknown error occurred')
}
+ }
+
+ /**
+ * Make a request with automatic retries
+ */
+ private async requestWithRetry(path: string, options: RequestInit = {}, attempt = 0): Promise {
+ try {
+ return await this.request(path, options)
+ } catch (error) {
+ // Only retry on network errors, not on 4xx client errors
+ if (attempt < this.retries && error instanceof VaultError && (!error.statusCode || error.statusCode >= 500)) {
+ // Exponential backoff
+ const delay = Math.pow(2, attempt) * 1000
+ await new Promise(resolve => setTimeout(resolve, delay))
+ return this.requestWithRetry(path, options, attempt + 1)
+ }
+ throw error
+ }
+ }
+
+ /**
+ * List secrets at a given path
+ *
+ * For KV v2, this uses the /metadata/ endpoint
+ * For KV v1, this uses the path directly
+ */
+ async list(path: string): Promise {
+ const normalizedPath = this.transformPath(path, 'metadata')
+
+ // Ensure path ends with / for LIST operations
+ const listPath = normalizedPath.endsWith('/') ? normalizedPath : `${normalizedPath}/`
+
+ const response = await this.requestWithRetry<{ data: { keys: string[] } }>(`${listPath}?list=true`, { method: 'LIST' })
+ return response?.data?.keys || []
+ }
+
+ /**
+ * Read a secret from Vault
+ *
+ * For KV v2, this uses the /data/ endpoint
+ * For KV v1, this uses the path directly
+ */
+ async read>(path: string): Promise {
+ const normalizedPath = this.transformPath(path, 'data')
+
+ if (this.kvVersion === 2) {
+ // KV v2 returns { data: { data: {...}, metadata: {...} } }
+ const response = await this.requestWithRetry<{
+ data: { data: T; metadata?: unknown }
+ }>(normalizedPath, { method: 'GET' })
+ return response?.data?.data || null
+ } else {
+ // KV v1 returns { data: {...} }
+ const response = await this.requestWithRetry<{ data: T }>(normalizedPath, { method: 'GET' })
+ return response?.data || null
+ }
+ }
+
+ /**
+ * Write a secret to Vault
+ *
+ * For KV v2, this uses the /data/ endpoint
+ * For KV v1, this uses the path directly
+ */
+ async write>(path: string, data: T): Promise {
+ const normalizedPath = this.transformPath(path, 'data')
+
+ const body = this.kvVersion === 2 ? { data } : data
+
+ await this.requestWithRetry(normalizedPath, {
+ method: 'POST',
+ body: JSON.stringify(body),
+ })
+ }
+
+ /**
+ * Delete a secret from Vault
+ *
+ * For KV v2, this uses the /data/ endpoint (soft delete)
+ * For KV v1, this uses the path directly (hard delete)
+ */
+ async delete(path: string): Promise {
+ const normalizedPath = this.transformPath(path, 'data')
+ await this.requestWithRetry(normalizedPath, {
+ method: 'DELETE',
+ })
+ }
+
+ /**
+ * Read secret metadata (KV v2 only)
+ * Returns version history, created time, etc.
+ */
+ async readMetadata(path: string): Promise<{
+ versions: Record<
+ string,
+ {
+ created_time: string
+ deletion_time: string
+ destroyed: boolean
+ }
+ >
+ current_version: number
+ oldest_version: number
+ created_time: string
+ updated_time: string
+ } | null> {
+ if (this.kvVersion !== 2) {
+ throw new VaultError('Metadata is only available in KV v2')
+ }
+
+ const normalizedPath = this.transformPath(path, 'metadata')
+ const response = await this.requestWithRetry<{
+ data: {
+ versions: Record<
+ string,
+ {
+ created_time: string
+ deletion_time: string
+ destroyed: boolean
+ }
+ >
+ current_version: number
+ oldest_version: number
+ created_time: string
+ updated_time: string
+ }
+ }>(normalizedPath, { method: 'GET' })
+
+ return response?.data || null
+ }
+
+ /**
+ * Get health status of Vault server
+ */
+ async health(): Promise<{
+ initialized: boolean
+ sealed: boolean
+ standby: boolean
+ version: string
+ }> {
+ // Health endpoint doesn't require authentication
+ const url = `${this.baseUrl}/v1/sys/health`
+ const response = await fetch(url)
+ return response.json()
+ }
+
+ /**
+ * Authenticate with username/password
+ */
+ async loginUserpass(username: string, password: string): Promise {
+ const response = await this.request<{
+ auth: { client_token: string }
+ }>('auth/userpass/login/' + username, {
+ method: 'POST',
+ body: JSON.stringify({ password }),
+ })
+
+ this.token = response.auth.client_token
+ return this.token
+ }
+
+ /**
+ * Authenticate with LDAP
+ */
+ async loginLdap(username: string, password: string): Promise {
+ const response = await this.request<{
+ auth: { client_token: string }
+ }>('auth/ldap/login/' + username, {
+ method: 'POST',
+ body: JSON.stringify({ password }),
+ })
+
+ this.token = response.auth.client_token
+ return this.token
+ }
+
+ /**
+ * Lookup current token info
+ */
+ async tokenLookupSelf(): Promise<{
+ data: {
+ accessor: string
+ creation_time: number
+ creation_ttl: number
+ display_name: string
+ entity_id: string
+ expire_time: string | null
+ explicit_max_ttl: number
+ id: string
+ issue_time: string
+ meta: Record
+ num_uses: number
+ orphan: boolean
+ path: string
+ policies: string[]
+ renewable: boolean
+ ttl: number
+ type: string
+ }
+ }> {
+ return this.requestWithRetry('auth/token/lookup-self', {
+ method: 'GET',
+ })
+ }
+
+ /**
+ * Revoke current token (logout)
+ */
+ async tokenRevokeSelf(): Promise {
+ await this.requestWithRetry('auth/token/revoke-self', {
+ method: 'POST',
+ })
+ this.token = undefined
+ }
+
+ /**
+ * List all secret engine mount points
+ * This also verifies the token is valid
+ */
+ async listMounts(): Promise<{
+ [key: string]: {
+ type: string
+ description: string
+ accessor: string
+ config: {
+ default_lease_ttl: number
+ max_lease_ttl: number
+ }
+ options: {
+ version?: string
+ } | null
+ }
+ }> {
+ const response = await this.requestWithRetry<{
+ data: {
+ auth?: {
+ [key: string]: {
+ type: string
+ description: string
+ accessor: string
+ config: Record
+ options: Record | null
+ }
+ }
+ secret?: {
+ [key: string]: {
+ type: string
+ description: string
+ accessor: string
+ config: {
+ default_lease_ttl: number
+ max_lease_ttl: number
+ }
+ options: {
+ version?: string
+ } | null
+ }
+ }
+ }
+ }>('sys/internal/ui/mounts', { method: 'GET' })
+
+ // Return only the secret engines (not auth methods)
+ return response?.data?.secret || {}
+ }
+
+ /**
+ * Detect KV version for a mount point
+ */
+ async detectKvVersion(mountPath: string): Promise<1 | 2> {
+ try {
+ const response = await this.requestWithRetry<{
+ data: {
+ options: { version?: string }
+ type: string
+ }
+ }>(`sys/internal/ui/mounts/${mountPath}`, { method: 'GET' })
+
+ const version = response?.data?.options?.version
+ return version === '2' ? 2 : 1
+ } catch {
+ // If detection fails, assume v2 (most common)
+ return 2
+ }
+ }
}
diff --git a/src/style.css b/src/style.css
index 23b87cc..d4c4d30 100644
--- a/src/style.css
+++ b/src/style.css
@@ -4,27 +4,27 @@
/* Custom scrollbar styling */
::-webkit-scrollbar {
- width: 8px;
- height: 8px;
+ width: 8px;
+ height: 8px;
}
::-webkit-scrollbar-track {
- @apply bg-base-300;
+ @apply bg-base-300;
}
::-webkit-scrollbar-thumb {
- @apply bg-base-content/30 rounded;
+ @apply bg-base-content/30 rounded;
}
::-webkit-scrollbar-thumb:hover {
- @apply bg-base-content/50;
+ @apply bg-base-content/50;
}
/* Code blocks */
pre {
- @apply bg-base-300 p-4 rounded-lg overflow-x-auto text-sm;
+ @apply bg-base-300 p-4 rounded-lg overflow-x-auto text-sm;
}
code {
- @apply bg-base-300 px-2 py-1 rounded text-sm font-mono;
-}
\ No newline at end of file
+ @apply bg-base-300 px-2 py-1 rounded text-sm font-mono;
+}
diff --git a/src/types.ts b/src/types.ts
index 8a48e9d..74338c8 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,50 +1,51 @@
export interface VaultServer {
- id: string;
- name: string;
- url: string;
- description?: string;
+ id: string
+ name: string
+ url: string
+ description?: string
// KV v2 is enforced - no version selection needed
- savedCredentials?: VaultCredentials; // Optional saved credentials (WARNING: stored in localStorage)
+ savedCredentials?: VaultCredentials // Optional saved credentials (WARNING: stored in localStorage)
}
export interface VaultCredentials {
- serverId: string;
- token?: string;
- username?: string;
- password?: string;
- authMethod: 'token' | 'userpass' | 'ldap';
+ serverId: string
+ token?: string
+ username?: string
+ password?: string
+ authMethod: 'token' | 'userpass' | 'ldap'
}
export interface MountPoint {
- path: string;
- type: string;
- description: string;
- accessor: string;
+ path: string
+ type: string
+ description: string
+ accessor: string
config: {
- default_lease_ttl: number;
- max_lease_ttl: number;
- };
- options: {
- version?: string;
- } | Record;
+ default_lease_ttl: number
+ max_lease_ttl: number
+ }
+ options:
+ | {
+ version?: string
+ }
+ | Record
}
export interface VaultConnection {
- server: VaultServer;
- credentials: VaultCredentials;
- isConnected: boolean;
- lastConnected?: Date;
- mountPoints?: MountPoint[];
+ server: VaultServer
+ credentials: VaultCredentials
+ isConnected: boolean
+ lastConnected?: Date
+ mountPoints?: MountPoint[]
}
export interface VaultSecret {
- path: string;
- data: Record;
+ path: string
+ data: Record
metadata?: {
- created_time: string;
- deletion_time: string;
- destroyed: boolean;
- version: number;
- };
+ created_time: string
+ deletion_time: string
+ destroyed: boolean
+ version: number
+ }
}
-
diff --git a/src/utils/cache.ts b/src/utils/cache.ts
index 05711b7..2f0a598 100644
--- a/src/utils/cache.ts
+++ b/src/utils/cache.ts
@@ -1,166 +1,166 @@
-import { loadConfig } from '../config';
+import { loadConfig } from '../config'
export interface CacheEntry {
- data: T;
- timestamp: number;
- size: number; // Size in bytes
+ data: T
+ timestamp: number
+ size: number // Size in bytes
}
export interface CacheStats {
- totalSize: number; // Total size in bytes
- entryCount: number;
- oldestEntry: number | null;
- newestEntry: number | null;
+ totalSize: number // Total size in bytes
+ entryCount: number
+ oldestEntry: number | null
+ newestEntry: number | null
}
class VaultCache {
- private readonly CACHE_KEY = 'vaultApiCache';
- private cache: Map>;
+ private readonly CACHE_KEY = 'vaultApiCache'
+ private cache: Map>
constructor() {
- this.cache = this.loadFromStorage();
+ this.cache = this.loadFromStorage()
}
private loadFromStorage(): Map> {
try {
- const stored = localStorage.getItem(this.CACHE_KEY);
+ const stored = localStorage.getItem(this.CACHE_KEY)
if (stored) {
- const parsed = JSON.parse(stored);
- return new Map(Object.entries(parsed));
+ const parsed = JSON.parse(stored)
+ return new Map(Object.entries(parsed))
}
} catch (error) {
- console.error('Failed to load cache from storage:', error);
+ console.error('Failed to load cache from storage:', error)
}
- return new Map();
+ return new Map()
}
private saveToStorage(): void {
try {
- const obj = Object.fromEntries(this.cache);
- localStorage.setItem(this.CACHE_KEY, JSON.stringify(obj));
+ const obj = Object.fromEntries(this.cache)
+ localStorage.setItem(this.CACHE_KEY, JSON.stringify(obj))
} catch (error) {
- console.error('Failed to save cache to storage:', error);
+ console.error('Failed to save cache to storage:', error)
// If quota exceeded, clear old entries and retry
- this.evictOldEntries(0.5); // Remove 50% of entries
+ this.evictOldEntries(0.5) // Remove 50% of entries
try {
- const obj = Object.fromEntries(this.cache);
- localStorage.setItem(this.CACHE_KEY, JSON.stringify(obj));
+ const obj = Object.fromEntries(this.cache)
+ localStorage.setItem(this.CACHE_KEY, JSON.stringify(obj))
} catch (retryError) {
- console.error('Failed to save cache after cleanup:', retryError);
+ console.error('Failed to save cache after cleanup:', retryError)
}
}
}
private calculateSize(data: unknown): number {
// Rough estimation of size in bytes
- return new Blob([JSON.stringify(data)]).size;
+ return new Blob([JSON.stringify(data)]).size
}
private evictOldEntries(fraction: number): void {
- const entries = Array.from(this.cache.entries());
- entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
- const toRemove = Math.floor(entries.length * fraction);
+ const entries = Array.from(this.cache.entries())
+ entries.sort((a, b) => a[1].timestamp - b[1].timestamp)
+ const toRemove = Math.floor(entries.length * fraction)
for (let i = 0; i < toRemove; i++) {
- this.cache.delete(entries[i][0]);
+ this.cache.delete(entries[i][0])
}
}
private enforceSizeLimit(): void {
- const config = loadConfig();
- if (!config.cache.enabled) return;
+ const config = loadConfig()
+ if (!config.cache.enabled) return
- const maxBytes = config.cache.maxSizeMB * 1024 * 1024;
- let totalSize = 0;
+ const maxBytes = config.cache.maxSizeMB * 1024 * 1024
+ let totalSize = 0
// Calculate total size
for (const entry of this.cache.values()) {
- totalSize += entry.size;
+ totalSize += entry.size
}
// If over limit, remove oldest entries
if (totalSize > maxBytes) {
- const entries = Array.from(this.cache.entries());
- entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
+ const entries = Array.from(this.cache.entries())
+ entries.sort((a, b) => a[1].timestamp - b[1].timestamp)
for (const [key, entry] of entries) {
- if (totalSize <= maxBytes * 0.8) break; // Remove until 80% of limit
- totalSize -= entry.size;
- this.cache.delete(key);
+ if (totalSize <= maxBytes * 0.8) break // Remove until 80% of limit
+ totalSize -= entry.size
+ this.cache.delete(key)
}
}
}
get(key: string): T | null {
- const config = loadConfig();
- if (!config.cache.enabled) return null;
+ const config = loadConfig()
+ if (!config.cache.enabled) return null
- const entry = this.cache.get(key) as CacheEntry | undefined;
- if (!entry) return null;
+ const entry = this.cache.get(key) as CacheEntry | undefined
+ if (!entry) return null
// Check if entry is expired
- const age = Date.now() - entry.timestamp;
+ const age = Date.now() - entry.timestamp
if (age > config.cache.maxAge) {
- this.cache.delete(key);
- return null;
+ this.cache.delete(key)
+ return null
}
- return entry.data;
+ return entry.data
}
set(key: string, data: T): void {
- const config = loadConfig();
- if (!config.cache.enabled) return;
+ const config = loadConfig()
+ if (!config.cache.enabled) return
- const size = this.calculateSize(data);
+ const size = this.calculateSize(data)
const entry: CacheEntry = {
data,
timestamp: Date.now(),
size,
- };
+ }
- this.cache.set(key, entry as CacheEntry);
- this.enforceSizeLimit();
- this.saveToStorage();
+ this.cache.set(key, entry as CacheEntry)
+ this.enforceSizeLimit()
+ this.saveToStorage()
}
has(key: string): boolean {
- const config = loadConfig();
- if (!config.cache.enabled) return false;
+ const config = loadConfig()
+ if (!config.cache.enabled) return false
- const entry = this.cache.get(key);
- if (!entry) return false;
+ const entry = this.cache.get(key)
+ if (!entry) return false
- const age = Date.now() - entry.timestamp;
+ const age = Date.now() - entry.timestamp
if (age > config.cache.maxAge) {
- this.cache.delete(key);
- return false;
+ this.cache.delete(key)
+ return false
}
- return true;
+ return true
}
delete(key: string): void {
- this.cache.delete(key);
- this.saveToStorage();
+ this.cache.delete(key)
+ this.saveToStorage()
}
clear(): void {
- this.cache.clear();
- this.saveToStorage();
+ this.cache.clear()
+ this.saveToStorage()
}
getStats(): CacheStats {
- let totalSize = 0;
- let oldestEntry: number | null = null;
- let newestEntry: number | null = null;
+ let totalSize = 0
+ let oldestEntry: number | null = null
+ let newestEntry: number | null = null
for (const entry of this.cache.values()) {
- totalSize += entry.size;
+ totalSize += entry.size
if (oldestEntry === null || entry.timestamp < oldestEntry) {
- oldestEntry = entry.timestamp;
+ oldestEntry = entry.timestamp
}
if (newestEntry === null || entry.timestamp > newestEntry) {
- newestEntry = entry.timestamp;
+ newestEntry = entry.timestamp
}
}
@@ -169,34 +169,33 @@ class VaultCache {
entryCount: this.cache.size,
oldestEntry,
newestEntry,
- };
+ }
}
// Clean up expired entries
cleanup(): void {
- const config = loadConfig();
- const now = Date.now();
- const keysToDelete: string[] = [];
+ const config = loadConfig()
+ const now = Date.now()
+ const keysToDelete: string[] = []
for (const [key, entry] of this.cache.entries()) {
if (now - entry.timestamp > config.cache.maxAge) {
- keysToDelete.push(key);
+ keysToDelete.push(key)
}
}
for (const key of keysToDelete) {
- this.cache.delete(key);
+ this.cache.delete(key)
}
if (keysToDelete.length > 0) {
- this.saveToStorage();
+ this.saveToStorage()
}
}
}
// Singleton instance
-export const vaultCache = new VaultCache();
+export const vaultCache = new VaultCache()
// Cleanup expired entries on page load
-vaultCache.cleanup();
-
+vaultCache.cleanup()
diff --git a/tailwind.config.js b/tailwind.config.js
index bccfec8..34c2984 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -9,8 +9,8 @@ export default {
},
plugins: [require("daisyui")],
daisyui: {
- themes: ["dark", "light"],
- darkTheme: "dark",
+ themes: ["business"],
+ darkTheme: "business",
},
}