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