some improvements
This commit is contained in:
@ -588,6 +588,88 @@ async def clear_current_reference() -> dict[str, Any]:
|
||||
raise HTTPException(status_code=500, detail=str(exc))
|
||||
|
||||
|
||||
@router.get("/reference/{reference_name}", response_model=ReferenceModel)
|
||||
async def get_reference(reference_name: str, preset_filename: str | None = None) -> ReferenceModel:
|
||||
"""Get information about a specific reference."""
|
||||
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")
|
||||
|
||||
info = settings_manager.get_reference_info(reference_name, preset)
|
||||
if not info:
|
||||
raise HTTPException(status_code=404, detail=f"Reference '{reference_name}' not found")
|
||||
|
||||
return ReferenceModel(
|
||||
name=info.name,
|
||||
timestamp=info.timestamp.isoformat(),
|
||||
preset_filename=info.preset_filename,
|
||||
description=info.description,
|
||||
metadata=info.metadata,
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.error("Failed to get reference info", name=reference_name, error=repr(exc))
|
||||
raise HTTPException(status_code=500, detail=str(exc))
|
||||
|
||||
|
||||
@router.get("/reference/{reference_name}/plot")
|
||||
async def get_reference_plot(reference_name: str, preset_filename: str | None = None) -> dict[str, Any]:
|
||||
"""Get magnitude plot for a reference measurement."""
|
||||
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")
|
||||
|
||||
reference_info = settings_manager.get_reference_info(reference_name, preset)
|
||||
if not reference_info:
|
||||
raise HTTPException(status_code=404, detail=f"Reference '{reference_name}' not found")
|
||||
|
||||
sweep_data = settings_manager.reference_manager.get_reference_sweep(reference_name, preset)
|
||||
if not sweep_data:
|
||||
raise HTTPException(status_code=404, detail=f"Reference data not found for '{reference_name}'")
|
||||
|
||||
from vna_system.core.visualization.magnitude_chart import generate_magnitude_plot_from_sweep_data
|
||||
|
||||
plot_fig = generate_magnitude_plot_from_sweep_data(sweep_data, preset)
|
||||
if "error" in plot_fig:
|
||||
raise HTTPException(status_code=500, detail=plot_fig["error"])
|
||||
|
||||
return {
|
||||
"reference_name": reference_name,
|
||||
"preset": {"filename": preset.filename, "mode": preset.mode.value},
|
||||
"plot": plot_fig,
|
||||
"timestamp": reference_info.timestamp.isoformat(),
|
||||
"description": reference_info.description
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.error("Failed to get reference plot", name=reference_name, error=repr(exc))
|
||||
raise HTTPException(status_code=500, detail=str(exc))
|
||||
|
||||
|
||||
@router.delete("/reference/{reference_name}")
|
||||
async def delete_reference(reference_name: str, preset_filename: str | None = None) -> dict[str, Any]:
|
||||
"""Delete a reference."""
|
||||
@ -622,6 +704,17 @@ async def delete_reference(reference_name: str, preset_filename: str | None = No
|
||||
raise HTTPException(status_code=500, detail=str(exc))
|
||||
|
||||
|
||||
@router.delete("/calibration/current")
|
||||
async def clear_current_calibration() -> dict[str, Any]:
|
||||
"""Clear the current calibration selection."""
|
||||
try:
|
||||
singletons.settings_manager.calibration_manager.clear_current_calibration()
|
||||
return {"success": True, "message": "Current calibration cleared"}
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.error("Failed to clear current calibration", 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."""
|
||||
|
||||
@ -23,6 +23,7 @@ class SettingsStatusModel(BaseModel):
|
||||
working_calibration: Dict[str, Any] | None
|
||||
available_presets: int
|
||||
available_calibrations: int
|
||||
available_references: int
|
||||
|
||||
|
||||
class SetPresetRequest(BaseModel):
|
||||
|
||||
@ -1 +1 @@
|
||||
s11_start100_stop8800_points1000_bw1khz/еуыеуые
|
||||
s11_start100_stop8800_points1000_bw1khz/вфыввф
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -7,12 +7,12 @@
|
||||
"points": 1000,
|
||||
"bandwidth": 1000.0
|
||||
},
|
||||
"calibration_name": "tuncTuncTuncSahur",
|
||||
"calibration_name": "вфыввф",
|
||||
"standards": [
|
||||
"open",
|
||||
"short",
|
||||
"load"
|
||||
],
|
||||
"created_timestamp": "2025-09-24T17:55:12.053850",
|
||||
"created_timestamp": "2025-09-30T18:55:17.618157",
|
||||
"is_complete": true
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -7,10 +7,10 @@
|
||||
"points": 1000,
|
||||
"bandwidth": 1000.0
|
||||
},
|
||||
"calibration_name": "tuncTuncTuncSahur",
|
||||
"calibration_name": "вфыввф",
|
||||
"standard": "load",
|
||||
"sweep_number": 1221,
|
||||
"sweep_timestamp": 1758725709.366605,
|
||||
"created_timestamp": "2025-09-24T17:55:12.053789",
|
||||
"sweep_number": 33,
|
||||
"sweep_timestamp": 1759247715.1613321,
|
||||
"created_timestamp": "2025-09-30T18:55:17.618031",
|
||||
"total_points": 1000
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -7,10 +7,10 @@
|
||||
"points": 1000,
|
||||
"bandwidth": 1000.0
|
||||
},
|
||||
"calibration_name": "tuncTuncTuncSahur",
|
||||
"calibration_name": "вфыввф",
|
||||
"standard": "open",
|
||||
"sweep_number": 1217,
|
||||
"sweep_timestamp": 1758725700.971026,
|
||||
"created_timestamp": "2025-09-24T17:55:12.049478",
|
||||
"sweep_number": 18,
|
||||
"sweep_timestamp": 1759247683.8793015,
|
||||
"created_timestamp": "2025-09-30T18:55:17.612864",
|
||||
"total_points": 1000
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -7,10 +7,10 @@
|
||||
"points": 1000,
|
||||
"bandwidth": 1000.0
|
||||
},
|
||||
"calibration_name": "tuncTuncTuncSahur",
|
||||
"calibration_name": "вфыввф",
|
||||
"standard": "short",
|
||||
"sweep_number": 1219,
|
||||
"sweep_timestamp": 1758725705.1671622,
|
||||
"created_timestamp": "2025-09-24T17:55:12.051603",
|
||||
"sweep_number": 19,
|
||||
"sweep_timestamp": 1759247685.9623504,
|
||||
"created_timestamp": "2025-09-30T18:55:17.615536",
|
||||
"total_points": 1000
|
||||
}
|
||||
@ -4,7 +4,7 @@
|
||||
"data_limitation": "ph_only_1",
|
||||
"cut": 2.0,
|
||||
"max": 5.0,
|
||||
"gain": 2.2,
|
||||
"gain": 0.0,
|
||||
"start_freq": 100.0,
|
||||
"stop_freq": 8800.0,
|
||||
"clear_history": false,
|
||||
|
||||
@ -188,6 +188,13 @@ class VNASettingsManager:
|
||||
raise ValueError("No current preset available")
|
||||
return self.reference_manager.delete_reference(reference_name, current_preset)
|
||||
|
||||
def get_reference_info(self, reference_name: str, preset: ConfigPreset | None = None) -> ReferenceInfo | None:
|
||||
"""Get detailed information about a reference."""
|
||||
current_preset = preset or self.get_current_preset()
|
||||
if not current_preset:
|
||||
raise ValueError("No current preset available")
|
||||
return self.reference_manager.get_reference_info(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)
|
||||
@ -309,7 +316,8 @@ class VNASettingsManager:
|
||||
"current_calibration": None,
|
||||
"working_calibration": None,
|
||||
"available_presets": len(self.get_available_presets()),
|
||||
"available_calibrations": 0
|
||||
"available_calibrations": 0,
|
||||
"available_references": 0,
|
||||
}
|
||||
|
||||
if current_preset:
|
||||
@ -322,6 +330,7 @@ class VNASettingsManager:
|
||||
"bandwidth": current_preset.bandwidth
|
||||
}
|
||||
summary["available_calibrations"] = len(self.get_available_calibrations(current_preset))
|
||||
summary["available_references"] = len(self.get_available_references(current_preset))
|
||||
|
||||
if current_calibration:
|
||||
summary["current_calibration"] = {
|
||||
|
||||
@ -1 +1 @@
|
||||
s21_start100_stop8800_points1000_bw1khz/ффывфы
|
||||
s11_start100_stop8800_points1000_bw1khz/asd
|
||||
@ -431,7 +431,7 @@
|
||||
/* Notifications */
|
||||
.notifications {
|
||||
position: fixed;
|
||||
top: var(--space-4);
|
||||
top: calc(var(--header-height) + var(--space-2));
|
||||
right: var(--space-4);
|
||||
z-index: var(--z-toast);
|
||||
display: flex;
|
||||
|
||||
@ -518,6 +518,11 @@
|
||||
margin-top: var(--space-3);
|
||||
}
|
||||
|
||||
#currentReferenceInfo {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Reference details */
|
||||
.reference-details {
|
||||
margin-top: var(--space-3);
|
||||
|
||||
@ -85,7 +85,7 @@ class VNADashboard {
|
||||
this.notifications.show({
|
||||
type: 'info',
|
||||
title: 'Панель готова',
|
||||
message: 'Соединение с системой ВНА установлено. Ожидание данных свипа...'
|
||||
message: 'Соединение с системой установлено. Ожидание данных свипа...'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@ -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_CLEAR_CURRENT: `${API_BASE}/settings/calibration/current`,
|
||||
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`,
|
||||
@ -86,7 +87,8 @@ export const API = {
|
||||
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)}`
|
||||
REFERENCE_ITEM: (name) => `${API_BASE}/settings/reference/${encodeURIComponent(name)}`,
|
||||
REFERENCE_PLOT: (name) => `${API_BASE}/settings/reference/${encodeURIComponent(name)}/plot`
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -136,10 +136,9 @@ const ICONS = {
|
||||
'eye-off': {
|
||||
viewBox: '0 0 24 24',
|
||||
elements: [
|
||||
{ type: 'path', attrs: { d: 'M3 3l18 18' } },
|
||||
{ type: 'path', attrs: { d: 'M9.5 9.5A4 4 0 0 0 12 16a4 4 0 0 0 3.5-6.5' } },
|
||||
{ type: 'path', attrs: { d: 'M7.2 7.5C4.8 8.9 3 12 3 12s2.7 5 9 5c1.5 0 2.8-.3 3.9-.8' } },
|
||||
{ type: 'path', attrs: { d: 'M17.2 10.2C18.7 9.2 21 12 21 12s-2.7 5-9 5' } }
|
||||
{ type: 'path', attrs: { d: 'M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12z' } },
|
||||
{ type: 'circle', attrs: { cx: 12, cy: 12, r: 3 } },
|
||||
{ type: 'line', attrs: { x1: 4, y1: 4, x2: 20, y2: 20 } }
|
||||
]
|
||||
},
|
||||
'alert-circle': {
|
||||
|
||||
@ -77,6 +77,7 @@ export class SettingsManager {
|
||||
setCalibrationBtn: document.getElementById('setCalibrationBtn'),
|
||||
viewPlotsBtn: document.getElementById('viewPlotsBtn'),
|
||||
deleteCalibrationBtn: document.getElementById('deleteCalibrationBtn'),
|
||||
clearCalibrationBtn: document.getElementById('clearCalibrationBtn'),
|
||||
viewCurrentPlotsBtn: document.getElementById('viewCurrentPlotsBtn'),
|
||||
|
||||
// Modal
|
||||
@ -139,6 +140,10 @@ export class SettingsManager {
|
||||
this.referenceManager.onReferenceUpdated = (reference) => {
|
||||
this.updateReferenceSummary(reference);
|
||||
};
|
||||
|
||||
this.referenceManager.onShowPlots = (plotData) => {
|
||||
this.showPlotsModal(plotData);
|
||||
};
|
||||
}
|
||||
|
||||
setupEventHandlers() {
|
||||
@ -163,6 +168,9 @@ export class SettingsManager {
|
||||
if (this.elements.systemStatus) {
|
||||
this.elements.systemStatus.textContent = 'Не подключено';
|
||||
}
|
||||
if (this.elements.referenceCount) {
|
||||
this.elements.referenceCount.textContent = '-';
|
||||
}
|
||||
this.notify(ERROR, 'Ошибка статуса', 'Не удалось получить текущее состояние системы');
|
||||
return null;
|
||||
}
|
||||
@ -198,8 +206,9 @@ export class SettingsManager {
|
||||
}
|
||||
|
||||
const effectiveStatus = {
|
||||
...(status ?? { available_presets: 0, available_calibrations: 0 }),
|
||||
...(status ?? { available_presets: 0, available_calibrations: 0, available_references: 0 }),
|
||||
current_preset: fallbackPreset,
|
||||
available_references: this.referenceManager?.availableReferences?.length ?? 0,
|
||||
};
|
||||
|
||||
if (!status) {
|
||||
@ -210,7 +219,7 @@ export class SettingsManager {
|
||||
this.elements.calibrationCount.textContent = effectiveStatus.available_calibrations ?? '-';
|
||||
}
|
||||
if (this.elements.referenceCount) {
|
||||
this.elements.referenceCount.textContent = '-';
|
||||
this.elements.referenceCount.textContent = effectiveStatus.available_references ?? '-';
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,7 +243,9 @@ export class SettingsManager {
|
||||
this.elements.calibrationCount.textContent = status?.available_calibrations ?? '-';
|
||||
}
|
||||
if (this.elements.referenceCount) {
|
||||
const count = this.referenceManager?.availableReferences?.length;
|
||||
const count = typeof status?.available_references === 'number'
|
||||
? status.available_references
|
||||
: this.referenceManager?.availableReferences?.length;
|
||||
this.elements.referenceCount.textContent = typeof count === 'number' ? count : '-';
|
||||
}
|
||||
this.updateHeaderSummary(status);
|
||||
@ -294,14 +305,25 @@ export class SettingsManager {
|
||||
|
||||
this.currentPlotsData = plotsData;
|
||||
|
||||
if (plotsData.reference_name) {
|
||||
this.renderReferencePlot(plotsData);
|
||||
} else {
|
||||
this.renderCalibrationPlots(plotsData.individual_plots, plotsData.preset);
|
||||
}
|
||||
|
||||
const title = modal.querySelector('.modal__title');
|
||||
if (title) {
|
||||
if (plotsData.reference_name) {
|
||||
title.innerHTML = `
|
||||
<span data-icon="target"></span>
|
||||
${plotsData.reference_name} - ${plotsData.preset.mode.toUpperCase()}
|
||||
`;
|
||||
} else {
|
||||
title.innerHTML = `
|
||||
<span data-icon="bar-chart-3"></span>
|
||||
${plotsData.calibration_name} - ${plotsData.preset.mode.toUpperCase()} Standards
|
||||
`;
|
||||
}
|
||||
renderIcons(title);
|
||||
}
|
||||
|
||||
@ -311,6 +333,60 @@ export class SettingsManager {
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
renderReferencePlot(plotsData) {
|
||||
const container = this.elements.plotsGrid;
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = '';
|
||||
|
||||
if (!plotsData.plot || plotsData.plot.error) {
|
||||
container.innerHTML = '<div class="plot-error">Не удалось загрузить график эталона</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.className = 'chart-card';
|
||||
card.innerHTML = `
|
||||
<div class="chart-card__header">
|
||||
<div class="chart-card__title">
|
||||
<span data-icon="target" class="chart-card__icon"></span>
|
||||
${plotsData.reference_name}
|
||||
</div>
|
||||
<div class="chart-card__actions">
|
||||
<button class="chart-card__action" data-action="fullscreen" title="На весь экран">
|
||||
<span data-icon="expand"></span>
|
||||
</button>
|
||||
<button class="chart-card__action" data-action="download" title="Скачать">
|
||||
<span data-icon="download"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-card__content">
|
||||
<div class="chart-card__plot" id="reference-plot"></div>
|
||||
</div>
|
||||
<div class="chart-card__meta">
|
||||
<div class="chart-card__timestamp">Дата: ${new Date(plotsData.timestamp).toLocaleString()}</div>
|
||||
<div class="chart-card__sweep">${plotsData.description || 'Эталон открытого пространства'}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
card.addEventListener('click', (e) => {
|
||||
const action = e.target.closest?.('[data-action]')?.dataset.action;
|
||||
if (!action) return;
|
||||
e.stopPropagation();
|
||||
|
||||
const plotEl = card.querySelector('.chart-card__plot');
|
||||
if (action === 'fullscreen') this.toggleFullscreen(card);
|
||||
if (action === 'download') this.downloadReferencePlot(plotsData, plotEl);
|
||||
});
|
||||
|
||||
container.appendChild(card);
|
||||
renderIcons(card);
|
||||
|
||||
const plotEl = card.querySelector('.chart-card__plot');
|
||||
this.renderPlotly(plotEl, plotsData.plot, plotsData.reference_name);
|
||||
}
|
||||
|
||||
renderCalibrationPlots(individualPlots, preset) {
|
||||
const container = this.elements.plotsGrid;
|
||||
if (!container) return;
|
||||
@ -473,6 +549,33 @@ export class SettingsManager {
|
||||
this.currentPlotsData = null;
|
||||
}
|
||||
|
||||
async downloadReferencePlot(plotsData, plotContainer) {
|
||||
try {
|
||||
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const base = `${plotsData.reference_name}_${ts}`;
|
||||
|
||||
if (plotContainer) {
|
||||
await downloadPlotlyImage(plotContainer, `${base}_plot`);
|
||||
}
|
||||
|
||||
const data = {
|
||||
reference_info: {
|
||||
name: plotsData.reference_name,
|
||||
timestamp: plotsData.timestamp,
|
||||
description: plotsData.description,
|
||||
preset: plotsData.preset
|
||||
},
|
||||
plot_data: plotsData.plot
|
||||
};
|
||||
downloadJSON(data, `${base}_data.json`);
|
||||
|
||||
this.notify(SUCCESS, 'Скачивание завершено', `Скачаны график и данные эталона ${plotsData.reference_name}`);
|
||||
} catch (e) {
|
||||
console.error('Download reference failed:', e);
|
||||
this.notify(ERROR, 'Ошибка скачивания', 'Не удалось скачать данные эталона');
|
||||
}
|
||||
}
|
||||
|
||||
async downloadCalibrationStandard(standardName, plotContainer) {
|
||||
try {
|
||||
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
|
||||
@ -26,6 +26,7 @@ export class CalibrationManager {
|
||||
this.handleSetCalibration = this.handleSetCalibration.bind(this);
|
||||
this.handleCalibrationChange = this.handleCalibrationChange.bind(this);
|
||||
this.handleDeleteCalibration = this.handleDeleteCalibration.bind(this);
|
||||
this.handleClearCalibration = this.handleClearCalibration.bind(this);
|
||||
}
|
||||
|
||||
init(elements) {
|
||||
@ -35,6 +36,7 @@ export class CalibrationManager {
|
||||
this.elements.calibrationDropdown?.addEventListener('change', this.handleCalibrationChange);
|
||||
this.elements.setCalibrationBtn?.addEventListener('click', this.handleSetCalibration);
|
||||
this.elements.deleteCalibrationBtn?.addEventListener('click', this.handleDeleteCalibration);
|
||||
this.elements.clearCalibrationBtn?.addEventListener('click', this.handleClearCalibration);
|
||||
|
||||
this.elements.calibrationNameInput?.addEventListener('input', () => {
|
||||
const hasName = this.elements.calibrationNameInput.value.trim().length > 0;
|
||||
@ -49,6 +51,7 @@ export class CalibrationManager {
|
||||
this.elements.calibrationDropdown?.removeEventListener('change', this.handleCalibrationChange);
|
||||
this.elements.setCalibrationBtn?.removeEventListener('click', this.handleSetCalibration);
|
||||
this.elements.deleteCalibrationBtn?.removeEventListener('click', this.handleDeleteCalibration);
|
||||
this.elements.clearCalibrationBtn?.removeEventListener('click', this.handleClearCalibration);
|
||||
this.resetCaptureState();
|
||||
}
|
||||
|
||||
@ -97,6 +100,9 @@ export class CalibrationManager {
|
||||
if (this.elements.deleteCalibrationBtn) {
|
||||
this.elements.deleteCalibrationBtn.disabled = true;
|
||||
}
|
||||
if (this.elements.clearCalibrationBtn) {
|
||||
this.elements.clearCalibrationBtn.disabled = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -114,6 +120,9 @@ export class CalibrationManager {
|
||||
if (this.elements.deleteCalibrationBtn) {
|
||||
this.elements.deleteCalibrationBtn.disabled = true;
|
||||
}
|
||||
if (this.elements.clearCalibrationBtn) {
|
||||
this.elements.clearCalibrationBtn.disabled = !this.currentCalibration;
|
||||
}
|
||||
}
|
||||
|
||||
updateWorking(working) {
|
||||
@ -385,6 +394,53 @@ export class CalibrationManager {
|
||||
);
|
||||
}
|
||||
|
||||
async handleClearCalibration() {
|
||||
if (!this.currentPreset || !this.currentCalibration) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(`Сбросить текущую калибровку «${this.currentCalibration.calibration_name}»? Вы сможете выбрать её снова позже.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.debouncer.debounce('clear-calibration', () =>
|
||||
this.reqGuard.runExclusive('clear-calibration', async () => {
|
||||
try {
|
||||
if (this.elements.clearCalibrationBtn) {
|
||||
ButtonState.set(this.elements.clearCalibrationBtn, { state: 'loading', icon: 'loader', text: 'Сброс...' });
|
||||
}
|
||||
|
||||
const result = await apiDelete(API.SETTINGS.CALIBRATION_CLEAR_CURRENT);
|
||||
|
||||
this.notify(INFO, 'Калибровка сброшена', result.message);
|
||||
|
||||
this.currentCalibration = null;
|
||||
if (this.elements.currentCalibration) {
|
||||
this.elements.currentCalibration.textContent = 'Нет';
|
||||
}
|
||||
|
||||
if (this.elements.calibrationDropdown) {
|
||||
this.elements.calibrationDropdown.value = '';
|
||||
}
|
||||
|
||||
await this.loadCalibrations();
|
||||
await this.loadWorkingCalibration();
|
||||
|
||||
if (this.onCalibrationSet) {
|
||||
await this.onCalibrationSet();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Clear calibration failed:', e);
|
||||
this.notify(ERROR, 'Ошибка калибровки', 'Не удалось сбросить калибровку');
|
||||
} finally {
|
||||
if (this.elements.clearCalibrationBtn) {
|
||||
ButtonState.set(this.elements.clearCalibrationBtn, { state: 'normal', icon: 'square', text: 'Сбросить' });
|
||||
}
|
||||
}
|
||||
}), TIMING.DEBOUNCE_CALIBRATION
|
||||
);
|
||||
}
|
||||
|
||||
updateStatus(status) {
|
||||
if (status.current_calibration) {
|
||||
this.currentCalibration = status.current_calibration;
|
||||
@ -393,6 +449,10 @@ export class CalibrationManager {
|
||||
this.currentCalibration = null;
|
||||
this.elements.currentCalibration.textContent = 'Нет';
|
||||
}
|
||||
|
||||
if (this.elements.clearCalibrationBtn) {
|
||||
this.elements.clearCalibrationBtn.disabled = !this.currentCalibration;
|
||||
}
|
||||
}
|
||||
|
||||
resetCaptureState(standard = null) {
|
||||
@ -413,6 +473,7 @@ export class CalibrationManager {
|
||||
}
|
||||
if (this.elements.saveCalibrationBtn) this.elements.saveCalibrationBtn.disabled = true;
|
||||
if (this.elements.progressText) this.elements.progressText.textContent = '0/0';
|
||||
if (this.elements.clearCalibrationBtn) this.elements.clearCalibrationBtn.disabled = true;
|
||||
}
|
||||
|
||||
getWorkingCalibration() {
|
||||
|
||||
@ -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, INFO } = NOTIFICATION_TYPES;
|
||||
const { SUCCESS, ERROR, WARNING } = NOTIFICATION_TYPES;
|
||||
|
||||
export class ReferenceManager {
|
||||
constructor(notifications) {
|
||||
@ -15,6 +15,7 @@ export class ReferenceManager {
|
||||
this.availableReferences = [];
|
||||
this.currentReference = null;
|
||||
this.currentPreset = null;
|
||||
this.lastPresetFilename = null;
|
||||
this.elements = {};
|
||||
this.debouncer = new Debouncer();
|
||||
this.reqGuard = new RequestGuard();
|
||||
@ -54,12 +55,23 @@ export class ReferenceManager {
|
||||
}
|
||||
|
||||
async setCurrentPreset(preset) {
|
||||
const previousPreset = this.currentPreset?.filename ?? null;
|
||||
const newPreset = preset?.filename ?? null;
|
||||
|
||||
this.currentPreset = preset;
|
||||
if (preset) {
|
||||
await this.loadReferences();
|
||||
} else {
|
||||
this.lastPresetFilename = newPreset;
|
||||
|
||||
if (!preset) {
|
||||
await this.clearReferenceCache();
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (previousPreset !== newPreset) {
|
||||
await this.clearServerCurrentReference();
|
||||
}
|
||||
|
||||
await this.loadReferences();
|
||||
}
|
||||
|
||||
async loadReferences() {
|
||||
@ -67,6 +79,7 @@ export class ReferenceManager {
|
||||
if (!this.currentPreset) {
|
||||
this.renderDropdown([]);
|
||||
this.updateInfo(null);
|
||||
this.updateReferenceCount(null);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -241,34 +254,21 @@ export class ReferenceManager {
|
||||
this.debouncer.debounce('preview-reference', () =>
|
||||
this.reqGuard.runExclusive('preview-reference', async () => {
|
||||
try {
|
||||
const url = buildUrl(API.SETTINGS.REFERENCE_ITEM(targetName), {
|
||||
ButtonState.set(this.elements.previewReferenceBtn, { state: 'loading', icon: 'loader', text: 'Загрузка...' });
|
||||
|
||||
const url = buildUrl(API.SETTINGS.REFERENCE_PLOT(targetName), {
|
||||
preset_filename: this.currentPreset?.filename
|
||||
});
|
||||
const details = await apiGet(url);
|
||||
const plotData = 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}`);
|
||||
if (this.onShowPlots) {
|
||||
this.onShowPlots(plotData);
|
||||
}
|
||||
|
||||
const message = lines.join(' • ');
|
||||
this.notify(INFO, 'Просмотр эталона', message);
|
||||
} catch (error) {
|
||||
console.error('Preview reference failed:', error);
|
||||
this.notify(ERROR, 'Ошибка эталона', 'Не удалось загрузить данные эталона');
|
||||
} finally {
|
||||
ButtonState.set(this.elements.previewReferenceBtn, { state: 'normal', icon: 'eye', text: 'Просмотр' });
|
||||
}
|
||||
})
|
||||
);
|
||||
@ -361,6 +361,23 @@ export class ReferenceManager {
|
||||
this.updateReferenceCount(0);
|
||||
}
|
||||
|
||||
async clearReferenceCache() {
|
||||
this.availableReferences = [];
|
||||
this.updateReferenceCount(0);
|
||||
this.updateInfo(null);
|
||||
}
|
||||
|
||||
async clearServerCurrentReference() {
|
||||
try {
|
||||
await apiDelete(API.SETTINGS.REFERENCE_CURRENT);
|
||||
} catch (error) {
|
||||
if (!error?.status || error.status !== 404) {
|
||||
console.error('Failed to clear current reference', error);
|
||||
}
|
||||
}
|
||||
await this.clearReferenceCache();
|
||||
}
|
||||
|
||||
notify(type, title, message) {
|
||||
this.notifications?.show?.({ type, title, message });
|
||||
}
|
||||
|
||||
@ -3,10 +3,9 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Панель управления системой ВНА для сбора и обработки данных в реальном времени">
|
||||
<meta name="keywords" content="ВНА, Vector Network Analyzer, РФ, микроволны, сбор данных, реальное время">
|
||||
<meta name="description" content="Панель управления системой для сбора и обработки данных в реальном времени">
|
||||
<meta name="author" content="VNA System">
|
||||
<title>Система ВНА — Панель управления</title>
|
||||
<title>Радар</title>
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/svg+xml" href="/static/assets/favicon.svg">
|
||||
@ -129,7 +128,7 @@
|
||||
<span data-icon="activity" class="empty-state__icon"></span>
|
||||
<h3 class="empty-state__title">Нет данных</h3>
|
||||
<p class="empty-state__description">
|
||||
Ожидаем данные свипа от системы ВНА...
|
||||
Ожидаем данные свипа от системы...
|
||||
</p>
|
||||
<div class="loading-spinner">
|
||||
<div class="spinner"></div>
|
||||
@ -223,7 +222,7 @@
|
||||
Графики текущей калибровки
|
||||
</button>
|
||||
<input type="text" class="calibration-name-input settings-input" id="calibrationNameInput" placeholder="Введите имя калибровки" disabled>
|
||||
<button class="btn btn--success" id="saveCalibrationBtn" disabled>
|
||||
<button class="btn btn--primary" id="saveCalibrationBtn" disabled>
|
||||
<span data-icon="save"></span>
|
||||
Сохранить калибровку
|
||||
</button>
|
||||
@ -242,6 +241,10 @@
|
||||
<span data-icon="check"></span>
|
||||
Сделать активной
|
||||
</button>
|
||||
<button class="btn btn--secondary" id="clearCalibrationBtn" disabled>
|
||||
<span data-icon="square"></span>
|
||||
Сбросить
|
||||
</button>
|
||||
<button class="btn btn--secondary" id="viewPlotsBtn" disabled>
|
||||
<span data-icon="bar-chart-3"></span>
|
||||
Показать графики
|
||||
@ -286,6 +289,10 @@
|
||||
<span data-icon="check"></span>
|
||||
Использовать
|
||||
</button>
|
||||
<button class="btn btn--secondary" id="clearReferenceBtn" disabled>
|
||||
<span data-icon="square"></span>
|
||||
Сбросить
|
||||
</button>
|
||||
<button class="btn btn--secondary" id="previewReferenceBtn" disabled>
|
||||
<span data-icon="bar-chart-3"></span>
|
||||
Просмотр
|
||||
|
||||
Reference in New Issue
Block a user