// ConnectionPage.qml — Discovery, PKI, auth, and connection UI. pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import QtQuick.Dialogs import Bobink Page { id: connectionPage property bool autoConnectFailed: false property bool showPkiSettings: false Component.onCompleted: { Bobink.discoveryUrl = discoveryUrlField.text; Bobink.startDiscovery(); } OpcUaAuth { id: auth certPath: Bobink.certFile keyPath: Bobink.keyFile mode: authModeCombo.currentValue password: passwordField.text username: usernameField.text } FileDialog { id: certFileDialog currentFolder: "file://" + trustFolderField.text nameFilters: ["DER certificates (*.der)", "All files (*)"] title: "Select Certificate" onAccepted: certFileField.text = selectedFile.toString().replace( "file://", "") } FileDialog { id: keyFileDialog currentFolder: "file://" + trustFolderField.text nameFilters: ["Key files (*.pem *.crt)", "All files (*)"] title: "Select Private Key" onAccepted: keyFileField.text = selectedFile.toString().replace( "file://", "") } FolderDialog { id: trustFolderDialog currentFolder: "file://" + trustFolderField.text title: "Select Trust Folder" onAccepted: trustFolderField.text = selectedFolder.toString().replace( "file://", "") } ColumnLayout { anchors.fill: parent anchors.margins: 20 spacing: 12 Label { font.bold: true text: "Discovery URL" } RowLayout { TextField { id: discoveryUrlField Layout.fillWidth: true text: "opc.tcp://localhost:4840" } Button { text: Bobink.discovering ? "Stop" : "Discover" onClicked: { if (Bobink.discovering) { Bobink.stopDiscovery(); } else { Bobink.discoveryUrl = discoveryUrlField.text; Bobink.startDiscovery(); } } } } Label { font.italic: true text: Bobink.discovering ? "Discovering... (" + Bobink.servers.length + " found)" : Bobink.servers.length + " server(s)" } ListView { id: serverListView Layout.fillWidth: true Layout.preferredHeight: 100 clip: true model: Bobink.servers ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } delegate: ItemDelegate { id: serverDelegate required property var modelData width: ListView.view.width contentItem: ColumnLayout { spacing: 2 Label { text: serverDelegate.modelData.serverName } Label { Layout.fillWidth: true color: "gray" elide: Text.ElideRight font.italic: true font.pointSize: 8 text: serverDelegate.modelData.applicationUri } } onClicked: { if (modelData.discoveryUrls.length > 0) serverUrlField.text = modelData.discoveryUrls[0]; } } } RowLayout { Label { font.bold: true text: "PKI" } Label { color: Bobink.certFile ? "green" : "gray" font.italic: true text: Bobink.certFile ? " (" + Bobink.certFile.split("/").pop( ) + ")" : " (no certificate found)" } Item { Layout.fillWidth: true } Button { text: connectionPage.showPkiSettings ? "Hide" : "Configure" onClicked: connectionPage.showPkiSettings = !connectionPage.showPkiSettings } } GridLayout { Layout.fillWidth: true columns: 3 visible: connectionPage.showPkiSettings Label { text: "Certificate:" } TextField { id: certFileField Layout.fillWidth: true placeholderText: "Client certificate (.der)" text: Bobink.certFile } Button { text: "Browse" onClicked: certFileDialog.open() } Label { text: "Private key:" } TextField { id: keyFileField Layout.fillWidth: true placeholderText: "Private key (.pem, .crt)" text: Bobink.keyFile } Button { text: "Browse" onClicked: keyFileDialog.open() } Label { text: "Trust folder:" } TextField { id: trustFolderField Layout.fillWidth: true text: Bobink.pkiDir } Button { text: "Browse" onClicked: trustFolderDialog.open() } } Button { Layout.fillWidth: true text: "Apply PKI" visible: connectionPage.showPkiSettings onClicked: { Bobink.pkiDir = trustFolderField.text; Bobink.certFile = certFileField.text; Bobink.keyFile = keyFileField.text; Bobink.applyPki(); } } Label { font.bold: true text: "Server URL" } TextField { id: serverUrlField Layout.fillWidth: true placeholderText: "opc.tcp://..." } Label { font.bold: true text: "Authentication" } ComboBox { id: authModeCombo Layout.fillWidth: true model: [ { text: "Anonymous", mode: OpcUaAuth.Anonymous }, { text: "Username / Password", mode: OpcUaAuth.UserPass }, { text: "Certificate", mode: OpcUaAuth.Certificate } ] textRole: "text" valueRole: "mode" } GridLayout { Layout.fillWidth: true columns: 2 visible: authModeCombo.currentValue === OpcUaAuth.UserPass Label { text: "Username:" } TextField { id: usernameField Layout.fillWidth: true } Label { text: "Password:" } TextField { id: passwordField Layout.fillWidth: true echoMode: TextInput.Password } } Button { Layout.fillWidth: true text: "Connect" onClicked: { connectionPage.autoConnectFailed = false; Bobink.auth = auth; Bobink.serverUrl = serverUrlField.text; Bobink.connectToServer(); } } Label { font.bold: true text: "Direct Connect" visible: connectionPage.autoConnectFailed } GridLayout { Layout.fillWidth: true columns: 2 visible: connectionPage.autoConnectFailed Label { text: "Security policy:" } ComboBox { id: securityPolicyCombo Layout.fillWidth: true model: [ { text: "Basic256Sha256", policy: Bobink.Basic256Sha256 }, { text: "Aes128-Sha256-RsaOaep", policy: Bobink.Aes128_Sha256_RsaOaep }, { text: "Aes256-Sha256-RsaPss", policy: Bobink.Aes256_Sha256_RsaPss } ] textRole: "text" valueRole: "policy" } Label { text: "Security mode:" } ComboBox { id: securityModeCombo Layout.fillWidth: true model: [ { text: "Sign & Encrypt", mode: Bobink.SignAndEncrypt }, { text: "Sign", mode: Bobink.Sign }, { text: "None", mode: Bobink.None } ] textRole: "text" valueRole: "mode" } } Button { Layout.fillWidth: true text: "Direct Connect" visible: connectionPage.autoConnectFailed onClicked: { Bobink.auth = auth; Bobink.serverUrl = serverUrlField.text; Bobink.connectDirect(securityPolicyCombo.currentValue, securityModeCombo.currentValue); } } Item { Layout.fillHeight: true } } }