aboutsummaryrefslogtreecommitdiffstats
path: root/demo/ConnectionPage.qml
diff options
context:
space:
mode:
authorThomas Vanbesien <tvanbesi@proton.me>2026-03-23 14:55:32 +0100
committerThomas Vanbesien <tvanbesi@proton.me>2026-03-23 14:55:32 +0100
commit5ff9705937ffc1647587e1b228effd30c8a0e309 (patch)
treee1c5d9397ebbd68dd593788d6b09688a1d6ee8bd /demo/ConnectionPage.qml
parent47096b375797c38b5b1e79f1366f1152cc292875 (diff)
downloadBobinkQtOpcUa-5ff9705937ffc1647587e1b228effd30c8a0e309.tar.gz
BobinkQtOpcUa-5ff9705937ffc1647587e1b228effd30c8a0e309.zip
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.
Diffstat (limited to 'demo/ConnectionPage.qml')
-rw-r--r--demo/ConnectionPage.qml400
1 files changed, 400 insertions, 0 deletions
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
+ }
+ }
+}