perf(webui): refactor dashboard modules and optimize API performance
This commit is contained in:
107
public/js/utils/error-handler.js
Normal file
107
public/js/utils/error-handler.js
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Error Handling Utilities
|
||||
* Provides standardized error handling with toast notifications
|
||||
*/
|
||||
window.ErrorHandler = window.ErrorHandler || {};
|
||||
|
||||
/**
|
||||
* Safely execute an async function with error handling
|
||||
* @param {Function} fn - Async function to execute
|
||||
* @param {string} errorMessage - User-friendly error message prefix
|
||||
* @param {object} options - Additional options
|
||||
* @param {boolean} options.rethrow - Whether to rethrow the error after handling (default: false)
|
||||
* @param {Function} options.onError - Custom error handler callback
|
||||
* @returns {Promise<any>} Result of the function or undefined on error
|
||||
*/
|
||||
window.ErrorHandler.safeAsync = async function(fn, errorMessage = 'Operation failed', options = {}) {
|
||||
const { rethrow = false, onError = null } = options;
|
||||
const store = Alpine.store('global');
|
||||
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error) {
|
||||
// Log error for debugging
|
||||
console.error(`[ErrorHandler] ${errorMessage}:`, error);
|
||||
|
||||
// Show toast notification
|
||||
const fullMessage = `${errorMessage}: ${error.message || 'Unknown error'}`;
|
||||
store.showToast(fullMessage, 'error');
|
||||
|
||||
// Call custom error handler if provided
|
||||
if (onError && typeof onError === 'function') {
|
||||
try {
|
||||
onError(error);
|
||||
} catch (handlerError) {
|
||||
console.error('[ErrorHandler] Custom error handler failed:', handlerError);
|
||||
}
|
||||
}
|
||||
|
||||
// Rethrow if requested
|
||||
if (rethrow) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrap a component method with error handling
|
||||
* @param {Function} method - Method to wrap
|
||||
* @param {string} errorMessage - Error message prefix
|
||||
* @returns {Function} Wrapped method
|
||||
*/
|
||||
window.ErrorHandler.wrapMethod = function(method, errorMessage = 'Operation failed') {
|
||||
return async function(...args) {
|
||||
return window.ErrorHandler.safeAsync(
|
||||
() => method.apply(this, args),
|
||||
errorMessage
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Show a success toast notification
|
||||
* @param {string} message - Success message
|
||||
*/
|
||||
window.ErrorHandler.showSuccess = function(message) {
|
||||
const store = Alpine.store('global');
|
||||
store.showToast(message, 'success');
|
||||
};
|
||||
|
||||
/**
|
||||
* Show an info toast notification
|
||||
* @param {string} message - Info message
|
||||
*/
|
||||
window.ErrorHandler.showInfo = function(message) {
|
||||
const store = Alpine.store('global');
|
||||
store.showToast(message, 'info');
|
||||
};
|
||||
|
||||
/**
|
||||
* Show an error toast notification
|
||||
* @param {string} message - Error message
|
||||
* @param {Error} error - Optional error object
|
||||
*/
|
||||
window.ErrorHandler.showError = function(message, error = null) {
|
||||
const store = Alpine.store('global');
|
||||
const fullMessage = error ? `${message}: ${error.message}` : message;
|
||||
store.showToast(fullMessage, 'error');
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate and execute an API call with error handling
|
||||
* @param {Function} apiCall - Async function that makes the API call
|
||||
* @param {string} successMessage - Message to show on success (optional)
|
||||
* @param {string} errorMessage - Message to show on error
|
||||
* @returns {Promise<any>} API response or undefined on error
|
||||
*/
|
||||
window.ErrorHandler.apiCall = async function(apiCall, successMessage = null, errorMessage = 'API call failed') {
|
||||
const result = await window.ErrorHandler.safeAsync(apiCall, errorMessage);
|
||||
|
||||
if (result !== undefined && successMessage) {
|
||||
window.ErrorHandler.showSuccess(successMessage);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
42
public/js/utils/model-config.js
Normal file
42
public/js/utils/model-config.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Model Configuration Utilities
|
||||
* Shared functions for model configuration updates
|
||||
*/
|
||||
window.ModelConfigUtils = window.ModelConfigUtils || {};
|
||||
|
||||
/**
|
||||
* Update model configuration with authentication and optimistic updates
|
||||
* @param {string} modelId - The model ID to update
|
||||
* @param {object} configUpdates - Configuration updates (pinned, hidden, alias, mapping)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
window.ModelConfigUtils.updateModelConfig = async function(modelId, configUpdates) {
|
||||
return window.ErrorHandler.safeAsync(async () => {
|
||||
const store = Alpine.store('global');
|
||||
|
||||
const { response, newPassword } = await window.utils.request('/api/models/config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ modelId, config: configUpdates })
|
||||
}, store.webuiPassword);
|
||||
|
||||
// Update password if server provided a new one
|
||||
if (newPassword) {
|
||||
store.webuiPassword = newPassword;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to update model config');
|
||||
}
|
||||
|
||||
// Optimistic update of local state
|
||||
const dataStore = Alpine.store('data');
|
||||
dataStore.modelConfig[modelId] = {
|
||||
...dataStore.modelConfig[modelId],
|
||||
...configUpdates
|
||||
};
|
||||
|
||||
// Recompute quota rows to reflect changes
|
||||
dataStore.computeQuotaRows();
|
||||
}, 'Failed to update model config');
|
||||
};
|
||||
168
public/js/utils/validators.js
Normal file
168
public/js/utils/validators.js
Normal file
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Input Validation Utilities
|
||||
* Provides validation functions for user inputs
|
||||
*/
|
||||
window.Validators = window.Validators || {};
|
||||
|
||||
/**
|
||||
* Validate a number is within a range
|
||||
* @param {number} value - Value to validate
|
||||
* @param {number} min - Minimum allowed value (inclusive)
|
||||
* @param {number} max - Maximum allowed value (inclusive)
|
||||
* @param {string} fieldName - Name of the field for error messages
|
||||
* @returns {object} { isValid: boolean, value: number, error: string|null }
|
||||
*/
|
||||
window.Validators.validateRange = function(value, min, max, fieldName = 'Value') {
|
||||
const numValue = Number(value);
|
||||
|
||||
if (isNaN(numValue)) {
|
||||
return {
|
||||
isValid: false,
|
||||
value: min,
|
||||
error: `${fieldName} must be a valid number`
|
||||
};
|
||||
}
|
||||
|
||||
if (numValue < min) {
|
||||
return {
|
||||
isValid: false,
|
||||
value: min,
|
||||
error: `${fieldName} must be at least ${min}`
|
||||
};
|
||||
}
|
||||
|
||||
if (numValue > max) {
|
||||
return {
|
||||
isValid: false,
|
||||
value: max,
|
||||
error: `${fieldName} must be at most ${max}`
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: true,
|
||||
value: numValue,
|
||||
error: null
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate a port number
|
||||
* @param {number} port - Port number to validate
|
||||
* @returns {object} { isValid: boolean, value: number, error: string|null }
|
||||
*/
|
||||
window.Validators.validatePort = function(port) {
|
||||
const { PORT_MIN, PORT_MAX } = window.AppConstants.VALIDATION;
|
||||
return window.Validators.validateRange(port, PORT_MIN, PORT_MAX, 'Port');
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate a string is not empty
|
||||
* @param {string} value - String to validate
|
||||
* @param {string} fieldName - Name of the field for error messages
|
||||
* @returns {object} { isValid: boolean, value: string, error: string|null }
|
||||
*/
|
||||
window.Validators.validateNotEmpty = function(value, fieldName = 'Field') {
|
||||
const trimmedValue = String(value || '').trim();
|
||||
|
||||
if (trimmedValue.length === 0) {
|
||||
return {
|
||||
isValid: false,
|
||||
value: trimmedValue,
|
||||
error: `${fieldName} cannot be empty`
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: true,
|
||||
value: trimmedValue,
|
||||
error: null
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate a boolean value
|
||||
* @param {any} value - Value to validate as boolean
|
||||
* @returns {object} { isValid: boolean, value: boolean, error: string|null }
|
||||
*/
|
||||
window.Validators.validateBoolean = function(value) {
|
||||
if (typeof value === 'boolean') {
|
||||
return {
|
||||
isValid: true,
|
||||
value: value,
|
||||
error: null
|
||||
};
|
||||
}
|
||||
|
||||
// Try to coerce common values
|
||||
if (value === 'true' || value === 1 || value === '1') {
|
||||
return { isValid: true, value: true, error: null };
|
||||
}
|
||||
|
||||
if (value === 'false' || value === 0 || value === '0') {
|
||||
return { isValid: true, value: false, error: null };
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: false,
|
||||
value: false,
|
||||
error: 'Value must be true or false'
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate a timeout/duration value (in milliseconds)
|
||||
* @param {number} value - Timeout value in ms
|
||||
* @param {number} minMs - Minimum allowed timeout (default: from constants)
|
||||
* @param {number} maxMs - Maximum allowed timeout (default: from constants)
|
||||
* @returns {object} { isValid: boolean, value: number, error: string|null }
|
||||
*/
|
||||
window.Validators.validateTimeout = function(value, minMs = null, maxMs = null) {
|
||||
const { TIMEOUT_MIN, TIMEOUT_MAX } = window.AppConstants.VALIDATION;
|
||||
return window.Validators.validateRange(value, minMs ?? TIMEOUT_MIN, maxMs ?? TIMEOUT_MAX, 'Timeout');
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate log limit
|
||||
* @param {number} value - Log limit value
|
||||
* @returns {object} { isValid: boolean, value: number, error: string|null }
|
||||
*/
|
||||
window.Validators.validateLogLimit = function(value) {
|
||||
const { LOG_LIMIT_MIN, LOG_LIMIT_MAX } = window.AppConstants.VALIDATION;
|
||||
return window.Validators.validateRange(value, LOG_LIMIT_MIN, LOG_LIMIT_MAX, 'Log limit');
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate and sanitize input with custom validator
|
||||
* @param {any} value - Value to validate
|
||||
* @param {Function} validator - Validator function
|
||||
* @param {boolean} showError - Whether to show error toast (default: true)
|
||||
* @returns {object} Validation result
|
||||
*/
|
||||
window.Validators.validate = function(value, validator, showError = true) {
|
||||
const result = validator(value);
|
||||
|
||||
if (!result.isValid && showError && result.error) {
|
||||
window.ErrorHandler.showError(result.error);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a validated input handler for Alpine.js
|
||||
* @param {Function} validator - Validator function
|
||||
* @param {Function} onValid - Callback when validation passes
|
||||
* @returns {Function} Handler function
|
||||
*/
|
||||
window.Validators.createHandler = function(validator, onValid) {
|
||||
return function(value) {
|
||||
const result = window.Validators.validate(value, validator, true);
|
||||
|
||||
if (result.isValid && onValid) {
|
||||
onValid.call(this, result.value);
|
||||
}
|
||||
|
||||
return result.value;
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user