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))
|
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}")
|
@router.delete("/reference/{reference_name}")
|
||||||
async def delete_reference(reference_name: str, preset_filename: str | None = None) -> dict[str, Any]:
|
async def delete_reference(reference_name: str, preset_filename: str | None = None) -> dict[str, Any]:
|
||||||
"""Delete a reference."""
|
"""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))
|
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}")
|
@router.delete("/calibration/{calibration_name}")
|
||||||
async def delete_calibration(calibration_name: str, preset_filename: str | None = None) -> dict[str, Any]:
|
async def delete_calibration(calibration_name: str, preset_filename: str | None = None) -> dict[str, Any]:
|
||||||
"""Delete a calibration set."""
|
"""Delete a calibration set."""
|
||||||
|
|||||||
@ -23,6 +23,7 @@ class SettingsStatusModel(BaseModel):
|
|||||||
working_calibration: Dict[str, Any] | None
|
working_calibration: Dict[str, Any] | None
|
||||||
available_presets: int
|
available_presets: int
|
||||||
available_calibrations: int
|
available_calibrations: int
|
||||||
|
available_references: int
|
||||||
|
|
||||||
|
|
||||||
class SetPresetRequest(BaseModel):
|
class SetPresetRequest(BaseModel):
|
||||||
@ -77,4 +78,4 @@ class CreateReferenceRequest(BaseModel):
|
|||||||
|
|
||||||
class SetReferenceRequest(BaseModel):
|
class SetReferenceRequest(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
preset_filename: str | None = None
|
preset_filename: str | None = None
|
||||||
|
|||||||
@ -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,
|
"points": 1000,
|
||||||
"bandwidth": 1000.0
|
"bandwidth": 1000.0
|
||||||
},
|
},
|
||||||
"calibration_name": "tuncTuncTuncSahur",
|
"calibration_name": "вфыввф",
|
||||||
"standards": [
|
"standards": [
|
||||||
"open",
|
"open",
|
||||||
"short",
|
"short",
|
||||||
"load"
|
"load"
|
||||||
],
|
],
|
||||||
"created_timestamp": "2025-09-24T17:55:12.053850",
|
"created_timestamp": "2025-09-30T18:55:17.618157",
|
||||||
"is_complete": true
|
"is_complete": true
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -7,10 +7,10 @@
|
|||||||
"points": 1000,
|
"points": 1000,
|
||||||
"bandwidth": 1000.0
|
"bandwidth": 1000.0
|
||||||
},
|
},
|
||||||
"calibration_name": "tuncTuncTuncSahur",
|
"calibration_name": "вфыввф",
|
||||||
"standard": "load",
|
"standard": "load",
|
||||||
"sweep_number": 1221,
|
"sweep_number": 33,
|
||||||
"sweep_timestamp": 1758725709.366605,
|
"sweep_timestamp": 1759247715.1613321,
|
||||||
"created_timestamp": "2025-09-24T17:55:12.053789",
|
"created_timestamp": "2025-09-30T18:55:17.618031",
|
||||||
"total_points": 1000
|
"total_points": 1000
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -7,10 +7,10 @@
|
|||||||
"points": 1000,
|
"points": 1000,
|
||||||
"bandwidth": 1000.0
|
"bandwidth": 1000.0
|
||||||
},
|
},
|
||||||
"calibration_name": "tuncTuncTuncSahur",
|
"calibration_name": "вфыввф",
|
||||||
"standard": "open",
|
"standard": "open",
|
||||||
"sweep_number": 1217,
|
"sweep_number": 18,
|
||||||
"sweep_timestamp": 1758725700.971026,
|
"sweep_timestamp": 1759247683.8793015,
|
||||||
"created_timestamp": "2025-09-24T17:55:12.049478",
|
"created_timestamp": "2025-09-30T18:55:17.612864",
|
||||||
"total_points": 1000
|
"total_points": 1000
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -7,10 +7,10 @@
|
|||||||
"points": 1000,
|
"points": 1000,
|
||||||
"bandwidth": 1000.0
|
"bandwidth": 1000.0
|
||||||
},
|
},
|
||||||
"calibration_name": "tuncTuncTuncSahur",
|
"calibration_name": "вфыввф",
|
||||||
"standard": "short",
|
"standard": "short",
|
||||||
"sweep_number": 1219,
|
"sweep_number": 19,
|
||||||
"sweep_timestamp": 1758725705.1671622,
|
"sweep_timestamp": 1759247685.9623504,
|
||||||
"created_timestamp": "2025-09-24T17:55:12.051603",
|
"created_timestamp": "2025-09-30T18:55:17.615536",
|
||||||
"total_points": 1000
|
"total_points": 1000
|
||||||
}
|
}
|
||||||
@ -4,7 +4,7 @@
|
|||||||
"data_limitation": "ph_only_1",
|
"data_limitation": "ph_only_1",
|
||||||
"cut": 2.0,
|
"cut": 2.0,
|
||||||
"max": 5.0,
|
"max": 5.0,
|
||||||
"gain": 2.2,
|
"gain": 0.0,
|
||||||
"start_freq": 100.0,
|
"start_freq": 100.0,
|
||||||
"stop_freq": 8800.0,
|
"stop_freq": 8800.0,
|
||||||
"clear_history": false,
|
"clear_history": false,
|
||||||
|
|||||||
@ -183,9 +183,9 @@ class BScanProcessor(BaseProcessor):
|
|||||||
latest_history = self._sweep_history[-1]
|
latest_history = self._sweep_history[-1]
|
||||||
reference_data = latest_history.get("reference_data")
|
reference_data = latest_history.get("reference_data")
|
||||||
|
|
||||||
if reference_data is None:
|
if reference_data is None:
|
||||||
logger.warning(f"Open air substraction cannot be done: reference_data is None")
|
logger.warning(f"Open air substraction cannot be done: reference_data is None")
|
||||||
self._config["open_air"] = False
|
self._config["open_air"] = False
|
||||||
|
|
||||||
if self._config["open_air"] and reference_data is not None:
|
if self._config["open_air"] and reference_data is not None:
|
||||||
reference_complex = self._get_complex_s11(reference_data)
|
reference_complex = self._get_complex_s11(reference_data)
|
||||||
|
|||||||
@ -188,6 +188,13 @@ class VNASettingsManager:
|
|||||||
raise ValueError("No current preset available")
|
raise ValueError("No current preset available")
|
||||||
return self.reference_manager.delete_reference(reference_name, current_preset)
|
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:
|
def delete_calibration(self, preset: ConfigPreset, calibration_name: str) -> None:
|
||||||
"""Delete a saved calibration for the given preset."""
|
"""Delete a saved calibration for the given preset."""
|
||||||
self.calibration_manager.delete_calibration(preset, calibration_name)
|
self.calibration_manager.delete_calibration(preset, calibration_name)
|
||||||
@ -309,7 +316,8 @@ class VNASettingsManager:
|
|||||||
"current_calibration": None,
|
"current_calibration": None,
|
||||||
"working_calibration": None,
|
"working_calibration": None,
|
||||||
"available_presets": len(self.get_available_presets()),
|
"available_presets": len(self.get_available_presets()),
|
||||||
"available_calibrations": 0
|
"available_calibrations": 0,
|
||||||
|
"available_references": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
if current_preset:
|
if current_preset:
|
||||||
@ -322,6 +330,7 @@ class VNASettingsManager:
|
|||||||
"bandwidth": current_preset.bandwidth
|
"bandwidth": current_preset.bandwidth
|
||||||
}
|
}
|
||||||
summary["available_calibrations"] = len(self.get_available_calibrations(current_preset))
|
summary["available_calibrations"] = len(self.get_available_calibrations(current_preset))
|
||||||
|
summary["available_references"] = len(self.get_available_references(current_preset))
|
||||||
|
|
||||||
if current_calibration:
|
if current_calibration:
|
||||||
summary["current_calibration"] = {
|
summary["current_calibration"] = {
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
s21_start100_stop8800_points1000_bw1khz/ффывфы
|
s11_start100_stop8800_points1000_bw1khz/asd
|
||||||
@ -431,7 +431,7 @@
|
|||||||
/* Notifications */
|
/* Notifications */
|
||||||
.notifications {
|
.notifications {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: var(--space-4);
|
top: calc(var(--header-height) + var(--space-2));
|
||||||
right: var(--space-4);
|
right: var(--space-4);
|
||||||
z-index: var(--z-toast);
|
z-index: var(--z-toast);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -518,6 +518,11 @@
|
|||||||
margin-top: var(--space-3);
|
margin-top: var(--space-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#currentReferenceInfo {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
/* Reference details */
|
/* Reference details */
|
||||||
.reference-details {
|
.reference-details {
|
||||||
margin-top: var(--space-3);
|
margin-top: var(--space-3);
|
||||||
|
|||||||
@ -85,7 +85,7 @@ class VNADashboard {
|
|||||||
this.notifications.show({
|
this.notifications.show({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
title: 'Панель готова',
|
title: 'Панель готова',
|
||||||
message: 'Соединение с системой ВНА установлено. Ожидание данных свипа...'
|
message: 'Соединение с системой установлено. Ожидание данных свипа...'
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -75,6 +75,7 @@ export const API = {
|
|||||||
CALIBRATION_START: `${API_BASE}/settings/calibration/start`,
|
CALIBRATION_START: `${API_BASE}/settings/calibration/start`,
|
||||||
CALIBRATION_SAVE: `${API_BASE}/settings/calibration/save`,
|
CALIBRATION_SAVE: `${API_BASE}/settings/calibration/save`,
|
||||||
CALIBRATION_SET: `${API_BASE}/settings/calibration/set`,
|
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_DELETE: (name) => `${API_BASE}/settings/calibration/${encodeURIComponent(name)}`,
|
||||||
CALIBRATION_ADD_STANDARD: `${API_BASE}/settings/calibration/add-standard`,
|
CALIBRATION_ADD_STANDARD: `${API_BASE}/settings/calibration/add-standard`,
|
||||||
CALIBRATION_STANDARDS_PLOTS: (name) => `${API_BASE}/settings/calibration/${encodeURIComponent(name)}/standards-plots`,
|
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_CREATE: `${API_BASE}/settings/reference/create`,
|
||||||
REFERENCE_SET: `${API_BASE}/settings/reference/set`,
|
REFERENCE_SET: `${API_BASE}/settings/reference/set`,
|
||||||
REFERENCE_CURRENT: `${API_BASE}/settings/reference/current`,
|
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': {
|
'eye-off': {
|
||||||
viewBox: '0 0 24 24',
|
viewBox: '0 0 24 24',
|
||||||
elements: [
|
elements: [
|
||||||
{ type: 'path', attrs: { d: 'M3 3l18 18' } },
|
{ type: 'path', attrs: { d: 'M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12z' } },
|
||||||
{ type: 'path', attrs: { d: 'M9.5 9.5A4 4 0 0 0 12 16a4 4 0 0 0 3.5-6.5' } },
|
{ type: 'circle', attrs: { cx: 12, cy: 12, r: 3 } },
|
||||||
{ 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: 'line', attrs: { x1: 4, y1: 4, x2: 20, y2: 20 } }
|
||||||
{ type: 'path', attrs: { d: 'M17.2 10.2C18.7 9.2 21 12 21 12s-2.7 5-9 5' } }
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'alert-circle': {
|
'alert-circle': {
|
||||||
|
|||||||
@ -77,6 +77,7 @@ export class SettingsManager {
|
|||||||
setCalibrationBtn: document.getElementById('setCalibrationBtn'),
|
setCalibrationBtn: document.getElementById('setCalibrationBtn'),
|
||||||
viewPlotsBtn: document.getElementById('viewPlotsBtn'),
|
viewPlotsBtn: document.getElementById('viewPlotsBtn'),
|
||||||
deleteCalibrationBtn: document.getElementById('deleteCalibrationBtn'),
|
deleteCalibrationBtn: document.getElementById('deleteCalibrationBtn'),
|
||||||
|
clearCalibrationBtn: document.getElementById('clearCalibrationBtn'),
|
||||||
viewCurrentPlotsBtn: document.getElementById('viewCurrentPlotsBtn'),
|
viewCurrentPlotsBtn: document.getElementById('viewCurrentPlotsBtn'),
|
||||||
|
|
||||||
// Modal
|
// Modal
|
||||||
@ -139,6 +140,10 @@ export class SettingsManager {
|
|||||||
this.referenceManager.onReferenceUpdated = (reference) => {
|
this.referenceManager.onReferenceUpdated = (reference) => {
|
||||||
this.updateReferenceSummary(reference);
|
this.updateReferenceSummary(reference);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.referenceManager.onShowPlots = (plotData) => {
|
||||||
|
this.showPlotsModal(plotData);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setupEventHandlers() {
|
setupEventHandlers() {
|
||||||
@ -163,6 +168,9 @@ export class SettingsManager {
|
|||||||
if (this.elements.systemStatus) {
|
if (this.elements.systemStatus) {
|
||||||
this.elements.systemStatus.textContent = 'Не подключено';
|
this.elements.systemStatus.textContent = 'Не подключено';
|
||||||
}
|
}
|
||||||
|
if (this.elements.referenceCount) {
|
||||||
|
this.elements.referenceCount.textContent = '-';
|
||||||
|
}
|
||||||
this.notify(ERROR, 'Ошибка статуса', 'Не удалось получить текущее состояние системы');
|
this.notify(ERROR, 'Ошибка статуса', 'Не удалось получить текущее состояние системы');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -198,8 +206,9 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const effectiveStatus = {
|
const effectiveStatus = {
|
||||||
...(status ?? { available_presets: 0, available_calibrations: 0 }),
|
...(status ?? { available_presets: 0, available_calibrations: 0, available_references: 0 }),
|
||||||
current_preset: fallbackPreset,
|
current_preset: fallbackPreset,
|
||||||
|
available_references: this.referenceManager?.availableReferences?.length ?? 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!status) {
|
if (!status) {
|
||||||
@ -210,7 +219,7 @@ export class SettingsManager {
|
|||||||
this.elements.calibrationCount.textContent = effectiveStatus.available_calibrations ?? '-';
|
this.elements.calibrationCount.textContent = effectiveStatus.available_calibrations ?? '-';
|
||||||
}
|
}
|
||||||
if (this.elements.referenceCount) {
|
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 ?? '-';
|
this.elements.calibrationCount.textContent = status?.available_calibrations ?? '-';
|
||||||
}
|
}
|
||||||
if (this.elements.referenceCount) {
|
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.elements.referenceCount.textContent = typeof count === 'number' ? count : '-';
|
||||||
}
|
}
|
||||||
this.updateHeaderSummary(status);
|
this.updateHeaderSummary(status);
|
||||||
@ -294,14 +305,25 @@ export class SettingsManager {
|
|||||||
|
|
||||||
this.currentPlotsData = plotsData;
|
this.currentPlotsData = plotsData;
|
||||||
|
|
||||||
this.renderCalibrationPlots(plotsData.individual_plots, plotsData.preset);
|
if (plotsData.reference_name) {
|
||||||
|
this.renderReferencePlot(plotsData);
|
||||||
|
} else {
|
||||||
|
this.renderCalibrationPlots(plotsData.individual_plots, plotsData.preset);
|
||||||
|
}
|
||||||
|
|
||||||
const title = modal.querySelector('.modal__title');
|
const title = modal.querySelector('.modal__title');
|
||||||
if (title) {
|
if (title) {
|
||||||
title.innerHTML = `
|
if (plotsData.reference_name) {
|
||||||
<span data-icon="bar-chart-3"></span>
|
title.innerHTML = `
|
||||||
${plotsData.calibration_name} - ${plotsData.preset.mode.toUpperCase()} Standards
|
<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);
|
renderIcons(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,6 +333,60 @@ export class SettingsManager {
|
|||||||
document.body.style.overflow = 'hidden';
|
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) {
|
renderCalibrationPlots(individualPlots, preset) {
|
||||||
const container = this.elements.plotsGrid;
|
const container = this.elements.plotsGrid;
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
@ -473,6 +549,33 @@ export class SettingsManager {
|
|||||||
this.currentPlotsData = null;
|
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) {
|
async downloadCalibrationStandard(standardName, plotContainer) {
|
||||||
try {
|
try {
|
||||||
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
||||||
|
|||||||
@ -26,6 +26,7 @@ export class CalibrationManager {
|
|||||||
this.handleSetCalibration = this.handleSetCalibration.bind(this);
|
this.handleSetCalibration = this.handleSetCalibration.bind(this);
|
||||||
this.handleCalibrationChange = this.handleCalibrationChange.bind(this);
|
this.handleCalibrationChange = this.handleCalibrationChange.bind(this);
|
||||||
this.handleDeleteCalibration = this.handleDeleteCalibration.bind(this);
|
this.handleDeleteCalibration = this.handleDeleteCalibration.bind(this);
|
||||||
|
this.handleClearCalibration = this.handleClearCalibration.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
init(elements) {
|
init(elements) {
|
||||||
@ -35,6 +36,7 @@ export class CalibrationManager {
|
|||||||
this.elements.calibrationDropdown?.addEventListener('change', this.handleCalibrationChange);
|
this.elements.calibrationDropdown?.addEventListener('change', this.handleCalibrationChange);
|
||||||
this.elements.setCalibrationBtn?.addEventListener('click', this.handleSetCalibration);
|
this.elements.setCalibrationBtn?.addEventListener('click', this.handleSetCalibration);
|
||||||
this.elements.deleteCalibrationBtn?.addEventListener('click', this.handleDeleteCalibration);
|
this.elements.deleteCalibrationBtn?.addEventListener('click', this.handleDeleteCalibration);
|
||||||
|
this.elements.clearCalibrationBtn?.addEventListener('click', this.handleClearCalibration);
|
||||||
|
|
||||||
this.elements.calibrationNameInput?.addEventListener('input', () => {
|
this.elements.calibrationNameInput?.addEventListener('input', () => {
|
||||||
const hasName = this.elements.calibrationNameInput.value.trim().length > 0;
|
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.calibrationDropdown?.removeEventListener('change', this.handleCalibrationChange);
|
||||||
this.elements.setCalibrationBtn?.removeEventListener('click', this.handleSetCalibration);
|
this.elements.setCalibrationBtn?.removeEventListener('click', this.handleSetCalibration);
|
||||||
this.elements.deleteCalibrationBtn?.removeEventListener('click', this.handleDeleteCalibration);
|
this.elements.deleteCalibrationBtn?.removeEventListener('click', this.handleDeleteCalibration);
|
||||||
|
this.elements.clearCalibrationBtn?.removeEventListener('click', this.handleClearCalibration);
|
||||||
this.resetCaptureState();
|
this.resetCaptureState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,6 +100,9 @@ export class CalibrationManager {
|
|||||||
if (this.elements.deleteCalibrationBtn) {
|
if (this.elements.deleteCalibrationBtn) {
|
||||||
this.elements.deleteCalibrationBtn.disabled = true;
|
this.elements.deleteCalibrationBtn.disabled = true;
|
||||||
}
|
}
|
||||||
|
if (this.elements.clearCalibrationBtn) {
|
||||||
|
this.elements.clearCalibrationBtn.disabled = true;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,6 +120,9 @@ export class CalibrationManager {
|
|||||||
if (this.elements.deleteCalibrationBtn) {
|
if (this.elements.deleteCalibrationBtn) {
|
||||||
this.elements.deleteCalibrationBtn.disabled = true;
|
this.elements.deleteCalibrationBtn.disabled = true;
|
||||||
}
|
}
|
||||||
|
if (this.elements.clearCalibrationBtn) {
|
||||||
|
this.elements.clearCalibrationBtn.disabled = !this.currentCalibration;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateWorking(working) {
|
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) {
|
updateStatus(status) {
|
||||||
if (status.current_calibration) {
|
if (status.current_calibration) {
|
||||||
this.currentCalibration = status.current_calibration;
|
this.currentCalibration = status.current_calibration;
|
||||||
@ -393,6 +449,10 @@ export class CalibrationManager {
|
|||||||
this.currentCalibration = null;
|
this.currentCalibration = null;
|
||||||
this.elements.currentCalibration.textContent = 'Нет';
|
this.elements.currentCalibration.textContent = 'Нет';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.elements.clearCalibrationBtn) {
|
||||||
|
this.elements.clearCalibrationBtn.disabled = !this.currentCalibration;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resetCaptureState(standard = null) {
|
resetCaptureState(standard = null) {
|
||||||
@ -413,6 +473,7 @@ export class CalibrationManager {
|
|||||||
}
|
}
|
||||||
if (this.elements.saveCalibrationBtn) this.elements.saveCalibrationBtn.disabled = true;
|
if (this.elements.saveCalibrationBtn) this.elements.saveCalibrationBtn.disabled = true;
|
||||||
if (this.elements.progressText) this.elements.progressText.textContent = '0/0';
|
if (this.elements.progressText) this.elements.progressText.textContent = '0/0';
|
||||||
|
if (this.elements.clearCalibrationBtn) this.elements.clearCalibrationBtn.disabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
getWorkingCalibration() {
|
getWorkingCalibration() {
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { Debouncer, RequestGuard, ButtonState } from '../utils.js';
|
|||||||
import { apiGet, apiPost, apiDelete, buildUrl } from '../api-client.js';
|
import { apiGet, apiPost, apiDelete, buildUrl } from '../api-client.js';
|
||||||
import { API, TIMING, NOTIFICATION_TYPES } from '../constants.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 {
|
export class ReferenceManager {
|
||||||
constructor(notifications) {
|
constructor(notifications) {
|
||||||
@ -15,6 +15,7 @@ export class ReferenceManager {
|
|||||||
this.availableReferences = [];
|
this.availableReferences = [];
|
||||||
this.currentReference = null;
|
this.currentReference = null;
|
||||||
this.currentPreset = null;
|
this.currentPreset = null;
|
||||||
|
this.lastPresetFilename = null;
|
||||||
this.elements = {};
|
this.elements = {};
|
||||||
this.debouncer = new Debouncer();
|
this.debouncer = new Debouncer();
|
||||||
this.reqGuard = new RequestGuard();
|
this.reqGuard = new RequestGuard();
|
||||||
@ -54,12 +55,23 @@ export class ReferenceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setCurrentPreset(preset) {
|
async setCurrentPreset(preset) {
|
||||||
|
const previousPreset = this.currentPreset?.filename ?? null;
|
||||||
|
const newPreset = preset?.filename ?? null;
|
||||||
|
|
||||||
this.currentPreset = preset;
|
this.currentPreset = preset;
|
||||||
if (preset) {
|
this.lastPresetFilename = newPreset;
|
||||||
await this.loadReferences();
|
|
||||||
} else {
|
if (!preset) {
|
||||||
|
await this.clearReferenceCache();
|
||||||
this.reset();
|
this.reset();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (previousPreset !== newPreset) {
|
||||||
|
await this.clearServerCurrentReference();
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.loadReferences();
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadReferences() {
|
async loadReferences() {
|
||||||
@ -67,6 +79,7 @@ export class ReferenceManager {
|
|||||||
if (!this.currentPreset) {
|
if (!this.currentPreset) {
|
||||||
this.renderDropdown([]);
|
this.renderDropdown([]);
|
||||||
this.updateInfo(null);
|
this.updateInfo(null);
|
||||||
|
this.updateReferenceCount(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,34 +254,21 @@ export class ReferenceManager {
|
|||||||
this.debouncer.debounce('preview-reference', () =>
|
this.debouncer.debounce('preview-reference', () =>
|
||||||
this.reqGuard.runExclusive('preview-reference', async () => {
|
this.reqGuard.runExclusive('preview-reference', async () => {
|
||||||
try {
|
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
|
preset_filename: this.currentPreset?.filename
|
||||||
});
|
});
|
||||||
const details = await apiGet(url);
|
const plotData = await apiGet(url);
|
||||||
|
|
||||||
const timestamp = details?.timestamp
|
if (this.onShowPlots) {
|
||||||
? new Date(details.timestamp).toLocaleString()
|
this.onShowPlots(plotData);
|
||||||
: '—';
|
|
||||||
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) {
|
} catch (error) {
|
||||||
console.error('Preview reference failed:', error);
|
console.error('Preview reference failed:', error);
|
||||||
this.notify(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);
|
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) {
|
notify(type, title, message) {
|
||||||
this.notifications?.show?.({ type, title, message });
|
this.notifications?.show?.({ type, title, message });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,10 +3,9 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta name="description" content="Панель управления системой ВНА для сбора и обработки данных в реальном времени">
|
<meta name="description" content="Панель управления системой для сбора и обработки данных в реальном времени">
|
||||||
<meta name="keywords" content="ВНА, Vector Network Analyzer, РФ, микроволны, сбор данных, реальное время">
|
|
||||||
<meta name="author" content="VNA System">
|
<meta name="author" content="VNA System">
|
||||||
<title>Система ВНА — Панель управления</title>
|
<title>Радар</title>
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
<link rel="icon" type="image/svg+xml" href="/static/assets/favicon.svg">
|
<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>
|
<span data-icon="activity" class="empty-state__icon"></span>
|
||||||
<h3 class="empty-state__title">Нет данных</h3>
|
<h3 class="empty-state__title">Нет данных</h3>
|
||||||
<p class="empty-state__description">
|
<p class="empty-state__description">
|
||||||
Ожидаем данные свипа от системы ВНА...
|
Ожидаем данные свипа от системы...
|
||||||
</p>
|
</p>
|
||||||
<div class="loading-spinner">
|
<div class="loading-spinner">
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
@ -223,7 +222,7 @@
|
|||||||
Графики текущей калибровки
|
Графики текущей калибровки
|
||||||
</button>
|
</button>
|
||||||
<input type="text" class="calibration-name-input settings-input" id="calibrationNameInput" placeholder="Введите имя калибровки" disabled>
|
<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>
|
<span data-icon="save"></span>
|
||||||
Сохранить калибровку
|
Сохранить калибровку
|
||||||
</button>
|
</button>
|
||||||
@ -242,6 +241,10 @@
|
|||||||
<span data-icon="check"></span>
|
<span data-icon="check"></span>
|
||||||
Сделать активной
|
Сделать активной
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn--secondary" id="clearCalibrationBtn" disabled>
|
||||||
|
<span data-icon="square"></span>
|
||||||
|
Сбросить
|
||||||
|
</button>
|
||||||
<button class="btn btn--secondary" id="viewPlotsBtn" disabled>
|
<button class="btn btn--secondary" id="viewPlotsBtn" disabled>
|
||||||
<span data-icon="bar-chart-3"></span>
|
<span data-icon="bar-chart-3"></span>
|
||||||
Показать графики
|
Показать графики
|
||||||
@ -286,6 +289,10 @@
|
|||||||
<span data-icon="check"></span>
|
<span data-icon="check"></span>
|
||||||
Использовать
|
Использовать
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn--secondary" id="clearReferenceBtn" disabled>
|
||||||
|
<span data-icon="square"></span>
|
||||||
|
Сбросить
|
||||||
|
</button>
|
||||||
<button class="btn btn--secondary" id="previewReferenceBtn" disabled>
|
<button class="btn btn--secondary" id="previewReferenceBtn" disabled>
|
||||||
<span data-icon="bar-chart-3"></span>
|
<span data-icon="bar-chart-3"></span>
|
||||||
Просмотр
|
Просмотр
|
||||||
|
|||||||
Reference in New Issue
Block a user