some russian translations and S button functios as single sweep now

This commit is contained in:
ayzen
2025-10-16 20:37:26 +03:00
parent 52b0af79e4
commit db5aa8d73d
9 changed files with 110 additions and 71 deletions

View File

@ -26,7 +26,7 @@ async def get_acquisition_status() -> dict[str, Any]:
acquisition = singletons.vna_data_acquisition_instance
if acquisition is None:
logger.error("Acquisition singleton is not initialized")
raise HTTPException(status_code=500, detail="Acquisition not initialized")
raise HTTPException(status_code=500, detail="Сбор данных не инициализирован")
return {
"running": acquisition.is_running,
@ -45,14 +45,14 @@ async def start_acquisition() -> dict[str, Any]:
acquisition = singletons.vna_data_acquisition_instance
if acquisition is None:
logger.error("Acquisition singleton is not initialized")
raise HTTPException(status_code=500, detail="Acquisition not initialized")
raise HTTPException(status_code=500, detail="Сбор данных не инициализирован")
if not acquisition.is_running:
acquisition.start()
logger.info("Acquisition thread started via API")
acquisition.set_continuous_mode(True)
return {"success": True, "message": "Acquisition started"}
return {"success": True, "message": "Сбор данных запущен"}
except HTTPException:
raise
except Exception as exc: # noqa: BLE001
@ -69,14 +69,14 @@ async def stop_acquisition() -> dict[str, Any]:
acquisition = singletons.vna_data_acquisition_instance
if acquisition is None:
logger.error("Acquisition singleton is not initialized")
raise HTTPException(status_code=500, detail="Acquisition not initialized")
raise HTTPException(status_code=500, detail="Сбор данных не инициализирован")
if not acquisition.is_running:
return {"success": True, "message": "Acquisition already stopped"}
return {"success": True, "message": "Сбор данных уже остановлен"}
acquisition.pause()
logger.info("Acquisition paused via API")
return {"success": True, "message": "Acquisition paused"}
return {"success": True, "message": "Сбор данных приостановлен"}
except HTTPException:
raise
except Exception as exc: # noqa: BLE001
@ -94,14 +94,14 @@ async def trigger_single_sweep() -> dict[str, Any]:
acquisition = singletons.vna_data_acquisition_instance
if acquisition is None:
logger.error("Acquisition singleton is not initialized")
raise HTTPException(status_code=500, detail="Acquisition not initialized")
raise HTTPException(status_code=500, detail="Сбор данных не инициализирован")
if not acquisition.is_running:
acquisition.start()
logger.info("Acquisition thread started (single-sweep request)")
acquisition.trigger_single_sweep()
return {"success": True, "message": "Single sweep triggered"}
return {"success": True, "message": "Одиночный свип запущен"}
except HTTPException:
raise
except Exception as exc: # noqa: BLE001
@ -111,7 +111,7 @@ async def trigger_single_sweep() -> dict[str, Any]:
@router.get("/acquisition/latest-sweep")
async def get_latest_sweep(
limit: int = Query(10, ge=1, le=1000, description="Max number of points to include in response"),
limit: int = Query(10, ge=1, le=1000, description="Максимальное число точек в ответе"),
) -> dict[str, Any]:
"""
Return the latest sweep metadata and a limited subset of points.
@ -125,11 +125,11 @@ async def get_latest_sweep(
acquisition = singletons.vna_data_acquisition_instance
if acquisition is None:
logger.error("Acquisition singleton is not initialized")
raise HTTPException(status_code=500, detail="Acquisition not initialized")
raise HTTPException(status_code=500, detail="Сбор данных не инициализирован")
latest_sweep = acquisition.sweep_buffer.get_latest_sweep()
if not latest_sweep:
return {"sweep": None, "message": "No sweep data available"}
return {"sweep": None, "message": "Данные свипа недоступны"}
points = latest_sweep.points[:limit]
return {
@ -139,7 +139,7 @@ async def get_latest_sweep(
"total_points": latest_sweep.total_points,
"points": points,
},
"message": f"Latest sweep #{latest_sweep.sweep_number} with {latest_sweep.total_points} points",
"message": f"Последний свип №{latest_sweep.sweep_number}, точек: {latest_sweep.total_points}",
}
except HTTPException:
raise

View File

@ -14,4 +14,4 @@ router = APIRouter(prefix="/api/v1", tags=["health"])
@router.get("/")
async def root():
"""Root endpoint."""
return {"message": "VNA System API", "version": "1.0.0"}
return {"message": "API системы VNA", "version": "1.0.0"}

View File

@ -67,14 +67,14 @@ async def set_preset(request: SetPresetRequest) -> dict[str, Any]:
presets = singletons.settings_manager.get_available_presets()
preset = next((p for p in presets if p.filename == request.filename), None)
if preset is None:
raise HTTPException(status_code=404, detail=f"Preset not found: {request.filename}")
raise HTTPException(status_code=404, detail=f"Пресет не найден: {request.filename}")
# Changing preset invalidates active calibration selection.
singletons.settings_manager.calibration_manager.clear_current_calibration()
singletons.settings_manager.set_current_preset(preset)
logger.info("Preset selected via API", filename=preset.filename, mode=preset.mode.value)
return {"success": True, "message": f"Preset set to {request.filename}"}
return {"success": True, "message": f"Пресет установлен: {request.filename}"}
except HTTPException:
raise
except Exception as exc: # noqa: BLE001
@ -112,7 +112,7 @@ async def get_calibrations(preset_filename: str | None = None) -> list[Calibrati
presets = singletons.settings_manager.get_available_presets()
preset = next((p for p in presets if p.filename == preset_filename), None)
if preset is None:
raise HTTPException(status_code=404, detail=f"Preset not found: {preset_filename}")
raise HTTPException(status_code=404, detail=f"Пресет не найден: {preset_filename}")
calibrations = singletons.settings_manager.get_available_calibrations(preset)
details: list[CalibrationModel] = []
@ -153,14 +153,14 @@ async def start_calibration(request: StartCalibrationRequest) -> dict[str, Any]:
presets = singletons.settings_manager.get_available_presets()
preset = next((p for p in presets if p.filename == request.preset_filename), None)
if preset is None:
raise HTTPException(status_code=404, detail=f"Preset not found: {request.preset_filename}")
raise HTTPException(status_code=404, detail=f"Пресет не найден: {request.preset_filename}")
calib = singletons.settings_manager.start_new_calibration(preset)
required = singletons.settings_manager.get_required_standards(calib.preset.mode)
return {
"success": True,
"message": "Calibration started",
"message": "Калибровка запущена",
"preset": calib.preset.filename,
"required_standards": [s.value for s in required],
}
@ -178,7 +178,7 @@ async def add_calibration_standard(request: CalibrateStandardRequest) -> dict[st
try:
standard = CalibrationStandard(request.standard)
except ValueError:
raise HTTPException(status_code=400, detail=f"Invalid calibration standard: {request.standard}")
raise HTTPException(status_code=400, detail=f"Недопустимый стандарт калибровки: {request.standard}")
sweep_no = singletons.settings_manager.capture_calibration_standard_from_acquisition(
standard, singletons.vna_data_acquisition_instance
@ -189,7 +189,7 @@ async def add_calibration_standard(request: CalibrateStandardRequest) -> dict[st
return {
"success": True,
"message": f"Added {standard.value} standard from sweep {sweep_no}",
"message": f"Добавлен стандарт {standard.value} из свипа {sweep_no}",
"sweep_number": sweep_no,
"progress": f"{progress[0]}/{progress[1]}",
"is_complete": working.is_complete() if working else False,
@ -208,7 +208,7 @@ async def save_calibration(request: SaveCalibrationRequest) -> dict[str, Any]:
saved = singletons.settings_manager.save_calibration_set(request.name)
return {
"success": True,
"message": f"Calibration '{request.name}' saved successfully",
"message": f"Калибровка \"{request.name}\" успешно сохранена",
"preset": saved.preset.filename,
"standards": [s.value for s in saved.standards.keys()],
}
@ -226,10 +226,10 @@ async def set_calibration(request: SetCalibrationRequest) -> dict[str, Any]:
presets = singletons.settings_manager.get_available_presets()
preset = next((p for p in presets if p.filename == request.preset_filename), None)
if preset is None:
raise HTTPException(status_code=404, detail=f"Preset not found: {request.preset_filename}")
raise HTTPException(status_code=404, detail=f"Пресет не найден: {request.preset_filename}")
singletons.settings_manager.set_current_calibration(request.name, preset)
return {"success": True, "message": f"Calibration set to '{request.name}'"}
return {"success": True, "message": f"Калибровка установлена: \"{request.name}\""}
except HTTPException:
raise
except Exception as exc: # noqa: BLE001
@ -266,7 +266,7 @@ async def remove_calibration_standard(request: RemoveStandardRequest) -> dict[st
try:
standard = CalibrationStandard(request.standard)
except ValueError:
raise HTTPException(status_code=400, detail=f"Invalid calibration standard: {request.standard}")
raise HTTPException(status_code=400, detail=f"Недопустимый стандарт калибровки: {request.standard}")
singletons.settings_manager.remove_calibration_standard(standard)
@ -275,7 +275,7 @@ async def remove_calibration_standard(request: RemoveStandardRequest) -> dict[st
return {
"success": True,
"message": f"Removed {standard.value} standard",
"message": f"Стандарт {standard.value} удалён",
"progress": f"{progress[0]}/{progress[1]}",
"is_complete": working.is_complete() if working else False,
}
@ -319,18 +319,18 @@ async def get_calibration_standards_plots(
presets = singletons.settings_manager.get_available_presets()
preset = next((p for p in presets if p.filename == preset_filename), None)
if preset is None:
raise HTTPException(status_code=404, detail=f"Preset not found: {preset_filename}")
raise HTTPException(status_code=404, detail=f"Пресет не найден: {preset_filename}")
else:
preset = singletons.settings_manager.get_current_preset()
if preset is None:
raise HTTPException(status_code=400, detail="No current preset selected")
raise HTTPException(status_code=400, detail="Текущий пресет не выбран")
# Resolve calibration directory (uses manager's internal layout)
calibration_manager = singletons.settings_manager.calibration_manager
calibration_dir = calibration_manager._get_preset_calibration_dir(preset) / calibration_name # noqa: SLF001
if not calibration_dir.exists():
raise HTTPException(status_code=404, detail=f"Calibration not found: {calibration_name}")
raise HTTPException(status_code=404, detail=f"Калибровка не найдена: {calibration_name}")
individual_plots = generate_standards_magnitude_plots(calibration_dir, preset)
@ -352,9 +352,9 @@ async def get_working_calibration_standards_plots() -> dict[str, Any]:
try:
working = singletons.settings_manager.get_current_working_calibration()
if not working:
raise HTTPException(status_code=404, detail="No working calibration active")
raise HTTPException(status_code=404, detail="Рабочая калибровка не активна")
if not working.standards:
raise HTTPException(status_code=404, detail="No standards captured in working calibration")
raise HTTPException(status_code=404, detail="В рабочей калибровке нет сохранённых стандартов")
from vna_system.core.visualization.magnitude_chart import generate_magnitude_plot_from_sweep_data
@ -371,8 +371,8 @@ async def get_working_calibration_standards_plots() -> dict[str, Any]:
fig = generate_magnitude_plot_from_sweep_data(sweep, working.preset)
if "error" not in fig and fig.get("data"):
fig["data"][0]["line"]["color"] = standard_colors.get(standard.value, "#1f77b4")
fig["data"][0]["name"] = f"{standard.value.upper()} Standard"
fig["layout"]["title"] = f"{standard.value.upper()} Standard Magnitude (Working)"
fig["data"][0]["name"] = f"Стандарт {standard.value.upper()}"
fig["layout"]["title"] = f"Амплитуда стандарта {standard.value.upper()} (рабочая)"
fig["raw_sweep_data"] = {
"sweep_number": sweep.sweep_number,
@ -390,13 +390,13 @@ async def get_working_calibration_standards_plots() -> dict[str, Any]:
}
individual[standard.value] = fig
except Exception as exc: # noqa: BLE001
individual[standard.value] = {"error": f"Failed to generate plot for {standard.value}: {exc}"}
individual[standard.value] = {"error": f"Не удалось построить график для {standard.value}: {exc}"}
if not individual:
raise HTTPException(status_code=404, detail="No valid plots generated for working calibration")
raise HTTPException(status_code=404, detail="Не удалось создать графики для рабочей калибровки")
return {
"calibration_name": "Working Calibration",
"calibration_name": "Рабочая калибровка",
"preset": {"filename": working.preset.filename, "mode": working.preset.mode.value},
"individual_plots": individual,
"is_working": True,
@ -425,11 +425,11 @@ async def get_references(preset_filename: str | None = None) -> List[dict[str, A
None
)
if not preset:
raise HTTPException(status_code=404, detail=f"Preset '{preset_filename}' not found")
raise HTTPException(status_code=404, detail=f"Пресет \"{preset_filename}\" не найден")
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")
raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан")
references = settings_manager.get_available_references(preset)
@ -462,11 +462,11 @@ async def get_current_reference(preset_filename: str | None = None) -> dict[str,
None
)
if not preset:
raise HTTPException(status_code=404, detail=f"Preset '{preset_filename}' not found")
raise HTTPException(status_code=404, detail=f"Пресет \"{preset_filename}\" не найден")
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")
raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан")
current_ref = settings_manager.get_current_reference(preset)
@ -500,11 +500,11 @@ async def create_reference(request: CreateReferenceRequest) -> dict[str, Any]:
None
)
if not preset:
raise HTTPException(status_code=404, detail=f"Preset '{request.preset_filename}' not found")
raise HTTPException(status_code=404, detail=f"Пресет \"{request.preset_filename}\" не найден")
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")
raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан")
# Create reference using the new capture method
reference_info = settings_manager.capture_reference_from_acquisition(
@ -517,7 +517,7 @@ async def create_reference(request: CreateReferenceRequest) -> dict[str, Any]:
return {
"success": True,
"message": f"Reference '{request.name}' created successfully",
"message": f"Эталон \"{request.name}\" успешно создан",
"reference": {
"name": reference_info.name,
"timestamp": reference_info.timestamp.isoformat(),
@ -545,20 +545,20 @@ async def set_current_reference(request: SetReferenceRequest) -> dict[str, Any]:
None
)
if not preset:
raise HTTPException(status_code=404, detail=f"Preset '{request.preset_filename}' not found")
raise HTTPException(status_code=404, detail=f"Пресет \"{request.preset_filename}\" не найден")
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")
raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан")
success = settings_manager.set_current_reference(request.name, preset)
if not success:
raise HTTPException(status_code=404, detail=f"Reference '{request.name}' not found")
raise HTTPException(status_code=404, detail=f"Эталон \"{request.name}\" не найден")
return {
"success": True,
"message": f"Reference '{request.name}' set as current"
"message": f"Эталон \"{request.name}\" выбран текущим"
}
except HTTPException:
raise
@ -575,11 +575,11 @@ async def clear_current_reference() -> dict[str, Any]:
success = settings_manager.clear_current_reference()
if not success:
raise HTTPException(status_code=500, detail="Failed to clear current reference")
raise HTTPException(status_code=500, detail="Не удалось сбросить текущий эталон")
return {
"success": True,
"message": "Current reference cleared"
"message": "Текущий эталон сброшен"
}
except HTTPException:
raise
@ -600,15 +600,15 @@ async def get_reference(reference_name: str, preset_filename: str | None = None)
None
)
if not preset:
raise HTTPException(status_code=404, detail=f"Preset '{preset_filename}' not found")
raise HTTPException(status_code=404, detail=f"Пресет \"{preset_filename}\" не найден")
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")
raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан")
info = settings_manager.get_reference_info(reference_name, preset)
if not info:
raise HTTPException(status_code=404, detail=f"Reference '{reference_name}' not found")
raise HTTPException(status_code=404, detail=f"Эталон \"{reference_name}\" не найден")
return ReferenceModel(
name=info.name,
@ -636,19 +636,19 @@ async def get_reference_plot(reference_name: str, preset_filename: str | None =
None
)
if not preset:
raise HTTPException(status_code=404, detail=f"Preset '{preset_filename}' not found")
raise HTTPException(status_code=404, detail=f"Пресет \"{preset_filename}\" не найден")
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")
raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан")
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")
raise HTTPException(status_code=404, detail=f"Эталон \"{reference_name}\" не найден")
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}'")
raise HTTPException(status_code=404, detail=f"Данные эталона \"{reference_name}\" не найдены")
from vna_system.core.visualization.magnitude_chart import generate_magnitude_plot_from_sweep_data
@ -682,20 +682,20 @@ async def delete_reference(reference_name: str, preset_filename: str | None = No
None
)
if not preset:
raise HTTPException(status_code=404, detail=f"Preset '{preset_filename}' not found")
raise HTTPException(status_code=404, detail=f"Пресет \"{preset_filename}\" не найден")
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")
raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан")
success = settings_manager.delete_reference(reference_name, preset)
if not success:
raise HTTPException(status_code=404, detail=f"Reference '{reference_name}' not found")
raise HTTPException(status_code=404, detail=f"Эталон \"{reference_name}\" не найден")
return {
"success": True,
"message": f"Reference '{reference_name}' deleted successfully"
"message": f"Эталон \"{reference_name}\" успешно удалён"
}
except HTTPException:
raise
@ -709,7 +709,7 @@ 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"}
return {"success": True, "message": "Текущая калибровка сброшена"}
except Exception as exc: # noqa: BLE001
logger.error("Failed to clear current calibration", error=repr(exc))
raise HTTPException(status_code=500, detail=str(exc))
@ -727,17 +727,17 @@ async def delete_calibration(calibration_name: str, preset_filename: str | None
None
)
if not preset:
raise HTTPException(status_code=404, detail=f"Preset '{preset_filename}' not found")
raise HTTPException(status_code=404, detail=f"Пресет \"{preset_filename}\" не найден")
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")
raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан")
settings_manager.delete_calibration(preset, calibration_name)
return {
"success": True,
"message": f"Calibration '{calibration_name}' deleted"
"message": f"Калибровка \"{calibration_name}\" удалена"
}
except HTTPException:
raise

View File

@ -28,7 +28,7 @@ async def dashboard():
if not index_file.exists():
logger.error(f"Dashboard template not found: {index_file}")
return HTMLResponse(
content="<h1>Dashboard Not Available</h1><p>The web UI template could not be found.</p>",
content="<h1>Панель недоступна</h1><p>Не удалось найти шаблон веб-интерфейса.</p>",
status_code=404
)
@ -40,7 +40,7 @@ async def dashboard():
except Exception as e:
logger.error(f"Error serving dashboard: {e}")
return HTMLResponse(
content=f"<h1>Error</h1><p>Unable to load dashboard: {e}</p>",
content=f"<h1>Ошибка</h1><p>Не удалось загрузить панель: {e}</p>",
status_code=500
)
@ -48,8 +48,8 @@ async def dashboard():
async def health_ui():
"""Health check endpoint for web UI."""
return {
"service": "VNA Web UI",
"status": "healthy",
"service": "Веб-интерфейс VNA",
"status": "работает",
"web_ui_dir": str(WEB_UI_DIR),
"static_dir_exists": STATIC_DIR.exists(),
"templates_dir_exists": TEMPLATES_DIR.exists(),

View File

@ -4,5 +4,5 @@
"autoscale": true,
"show_magnitude": true,
"show_phase": false,
"open_air": false
"open_air": true
}

View File

@ -94,6 +94,23 @@
height: 16px;
}
.btn__shortcut {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 var(--space-2);
border-radius: var(--radius-sm);
border: 1px solid var(--border-secondary);
background-color: var(--bg-tertiary);
color: var(--text-tertiary);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
letter-spacing: 0.05em;
text-transform: uppercase;
line-height: 1;
min-height: 0;
}
/* Processor Toggles */
.processor-toggles {
display: flex;

View File

@ -397,6 +397,10 @@ body {
display: none;
}
.header__controls .btn__shortcut {
display: none;
}
.header__controls .btn {
min-width: auto;
padding: var(--space-2);

View File

@ -198,8 +198,25 @@ class VNADashboard {
* Handle keyboard shortcuts
*/
handleKeyboardShortcuts(event) {
const activeElement = document.activeElement;
const activeTag = activeElement?.tagName?.toLowerCase();
const isEditable = activeElement?.isContentEditable ||
activeTag === 'input' ||
activeTag === 'textarea' ||
activeTag === 'select';
const key = event.key?.toLowerCase();
if (!isEditable && !event.ctrlKey && !event.metaKey && !event.altKey && key === 's') {
if (event.repeat) {
return;
}
event.preventDefault();
this.acquisition?.triggerSingleSweep?.();
return;
}
// Ctrl/Cmd + Shift + R: Reconnect WebSocket
if ((event.ctrlKey || event.metaKey) && event.key === 'r' && event.shiftKey) {
if ((event.ctrlKey || event.metaKey) && key === 'r' && event.shiftKey) {
event.preventDefault();
this.websocket.reconnect();
}

View File

@ -51,9 +51,10 @@
<span data-icon="square"></span>
<span class="btn__text">Стоп</span>
</button>
<button class="btn btn--accent btn--sm" id="singleSweepBtn" title="Запустить одиночный свип">
<button class="btn btn--accent btn--sm" id="singleSweepBtn" title="Запустить одиночный свип (клавиша S)" aria-keyshortcuts="S">
<span data-icon="zap"></span>
<span class="btn__text">Одиночный</span>
<span class="btn__shortcut" aria-hidden="true">S</span>
</button>
</div>