// Main.qml — Demo app for testing BobinkClient connection flow. import QtQuick import QtQuick.Controls import QtQuick.Layouts import QtQuick.Dialogs import Bobink ApplicationWindow { id: root width: 800 height: 900 visible: true title: "Bobink Demo" property bool autoConnectFailed: false property bool showPkiSettings: false Component.onCompleted: { BobinkClient.discoveryUrl = discoveryUrlField.text; BobinkClient.startDiscovery(); } Connections { target: BobinkClient function onServersChanged() { debugConsole.appendLog("Discovered server list updated"); } function onConnectedChanged() { debugConsole.appendLog("Connected: " + BobinkClient.connected); if (BobinkClient.connected) root.autoConnectFailed = false; } function onConnectionError(message) { debugConsole.appendLog("ERROR: " + message); root.autoConnectFailed = true; } function onStatusMessage(message) { debugConsole.appendLog(message); } function onDiscoveringChanged() { debugConsole.appendLog("Discovering: " + BobinkClient.discovering); } function onCertificateTrustRequested(certInfo) { certTrustDialog.certInfo = certInfo; certTrustDialog.open(); } } Dialog { id: certTrustDialog property string certInfo anchors.centerIn: parent title: "Certificate Trust" modal: true standardButtons: Dialog.Yes | Dialog.No Label { text: certTrustDialog.certInfo } onAccepted: BobinkClient.acceptCertificate() onRejected: BobinkClient.rejectCertificate() } ColumnLayout { anchors.fill: parent anchors.margins: 20 spacing: 12 Label { text: "Discovery URL" font.bold: true } RowLayout { TextField { id: discoveryUrlField Layout.fillWidth: true text: "opc.tcp://localhost:4840" } Button { text: BobinkClient.discovering ? "Stop" : "Discover" onClicked: { if (BobinkClient.discovering) { BobinkClient.stopDiscovery(); } else { BobinkClient.discoveryUrl = discoveryUrlField.text; BobinkClient.startDiscovery(); } } } } Label { text: BobinkClient.discovering ? "Discovering... (" + BobinkClient.servers.length + " found)" : BobinkClient.servers.length + " server(s)" font.italic: true } ListView { Layout.fillWidth: true Layout.preferredHeight: 100 clip: true model: BobinkClient.servers delegate: ItemDelegate { required property var modelData width: ListView.view.width text: modelData.serverName + " — " + modelData.applicationUri onClicked: { if (modelData.discoveryUrls.length > 0) serverUrlField.text = modelData.discoveryUrls[0]; } } ScrollBar.vertical: ScrollBar {} } RowLayout { Label { text: "PKI" font.bold: true } Label { text: BobinkClient.certFile ? " (" + BobinkClient.certFile.split("/").pop() + ")" : " (no certificate found)" font.italic: true color: BobinkClient.certFile ? "green" : "gray" } Item { Layout.fillWidth: true } Button { text: root.showPkiSettings ? "Hide" : "Configure..." flat: true onClicked: root.showPkiSettings = !root.showPkiSettings } } FileDialog { id: certFileDialog title: "Select Certificate" currentFolder: "file://" + trustFolderField.text nameFilters: ["DER certificates (*.der)", "All files (*)"] onAccepted: certFileField.text = selectedFile.toString().replace("file://", "") } FileDialog { id: keyFileDialog title: "Select Private Key" currentFolder: "file://" + trustFolderField.text nameFilters: ["Key files (*.pem *.crt)", "All files (*)"] onAccepted: keyFileField.text = selectedFile.toString().replace("file://", "") } FolderDialog { id: trustFolderDialog title: "Select Trust Folder" currentFolder: "file://" + trustFolderField.text onAccepted: trustFolderField.text = selectedFolder.toString().replace("file://", "") } GridLayout { columns: 3 Layout.fillWidth: true visible: root.showPkiSettings Label { text: "Certificate:" } TextField { id: certFileField Layout.fillWidth: true text: BobinkClient.certFile placeholderText: "Client certificate (.der)" } Button { text: "Browse..." onClicked: certFileDialog.open() } Label { text: "Private key:" } TextField { id: keyFileField Layout.fillWidth: true text: BobinkClient.keyFile placeholderText: "Private key (.pem, .crt)" } Button { text: "Browse..." onClicked: keyFileDialog.open() } Label { text: "Trust folder:" } TextField { id: trustFolderField Layout.fillWidth: true text: BobinkClient.pkiDir } Button { text: "Browse..." onClicked: trustFolderDialog.open() } } Button { text: "Apply PKI" Layout.fillWidth: true visible: root.showPkiSettings onClicked: { BobinkClient.pkiDir = trustFolderField.text; BobinkClient.certFile = certFileField.text; BobinkClient.keyFile = keyFileField.text; BobinkClient.applyPki(); } } Label { text: "Server URL" font.bold: true } TextField { id: serverUrlField Layout.fillWidth: true placeholderText: "opc.tcp://..." } Label { text: "Authentication" font.bold: true } BobinkAuth { id: auth mode: authModeCombo.currentValue username: usernameField.text password: passwordField.text certPath: BobinkClient.certFile keyPath: BobinkClient.keyFile } ComboBox { id: authModeCombo Layout.fillWidth: true textRole: "text" valueRole: "mode" model: [ { text: "Anonymous", mode: BobinkAuth.Anonymous }, { text: "Username / Password", mode: BobinkAuth.UserPass }, { text: "Certificate", mode: BobinkAuth.Certificate } ] } GridLayout { columns: 2 visible: authModeCombo.currentValue === BobinkAuth.UserPass Layout.fillWidth: true Label { text: "Username:" } TextField { id: usernameField Layout.fillWidth: true } Label { text: "Password:" } TextField { id: passwordField Layout.fillWidth: true echoMode: TextInput.Password } } Button { text: BobinkClient.connected ? "Disconnect" : "Connect" Layout.fillWidth: true onClicked: { if (BobinkClient.connected) { BobinkClient.disconnectFromServer(); } else { root.autoConnectFailed = false; BobinkClient.auth = auth; BobinkClient.serverUrl = serverUrlField.text; BobinkClient.connectToServer(); } } } Label { text: "Direct Connect" font.bold: true visible: root.autoConnectFailed && !BobinkClient.connected } GridLayout { columns: 2 Layout.fillWidth: true visible: root.autoConnectFailed && !BobinkClient.connected Label { text: "Security policy:" } ComboBox { id: securityPolicyCombo Layout.fillWidth: true textRole: "text" valueRole: "policy" model: [ { text: "Basic256Sha256", policy: BobinkClient.Basic256Sha256 }, { text: "Aes128-Sha256-RsaOaep", policy: BobinkClient.Aes128_Sha256_RsaOaep }, { text: "Aes256-Sha256-RsaPss", policy: BobinkClient.Aes256_Sha256_RsaPss } ] } Label { text: "Security mode:" } ComboBox { id: securityModeCombo Layout.fillWidth: true textRole: "text" valueRole: "mode" model: [ { text: "Sign & Encrypt", mode: BobinkClient.SignAndEncrypt }, { text: "Sign", mode: BobinkClient.Sign }, { text: "None", mode: BobinkClient.None } ] } } Button { text: "Direct Connect" Layout.fillWidth: true visible: root.autoConnectFailed && !BobinkClient.connected onClicked: { BobinkClient.auth = auth; BobinkClient.serverUrl = serverUrlField.text; BobinkClient.connectDirect(securityPolicyCombo.currentValue, securityModeCombo.currentValue); } } Label { text: "Status: " + (BobinkClient.connected ? "Connected" : "Disconnected") color: BobinkClient.connected ? "green" : "red" } Item { Layout.fillHeight: true } Rectangle { id: debugConsole Layout.fillWidth: true Layout.preferredHeight: 100 color: "#1e1e1e" border.color: "#444" radius: 4 function appendLog(msg) { let ts = new Date().toLocaleTimeString(Qt.locale(), "HH:mm:ss"); statusLog.text += "[" + ts + "] " + msg + "\n"; statusLog.cursorPosition = statusLog.text.length; } ScrollView { anchors.fill: parent anchors.margins: 4 TextArea { id: statusLog readOnly: true color: "#cccccc" font.family: "monospace" font.pointSize: 9 wrapMode: TextEdit.Wrap background: null } } } } }