import { Theme } from "theme.slint"; import { LabeledInput, PrimaryButton, GhostButton } from "widgets.slint"; export struct SessionDraft { id: string, name: string, group: string, kind: string, // "ssh" | "serial" | "telnet" host: string, port: int, user: string, auth: string, // "password" | "key" password: string, private-key-path: string, proxy: string, // "" | "socks5://host:port" | "http://host:port" // Serial-only (ignored unless kind == "serial") serial-port: string, baud-rate: int, data-bits: int, stop-bits: int, parity: string, // "none" | "odd" | "even" flow-control: string, // "none" | "hardware" | "software" } // A single segment in a segmented selector (auth toggle, baud presets, …). component SegOption inherits Rectangle { in property label; in property active; callback clicked(); height: 30px; horizontal-stretch: 1; border-radius: Theme.radius-sm; border-width: 1px; background: active ? Theme.accent : Theme.bg-root; border-color: active ? Theme.accent : Theme.border-subtle; Text { width: parent.width; height: parent.height; text: root.label; color: active ? white : Theme.text-primary; font-size: Theme.fs-md; horizontal-alignment: center; vertical-alignment: center; } TouchArea { mouse-cursor: pointer; clicked => { root.clicked(); } } } component FolderOption inherits Rectangle { in property label; in property active; callback clicked(); height: 30px; border-radius: Theme.radius-sm; border-width: 1px; border-color: active ? Theme.accent : Theme.border-subtle; background: active ? Theme.bg-active : Theme.bg-root; HorizontalLayout { height: parent.height; padding-left: 10px; padding-right: 10px; spacing: 8px; Text { width: 18px; height: parent.height; text: "\u{E2C7}"; font-family: "Material Icons"; color: active ? Theme.accent : Theme.text-muted; font-size: Theme.fs-sm; horizontal-alignment: center; vertical-alignment: center; } Text { height: parent.height; text: root.label; color: Theme.text-primary; font-size: Theme.fs-md; vertical-alignment: center; horizontal-stretch: 1; overflow: elide; } if active : Text { width: 18px; height: parent.height; text: "\u{E5CA}"; font-family: "Material Icons"; color: Theme.accent; font-size: Theme.fs-sm; horizontal-alignment: center; vertical-alignment: center; } } TouchArea { mouse-cursor: pointer; clicked => { root.clicked(); } } } // Modal dialog for creating / editing a session (SSH / Serial / Telnet). // The dialog is purely presentational — it stores values in mutable // properties and emits `submit` / `cancel` callbacks up to the main window. export component SessionDialog inherits Rectangle { in-out property is-open: false; in-out property is-editing: false; in-out property draft-id; in-out property draft-name; in-out property draft-group: "Default"; in-out property draft-kind: "ssh"; in-out property draft-host; in-out property draft-port: "22"; in-out property draft-user: "root"; in-out property draft-auth: "password"; in-out property draft-password; in-out property draft-key-path; in-out property draft-proxy; in-out property draft-serial-port; in-out property draft-baud: "115200"; in-out property draft-data-bits: "8"; in-out property draft-stop-bits: "1"; in-out property draft-parity: "none"; in-out property draft-flow: "none"; in property <[string]> folders; callback submit(SessionDraft); callback cancel(); callback pick-key-file(); // open a native file picker for the private key visible: is-open; background: #000000aa; // Swallow clicks on the backdrop TouchArea { width: 100%; height: 100%; clicked => { root.cancel(); } } Rectangle { width: min(440px, parent.width - 48px); height: min(620px, parent.height - 48px); x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2; background: Theme.bg-panel; border-radius: Theme.radius-lg; border-width: 1px; border-color: Theme.border-strong; drop-shadow-blur: 24px; drop-shadow-color: #00000080; // Prevent backdrop click from propagating when user clicks inside the card TouchArea { width: 100%; height: 100%; } VerticalLayout { padding: 20px; spacing: 12px; Text { text: is-editing ? @tr("Edit session") : @tr("New session"); color: Theme.text-primary; font-size: Theme.fs-xl; font-weight: 600; } Flickable { vertical-stretch: 1; viewport-width: self.width; viewport-height: max(self.height, form-col.preferred-height); form-col := VerticalLayout { width: parent.width; alignment: start; spacing: 12px; // Connection type selector (always shown). VerticalLayout { spacing: 4px; Text { text: @tr("Connection type"); color: Theme.text-secondary; font-size: Theme.fs-sm; } HorizontalLayout { height: 30px; spacing: 6px; SegOption { label: "SSH"; active: root.draft-kind == "ssh"; clicked => { root.draft-kind = "ssh"; } } SegOption { label: @tr("Serial"); active: root.draft-kind == "serial"; clicked => { root.draft-kind = "serial"; } } SegOption { label: "Telnet"; active: root.draft-kind == "telnet"; clicked => { root.draft-kind = "telnet"; } } } } LabeledInput { label: @tr("Name"); placeholder: @tr("e.g. production web-01"); value <=> root.draft-name; } VerticalLayout { spacing: 4px; Text { text: @tr("Folder"); color: Theme.text-secondary; font-size: Theme.fs-sm; } VerticalLayout { spacing: 6px; for folder in root.folders : FolderOption { label: folder; active: root.draft-group == folder; clicked => { root.draft-group = folder; } } } } // ── SSH / Telnet share host + port ────────────────────────────── if root.draft-kind != "serial" : HorizontalLayout { spacing: 10px; LabeledInput { label: @tr("Host / IP"); placeholder: "192.168.1.10"; value <=> root.draft-host; horizontal-stretch: 2; } LabeledInput { label: @tr("Port"); placeholder: root.draft-kind == "telnet" ? "23" : "22"; value <=> root.draft-port; horizontal-stretch: 1; } } // ── SSH-only: user + auth ─────────────────────────────────────── if root.draft-kind == "ssh" : LabeledInput { label: @tr("Username"); placeholder: "root"; value <=> root.draft-user; } if root.draft-kind == "ssh" : VerticalLayout { spacing: 4px; Text { text: @tr("Authentication"); color: Theme.text-secondary; font-size: Theme.fs-sm; } HorizontalLayout { height: 30px; spacing: 6px; SegOption { label: @tr("Password"); active: root.draft-auth == "password"; clicked => { root.draft-auth = "password"; } } SegOption { label: @tr("Private key"); active: root.draft-auth == "key"; clicked => { root.draft-auth = "key"; } } } } if root.draft-kind == "ssh" && root.draft-auth == "password" : LabeledInput { label: @tr("Password"); placeholder: root.is-editing ? @tr("Leave blank to keep the current password") : "••••••••"; password: true; value <=> root.draft-password; } if root.draft-kind == "ssh" && root.draft-auth == "key" : VerticalLayout { spacing: 4px; Text { text: @tr("Private key file"); color: Theme.text-secondary; font-size: Theme.fs-sm; } HorizontalLayout { height: 32px; spacing: 8px; Rectangle { height: 32px; horizontal-stretch: 1; border-radius: Theme.radius-sm; border-width: 1px; border-color: keyin.has-focus ? Theme.accent : Theme.border-subtle; background: Theme.bg-root; keyin := TextInput { x: 10px; y: 0px; width: parent.width - 20px; height: parent.height; text <=> root.draft-key-path; color: Theme.text-primary; font-size: Theme.fs-md; vertical-alignment: center; single-line: true; } if root.draft-key-path == "" : Text { x: 10px; y: 0px; height: parent.height; text: "/home/you/.ssh/id_ed25519"; color: Theme.text-muted; font-size: Theme.fs-md; vertical-alignment: center; } } Rectangle { width: 64px; height: 32px; border-radius: Theme.radius-sm; border-width: 1px; border-color: Theme.border-subtle; background: browse-ta.has-hover ? Theme.bg-hover : Theme.bg-elevated; Text { width: parent.width; height: parent.height; text: @tr("Browse…"); color: Theme.text-primary; font-size: Theme.fs-sm; horizontal-alignment: center; vertical-alignment: center; } browse-ta := TouchArea { mouse-cursor: pointer; clicked => { root.pick-key-file(); } } } } Text { text: @tr("Pick the private key itself (not the .pub public key)"); color: Theme.text-muted; font-size: Theme.fs-xs; } } // ── Serial-only fields ────────────────────────────────────────── if root.draft-kind == "serial" : LabeledInput { label: @tr("Serial port"); placeholder: "COM3 / /dev/ttyUSB0"; value <=> root.draft-serial-port; } if root.draft-kind == "serial" : LabeledInput { label: @tr("Baud rate"); placeholder: "115200"; value <=> root.draft-baud; } if root.draft-kind == "serial" : VerticalLayout { spacing: 4px; Text { text: @tr("Data bits / Stop bits / Parity"); color: Theme.text-secondary; font-size: Theme.fs-sm; } HorizontalLayout { height: 30px; spacing: 6px; SegOption { label: "7"; active: root.draft-data-bits == "7"; clicked => { root.draft-data-bits = "7"; } } SegOption { label: "8"; active: root.draft-data-bits == "8"; clicked => { root.draft-data-bits = "8"; } } Rectangle { width: 8px; } SegOption { label: "1"; active: root.draft-stop-bits == "1"; clicked => { root.draft-stop-bits = "1"; } } SegOption { label: "2"; active: root.draft-stop-bits == "2"; clicked => { root.draft-stop-bits = "2"; } } Rectangle { width: 8px; } SegOption { label: "N"; active: root.draft-parity == "none"; clicked => { root.draft-parity = "none"; } } SegOption { label: "O"; active: root.draft-parity == "odd"; clicked => { root.draft-parity = "odd"; } } SegOption { label: "E"; active: root.draft-parity == "even"; clicked => { root.draft-parity = "even"; } } } } if root.draft-kind == "serial" : VerticalLayout { spacing: 4px; Text { text: @tr("Flow control"); color: Theme.text-secondary; font-size: Theme.fs-sm; } HorizontalLayout { height: 30px; spacing: 6px; SegOption { label: @tr("None"); active: root.draft-flow == "none"; clicked => { root.draft-flow = "none"; } } SegOption { label: @tr("Hardware"); active: root.draft-flow == "hardware"; clicked => { root.draft-flow = "hardware"; } } SegOption { label: @tr("Software"); active: root.draft-flow == "software"; clicked => { root.draft-flow = "software"; } } } } // ── Optional outbound proxy (SSH / Telnet only) ───────────────── if root.draft-kind != "serial" : VerticalLayout { spacing: 4px; LabeledInput { label: @tr("Proxy (optional)"); placeholder: "socks5://127.0.0.1:1080"; value <=> root.draft-proxy; } Text { text: @tr("Leave blank to use $ALL_PROXY or connect directly"); color: Theme.text-muted; font-size: Theme.fs-xs; } } } } HorizontalLayout { height: 30px; alignment: end; spacing: 8px; GhostButton { text: @tr("Cancel"); clicked => { root.cancel(); } } PrimaryButton { text: is-editing ? @tr("Save") : @tr("Create"); clicked => { root.submit({ id: root.draft-id, name: root.draft-name, group: root.draft-group, kind: root.draft-kind, host: root.draft-host, port: root.draft-port.to-float(), user: root.draft-user, auth: root.draft-auth, password: root.draft-password, private-key-path: root.draft-key-path, proxy: root.draft-proxy, serial-port: root.draft-serial-port, baud-rate: root.draft-baud.to-float(), data-bits: root.draft-data-bits.to-float(), stop-bits: root.draft-stop-bits.to-float(), parity: root.draft-parity, flow-control: root.draft-flow, }); } } } } } }