updated view and added calibration deletion
This commit is contained in:
@ -621,3 +621,33 @@ async def delete_reference(reference_name: str, preset_filename: str | None = No
|
||||
logger.error("Failed to delete reference", name=reference_name, error=repr(exc))
|
||||
raise HTTPException(status_code=500, detail=str(exc))
|
||||
|
||||
|
||||
@router.delete("/calibration/{calibration_name}")
|
||||
async def delete_calibration(calibration_name: str, preset_filename: str | None = None) -> dict[str, Any]:
|
||||
"""Delete a calibration set."""
|
||||
try:
|
||||
settings_manager = singletons.settings_manager
|
||||
|
||||
if preset_filename:
|
||||
preset = next(
|
||||
(p for p in settings_manager.get_available_presets() if p.filename == preset_filename),
|
||||
None
|
||||
)
|
||||
if not preset:
|
||||
raise HTTPException(status_code=404, detail=f"Preset '{preset_filename}' not found")
|
||||
else:
|
||||
preset = settings_manager.get_current_preset()
|
||||
if not preset:
|
||||
raise HTTPException(status_code=400, detail="No current preset set and no preset_filename provided")
|
||||
|
||||
settings_manager.delete_calibration(preset, calibration_name)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"Calibration '{calibration_name}' deleted"
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.error("Failed to delete calibration", name=calibration_name, error=repr(exc))
|
||||
raise HTTPException(status_code=500, detail=str(exc))
|
||||
|
||||
@ -1 +1 @@
|
||||
config_inputs/s21_start100_stop8800_points1000_bw1khz.bin
|
||||
config_inputs/s11_start100_stop8800_points1000_bw1khz.bin
|
||||
1
vna_system/calibration/current_calibration
Symbolic link
1
vna_system/calibration/current_calibration
Symbolic link
@ -0,0 +1 @@
|
||||
s11_start100_stop8800_points1000_bw1khz/еуыеуые
|
||||
@ -1,18 +0,0 @@
|
||||
{
|
||||
"preset": {
|
||||
"filename": "s11_start100_stop8800_points1000_bw1khz.bin",
|
||||
"mode": "s11",
|
||||
"start_freq": 100000000.0,
|
||||
"stop_freq": 8800000000.0,
|
||||
"points": 1000,
|
||||
"bandwidth": 1000.0
|
||||
},
|
||||
"calibration_name": "яыф",
|
||||
"standards": [
|
||||
"open",
|
||||
"load",
|
||||
"short"
|
||||
],
|
||||
"created_timestamp": "2025-09-26T17:20:00.022650",
|
||||
"is_complete": true
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,16 +0,0 @@
|
||||
{
|
||||
"preset": {
|
||||
"filename": "s11_start100_stop8800_points1000_bw1khz.bin",
|
||||
"mode": "s11",
|
||||
"start_freq": 100000000.0,
|
||||
"stop_freq": 8800000000.0,
|
||||
"points": 1000,
|
||||
"bandwidth": 1000.0
|
||||
},
|
||||
"calibration_name": "яыф",
|
||||
"standard": "load",
|
||||
"sweep_number": 12,
|
||||
"sweep_timestamp": 1758896376.33808,
|
||||
"created_timestamp": "2025-09-26T17:20:00.020322",
|
||||
"total_points": 1000
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,16 +0,0 @@
|
||||
{
|
||||
"preset": {
|
||||
"filename": "s11_start100_stop8800_points1000_bw1khz.bin",
|
||||
"mode": "s11",
|
||||
"start_freq": 100000000.0,
|
||||
"stop_freq": 8800000000.0,
|
||||
"points": 1000,
|
||||
"bandwidth": 1000.0
|
||||
},
|
||||
"calibration_name": "яыф",
|
||||
"standard": "open",
|
||||
"sweep_number": 17,
|
||||
"sweep_timestamp": 1758896395.4880857,
|
||||
"created_timestamp": "2025-09-26T17:20:00.016886",
|
||||
"total_points": 1000
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,16 +0,0 @@
|
||||
{
|
||||
"preset": {
|
||||
"filename": "s11_start100_stop8800_points1000_bw1khz.bin",
|
||||
"mode": "s11",
|
||||
"start_freq": 100000000.0,
|
||||
"stop_freq": 8800000000.0,
|
||||
"points": 1000,
|
||||
"bandwidth": 1000.0
|
||||
},
|
||||
"calibration_name": "яыф",
|
||||
"standard": "short",
|
||||
"sweep_number": 13,
|
||||
"sweep_timestamp": 1758896378.4093437,
|
||||
"created_timestamp": "2025-09-26T17:20:00.022500",
|
||||
"total_points": 1000
|
||||
}
|
||||
@ -2,9 +2,9 @@
|
||||
"open_air": true,
|
||||
"axis": "abs",
|
||||
"data_limitation": "ph_only_1",
|
||||
"cut": 1.291,
|
||||
"max": 1.0,
|
||||
"gain": 0.9,
|
||||
"cut": 2.0,
|
||||
"max": 5.0,
|
||||
"gain": 2.2,
|
||||
"start_freq": 100.0,
|
||||
"stop_freq": 8800.0,
|
||||
"clear_history": false,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"y_min": -80,
|
||||
"y_max": 40,
|
||||
"show_phase": true
|
||||
"show_phase": false
|
||||
}
|
||||
@ -183,6 +183,10 @@ class BScanProcessor(BaseProcessor):
|
||||
latest_history = self._sweep_history[-1]
|
||||
reference_data = latest_history.get("reference_data")
|
||||
|
||||
if reference_data is None:
|
||||
logger.warning(f"Open air substraction cannot be done: reference_data is None")
|
||||
self._config["open_air"] = False
|
||||
|
||||
if self._config["open_air"] and reference_data is not None:
|
||||
reference_complex = self._get_complex_s11(reference_data)
|
||||
if reference_complex is not None and reference_complex.size:
|
||||
|
||||
@ -188,6 +188,10 @@ class VNASettingsManager:
|
||||
raise ValueError("No current preset available")
|
||||
return self.reference_manager.delete_reference(reference_name, current_preset)
|
||||
|
||||
def delete_calibration(self, preset: ConfigPreset, calibration_name: str) -> None:
|
||||
"""Delete a saved calibration for the given preset."""
|
||||
self.calibration_manager.delete_calibration(preset, calibration_name)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Acquisition integration
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@ -74,15 +74,6 @@ else
|
||||
log_warning "Consider creating a virtual environment: python3 -m venv venv"
|
||||
fi
|
||||
|
||||
# Check configuration files
|
||||
if [ ! -f "vna_system/api/api_config.json" ]; then
|
||||
log_warning "API config not found. Using defaults."
|
||||
fi
|
||||
|
||||
if [ ! -f "vna_system/core/processing/config.json" ]; then
|
||||
log_warning "Processing config not found. Some processors may not work."
|
||||
fi
|
||||
|
||||
# Start the server
|
||||
log_info "Starting VNA System API server..."
|
||||
log_info "Press Ctrl+C to stop the server"
|
||||
|
||||
@ -518,61 +518,40 @@
|
||||
margin-top: var(--space-3);
|
||||
}
|
||||
|
||||
/* Current Reference Info */
|
||||
.current-reference-info {
|
||||
margin-top: var(--space-4);
|
||||
padding-top: var(--space-4);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.reference-info-card {
|
||||
background: var(--color-gray-800);
|
||||
border: 1px solid var(--color-gray-600);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.reference-info-card h5 {
|
||||
margin: 0 0 var(--space-3) 0;
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--weight-semibold);
|
||||
color: var(--color-gray-300);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
/* Reference details */
|
||||
.reference-details {
|
||||
margin-top: var(--space-3);
|
||||
padding: var(--space-3);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-primary);
|
||||
background: var(--bg-surface);
|
||||
box-shadow: var(--shadow-sm);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
background: var(--color-gray-900);
|
||||
padding: var(--space-3);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--color-gray-700);
|
||||
}
|
||||
|
||||
.reference-name {
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--weight-semibold);
|
||||
color: var(--color-white);
|
||||
.reference-details__row {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
font-size: var(--font-size-sm);
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.reference-timestamp {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-gray-300);
|
||||
font-family: var(--font-mono);
|
||||
.reference-details__label {
|
||||
min-width: 90px;
|
||||
color: var(--text-secondary);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.reference-description {
|
||||
margin: 0;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-gray-400);
|
||||
font-style: italic;
|
||||
.reference-details__value {
|
||||
color: var(--text-primary);
|
||||
flex: 1 1 auto;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.reference-description:empty::before {
|
||||
content: "Описание не указано";
|
||||
opacity: 0.7;
|
||||
.reference-details__row--description {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
|
||||
@ -121,17 +121,21 @@ export class AcquisitionManager {
|
||||
async updateStatus() {
|
||||
try {
|
||||
const status = await apiGet(API.ACQUISITION.STATUS);
|
||||
const normalized = { ...status, connected: true };
|
||||
|
||||
this.currentStatus = status;
|
||||
this.updateUI(status);
|
||||
this.currentStatus = normalized;
|
||||
this.updateUI(normalized);
|
||||
} catch (error) {
|
||||
console.error('Error getting acquisition status:', error);
|
||||
this.updateUI({
|
||||
const fallback = {
|
||||
running: false,
|
||||
paused: false,
|
||||
continuous_mode: true,
|
||||
sweep_count: 0
|
||||
});
|
||||
sweep_count: 0,
|
||||
connected: false
|
||||
};
|
||||
this.currentStatus = fallback;
|
||||
this.updateUI(fallback);
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,6 +180,20 @@ export class AcquisitionManager {
|
||||
this.elements.singleSweepBtn.disabled = false;
|
||||
}
|
||||
|
||||
const systemStatusEl = document.getElementById('systemStatus');
|
||||
if (systemStatusEl) {
|
||||
if (status.connected === false) {
|
||||
systemStatusEl.textContent = 'Не подключено';
|
||||
} else {
|
||||
const mode = status.continuous_mode ? 'Непрерывный' : 'Одиночный';
|
||||
let suffix = ' (остановлен)';
|
||||
if (status.running) {
|
||||
suffix = status.paused ? ' (пауза)' : ' (запущен)';
|
||||
}
|
||||
systemStatusEl.textContent = `Режим: ${mode}${suffix}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Update sweep count in header if available
|
||||
const sweepCountEl = document.getElementById('sweepCount');
|
||||
if (sweepCountEl) {
|
||||
|
||||
@ -75,6 +75,7 @@ export const API = {
|
||||
CALIBRATION_START: `${API_BASE}/settings/calibration/start`,
|
||||
CALIBRATION_SAVE: `${API_BASE}/settings/calibration/save`,
|
||||
CALIBRATION_SET: `${API_BASE}/settings/calibration/set`,
|
||||
CALIBRATION_DELETE: (name) => `${API_BASE}/settings/calibration/${encodeURIComponent(name)}`,
|
||||
CALIBRATION_ADD_STANDARD: `${API_BASE}/settings/calibration/add-standard`,
|
||||
CALIBRATION_STANDARDS_PLOTS: (name) => `${API_BASE}/settings/calibration/${encodeURIComponent(name)}/standards-plots`,
|
||||
|
||||
|
||||
@ -76,6 +76,7 @@ export class SettingsManager {
|
||||
calibrationDropdown: document.getElementById('calibrationDropdown'),
|
||||
setCalibrationBtn: document.getElementById('setCalibrationBtn'),
|
||||
viewPlotsBtn: document.getElementById('viewPlotsBtn'),
|
||||
deleteCalibrationBtn: document.getElementById('deleteCalibrationBtn'),
|
||||
viewCurrentPlotsBtn: document.getElementById('viewCurrentPlotsBtn'),
|
||||
|
||||
// Modal
|
||||
@ -91,6 +92,7 @@ export class SettingsManager {
|
||||
setReferenceBtn: document.getElementById('setReferenceBtn'),
|
||||
clearReferenceBtn: document.getElementById('clearReferenceBtn'),
|
||||
deleteReferenceBtn: document.getElementById('deleteReferenceBtn'),
|
||||
previewReferenceBtn: document.getElementById('previewReferenceBtn'),
|
||||
currentReferenceInfo: document.getElementById('currentReferenceInfo'),
|
||||
currentReferenceName: document.getElementById('currentReferenceName'),
|
||||
currentReferenceTimestamp: document.getElementById('currentReferenceTimestamp'),
|
||||
@ -99,6 +101,7 @@ export class SettingsManager {
|
||||
// Status
|
||||
presetCount: document.getElementById('presetCount'),
|
||||
calibrationCount: document.getElementById('calibrationCount'),
|
||||
referenceCount: document.getElementById('referenceCount'),
|
||||
systemStatus: document.getElementById('systemStatus')
|
||||
};
|
||||
|
||||
@ -158,7 +161,7 @@ export class SettingsManager {
|
||||
} catch (e) {
|
||||
console.error('Status load failed:', e);
|
||||
if (this.elements.systemStatus) {
|
||||
this.elements.systemStatus.textContent = 'Ошибка';
|
||||
this.elements.systemStatus.textContent = 'Не подключено';
|
||||
}
|
||||
this.notify(ERROR, 'Ошибка статуса', 'Не удалось получить текущее состояние системы');
|
||||
return null;
|
||||
@ -191,7 +194,7 @@ export class SettingsManager {
|
||||
await this.calibrationManager.loadWorkingCalibration();
|
||||
|
||||
if (!status && this.elements.systemStatus) {
|
||||
this.elements.systemStatus.textContent = 'Готово';
|
||||
this.elements.systemStatus.textContent = 'Не подключено';
|
||||
}
|
||||
|
||||
const effectiveStatus = {
|
||||
@ -206,6 +209,9 @@ export class SettingsManager {
|
||||
if (this.elements.calibrationCount) {
|
||||
this.elements.calibrationCount.textContent = effectiveStatus.available_calibrations ?? '-';
|
||||
}
|
||||
if (this.elements.referenceCount) {
|
||||
this.elements.referenceCount.textContent = '-';
|
||||
}
|
||||
}
|
||||
|
||||
this.updateHeaderSummary(effectiveStatus);
|
||||
@ -227,10 +233,10 @@ export class SettingsManager {
|
||||
if (this.elements.calibrationCount) {
|
||||
this.elements.calibrationCount.textContent = status?.available_calibrations ?? '-';
|
||||
}
|
||||
if (this.elements.systemStatus) {
|
||||
this.elements.systemStatus.textContent = 'Готово';
|
||||
if (this.elements.referenceCount) {
|
||||
const count = this.referenceManager?.availableReferences?.length;
|
||||
this.elements.referenceCount.textContent = typeof count === 'number' ? count : '-';
|
||||
}
|
||||
|
||||
this.updateHeaderSummary(status);
|
||||
this.updateReferenceSummary(this.referenceManager.getCurrentReference());
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
import { Debouncer, RequestGuard, ButtonState } from '../utils.js';
|
||||
import { renderIcons } from '../icons.js';
|
||||
import { apiGet, apiPost, buildUrl } from '../api-client.js';
|
||||
import { apiGet, apiPost, apiDelete, buildUrl } from '../api-client.js';
|
||||
import { API, TIMING, CALIBRATION_STANDARDS, NOTIFICATION_TYPES } from '../constants.js';
|
||||
|
||||
const { SUCCESS, ERROR, INFO } = NOTIFICATION_TYPES;
|
||||
@ -25,6 +25,7 @@ export class CalibrationManager {
|
||||
this.handleSaveCalibration = this.handleSaveCalibration.bind(this);
|
||||
this.handleSetCalibration = this.handleSetCalibration.bind(this);
|
||||
this.handleCalibrationChange = this.handleCalibrationChange.bind(this);
|
||||
this.handleDeleteCalibration = this.handleDeleteCalibration.bind(this);
|
||||
}
|
||||
|
||||
init(elements) {
|
||||
@ -33,6 +34,7 @@ export class CalibrationManager {
|
||||
this.elements.saveCalibrationBtn?.addEventListener('click', this.handleSaveCalibration);
|
||||
this.elements.calibrationDropdown?.addEventListener('change', this.handleCalibrationChange);
|
||||
this.elements.setCalibrationBtn?.addEventListener('click', this.handleSetCalibration);
|
||||
this.elements.deleteCalibrationBtn?.addEventListener('click', this.handleDeleteCalibration);
|
||||
|
||||
this.elements.calibrationNameInput?.addEventListener('input', () => {
|
||||
const hasName = this.elements.calibrationNameInput.value.trim().length > 0;
|
||||
@ -46,6 +48,7 @@ export class CalibrationManager {
|
||||
this.elements.saveCalibrationBtn?.removeEventListener('click', this.handleSaveCalibration);
|
||||
this.elements.calibrationDropdown?.removeEventListener('change', this.handleCalibrationChange);
|
||||
this.elements.setCalibrationBtn?.removeEventListener('click', this.handleSetCalibration);
|
||||
this.elements.deleteCalibrationBtn?.removeEventListener('click', this.handleDeleteCalibration);
|
||||
this.resetCaptureState();
|
||||
}
|
||||
|
||||
@ -91,6 +94,9 @@ export class CalibrationManager {
|
||||
dd.disabled = true;
|
||||
this.elements.setCalibrationBtn.disabled = true;
|
||||
this.elements.viewPlotsBtn.disabled = true;
|
||||
if (this.elements.deleteCalibrationBtn) {
|
||||
this.elements.deleteCalibrationBtn.disabled = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -105,6 +111,9 @@ export class CalibrationManager {
|
||||
dd.disabled = false;
|
||||
this.elements.setCalibrationBtn.disabled = true;
|
||||
this.elements.viewPlotsBtn.disabled = true;
|
||||
if (this.elements.deleteCalibrationBtn) {
|
||||
this.elements.deleteCalibrationBtn.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
updateWorking(working) {
|
||||
@ -195,6 +204,9 @@ export class CalibrationManager {
|
||||
const v = this.elements.calibrationDropdown.value;
|
||||
this.elements.setCalibrationBtn.disabled = !v;
|
||||
this.elements.viewPlotsBtn.disabled = !v;
|
||||
if (this.elements.deleteCalibrationBtn) {
|
||||
this.elements.deleteCalibrationBtn.disabled = !v;
|
||||
}
|
||||
}
|
||||
|
||||
async handleStartCalibration() {
|
||||
@ -233,6 +245,7 @@ export class CalibrationManager {
|
||||
|
||||
const btn = document.querySelector(`[data-standard="${standard}"]`);
|
||||
ButtonState.set(btn, { state: 'loading', icon: 'upload', text: 'Снятие...' });
|
||||
this.toggleStandardButtons(true);
|
||||
|
||||
this.notify(INFO, 'Снятие стандарта', `Снимается стандарт ${standard.toUpperCase()}...`);
|
||||
|
||||
@ -246,11 +259,29 @@ export class CalibrationManager {
|
||||
console.error('Capture standard failed:', e);
|
||||
this.notify(ERROR, 'Ошибка калибровки', 'Не удалось снять стандарт калибровки');
|
||||
this.resetCaptureState(standard);
|
||||
} finally {
|
||||
this.toggleStandardButtons(false);
|
||||
}
|
||||
}), TIMING.DEBOUNCE_CALIBRATION
|
||||
);
|
||||
}
|
||||
|
||||
toggleStandardButtons(disabled) {
|
||||
const buttons = this.elements.calibrationStandards?.querySelectorAll('.calibration-standard-btn');
|
||||
if (!buttons) return;
|
||||
buttons.forEach(btn => {
|
||||
if (disabled) {
|
||||
btn.dataset.prevDisabled = btn.disabled ? '1' : '0';
|
||||
btn.disabled = true;
|
||||
} else {
|
||||
if (btn.dataset.prevDisabled !== undefined) {
|
||||
btn.disabled = btn.dataset.prevDisabled === '1';
|
||||
delete btn.dataset.prevDisabled;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async handleSaveCalibration() {
|
||||
const name = this.elements.calibrationNameInput.value.trim();
|
||||
if (!name) return;
|
||||
@ -312,6 +343,48 @@ export class CalibrationManager {
|
||||
);
|
||||
}
|
||||
|
||||
async handleDeleteCalibration() {
|
||||
const name = this.elements.calibrationDropdown.value;
|
||||
if (!name || !this.currentPreset) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(`Удалить калибровку «${name}»? Это действие необратимо.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.debouncer.debounce('delete-calibration', () =>
|
||||
this.reqGuard.runExclusive('delete-calibration', async () => {
|
||||
try {
|
||||
if (this.elements.deleteCalibrationBtn) {
|
||||
ButtonState.set(this.elements.deleteCalibrationBtn, { state: 'loading', icon: 'loader', text: 'Удаление...' });
|
||||
}
|
||||
|
||||
const url = buildUrl(API.SETTINGS.CALIBRATION_DELETE(name), {
|
||||
preset_filename: this.currentPreset.filename
|
||||
});
|
||||
const result = await apiDelete(url);
|
||||
|
||||
this.notify(SUCCESS, 'Калибровка удалена', result.message);
|
||||
|
||||
await this.loadCalibrations();
|
||||
await this.loadWorkingCalibration();
|
||||
|
||||
if (this.onCalibrationSet) {
|
||||
await this.onCalibrationSet();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Delete calibration failed:', e);
|
||||
this.notify(ERROR, 'Ошибка калибровки', 'Не удалось удалить калибровку');
|
||||
} finally {
|
||||
if (this.elements.deleteCalibrationBtn) {
|
||||
ButtonState.set(this.elements.deleteCalibrationBtn, { state: 'normal', icon: 'trash-2', text: 'Удалить' });
|
||||
}
|
||||
}
|
||||
}), TIMING.DEBOUNCE_CALIBRATION
|
||||
);
|
||||
}
|
||||
|
||||
updateStatus(status) {
|
||||
if (status.current_calibration) {
|
||||
this.currentCalibration = status.current_calibration;
|
||||
|
||||
@ -68,14 +68,57 @@ export class PresetManager {
|
||||
}
|
||||
|
||||
formatDisplay(p) {
|
||||
let s = `${p.filename} (${p.mode})`;
|
||||
if (p.start_freq && p.stop_freq) {
|
||||
const startMHz = (p.start_freq / 1e6).toFixed(0);
|
||||
const stopMHz = (p.stop_freq / 1e6).toFixed(0);
|
||||
s += ` - ${startMHz}-${stopMHz}MHz`;
|
||||
if (!p) return '';
|
||||
|
||||
const parts = [];
|
||||
if (p.mode) {
|
||||
parts.push(p.mode.toUpperCase());
|
||||
}
|
||||
if (p.points) s += `, ${p.points}pts`;
|
||||
return s;
|
||||
|
||||
const start = this.formatFrequency(p.start_freq);
|
||||
const stop = this.formatFrequency(p.stop_freq);
|
||||
if (start && stop) {
|
||||
parts.push(`${start} – ${stop}`);
|
||||
} else if (start || stop) {
|
||||
parts.push(start || stop);
|
||||
}
|
||||
|
||||
if (p.points) {
|
||||
parts.push(`${p.points} точек`);
|
||||
}
|
||||
|
||||
const bandwidth = this.formatFrequency(p.bandwidth);
|
||||
if (bandwidth) {
|
||||
parts.push(`ПП ${bandwidth}`);
|
||||
}
|
||||
|
||||
if (!parts.length && p.filename) {
|
||||
return p.filename;
|
||||
}
|
||||
|
||||
return parts.join(' • ');
|
||||
}
|
||||
|
||||
formatFrequency(value) {
|
||||
if (value === null || value === undefined) return null;
|
||||
const numeric = Number(value);
|
||||
if (!Number.isFinite(numeric) || numeric <= 0) return null;
|
||||
|
||||
const units = [
|
||||
{ divider: 1e9, suffix: 'ГГц' },
|
||||
{ divider: 1e6, suffix: 'МГц' },
|
||||
{ divider: 1e3, suffix: 'кГц' },
|
||||
];
|
||||
|
||||
for (const unit of units) {
|
||||
if (numeric >= unit.divider) {
|
||||
const scaled = numeric / unit.divider;
|
||||
const formatted = scaled >= 10 ? scaled.toFixed(0) : scaled.toFixed(2);
|
||||
return `${formatted} ${unit.suffix}`;
|
||||
}
|
||||
}
|
||||
|
||||
return `${numeric.toFixed(0)} Гц`;
|
||||
}
|
||||
|
||||
handlePresetChange() {
|
||||
@ -114,7 +157,7 @@ export class PresetManager {
|
||||
updateStatus(status) {
|
||||
if (status.current_preset) {
|
||||
this.currentPreset = status.current_preset;
|
||||
this.elements.currentPreset.textContent = status.current_preset.filename;
|
||||
this.elements.currentPreset.textContent = this.formatDisplay(status.current_preset);
|
||||
} else {
|
||||
this.currentPreset = null;
|
||||
this.elements.currentPreset.textContent = 'Нет';
|
||||
@ -129,7 +172,7 @@ export class PresetManager {
|
||||
setCurrentPresetDirect(preset) {
|
||||
this.currentPreset = preset;
|
||||
if (this.elements.currentPreset) {
|
||||
this.elements.currentPreset.textContent = preset ? preset.filename : 'Нет';
|
||||
this.elements.currentPreset.textContent = preset ? this.formatDisplay(preset) : 'Нет';
|
||||
}
|
||||
this.syncSelectedPreset();
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ 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;
|
||||
const { SUCCESS, ERROR, WARNING, INFO } = NOTIFICATION_TYPES;
|
||||
|
||||
export class ReferenceManager {
|
||||
constructor(notifications) {
|
||||
@ -25,6 +25,7 @@ export class ReferenceManager {
|
||||
this.handleSetReference = this.handleSetReference.bind(this);
|
||||
this.handleClearReference = this.handleClearReference.bind(this);
|
||||
this.handleDeleteReference = this.handleDeleteReference.bind(this);
|
||||
this.handlePreviewReference = this.handlePreviewReference.bind(this);
|
||||
}
|
||||
|
||||
init(elements) {
|
||||
@ -34,6 +35,7 @@ export class ReferenceManager {
|
||||
this.elements.setReferenceBtn?.addEventListener('click', this.handleSetReference);
|
||||
this.elements.clearReferenceBtn?.addEventListener('click', this.handleClearReference);
|
||||
this.elements.deleteReferenceBtn?.addEventListener('click', this.handleDeleteReference);
|
||||
this.elements.previewReferenceBtn?.addEventListener('click', this.handlePreviewReference);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@ -42,6 +44,13 @@ export class ReferenceManager {
|
||||
this.elements.setReferenceBtn?.removeEventListener('click', this.handleSetReference);
|
||||
this.elements.clearReferenceBtn?.removeEventListener('click', this.handleClearReference);
|
||||
this.elements.deleteReferenceBtn?.removeEventListener('click', this.handleDeleteReference);
|
||||
this.elements.previewReferenceBtn?.removeEventListener('click', this.handlePreviewReference);
|
||||
}
|
||||
|
||||
updateReferenceCount(count) {
|
||||
if (this.elements.referenceCount) {
|
||||
this.elements.referenceCount.textContent = typeof count === 'number' ? count : '-';
|
||||
}
|
||||
}
|
||||
|
||||
async setCurrentPreset(preset) {
|
||||
@ -65,6 +74,7 @@ export class ReferenceManager {
|
||||
preset_filename: this.currentPreset.filename
|
||||
});
|
||||
this.availableReferences = await apiGet(referencesUrl);
|
||||
this.updateReferenceCount(this.availableReferences.length);
|
||||
|
||||
const currentUrl = buildUrl(API.SETTINGS.REFERENCE_CURRENT, {
|
||||
preset_filename: this.currentPreset.filename
|
||||
@ -77,6 +87,7 @@ export class ReferenceManager {
|
||||
console.error('Failed to load references:', error);
|
||||
this.renderDropdown([]);
|
||||
this.updateInfo(null);
|
||||
this.updateReferenceCount(null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,6 +103,7 @@ export class ReferenceManager {
|
||||
const setBtn = this.elements.setReferenceBtn;
|
||||
const deleteBtn = this.elements.deleteReferenceBtn;
|
||||
const clearBtn = this.elements.clearReferenceBtn;
|
||||
const previewBtn = this.elements.previewReferenceBtn;
|
||||
|
||||
if (references.length === 0) {
|
||||
this.elements.referenceDropdown.innerHTML = '<option value="">Эталоны отсутствуют</option>';
|
||||
@ -99,6 +111,7 @@ export class ReferenceManager {
|
||||
if (setBtn) setBtn.disabled = true;
|
||||
if (clearBtn) clearBtn.disabled = true;
|
||||
if (deleteBtn) deleteBtn.disabled = true;
|
||||
if (previewBtn) previewBtn.disabled = true;
|
||||
} else {
|
||||
this.elements.referenceDropdown.innerHTML = '<option value="">Выберите эталон...</option>';
|
||||
|
||||
@ -117,6 +130,7 @@ export class ReferenceManager {
|
||||
|
||||
if (setBtn) setBtn.disabled = false;
|
||||
if (deleteBtn) deleteBtn.disabled = false;
|
||||
if (previewBtn) previewBtn.disabled = false;
|
||||
}
|
||||
|
||||
this.updateButtons();
|
||||
@ -132,10 +146,12 @@ export class ReferenceManager {
|
||||
const setBtn = this.elements.setReferenceBtn;
|
||||
const deleteBtn = this.elements.deleteReferenceBtn;
|
||||
const clearBtn = this.elements.clearReferenceBtn;
|
||||
const previewBtn = this.elements.previewReferenceBtn;
|
||||
|
||||
if (setBtn) setBtn.disabled = !hasSelection;
|
||||
if (deleteBtn) deleteBtn.disabled = !hasSelection;
|
||||
if (clearBtn) clearBtn.disabled = !hasCurrent;
|
||||
if (previewBtn) previewBtn.disabled = !(hasSelection || hasCurrent);
|
||||
}
|
||||
|
||||
updateInfo(reference) {
|
||||
@ -148,11 +164,15 @@ export class ReferenceManager {
|
||||
|
||||
if (!reference) {
|
||||
this.elements.currentReferenceInfo.style.display = 'none';
|
||||
if (this.elements.currentReferenceName) this.elements.currentReferenceName.textContent = '—';
|
||||
if (this.elements.currentReferenceTimestamp) this.elements.currentReferenceTimestamp.textContent = '—';
|
||||
if (this.elements.currentReferenceDescription) this.elements.currentReferenceDescription.textContent = '—';
|
||||
this.onReferenceUpdated?.(null);
|
||||
this.updateButtons();
|
||||
return;
|
||||
}
|
||||
|
||||
this.elements.currentReferenceInfo.style.display = 'block';
|
||||
this.elements.currentReferenceInfo.style.display = 'flex';
|
||||
|
||||
if (this.elements.currentReferenceName) {
|
||||
this.elements.currentReferenceName.textContent = reference.name;
|
||||
@ -166,10 +186,11 @@ export class ReferenceManager {
|
||||
}
|
||||
|
||||
if (this.elements.currentReferenceDescription) {
|
||||
this.elements.currentReferenceDescription.textContent = reference.description || '';
|
||||
this.elements.currentReferenceDescription.textContent = reference.description || '—';
|
||||
}
|
||||
|
||||
this.onReferenceUpdated?.(reference);
|
||||
this.updateButtons();
|
||||
}
|
||||
|
||||
async handleCreateReference() {
|
||||
@ -208,6 +229,51 @@ export class ReferenceManager {
|
||||
this.updateButtons();
|
||||
}
|
||||
|
||||
async handlePreviewReference() {
|
||||
const dropdown = this.elements.referenceDropdown;
|
||||
const selectedName = dropdown?.value ?? '';
|
||||
const targetName = selectedName || this.currentReference?.name;
|
||||
if (!targetName) {
|
||||
this.notify(WARNING, 'Нет данных', 'Не выбран ни один эталон для просмотра');
|
||||
return;
|
||||
}
|
||||
|
||||
this.debouncer.debounce('preview-reference', () =>
|
||||
this.reqGuard.runExclusive('preview-reference', async () => {
|
||||
try {
|
||||
const url = buildUrl(API.SETTINGS.REFERENCE_ITEM(targetName), {
|
||||
preset_filename: this.currentPreset?.filename
|
||||
});
|
||||
const details = await apiGet(url);
|
||||
|
||||
const timestamp = details?.timestamp
|
||||
? new Date(details.timestamp).toLocaleString()
|
||||
: '—';
|
||||
const description = details?.description ? details.description : '—';
|
||||
|
||||
const lines = [
|
||||
`Имя: ${details?.name ?? targetName}`,
|
||||
`Дата: ${timestamp}`,
|
||||
`Описание: ${description}`
|
||||
];
|
||||
|
||||
if (details?.metadata && Object.keys(details.metadata).length) {
|
||||
const metaPairs = Object.entries(details.metadata)
|
||||
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
|
||||
.join('; ');
|
||||
lines.push(`Метаданные: ${metaPairs}`);
|
||||
}
|
||||
|
||||
const message = lines.join(' • ');
|
||||
this.notify(INFO, 'Просмотр эталона', message);
|
||||
} catch (error) {
|
||||
console.error('Preview reference failed:', error);
|
||||
this.notify(ERROR, 'Ошибка эталона', 'Не удалось загрузить данные эталона');
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async handleSetReference() {
|
||||
const referenceName = this.elements.referenceDropdown.value;
|
||||
if (!referenceName) return;
|
||||
@ -263,7 +329,7 @@ export class ReferenceManager {
|
||||
const referenceName = this.elements.referenceDropdown.value;
|
||||
if (!referenceName) return;
|
||||
|
||||
if (!confirm(`Are you sure you want to delete reference "${referenceName}"? This action cannot be undone.`)) {
|
||||
if (!confirm(`Удалить эталон «${referenceName}»? Это действие необратимо.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -292,6 +358,7 @@ export class ReferenceManager {
|
||||
this.currentReference = null;
|
||||
this.renderDropdown([]);
|
||||
this.updateInfo(null);
|
||||
this.updateReferenceCount(0);
|
||||
}
|
||||
|
||||
notify(type, title, message) {
|
||||
|
||||
@ -246,6 +246,10 @@
|
||||
<span data-icon="bar-chart-3"></span>
|
||||
Показать графики
|
||||
</button>
|
||||
<button class="btn btn--secondary" id="deleteCalibrationBtn" disabled>
|
||||
<span data-icon="trash-2"></span>
|
||||
Удалить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -278,19 +282,33 @@
|
||||
<option value="">Эталоны отсутствуют</option>
|
||||
</select>
|
||||
<div class="reference-actions settings-action-group">
|
||||
<button class="btn btn--primary" id="setReferenceBtn" disabled>
|
||||
<button class="btn btn--primary" id="setReferenceBtn" disabled>
|
||||
<span data-icon="check"></span>
|
||||
Использовать
|
||||
</button>
|
||||
<button class="btn btn--secondary" id="previewReferenceBtn" disabled>
|
||||
<span data-icon="bar-chart-3"></span>
|
||||
Просмотр
|
||||
</button>
|
||||
<button class="btn btn--secondary" id="previewReferenceBtn" disabled>
|
||||
<span data-icon="bar-chart-3"></span>
|
||||
Просмотр
|
||||
</button>
|
||||
<button class="btn btn--secondary" id="deleteReferenceBtn" disabled>
|
||||
<span data-icon="trash-2"></span>
|
||||
Удалить
|
||||
</button>
|
||||
</div>
|
||||
<div class="reference-details" id="currentReferenceInfo" style="display: none;">
|
||||
<div class="reference-details__row">
|
||||
<span class="reference-details__label">Имя:</span>
|
||||
<span class="reference-details__value" id="currentReferenceName">—</span>
|
||||
</div>
|
||||
<div class="reference-details__row">
|
||||
<span class="reference-details__label">Дата:</span>
|
||||
<span class="reference-details__value" id="currentReferenceTimestamp">—</span>
|
||||
</div>
|
||||
<div class="reference-details__row reference-details__row--description">
|
||||
<span class="reference-details__label">Описание:</span>
|
||||
<span class="reference-details__value" id="currentReferenceDescription">—</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -304,21 +322,17 @@
|
||||
<span class="status-label">Получено свипов:</span>
|
||||
<span class="status-value" id="sweepCount">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Обработано точек:</span>
|
||||
<span class="status-value" id="pointCount">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Активные процессоры:</span>
|
||||
<span class="status-value" id="processorCount">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Калибровок сохранено:</span>
|
||||
<span class="status-value" id="calibrationCount">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Эталонов сохранено:</span>
|
||||
<span class="status-value" id="referenceCount">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Статус системы:</span>
|
||||
<span class="status-value" id="systemStatus">Проверка...</span>
|
||||
<span class="status-value" id="systemStatus">Режим: —</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user