plesty.server.view.window ========================= .. py:module:: plesty.server.view.window .. autoapi-nested-parse:: The console's main window: menu bar, device table, detail pane. Layout (designed for a bench, not docked):: ┌ Bench Device View Help ──────────────────────────────────────────┐ │ ┌ Devices ─────────────────────────────────────────────────────────┐ │ │ │ NAME SOURCE STATE PORT PID STARTED │ │ │ │ pm100d plesty-pm100d==0.2.1 running 5551 4242 10:21:03 │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ ┌ pm100d ──────────────────── [Log] [Jobs] [Reports] ──────────────┐ │ │ │ === plesty-server start … :: python -m plesty.pm100d … │ │ │ │ [SERVER] Running at 127.0.0.1:5551 │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ ~/.plesty/server · fleet.yaml · 2 devices · refreshed 10:21:05 │ └────────────────────────────────────────────────────────────────────┘ The window owns a :class:`Bench` and a :class:`Worker`; every menu action calls one presenter method on the worker and refreshes when it returns. Attributes ---------- .. autoapisummary:: plesty.server.view.window.COLUMNS plesty.server.view.window.CHECK_COLUMN plesty.server.view.window.NAME_COLUMN plesty.server.view.window.REFRESH_MS plesty.server.view.window.NOTICE_MS Classes ------- .. autoapisummary:: plesty.server.view.window.MainWindow Functions --------- .. autoapisummary:: plesty.server.view.window._card plesty.server.view.window._button plesty.server.view.window._short plesty.server.view.window._clock plesty.server.view.window._tone_brush Module Contents --------------- .. py:data:: COLUMNS :value: ('Fleet', 'Name', 'Source', 'State', 'Port', 'PID', 'Started') .. py:data:: CHECK_COLUMN :value: 0 .. py:data:: NAME_COLUMN :value: 1 .. py:data:: REFRESH_MS :value: 3000 .. py:data:: NOTICE_MS :value: 20000 .. py:class:: MainWindow(bench: plesty.server.presenter.Bench, title: str = 'PLESTY bench', version: str = '') Bases: :py:obj:`PySide6.QtWidgets.QMainWindow` The console window. Build the window around *bench*. :param bench: The presenter. :param title: Window title. :param version: Shown in the About box. .. py:attribute:: notice .. py:attribute:: bench .. py:attribute:: version :value: '' .. py:attribute:: worker .. py:attribute:: _views :type: list[plesty.server.presenter.DeviceView] :value: [] .. py:attribute:: _starting :type: set[str] .. py:attribute:: _timer .. py:method:: _build_menu() -> None .. py:method:: _add(menu: PySide6.QtWidgets.QMenu, text: str, slot: collections.abc.Callable[[], Any], shortcut: Any = None) -> PySide6.QtGui.QAction .. py:method:: _build_body() -> None .. py:property:: selected :type: str | None Name of the selected device, or ``None``. .. py:method:: select(name: str) -> None Select the row of *name* (no-op when absent). .. py:method:: refresh() -> None Re-read the devices on the worker and repaint when they arrive. .. py:method:: _show_devices(views: list[plesty.server.presenter.DeviceView]) -> None .. py:method:: _selection_changed() -> None .. py:method:: _update_buttons() -> None Enable the device buttons for the selected row and its state. .. py:method:: _refresh_detail() -> None .. py:method:: _show_log(text: str) -> None .. py:method:: _show_jobs(jobs: list[plesty.server.model.Job]) -> None .. py:method:: _show_reports(reports: list[Any]) -> None .. py:method:: _note(line: str) -> None Show a progress line on its own row, and keep it. Its own row, because a failure is standing information the operator has to act on and progress is not: neither may hide the other, which is also why this is not ``QStatusBar.showMessage`` — that hides every non-permanent widget, the standing failure included. It clears itself after :data:`NOTICE_MS`, because "installing …" an hour later is a lie about what the bench is doing. Nothing is lost — View ▸ Messages… has every line of the session. .. py:method:: clear_notice() -> None Drop the progress line (the timer, and any action that finishes). .. py:property:: notice_text :type: str The progress line currently shown (empty when none). .. py:method:: show_messages() -> None View → Messages…: every progress line this session, newest last. .. py:method:: _show_message(text: str, tone: str) -> None .. py:method:: show_error(message: str) -> None Report a failure in the window, where it costs the operator nothing to read. Not a modal. What fails here fails *asynchronously* — an install takes minutes and the operator is often elsewhere — and a modal blocks the Qt event loop: the device table stops refreshing, an open job log stops tailing, and the console is frozen until somebody clicks it away. The message stays until it is dismissed or another replaces it. .. py:method:: clear_error() -> None Hide the standing message (the Dismiss button, and any action that succeeds). .. py:property:: error_text :type: str What the window is currently reporting as failed (empty when nothing). .. py:method:: _call(action: collections.abc.Callable[[], Any], then: collections.abc.Callable[[Any], None] | None = None) -> None Run a presenter call on the worker; refresh afterwards, report on error. .. py:method:: _require() -> str | None .. py:method:: _item_changed(item: PySide6.QtWidgets.QTableWidgetItem) -> None A check box toggled: include/exclude the device from the working fleet. .. py:method:: _fleets_dir() -> pathlib.Path Where the fleet file dialogs start: ``/fleets/`` (created on demand). .. py:method:: new_fleet(path: str | None = None) -> None File → New fleet…: an empty fleet file, made current (devices are added to it). .. py:method:: open_fleet(path: str | None = None) -> None File → Open fleet…: replace the declarations with another fleet file. .. py:method:: save_fleet_as(path: str | None = None) -> None File → Save working fleet as…: the checked devices to a new fleet file. .. py:method:: _fleet_changed() -> None .. py:method:: edit_device_config() -> None Edit → Device config…: declaration + .env of the highlighted device. The catalogue is fetched on the worker first, so the module's repository URL and latest tag are in the drop-downs when the operator switches a release declaration to a repository one. .. py:method:: edit_fleet_file() -> None Edit → Fleet file…: the YAML of the whole fleet. .. py:method:: open_catalogue() -> None Catalogue → Search & install…: fetch the hub catalogue, install a module. Only the module: the fleet is not touched here. Declaring an instance of it is Edit ▸ Add device…, which offers what the bench holds. .. py:method:: declare_device() -> None Edit → Add device…: declare an instance of an *installed* module. With nothing installed the user is sent to the catalogue instead. .. py:method:: edit_device() -> None Edit the declaration only (used by the context menu). .. py:method:: remove_device() -> None Edit → Remove from fleet (also the Remove button). .. py:method:: start_device() -> None Device → Start (one start at a time per device). .. py:method:: stop_device() -> None Device → Stop. .. py:method:: restart_device() -> None Device → Restart. .. py:method:: install_device() -> None Device → Install: starts the job and opens its dialog. .. py:method:: update_device() -> None Device → Update: starts the job and opens its dialog. .. py:method:: uninstall_device() -> None Device → Uninstall. .. py:method:: field_test_device() -> None Device → Field test…: configure, start the job, open its dialog. .. py:method:: fleet_up() -> None Device → Fleet up (the working fleet). .. py:method:: fleet_down() -> None Device → Fleet down (the working fleet). .. py:method:: show_jobs() -> None View → Jobs…: the Jobs tab of the detail pane. .. py:method:: _open_job(job: plesty.server.model.Job) -> None .. py:method:: _open_job_row(item: PySide6.QtWidgets.QTableWidgetItem) -> None .. py:method:: _context_menu(pos: Any) -> None .. py:method:: closeEvent(event: Any) -> None Closing the console closes what it manages: servers and running jobs. Asks first when something is running — an accidental close must not take a bench down — then stops everything synchronously so nothing is left unmanaged behind a window that is gone. .. py:function:: _card(title: str | PySide6.QtWidgets.QLabel, body: PySide6.QtWidgets.QWidget, buttons: list[PySide6.QtWidgets.QWidget] | None = None, own_row: bool = False) -> PySide6.QtWidgets.QWidget A titled card with optional buttons: the console's layout primitive. :param title: Card title (a label is reused as-is). :param body: The card's content. :param buttons: Buttons shown on the title line (few) or, with *own_row*, on a bar of their own between the title and the body (many). :param own_row: Put the buttons on their own row. .. py:function:: _button(text: str, slot: collections.abc.Callable[[], Any], tooltip: str = '', primary: bool = False, danger: bool = False) -> PySide6.QtWidgets.QPushButton A card-header button wired to *slot*. .. py:function:: _short(path: Any) -> str A path with the home directory abbreviated to ``~``. .. py:function:: _clock(stamp: str | None) -> str ``HH:MM:SS`` of an ISO timestamp (local time), or ``""``. .. py:function:: _tone_brush(widget: PySide6.QtWidgets.QWidget, state: str) -> Any The palette colour for a state, looked up from the theme on the window.