updated view and added calibration deletion

This commit is contained in:
Ayzen
2025-09-30 18:09:50 +03:00
parent 7c26285e7f
commit facff2b4f8
23 changed files with 327 additions and 12183 deletions

View File

@ -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))

View File

@ -1 +1 @@
config_inputs/s21_start100_stop8800_points1000_bw1khz.bin
config_inputs/s11_start100_stop8800_points1000_bw1khz.bin

View File

@ -0,0 +1 @@
s11_start100_stop8800_points1000_bw1khz/еуыеуые

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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,

View File

@ -1,5 +1,5 @@
{
"y_min": -80,
"y_max": 40,
"show_phase": true
"show_phase": false
}

View File

@ -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:

View File

@ -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
# ------------------------------------------------------------------ #
@ -369,4 +373,4 @@ class VNASettingsManager:
current_sweep=current_sweep_number,
timeout=timeout_seconds
)
return None
return None

View File

@ -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"

View File

@ -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 */

View File

@ -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) {

View File

@ -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`,

View File

@ -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());
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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) {

View File

@ -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>