From cf12c943f7e79bea51bebe5d10df38d80b07a180 Mon Sep 17 00:00:00 2001 From: umorist47 Date: Sun, 8 Mar 2026 03:16:28 +0300 Subject: [PATCH] 1.1 --- .../contents/config/main.xml | 3 + .../contents/ui/config/General.qml | 6 + .../contents/ui/main.qml | 1335 ++++++++++------- .../contents/ui/worker.mjs | 101 ++ com.umorist47.meowrelaygui/metadata.json | 2 +- 5 files changed, 935 insertions(+), 512 deletions(-) diff --git a/com.umorist47.meowrelaygui/contents/config/main.xml b/com.umorist47.meowrelaygui/contents/config/main.xml index 559530b..5a64469 100644 --- a/com.umorist47.meowrelaygui/contents/config/main.xml +++ b/com.umorist47.meowrelaygui/contents/config/main.xml @@ -57,6 +57,9 @@ true + + true + true diff --git a/com.umorist47.meowrelaygui/contents/ui/config/General.qml b/com.umorist47.meowrelaygui/contents/ui/config/General.qml index 37b20bc..9105fdd 100644 --- a/com.umorist47.meowrelaygui/contents/ui/config/General.qml +++ b/com.umorist47.meowrelaygui/contents/ui/config/General.qml @@ -26,6 +26,7 @@ KCM.SimpleKCM { property alias cfg_BIpostebalka: showSwitchEbalkaBI.checked property alias cfg_BIdevicesupdate: showDevicesActionsBI.checked property alias cfg_BIfeaturesupdate: showFeaturesActionsBI.checked + property alias cfg_BIpostfirewall: showFirewallActionsBI.checked property alias cfg_BIautoupdate: showAutoUpdateBI.checked property alias cfg_SBdisplay: showSB.checked @@ -84,6 +85,7 @@ KCM.SimpleKCM { } RowLayout { Kirigami.FormData.label: i18n("Ping") + enabled: false CheckBox { id: enablePing } @@ -137,6 +139,10 @@ KCM.SimpleKCM { id: showFeaturesActionsBI text: i18n("Features actions") } + CheckBox { + id: showFirewallActionsBI + text: i18n("Firewall actions") + } CheckBox { id: showAutoUpdateBI text: i18n("AutoUpdate actions") diff --git a/com.umorist47.meowrelaygui/contents/ui/main.qml b/com.umorist47.meowrelaygui/contents/ui/main.qml index 6b64a3e..5300251 100644 --- a/com.umorist47.meowrelaygui/contents/ui/main.qml +++ b/com.umorist47.meowrelaygui/contents/ui/main.qml @@ -23,18 +23,17 @@ PlasmoidItem { //me/profile property var profileData: null - property alias profileDataAlias: root.profileData - property string user: profileData.user - property bool is_admin: profileData.user_data.is_admin - property bool share: profileData.user_data.share - property bool ebalka: profileData.ebalka - property string current_device: profileData.current_device - property string current: profileData.current - property string provider: profileData.provider - property string ip: profileData.ip - property var features: profileData.features - property var devices: profileData.devices + property string user: profileData ? profileData.user : "" + property bool is_admin: profileData ? profileData.user_data.is_admin : false + property bool share: profileData ? profileData.user_data.share : false + property bool ebalka: profileData ? profileData.ebalka : false + property string current_device: profileData ? profileData.current_device : "" + property string current: profileData ? profileData.current : "" + property string provider: profileData ? profileData.provider : "" + property string ip: profileData ? profileData.ip : "" + property var features: profileData ? profileData.features : null + property var devices: profileData ? profileData.devices : null //routes property var routesData: null @@ -42,8 +41,38 @@ PlasmoidItem { //stats property var statsData: null - property double uptime: statsData.uptime - property var servers: statsData.servers + //firewall + property var firewallData: null + property bool firewallAccess: firewallData ? (firewallData.state === 0) : false + property bool firewallEnabled: false + + property double uptime: statsData ? statsData.uptime : 0 + property var servers: statsData ? statsData.servers : null + + // tabs + ListModel { + id: tabs + ListElement { + name: "Route" + visible: true + } + ListElement { + name: "Devices" + visible: true + } + ListElement { + name: "Firewall" + visible: false + } + ListElement { + name: "Stats" + visible: true + } + ListElement { + name: "Features" + visible: true + } + } // tabs json responses ListModel { id: routeModel } @@ -51,6 +80,9 @@ PlasmoidItem { ListModel { id: profileModel } ListModel { id: devicesModel } ListModel { id: featuresModel } + ListModel { id: firewallModel } + ListModel { id: ownedipsModel } + ListModel { id: ownedlocalipsModel } // buttons blocker (antispam) property int isLoadingItems: 0 @@ -62,6 +94,8 @@ PlasmoidItem { property bool countriesBlocked: false property bool devicesBlocked: false property bool featuresBlocked: false + property bool firewallBlocked: false + property bool timeouted: false onIsLoadingItemsChanged: { if (isLoadingItems > 0) { @@ -100,11 +134,7 @@ PlasmoidItem { repeat: false onTriggered: { console.error("MeowRelay unreachable!") //ваша няшка умерла - routeModel.clear() - routeModel.append({"code":"ERR","name":"Error, MeowRelay unreachable :<","provider":"NONE"}) - routesData = [{"code":"ERR","name":"Error, MeowRelay unreachable :<","provider":"NONE"}] - profileData = {"provider":"NONE"} - statsData = null + timeouted = true isBlocked = false isLoadingItems = 0 countriesBlocked = true @@ -112,6 +142,7 @@ PlasmoidItem { ebalkaBlocked = true devicesBlocked = true featuresBlocked = true + firewallBlocked = true Plasmoid.configuration.OnErrorRefresh ? ( meowRelayWatchdog.start(), isLoadingItems += Plasmoid.configuration.BIrefresh, @@ -119,19 +150,62 @@ PlasmoidItem { superWorker.sendMessage({ "action": "fetch_routes" }), superWorker.sendMessage({ "action": "fetch_profile" }), superWorker.sendMessage({ "action": "fetch_stats" }), + superWorker.sendMessage({ "action": "fetch_firewall" }), fetchRoutesTimer.restart(), fetchProfileTimer.restart(), fetchStatsTimer.restart() ) : null } } + Timer { + id: meowRelayWatchdogRoutes + interval: 10000 + repeat: false + onTriggered: { + console.error("MeowRelay unreachable!") //ваша няшка умерла + timeouted = true + refreshBlocked = false + Plasmoid.configuration.OnErrorRefresh ? ( + meowRelayWatchdogRoutes.start(), + isLoadingItems += Plasmoid.configuration.BIrefresh, + //isBlocked = true, + superWorker.sendMessage({ "action": "fetch_routes" }), + fetchRoutesTimer.restart() + ) : null + } + } + Timer { + id: meowRelayWatchdogProfile + interval: 10000 + repeat: false + onTriggered: { + console.error("MeowRelay unreachable!") //ваша няшка умерла + timeouted = true + refreshBlocked = false + Plasmoid.configuration.OnErrorRefresh ? ( + meowRelayWatchdogProfile.start(), + isLoadingItems += Plasmoid.configuration.BIrefresh, + //isBlocked = true, + superWorker.sendMessage({ "action": "fetch_profile" }), + fetchProfileTimer.restart() + ) : null + } + } Timer { id: meowRelayWatchdogStats interval: 10000 repeat: false onTriggered: { console.error("MeowRelay unreachable!") //ваша няшка умерла + timeouted = true refreshBlocked = false + Plasmoid.configuration.OnErrorRefresh ? ( + meowRelayWatchdogStats.start(), + isLoadingItems += Plasmoid.configuration.BIrefresh, + //isBlocked = true, + superWorker.sendMessage({ "action": "fetch_stats" }), + fetchStatsTimer.restart() + ) : null } } Timer { @@ -160,7 +234,9 @@ PlasmoidItem { if (Plasmoid.configuration.BIrefresh || Plasmoid.configuration.BIautoupdate) { isLoadingItems -= 1 }; + meowRelayWatchdogRoutes.stop(); refreshBlocked = false; + timeouted = false; }, "fetch_stats": ({ data }) => { statsModel.clear(); @@ -187,11 +263,13 @@ PlasmoidItem { }; meowRelayWatchdogStats.stop(); refreshBlocked = false; + timeouted = false; }, "fetch_profile": ({ data }) => { profileModel.clear(); devicesModel.clear(); featuresModel.clear(); + ownedlocalipsModel.clear(); profileData = data; for (let i = 0; i < data.length; i++) { profileModel.append(data[i]); @@ -199,13 +277,24 @@ PlasmoidItem { for (let i = 0; i < data.devices.length; i++) { devicesModel.append(data.devices[i]) } + if (Array.isArray(data.devices)) { + data.devices.forEach(function(device) { + ownedlocalipsModel.append({ "ip": device.ip }); + }); + } for (let i = 0; i < data.features.length; i++) { featuresModel.append(data.features[i]) + if (data.features[i].name == "FEATURE_PORTCONFIGURATOR") { + firewallEnabled = data.features[i].value + tabs.setProperty(2, "visible", firewallEnabled) + } } if (Plasmoid.configuration.BIrefresh || Plasmoid.configuration.BIautoupdate) { isLoadingItems -= 1 } meowRelayWatchdog.stop(); + meowRelayWatchdogProfile.stop(); + timeouted = false; isBlocked = false; refreshBlocked = false; countriesBlocked = false; @@ -214,6 +303,37 @@ PlasmoidItem { devicesBlocked = false; featuresBlocked = false; }, + "fetch_firewall": ({ data }) => { + firewallModel.clear(); + ownedipsModel.clear(); + firewallData = data; + if (data.state === 0 && data.own && Array.isArray(data.own)) { + data.own.forEach(function(ip) { + ownedipsModel.append({ ip }); + }); + } + if (data.state === 0 && data.rules) { + Object.keys(data.rules).forEach(function(ipKey) { + var rulesArray = data.rules[ipKey]; + + rulesArray.forEach(function(rule) { + firewallModel.append({ + "ip": ipKey, + "dst": rule.dst, + "port": rule.port, + "local_port": rule.local_port, + "proto": rule.proto, + "packets": (rule.packets ? rule.packets : 0), + "bytes": (rule.bytes ? rule.bytes : 0) + }); + }); + }); + } + firewallBlocked = false; + if (Plasmoid.configuration.BIpostfirewall) { + isLoadingItems -= 1 + }; + }, "post_country": ({ status }) => { if (status === 200) { superWorker.sendMessage({ "action": "fetch_profile" }) @@ -286,6 +406,18 @@ PlasmoidItem { if (Plasmoid.configuration.BIpostcountry) { isLoadingItems -= 1 } + }, + "post_ruleadd": ({ status }) => { + superWorker.sendMessage({ "action": "fetch_firewall" }); + if (Plasmoid.configuration.BIpostfirewall) { + isLoadingItems -= 1 + } + }, + "post_ruledrop": ({ status }) => { + superWorker.sendMessage({ "action": "fetch_firewall" }); + if (Plasmoid.configuration.BIpostfirewall) { + isLoadingItems -= 1 + } } }) @@ -304,10 +436,11 @@ PlasmoidItem { superWorker.sendMessage({ "action": "fetch_routes" }) superWorker.sendMessage({ "action": "fetch_stats" }) superWorker.sendMessage({ "action": "fetch_profile" }) + superWorker.sendMessage({ "action": "fetch_firewall" }) fetchRoutesTimer.start() fetchStatsTimer.start() fetchProfileTimer.start() - meowRelayPing.start() + // meowRelayPing.start() } // autoupdate fetch @@ -318,8 +451,9 @@ PlasmoidItem { repeat: true triggeredOnStart: false onTriggered: { - refreshBlocked = true + //refreshBlocked = true isLoadingItems += Plasmoid.configuration.BIautoupdate + meowRelayWatchdogRoutes.start() superWorker.sendMessage({ "action": "fetch_routes" }) } } @@ -330,7 +464,7 @@ PlasmoidItem { repeat: true triggeredOnStart: false onTriggered: { - refreshBlocked = true + //refreshBlocked = true isLoadingItems += Plasmoid.configuration.BIautoupdate meowRelayWatchdogStats.start() superWorker.sendMessage({ "action": "fetch_stats" }) @@ -343,109 +477,14 @@ PlasmoidItem { repeat: true triggeredOnStart: false onTriggered: { - refreshBlocked = true + //refreshBlocked = true isLoadingItems += Plasmoid.configuration.BIautoupdate - meowRelayWatchdog.start() + meowRelayWatchdogProfile.start() superWorker.sendMessage({ "action": "fetch_profile" }) } } - compactRepresentation: Item { - Layout.minimumWidth: govnoLayout.implicitWidth - Layout.minimumHeight: Kirigami.Units.iconSizes.medium - - Layout.maximumWidth: govnoLayout.implicitWidth - Layout.maximumHeight: Layout.minimumHeight - - Layout.preferredWidth: govnoLayout.implicitWidth - Layout.preferredHeight: Layout.minimumHeight - - MouseArea { - id: mouseArea - //property bool wasExpanded - - Accessible.name: Plasmoid.title - Accessible.role: Accessible.Button - //Accessible.description: desc - - //onPressed: wasExpanded = root.expanded - //onClicked: root.expanded = !wasExpanded - - onClicked: Plasmoid.activated(); - - Keys.onPressed: { - switch (event.key) { - case Qt.Key_Space: - case Qt.Key_Enter: - case Qt.Key_Return: - case Qt.Key_Select: - Plasmoid.activated(); - break; - } - } - - // иконки не будет без этой залупы - anchors.fill: parent - anchors.leftMargin: 0 - - activeFocusOnTab: true - hoverEnabled: true - - Component { - id: meowIconComponent - Kirigami.Icon { - Layout.preferredWidth: Kirigami.Units.gridUnit * 1.5 - Layout.preferredHeight: Kirigami.Units.gridUnit * 1.5 - Layout.alignment: Qt.AlignVCenter - source: Qt.resolvedUrl("../images/dogfood.svg") - isMask: true - active: mouseArea.containsMouse - } - } - Component { - id: genericLabelComponent - Label { - property string labelText: "yaebalrot" - text: labelText === "yaebalrot" ? "" : labelText - color: text === "error" ? "red" : Kirigami.Theme.textColor - font.pointSize: Kirigami.Theme.mediumFont.pointSize - verticalAlignment: Text.AlignVCenter - } - } - - property var configArray: Plasmoid.configuration.ExtraLayout === true ? Plasmoid.configuration.StringLayoutPlacement.split(" ") : ["meow_icon"] - RowLayout { - id: govnoLayout - anchors.fill: parent - spacing: Kirigami.Units.smallSpacing - - Repeater { - model: mouseArea.configArray - - Loader { - id: govnoebanoe - // Logic to choose WHICH component to spawn - sourceComponent: (modelData === "meow_icon") ? meowIconComponent : genericLabelComponent - - // Logic to pass DATA to that component - Binding { - target: govnoebanoe.item // The Label we just created - property: "labelText" // The property on the Label - value: root[modelData] // The live variable in root - when: govnoebanoe.status === Loader.Ready && modelData !== "meow_icon" - } - Layout.fillHeight: true - } - } - } - // Kirigami.Icon { - // anchors.fill: parent - // source: Qt.resolvedUrl("../images/dogfood.svg") - // isMask: true - // active: mouseArea.containsMouse - // } - } - } + compactRepresentation: Compact {} fullRepresentation: PlasmaExtras.Representation { //id: fullRoot @@ -456,6 +495,11 @@ PlasmoidItem { //spacing: 0 header: PlasmaExtras.PlasmoidHeading { + // Make this toolbar's buttons align vertically with the ones above + rightPadding: -1 + // Allow tabbar to touch the header's bottom border + bottomPadding: -bottomInset + contentItem: ColumnLayout { spacing: Kirigami.Units.smallSpacing @@ -520,9 +564,11 @@ PlasmoidItem { Layout.fillWidth: true Repeater { - model: ["Route", "Devices", "Stats", "Features"] + model: tabs delegate: PlasmaComponents.TabButton { - text: modelData + text: model.name + visible: model.visible + width: firewallEnabled ? (mainTabBar.width / 5) : ((model.name == "Firewall") ? 0 : (mainTabBar.width / 4)) // это пизда или нармалды } } } @@ -542,6 +588,114 @@ PlasmoidItem { Layout.fillWidth: true } + // Tab 3 util buttons + RowLayout { + spacing: Kirigami.Units.smallSpacing + visible: (mainTabBar.currentIndex === 2 && firewallAccess && firewallEnabled) + PlasmaComponents.ComboBox { + id: firewallProto + model: ["tcp", "udp"] + currentIndex: 0 + } + Kirigami.ContextualHelpButton { + toolTipText: i18n( + 'All ' + firewallProto.currentValue + ' traffic from ' + firewallIp.currentValue + ':' + firewallPortSpinner.value + ' will be forwarded to ' + firewallLocalIp.currentValue + ':' + firewallLocalPortSpinner.value + ) + } + Item { + Layout.fillWidth: true + } + PlasmaComponents.Button { + text: "Update" + icon.name: "view-refresh" + enabled: !(isBlocked) + onClicked: { + isLoadingItems += Plasmoid.configuration.BIpostfirewall + firewallBlocked = true + superWorker.sendMessage({ "action": "fetch_firewall" }) + } + } + PlasmaComponents.Button { + text: "Add" + enabled: !(isBlocked || firewallBlocked) + onClicked: { + isLoadingItems += Plasmoid.configuration.BIpostfirewall + firewallBlocked = true + superWorker.sendMessage({ "action": "post_ruleadd", "dst": firewallLocalIp.currentValue, "ext": firewallIp.currentValue, "local_port": firewallLocalPortSpinner.value, "port": firewallPortSpinner.value, "proto": firewallProto.currentValue }) + } + } + } + RowLayout { + spacing: Kirigami.Units.smallSpacing + visible: (mainTabBar.currentIndex === 2 && firewallAccess && firewallEnabled) + PlasmaComponents.ComboBox { + id: firewallIp + model: ownedipsModel + currentIndex: 0 + Connections { + target: ownedipsModel + + function onCountChanged() { + if (ownedipsModel.count > 0) { + firewallIp.currentIndex = 0; + } + } + } + } + PlasmaComponents.Label { + text: ":" + font.bold: true + } + SpinBox { + id: firewallPortSpinner + from: 1 + to: 65535 + value: 1 + } + Item { + Layout.fillWidth: true + } + PlasmaComponents.Label { + text: "→" + font.bold: true + } + Item { + Layout.fillWidth: true + } + PlasmaComponents.ComboBox { + id: firewallLocalIp + model: ownedlocalipsModel + currentIndex: 0 + Connections { + target: ownedlocalipsModel + + function onCountChanged() { + if (ownedlocalipsModel.count > 0) { + firewallLocalIp.currentIndex = 0; + } + } + } + } + PlasmaComponents.Label { + text: ":" + font.bold: true + } + SpinBox { + id: firewallLocalPortSpinner + from: 1 + to: 65535 + value: 1 + } + } + Rectangle { + visible: (mainTabBar.currentIndex === 2 && firewallAccess && firewallEnabled) + height: 1 + Layout.alignment: AlignVCenter + Layout.fillWidth: true + color: PlasmaCore.Theme.dividerColor + opacity: 0.2 + } + RowLayout { spacing: Kirigami.Units.smallSpacing PlasmaComponents.Label { @@ -617,6 +771,7 @@ PlasmoidItem { superWorker.sendMessage({ "action": "fetch_routes" }) superWorker.sendMessage({ "action": "fetch_profile" }) superWorker.sendMessage({ "action": "fetch_stats" }) + superWorker.sendMessage({ "action": "fetch_firewall" }) fetchRoutesTimer.restart() fetchProfileTimer.restart() fetchStatsTimer.restart() @@ -629,6 +784,20 @@ PlasmoidItem { implicitWidth: 28 implicitHeight: 28 } + Kirigami.Icon { + source: "network-limited-symbolic" + visible: timeouted + implicitWidth: 30 + implicitHeight: 30 + //isMask: true + //active: mouseArea.containsMouse + } + Kirigami.ContextualHelpButton { + visible: timeouted + toolTipText: i18n( + "MeowRelay unreachable! (timeout)" + ) + } Item { Layout.fillWidth: true } @@ -674,411 +843,555 @@ PlasmoidItem { currentIndex: mainTabBar.currentIndex // Tab 1 Routes - PlasmaComponents.ScrollView { - id: routeScroll - anchors.fill: parent - ScrollBar.vertical.policy: scrollbartype - - Column { - id: listContainer - width: routeScroll.availableWidth - spacing: 0 - - // Countries - Repeater { - model: routeModel - delegate: PlasmaComponents.RadioDelegate { - width: listContainer.width - height: Kirigami.Units.gridUnit * 3 - enabled: !countriesBlocked - checked: (model.code === current && model.provider === provider) - - contentItem: RowLayout { - spacing: Kirigami.Units.largeSpacing - PlasmaComponents.Label { - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - font.pixelSize: 24 - text: getFlag(model.code) - } - - ColumnLayout { - spacing: 0 - Layout.fillWidth: true - PlasmaComponents.Label { - text: model.name - font.bold: true - Layout.fillWidth: true - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - PlasmaComponents.Label { - text: model.provider - font.pointSize: Kirigami.Theme.smallFont.pointSize - opacity: 0.7 - Layout.fillWidth: true - verticalAlignment: Text.AlignVCenter - } - } - } - onClicked: { - isLoadingItems += Plasmoid.configuration.BIpostcountry - superWorker.sendMessage({ "action": "post_country", "code": model.code, "provider": model.provider }) - } - } - } + Item { + PlasmaExtras.PlaceholderMessage { + anchors.centerIn: parent + text: "Connecting to\nMeowRelay" + iconName: "akregator-symbolic" + visible: routesData === null } - } + PlasmaComponents.ScrollView { + id: routeScroll + anchors.fill: parent + ScrollBar.vertical.policy: scrollbartype - PlasmaComponents.ScrollView { - id: devicesScroll - anchors.fill: parent - ScrollBar.vertical.policy: scrollbartype + Column { + id: listContainer + width: routeScroll.availableWidth + spacing: 0 - Column { - id: deviceListContainer - width: devicesScroll.availableWidth - spacing: Kirigami.Units.smallSpacing - Item { - width: deviceListContainer.width - height: Kirigami.Units.smallSpacing/2 - } + // countries + Repeater { + model: routeModel + delegate: PlasmaComponents.RadioDelegate { + width: listContainer.width + height: Kirigami.Units.gridUnit * 3 + enabled: !countriesBlocked + checked: (model.code === current && model.provider === provider) - // devices - Repeater { - model: devicesModel - delegate: PlasmaComponents.Button { - id: devicesButton - width: deviceListContainer.width - height: Kirigami.Units.gridUnit * 4 - - contentItem: RowLayout { - x: Kirigami.Units.largeSpacing - y: Kirigami.Units.largeSpacing - width: devicesButton.availableWidth - spacing: Kirigami.Units.smallSpacing - ColumnLayout { - spacing: 0 - RowLayout { - Layout.fillWidth: true - PlasmaComponents.Label { - text: model.name - font.bold: true - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - PlasmaComponents.Label { - text: "(LOSS 80%)" - color: "#f38ba8" - visible: model.ebalka - Layout.fillWidth: true - } - } - RowLayout { - PlasmaComponents.Label { - text: model.ip - font.pointSize: Kirigami.Theme.smallFont.pointSize - opacity: 0.7 - verticalAlignment: Text.AlignVCenter - } - PlasmaComponents.Label { - text: "↓" - color: "#00ff00" - } - PlasmaComponents.Label { - text: formatTraffic(model.rx) - font.pointSize: Kirigami.Theme.smallFont.pointSize - verticalAlignment: Text.AlignVCenter - } - PlasmaComponents.Label { - text: "↑" - color: "#00aaff" - } - PlasmaComponents.Label { - text: formatTraffic(model.tx) - font.pointSize: Kirigami.Theme.smallFont.pointSize - verticalAlignment: Text.AlignVCenter - } - } - RowLayout { - PlasmaComponents.Label { - text: getFlag(model.country) - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - PlasmaComponents.Label { - text: model.country + " " + model.provider - opacity: 0.7 - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - PlasmaComponents.Label { - text: (model.online ? "online" : "offline") - color: (model.online ? "#a6e3a1" : "#f38ba8") - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - } - } - Item { - Layout.fillWidth: true - } - ColumnLayout { - RowLayout { - Item { - Layout.fillWidth: true - } - PlasmaComponents.Switch { - text: "80% loss" - checked: model.ebalka - enabled: !(isBlocked || devicesBlocked) - onClicked: { - isLoadingItems += Plasmoid.configuration.BIdevicesupdate - devicesBlocked = true - superWorker.sendMessage({ "action": "post_ebalka", "bool": !model.ebalka, "ip": model.ip }) - // ebalka = !ebalka - } - } - } - RowLayout { - Item { - Layout.fillWidth: true - } - PlasmaComponents.Button { - id: menuButton - text: "Rename" - PlasmaComponents.Menu { - id: inputMenu - padding: Kirigami.Units.largeSpacing - - function confirmAction() { - superWorker.sendMessage({ "action": "post_name", "ip": model.ip, "name": textInput.text }) - inputMenu.close(); - textInput.text = ""; - } - - ColumnLayout { - spacing: Kirigami.Units.smallSpacing - RowLayout { - PlasmaComponents.Label { - text: "Enter new name:" - font.pixelSize: Kirigami.Units.gridUnit * 0.8 - } - } - RowLayout { - PlasmaComponents.TextField { - id: textInput - placeholderText: (model.name).split(" ").slice(2).join(" ") - focus: true - onAccepted: inputMenu.confirmAction() - } - PlasmaComponents.Button { - icon.name: "dialog-ok-apply" - onClicked: inputMenu.confirmAction() - } - } - } - } - - onClicked: { - inputMenu.open(); - textInput.forceActiveFocus(); - } - } - } - } - } - //onClicked: { - //} - } - } - Item { - width: deviceListContainer.width - height: Kirigami.Units.smallSpacing/2 - } - } - } - - PlasmaComponents.ScrollView { - id: statsScroll - anchors.fill: parent - ScrollBar.vertical.policy: scrollbartype - - Column { - id: statsListContainer - width: statsScroll.availableWidth - spacing: Kirigami.Units.smallSpacing - Item { - width: statsListContainer.width - height: Kirigami.Units.smallSpacing/2 - } - - // stats - Repeater { - model: statsModel - delegate: PlasmaComponents.Button { - id: statsButton - width: statsListContainer.width - height: Kirigami.Units.gridUnit * 4 - enabled: !(isBlocked || countriesBlocked) - - contentItem: RowLayout { - x: Kirigami.Units.largeSpacing - y: Kirigami.Units.largeSpacing - width: statsButton.availableWidth - spacing: Kirigami.Units.smallSpacing - ColumnLayout { - spacing: 0 - RowLayout { - Layout.fillWidth: true - PlasmaComponents.Label { - text: getFlag(model.country) - elide: Text.ElideRight - font.pixelSize: 16 - verticalAlignment: Text.AlignVCenter - } - PlasmaComponents.Label { - text: model.name - font.bold: true - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - } + contentItem: RowLayout { + spacing: Kirigami.Units.largeSpacing PlasmaComponents.Label { - text: "Up: " + (Math.floor(model.uptime / 86400)) + "d " + (Math.floor((model.uptime % 86400) / 3600)) + "h " + (Math.floor(((model.uptime % 86400) % 3600) / 60)) + "m" - } - } - Item { - Layout.fillWidth: true - } - ColumnLayout { - spacing: 0 - RowLayout { - Item { - Layout.fillWidth: true - } - PlasmaComponents.Label { - text: model.connected + " peers (online: " + model.online + ")" - color: "#a6e3a1" - font.bold: true - } - } - RowLayout { - Item { - Layout.fillWidth: true - } - PlasmaComponents.Label { - text: "↓" - color: "#00ff00" - } - PlasmaComponents.Label { - text: formatTraffic(model.trafficString.split(" ")[0]) - } - PlasmaComponents.Label { - text: "(" + formatTraffic(model.speedString.split(" ")[0]) + ")" - opacity: 0.7 - } - PlasmaComponents.Label { - text: "↑" - color: "#00aaff" - } - PlasmaComponents.Label { - text: formatTraffic(model.trafficString.split(" ")[1]) - } - PlasmaComponents.Label { - text: "(" + formatTraffic(model.speedString.split(" ")[1]) + ")" - opacity: 0.7 - } - } - RowLayout { - Item { - Layout.fillWidth: true - } - PlasmaComponents.Label { - text: model.pingString.split(" ").join(", ") + " ms" - color: "#a6e3a1" - font.bold: true - } - } - } - } - onClicked: { - isLoadingItems += Plasmoid.configuration.BIpostcountry - superWorker.sendMessage({ "action": "post_idconnect", "iface": model.name }) - } - } - } - Item { - width: statsListContainer.width - height: Kirigami.Units.smallSpacing/2 - } - } - } - - PlasmaComponents.ScrollView { - id: featuresScroll - anchors.fill: parent - ScrollBar.vertical.policy: scrollbartype - - Column { - id: featuresListContainer - width: featuresScroll.availableWidth - spacing: Kirigami.Units.smallSpacing - Item { - width: featuresListContainer.width - height: Kirigami.Units.smallSpacing/2 - } - - // features - Repeater { - model: featuresModel - delegate: PlasmaComponents.Button { - id: featuresButton - width: featuresListContainer.width - height: Kirigami.Units.gridUnit * 4 - - contentItem: RowLayout { - x: Kirigami.Units.largeSpacing - y: Kirigami.Units.largeSpacing - width: featuresButton.availableWidth - spacing: Kirigami.Units.smallSpacing - ColumnLayout { - spacing: 2 - Layout.fillWidth: true - PlasmaComponents.Label { - text: model.name - elide: Text.ElideRight - //font.pixelSize: 16 + horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter - } - PlasmaComponents.Label { - text: model.desc wrapMode: Text.Wrap - opacity: 0.7 - //font.bold: true - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter + font.pixelSize: 24 + text: getFlag(model.code) + } + + ColumnLayout { + spacing: 0 Layout.fillWidth: true + PlasmaComponents.Label { + text: model.name + font.bold: true + Layout.fillWidth: true + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + PlasmaComponents.Label { + text: model.provider + font.pointSize: Kirigami.Theme.smallFont.pointSize + opacity: 0.7 + Layout.fillWidth: true + verticalAlignment: Text.AlignVCenter + } } } - PlasmaComponents.Switch { - checked: model.value - enabled: !(isBlocked || featuresBlocked) - onClicked: { - isLoadingItems += Plasmoid.configuration.BIfeaturesupdate - //featuresBlocked = true - superWorker.sendMessage({ "action": "post_feature", "name": model.name, "value": !model.value }) + onClicked: { + isLoadingItems += Plasmoid.configuration.BIpostcountry + superWorker.sendMessage({ "action": "post_country", "code": model.code, "provider": model.provider }) + } + } + } + } + } + } + + // Tab 2 Devices + Item { + PlasmaExtras.PlaceholderMessage { + anchors.centerIn: parent + text: "Connecting to\nMeowRelay" + iconName: "akregator-symbolic" + visible: devices === null + } + PlasmaComponents.ScrollView { + id: devicesScroll + anchors.fill: parent + ScrollBar.vertical.policy: scrollbartype + + Column { + id: deviceListContainer + width: devicesScroll.availableWidth + spacing: Kirigami.Units.smallSpacing + Item { + width: deviceListContainer.width + height: Kirigami.Units.smallSpacing/2 + } + + // devices + Repeater { + model: devicesModel + delegate: PlasmaComponents.Button { + id: devicesButton + width: deviceListContainer.width + height: Kirigami.Units.gridUnit * 4 + + contentItem: RowLayout { + x: Kirigami.Units.largeSpacing + y: Kirigami.Units.largeSpacing + width: devicesButton.availableWidth + spacing: Kirigami.Units.smallSpacing + ColumnLayout { + spacing: 0 + RowLayout { + Layout.fillWidth: true + PlasmaComponents.Label { + text: model.name + font.bold: true + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + PlasmaComponents.Label { + text: "(LOSS 80%)" + color: "#f38ba8" + visible: model.ebalka + Layout.fillWidth: true + } + } + RowLayout { + PlasmaComponents.Label { + text: model.ip + //opacity: 0.7 + verticalAlignment: Text.AlignVCenter + } + PlasmaComponents.Label { + text: "↓" + color: "#00ff00" + } + PlasmaComponents.Label { + text: formatTraffic(model.rx) + verticalAlignment: Text.AlignVCenter + } + PlasmaComponents.Label { + text: "↑" + color: "#00aaff" + } + PlasmaComponents.Label { + text: formatTraffic(model.tx) + verticalAlignment: Text.AlignVCenter + } + } + RowLayout { + PlasmaComponents.Label { + text: getFlag(model.country) + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + PlasmaComponents.Label { + text: model.country + " " + model.provider + opacity: 0.7 + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + PlasmaComponents.Label { + text: (model.online ? "online" : "offline") + color: (model.online ? "#a6e3a1" : "#f38ba8") + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + } + } + Item { + Layout.fillWidth: true + } + ColumnLayout { + RowLayout { + Item { + Layout.fillWidth: true + } + PlasmaComponents.Switch { + text: "80% loss" + checked: model.ebalka + enabled: !(isBlocked || devicesBlocked) + onClicked: { + isLoadingItems += Plasmoid.configuration.BIdevicesupdate + devicesBlocked = true + superWorker.sendMessage({ "action": "post_ebalka", "bool": !model.ebalka, "ip": model.ip }) + // ebalka = !ebalka + } + } + } + RowLayout { + Item { + Layout.fillWidth: true + } + PlasmaComponents.Button { + id: menuButton + text: "Rename" + enabled: !(isBlocked || devicesBlocked) + PlasmaComponents.Menu { + id: inputMenu + padding: Kirigami.Units.largeSpacing + + function confirmAction() { + superWorker.sendMessage({ "action": "post_name", "ip": model.ip, "name": textInput.text }) + inputMenu.close(); + textInput.text = ""; + } + + ColumnLayout { + spacing: Kirigami.Units.smallSpacing + RowLayout { + PlasmaComponents.Label { + text: "Enter new name:" + font.pixelSize: Kirigami.Units.gridUnit * 0.8 + } + } + RowLayout { + PlasmaComponents.TextField { + id: textInput + placeholderText: (model.name).split(" ").slice(2).join(" ") + focus: true + onAccepted: inputMenu.confirmAction() + } + PlasmaComponents.Button { + icon.name: "dialog-ok-apply" + onClicked: inputMenu.confirmAction() + } + } + } + } + + onClicked: { + inputMenu.open(); + textInput.forceActiveFocus(); + } + } + } + } + } + //onClicked: { + //} + } + } + Item { + width: deviceListContainer.width + height: Kirigami.Units.smallSpacing/2 + } + } + } + } + + // Tab 3 Firewall + Item { + PlasmaExtras.PlaceholderMessage { + anchors.centerIn: parent + text: "Connecting to\nMeowRelay" + iconName: "akregator-symbolic" + visible: firewallData === null + } + PlasmaExtras.PlaceholderMessage { + anchors.centerIn: parent + text: "Nothing to see here" + iconName: "face-sad" + visible: (!firewallAccess && firewallData) + } + PlasmaComponents.ScrollView { + id: firewallScroll + anchors.fill: parent + ScrollBar.vertical.policy: scrollbartype + + Column { + id: firewallListContainer + width: firewallScroll.availableWidth + spacing: Kirigami.Units.smallSpacing + Item { + width: firewallListContainer.width + height: Kirigami.Units.smallSpacing/2 + } + + // firewall + Repeater { + model: firewallModel + delegate: PlasmaComponents.Button { + id: firewallButton + width: firewallListContainer.width + height: Kirigami.Units.gridUnit * 4 + + contentItem: RowLayout { + x: Kirigami.Units.largeSpacing + y: Kirigami.Units.largeSpacing + width: firewallButton.availableWidth + spacing: Kirigami.Units.smallSpacing + ColumnLayout { + spacing: 0 + RowLayout { + Layout.fillWidth: true + PlasmaComponents.Label { + text: model.ip + ":" + model.port + font.bold: true + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + PlasmaComponents.Label { + text: "→" + font.bold: true + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + PlasmaComponents.Label { + text: model.dst + ":" + model.local_port + font.bold: true + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + PlasmaComponents.Label { + text: "(" + model.proto + ")" + opacity: 0.7 + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + } + RowLayout { + Layout.fillWidth: true + PlasmaComponents.Label { + text: model.packets + " pcks /" + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + PlasmaComponents.Label { + text: formatTraffic(model.bytes) + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + } + } + Item { + Layout.fillWidth: true + } + ColumnLayout { + spacing: 0 + PlasmaComponents.Button { + id: dropFirewallButton + text: "Drop" + enabled: !(isBlocked || firewallBlocked) + onClicked: { + isLoadingItems += Plasmoid.configuration.BIpostfirewall + firewallBlocked = true + superWorker.sendMessage({ "action": "post_ruledrop", "dst": model.dst, "ext": model.ip, "local_port": model.local_port, "port": model.port, "proto": model.proto }) + } + } } } } - //onClicked: { - //} + } + Item { + width: firewallListContainer.width + height: Kirigami.Units.smallSpacing/2 } } - Item { - width: featuresListContainer.width - height: Kirigami.Units.smallSpacing/2 + } + } + + // Tab 4 Stats + Item { + PlasmaExtras.PlaceholderMessage { + anchors.centerIn: parent + text: "Connecting to\nMeowRelay" + iconName: "akregator-symbolic" + visible: statsData === null + } + PlasmaComponents.ScrollView { + id: statsScroll + anchors.fill: parent + ScrollBar.vertical.policy: scrollbartype + + Column { + id: statsListContainer + width: statsScroll.availableWidth + spacing: Kirigami.Units.smallSpacing + Item { + width: statsListContainer.width + height: Kirigami.Units.smallSpacing/2 + } + + // stats + Repeater { + model: statsModel + delegate: PlasmaComponents.Button { + id: statsButton + width: statsListContainer.width + height: Kirigami.Units.gridUnit * 4 + enabled: !(isBlocked || countriesBlocked) + + contentItem: RowLayout { + x: Kirigami.Units.largeSpacing + y: Kirigami.Units.largeSpacing + width: statsButton.availableWidth + spacing: Kirigami.Units.smallSpacing + ColumnLayout { + spacing: 0 + RowLayout { + Layout.fillWidth: true + PlasmaComponents.Label { + text: getFlag(model.country) + elide: Text.ElideRight + font.pixelSize: 16 + verticalAlignment: Text.AlignVCenter + } + PlasmaComponents.Label { + text: model.name + font.bold: true + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + } + PlasmaComponents.Label { + text: "Up: " + (Math.floor(model.uptime / 86400)) + "d " + (Math.floor((model.uptime % 86400) / 3600)) + "h " + (Math.floor(((model.uptime % 86400) % 3600) / 60)) + "m" + } + } + Item { + Layout.fillWidth: true + } + ColumnLayout { + spacing: 0 + RowLayout { + Item { + Layout.fillWidth: true + } + PlasmaComponents.Label { + text: model.connected + " peers (online: " + model.online + ")" + color: "#a6e3a1" + font.bold: true + } + } + RowLayout { + Item { + Layout.fillWidth: true + } + PlasmaComponents.Label { + text: "↓" + color: "#00ff00" + } + PlasmaComponents.Label { + text: formatTraffic(model.trafficString.split(" ")[0]) + } + PlasmaComponents.Label { + text: "(" + formatTraffic(model.speedString.split(" ")[0]) + ")" + opacity: 0.7 + } + PlasmaComponents.Label { + text: "↑" + color: "#00aaff" + } + PlasmaComponents.Label { + text: formatTraffic(model.trafficString.split(" ")[1]) + } + PlasmaComponents.Label { + text: "(" + formatTraffic(model.speedString.split(" ")[1]) + ")" + opacity: 0.7 + } + } + RowLayout { + Item { + Layout.fillWidth: true + } + PlasmaComponents.Label { + text: model.pingString.split(" ").join(", ") + " ms" + color: "#a6e3a1" + font.bold: true + } + } + } + } + onClicked: { + isLoadingItems += Plasmoid.configuration.BIpostcountry + superWorker.sendMessage({ "action": "post_idconnect", "iface": model.name }) + } + } + } + Item { + width: statsListContainer.width + height: Kirigami.Units.smallSpacing/2 + } + } + } + } + + // Tab 5 Features + Item { + PlasmaExtras.PlaceholderMessage { + anchors.centerIn: parent + text: "Connecting to\nMeowRelay" + iconName: "akregator-symbolic" + visible: features === null + } + PlasmaComponents.ScrollView { + id: featuresScroll + anchors.fill: parent + ScrollBar.vertical.policy: scrollbartype + + Column { + id: featuresListContainer + width: featuresScroll.availableWidth + spacing: Kirigami.Units.smallSpacing + Item { + width: featuresListContainer.width + height: Kirigami.Units.smallSpacing/2 + } + + // features + Repeater { + model: featuresModel + delegate: PlasmaComponents.Button { + id: featuresButton + width: featuresListContainer.width + height: Kirigami.Units.gridUnit * 4 + + contentItem: RowLayout { + x: Kirigami.Units.largeSpacing + y: Kirigami.Units.largeSpacing + width: featuresButton.availableWidth + spacing: Kirigami.Units.smallSpacing + ColumnLayout { + spacing: 2 + Layout.fillWidth: true + PlasmaComponents.Label { + text: model.name + elide: Text.ElideRight + //font.pixelSize: 16 + verticalAlignment: Text.AlignVCenter + } + PlasmaComponents.Label { + text: model.desc + wrapMode: Text.Wrap + opacity: 0.7 + //font.bold: true + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + Layout.fillWidth: true + } + } + PlasmaComponents.Switch { + checked: model.value + enabled: !(isBlocked || featuresBlocked) + onClicked: { + isLoadingItems += Plasmoid.configuration.BIfeaturesupdate + //featuresBlocked = true + superWorker.sendMessage({ "action": "post_feature", "name": model.name, "value": !model.value }) + } + } + } + //onClicked: { + //} + } + } + Item { + width: featuresListContainer.width + height: Kirigami.Units.smallSpacing/2 + } } } } diff --git a/com.umorist47.meowrelaygui/contents/ui/worker.mjs b/com.umorist47.meowrelaygui/contents/ui/worker.mjs index f4c9a57..c3753d7 100644 --- a/com.umorist47.meowrelaygui/contents/ui/worker.mjs +++ b/com.umorist47.meowrelaygui/contents/ui/worker.mjs @@ -134,6 +134,30 @@ function fetchStats() { xhr.send(); } +// GET firewall +function fetchFirewall() { + + let xhr = getXhr(); + + if (!xhr) { + console.log("Pool full, retrying..."); + setTimeout(() => fetchFirewall(), 100); + return; + } + xhr.open("GET", "http://10.7.0.1:7777/meowrok/get"); + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + if (xhr.status === 200) { + let response = JSON.parse(xhr.responseText); + WorkerScript.sendMessage({ "action": "fetch_firewall", "data": response }); + } else { + console.error("Failed to fetch firewall:", xhr.status); + } + } + } + xhr.send(); +} + // POST select country function selectCountry(code, provider) { //isLoading = true; @@ -336,17 +360,94 @@ function connectById(iface) { xhr.send(data); } +// POST aff rule to firewall +function firewallAddRule(dst, ext, local_port, port, proto) { + //isLoading = true; + + let xhr = getXhr(); + + if (!xhr) { + console.log("Pool full, retrying..."); + setTimeout(() => firewallAddRule(dst, ext, local_port, port, proto), 100); + return; + } + xhr.open("POST", "http://10.7.0.1:7777/meowrok/add"); + xhr.setRequestHeader("Content-Type", "application/json"); + + var data = JSON.stringify({ + "dst": dst, + "ext": ext, + "local_port": local_port, + "port": port, + "proto": proto + }); + + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + //isLoading = false; + if (xhr.status === 200) { + console.log("Success: rule added!"); + WorkerScript.sendMessage({ "action": "post_ruleadd", "status": xhr.status}); + } else { + console.error("POST failed:", xhr.status); + WorkerScript.sendMessage({ "action": "post_ruleadd", "status": xhr.status}); + } + } + } + xhr.send(data); +} + +// POST drop rule from firewall +function firewallDropRule(dst, ext, local_port, port, proto) { + //isLoading = true; + + let xhr = getXhr(); + + if (!xhr) { + console.log("Pool full, retrying..."); + setTimeout(() => firewallDropRule(dst, ext, local_port, port, proto), 100); + return; + } + xhr.open("POST", "http://10.7.0.1:7777/meowrok/drop"); + xhr.setRequestHeader("Content-Type", "application/json"); + + var data = JSON.stringify({ + "dst": dst, + "ext": ext, + "local_port": local_port, + "port": port, + "proto": proto + }); + + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + //isLoading = false; + if (xhr.status === 200) { + console.log("Success: rule dropped!"); + WorkerScript.sendMessage({ "action": "post_ruledrop", "status": xhr.status}); + } else { + console.error("POST failed:", xhr.status); + WorkerScript.sendMessage({ "action": "post_ruledrop", "status": xhr.status}); + } + } + } + xhr.send(data); +} + WorkerScript.onMessage = function(message) { const actions = { "fetch_routes": () => fetchRoutes(message), "fetch_stats": () => fetchStats(message), "fetch_profile": () => fetchProfile(message), + "fetch_firewall": () => fetchFirewall(message), "post_country": () => selectCountry(message.code, message.provider), "post_share": () => switchShare(message.bool), "post_ebalka": () => switchEbalka(message.bool, message.ip), "post_name": () => renameDevice(message.ip, message.name), "post_feature": () => switchFeature(message.name, message.value), "post_idconnect": () => connectById(message.iface), + "post_ruleadd": () => firewallAddRule(message.dst, message.ext, message.local_port, message.port, message.proto), + "post_ruledrop": () => firewallDropRule(message.dst, message.ext, message.local_port, message.port, message.proto), "ping": () => keepAlivePing(message) }; diff --git a/com.umorist47.meowrelaygui/metadata.json b/com.umorist47.meowrelaygui/metadata.json index 84b7d22..cb34d75 100644 --- a/com.umorist47.meowrelaygui/metadata.json +++ b/com.umorist47.meowrelaygui/metadata.json @@ -13,7 +13,7 @@ "Icon": "preferences-system-network-proxy", "Id": "com.umorist47.meowrelaygui", "Name": "MeowRelay", - "Version": "1.0", + "Version": "1.1", "Website": "", "License": "GPL3" },