diff --git a/vna_system/core/processors/configs/magnitude_config.json b/vna_system/core/processors/configs/magnitude_config.json index d0aae19..f266070 100644 --- a/vna_system/core/processors/configs/magnitude_config.json +++ b/vna_system/core/processors/configs/magnitude_config.json @@ -1,5 +1,5 @@ { "y_min": -80, "y_max": 40, - "show_phase": false + "show_phase": true } \ No newline at end of file diff --git a/vna_system/web_ui/static/js/modules/acquisition.js b/vna_system/web_ui/static/js/modules/acquisition.js index 9d930bf..fc2c323 100644 --- a/vna_system/web_ui/static/js/modules/acquisition.js +++ b/vna_system/web_ui/static/js/modules/acquisition.js @@ -4,6 +4,10 @@ */ import { setButtonLoading } from './utils.js'; +import { apiGet, apiPost } from './api-client.js'; +import { API, TIMING, NOTIFICATION_TYPES } from './constants.js'; + +const { SUCCESS, ERROR } = NOTIFICATION_TYPES; export class AcquisitionManager { constructor(notifications) { @@ -44,8 +48,8 @@ export class AcquisitionManager { // Initial status update this.updateStatus(); - // Poll status every 2 seconds - this.statusInterval = setInterval(this.updateStatus, 2000); + // Poll status periodically + this.statusInterval = setInterval(this.updateStatus, TIMING.STATUS_POLL_INTERVAL); this.isInitialized = true; console.log('AcquisitionManager initialized'); @@ -55,22 +59,17 @@ export class AcquisitionManager { try { const originalState = setButtonLoading(this.elements.startBtn, true); - const response = await fetch('/api/v1/acquisition/start', { - method: 'POST', - headers: { 'Content-Type': 'application/json' } - }); - - const result = await response.json(); + const result = await apiPost(API.ACQUISITION.START); if (result.success) { - this.notifications.show({type: 'success', title: 'Acquisition Started', message: result.message}); + this.notifications.show({ type: SUCCESS, title: 'Acquisition Started', message: result.message }); await this.updateStatus(); } else { - this.notifications.show({type: 'error', title: 'Start Failed', message: result.error || 'Failed to start acquisition'}); + this.notifications.show({ type: ERROR, title: 'Start Failed', message: result.error || 'Failed to start acquisition' }); } } catch (error) { console.error('Error starting acquisition:', error); - this.notifications.show({type: 'error', title: 'Start Failed', message: 'Failed to start acquisition'}); + this.notifications.show({ type: ERROR, title: 'Start Failed', message: 'Failed to start acquisition' }); } finally { setButtonLoading(this.elements.startBtn, false, originalState); } @@ -80,22 +79,17 @@ export class AcquisitionManager { try { const originalState = setButtonLoading(this.elements.stopBtn, true); - const response = await fetch('/api/v1/acquisition/stop', { - method: 'POST', - headers: { 'Content-Type': 'application/json' } - }); - - const result = await response.json(); + const result = await apiPost(API.ACQUISITION.STOP); if (result.success) { - this.notifications.show({type: 'success', title: 'Acquisition Stopped', message: result.message}); + this.notifications.show({ type: SUCCESS, title: 'Acquisition Stopped', message: result.message }); await this.updateStatus(); } else { - this.notifications.show({type: 'error', title: 'Stop Failed', message: result.error || 'Failed to stop acquisition'}); + this.notifications.show({ type: ERROR, title: 'Stop Failed', message: result.error || 'Failed to stop acquisition' }); } } catch (error) { console.error('Error stopping acquisition:', error); - this.notifications.show({type: 'error', title: 'Stop Failed', message: 'Failed to stop acquisition'}); + this.notifications.show({ type: ERROR, title: 'Stop Failed', message: 'Failed to stop acquisition' }); } finally { setButtonLoading(this.elements.stopBtn, false, originalState); } @@ -105,22 +99,17 @@ export class AcquisitionManager { try { const originalState = setButtonLoading(this.elements.singleSweepBtn, true); - const response = await fetch('/api/v1/acquisition/single-sweep', { - method: 'POST', - headers: { 'Content-Type': 'application/json' } - }); - - const result = await response.json(); + const result = await apiPost(API.ACQUISITION.SINGLE); if (result.success) { - this.notifications.show({type: 'success', title: 'Single Sweep', message: result.message}); + this.notifications.show({ type: SUCCESS, title: 'Single Sweep', message: result.message }); await this.updateStatus(); } else { - this.notifications.show({type: 'error', title: 'Single Sweep Failed', message: result.error || 'Failed to trigger single sweep'}); + this.notifications.show({ type: ERROR, title: 'Single Sweep Failed', message: result.error || 'Failed to trigger single sweep' }); } } catch (error) { console.error('Error triggering single sweep:', error); - this.notifications.show({type: 'error', title: 'Single Sweep Failed', message: 'Failed to trigger single sweep'}); + this.notifications.show({ type: ERROR, title: 'Single Sweep Failed', message: 'Failed to trigger single sweep' }); } finally { setButtonLoading(this.elements.singleSweepBtn, false, originalState); } @@ -128,8 +117,7 @@ export class AcquisitionManager { async updateStatus() { try { - const response = await fetch('/api/v1/acquisition/status'); - const status = await response.json(); + const status = await apiGet(API.ACQUISITION.STATUS); this.currentStatus = status; this.updateUI(status); @@ -215,4 +203,4 @@ export class AcquisitionManager { this.isInitialized = false; console.log('AcquisitionManager destroyed'); } -} \ No newline at end of file +} diff --git a/vna_system/web_ui/static/js/modules/api-client.js b/vna_system/web_ui/static/js/modules/api-client.js new file mode 100644 index 0000000..ab37d76 --- /dev/null +++ b/vna_system/web_ui/static/js/modules/api-client.js @@ -0,0 +1,94 @@ +/** + * API Client + * Centralized API request handling with consistent error handling + */ + +/** + * Default fetch options + */ +const DEFAULT_OPTIONS = { + headers: { + 'Content-Type': 'application/json' + } +}; + +/** + * Base API request wrapper + * @param {string} url - API endpoint URL + * @param {Object} options - Fetch options + * @returns {Promise} Response data + */ +async function apiRequest(url, options = {}) { + const response = await fetch(url, { + ...DEFAULT_OPTIONS, + ...options, + headers: { + ...DEFAULT_OPTIONS.headers, + ...options.headers + } + }); + + if (!response.ok) { + const errorText = await response.text().catch(() => response.statusText); + const error = new Error(`HTTP ${response.status}: ${errorText}`); + error.status = response.status; + error.body = errorText; + throw error; + } + + // Check if response has content + const contentType = response.headers.get('content-type'); + if (contentType && contentType.includes('application/json')) { + return await response.json(); + } + + return await response.text(); +} + +/** + * GET request + * @param {string} url - API endpoint URL + * @returns {Promise} Response data + */ +export async function apiGet(url) { + return await apiRequest(url, { method: 'GET' }); +} + +/** + * POST request + * @param {string} url - API endpoint URL + * @param {Object} data - Request body data + * @returns {Promise} Response data + */ +export async function apiPost(url, data = null) { + const options = { method: 'POST' }; + if (data !== null) { + options.body = JSON.stringify(data); + } + return await apiRequest(url, options); +} + +/** + * DELETE request + * @param {string} url - API endpoint URL + * @returns {Promise} Response data + */ +export async function apiDelete(url) { + return await apiRequest(url, { method: 'DELETE' }); +} + +/** + * Build URL with query parameters + * @param {string} baseUrl - Base URL + * @param {Object} params - Query parameters + * @returns {string} URL with query string + */ +export function buildUrl(baseUrl, params = {}) { + const url = new URL(baseUrl, window.location.origin); + Object.entries(params).forEach(([key, value]) => { + if (value !== null && value !== undefined) { + url.searchParams.append(key, value); + } + }); + return url.toString(); +} diff --git a/vna_system/web_ui/static/js/modules/constants.js b/vna_system/web_ui/static/js/modules/constants.js new file mode 100644 index 0000000..dac9598 --- /dev/null +++ b/vna_system/web_ui/static/js/modules/constants.js @@ -0,0 +1,140 @@ +/** + * Application Constants + * Centralized configuration values + */ + +// Timing constants (milliseconds) +export const TIMING = { + DEBOUNCE_DEFAULT: 300, + DEBOUNCE_SETTINGS: 300, + DEBOUNCE_RESIZE: 300, + DEBOUNCE_PRESET: 300, + DEBOUNCE_CALIBRATION: 400, + DEBOUNCE_REFERENCE: 500, + DEBOUNCE_DOWNLOAD: 600, + + BUTTON_FEEDBACK: 1000, + + STATUS_POLL_INTERVAL: 2000, + + RECONNECT_BASE: 3000, + RECONNECT_MAX: 30000, + + CONNECTION_TIMEOUT: 5000, + PING_INTERVAL: 30000, + + NOTIFICATION_SUCCESS: 3000, + NOTIFICATION_ERROR: 7000, + NOTIFICATION_WARNING: 5000, + NOTIFICATION_INFO: 4000, + NOTIFICATION_ANIMATION: 300, + + FULLSCREEN_RESIZE_DELAY: 200, + FULLSCREEN_EXIT_DELAY: 100, + + CHART_ANIMATION_DELAY: 50, + ICON_INIT_DELAY: 10 +}; + +// Size limits +export const LIMITS = { + MAX_NOTIFICATIONS: 5, + MAX_RECONNECT_ATTEMPTS: 10, + MAX_QUEUE_SIZE: 100, + MAX_DATA_POINTS: 1000, + MAX_CHARTS_SAVED: 10, + CHART_DATA_HISTORY: 100, + + STORAGE_CLEANUP_DAYS: 7, + MESSAGE_RATE_WINDOW: 1000, + + NOTIFICATION_DEDUPE_WINDOW: 2000, + NOTIFICATION_CLEANUP_AGE: 5000 +}; + +// API endpoints +const API_BASE = '/api/v1'; + +export const API = { + BASE: API_BASE, + + ACQUISITION: { + START: `${API_BASE}/acquisition/start`, + STOP: `${API_BASE}/acquisition/stop`, + SINGLE: `${API_BASE}/acquisition/single-sweep`, + STATUS: `${API_BASE}/acquisition/status` + }, + + SETTINGS: { + PRESETS: `${API_BASE}/settings/presets`, + PRESET_SET: `${API_BASE}/settings/preset/set`, + STATUS: `${API_BASE}/settings/status`, + + CALIBRATIONS: `${API_BASE}/settings/calibrations`, + CALIBRATION_START: `${API_BASE}/settings/calibration/start`, + CALIBRATION_SAVE: `${API_BASE}/settings/calibration/save`, + CALIBRATION_SET: `${API_BASE}/settings/calibration/set`, + CALIBRATION_ADD_STANDARD: `${API_BASE}/settings/calibration/add-standard`, + CALIBRATION_STANDARDS_PLOTS: (name) => `${API_BASE}/settings/calibration/${encodeURIComponent(name)}/standards-plots`, + + WORKING_CALIBRATION: `${API_BASE}/settings/working-calibration`, + WORKING_CALIBRATION_PLOTS: `${API_BASE}/settings/working-calibration/standards-plots`, + + REFERENCES: `${API_BASE}/settings/references`, + REFERENCE_CREATE: `${API_BASE}/settings/reference/create`, + REFERENCE_SET: `${API_BASE}/settings/reference/set`, + REFERENCE_CURRENT: `${API_BASE}/settings/reference/current`, + REFERENCE_ITEM: (name) => `${API_BASE}/settings/reference/${encodeURIComponent(name)}` + } +}; + +// Storage keys +export const STORAGE_KEYS = { + PREFERENCES: 'preferences', + CHART_DATA: 'chart_data', + SESSION: 'session_data', + DEBUG: 'debug_info' +}; + +// WebSocket events +export const WS_EVENTS = { + CONNECTING: 'connecting', + CONNECTED: 'connected', + DISCONNECTED: 'disconnected', + DATA: 'data', + PROCESSOR_RESULT: 'processor_result', + PROCESSOR_HISTORY: 'processor_history', + ERROR: 'error' +}; + +// WebSocket message types +export const WS_MESSAGE_TYPES = { + PING: 'ping', + PONG: 'pong', + RECALCULATE: 'recalculate', + GET_HISTORY: 'get_history', + PROCESSOR_RESULT: 'processor_result', + PROCESSOR_HISTORY: 'processor_history', + ERROR: 'error' +}; + +// Notification types +export const NOTIFICATION_TYPES = { + SUCCESS: 'success', + ERROR: 'error', + WARNING: 'warning', + INFO: 'info' +}; + +// Chart update queue +export const CHART = { + UPDATE_INTERVAL: 100, + MAX_DATA_POINTS: 1000, + ANIMATION_ENABLED: true +}; + +// Calibration standards by mode +export const CALIBRATION_STANDARDS = { + S11: ['open', 'short', 'load'], + S21: ['through'] +}; diff --git a/vna_system/web_ui/static/js/modules/settings.js b/vna_system/web_ui/static/js/modules/settings.js index 3e93828..e5c0218 100644 --- a/vna_system/web_ui/static/js/modules/settings.js +++ b/vna_system/web_ui/static/js/modules/settings.js @@ -13,6 +13,10 @@ import { downloadPlotlyImage, cleanupPlotly } from './plotly-utils.js'; +import { apiGet, buildUrl } from './api-client.js'; +import { API, TIMING, NOTIFICATION_TYPES } from './constants.js'; + +const { SUCCESS, ERROR, WARNING } = NOTIFICATION_TYPES; export class SettingsManager { constructor(notifications, websocket, acquisition) { @@ -48,7 +52,7 @@ export class SettingsManager { console.log('Settings Manager initialized'); } catch (err) { console.error('Settings Manager init failed:', err); - this.notify('error', 'Settings Error', 'Failed to initialize settings'); + this.notify(ERROR, 'Settings Error', 'Failed to initialize settings'); } } @@ -136,9 +140,7 @@ export class SettingsManager { async loadStatus() { try { - const r = await fetch('/api/v1/settings/status'); - if (!r.ok) throw new Error(`HTTP ${r.status}`); - const status = await r.json(); + const status = await apiGet(API.SETTINGS.STATUS); this.updateStatusDisplay(status); } catch (e) { console.error('Status load failed:', e); @@ -167,19 +169,19 @@ export class SettingsManager { try { ButtonState.set(this.elements.viewPlotsBtn, { state: 'loading', icon: 'loader', text: 'Loading...' }); - const url = `/api/v1/settings/calibration/${encodeURIComponent(name)}/standards-plots?preset_filename=${encodeURIComponent(preset.filename)}`; - const r = await fetch(url); - if (!r.ok) throw new Error(`HTTP ${r.status}`); - const plotsData = await r.json(); + const url = buildUrl(API.SETTINGS.CALIBRATION_STANDARDS_PLOTS(name), { + preset_filename: preset.filename + }); + const plotsData = await apiGet(url); this.showPlotsModal(plotsData); } catch (e) { console.error('Load plots failed:', e); - this.notify('error', 'Plots Error', 'Failed to load calibration plots'); + this.notify(ERROR, 'Plots Error', 'Failed to load calibration plots'); } finally { ButtonState.set(this.elements.viewPlotsBtn, { state: 'normal', icon: 'bar-chart-3', text: 'View Plots' }); } - }, 300); + }, TIMING.DEBOUNCE_CALIBRATION); } async handleViewCurrentPlots() { @@ -190,23 +192,19 @@ export class SettingsManager { try { ButtonState.set(this.elements.viewCurrentPlotsBtn, { state: 'loading', icon: 'loader', text: 'Loading...' }); - const r = await fetch('/api/v1/settings/working-calibration/standards-plots'); - if (!r.ok) { - if (r.status === 404) { - this.notify('warning', 'No Data', 'No working calibration or standards available to plot'); - return; - } - throw new Error(`HTTP ${r.status}`); - } - const plotsData = await r.json(); + const plotsData = await apiGet(API.SETTINGS.WORKING_CALIBRATION_PLOTS); this.showPlotsModal(plotsData); } catch (e) { - console.error('Load current plots failed:', e); - this.notify('error', 'Plots Error', 'Failed to load current calibration plots'); + if (e.status === 404) { + this.notify(WARNING, 'No Data', 'No working calibration or standards available to plot'); + } else { + console.error('Load current plots failed:', e); + this.notify(ERROR, 'Plots Error', 'Failed to load current calibration plots'); + } } finally { ButtonState.set(this.elements.viewCurrentPlotsBtn, { state: 'normal', icon: 'bar-chart-3', text: 'View Current Plots' }); } - }, 300); + }, TIMING.DEBOUNCE_CALIBRATION); } showPlotsModal(plotsData) { @@ -368,7 +366,7 @@ export class SettingsManager { const downloadAllBtn = modal.querySelector('#downloadAllBtn'); if (downloadAllBtn) { downloadAllBtn.addEventListener('click', () => - this.debouncer.debounce('download-all', () => this.downloadAllCalibrationData(), 600) + this.debouncer.debounce('download-all', () => this.downloadAllCalibrationData(), TIMING.DEBOUNCE_DOWNLOAD) ); } @@ -407,10 +405,10 @@ export class SettingsManager { const data = this.prepareCalibrationDownloadData(standardName); downloadJSON(data, `${base}_data.json`); - this.notify('success', 'Download Complete', `Downloaded ${standardName.toUpperCase()} standard plot and data`); + this.notify(SUCCESS, 'Download Complete', `Downloaded ${standardName.toUpperCase()} standard plot and data`); } catch (e) { console.error('Download standard failed:', e); - this.notify('error', 'Download Failed', 'Failed to download calibration data'); + this.notify(ERROR, 'Download Failed', 'Failed to download calibration data'); } } @@ -436,10 +434,10 @@ export class SettingsManager { downloadJSON(complete, `${base}.json`); - this.notify('success', 'Complete Download', `Downloaded complete calibration data for ${calibrationName}`); + this.notify(SUCCESS, 'Complete Download', `Downloaded complete calibration data for ${calibrationName}`); } catch (e) { console.error('Download all failed:', e); - this.notify('error', 'Download Failed', 'Failed to download complete calibration data'); + this.notify(ERROR, 'Download Failed', 'Failed to download complete calibration data'); } finally { const btn = this.elements.downloadAllBtn; if (btn) ButtonState.set(btn, { state: 'normal', icon: 'download-cloud', text: 'Download All' }); @@ -480,4 +478,4 @@ export class SettingsManager { notify(type, title, message) { this.notifications?.show?.({ type, title, message }); } -} \ No newline at end of file +} diff --git a/vna_system/web_ui/static/js/modules/settings/calibration-manager.js b/vna_system/web_ui/static/js/modules/settings/calibration-manager.js index 96c1b34..b141560 100644 --- a/vna_system/web_ui/static/js/modules/settings/calibration-manager.js +++ b/vna_system/web_ui/static/js/modules/settings/calibration-manager.js @@ -4,6 +4,10 @@ */ import { Debouncer, RequestGuard, ButtonState } from '../utils.js'; +import { apiGet, apiPost, buildUrl } from '../api-client.js'; +import { API, TIMING, CALIBRATION_STANDARDS, NOTIFICATION_TYPES } from '../constants.js'; + +const { SUCCESS, ERROR, INFO } = NOTIFICATION_TYPES; export class CalibrationManager { constructor(notifications) { @@ -57,9 +61,7 @@ export class CalibrationManager { async loadWorkingCalibration() { try { - const r = await fetch('/api/v1/settings/working-calibration'); - if (!r.ok) throw new Error(`HTTP ${r.status}`); - const working = await r.json(); + const working = await apiGet(API.SETTINGS.WORKING_CALIBRATION); this.updateWorking(working); } catch (e) { console.error('Working calibration load failed:', e); @@ -69,9 +71,10 @@ export class CalibrationManager { async loadCalibrations() { if (!this.currentPreset) return; try { - const r = await fetch(`/api/v1/settings/calibrations?preset_filename=${encodeURIComponent(this.currentPreset.filename)}`); - if (!r.ok) throw new Error(`HTTP ${r.status}`); - const calibrations = await r.json(); + const url = buildUrl(API.SETTINGS.CALIBRATIONS, { + preset_filename: this.currentPreset.filename + }); + const calibrations = await apiGet(url); this.populateDropdown(calibrations); } catch (e) { console.error('Calibrations load failed:', e); @@ -184,10 +187,9 @@ export class CalibrationManager { } getStandardsForMode() { - if (!this.currentPreset) return []; - if (this.currentPreset.mode === 's11') return ['open', 'short', 'load']; - if (this.currentPreset.mode === 's21') return ['through']; - return []; + if (!this.currentPreset || !this.currentPreset.mode) return []; + const modeKey = this.currentPreset.mode.toUpperCase(); + return CALIBRATION_STANDARDS[modeKey] || []; } handleCalibrationChange() { @@ -204,24 +206,20 @@ export class CalibrationManager { try { ButtonState.set(this.elements.startCalibrationBtn, { state: 'loading', icon: 'loader', text: 'Starting...' }); - const r = await fetch('/api/v1/settings/calibration/start', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ preset_filename: this.currentPreset.filename }) + const result = await apiPost(API.SETTINGS.CALIBRATION_START, { + preset_filename: this.currentPreset.filename }); - if (!r.ok) throw new Error(`HTTP ${r.status}`); - const result = await r.json(); - this.notify('info', 'Calibration Started', `Started calibration for ${result.preset}`); + this.notify(INFO, 'Calibration Started', `Started calibration for ${result.preset}`); await this.loadWorkingCalibration(); } catch (e) { console.error('Start calibration failed:', e); - this.notify('error', 'Calibration Error', 'Failed to start calibration'); + this.notify(ERROR, 'Calibration Error', 'Failed to start calibration'); } finally { ButtonState.set(this.elements.startCalibrationBtn, { state: 'normal', icon: 'play', text: 'Start Calibration' }); } - }), 400 + }), TIMING.DEBOUNCE_CALIBRATION ); } @@ -237,27 +235,20 @@ export class CalibrationManager { const btn = document.querySelector(`[data-standard="${standard}"]`); ButtonState.set(btn, { state: 'loading', icon: 'upload', text: 'Capturing...' }); - this.notify('info', 'Capturing Standard', `Capturing ${standard.toUpperCase()} standard...`); + this.notify(INFO, 'Capturing Standard', `Capturing ${standard.toUpperCase()} standard...`); - const r = await fetch('/api/v1/settings/calibration/add-standard', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ standard }) - }); + const result = await apiPost(API.SETTINGS.CALIBRATION_ADD_STANDARD, { standard }); - if (!r.ok) throw new Error(`HTTP ${r.status}`); - const result = await r.json(); - - this.notify('success', 'Standard Captured', result.message); + this.notify(SUCCESS, 'Standard Captured', result.message); this.resetCaptureState(); await this.loadWorkingCalibration(); } catch (e) { console.error('Capture standard failed:', e); - this.notify('error', 'Calibration Error', 'Failed to capture calibration standard'); + this.notify(ERROR, 'Calibration Error', 'Failed to capture calibration standard'); this.resetCaptureState(standard); } - }), 500 + }), TIMING.DEBOUNCE_CALIBRATION ); } @@ -270,15 +261,9 @@ export class CalibrationManager { try { ButtonState.set(this.elements.saveCalibrationBtn, { state: 'loading', icon: 'loader', text: 'Saving...' }); - const r = await fetch('/api/v1/settings/calibration/save', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name }) - }); - if (!r.ok) throw new Error(`HTTP ${r.status}`); - const result = await r.json(); + const result = await apiPost(API.SETTINGS.CALIBRATION_SAVE, { name }); - this.notify('success', 'Calibration Saved', result.message); + this.notify(SUCCESS, 'Calibration Saved', result.message); this.hideSteps(); this.elements.calibrationNameInput.value = ''; @@ -291,11 +276,11 @@ export class CalibrationManager { } } catch (e) { console.error('Save calibration failed:', e); - this.notify('error', 'Calibration Error', 'Failed to save calibration'); + this.notify(ERROR, 'Calibration Error', 'Failed to save calibration'); } finally { ButtonState.set(this.elements.saveCalibrationBtn, { state: 'disabled', icon: 'save', text: 'Save Calibration' }); } - }), 400 + }), TIMING.DEBOUNCE_CALIBRATION ); } @@ -308,26 +293,23 @@ export class CalibrationManager { try { ButtonState.set(this.elements.setCalibrationBtn, { state: 'loading', icon: 'loader', text: 'Setting...' }); - const r = await fetch('/api/v1/settings/calibration/set', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name, preset_filename: this.currentPreset.filename }) + const result = await apiPost(API.SETTINGS.CALIBRATION_SET, { + name, + preset_filename: this.currentPreset.filename }); - if (!r.ok) throw new Error(`HTTP ${r.status}`); - const result = await r.json(); - this.notify('success', 'Calibration Set', result.message); + this.notify(SUCCESS, 'Calibration Set', result.message); if (this.onCalibrationSet) { await this.onCalibrationSet(); } } catch (e) { console.error('Set calibration failed:', e); - this.notify('error', 'Calibration Error', 'Failed to set active calibration'); + this.notify(ERROR, 'Calibration Error', 'Failed to set active calibration'); } finally { ButtonState.set(this.elements.setCalibrationBtn, { state: 'normal', icon: 'check', text: 'Set Active' }); } - }), 300 + }), TIMING.DEBOUNCE_CALIBRATION ); } @@ -368,4 +350,4 @@ export class CalibrationManager { notify(type, title, message) { this.notifications?.show?.({ type, title, message }); } -} \ No newline at end of file +} diff --git a/vna_system/web_ui/static/js/modules/settings/preset-manager.js b/vna_system/web_ui/static/js/modules/settings/preset-manager.js index 411078c..5646888 100644 --- a/vna_system/web_ui/static/js/modules/settings/preset-manager.js +++ b/vna_system/web_ui/static/js/modules/settings/preset-manager.js @@ -4,6 +4,8 @@ */ import { Debouncer, RequestGuard, ButtonState } from '../utils.js'; +import { apiGet, apiPost } from '../api-client.js'; +import { API, TIMING } from '../constants.js'; export class PresetManager { constructor(notifications) { @@ -30,9 +32,7 @@ export class PresetManager { async loadPresets() { try { - const r = await fetch('/api/v1/settings/presets'); - if (!r.ok) throw new Error(`HTTP ${r.status}`); - const presets = await r.json(); + const presets = await apiGet(API.SETTINGS.PRESETS); this.populateDropdown(presets); } catch (e) { console.error('Presets load failed:', e); @@ -88,13 +88,7 @@ export class PresetManager { try { ButtonState.set(this.elements.setPresetBtn, { state: 'loading', icon: 'loader', text: 'Setting...' }); - const r = await fetch('/api/v1/settings/preset/set', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ filename }) - }); - if (!r.ok) throw new Error(`HTTP ${r.status}`); - const result = await r.json(); + const result = await apiPost(API.SETTINGS.PRESET_SET, { filename }); this.notify('success', 'Preset Set', result.message); this.currentPreset = { filename }; @@ -108,7 +102,7 @@ export class PresetManager { } finally { ButtonState.set(this.elements.setPresetBtn, { state: 'normal', icon: 'check', text: 'Set Active' }); } - }), 300 + }), TIMING.DEBOUNCE_PRESET ); } diff --git a/vna_system/web_ui/static/js/modules/settings/reference-manager.js b/vna_system/web_ui/static/js/modules/settings/reference-manager.js index 0ac10e3..14c9aba 100644 --- a/vna_system/web_ui/static/js/modules/settings/reference-manager.js +++ b/vna_system/web_ui/static/js/modules/settings/reference-manager.js @@ -4,6 +4,10 @@ */ import { Debouncer, RequestGuard, ButtonState } from '../utils.js'; +import { apiGet, apiPost, apiDelete, buildUrl } from '../api-client.js'; +import { API, TIMING, NOTIFICATION_TYPES } from '../constants.js'; + +const { SUCCESS, ERROR, WARNING } = NOTIFICATION_TYPES; export class ReferenceManager { constructor(notifications) { @@ -56,13 +60,15 @@ export class ReferenceManager { return; } - const referencesResponse = await fetch(`/api/v1/settings/references?preset_filename=${encodeURIComponent(this.currentPreset.filename)}`); - if (!referencesResponse.ok) throw new Error(`HTTP ${referencesResponse.status}`); - this.availableReferences = await referencesResponse.json(); + const referencesUrl = buildUrl(API.SETTINGS.REFERENCES, { + preset_filename: this.currentPreset.filename + }); + this.availableReferences = await apiGet(referencesUrl); - const currentResponse = await fetch(`/api/v1/settings/reference/current?preset_filename=${encodeURIComponent(this.currentPreset.filename)}`); - if (!currentResponse.ok) throw new Error(`HTTP ${currentResponse.status}`); - this.currentReference = await currentResponse.json(); + const currentUrl = buildUrl(API.SETTINGS.REFERENCE_CURRENT, { + preset_filename: this.currentPreset.filename + }); + this.currentReference = await apiGet(currentUrl); this.renderDropdown(this.availableReferences); this.updateInfo(this.currentReference); @@ -139,7 +145,7 @@ export class ReferenceManager { async handleCreateReference() { const name = this.elements.referenceNameInput.value.trim(); if (!name) { - this.notify('warning', 'Missing Name', 'Please enter a name for the reference'); + this.notify(WARNING, 'Missing Name', 'Please enter a name for the reference'); return; } @@ -150,16 +156,9 @@ export class ReferenceManager { try { ButtonState.set(this.elements.createReferenceBtn, { state: 'loading', icon: 'loader', text: 'Creating...' }); - const response = await fetch('/api/v1/settings/reference/create', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name, description }) - }); + const result = await apiPost(API.SETTINGS.REFERENCE_CREATE, { name, description }); - if (!response.ok) throw new Error(`HTTP ${response.status}`); - const result = await response.json(); - - this.notify('success', 'Reference Created', result.message); + this.notify(SUCCESS, 'Reference Created', result.message); this.elements.referenceNameInput.value = ''; this.elements.referenceDescriptionInput.value = ''; @@ -167,11 +166,11 @@ export class ReferenceManager { await this.loadReferences(); } catch (error) { console.error('Create reference failed:', error); - this.notify('error', 'Reference Error', 'Failed to create reference'); + this.notify(ERROR, 'Reference Error', 'Failed to create reference'); } finally { ButtonState.set(this.elements.createReferenceBtn, { state: 'normal', icon: 'target', text: 'Capture Reference' }); } - }), 500 + }), TIMING.DEBOUNCE_REFERENCE ); } @@ -188,25 +187,18 @@ export class ReferenceManager { try { ButtonState.set(this.elements.setReferenceBtn, { state: 'loading', icon: 'loader', text: 'Setting...' }); - const response = await fetch('/api/v1/settings/reference/set', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name: referenceName }) - }); + const result = await apiPost(API.SETTINGS.REFERENCE_SET, { name: referenceName }); - if (!response.ok) throw new Error(`HTTP ${response.status}`); - const result = await response.json(); - - this.notify('success', 'Reference Set', result.message); + this.notify(SUCCESS, 'Reference Set', result.message); await this.loadReferences(); } catch (error) { console.error('Set reference failed:', error); - this.notify('error', 'Reference Error', 'Failed to set reference'); + this.notify(ERROR, 'Reference Error', 'Failed to set reference'); } finally { ButtonState.set(this.elements.setReferenceBtn, { state: 'normal', icon: 'check', text: 'Set Active' }); } - }), 400 + }), TIMING.DEBOUNCE_REFERENCE ); } @@ -216,23 +208,18 @@ export class ReferenceManager { try { ButtonState.set(this.elements.clearReferenceBtn, { state: 'loading', icon: 'loader', text: 'Clearing...' }); - const response = await fetch('/api/v1/settings/reference/current', { - method: 'DELETE' - }); + const result = await apiDelete(API.SETTINGS.REFERENCE_CURRENT); - if (!response.ok) throw new Error(`HTTP ${response.status}`); - const result = await response.json(); - - this.notify('success', 'Reference Cleared', result.message); + this.notify(SUCCESS, 'Reference Cleared', result.message); await this.loadReferences(); } catch (error) { console.error('Clear reference failed:', error); - this.notify('error', 'Reference Error', 'Failed to clear reference'); + this.notify(ERROR, 'Reference Error', 'Failed to clear reference'); } finally { ButtonState.set(this.elements.clearReferenceBtn, { state: 'normal', icon: 'x', text: 'Clear' }); } - }), 400 + }), TIMING.DEBOUNCE_REFERENCE ); } @@ -249,23 +236,18 @@ export class ReferenceManager { try { ButtonState.set(this.elements.deleteReferenceBtn, { state: 'loading', icon: 'loader', text: 'Deleting...' }); - const response = await fetch(`/api/v1/settings/reference/${encodeURIComponent(referenceName)}`, { - method: 'DELETE' - }); + const result = await apiDelete(API.SETTINGS.REFERENCE_ITEM(referenceName)); - if (!response.ok) throw new Error(`HTTP ${response.status}`); - const result = await response.json(); - - this.notify('success', 'Reference Deleted', result.message); + this.notify(SUCCESS, 'Reference Deleted', result.message); await this.loadReferences(); } catch (error) { console.error('Delete reference failed:', error); - this.notify('error', 'Reference Error', 'Failed to delete reference'); + this.notify(ERROR, 'Reference Error', 'Failed to delete reference'); } finally { ButtonState.set(this.elements.deleteReferenceBtn, { state: 'normal', icon: 'trash-2', text: 'Delete' }); } - }), 400 + }), TIMING.DEBOUNCE_REFERENCE ); } @@ -279,4 +261,4 @@ export class ReferenceManager { notify(type, title, message) { this.notifications?.show?.({ type, title, message }); } -} \ No newline at end of file +}