From 5ff9705937ffc1647587e1b228effd30c8a0e309 Mon Sep 17 00:00:00 2001 From: Thomas Vanbesien Date: Mon, 23 Mar 2026 14:55:32 +0100 Subject: Refactor Main.qml into separate components and add comments Extract ConnectionPage, CertTrustDialog, and DebugConsole from Main.qml into their own QML files. Add inline comments to OpcUaMonitoredNode::handleValueUpdated explaining the range write guard, value store, and timestamp update. --- demo/ConnectionPage.qml | 400 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 400 insertions(+) create mode 100644 demo/ConnectionPage.qml (limited to 'demo/ConnectionPage.qml') diff --git a/demo/ConnectionPage.qml b/demo/ConnectionPage.qml new file mode 100644 index 0000000..ba41246 --- /dev/null +++ b/demo/ConnectionPage.qml @@ -0,0 +1,400 @@ +// 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 + } + } +} -- cgit v1.2.3