Initial commit: DashCaddy v1.0
Full codebase including API server (32 modules + routes), dashboard frontend, DashCA certificate distribution, installer script, and deployment skills.
This commit is contained in:
259
status/js/error-handler.js
Normal file
259
status/js/error-handler.js
Normal file
@@ -0,0 +1,259 @@
|
||||
/**
|
||||
* Error Handler
|
||||
* Handles errors gracefully without breaking the onboarding tour
|
||||
*/
|
||||
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
class ErrorHandler {
|
||||
constructor() {
|
||||
this.errors = [];
|
||||
this.maxErrors = 50; // Keep last 50 errors
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an error without breaking the tour
|
||||
* @param {string} context - Context where error occurred
|
||||
* @param {Error|string} error - The error object or message
|
||||
* @param {Object} metadata - Additional metadata
|
||||
*/
|
||||
logError(context, error, metadata = {}) {
|
||||
const errorEntry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
context,
|
||||
message: error instanceof Error ? error.message : error,
|
||||
stack: error instanceof Error ? error.stack : null,
|
||||
metadata
|
||||
};
|
||||
|
||||
// Add to errors array
|
||||
this.errors.push(errorEntry);
|
||||
|
||||
// Keep only last maxErrors
|
||||
if (this.errors.length > this.maxErrors) {
|
||||
this.errors.shift();
|
||||
}
|
||||
|
||||
// Log to console
|
||||
console.error(`[Onboarding Error] ${context}:`, error, metadata);
|
||||
|
||||
// Optionally send to error tracking service
|
||||
// this.sendToErrorTracking(errorEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to recover from an error and continue tour
|
||||
* @param {Error} error - The error object
|
||||
* @param {number} currentStep - Current step index
|
||||
* @returns {Object} Recovery action
|
||||
*/
|
||||
recoverFromError(error, currentStep) {
|
||||
const errorType = this.classifyError(error);
|
||||
|
||||
switch (errorType) {
|
||||
case 'ELEMENT_NOT_FOUND':
|
||||
this.logError('Element Not Found', error, { currentStep });
|
||||
return {
|
||||
action: 'SKIP_STEP',
|
||||
nextStep: currentStep + 1,
|
||||
message: 'Target element not found, skipping to next step'
|
||||
};
|
||||
|
||||
case 'STORAGE_UNAVAILABLE':
|
||||
this.logError('Storage Unavailable', error);
|
||||
return {
|
||||
action: 'USE_MEMORY_STORAGE',
|
||||
message: 'Local storage unavailable, using in-memory storage'
|
||||
};
|
||||
|
||||
case 'DRIVER_NOT_LOADED':
|
||||
this.logError('Driver.js Not Loaded', error);
|
||||
return {
|
||||
action: 'ABORT_TOUR',
|
||||
message: 'Driver.js library not loaded, cannot start tour'
|
||||
};
|
||||
|
||||
case 'INVALID_TOOLTIP':
|
||||
this.logError('Invalid Tooltip Configuration', error, { currentStep });
|
||||
return {
|
||||
action: 'SKIP_STEP',
|
||||
nextStep: currentStep + 1,
|
||||
message: 'Invalid tooltip configuration, skipping'
|
||||
};
|
||||
|
||||
case 'THEME_DETECTION_FAILED':
|
||||
this.logError('Theme Detection Failed', error);
|
||||
return {
|
||||
action: 'USE_DEFAULT_THEME',
|
||||
message: 'Using default dark theme'
|
||||
};
|
||||
|
||||
default:
|
||||
this.logError('Unknown Error', error, { currentStep });
|
||||
return {
|
||||
action: 'ABORT_TOUR',
|
||||
message: 'Unexpected error occurred, aborting tour'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify error type
|
||||
* @private
|
||||
* @param {Error} error - The error object
|
||||
* @returns {string} Error type
|
||||
*/
|
||||
classifyError(error) {
|
||||
const message = error.message || error.toString();
|
||||
|
||||
if (message.includes('element') && message.includes('not found')) {
|
||||
return 'ELEMENT_NOT_FOUND';
|
||||
}
|
||||
if (message.includes('storage') || message.includes('quota')) {
|
||||
return 'STORAGE_UNAVAILABLE';
|
||||
}
|
||||
if (message.includes('driver') || message.includes('undefined')) {
|
||||
return 'DRIVER_NOT_LOADED';
|
||||
}
|
||||
if (message.includes('invalid') || message.includes('validation')) {
|
||||
return 'INVALID_TOOLTIP';
|
||||
}
|
||||
if (message.includes('theme')) {
|
||||
return 'THEME_DETECTION_FAILED';
|
||||
}
|
||||
|
||||
return 'UNKNOWN';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all logged errors
|
||||
* @returns {Array} Array of error entries
|
||||
*/
|
||||
getErrors() {
|
||||
return [...this.errors];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all logged errors
|
||||
*/
|
||||
clearErrors() {
|
||||
this.errors = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error statistics
|
||||
* @returns {Object} Error statistics
|
||||
*/
|
||||
getStatistics() {
|
||||
const stats = {
|
||||
total: this.errors.length,
|
||||
byContext: {},
|
||||
byType: {},
|
||||
recent: this.errors.slice(-10)
|
||||
};
|
||||
|
||||
this.errors.forEach(error => {
|
||||
// Count by context
|
||||
stats.byContext[error.context] = (stats.byContext[error.context] || 0) + 1;
|
||||
|
||||
// Count by type
|
||||
const type = this.classifyError({ message: error.message });
|
||||
stats.byType[type] = (stats.byType[type] || 0) + 1;
|
||||
});
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle graceful degradation when Driver.js fails to load
|
||||
* @returns {boolean} Whether fallback was successful
|
||||
*/
|
||||
handleDriverLoadFailure() {
|
||||
this.logError('Driver.js Load Failure', 'Driver.js library failed to load');
|
||||
|
||||
// Show fallback message
|
||||
const fallbackMessage = document.createElement('div');
|
||||
fallbackMessage.id = 'onboarding-fallback';
|
||||
fallbackMessage.style.cssText = `
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background: var(--card-base, #2a2a2a);
|
||||
color: var(--fg, #ffffff);
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
z-index: 9999;
|
||||
max-width: 300px;
|
||||
font-size: 14px;
|
||||
`;
|
||||
fallbackMessage.innerHTML = `
|
||||
<strong>Welcome to DashCaddy!</strong><br>
|
||||
<p style="margin: 10px 0 0 0; font-size: 12px;">
|
||||
The interactive tour is unavailable, but you can explore the dashboard freely.
|
||||
Check the documentation for help getting started.
|
||||
</p>
|
||||
`;
|
||||
|
||||
document.body.appendChild(fallbackMessage);
|
||||
|
||||
// Auto-remove after 10 seconds
|
||||
setTimeout(() => {
|
||||
if (fallbackMessage.parentNode) {
|
||||
fallbackMessage.parentNode.removeChild(fallbackMessage);
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle storage unavailable scenario
|
||||
* @returns {Object} In-memory storage fallback
|
||||
*/
|
||||
handleStorageUnavailable() {
|
||||
this.logError('Storage Unavailable', 'Local storage is not available');
|
||||
|
||||
// Create in-memory storage
|
||||
const memoryStorage = {
|
||||
data: {},
|
||||
getItem(key) {
|
||||
return this.data[key] || null;
|
||||
},
|
||||
setItem(key, value) {
|
||||
this.data[key] = value;
|
||||
},
|
||||
removeItem(key) {
|
||||
delete this.data[key];
|
||||
},
|
||||
clear() {
|
||||
this.data = {};
|
||||
}
|
||||
};
|
||||
|
||||
console.warn('[ErrorHandler] Using in-memory storage - progress will not persist');
|
||||
return memoryStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send error to tracking service (placeholder)
|
||||
* @private
|
||||
* @param {Object} errorEntry - Error entry to send
|
||||
*/
|
||||
sendToErrorTracking(errorEntry) {
|
||||
// Placeholder for error tracking integration
|
||||
// Could integrate with Sentry, LogRocket, etc.
|
||||
// Example:
|
||||
// if (window.Sentry) {
|
||||
// Sentry.captureException(new Error(errorEntry.message), {
|
||||
// extra: errorEntry.metadata
|
||||
// });
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
window.ErrorHandler = ErrorHandler;
|
||||
console.log('[ErrorHandler] Module loaded');
|
||||
|
||||
})(window);
|
||||
Reference in New Issue
Block a user