From 5680d76e8beee080c08d7f841166ff8c9e6d7030 Mon Sep 17 00:00:00 2001 From: umorist47 Date: Mon, 23 Feb 2026 05:28:35 +0300 Subject: [PATCH] 1.0 --- .../contents/config/config.qml | 10 + .../contents/config/main.xml | 81 ++ .../contents/images/dogfood.svg | 53 + .../contents/ui/.unused.qml | 163 +++ .../contents/ui/config/General.qml | 189 +++ .../contents/ui/main.qml | 1091 +++++++++++++++++ .../contents/ui/worker.mjs | 356 ++++++ com.umorist47.meowrelaygui/metadata.json | 21 + 8 files changed, 1964 insertions(+) create mode 100644 com.umorist47.meowrelaygui/contents/config/config.qml create mode 100644 com.umorist47.meowrelaygui/contents/config/main.xml create mode 100644 com.umorist47.meowrelaygui/contents/images/dogfood.svg create mode 100644 com.umorist47.meowrelaygui/contents/ui/.unused.qml create mode 100644 com.umorist47.meowrelaygui/contents/ui/config/General.qml create mode 100644 com.umorist47.meowrelaygui/contents/ui/main.qml create mode 100644 com.umorist47.meowrelaygui/contents/ui/worker.mjs create mode 100644 com.umorist47.meowrelaygui/metadata.json diff --git a/com.umorist47.meowrelaygui/contents/config/config.qml b/com.umorist47.meowrelaygui/contents/config/config.qml new file mode 100644 index 0000000..982f913 --- /dev/null +++ b/com.umorist47.meowrelaygui/contents/config/config.qml @@ -0,0 +1,10 @@ +import QtQuick +import org.kde.plasma.configuration + +ConfigModel { + ConfigCategory { + name: "General" + icon: Qt.resolvedUrl("../images/dogfood.svg") + source: "config/General.qml" + } +} diff --git a/com.umorist47.meowrelaygui/contents/config/main.xml b/com.umorist47.meowrelaygui/contents/config/main.xml new file mode 100644 index 0000000..559530b --- /dev/null +++ b/com.umorist47.meowrelaygui/contents/config/main.xml @@ -0,0 +1,81 @@ + + + + + + + true + + + 60 + 1 + 1440 + + + true + + + 1 + 1 + 1440 + + + true + + + 60 + 1 + 1440 + + + false + + + 5 + 5 + 20 + + + true + + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + + true + + + false + + + meow_icon iflag_extra provider + + + + diff --git a/com.umorist47.meowrelaygui/contents/images/dogfood.svg b/com.umorist47.meowrelaygui/contents/images/dogfood.svg new file mode 100644 index 0000000..cedb559 --- /dev/null +++ b/com.umorist47.meowrelaygui/contents/images/dogfood.svg @@ -0,0 +1,53 @@ + + + + + + + + + + diff --git a/com.umorist47.meowrelaygui/contents/ui/.unused.qml b/com.umorist47.meowrelaygui/contents/ui/.unused.qml new file mode 100644 index 0000000..bca2a0a --- /dev/null +++ b/com.umorist47.meowrelaygui/contents/ui/.unused.qml @@ -0,0 +1,163 @@ +// functions from main.qml --> reworked + // GET routes + function fetchRoutes() { + isLoading = true; + routeModel.clear(); + + var xhr = new XMLHttpRequest(); + xhr.open("GET", "http://10.7.0.1:7777/routes"); + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + isLoading = false; + if (xhr.status === 200) { + var response = JSON.parse(xhr.responseText); + for (var i = 0; i < response.length; i++) { + routeModel.append(response[i]); + } + } else { + console.error("Failed to fetch routes:", xhr.status); + } + } + } + xhr.send(); + } + + // GET profile + function fetchProfile() { + //isLoading = true; + profileModel.clear(); + + var xhr = new XMLHttpRequest(); + xhr.open("GET", "http://10.7.0.1:7777/me/profile"); + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + //isLoading = false; + if (xhr.status === 200) { + profileData = JSON.parse(xhr.responseText); + //for (var i = 0; i < response.length; i++) { + // profileModel.append(response[i]); + //} + } else { + console.error("Failed to fetch profile:", xhr.status); + } + } + } + xhr.send(); + } + + // GET stats + function fetchStats() { + //isLoading = true; + statsModel.clear(); + + var xhr = new XMLHttpRequest(); + xhr.open("GET", "http://10.7.0.1:7777/stats"); + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + //isLoading = false; + if (xhr.status === 200) { + statsData = JSON.parse(xhr.responseText); + //for (var i = 0; i < response.length; i++) { + // profileModel.append(response[i]); + //} + } else { + console.error("Failed to fetch profile:", xhr.status); + } + } + } + xhr.send(); + } + + // POST select country + function selectCountry(code, provider) { + isLoading = true; + + var xhr = new XMLHttpRequest(); + xhr.open("POST", "http://10.7.0.1:7777/me/country"); + xhr.setRequestHeader("Content-Type", "application/json"); + + var data = JSON.stringify({ + "countryCode": code, + "provider": provider + }); + + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + isLoading = false; + if (xhr.status === 200) { + console.log("Success: Changed to " + code); + } else { + console.error("POST failed:", xhr.status); + } + } + } + xhr.send(data); + } + + // POST switch share + function switchShare(bool) { + //isLoading = true; + + var xhr = new XMLHttpRequest(); + xhr.open("POST", "http://10.7.0.1:7777/me/profile"); + xhr.setRequestHeader("Content-Type", "application/json"); + + var data = JSON.stringify({ + "shareTraffic": bool + }); + + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + //isLoading = false; + if (xhr.status === 200) { + console.log("Success: Share changed to " + bool); + } else { + console.error("POST failed:", xhr.status); + } + } + } + xhr.send(data); + } + + // POST switch ebalka + function switchEbalka(bool, ip) { + //isLoading = true; + + var xhr = new XMLHttpRequest(); + xhr.open("POST", "http://10.7.0.1:7777/me/ebalka"); + xhr.setRequestHeader("Content-Type", "application/json"); + + var data = JSON.stringify({ + "ebalka": bool, + "ip": ip + }); + + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + //isLoading = false; + if (xhr.status === 200) { + console.log("Success: Ebalka for " + ip + " changed to " + bool); + } else { + console.error("POST failed:", xhr.status); + } + } + } + xhr.send(data); + } + + // не смотрите сюда + function getFlag($code) { + if ($code == 'US') return '🇺🇸'; + if ($code == 'FI') return '🇫🇮'; + if ($code == 'EE') return '🇪🇪'; + if ($code == 'DE') return '🇩🇪'; + if ($code == 'AM') return '🇦🇲'; + if ($code == 'AD') return '🇦🇩'; + if ($code == 'GB') return '🇬🇧'; + if ($code == 'MC') return '🇲🇨'; + if ($code == 'HK') return '🇭🇰'; + if ($code == 'ES') return '🇪🇸'; + if ($code == 'RU') return '🇷🇺'; + if ($code == 'CH') return '🇨🇭'; + return '❔'; + } diff --git a/com.umorist47.meowrelaygui/contents/ui/config/General.qml b/com.umorist47.meowrelaygui/contents/ui/config/General.qml new file mode 100644 index 0000000..37b20bc --- /dev/null +++ b/com.umorist47.meowrelaygui/contents/ui/config/General.qml @@ -0,0 +1,189 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import org.kde.kirigami as Kirigami +import org.kde.kcmutils as KCM +import org.kde.plasma.components as PlasmaComponents + +KCM.SimpleKCM { + id: generalConfig + + property alias cfg_RoutesAutoUp: enableRoutesUpdate.checked + property alias cfg_RoutesUpInt: routesUpdateInterval.value + property alias cfg_StatsAutoUp: enableStatsUpdate.checked + property alias cfg_StatsUpInt: statsUpdateInterval.value + property alias cfg_ProfileAutoUp: enableProfileUpdate.checked + property alias cfg_ProfileUpInt: profileUpdateInterval.value + + property alias cfg_AutoPing: enablePing.checked + property alias cfg_AutoPingInt: pingInterval.value + property alias cfg_OnErrorRefresh: enableOnErrorRefresh.checked + + property alias cfg_BIinit: showOnInitBI.checked + property alias cfg_BIrefresh: showRefreshBI.checked + property alias cfg_BIpostcountry: showPostCountryBI.checked + property alias cfg_BIpostshare: showSwitchShareBI.checked + property alias cfg_BIpostebalka: showSwitchEbalkaBI.checked + property alias cfg_BIdevicesupdate: showDevicesActionsBI.checked + property alias cfg_BIfeaturesupdate: showFeaturesActionsBI.checked + property alias cfg_BIautoupdate: showAutoUpdateBI.checked + + property alias cfg_SBdisplay: showSB.checked + property alias cfg_ExtraLayout: showExtra.checked + property alias cfg_StringLayoutPlacement: extraInput.text + + Kirigami.FormLayout { + id: form + + Kirigami.Separator { + Kirigami.FormData.isSection: true + Kirigami.FormData.label: i18n("AutoUpdate content") + } + + RowLayout { + Kirigami.FormData.label: i18n("Routes") + CheckBox { + id: enableRoutesUpdate + } + SpinBox { + id: routesUpdateInterval + enabled: enableRoutesUpdate.checked + from: 1 + to: 1440 + textFromValue: function(text) { return text + "m"; } + valueFromText: function(value) { return parseInt(value); } + } + } + RowLayout { + Kirigami.FormData.label: i18n("Stats") + CheckBox { + id: enableStatsUpdate + } + SpinBox { + id: statsUpdateInterval + enabled: enableStatsUpdate.checked + from: 1 + to: 1440 + textFromValue: function(text) { return text + "m"; } + valueFromText: function(value) { return parseInt(value); } + } + } + RowLayout { + Kirigami.FormData.label: i18n("Profile") + CheckBox { + id: enableProfileUpdate + } + SpinBox { + id: profileUpdateInterval + enabled: enableProfileUpdate.checked + from: 1 + to: 1440 + textFromValue: function(text) { return text + "m"; } + valueFromText: function(value) { return parseInt(value); } + } + } + RowLayout { + Kirigami.FormData.label: i18n("Ping") + CheckBox { + id: enablePing + } + SpinBox { + id: pingInterval + enabled: enablePing.checked + from: 5 + to: 20 + textFromValue: function(text) { return text + "s"; } + valueFromText: function(value) { return parseInt(value); } + } + } + RowLayout { + Kirigami.FormData.label: i18n("AutoRefresh on error") + CheckBox { + id: enableOnErrorRefresh + } + } + + Kirigami.Separator { + Kirigami.FormData.isSection: true + Kirigami.FormData.label: i18n("Busy indicator") + } + + CheckBox { + Kirigami.FormData.label: i18n("Show Busy indicator on actions:") + id: showOnInitBI + text: i18n("Plasmoid Init") + } + CheckBox { + id: showRefreshBI + text: i18n("Refresh button") + } + CheckBox { + id: showPostCountryBI + text: i18n("Select country") + } + CheckBox { + id: showSwitchShareBI + text: i18n("Switch Share") + } + CheckBox { + id: showSwitchEbalkaBI + text: i18n("Switch 80% loss") + } + CheckBox { + id: showDevicesActionsBI + text: i18n("Devices actions") + } + CheckBox { + id: showFeaturesActionsBI + text: i18n("Features actions") + } + CheckBox { + id: showAutoUpdateBI + text: i18n("AutoUpdate actions") + } + + Kirigami.Separator { + Kirigami.FormData.isSection: true + Kirigami.FormData.label: i18n("Extra") + } + + RowLayout { + Kirigami.FormData.label: i18n("Show scrollbars") + CheckBox { + id: showSB + } + Kirigami.ContextualHelpButton { + toolTipText: i18n( + "If true, scrollbars will be shown as needed" + ) + } + } + RowLayout { + Kirigami.FormData.label: i18n("CompactRepresentation extra data display") + CheckBox { + id: showExtra + } + TextField { + id: extraInput + enabled: showExtra.checked + placeholderText: "meow_icon iflag_extra provider ..." + //bottomPadding: Kirigami.Units.gridUnit + wrapMode: Text.Wrap + } + } + PlasmaComponents.Label { + text: ( + "meow_icon - plasmoid icon\n" + + "user - your username\n" + + "current_device - current device name\n" + + "current - current country code (US, FI etc.)\n" + + "provider - vpn provider (NORD, WARP etc.)\n" + + "ip - current device ip (MeowRelay local)\n" + + "iflag - current country flag\n" + + "iflag_extra - current country flag + country code (joined)\n" + + "any variable from main.qml (root section)" + ) + opacity: 0.7 + } + } +} diff --git a/com.umorist47.meowrelaygui/contents/ui/main.qml b/com.umorist47.meowrelaygui/contents/ui/main.qml new file mode 100644 index 0000000..6b64a3e --- /dev/null +++ b/com.umorist47.meowrelaygui/contents/ui/main.qml @@ -0,0 +1,1091 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import org.kde.kirigami as Kirigami +import org.kde.plasma.core as PlasmaCore +import org.kde.plasma.components as PlasmaComponents +import org.kde.plasma.extras as PlasmaExtras +import org.kde.plasma.plasmoid + +PlasmoidItem { + id: root + + Plasmoid.title: "MeowRelay" + + // compactRepresentation extra content + property var iflag: getFlag(current) + property var iflag_extra: getFlag(current) + current + + // extra + hideOnWindowDeactivate: !plasmoid.configuration.isPinned + + property var scrollbartype: Plasmoid.configuration.SBdisplay ? ScrollBar.AsNeeded : ScrollBar.AlwaysOff + + //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 + + //routes + property var routesData: null + + //stats + property var statsData: null + + property double uptime: statsData.uptime + property var servers: statsData.servers + + // tabs json responses + ListModel { id: routeModel } + ListModel { id: statsModel } + ListModel { id: profileModel } + ListModel { id: devicesModel } + ListModel { id: featuresModel } + + // buttons blocker (antispam) + property int isLoadingItems: 0 + property bool isLoading: false // колясочные технологии, по итогу юзается токо isLoadingItems + property bool isBlocked: false + property bool shareBlocked: false + property bool ebalkaBlocked: false + property bool refreshBlocked: false + property bool countriesBlocked: false + property bool devicesBlocked: false + property bool featuresBlocked: false + + onIsLoadingItemsChanged: { + if (isLoadingItems > 0) { + isLoading = true + } + else if (isLoadingItems < 0) { + isLoading = false + isLoadingItems = 0 + } + else { + isLoading = false + } + } + + // flags + function getFlag(code) { + if (!code || code.length !== 2) return '❔'; + let upper = code.toUpperCase(); + let c1 = upper.charCodeAt(0) + 127397; + let c2 = upper.charCodeAt(1) + 127397; + return String.fromCodePoint(c1, c2); + } + + // traffic + function formatTraffic(bytes) { + if (bytes === 0) return "0 B"; + var units = ["B", "KB", "MB", "GB", "TB"]; + var i = Math.floor(Math.log(bytes) / Math.log(1024)); + var size = (bytes / Math.pow(1024, i)).toFixed(1); + return parseFloat(size) + " " + units[i]; + } + + Timer { + id: meowRelayWatchdog + interval: 10000 + 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 + isBlocked = false + isLoadingItems = 0 + countriesBlocked = true + shareBlocked = true + ebalkaBlocked = true + devicesBlocked = true + featuresBlocked = true + Plasmoid.configuration.OnErrorRefresh ? ( + meowRelayWatchdog.start(), + isLoadingItems += Plasmoid.configuration.BIrefresh, + //isBlocked = true, + superWorker.sendMessage({ "action": "fetch_routes" }), + superWorker.sendMessage({ "action": "fetch_profile" }), + superWorker.sendMessage({ "action": "fetch_stats" }), + fetchRoutesTimer.restart(), + fetchProfileTimer.restart(), + fetchStatsTimer.restart() + ) : null + } + } + Timer { + id: meowRelayWatchdogStats + interval: 10000 + repeat: false + onTriggered: { + console.error("MeowRelay unreachable!") //ваша няшка умерла + refreshBlocked = false + } + } + Timer { + id: meowRelayPing + interval: Plasmoid.configuration.AutoPingInt * 1000 + running: Plasmoid.configuration.AutoPing + repeat: false + onTriggered: { + superWorker.sendMessage({ "action": "ping" }) + console.log("MeowRelay ping pong") + } + } + + // раб в отдельном потоке чтобы кде не фризило + WorkerScript { + id: superWorker + source: "worker.mjs" + + property var handlers: ({ + "fetch_routes": ({ data }) => { + routeModel.clear(); + routesData = data; + for (let i = 0; i < data.length; i++) { + routeModel.append(data[i]); + }; + if (Plasmoid.configuration.BIrefresh || Plasmoid.configuration.BIautoupdate) { + isLoadingItems -= 1 + }; + refreshBlocked = false; + }, + "fetch_stats": ({ data }) => { + statsModel.clear(); + statsData = data; + var serverArray = []; + for (var Name in servers) { + if (servers.hasOwnProperty(Name)) { + var item = servers[Name]; + item.name = Name; + item.pingString = item.ping.join(" "); + item.trafficString = item.traffic.join(" "); + item.speedString = item.speed.join(" "); + serverArray.push(item); + } + } + serverArray.sort(function(a, b) { + return b.connected - a.connected; + }); + for (let i = 0; i < serverArray.length; i++) { + statsModel.append(serverArray[i]); + }; + if (Plasmoid.configuration.BIrefresh || Plasmoid.configuration.BIautoupdate) { + isLoadingItems -= 1 + }; + meowRelayWatchdogStats.stop(); + refreshBlocked = false; + }, + "fetch_profile": ({ data }) => { + profileModel.clear(); + devicesModel.clear(); + featuresModel.clear(); + profileData = data; + for (let i = 0; i < data.length; i++) { + profileModel.append(data[i]); + } + for (let i = 0; i < data.devices.length; i++) { + devicesModel.append(data.devices[i]) + } + for (let i = 0; i < data.features.length; i++) { + featuresModel.append(data.features[i]) + } + if (Plasmoid.configuration.BIrefresh || Plasmoid.configuration.BIautoupdate) { + isLoadingItems -= 1 + } + meowRelayWatchdog.stop(); + isBlocked = false; + refreshBlocked = false; + countriesBlocked = false; + shareBlocked = false; + ebalkaBlocked = false; + devicesBlocked = false; + featuresBlocked = false; + }, + "post_country": ({ status }) => { + if (status === 200) { + superWorker.sendMessage({ "action": "fetch_profile" }) + //done + } + else { + superWorker.sendMessage({ "actions": "fetch_routes" }) + //error + }; + if (Plasmoid.configuration.BIpostcountry) { + isLoadingItems -= 1 + } + }, + "post_share": ({ status }) => { + if (status === 200) { + superWorker.sendMessage({ "action": "fetch_profile" }) + //done + } + else { + superWorker.sendMessage({ "action": "fetch_profile" }) + // share = !share + //error + }; + if (Plasmoid.configuration.BIpostshare) { + isLoadingItems -= 1 + } + shareBlocked = false; + }, + "post_ebalka": ({ status, ip }) => { + if (status === 200 && ip != null) { + superWorker.sendMessage({ "action": "fetch_profile" }) + //done + } + else { + superWorker.sendMessage({ "action": "fetch_profile" }) // кофе + //error + }; + if (Plasmoid.configuration.BIpostebalka) { + isLoadingItems -= 1 + } + if (Plasmoid.configuration.BIdevicesupdate) { + isLoadingItems -= 1 + } + ebalkaBlocked = false; + devicesBlocked = false; + }, + "post_name": ({ status }) => { + if (status === 200) { + superWorker.sendMessage({ "action": "fetch_profile" }) + //done + } + else { + //error + }; + if (Plasmoid.configuration.BIdevicesupdate) { + isLoadingItems -= 1 + } + devicesBlocked = false; + }, + "post_feature": ({ status }) => { + superWorker.sendMessage({ "action": "fetch_profile" }); + if (Plasmoid.configuration.BIfeaturesupdate) { + isLoadingItems -= 1 + } + featuresBlocked = false; + }, + "post_idconnect": ({ status }) => { + superWorker.sendMessage({ "action": "fetch_profile" }); + superWorker.sendMessage({ "action": "fetch_routes" }); + if (Plasmoid.configuration.BIpostcountry) { + isLoadingItems -= 1 + } + } + }) + + onMessage: (msg) => { + if (handlers[msg.action]) { + handlers[msg.action](msg); + } + } + } + + // init fetch + Component.onCompleted: { + isLoadingItems += Plasmoid.configuration.BIinit + isBlocked = true + meowRelayWatchdog.start() + superWorker.sendMessage({ "action": "fetch_routes" }) + superWorker.sendMessage({ "action": "fetch_stats" }) + superWorker.sendMessage({ "action": "fetch_profile" }) + fetchRoutesTimer.start() + fetchStatsTimer.start() + fetchProfileTimer.start() + meowRelayPing.start() + } + + // autoupdate fetch + Timer { + id: fetchRoutesTimer + interval: (Plasmoid.configuration.RoutesUpInt * 60000) + running: Plasmoid.configuration.RoutesAutoUp + repeat: true + triggeredOnStart: false + onTriggered: { + refreshBlocked = true + isLoadingItems += Plasmoid.configuration.BIautoupdate + superWorker.sendMessage({ "action": "fetch_routes" }) + } + } + Timer { + id: fetchStatsTimer + interval: (Plasmoid.configuration.StatsUpInt * 60000) + running: Plasmoid.configuration.StatsAutoUp + repeat: true + triggeredOnStart: false + onTriggered: { + refreshBlocked = true + isLoadingItems += Plasmoid.configuration.BIautoupdate + meowRelayWatchdogStats.start() + superWorker.sendMessage({ "action": "fetch_stats" }) + } + } + Timer { + id: fetchProfileTimer + interval: (Plasmoid.configuration.ProfileUpInt * 60000) + running: Plasmoid.configuration.ProfileAutoUp + repeat: true + triggeredOnStart: false + onTriggered: { + refreshBlocked = true + isLoadingItems += Plasmoid.configuration.BIautoupdate + meowRelayWatchdog.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 + // } + } + } + + fullRepresentation: PlasmaExtras.Representation { + //id: fullRoot + Layout.minimumWidth: Kirigami.Units.gridUnit * 25 + Layout.minimumHeight: Kirigami.Units.gridUnit * 30 + Layout.preferredWidth: Kirigami.Units.gridUnit * 25 + Layout.preferredHeight: Kirigami.Units.gridUnit * 30 + //spacing: 0 + + header: PlasmaExtras.PlasmoidHeading { + contentItem: ColumnLayout { + spacing: Kirigami.Units.smallSpacing + + // row 1 (name) + RowLayout { + Layout.fillWidth: true + + PlasmaExtras.Heading { + text: " " + plasmoid.title + level: 1 + Layout.fillWidth: false + elide: Text.ElideRight + } + Rectangle { + height: Kirigami.Units.gridUnit + Layout.alignment: AlignVCenter + width: 1 + color: PlasmaCore.Theme.dividerColor + opacity: 0.7 + } + PlasmaExtras.Heading { + text: (user ? user : "unknown") + opacity: 0.7 + level: 1 + Layout.fillWidth: true + elide: Text.ElideRight + } + + // config button + PlasmaComponents.ToolButton { + id: configButton + icon.name: "configure" + display: ToolButton.IconOnly + onClicked: plasmoid.internalAction("configure").trigger() + ToolTip.text: i18n("Configure") + ToolTip.visible: hovered + ToolTip.delay: Kirigami.Units.toolTipDelay + KeyNavigation.right: pinButton + } + + // pin button + PlasmaComponents.ToolButton { + id: pinButton + icon.name: "window-pin" + display: ToolButton.IconOnly + checkable: true + checked: plasmoid.configuration.isPinned + down: checked + onToggled: plasmoid.configuration.isPinned = checked + ToolTip.text: i18n("Keep Open") + ToolTip.visible: hovered + ToolTip.delay: Kirigami.Units.toolTipDelay + KeyNavigation.left: configButton + visible: plasmoid.formFactor === 2 || plasmoid.formFactor === 3 + enabled: visible + } + } + + // row 2 (tabs) + PlasmaComponents.TabBar { + id: mainTabBar + Layout.fillWidth: true + + Repeater { + model: ["Route", "Devices", "Stats", "Features"] + delegate: PlasmaComponents.TabButton { + text: modelData + } + } + } + } + } + + // Footer + footer: PlasmaExtras.PlasmoidHeading { + id: footerItem + ColumnLayout { + anchors.fill: parent + + PlasmaComponents.ProgressBar { + indeterminate: true + //visible: isLoading + visible: false + Layout.fillWidth: true + } + + RowLayout { + spacing: Kirigami.Units.smallSpacing + PlasmaComponents.Label { + text: "Up:" + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + PlasmaComponents.Label { + text: (Math.floor(uptime / 86400)) + "d " + (Math.floor((uptime % 86400) / 3600)) + "h " + (Math.floor(((uptime % 86400) % 3600) / 60)) + "m" + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + Item { + Layout.fillWidth: true + } + PlasmaComponents.Label { + text: getFlag(current) + current + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + PlasmaComponents.Label { + text: provider + opacity: 0.7 + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + Rectangle { + height: Kirigami.Units.gridUnit + Layout.alignment: AlignVCenter + width: 1 + color: PlasmaCore.Theme.dividerColor + opacity: 0.2 + } + PlasmaComponents.Label { + text: "Device:" + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + PlasmaComponents.Label { + text: current_device + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + Rectangle { + height: Kirigami.Units.gridUnit + Layout.alignment: AlignVCenter + width: 1 + color: PlasmaCore.Theme.dividerColor + opacity: 0.2 + } + PlasmaComponents.Label { + text: "Ip:" + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + PlasmaComponents.Label { + text: ip + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + } + + RowLayout { + + PlasmaComponents.Button { + text: "Refresh" + icon.name: "view-refresh" + onClicked: { + meowRelayWatchdog.running ? meowRelayWatchdog.stop() : null + meowRelayWatchdog.start() + isLoadingItems += Plasmoid.configuration.BIrefresh + isBlocked = true + superWorker.sendMessage({ "action": "fetch_routes" }) + superWorker.sendMessage({ "action": "fetch_profile" }) + superWorker.sendMessage({ "action": "fetch_stats" }) + fetchRoutesTimer.restart() + fetchProfileTimer.restart() + fetchStatsTimer.restart() + } + enabled: !(isBlocked || refreshBlocked) + Layout.fillWidth: false + } + PlasmaComponents.BusyIndicator { + running: isLoadingItems //isLoading + implicitWidth: 28 + implicitHeight: 28 + } + Item { + Layout.fillWidth: true + } + PlasmaComponents.Switch { + text: "Share" + checked: share + enabled: !(isBlocked || shareBlocked) + onClicked: { + isLoadingItems += Plasmoid.configuration.BIpostshare + shareBlocked = true + superWorker.sendMessage({ "action": "post_share", "bool": !share }) + share = !share + } + } + Rectangle { + height: Kirigami.Units.gridUnit + Layout.alignment: AlignVCenter + width: 1 + color: PlasmaCore.Theme.dividerColor + opacity: 0.2 + } + PlasmaComponents.Switch { + text: "80% loss" + checked: ebalka + enabled: !(isBlocked || ebalkaBlocked) + onClicked: { + isLoadingItems += Plasmoid.configuration.BIpostebalka + ebalkaBlocked = true + superWorker.sendMessage({ "action": "post_ebalka", "bool": !ebalka, "ip": null }) + ebalka = !ebalka + } + } + + } + } + } + + // Content + contentItem: Item { + StackLayout { + id: tabStack + anchors.fill: parent + 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 }) + } + } + } + } + } + + 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 + 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 + } + } + 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 + 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 + } + } + } + + // Placeholder for other tabs + // PlasmaExtras.PlaceholderMessage { text: "Chairy Support" } // да в пизду крч нахуй надо + } + } + } +} diff --git a/com.umorist47.meowrelaygui/contents/ui/worker.mjs b/com.umorist47.meowrelaygui/contents/ui/worker.mjs new file mode 100644 index 0000000..f4c9a57 --- /dev/null +++ b/com.umorist47.meowrelaygui/contents/ui/worker.mjs @@ -0,0 +1,356 @@ +// чисто в теории это должно помочь долгой загрузке пакетов (так гемини сказал) +// на деле же хуй знает ибо там иногда хендшейк слетает и он просто заного его шлет +const xhrPool = []; +const MAX_POOL_SIZE = 6; // Qt's typical max concurrent connections + +function getXhr() { + // Look for an idle object + for (let i = 0; i < xhrPool.length; i++) { + if (xhrPool[i].readyState === 0 || xhrPool[i].readyState === 4) { + return xhrPool[i]; + } + } + // If none idle and we have room, make a new one + if (xhrPool.length < MAX_POOL_SIZE) { + let newXhr = new XMLHttpRequest(); + xhrPool.push(newXhr); + return newXhr; + } + // If all busy, return null (you should queue the request) + return null; +} + +// KeepAlive ping +function keepAlivePing() { + let xhr = getXhr(); + + if (!xhr) { + console.log("Pool full, retrying..."); + setTimeout(() => keepAlivePing(), 100); + return; + } + xhr.open("HEAD", "http://10.7.0.1:7777/me/profile"); + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + //isLoading = false; + if (xhr.status === 200) { + //console.log("ping pong") + } + } + } + xhr.send(); +} + +// GET routes +function fetchRoutes() { + //isLoading = true; + //routeModel.clear(); + + let xhr = getXhr(); + + if (!xhr) { + console.log("Pool full, retrying..."); + setTimeout(() => fetchRoutes(), 100); + return; + } + xhr.open("GET", "http://10.7.0.1:7777/routes"); + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + //isLoading = false; + if (xhr.status === 200) { + let response = JSON.parse(xhr.responseText); + WorkerScript.sendMessage({ "action": "fetch_routes", "data": response }); + //for (var i = 0; i < response.length; i++) { + // routeModel.append(response[i]); + //} + } else { + WorkerScript.sendMessage({ "action": "fetch_routes", "data": [{"code":"ERR","name":"Error, please try again :<","provider":xhr.status}] }); + console.error("Failed to fetch routes:", xhr.status); + } + } + } + xhr.send(); +} + +// GET profile +function fetchProfile() { + //isLoading = true; + //profileModel.clear(); + + let xhr = getXhr(); + + if (!xhr) { + console.log("Pool full, retrying..."); + setTimeout(() => fetchProfile(), 100); + return; + } + xhr.open("GET", "http://10.7.0.1:7777/me/profile"); + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + //isLoading = false; + if (xhr.status === 200) { + let response = JSON.parse(xhr.responseText); + WorkerScript.sendMessage({ "action": "fetch_profile", "data": response }); + //for (var i = 0; i < response.length; i++) { + // profileModel.append(response[i]); + //} + } else { + WorkerScript.sendMessage({ "action": "fetch_profile", "data": {"provider":xhr.status, "current_device":"error"} }); + console.error("Failed to fetch profile:", xhr.status); + } + } + } + xhr.send(); +} + +// GET stats +function fetchStats() { + //isLoading = true; + //statsModel.clear(); + + let xhr = getXhr(); + + if (!xhr) { + console.log("Pool full, retrying..."); + setTimeout(() => fetchStats(), 100); + return; + } + xhr.open("GET", "http://10.7.0.1:7777/stats"); + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + //isLoading = false; + if (xhr.status === 200) { + let response = JSON.parse(xhr.responseText); + WorkerScript.sendMessage({ "action": "fetch_stats", "data": response }); + //for (var i = 0; i < response.length; i++) { + // profileModel.append(response[i]); + //} + } else { + WorkerScript.sendMessage({ "action": "fetch_stats", "data": {} }); + console.error("Failed to fetch profile:", xhr.status); + } + } + } + xhr.send(); +} + +// POST select country +function selectCountry(code, provider) { + //isLoading = true; + + let xhr = getXhr(); + + if (!xhr) { + console.log("Pool full, retrying..."); + setTimeout(() => selectCountry(code, provider), 100); + return; + } + xhr.open("POST", "http://10.7.0.1:7777/me/country"); + xhr.setRequestHeader("Content-Type", "application/json"); + + var data = JSON.stringify({ + "countryCode": code, + "provider": provider + }); + + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + //isLoading = false; + if (xhr.status === 200) { + console.log("Success: Changed to " + code); + WorkerScript.sendMessage({ "action": "post_country", "status": xhr.status}); + } else { + console.error("POST failed:", xhr.status); + WorkerScript.sendMessage({ "action": "post_country", "status": xhr.status}); + } + } + } + xhr.send(data); +} + +// POST switch share +function switchShare(bool) { + //isLoading = true; + + let xhr = getXhr(); + + if (!xhr) { + console.log("Pool full, retrying..."); + setTimeout(() => switchShare(bool), 100); + return; + } + xhr.open("POST", "http://10.7.0.1:7777/me/profile"); + xhr.setRequestHeader("Content-Type", "application/json"); + + var data = JSON.stringify({ + "shareTraffic": bool + }); + + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + //isLoading = false; + if (xhr.status === 200) { + console.log("Success: Share changed to " + bool); + WorkerScript.sendMessage({ "action": "post_share", "status": xhr.status}); + } else { + console.error("POST failed:", xhr.status); + WorkerScript.sendMessage({ "action": "post_share", "status": xhr.status}); + } + } + } + xhr.send(data); +} + +// POST switch ebalka +function switchEbalka(bool, ip) { + //isLoading = true; + + let xhr = getXhr(); + + if (!xhr) { + console.log("Pool full, retrying..."); + setTimeout(() => switchEbalka(bool, ip), 100); + return; + } + xhr.open("POST", "http://10.7.0.1:7777/me/ebalka"); + xhr.setRequestHeader("Content-Type", "application/json"); + + var data = JSON.stringify({ + "ebalka": bool, + "ip": ip + }); + + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + //isLoading = false; + if (xhr.status === 200) { + console.log("Success: Ebalka for " + ip + " changed to " + bool); + WorkerScript.sendMessage({ "action": "post_ebalka", "status": xhr.status, "ip": ip}); + } else { + console.error("POST failed:", xhr.status); + WorkerScript.sendMessage({ "action": "post_ebalka", "status": xhr.status, "ip": ip}); + } + } + } + xhr.send(data); +} + +// POST switch ebalka +function renameDevice(ip, name) { + //isLoading = true; + + let xhr = getXhr(); + + if (!xhr) { + console.log("Pool full, retrying..."); + setTimeout(() => renameDevice(ip, name), 100); + return; + } + xhr.open("POST", "http://10.7.0.1:7777/me/device"); + xhr.setRequestHeader("Content-Type", "application/json"); + + var data = JSON.stringify({ + "ip": ip, + "name": name + }); + + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + //isLoading = false; + if (xhr.status === 200) { + console.log("Success: Name for " + ip + " changed to " + name); + WorkerScript.sendMessage({ "action": "post_name", "status": xhr.status, "ip": ip}); + } else { + console.error("POST failed:", xhr.status); + WorkerScript.sendMessage({ "action": "post_name", "status": xhr.status, "ip": ip}); + } + } + } + xhr.send(data); +} + +// POST feature switch +function switchFeature(name, value) { + //isLoading = true; + + let xhr = getXhr(); + + if (!xhr) { + console.log("Pool full, retrying..."); + setTimeout(() => switchFeature(name, value), 100); + return; + } + xhr.open("POST", "http://10.7.0.1:7777/me/feature"); + xhr.setRequestHeader("Content-Type", "application/json"); + + var data = JSON.stringify({ + "name": name, + "value": value + }); + + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + //isLoading = false; + if (xhr.status === 200) { + console.log("Success: Feature " + name + " changed to " + value); + WorkerScript.sendMessage({ "action": "post_feature" }); + } else { + console.error("POST failed:", xhr.status); + WorkerScript.sendMessage({ "action": "post_feature" }); + } + } + } + xhr.send(data); +} + +// POST connect by server id +function connectById(iface) { + //isLoading = true; + + let xhr = getXhr(); + + if (!xhr) { + console.log("Pool full, retrying..."); + setTimeout(() => connectById(iface), 100); + return; + } + xhr.open("POST", "http://10.7.0.1:7777/me/connect_by_id"); + xhr.setRequestHeader("Content-Type", "application/json"); + + var data = JSON.stringify({ + "iface": iface + }); + + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + //isLoading = false; + if (xhr.status === 200) { + console.log("Success: Changed to " + iface); + WorkerScript.sendMessage({ "action": "post_idconnect", "status": xhr.status}); + } else { + console.error("POST failed:", xhr.status); + WorkerScript.sendMessage({ "action": "post_idconnect", "status": xhr.status}); + } + } + } + xhr.send(data); +} + +WorkerScript.onMessage = function(message) { + const actions = { + "fetch_routes": () => fetchRoutes(message), + "fetch_stats": () => fetchStats(message), + "fetch_profile": () => fetchProfile(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), + "ping": () => keepAlivePing(message) + }; + + if (actions[message.action]) { + actions[message.action](); + } +} diff --git a/com.umorist47.meowrelaygui/metadata.json b/com.umorist47.meowrelaygui/metadata.json new file mode 100644 index 0000000..84b7d22 --- /dev/null +++ b/com.umorist47.meowrelaygui/metadata.json @@ -0,0 +1,21 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "me@umorist47.ru", + "Name": "Umorist Kekovich" + } + ], + "Category": "System", + "Description": "MeowRelay configurator plasmoid.", + "EnabledByDefault": true, + "Icon": "preferences-system-network-proxy", + "Id": "com.umorist47.meowrelaygui", + "Name": "MeowRelay", + "Version": "1.0", + "Website": "", + "License": "GPL3" + }, + "X-Plasma-API-Minimum-Version": "6.0" +}