mirror of
https://github.com/Mahdi-zarei/nekoray.git
synced 2025-12-19 13:42:51 +08:00
feat: Partially implement new route GUI
This commit is contained in:
parent
2bc9fd210c
commit
c201548aa8
@ -22,11 +22,11 @@ endif ()
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network Svg LinguistTools)
|
||||
|
||||
if (NKR_CROSS)
|
||||
set_property(TARGET Qt5::moc PROPERTY IMPORTED_LOCATION /usr/bin/moc)
|
||||
set_property(TARGET Qt5::uic PROPERTY IMPORTED_LOCATION /usr/bin/uic)
|
||||
set_property(TARGET Qt5::rcc PROPERTY IMPORTED_LOCATION /usr/bin/rcc)
|
||||
set_property(TARGET Qt5::lrelease PROPERTY IMPORTED_LOCATION /usr/bin/lrelease)
|
||||
set_property(TARGET Qt5::lupdate PROPERTY IMPORTED_LOCATION /usr/bin/lupdate)
|
||||
set_property(TARGET Qt6::moc PROPERTY IMPORTED_LOCATION /usr/bin/moc)
|
||||
set_property(TARGET Qt6::uic PROPERTY IMPORTED_LOCATION /usr/bin/uic)
|
||||
set_property(TARGET Qt6::rcc PROPERTY IMPORTED_LOCATION /usr/bin/rcc)
|
||||
set_property(TARGET Qt6::lrelease PROPERTY IMPORTED_LOCATION /usr/bin/lrelease)
|
||||
set_property(TARGET Qt6::lupdate PROPERTY IMPORTED_LOCATION /usr/bin/lupdate)
|
||||
endif ()
|
||||
|
||||
#### Platform Variables ####
|
||||
@ -250,10 +250,15 @@ set(PROJECT_SOURCES
|
||||
ui/widget/GroupItem.cpp
|
||||
ui/widget/GroupItem.h
|
||||
ui/widget/GroupItem.ui
|
||||
ui/widget/RouteItem.cpp
|
||||
ui/widget/RouteItem.h
|
||||
ui/widget/RouteItem.ui
|
||||
|
||||
res/neko.qrc
|
||||
res/theme/feiyangqingyun/qss.qrc
|
||||
${QV2RAY_RC}
|
||||
db/RouteEntity.h
|
||||
db/RouteEntity.cpp
|
||||
)
|
||||
|
||||
# Qt exe
|
||||
|
||||
@ -493,11 +493,6 @@ namespace NekoGui {
|
||||
status->result->coreConfig.insert("inbounds", status->inbounds);
|
||||
status->result->coreConfig.insert("outbounds", status->outbounds);
|
||||
|
||||
// user rule
|
||||
if (!status->forTest) {
|
||||
DOMAIN_USER_RULE
|
||||
IP_USER_RULE
|
||||
}
|
||||
|
||||
// sing-box common rule object
|
||||
auto make_rule = [&](const QStringList &list, bool isIP = false) {
|
||||
@ -722,12 +717,7 @@ namespace NekoGui {
|
||||
if (geosite.isEmpty()) status->result->error = +"geosite.db not found";
|
||||
|
||||
// final add routing rule
|
||||
auto routingRules = QString2QJsonObject(dataStore->routing->custom)["rules"].toArray();
|
||||
if (status->forTest) routingRules = {};
|
||||
if (!status->forTest) QJSONARRAY_ADD(routingRules, QString2QJsonObject(dataStore->custom_route_global)["rules"].toArray())
|
||||
QJSONARRAY_ADD(routingRules, status->routingRules)
|
||||
auto routeObj = QJsonObject{
|
||||
{"rules", routingRules},
|
||||
{"auto_detect_interface", true},
|
||||
{
|
||||
"geoip",
|
||||
|
||||
@ -37,8 +37,10 @@ namespace NekoGui {
|
||||
//
|
||||
profiles = {};
|
||||
groups = {};
|
||||
routes = {};
|
||||
profilesIdOrder = filterIntJsonFile("profiles");
|
||||
groupsIdOrder = filterIntJsonFile("groups");
|
||||
routesIdOrder = filterIntJsonFile("routes");
|
||||
// Load Proxys
|
||||
QList<int> delProfile;
|
||||
for (auto id: profilesIdOrder) {
|
||||
@ -75,12 +77,29 @@ namespace NekoGui {
|
||||
groupsTabOrder << id;
|
||||
}
|
||||
}
|
||||
// Load Routing profiles
|
||||
for (auto id : routesIdOrder) {
|
||||
auto route = LoadRouteChain(QString("routes/%1.json").arg(id));
|
||||
if (route == nullptr) {
|
||||
MW_show_log(QString("File routes/%1.json is corrupted, consider manually handling it").arg(id));
|
||||
continue;
|
||||
}
|
||||
|
||||
routes[id] = route;
|
||||
}
|
||||
|
||||
// First setup
|
||||
if (groups.empty()) {
|
||||
auto defaultGroup = NekoGui::ProfileManager::NewGroup();
|
||||
defaultGroup->name = QObject::tr("Default");
|
||||
NekoGui::profileManager->AddGroup(defaultGroup);
|
||||
}
|
||||
|
||||
if (routes.empty()) {
|
||||
auto defaultRoute = NekoGui::RoutingChain::GetDefaultChain();
|
||||
NekoGui::profileManager->AddRouteChain(defaultRoute);
|
||||
}
|
||||
|
||||
//
|
||||
if (dataStore->flag_reorder) {
|
||||
{
|
||||
@ -163,6 +182,16 @@ namespace NekoGui {
|
||||
return ent;
|
||||
}
|
||||
|
||||
std::shared_ptr<RoutingChain> ProfileManager::LoadRouteChain(const QString &jsonPath) {
|
||||
std::shared_ptr<RoutingChain> routingChain;
|
||||
routingChain->fn = jsonPath;
|
||||
if (!routingChain->Load()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return routingChain;
|
||||
}
|
||||
|
||||
// 新建的不给 fn 和 id
|
||||
|
||||
std::shared_ptr<ProxyEntity> ProfileManager::NewProxyEntity(const QString &type) {
|
||||
@ -373,6 +402,37 @@ namespace NekoGui {
|
||||
return GetGroup(dataStore->current_group);
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<RoutingChain> ProfileManager::NewRouteChain() {
|
||||
auto route = std::make_shared<RoutingChain>();
|
||||
return route;
|
||||
}
|
||||
|
||||
int ProfileManager::NewRouteChainID() const {
|
||||
if (routes.empty()) {
|
||||
return 0;
|
||||
}
|
||||
return routesIdOrder.last() + 1;
|
||||
}
|
||||
|
||||
bool ProfileManager::AddRouteChain(const std::shared_ptr<RoutingChain> chain) {
|
||||
if (chain->id >= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
chain->id = NewRouteChainID();
|
||||
routes[chain->id] = chain;
|
||||
routesIdOrder.push_back(chain->id);
|
||||
chain->fn = QString("routes/%1.json").arg(chain->id);
|
||||
chain->Save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<RoutingChain> ProfileManager::GetRouteChain(int id) {
|
||||
return routes.count(id) > 0 ? routes[id] : nullptr;
|
||||
}
|
||||
|
||||
QList<std::shared_ptr<ProxyEntity>> Group::Profiles() const {
|
||||
QList<std::shared_ptr<ProxyEntity>> ret;
|
||||
for (const auto &[_, profile]: profileManager->profiles) {
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include "main/NekoGui.hpp"
|
||||
#include "ProxyEntity.hpp"
|
||||
#include "Group.hpp"
|
||||
#include "RouteEntity.h"
|
||||
|
||||
namespace NekoGui {
|
||||
class ProfileManager : private JsonStore {
|
||||
@ -16,6 +17,7 @@ namespace NekoGui {
|
||||
|
||||
std::map<int, std::shared_ptr<ProxyEntity>> profiles;
|
||||
std::map<int, std::shared_ptr<Group>> groups;
|
||||
std::map<int, std::shared_ptr<RoutingChain>> routes;
|
||||
|
||||
ProfileManager();
|
||||
|
||||
@ -28,6 +30,8 @@ namespace NekoGui {
|
||||
|
||||
[[nodiscard]] static std::shared_ptr<Group> NewGroup();
|
||||
|
||||
[[nodiscard]] static std::shared_ptr<RoutingChain> NewRouteChain();
|
||||
|
||||
bool AddProfile(const std::shared_ptr<ProxyEntity> &ent, int gid = -1);
|
||||
|
||||
void DeleteProfile(int id);
|
||||
@ -44,18 +48,27 @@ namespace NekoGui {
|
||||
|
||||
std::shared_ptr<Group> CurrentGroup();
|
||||
|
||||
bool AddRouteChain(std::shared_ptr<RoutingChain> chain);
|
||||
|
||||
std::shared_ptr<RoutingChain> GetRouteChain(int id);
|
||||
|
||||
private:
|
||||
// sort by id
|
||||
QList<int> profilesIdOrder;
|
||||
QList<int> groupsIdOrder;
|
||||
QList<int> routesIdOrder;
|
||||
|
||||
[[nodiscard]] int NewProfileID() const;
|
||||
|
||||
[[nodiscard]] int NewGroupID() const;
|
||||
|
||||
[[nodiscard]] int NewRouteChainID() const;
|
||||
|
||||
static std::shared_ptr<ProxyEntity> LoadProxyEntity(const QString &jsonPath);
|
||||
|
||||
static std::shared_ptr<Group> LoadGroup(const QString &jsonPath);
|
||||
|
||||
static std::shared_ptr<RoutingChain> LoadRouteChain(const QString &jsonPath);
|
||||
};
|
||||
|
||||
extern ProfileManager *profileManager;
|
||||
|
||||
252
db/RouteEntity.cpp
Normal file
252
db/RouteEntity.cpp
Normal file
@ -0,0 +1,252 @@
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include "RouteEntity.h"
|
||||
#include "db/Database.hpp"
|
||||
|
||||
namespace NekoGui {
|
||||
QJsonArray get_as_array(const QList<QString>& str, bool castToNum = false) {
|
||||
QJsonArray res;
|
||||
for (const auto &item: str) {
|
||||
if (castToNum) res.append(item.toInt());
|
||||
else res.append(item);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
QJsonObject RouteRule::get_rule_json() const {
|
||||
QJsonObject obj;
|
||||
|
||||
if (ip_version != "") obj["ip_version"] = ip_version.toInt();
|
||||
if (network != "") obj["network"] = network;
|
||||
if (protocol != "") obj["protocol"] = protocol;
|
||||
if (!domain.empty()) obj["domain"] = get_as_array(domain);
|
||||
if (!domain_suffix.empty()) obj["domain_suffix"] = get_as_array(domain_suffix);
|
||||
if (!domain_keyword.empty()) obj["domain_keyword"] = get_as_array(domain_keyword);
|
||||
if (!domain_regex.empty()) obj["domain_regex"] = get_as_array(domain_regex);
|
||||
if (!source_ip_cidr.empty()) obj["source_ip_cidr"] = get_as_array(source_ip_cidr);
|
||||
if (source_ip_is_private != nullptr) obj["source_ip_is_private"] = *source_ip_is_private;
|
||||
if (!ip_cidr.empty()) obj["ip_cidr"] = get_as_array(ip_cidr);
|
||||
if (ip_is_private != nullptr) obj["ip_is_private"] = *ip_is_private;
|
||||
if (!source_port.empty()) obj["source_port"] = get_as_array(source_port, true);
|
||||
if (!source_port_range.empty()) obj["source_port_range"] = get_as_array(source_port_range);
|
||||
if (!port.empty()) obj["port"] = get_as_array(port, true);
|
||||
if (!port_range.empty()) obj["port_range"] = get_as_array(port_range);
|
||||
if (!process_name.empty()) obj["process_name"] = get_as_array(process_name);
|
||||
if (!process_path.empty()) obj["process_path"] = get_as_array(process_path);
|
||||
if (!rule_set.empty()) obj["rule_set"] = get_as_array(rule_set);
|
||||
if (invert) obj["invert"] = invert;
|
||||
|
||||
switch (outboundID) { // TODO use constants
|
||||
case -2:
|
||||
obj["outbound"] = "direct";
|
||||
case -3:
|
||||
obj["outbound"] = "block";
|
||||
case -4:
|
||||
obj["outbound"] = "dns_out";
|
||||
default:
|
||||
auto prof = NekoGui::profileManager->GetProfile(outboundID);
|
||||
if (prof == nullptr) {
|
||||
MW_show_log("The outbound described in the rule chain is missing, maybe your data is corrupted");
|
||||
return {};
|
||||
}
|
||||
obj["outbound"] = prof->bean->DisplayName();
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
// TODO use constant for field names
|
||||
QStringList RouteRule::get_attributes() {
|
||||
return {
|
||||
"ip_version",
|
||||
"network",
|
||||
"protocol",
|
||||
"domain",
|
||||
"domain_suffix",
|
||||
"domain_keyword",
|
||||
"domain_regex",
|
||||
"source_ip_cidr",
|
||||
"source_ip_is_private",
|
||||
"ip_cidr",
|
||||
"ip_is_private",
|
||||
"source_port",
|
||||
"source_port_range",
|
||||
"port",
|
||||
"port_range",
|
||||
"process_name",
|
||||
"process_path",
|
||||
"rule_set",
|
||||
"invert",
|
||||
};
|
||||
}
|
||||
|
||||
inputType RouteRule::get_input_type(const QString& fieldName) {
|
||||
if (fieldName == "invert" ||
|
||||
fieldName == "source_ip_is_private" ||
|
||||
fieldName == "ip_is_private") return trufalse;
|
||||
|
||||
if (fieldName == "ip_version" ||
|
||||
fieldName == "network" ||
|
||||
fieldName == "protocol") return select;
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
QStringList RouteRule::get_values_for_field(const QString& fieldName) {
|
||||
if (fieldName == "ip_version") {
|
||||
return {"4", "6"};
|
||||
}
|
||||
if (fieldName == "network") {
|
||||
return {"tcp", "udp"};
|
||||
}
|
||||
if (fieldName == "protocol") {
|
||||
return {"http", "tls", "quic", "stun", "dns", "bittorrent"};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QStringList RouteRule::get_current_value_string(const QString& fieldName) {
|
||||
if (fieldName == "ip_version" && ip_version != "") {
|
||||
return {ip_version};
|
||||
}
|
||||
if (fieldName == "network" && network != "") {
|
||||
return {network};
|
||||
}
|
||||
if (fieldName == "protocol" && protocol != "") {
|
||||
return {protocol};
|
||||
}
|
||||
if (fieldName == "domain") return domain;
|
||||
if (fieldName == "domain_suffix") return domain_suffix;
|
||||
if (fieldName == "domain_keyword") return domain_keyword;
|
||||
if (fieldName == "domain_regex") return domain_regex;
|
||||
if (fieldName == "source_ip_cidr") return source_ip_cidr;
|
||||
if (fieldName == "ip_cidr") return ip_cidr;
|
||||
if (fieldName == "source_port") return source_port;
|
||||
if (fieldName == "source_port_range") return source_port_range;
|
||||
if (fieldName == "port") return port;
|
||||
if (fieldName == "port_range") return port_range;
|
||||
if (fieldName == "process_name") return process_name;
|
||||
if (fieldName == "process_path") return process_path;
|
||||
if (fieldName == "rule_set") return rule_set;
|
||||
return {};
|
||||
}
|
||||
|
||||
bool* RouteRule::get_current_value_bool(const QString& fieldName) const {
|
||||
if (fieldName == "source_ip_is_private") {
|
||||
return source_ip_is_private;
|
||||
}
|
||||
if (fieldName == "ip_is_private") {
|
||||
return ip_is_private;
|
||||
}
|
||||
if (fieldName == "invert") {
|
||||
return reinterpret_cast<bool*>(invert);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void RouteRule::set_field_value(const QString& fieldName, const QStringList& value) {
|
||||
/*
|
||||
* "ip_version",
|
||||
"network",
|
||||
"protocol",
|
||||
"domain",
|
||||
"domain_suffix",
|
||||
"domain_keyword",
|
||||
"domain_regex",
|
||||
"source_ip_cidr",
|
||||
"source_ip_is_private",
|
||||
"ip_cidr",
|
||||
"ip_is_private",
|
||||
"source_port",
|
||||
"source_port_range",
|
||||
"port",
|
||||
"port_range",
|
||||
"process_name",
|
||||
"process_path",
|
||||
"rule_set",
|
||||
"invert",
|
||||
*/
|
||||
if (fieldName == "ip_version") {
|
||||
ip_version = value[0];
|
||||
}
|
||||
if (fieldName == "network") {
|
||||
network = value[0];
|
||||
}
|
||||
if (fieldName == "protocol") {
|
||||
protocol = value[0];
|
||||
}
|
||||
if (fieldName == "domain") {
|
||||
domain = value;
|
||||
}
|
||||
if (fieldName == "domain_suffix") {
|
||||
domain_suffix = value;
|
||||
}
|
||||
if (fieldName == "domain_keyword") {
|
||||
domain_keyword = value;
|
||||
}
|
||||
if (fieldName == "domain_regex") {
|
||||
domain_regex = value;
|
||||
}
|
||||
if (fieldName == "source_ip_cidr") {
|
||||
source_ip_cidr = value;
|
||||
}
|
||||
if (fieldName == "source_ip_is_private") {
|
||||
source_ip_is_private = reinterpret_cast<bool*>((value[0] == "true"));
|
||||
}
|
||||
if (fieldName == "ip_cidr") {
|
||||
ip_cidr = value;
|
||||
}
|
||||
if (fieldName == "ip_is_private") {
|
||||
ip_is_private = reinterpret_cast<bool*>((value[0] == "true"));
|
||||
}
|
||||
if (fieldName == "source_port") {
|
||||
source_port = value;
|
||||
}
|
||||
if (fieldName == "source_port_range") {
|
||||
source_port_range = value;
|
||||
}
|
||||
if (fieldName == "port") {
|
||||
port = value;
|
||||
}
|
||||
if (fieldName == "port_range") {
|
||||
port_range = value;
|
||||
}
|
||||
if (fieldName == "process_name") {
|
||||
process_name = value;
|
||||
}
|
||||
if (fieldName == "process_path") {
|
||||
process_path = value;
|
||||
}
|
||||
if (fieldName == "rule_set") {
|
||||
rule_set = value;
|
||||
}
|
||||
if (fieldName == "invert") {
|
||||
invert = value[0]=="true";
|
||||
}
|
||||
}
|
||||
|
||||
QJsonArray RoutingChain::get_route_rules() {
|
||||
QJsonArray res;
|
||||
for (const auto &item: Rules) {
|
||||
auto rule_json = item->get_rule_json();
|
||||
if (rule_json.empty()) {
|
||||
MW_show_log("Aborted generating routing section, an error has occurred");
|
||||
return {};
|
||||
}
|
||||
res += rule_json;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::shared_ptr<RoutingChain> RoutingChain::GetDefaultChain() {
|
||||
auto defaultChain = RoutingChain();
|
||||
defaultChain.name = "Default";
|
||||
auto defaultRule = RouteRule();
|
||||
defaultRule.protocol = {"dns"};
|
||||
defaultRule.outboundID = -4;
|
||||
defaultChain.Rules << std::make_shared<RouteRule>(defaultRule);
|
||||
return std::make_shared<RoutingChain>(defaultChain);
|
||||
}
|
||||
}
|
||||
@ -3,11 +3,14 @@
|
||||
#include "main/NekoGui.hpp"
|
||||
|
||||
namespace NekoGui {
|
||||
enum inputType {trufalse, select, text};
|
||||
|
||||
class RouteRule : public JsonStore {
|
||||
public:
|
||||
int ip_version = 0;
|
||||
QList<QString> network;
|
||||
QList<QString> protocol;
|
||||
QString name = "";
|
||||
QString ip_version;
|
||||
QString network;
|
||||
QString protocol;
|
||||
QList<QString> domain;
|
||||
QList<QString> domain_suffix;
|
||||
QList<QString> domain_keyword;
|
||||
@ -24,11 +27,15 @@ namespace NekoGui {
|
||||
QList<QString> process_path;
|
||||
QList<QString> rule_set;
|
||||
bool invert = false;
|
||||
int outboundID = -1;
|
||||
int outboundID = -1; // -2 is direct -3 is block -4 is dns_out
|
||||
|
||||
QList<QString> check_for_errors();
|
||||
|
||||
QJsonObject get_rule_json();
|
||||
[[nodiscard]] QJsonObject get_rule_json() const;
|
||||
static QStringList get_attributes();
|
||||
static inputType get_input_type(const QString& fieldName);
|
||||
static QStringList get_values_for_field(const QString& fieldName);
|
||||
QStringList get_current_value_string(const QString& fieldName);
|
||||
[[nodiscard]] bool* get_current_value_bool(const QString& fieldName) const;
|
||||
void set_field_value(const QString& fieldName, const QStringList& value);
|
||||
};
|
||||
|
||||
class RoutingChain : public JsonStore {
|
||||
@ -39,6 +46,6 @@ namespace NekoGui {
|
||||
|
||||
QJsonArray get_route_rules();
|
||||
|
||||
QJsonArray get_default_route_rules();
|
||||
static std::shared_ptr<RoutingChain> GetDefaultChain();
|
||||
};
|
||||
} // namespace NekoGui
|
||||
@ -327,32 +327,9 @@ namespace NekoGui {
|
||||
|
||||
// preset routing
|
||||
Routing::Routing(int preset) : JsonStore() {
|
||||
if (preset == 1) {
|
||||
direct_ip =
|
||||
"geoip:cn\n"
|
||||
"geoip:private";
|
||||
direct_domain = "geosite:cn";
|
||||
proxy_ip = "";
|
||||
proxy_domain = "";
|
||||
block_ip = "";
|
||||
block_domain =
|
||||
"geosite:category-ads-all\n"
|
||||
"domain:appcenter.ms\n"
|
||||
"domain:firebase.io\n"
|
||||
"domain:crashlytics.com\n";
|
||||
}
|
||||
if (IS_NEKO_BOX) {
|
||||
if (!Preset::SingBox::DomainStrategy.contains(domain_strategy)) domain_strategy = "";
|
||||
if (!Preset::SingBox::DomainStrategy.contains(outbound_domain_strategy)) outbound_domain_strategy = "";
|
||||
}
|
||||
_add(new configItem("direct_ip", &this->direct_ip, itemType::string));
|
||||
_add(new configItem("direct_domain", &this->direct_domain, itemType::string));
|
||||
_add(new configItem("proxy_ip", &this->proxy_ip, itemType::string));
|
||||
_add(new configItem("proxy_domain", &this->proxy_domain, itemType::string));
|
||||
_add(new configItem("block_ip", &this->block_ip, itemType::string));
|
||||
_add(new configItem("block_domain", &this->block_domain, itemType::string));
|
||||
_add(new configItem("def_outbound", &this->def_outbound, itemType::string));
|
||||
_add(new configItem("custom", &this->custom, itemType::string));
|
||||
if (!Preset::SingBox::DomainStrategy.contains(domain_strategy)) domain_strategy = "";
|
||||
if (!Preset::SingBox::DomainStrategy.contains(outbound_domain_strategy)) outbound_domain_strategy = "";
|
||||
_add(new configItem("current_route_id", &this->current_route_id, itemType::integer));
|
||||
//
|
||||
_add(new configItem("remote_dns", &this->remote_dns, itemType::string));
|
||||
_add(new configItem("remote_dns_strategy", &this->remote_dns_strategy, itemType::string));
|
||||
@ -367,18 +344,6 @@ namespace NekoGui {
|
||||
_add(new configItem("dns_final_out", &this->dns_final_out, itemType::string));
|
||||
}
|
||||
|
||||
QString Routing::DisplayRouting() const {
|
||||
return QString("[Proxy] %1\n[Proxy] %2\n[Direct] %3\n[Direct] %4\n[Block] %5\n[Block] %6\n[Default Outbound] %7\n[DNS] %8")
|
||||
.arg(SplitLinesSkipSharp(proxy_domain).join(","), 10)
|
||||
.arg(SplitLinesSkipSharp(proxy_ip).join(","), 10)
|
||||
.arg(SplitLinesSkipSharp(direct_domain).join(","), 10)
|
||||
.arg(SplitLinesSkipSharp(direct_ip).join(","), 10)
|
||||
.arg(SplitLinesSkipSharp(block_domain).join(","), 10)
|
||||
.arg(SplitLinesSkipSharp(block_ip).join(","), 10)
|
||||
.arg(def_outbound)
|
||||
.arg(use_dns_object ? "DNS Object" : "Simple DNS");
|
||||
}
|
||||
|
||||
QStringList Routing::List() {
|
||||
QDir dr(ROUTES_PREFIX);
|
||||
return dr.entryList(QDir::Files);
|
||||
|
||||
@ -4,14 +4,8 @@ namespace NekoGui {
|
||||
|
||||
class Routing : public JsonStore {
|
||||
public:
|
||||
QString direct_ip;
|
||||
QString direct_domain;
|
||||
QString proxy_ip;
|
||||
QString proxy_domain;
|
||||
QString block_ip;
|
||||
QString block_domain;
|
||||
int current_route_id = 0;
|
||||
QString def_outbound = "proxy";
|
||||
QString custom = "{\"rules\": []}";
|
||||
|
||||
// DNS
|
||||
QString remote_dns = "https://8.8.8.8/dns-query";
|
||||
@ -30,8 +24,6 @@ namespace NekoGui {
|
||||
|
||||
explicit Routing(int preset = 0);
|
||||
|
||||
[[nodiscard]] QString DisplayRouting() const;
|
||||
|
||||
static QStringList List();
|
||||
|
||||
static bool SetToActive(const QString &name);
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
#include "dialog_manage_routes.h"
|
||||
#include "ui_dialog_manage_routes.h"
|
||||
#include "db/Database.hpp"
|
||||
//#include "ui_RouteItem.h"
|
||||
|
||||
#include "3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp"
|
||||
#include "3rdparty/qv2ray/v3/components/GeositeReader/GeositeReader.hpp"
|
||||
@ -8,38 +10,63 @@
|
||||
|
||||
#include <QFile>
|
||||
#include <QMessageBox>
|
||||
#include <QListWidget>
|
||||
#include <QLineEdit>
|
||||
|
||||
#define REFRESH_ACTIVE_ROUTING(name, obj) \
|
||||
this->active_routing = name; \
|
||||
setWindowTitle(title_base + " [" + name + "]"); \
|
||||
UpdateDisplayRouting(obj, false);
|
||||
QList<QString> getRouteProfiles() {
|
||||
auto routeProfiles = NekoGui::profileManager->routes;
|
||||
QList<QString> res;
|
||||
|
||||
for (const auto &item: routeProfiles) {
|
||||
res << item.second->name;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int getRouteID(const QString& name) {
|
||||
auto routeProfiles = NekoGui::profileManager->routes;
|
||||
|
||||
for (const auto &item: routeProfiles) {
|
||||
if (item.second->name == name) return item.first;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
QString getRouteName(int id) {
|
||||
return NekoGui::profileManager->routes.count(id) ? NekoGui::profileManager->routes[id]->name : "";
|
||||
}
|
||||
|
||||
QList<QString> deleteItemFromList(const QList<QString>& base, const QString& target) {
|
||||
QList<QString> res;
|
||||
for (const auto &item: base) {
|
||||
if (item == target) continue;
|
||||
res << item;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void DialogManageRoutes::reloadProfileItems() {
|
||||
ui->route_prof->clear();
|
||||
ui->route_profiles->clear();
|
||||
ui->route_prof->addItems(currentRouteProfiles);
|
||||
ui->route_profiles->addItems(currentRouteProfiles);
|
||||
}
|
||||
|
||||
DialogManageRoutes::DialogManageRoutes(QWidget *parent) : QDialog(parent), ui(new Ui::DialogManageRoutes) {
|
||||
ui->setupUi(this);
|
||||
title_base = windowTitle();
|
||||
currentRouteProfiles = getRouteProfiles();
|
||||
|
||||
QStringList qsValue = {""};
|
||||
QString dnsHelpDocumentUrl;
|
||||
if (IS_NEKO_BOX) {
|
||||
ui->outbound_domain_strategy->addItems(Preset::SingBox::DomainStrategy);
|
||||
ui->domainStrategyCombo->addItems(Preset::SingBox::DomainStrategy);
|
||||
qsValue += QString("prefer_ipv4 prefer_ipv6 ipv4_only ipv6_only").split(" ");
|
||||
ui->dns_object->setPlaceholderText(DecodeB64IfValid("ewogICJzZXJ2ZXJzIjogW10sCiAgInJ1bGVzIjogW10sCiAgImZpbmFsIjogIiIsCiAgInN0cmF0ZWd5IjogIiIsCiAgImRpc2FibGVfY2FjaGUiOiBmYWxzZSwKICAiZGlzYWJsZV9leHBpcmUiOiBmYWxzZSwKICAiaW5kZXBlbmRlbnRfY2FjaGUiOiBmYWxzZSwKICAicmV2ZXJzZV9tYXBwaW5nIjogZmFsc2UsCiAgImZha2VpcCI6IHt9Cn0="));
|
||||
dnsHelpDocumentUrl = "https://sing-box.sagernet.org/configuration/dns/";
|
||||
} else {
|
||||
ui->outbound_domain_strategy->addItems({"AsIs", "UseIPv4", "UseIPv6", "PreferIPv4", "PreferIPv6"});
|
||||
ui->domainStrategyCombo->addItems({"AsIs", "IPIfNonMatch", "IPOnDemand"});
|
||||
qsValue += QString("use_ip use_ip4 use_ip6").split(" ");
|
||||
ui->dns_object->setPlaceholderText(DecodeB64IfValid("ewogICJzZXJ2ZXJzIjogW10KfQ=="));
|
||||
dnsHelpDocumentUrl = "https://www.v2fly.org/config/dns.html";
|
||||
}
|
||||
|
||||
ui->outbound_domain_strategy->addItems(Preset::SingBox::DomainStrategy);
|
||||
ui->domainStrategyCombo->addItems(Preset::SingBox::DomainStrategy);
|
||||
qsValue += QString("prefer_ipv4 prefer_ipv6 ipv4_only ipv6_only").split(" ");
|
||||
ui->dns_object->setPlaceholderText(DecodeB64IfValid("ewogICJzZXJ2ZXJzIjogW10sCiAgInJ1bGVzIjogW10sCiAgImZpbmFsIjogIiIsCiAgInN0cmF0ZWd5IjogIiIsCiAgImRpc2FibGVfY2FjaGUiOiBmYWxzZSwKICAiZGlzYWJsZV9leHBpcmUiOiBmYWxzZSwKICAiaW5kZXBlbmRlbnRfY2FjaGUiOiBmYWxzZSwKICAicmV2ZXJzZV9tYXBwaW5nIjogZmFsc2UsCiAgImZha2VpcCI6IHt9Cn0="));
|
||||
dnsHelpDocumentUrl = "https://sing-box.sagernet.org/configuration/dns/";
|
||||
|
||||
ui->direct_dns_strategy->addItems(qsValue);
|
||||
ui->remote_dns_strategy->addItems(qsValue);
|
||||
//
|
||||
D_C_LOAD_STRING(custom_route_global)
|
||||
//
|
||||
connect(ui->use_dns_object, &QCheckBox::stateChanged, this, [=](int state) {
|
||||
auto useDNSObject = state == Qt::Checked;
|
||||
ui->simple_dns_box->setDisabled(useDNSObject);
|
||||
@ -57,40 +84,28 @@ DialogManageRoutes::DialogManageRoutes(QWidget *parent) : QDialog(parent), ui(ne
|
||||
ui->dns_object->setPlainText(QJsonObject2QString(obj, false));
|
||||
}
|
||||
});
|
||||
//
|
||||
connect(ui->custom_route_edit, &QPushButton::clicked, this, [=] {
|
||||
C_EDIT_JSON_ALLOW_EMPTY(custom_route)
|
||||
});
|
||||
connect(ui->custom_route_global_edit, &QPushButton::clicked, this, [=] {
|
||||
C_EDIT_JSON_ALLOW_EMPTY(custom_route_global)
|
||||
});
|
||||
//
|
||||
builtInSchemesMenu = new QMenu(this);
|
||||
builtInSchemesMenu->addActions(this->getBuiltInSchemes());
|
||||
ui->preset->setMenu(builtInSchemesMenu);
|
||||
ui->sniffing_mode->setCurrentIndex(NekoGui::dataStore->routing->sniffing_mode);
|
||||
ui->outbound_domain_strategy->setCurrentText(NekoGui::dataStore->routing->outbound_domain_strategy);
|
||||
ui->domainStrategyCombo->setCurrentText(NekoGui::dataStore->routing->domain_strategy);
|
||||
ui->use_dns_object->setChecked(NekoGui::dataStore->routing->use_dns_object);
|
||||
ui->dns_object->setPlainText(NekoGui::dataStore->routing->dns_object);
|
||||
ui->dns_routing->setChecked(NekoGui::dataStore->routing->dns_routing);
|
||||
ui->remote_dns->setCurrentText(NekoGui::dataStore->routing->remote_dns);
|
||||
ui->remote_dns_strategy->setCurrentText(NekoGui::dataStore->routing->remote_dns_strategy);
|
||||
ui->direct_dns->setCurrentText(NekoGui::dataStore->routing->direct_dns);
|
||||
ui->direct_dns_strategy->setCurrentText(NekoGui::dataStore->routing->direct_dns_strategy);
|
||||
ui->dns_final_out->setCurrentText(NekoGui::dataStore->routing->dns_final_out);
|
||||
reloadProfileItems();
|
||||
ui->route_prof->setCurrentText(getRouteName(NekoGui::dataStore->routing->current_route_id));
|
||||
|
||||
|
||||
connect(ui->delete_route, &QPushButton::clicked, this, [=]{
|
||||
auto current = ui->route_profiles->currentItem()->text();
|
||||
currentRouteProfiles = deleteItemFromList(currentRouteProfiles, current);
|
||||
reloadProfileItems();
|
||||
});
|
||||
|
||||
|
||||
QString geoipFn = NekoGui::FindCoreAsset("geoip.dat");
|
||||
QString geositeFn = NekoGui::FindCoreAsset("geosite.dat");
|
||||
//
|
||||
const auto sourceStringsDomain = Qv2ray::components::GeositeReader::ReadGeoSiteFromFile(geositeFn);
|
||||
directDomainTxt = new AutoCompleteTextEdit("geosite", sourceStringsDomain, this);
|
||||
proxyDomainTxt = new AutoCompleteTextEdit("geosite", sourceStringsDomain, this);
|
||||
blockDomainTxt = new AutoCompleteTextEdit("geosite", sourceStringsDomain, this);
|
||||
//
|
||||
const auto sourceStringsIP = Qv2ray::components::GeositeReader::ReadGeoSiteFromFile(geoipFn);
|
||||
directIPTxt = new AutoCompleteTextEdit("geoip", sourceStringsIP, this);
|
||||
proxyIPTxt = new AutoCompleteTextEdit("geoip", sourceStringsIP, this);
|
||||
blockIPTxt = new AutoCompleteTextEdit("geoip", sourceStringsIP, this);
|
||||
//
|
||||
ui->directTxtLayout->addWidget(directDomainTxt, 0, 0);
|
||||
ui->proxyTxtLayout->addWidget(proxyDomainTxt, 0, 0);
|
||||
ui->blockTxtLayout->addWidget(blockDomainTxt, 0, 0);
|
||||
//
|
||||
ui->directIPLayout->addWidget(directIPTxt, 0, 0);
|
||||
ui->proxyIPLayout->addWidget(proxyIPTxt, 0, 0);
|
||||
ui->blockIPLayout->addWidget(blockIPTxt, 0, 0);
|
||||
//
|
||||
REFRESH_ACTIVE_ROUTING(NekoGui::dataStore->active_routing, NekoGui::dataStore->routing.get())
|
||||
|
||||
ADD_ASTERISK(this)
|
||||
}
|
||||
@ -100,158 +115,24 @@ DialogManageRoutes::~DialogManageRoutes() {
|
||||
}
|
||||
|
||||
void DialogManageRoutes::accept() {
|
||||
D_C_SAVE_STRING(custom_route_global)
|
||||
bool routeChanged = false;
|
||||
if (NekoGui::dataStore->active_routing != active_routing) routeChanged = true;
|
||||
SaveDisplayRouting(NekoGui::dataStore->routing.get());
|
||||
NekoGui::dataStore->active_routing = active_routing;
|
||||
NekoGui::dataStore->routing->fn = ROUTES_PREFIX + NekoGui::dataStore->active_routing;
|
||||
if (NekoGui::dataStore->routing->Save()) routeChanged = true;
|
||||
//
|
||||
QString info = "UpdateDataStore";
|
||||
if (routeChanged) info += "RouteChanged";
|
||||
MW_dialog_message(Dialog_DialogManageRoutes, info);
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
// built in settings
|
||||
|
||||
QList<QAction *> DialogManageRoutes::getBuiltInSchemes() {
|
||||
QList<QAction *> list;
|
||||
list.append(this->schemeToAction(tr("Bypass LAN and China"), routing_cn_lan));
|
||||
list.append(this->schemeToAction(tr("Global"), routing_global));
|
||||
return list;
|
||||
}
|
||||
|
||||
QAction *DialogManageRoutes::schemeToAction(const QString &name, const NekoGui::Routing &scheme) {
|
||||
auto *action = new QAction(name, this);
|
||||
connect(action, &QAction::triggered, [this, &scheme] { this->UpdateDisplayRouting((NekoGui::Routing *) &scheme, true); });
|
||||
return action;
|
||||
}
|
||||
|
||||
void DialogManageRoutes::UpdateDisplayRouting(NekoGui::Routing *conf, bool qv) {
|
||||
//
|
||||
directDomainTxt->setPlainText(conf->direct_domain);
|
||||
proxyDomainTxt->setPlainText(conf->proxy_domain);
|
||||
blockDomainTxt->setPlainText(conf->block_domain);
|
||||
//
|
||||
blockIPTxt->setPlainText(conf->block_ip);
|
||||
directIPTxt->setPlainText(conf->direct_ip);
|
||||
proxyIPTxt->setPlainText(conf->proxy_ip);
|
||||
//
|
||||
CACHE.custom_route = conf->custom;
|
||||
ui->def_outbound->setCurrentText(conf->def_outbound);
|
||||
//
|
||||
if (qv) return;
|
||||
//
|
||||
ui->sniffing_mode->setCurrentIndex(conf->sniffing_mode);
|
||||
ui->outbound_domain_strategy->setCurrentText(conf->outbound_domain_strategy);
|
||||
ui->domainStrategyCombo->setCurrentText(conf->domain_strategy);
|
||||
ui->use_dns_object->setChecked(conf->use_dns_object);
|
||||
ui->dns_object->setPlainText(conf->dns_object);
|
||||
ui->dns_routing->setChecked(conf->dns_routing);
|
||||
ui->remote_dns->setCurrentText(conf->remote_dns);
|
||||
ui->remote_dns_strategy->setCurrentText(conf->remote_dns_strategy);
|
||||
ui->direct_dns->setCurrentText(conf->direct_dns);
|
||||
ui->direct_dns_strategy->setCurrentText(conf->direct_dns_strategy);
|
||||
ui->dns_final_out->setCurrentText(conf->dns_final_out);
|
||||
}
|
||||
|
||||
void DialogManageRoutes::SaveDisplayRouting(NekoGui::Routing *conf) {
|
||||
conf->direct_ip = directIPTxt->toPlainText();
|
||||
conf->direct_domain = directDomainTxt->toPlainText();
|
||||
conf->proxy_ip = proxyIPTxt->toPlainText();
|
||||
conf->proxy_domain = proxyDomainTxt->toPlainText();
|
||||
conf->block_ip = blockIPTxt->toPlainText();
|
||||
conf->block_domain = blockDomainTxt->toPlainText();
|
||||
conf->def_outbound = ui->def_outbound->currentText();
|
||||
conf->custom = CACHE.custom_route;
|
||||
//
|
||||
conf->sniffing_mode = ui->sniffing_mode->currentIndex();
|
||||
conf->domain_strategy = ui->domainStrategyCombo->currentText();
|
||||
conf->outbound_domain_strategy = ui->outbound_domain_strategy->currentText();
|
||||
conf->use_dns_object = ui->use_dns_object->isChecked();
|
||||
conf->dns_object = ui->dns_object->toPlainText();
|
||||
conf->dns_routing = ui->dns_routing->isChecked();
|
||||
conf->remote_dns = ui->remote_dns->currentText();
|
||||
conf->remote_dns_strategy = ui->remote_dns_strategy->currentText();
|
||||
conf->direct_dns = ui->direct_dns->currentText();
|
||||
conf->direct_dns_strategy = ui->direct_dns_strategy->currentText();
|
||||
conf->dns_final_out = ui->dns_final_out->currentText();
|
||||
}
|
||||
|
||||
void DialogManageRoutes::on_load_save_clicked() {
|
||||
auto w = new QDialog;
|
||||
auto layout = new QVBoxLayout;
|
||||
w->setLayout(layout);
|
||||
auto lineEdit = new QLineEdit;
|
||||
layout->addWidget(lineEdit);
|
||||
auto list = new QListWidget;
|
||||
layout->addWidget(list);
|
||||
for (const auto &name: NekoGui::Routing::List()) {
|
||||
list->addItem(name);
|
||||
if (currentRouteProfiles.empty()) {
|
||||
MessageBoxInfo("Invalid settings", "Routing profile cannot be empty");
|
||||
return;
|
||||
}
|
||||
connect(list, &QListWidget::currentTextChanged, lineEdit, &QLineEdit::setText);
|
||||
auto bottom = new QHBoxLayout;
|
||||
layout->addLayout(bottom);
|
||||
auto load = new QPushButton;
|
||||
load->setText(tr("Load"));
|
||||
bottom->addWidget(load);
|
||||
auto save = new QPushButton;
|
||||
save->setText(tr("Save"));
|
||||
bottom->addWidget(save);
|
||||
auto remove = new QPushButton;
|
||||
remove->setText(tr("Remove"));
|
||||
bottom->addWidget(remove);
|
||||
auto cancel = new QPushButton;
|
||||
cancel->setText(tr("Cancel"));
|
||||
bottom->addWidget(cancel);
|
||||
connect(load, &QPushButton::clicked, w, [=] {
|
||||
auto fn = lineEdit->text();
|
||||
if (!fn.isEmpty()) {
|
||||
auto r = std::make_unique<NekoGui::Routing>();
|
||||
r->load_control_must = true;
|
||||
r->fn = ROUTES_PREFIX + fn;
|
||||
if (r->Load()) {
|
||||
if (QMessageBox::question(nullptr, software_name, tr("Load routing: %1").arg(fn) + "\n" + r->DisplayRouting()) == QMessageBox::Yes) {
|
||||
REFRESH_ACTIVE_ROUTING(fn, r.get()) // temp save to the window
|
||||
w->accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(save, &QPushButton::clicked, w, [=] {
|
||||
auto fn = lineEdit->text();
|
||||
if (!fn.isEmpty()) {
|
||||
auto r = std::make_unique<NekoGui::Routing>();
|
||||
SaveDisplayRouting(r.get());
|
||||
r->fn = ROUTES_PREFIX + fn;
|
||||
if (QMessageBox::question(nullptr, software_name, tr("Save routing: %1").arg(fn) + "\n" + r->DisplayRouting()) == QMessageBox::Yes) {
|
||||
r->Save();
|
||||
REFRESH_ACTIVE_ROUTING(fn, r.get())
|
||||
w->accept();
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(remove, &QPushButton::clicked, w, [=] {
|
||||
auto fn = lineEdit->text();
|
||||
if (!fn.isEmpty() && NekoGui::Routing::List().length() > 1) {
|
||||
if (QMessageBox::question(nullptr, software_name, tr("Remove routing: %1").arg(fn)) == QMessageBox::Yes) {
|
||||
QFile f(ROUTES_PREFIX + fn);
|
||||
f.remove();
|
||||
if (NekoGui::dataStore->active_routing == fn) {
|
||||
NekoGui::Routing::SetToActive(NekoGui::Routing::List().first());
|
||||
REFRESH_ACTIVE_ROUTING(NekoGui::dataStore->active_routing, NekoGui::dataStore->routing.get())
|
||||
}
|
||||
w->accept();
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(cancel, &QPushButton::clicked, w, &QDialog::accept);
|
||||
connect(list, &QListWidget::itemDoubleClicked, this, [=](QListWidgetItem *item) {
|
||||
lineEdit->setText(item->text());
|
||||
emit load->clicked();
|
||||
});
|
||||
w->exec();
|
||||
w->deleteLater();
|
||||
}
|
||||
|
||||
NekoGui::dataStore->routing->sniffing_mode = ui->sniffing_mode->currentIndex();
|
||||
NekoGui::dataStore->routing->domain_strategy = ui->domainStrategyCombo->currentText();
|
||||
NekoGui::dataStore->routing->outbound_domain_strategy = ui->outbound_domain_strategy->currentText();
|
||||
NekoGui::dataStore->routing->use_dns_object = ui->use_dns_object->isChecked();
|
||||
NekoGui::dataStore->routing->dns_object = ui->dns_object->toPlainText();
|
||||
NekoGui::dataStore->routing->dns_routing = ui->dns_routing->isChecked();
|
||||
NekoGui::dataStore->routing->remote_dns = ui->remote_dns->currentText();
|
||||
NekoGui::dataStore->routing->remote_dns_strategy = ui->remote_dns_strategy->currentText();
|
||||
NekoGui::dataStore->routing->direct_dns = ui->direct_dns->currentText();
|
||||
NekoGui::dataStore->routing->direct_dns_strategy = ui->direct_dns_strategy->currentText();
|
||||
NekoGui::dataStore->routing->dns_final_out = ui->dns_final_out->currentText();
|
||||
|
||||
// TODO add mine
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
@ -23,37 +23,15 @@ public:
|
||||
private:
|
||||
Ui::DialogManageRoutes *ui;
|
||||
|
||||
struct {
|
||||
QString custom_route;
|
||||
QString custom_route_global;
|
||||
} CACHE;
|
||||
|
||||
QMenu *builtInSchemesMenu;
|
||||
Qv2ray::ui::widgets::AutoCompleteTextEdit *directDomainTxt;
|
||||
Qv2ray::ui::widgets::AutoCompleteTextEdit *proxyDomainTxt;
|
||||
Qv2ray::ui::widgets::AutoCompleteTextEdit *blockDomainTxt;
|
||||
//
|
||||
Qv2ray::ui::widgets::AutoCompleteTextEdit *directIPTxt;
|
||||
Qv2ray::ui::widgets::AutoCompleteTextEdit *blockIPTxt;
|
||||
Qv2ray::ui::widgets::AutoCompleteTextEdit *proxyIPTxt;
|
||||
//
|
||||
NekoGui::Routing routing_cn_lan = NekoGui::Routing(1);
|
||||
NekoGui::Routing routing_global = NekoGui::Routing(0);
|
||||
//
|
||||
QString title_base;
|
||||
QString active_routing;
|
||||
void reloadProfileItems();
|
||||
|
||||
QList<QString> currentRouteProfiles;
|
||||
public slots:
|
||||
|
||||
void accept() override;
|
||||
|
||||
QList<QAction *> getBuiltInSchemes();
|
||||
void on_new_route_clicked();
|
||||
|
||||
QAction *schemeToAction(const QString &name, const NekoGui::Routing &scheme);
|
||||
void on_edit_route_clicked();
|
||||
|
||||
void UpdateDisplayRouting(NekoGui::Routing *conf, bool qv);
|
||||
|
||||
void SaveDisplayRouting(NekoGui::Routing *conf);
|
||||
|
||||
void on_load_save_clicked();
|
||||
void on_delete_route_clicked();
|
||||
};
|
||||
|
||||
@ -27,47 +27,18 @@
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="4" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="outbound_domain_strategy">
|
||||
<property name="editable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Route sets</string>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Server Address Strategy</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<item>
|
||||
<widget class="QPushButton" name="load_save">
|
||||
<property name="text">
|
||||
<string>Mange route set</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="custom_route_global_edit">
|
||||
<property name="text">
|
||||
<string>Custom Route (global)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Note: Other settings are independent for each route set.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
@ -81,8 +52,15 @@ For sing-box, it sets inbound.domain_strategy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="outbound_domain_strategy">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Sniffing Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="domainStrategyCombo">
|
||||
<property name="editable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
@ -107,40 +85,16 @@ For sing-box, it sets inbound.domain_strategy</string>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="route_prof"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="route_prof_l">
|
||||
<property name="text">
|
||||
<string>Sniffing Mode</string>
|
||||
<string>Routing Profile</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Server Address Strategy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="domainStrategyCombo">
|
||||
<property name="editable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -182,6 +136,11 @@ For more information, see the document "Configuration/DNS".</string>
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>8.8.8.8</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">https://8.8.8.8/dns-query</string>
|
||||
@ -414,188 +373,44 @@ For more information, see the document "Configuration/DNS".</string>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>Simple Route</string>
|
||||
<string>Route</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gb2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<widget class="QGroupBox" name="route_profiles_box">
|
||||
<property name="title">
|
||||
<string>Routing Profiles</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_3" rowstretch="0,1,1" columnstretch="0,1,1,1">
|
||||
<item row="0" column="3">
|
||||
<widget class="QLabel" name="label_82">
|
||||
<property name="text">
|
||||
<string>Block</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<layout class="QGridLayout" name="blockTxtLayout"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label_80">
|
||||
<property name="text">
|
||||
<string>Direct</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Domain</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QGridLayout" name="directTxtLayout"/>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<layout class="QGridLayout" name="proxyIPLayout"/>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="label_81">
|
||||
<property name="text">
|
||||
<string>Proxy</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<layout class="QGridLayout" name="proxyTxtLayout"/>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<layout class="QGridLayout" name="blockIPLayout"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>IP</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QGridLayout" name="directIPLayout"/>
|
||||
</item>
|
||||
</layout>
|
||||
<widget class="QListWidget" name="route_profiles"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QWidget" name="horizontalWidget_2" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
<widget class="QWidget" name="widget_2" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="new_route">
|
||||
<property name="text">
|
||||
<string>New</string>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="edit_route">
|
||||
<property name="text">
|
||||
<string>Edit</string>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="delete_route">
|
||||
<property name="text">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QToolButton" name="preset">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Preset</string>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::InstantPopup</enum>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextBesideIcon</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="custom_route_edit">
|
||||
<property name="text">
|
||||
<string>Custom Route</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="horizontalWidget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Default Outbound</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="def_outbound">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">proxy</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">bypass</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">block</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@ -628,8 +443,6 @@ For more information, see the document "Configuration/DNS".</string>
|
||||
<tabstop>sniffing_mode</tabstop>
|
||||
<tabstop>domainStrategyCombo</tabstop>
|
||||
<tabstop>outbound_domain_strategy</tabstop>
|
||||
<tabstop>load_save</tabstop>
|
||||
<tabstop>custom_route_global_edit</tabstop>
|
||||
<tabstop>remote_dns</tabstop>
|
||||
<tabstop>remote_dns_strategy</tabstop>
|
||||
<tabstop>direct_dns</tabstop>
|
||||
@ -640,9 +453,6 @@ For more information, see the document "Configuration/DNS".</string>
|
||||
<tabstop>format_dns_object</tabstop>
|
||||
<tabstop>dns_document</tabstop>
|
||||
<tabstop>dns_object</tabstop>
|
||||
<tabstop>preset</tabstop>
|
||||
<tabstop>custom_route_edit</tabstop>
|
||||
<tabstop>def_outbound</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
|
||||
@ -304,7 +304,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
|
||||
r.load_control_must = true;
|
||||
r.fn = ROUTES_PREFIX + fn;
|
||||
if (r.Load()) {
|
||||
if (QMessageBox::question(GetMessageBoxParent(), software_name, tr("Load routing and apply: %1").arg(fn) + "\n" + r.DisplayRouting()) == QMessageBox::Yes) {
|
||||
if (QMessageBox::question(GetMessageBoxParent(), software_name, tr("Load routing and apply: %1").arg(fn) + "\n" ) == QMessageBox::Yes) {
|
||||
NekoGui::Routing::SetToActive(fn);
|
||||
if (NekoGui::dataStore->started_id >= 0) {
|
||||
neko_start(NekoGui::dataStore->started_id);
|
||||
@ -1577,32 +1577,6 @@ void MainWindow::on_masterLogBrowser_customContextMenuRequested(const QPoint &po
|
||||
auto item = QInputDialog::getItem(GetMessageBoxParent(), tr("Save as route"),
|
||||
tr("Save \"%1\" as a routing rule?").arg(newStr),
|
||||
items, select, false, &ok);
|
||||
if (ok) {
|
||||
auto index = items.indexOf(item);
|
||||
switch (index) {
|
||||
case 0:
|
||||
ADD_TO_CURRENT_ROUTE(proxy_ip, newStr);
|
||||
break;
|
||||
case 1:
|
||||
ADD_TO_CURRENT_ROUTE(direct_ip, newStr);
|
||||
break;
|
||||
case 2:
|
||||
ADD_TO_CURRENT_ROUTE(block_ip, newStr);
|
||||
break;
|
||||
case 3:
|
||||
ADD_TO_CURRENT_ROUTE(proxy_domain, newStr);
|
||||
break;
|
||||
case 4:
|
||||
ADD_TO_CURRENT_ROUTE(direct_domain, newStr);
|
||||
break;
|
||||
case 5:
|
||||
ADD_TO_CURRENT_ROUTE(block_domain, newStr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
MW_dialog_message("", "UpdateDataStore,RouteChanged");
|
||||
}
|
||||
});
|
||||
menu->addAction(action_add_route);
|
||||
|
||||
|
||||
122
ui/widget/RouteItem.cpp
Normal file
122
ui/widget/RouteItem.cpp
Normal file
@ -0,0 +1,122 @@
|
||||
#include "RouteItem.h"
|
||||
#include "ui_RouteItem.h"
|
||||
#include "db/RouteEntity.h"
|
||||
#include "db/Database.hpp"
|
||||
|
||||
#define ADJUST_SIZE runOnUiThread([=] { adjustSize(); adjustPosition(mainwindow); }, this);
|
||||
|
||||
int RouteItem::getIndexOf(const QString& name) const {
|
||||
for (int i=0;i<chain->Rules.size();i++) {
|
||||
if (chain->Rules[i]->name == name) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
QString get_outbound_name(int id) {
|
||||
// -2 is direct -3 is block -4 is dns_out
|
||||
if (id == -2) return "direct";
|
||||
if (id == -3) return "block";
|
||||
if (id == -4) return "dns_out";
|
||||
auto profiles = NekoGui::profileManager->profiles;
|
||||
if (profiles.count(id)) return profiles[id]->bean->name;
|
||||
return "INVALID OUTBOUND";
|
||||
}
|
||||
|
||||
QStringList get_all_outbounds() {
|
||||
QStringList res;
|
||||
auto profiles = NekoGui::profileManager->profiles;
|
||||
for (const auto &item: profiles) {
|
||||
res.append(item.second->bean->name);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
RouteItem::RouteItem(QWidget *parent, const std::shared_ptr<NekoGui::RoutingChain>& routeChain)
|
||||
: QGroupBox(parent), ui(new Ui::RouteItem) {
|
||||
ui->setupUi(this);
|
||||
|
||||
// make a copy
|
||||
chain = routeChain;
|
||||
|
||||
for (const auto &item: chain->Rules) {
|
||||
ui->route_items->addItem(item->name);
|
||||
}
|
||||
|
||||
QStringList outboundOptions = {"direct", "block", "dns_out"};
|
||||
outboundOptions << get_all_outbounds();
|
||||
|
||||
ui->rule_attr->addItems(NekoGui::RouteRule::get_attributes());
|
||||
ui->rule_out->addItems(outboundOptions);
|
||||
ui->rule_attr_text->hide();
|
||||
ui->rule_attr_data->setTitle("");
|
||||
|
||||
connect(ui->rule_attr_selector, &QComboBox::currentTextChanged, this, [=](const QString& text){
|
||||
if (currentIndex == -1) return;
|
||||
chain->Rules[currentIndex]->set_field_value(ui->rule_attr->currentText(), {text});
|
||||
});
|
||||
|
||||
connect(ui->rule_attr_text, &QTextEdit::textChanged, this, [=] {
|
||||
if (currentIndex == -1) return;
|
||||
auto currentVal = ui->rule_attr_text->toPlainText().split('\n');
|
||||
chain->Rules[currentIndex]->set_field_value(ui->rule_attr->currentText(), currentVal);
|
||||
});
|
||||
|
||||
connect(ui->route_items, &QListWidget::itemClicked, this, [=](const QListWidgetItem *item) {
|
||||
auto idx = getIndexOf(item->text());
|
||||
if (idx == -1) return;
|
||||
currentIndex = idx;
|
||||
auto ruleItem = chain->Rules[idx];
|
||||
ui->rule_out->setCurrentText(get_outbound_name(ruleItem->outboundID));
|
||||
});
|
||||
|
||||
connect(ui->rule_attr, &QComboBox::currentTextChanged, this, [=](const QString& text){
|
||||
if (currentIndex == -1) return;
|
||||
ui->rule_attr_data->setTitle(text);
|
||||
auto inputType = NekoGui::RouteRule::get_input_type(text);
|
||||
switch (inputType) {
|
||||
case NekoGui::trufalse: {
|
||||
QStringList items = {"", "true", "false"};
|
||||
auto currentValPtr = chain->Rules[currentIndex]->get_current_value_bool(text);
|
||||
QString currentVal = currentValPtr == nullptr ? "" : *currentValPtr ? "true" : "false";
|
||||
showSelectItem(items, currentVal);
|
||||
break;
|
||||
}
|
||||
case NekoGui::select: {
|
||||
auto items = NekoGui::RouteRule::get_values_for_field(text);
|
||||
auto currentVal = chain->Rules[currentIndex]->get_current_value_string(text)[0];
|
||||
showSelectItem(items, currentVal);
|
||||
break;
|
||||
}
|
||||
case NekoGui::text: {
|
||||
auto currentItems = chain->Rules[currentIndex]->get_current_value_string(text);
|
||||
showTextEnterItem(currentItems);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RouteItem::~RouteItem() {
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void RouteItem::showSelectItem(const QStringList& items, const QString& currentItem) {
|
||||
ui->rule_attr_text->hide();
|
||||
ui->rule_attr_selector->clear();
|
||||
ui->rule_attr_selector->show();
|
||||
QStringList fullItems = {""};
|
||||
fullItems << items;
|
||||
ui->rule_attr_selector->addItems(fullItems);
|
||||
ui->rule_attr_selector->setCurrentText(currentItem);
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void RouteItem::showTextEnterItem(const QStringList& items) {
|
||||
ui->rule_attr_selector->hide();
|
||||
ui->rule_attr_text->clear();
|
||||
ui->rule_attr_text->show();
|
||||
ui->rule_attr_text->setText(items.join('\n'));
|
||||
adjustSize();
|
||||
}
|
||||
43
ui/widget/RouteItem.h
Normal file
43
ui/widget/RouteItem.h
Normal file
@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QListWidgetItem>
|
||||
#include <QGroupBox>
|
||||
|
||||
#include "db/RouteEntity.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace Ui {
|
||||
class RouteItem;
|
||||
}
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class RouteItem : public QGroupBox {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RouteItem(QWidget *parent = nullptr, const std::shared_ptr<NekoGui::RoutingChain>& routeChain = nullptr);
|
||||
~RouteItem() override;
|
||||
|
||||
std::shared_ptr<NekoGui::RoutingChain> chain;
|
||||
signals:
|
||||
void settingsChanged(const std::shared_ptr<NekoGui::RoutingChain> routeChain);
|
||||
|
||||
private:
|
||||
Ui::RouteItem *ui;
|
||||
int currentIndex = -1;
|
||||
|
||||
[[nodiscard]] int getIndexOf(const QString& name) const;
|
||||
|
||||
void showSelectItem(const QStringList& items, const QString& currentItem);
|
||||
|
||||
void showTextEnterItem(const QStringList& items);
|
||||
|
||||
private slots:
|
||||
void on_ok_button_clicked();
|
||||
void on_cancel_button_clicked();
|
||||
void on_new_route_item_clicked();
|
||||
void on_moveup_route_item_clicked();
|
||||
void on_movedown_route_item_clicked();
|
||||
void on_route_view_json_clicked();
|
||||
};
|
||||
153
ui/widget/RouteItem.ui
Normal file
153
ui/widget/RouteItem.ui
Normal file
@ -0,0 +1,153 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>RouteItem</class>
|
||||
<widget class="QGroupBox" name="RouteItem">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>780</width>
|
||||
<height>728</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>GroupBox</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="route_name_l">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="route_name"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="route_view_json">
|
||||
<property name="text">
|
||||
<string>View JSON</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Rules</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QListWidget" name="route_items"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_2" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="new_route_item">
|
||||
<property name="text">
|
||||
<string>New</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="moveup_route_item">
|
||||
<property name="text">
|
||||
<string>Move Up</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="movedown_route_item">
|
||||
<property name="text">
|
||||
<string>Move Down</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Rule Attributes</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_3" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="rule_attr_l">
|
||||
<property name="text">
|
||||
<string>Attribute</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="rule_attr"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="rule_out_l">
|
||||
<property name="text">
|
||||
<string>Outbound</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="rule_out"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="rule_attr_data">
|
||||
<property name="title">
|
||||
<string>Name_Placeholder</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QTextEdit" name="rule_attr_text"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="rule_attr_selector"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_4" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QPushButton" name="ok_button">
|
||||
<property name="text">
|
||||
<string>Ok</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cancel_button">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
Loading…
Reference in New Issue
Block a user