gui: add DragonRelayView — add/connect to relay server UI
This commit is contained in:
parent
999cecb21c
commit
33edc6affe
1 changed files with 246 additions and 0 deletions
246
app/gui/DragonRelayView.qml
Normal file
246
app/gui/DragonRelayView.qml
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
// DragonRelayView.qml
|
||||||
|
//
|
||||||
|
// Add a DragonRelay server and connect to its VPN.
|
||||||
|
// Wire this view up to a DragonRelayBackend C++ object exposed to QML via
|
||||||
|
// qmlRegisterType or setContextProperty:
|
||||||
|
//
|
||||||
|
// // main.cpp
|
||||||
|
// engine.rootContext()->setContextProperty("dragonRelay", &g_relayBackend);
|
||||||
|
//
|
||||||
|
// DragonRelayBackend (to be written as a thin QObject wrapper around
|
||||||
|
// RelayClient + TunnelManager) should expose:
|
||||||
|
// Q_PROPERTY(int status ...) // 0=Idle, 1=Connecting, 2=Connected, 3=Error
|
||||||
|
// Q_PROPERTY(QString statusText ...) // human-readable status
|
||||||
|
// Q_PROPERTY(var hosts ...) // list model of {name, ip, port}
|
||||||
|
// Q_INVOKABLE void connectRelay(url, username, password)
|
||||||
|
// Q_INVOKABLE void disconnectRelay()
|
||||||
|
// Q_INVOKABLE void streamHost(ip, app)
|
||||||
|
// signal: hostsChanged()
|
||||||
|
// signal: errorOccurred(message)
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
Page {
|
||||||
|
id: root
|
||||||
|
title: qsTr("DragonRelay")
|
||||||
|
|
||||||
|
// ── State ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
readonly property int stateIdle: 0
|
||||||
|
readonly property int stateConnecting: 1
|
||||||
|
readonly property int stateConnected: 2
|
||||||
|
readonly property int stateError: 3
|
||||||
|
|
||||||
|
// Bound to dragonRelay.status in real integration
|
||||||
|
property int currentState: stateIdle
|
||||||
|
property string statusText: qsTr("Not connected")
|
||||||
|
|
||||||
|
// ── Header bar ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
header: ToolBar {
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 12
|
||||||
|
anchors.rightMargin: 12
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("🐉 DragonRelay")
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
// Connection status pill
|
||||||
|
Rectangle {
|
||||||
|
width: statusLabel.implicitWidth + 20
|
||||||
|
height: 24
|
||||||
|
radius: 12
|
||||||
|
color: {
|
||||||
|
switch (root.currentState) {
|
||||||
|
case root.stateConnecting: return "#f59e0b" // amber
|
||||||
|
case root.stateConnected: return "#10b981" // green
|
||||||
|
case root.stateError: return "#ef4444" // red
|
||||||
|
default: return "#6b7280" // grey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
id: statusLabel
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: root.statusText
|
||||||
|
color: "white"
|
||||||
|
font.pixelSize: 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Body ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 24
|
||||||
|
spacing: 20
|
||||||
|
|
||||||
|
// ── Login form (shown when idle or errored) ────────────────────────
|
||||||
|
|
||||||
|
GroupBox {
|
||||||
|
id: loginBox
|
||||||
|
title: qsTr("Connect to a DragonRelay server")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: root.currentState === root.stateIdle
|
||||||
|
|| root.currentState === root.stateError
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: urlField
|
||||||
|
Layout.fillWidth: true
|
||||||
|
placeholderText: qsTr("Relay URL e.g. http://10.0.0.5:8080")
|
||||||
|
text: "http://"
|
||||||
|
inputMethodHints: Qt.ImhUrlCharactersOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: userField
|
||||||
|
Layout.fillWidth: true
|
||||||
|
placeholderText: qsTr("Username")
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: passField
|
||||||
|
Layout.fillWidth: true
|
||||||
|
placeholderText: qsTr("Password")
|
||||||
|
echoMode: TextInput.Password
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
id: saveCheck
|
||||||
|
text: qsTr("Remember this server")
|
||||||
|
checked: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: connectBtn
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: root.currentState === root.stateConnecting
|
||||||
|
? qsTr("Connecting…")
|
||||||
|
: qsTr("Connect")
|
||||||
|
enabled: urlField.text.length > 7
|
||||||
|
&& userField.text.length > 0
|
||||||
|
&& passField.text.length > 0
|
||||||
|
&& root.currentState !== root.stateConnecting
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
root.currentState = root.stateConnecting
|
||||||
|
root.statusText = qsTr("Connecting…")
|
||||||
|
// Real call: dragonRelay.connectRelay(urlField.text, userField.text, passField.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error message
|
||||||
|
Label {
|
||||||
|
id: errorLabel
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: root.currentState === root.stateError
|
||||||
|
color: "#ef4444"
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: root.statusText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Host list (shown when connected) ──────────────────────────────
|
||||||
|
|
||||||
|
GroupBox {
|
||||||
|
title: qsTr("Streaming hosts")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
visible: root.currentState === root.stateConnected
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: hostList
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
clip: true
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
// In real integration: model: dragonRelay.hosts
|
||||||
|
model: ListModel {
|
||||||
|
// Placeholder for design-time preview
|
||||||
|
ListElement { hostName: "Gaming PC"; hostIp: "10.99.0.10" }
|
||||||
|
ListElement { hostName: "Living Room"; hostIp: "10.99.0.11" }
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
width: hostList.width
|
||||||
|
height: 56
|
||||||
|
radius: 8
|
||||||
|
color: hostMouse.containsMouse ? "#374151" : "#1f2937"
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 16
|
||||||
|
anchors.rightMargin: 16
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Label {
|
||||||
|
text: model.hostName
|
||||||
|
font.pixelSize: 14
|
||||||
|
font.bold: true
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: model.hostIp
|
||||||
|
font.pixelSize: 11
|
||||||
|
color: "#9ca3af"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: qsTr("Stream")
|
||||||
|
onClicked: {
|
||||||
|
// Real call: dragonRelay.streamHost(model.hostIp, "Desktop")
|
||||||
|
console.log("stream", model.hostIp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: hostMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
onDoubleClicked: {
|
||||||
|
// Real call: dragonRelay.streamHost(model.hostIp, "Desktop")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
text: qsTr("Disconnect")
|
||||||
|
flat: true
|
||||||
|
onClicked: {
|
||||||
|
root.currentState = root.stateIdle
|
||||||
|
root.statusText = qsTr("Not connected")
|
||||||
|
// Real call: dragonRelay.disconnectRelay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillHeight: true; visible: root.currentState !== root.stateConnected }
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue