Files
Danijel Micic 688c2ba52c feat: Phase 1 Quick Wins - Dark mode, presets, history, and enhanced errors
- Add dark/light theme toggle with IndexedDB persistence
- Implement configuration presets (Office365, Gmail, SendGrid, Mailgun, Amazon SES)
- Add test history panel with last 50 tests and click-to-load
- Create IndexedDB wrapper for persistent storage
- Add enhanced error messages with troubleshooting tips
- Improve UX with auto-save last configuration
- Add custom scrollbars and smooth theme transitions
2025-12-15 14:00:32 +11:00

200 lines
6.6 KiB
JavaScript

// IndexedDB Helper for SMTP Tester
// Manages persistent storage for test history, settings, and preferences
const DB_NAME = 'SMTPTesterDB';
const DB_VERSION = 1;
const STORES = {
HISTORY: 'testHistory',
SETTINGS: 'settings',
PREFERENCES: 'preferences'
};
class SMTPDatabase {
constructor() {
this.db = null;
}
// Initialize database
async init() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve(this.db);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Create test history store
if (!db.objectStoreNames.contains(STORES.HISTORY)) {
const historyStore = db.createObjectStore(STORES.HISTORY, {
keyPath: 'id',
autoIncrement: true
});
historyStore.createIndex('timestamp', 'timestamp', { unique: false });
historyStore.createIndex('host', 'host', { unique: false });
}
// Create settings store
if (!db.objectStoreNames.contains(STORES.SETTINGS)) {
db.createObjectStore(STORES.SETTINGS, { keyPath: 'key' });
}
// Create preferences store
if (!db.objectStoreNames.contains(STORES.PREFERENCES)) {
db.createObjectStore(STORES.PREFERENCES, { keyPath: 'key' });
}
};
});
}
// Save test result to history
async saveTest(testData) {
const transaction = this.db.transaction([STORES.HISTORY], 'readwrite');
const store = transaction.objectStore(STORES.HISTORY);
const record = {
...testData,
timestamp: new Date().toISOString()
};
return new Promise((resolve, reject) => {
const request = store.add(record);
request.onsuccess = () => {
this.cleanupOldHistory(); // Keep only last 50 records
resolve(request.result);
};
request.onerror = () => reject(request.error);
});
}
// Get test history (last N records)
async getHistory(limit = 50) {
const transaction = this.db.transaction([STORES.HISTORY], 'readonly');
const store = transaction.objectStore(STORES.HISTORY);
const index = store.index('timestamp');
return new Promise((resolve, reject) => {
const request = index.openCursor(null, 'prev'); // Reverse order (newest first)
const results = [];
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor && results.length < limit) {
results.push(cursor.value);
cursor.continue();
} else {
resolve(results);
}
};
request.onerror = () => reject(request.error);
});
}
// Clean up old history (keep only last 50)
async cleanupOldHistory() {
const transaction = this.db.transaction([STORES.HISTORY], 'readwrite');
const store = transaction.objectStore(STORES.HISTORY);
const index = store.index('timestamp');
return new Promise((resolve, reject) => {
const request = index.openCursor(null, 'prev');
let count = 0;
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
count++;
if (count > 50) {
cursor.delete();
}
cursor.continue();
} else {
resolve();
}
};
request.onerror = () => reject(request.error);
});
}
// Save user settings (last used configuration)
async saveSettings(settings) {
const transaction = this.db.transaction([STORES.SETTINGS], 'readwrite');
const store = transaction.objectStore(STORES.SETTINGS);
const record = {
key: 'lastConfig',
...settings,
updatedAt: new Date().toISOString()
};
return new Promise((resolve, reject) => {
const request = store.put(record);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
// Get user settings
async getSettings() {
const transaction = this.db.transaction([STORES.SETTINGS], 'readonly');
const store = transaction.objectStore(STORES.SETTINGS);
return new Promise((resolve, reject) => {
const request = store.get('lastConfig');
request.onsuccess = () => resolve(request.result || null);
request.onerror = () => reject(request.error);
});
}
// Save preference (e.g., theme)
async savePreference(key, value) {
const transaction = this.db.transaction([STORES.PREFERENCES], 'readwrite');
const store = transaction.objectStore(STORES.PREFERENCES);
const record = { key, value };
return new Promise((resolve, reject) => {
const request = store.put(record);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
// Get preference
async getPreference(key) {
const transaction = this.db.transaction([STORES.PREFERENCES], 'readonly');
const store = transaction.objectStore(STORES.PREFERENCES);
return new Promise((resolve, reject) => {
const request = store.get(key);
request.onsuccess = () => resolve(request.result ? request.result.value : null);
request.onerror = () => reject(request.error);
});
}
// Clear all history
async clearHistory() {
const transaction = this.db.transaction([STORES.HISTORY], 'readwrite');
const store = transaction.objectStore(STORES.HISTORY);
return new Promise((resolve, reject) => {
const request = store.clear();
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
}
// Create singleton instance
const db = new SMTPDatabase();
// Initialize on load
db.init().catch(err => console.error('Failed to initialize database:', err));
// Export for use in other scripts
window.smtpDB = db;