xcbosa-itx 2 years ago
parent
commit
86d1324924

+ 2 - 1
controller/LoginController.cpp

@@ -7,6 +7,7 @@
 #include "../processor/templates/framework7/Framework7Document.hpp"
 #include "../fs.hpp"
 #include "../user.hpp"
+#include "../view/view.h"
 
 using namespace std;
 using namespace xc::processor;
@@ -20,7 +21,7 @@ namespace xc::controller {
         return new TemplateResponseData({
             Framework7Document({
                 If(request.getCookie("Token") == "loginFailed", {
-                    script("window.onload = function() { app.dialog.alert('登陆失败,请检查用户名或密码') }")
+                    OnLoadScript("app.dialog.alert('登陆失败,请检查用户名或密码')")
                 }),
                 BlockTitleView("需要登陆"),
                 FormView({

+ 66 - 2
controller/PortListController.cpp

@@ -3,23 +3,78 @@
 //
 
 #include "../processor/processor.h"
+#include "../utils/utils.h"
 #include "../webuiconf.h"
 #include "../processor/templates/framework7/Framework7Document.hpp"
+#include "../user.hpp"
+#include "../frp.h"
 
 using namespace std;
 using namespace xc::processor;
 using namespace xc::processor::templates;
 using namespace xc::processor::templates::framework7;
+using namespace xc::utils;
 using namespace configor;
 
 namespace xc::controller {
 
+    double divide(double a, double b, double zeroIf) {
+        if (b == 0) return zeroIf;
+        return a / b;
+    }
+
     ResponseData *PortListController(RequestData request) {
+        string username = user::getTokenUserName(request.getCookie("Token"));
+        auto profiles = frp::listUserAvailableProfiles(username);
+        int totalPortCnt(0), usedPortCnt(0);
+        for (auto profile : profiles) {
+            totalPortCnt += profile.getAllowPortCount();
+            usedPortCnt += profile.ports.size();
+        }
+        double guagePercent = 1 - usedPortCnt / (double)totalPortCnt;
+        if (totalPortCnt == 0) {
+            guagePercent = 0;
+        }
         return new TemplateResponseData({
             Framework7Document({
-                a("退出登陆").classAdd("link").onclick("window.location='/quitLogin'")
+                a("退出登陆").classAdd("link").onclick("window.location='/quitLogin'"),
+                a("添加端口").classAdd("link").onclick("window.location='/createPort'")
             }, {
-                p("登陆成功")
+                BlockTitleView("总览"),
+                BlockView({
+                    h1("端口总览").style("text-align", "center"),
+                    GuageView(to_string(usedPortCnt) + " / " + to_string(totalPortCnt) + "端口", "已开通", guagePercent, 300)
+                }),
+                Foreach(profiles, [] (frp::ProfileInfo profile) {
+                    return View("", {
+                        BlockTitleView(""), center({
+                        templates::div({
+                            templates::div({
+                                templates::div({
+                                    templates::div({
+                                        profile.getServerAddr(),
+                                        VerticalSpacer(10),
+                                        small("您可用范围: " + to_string(profile.getAllowPortLow()) + " - " + to_string(profile.getAllowPortLow() + profile.getAllowPortCount() - 1)).style("opacity", "0.7"),
+                                        GuageView(to_string(profile.ports.size()) + "/" + to_string(profile.getAllowPortCount()) + "端口",
+                                                  "可在此服务器创建", divide(profile.getAllowPortCount() - profile.ports.size(), profile.getAllowPortCount(), 0), 200)
+                                    }).classAdd("card-header text-color-white display-block"),
+                                    Link("关闭").href("#").classAdd("link card-close card-opened-fade-in color-white").prop("style", "position: absolute; right: 15px; top: 15px")
+                                }).classAdd("bg-class-red").style("height", "300px"),
+                                templates::div({
+                                    templates::div({
+                                        templates::div({
+                                            templates::div({
+                                                templates::div({
+                                                    "This is a simple card with pla"
+                                                }).classAdd("card-content card-content-padding")
+                                            }).classAdd("card")
+                                        }).classAdd("page-content")
+                                    }).classAdd("page")
+                                }).classAdd("card-content-padding")
+                            }).classAdd("card-content")
+                        }).classAdd("card card-expandable")})
+                    });
+                })
             }, {
                 a("2023 © Frp-WebUI by XCBOSA").classAdd("link").onclick("window.open('https://github.com/XCBOSA/frp-webui-500k.git')")
             })
@@ -28,4 +83,13 @@ namespace xc::controller {
 
     ContentGeneratorDefineWithNameS("PortListController", false, PortListController(request))
 
+    ResponseData *CreatePortController(RequestData request) {
+        string username = user::getTokenUserName(request.getCookie("Token"));
+        if (!username.empty()) {
+            
+        }
+    }
+
+    ContentGeneratorDefineWithNameS("/createPort", request.getURLPath() == "/createPort", CreatePortController(request))
+
 }

+ 15 - 0
controller/auto-generated/index.js.cpp

@@ -38,6 +38,21 @@ namespace xc::controller {
             string("    ]\n") + 
             string("});\n") + 
             string("\n") + 
+            string("function createGuage(elAppendix, valueText, description, value, size) {\n") + 
+            string("    app.gauge.create({\n") + 
+            string("        el: \'.gauge_template_\' + elAppendix,\n") + 
+            string("        type: \'circle\',\n") + 
+            string("        value: value,\n") + 
+            string("        size: size,\n") + 
+            string("        borderColor: \'#2196f3\',\n") + 
+            string("        borderWidth: 10,\n") + 
+            string("        valueText: valueText,\n") + 
+            string("        valueFontSize: 41,\n") + 
+            string("        valueTextColor: \'#2196f3\',\n") + 
+            string("        labelText: description,\n") + 
+            string("    })\n") + 
+            string("}\n") + 
+            string("\n") + 
             string("function doLogin(salt) {\n") + 
             string("    let data = app.form.convertToData(\"#loginForm\")\n") + 
             string("    if (data.username.length == 0) {\n") + 

+ 15 - 0
data/html/index.js

@@ -19,6 +19,21 @@ let app = new Framework7({
     ]
 });
 
+function createGuage(elAppendix, valueText, description, value, size) {
+    app.gauge.create({
+        el: '.gauge_template_' + elAppendix,
+        type: 'circle',
+        value: value,
+        size: size,
+        borderColor: '#2196f3',
+        borderWidth: 10,
+        valueText: valueText,
+        valueFontSize: 41,
+        valueTextColor: '#2196f3',
+        labelText: description,
+    })
+}
+
 function doLogin(salt) {
     let data = app.form.convertToData("#loginForm")
     if (data.username.length == 0) {

+ 146 - 3
frp.cpp

@@ -10,12 +10,13 @@
 #include "fs.hpp"
 #include <signal.h>
 #include <strstream>
+#include "frp.h"
 
 using namespace std;
 using namespace xc;
 using namespace xc::utils;
 
-namespace frp {
+namespace xc::frp {
 
     set<int> profileUsingPorts(string profile) {
         set<int> usingPorts;
@@ -97,6 +98,14 @@ namespace frp {
         }
 
         void update() {
+            struct stat buf;
+            if (stat(this->filePath.c_str(), &buf) == 0) {
+                if (this->mutationTime != buf.st_mtimespec.tv_nsec) {
+                    this->mutationTime = buf.st_mtimespec.tv_nsec;
+                    this->reloadConfig();
+                    return;
+                }
+            }
             if (getRunningPid() == 0) {
                 cout << "[FrpProcessWrapper] [" << fileName << "] Exit unexpectedly, restarting..." << endl;
                 this->doStart();
@@ -134,15 +143,21 @@ namespace frp {
         }
 
         void reloadConfig() {
-            this->stop();
-            this->startAndKeepRunning();
+            cout << "[FrpProcessWrapper] [" << fileName << "] Changes Detected, Reload..." << endl;
+            this->doKill();
+            this->doStart();
         }
 
         bool updated;
         string fileName;
         string filePath;
+        long mutationTime;
     private:
         void doStart() {
+            struct stat buf;
+            if (stat(this->filePath.c_str(), &buf) == 0) {
+                this->mutationTime = buf.st_mtimespec.tv_nsec;
+            }
             ostringstream oss;
             oss << "frpc -c " << this->filePath << " &";
             string launchCmd = oss.str();
@@ -213,4 +228,132 @@ namespace frp {
         }
     }
 
+    ProfilePortInfo::ProfilePortInfo() { }
+
+    ProfilePortInfo::ProfilePortInfo(string localIp, int localPort, int remotePort):
+        localIp(localIp), localPort(localPort), remotePort(remotePort) { }
+
+    ProfileInfo::ProfileInfo() { }
+
+    ProfileInfo::ProfileInfo(string profileName) {
+        this->profileName = profileName;
+        INIFile file(conf::getFrpcDir() + "/" + profileName);
+        for (auto &it : file.data) {
+            if (it.getTitle() == "common") {
+                this->serverAddr = it.get("server_addr");
+                this->serverPort = to_int(it.get("server_port"), 0);
+                this->token = it.get("token");
+                string users = it.get("webui_availableForUsers");
+                for (auto it : split(users, "/")) {
+                    trim(it);
+                    if (!it.empty()) {
+                        this->users.insert(it);
+                    }
+                }
+                string allowPortStr = it.get("webui_allowServerPorts");
+                auto list = split(allowPortStr, ",");
+                if (list.size() == 2) {
+                    this->allowPortLow = to_int(list[0], 0);
+                    this->allowPortCount = to_int(list[1], 0);
+                }
+            } else {
+                string type = it.get("type");
+                string localIp = it.get("local_ip");
+                bool success;
+                int localPort = to_int(it.get("local_port"), success);
+                if (!success || is_in(localPort, 0, 65536)) { continue; }
+                int remotePort = to_int(it.get("remote_port"), success);
+                if (!success || is_in(localPort, 0, 65536)) { continue; }
+                if (type == "tcp" || type == "udp") {
+                    bool alreadyHave = false;
+                    for (auto v : this->ports) {
+                        if (v.remotePort == remotePort) {
+                            alreadyHave = true;
+                        }
+                    }
+                    if (alreadyHave) { continue; }
+                    this->ports.push_back(ProfilePortInfo(localIp, localPort, remotePort));
+                }
+            }
+        }
+    }
+
+    void ProfileInfo::addPortInfo(ProfilePortInfo portInfo) {
+        for (auto &port : this->ports) {
+            if (port.remotePort == portInfo.remotePort) {
+                port.localPort = portInfo.localPort;
+                port.localIp = portInfo.localIp;
+                return;
+            }
+        }
+        this->ports.insert(this->ports.begin(), portInfo);
+    }
+
+    void ProfileInfo::removePort(int remotePort) {
+        std::remove_if(this->ports.begin(), this->ports.end(), [&](const auto &item) {
+            return item.remotePort == remotePort;
+        });
+    }
+
+    string ProfileInfo::getServerAddr() { return this->serverAddr; }
+
+    int ProfileInfo::getAllowPortLow() { return this->allowPortLow; }
+
+    int ProfileInfo::getAllowPortCount() { return this->allowPortCount; }
+
+    void ProfileInfo::save() const {
+        string path = conf::getFrpcDir() + "/" + this->profileName;
+        INIFile ini(path);
+        ini.getMust("common")->set("server_addr", this->serverAddr);
+        ini.getMust("common")->set("server_port", to_string(this->serverPort));
+        ini.getMust("common")->set("server_token", this->token);
+        ini.getMust("common")->set("tls_enable", "true");
+        ostringstream usersOss;
+        for (auto user : this->users) {
+            usersOss << user;
+            usersOss << "/";
+        }
+        ini.getMust("common")->set("webui_availableForUsers", usersOss.str());
+        ostringstream oss;
+        oss << this->allowPortLow << "," << this->allowPortCount;
+        ini.getMust("common")->set("webui_allowServerPorts", oss.str());
+        ini.data.clear();
+        for (auto port : this->ports) {
+            for (auto type : conf::supportTypes) {
+                ostringstream titleOss;
+                titleOss << "frpc-webui-" << port.remotePort << "-" << type;
+                ini.getMust(titleOss.str())->set("type", type);
+                ini.getMust(titleOss.str())->set("local_ip", port.localIp);
+                ini.getMust(titleOss.str())->set("local_port", to_string(port.localPort));
+                ini.getMust(titleOss.str())->set("remote_port", to_string(port.remotePort));
+                ini.getMust(titleOss.str())->set("use_encryption", "true");
+                ini.getMust(titleOss.str())->set("use_compression", "true");
+            }
+        }
+        ini.save();
+    }
+
+    void ProfileInfo::addUser(string name) {
+        this->users.insert(name);
+    }
+
+    void ProfileInfo::removeUser(string name) {
+        this->users.erase(name);
+    }
+
+    bool ProfileInfo::availableForUser(string name) {
+        return this->users.count(name) > 0;
+    }
+
+    vector<ProfileInfo> listUserAvailableProfiles(string user) {
+        vector<ProfileInfo> profiles;
+        for (auto profile : fs::contentsOfDirectory(conf::getFrpcDir())) {
+            ProfileInfo profileInfo(profile);
+            if (profileInfo.availableForUser(user)) {
+                profiles.push_back(profileInfo);
+            }
+        }
+        return profiles;
+    }
+
 }

+ 52 - 8
frp.h

@@ -5,22 +5,66 @@
 #pragma once
 
 #include <string>
-#include "utils/utils.h"
-#include "webuiconf.h"
-#include "fs.hpp"
-#include <signal.h>
-#include <strstream>
+#include <set>
 
 using namespace std;
 using namespace xc;
 using namespace xc::utils;
 
-namespace frp {
+namespace xc::frp {
+
+    void frpDaemon();
+
+    /*仅当用户没有更改,但用户要求强制重启时调用,FrpDaemon会自动检查文件变化并自动更新。*/
+    void reloadProfileFilePath(string filePath);
 
     set<int> profileUsingPorts(string profile);
     set<int> serverUsingPorts(string serverIp);
     void addProfile(string name, string ip, string port, string token);
-    void reloadProfileFilePath(string filePath);
-    void frpDaemon();
+
+    class ProfilePortInfo {
+    public:
+        ProfilePortInfo();
+        ProfilePortInfo(string localIp, int localPort, int remotePort);
+        string localIp;
+        int localPort;
+        int remotePort;
+        CONFIGOR_BIND(json::value, ProfilePortInfo, REQUIRED(localIp), REQUIRED(localPort), REQUIRED(remotePort))
+    };
+
+    class ProfileInfo {
+    public:
+        ProfileInfo();
+        ProfileInfo(string profileName);
+        string getServerAddr();
+        int getAllowPortLow();
+        int getAllowPortCount();
+        void addPortInfo(ProfilePortInfo portInfo);
+        void removePort(int remotePort);
+        bool availableForUser(string name);
+        void addUser(string name);
+        void removeUser(string name);
+        void save() const;
+        CONFIGOR_BIND(json::value, ProfileInfo,
+                      REQUIRED(users),
+                      REQUIRED(profileName),
+                      REQUIRED(serverAddr),
+                      REQUIRED(serverPort),
+                      REQUIRED(token),
+                      REQUIRED(allowPortLow),
+                      REQUIRED(allowPortCount),
+                      REQUIRED(ports))
+        vector<ProfilePortInfo> ports;
+    private:
+        set<string> users;
+        string profileName;
+        string serverAddr;
+        int serverPort;
+        string token;
+        int allowPortLow;
+        int allowPortCount;
+    };
+
+    vector<ProfileInfo> listUserAvailableProfiles(string user);
 
 }

+ 7 - 0
main.cpp

@@ -24,6 +24,13 @@ const static strcmd createFrp("frp", "frp <Name> <Server-Addr> <Server-Port> <To
     cout << "成功创建Frpc配置文件 " << args[0] << endl;
 });
 
+const static strcmd assign("assign", "assign <UserName> <Profile>", "使用户有权访问配置文件", 2, [] (auto args) {
+    frp::ProfileInfo profile(args[1]);
+    profile.addUser(args[0]);
+    profile.save();
+    cout << "成功将配置文件 " << args[1] << " 绑定到用户 " << args[0] << endl;
+});
+
 int main(int argc, char **argv) {
     std::cout << "Hello, World!" << std::endl;
 

+ 14 - 0
processor/templates/ViewTemplatePrototypes.cpp

@@ -361,6 +361,20 @@ namespace xc {
             __GenerateElemImpl_KeySameAsTagName(wbr)
             __GenerateElemImpl_KeySameAsTagName(xmp)
 
+            OnLoadScriptHeader::OnLoadScriptHeader(): script("function addOnLoad(func) {\n"
+                                                             "    let oldOnload = window.onload\n"
+                                                             "    if (typeof window.onload != \"function\") {\n"
+                                                             "      window.onload = func\n"
+                                                             "    } else {\n"
+                                                             "      window.onload = function() {\n"
+                                                             "        oldOnload()\n"
+                                                             "        func()\n"
+                                                             "      }\n"
+                                                             "    }\n"
+                                                             "  }\n") { }
+
+            OnLoadScript::OnLoadScript(string code): script("addOnLoad(function() {" + code + "})") { }
+
         } // xc
     } // processor
 } // templates

+ 8 - 0
processor/templates/ViewTemplatePrototypes.h

@@ -227,6 +227,14 @@ namespace xc {
             __GenerateElemDefs(wbr)
             __GenerateElemDefs(xmp)
 
+            class OnLoadScriptHeader: public script {
+            public: OnLoadScriptHeader();
+            };
+
+            class OnLoadScript: public script {
+            public: OnLoadScript(string code);
+            };
+
 
         } // xc
     } // processor

+ 44 - 1
processor/templates/framework7/Framework7Document.hpp

@@ -6,6 +6,7 @@
 #define FRPCWEBUI_FRAMEWORK7DOCUMENT_H
 
 #include "../../processor.h"
+#include "../../../webuiconf.h"
 
 namespace xc::processor::templates::framework7 {
     class Framework7Document: public View {
@@ -19,7 +20,8 @@ namespace xc::processor::templates::framework7 {
                     meta().name("theme-color").content("#2196f3"),
                     templates::title(conf::title),
                     link().rel("stylesheet").href("framework7/framework7-bundle.min.css"),
-                    link().rel("stylesheet").href("index.css")
+                    link().rel("stylesheet").href("index.css"),
+                    OnLoadScriptHeader()
                 }),
                 body({
                     div({
@@ -45,6 +47,32 @@ namespace xc::processor::templates::framework7 {
             this->inner(html);
         }
 
+        Framework7Document(ViewCollection pageContent): View({ }) {
+            html html({
+                head({
+                    meta().charset("utf-8"),
+                    meta().name("viewport").content("width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, viewport-fit=cover"),
+                    meta().name("apple-mobile-web-app-capable").content("yes"),
+                    meta().name("theme-color").content("#2196f3"),
+                    templates::title(conf::title),
+                    link().rel("stylesheet").href("framework7/framework7-bundle.min.css"),
+                    link().rel("stylesheet").href("index.css"),
+                    OnLoadScriptHeader()
+                }),
+                body({
+                    div({
+                        div({
+                            pageContent
+                        }).prop("data-name", "home").classAdd("page")
+                    }).classAdd("view view-main")
+                }).id("app"),
+                script().type("text/javascript").src("framework7/framework7-bundle.min.js"),
+                script().type("text/javascript").src("index.js"),
+                script().type("text/javascript").src("sha256.js"),
+            });
+            this->inner(html);
+        }
+
         Framework7Document(ViewCollection pageContent, ViewCollection tabBarContent): Framework7Document({ }, pageContent, tabBarContent) { };
     };
 
@@ -144,6 +172,21 @@ namespace xc::processor::templates::framework7 {
     typedef p Label;
     typedef a Link;
 
+    class GuageView: public div {
+    public:
+        GuageView(string valueText, string description, double value, double size) {
+            int number = std::rand();
+            this->classAdd("gauge gauge_template_" + to_string(number));
+            this->style("display", "block");
+            this->inner({
+                OnLoadScript("createGuage(" + to_string(number) +
+                    ", \"" + fixStringTransfer(valueText) + "\"" +
+                    ", \"" + fixStringTransfer(description) + "\"" +
+                    ", " + to_string(value) + ", " + to_string(size) + ");")
+            });
+        }
+    };
+
 
 
 } // framework7

+ 0 - 1
utils/INI.h

@@ -40,7 +40,6 @@ namespace xc {
             void remove(string header);
 
             string getINIString();
-        private:
             vector<INISection> data;
         };
 

+ 21 - 0
utils/strop.cpp

@@ -58,5 +58,26 @@ namespace xc {
             return oss.str();
         }
 
+        int to_int(string s, bool &isSuccess) {
+            try {
+                isSuccess = true;
+                return stoi(s);
+            } catch (...) {
+                isSuccess = false;
+            }
+        }
+
+        int to_int(string s, int defaultValue) {
+            try {
+                return stoi(s);
+            } catch (...) {
+                return defaultValue;
+            }
+        }
+
+        bool is_in(int value, int minIncluded, int maxExcluded) {
+            return value >= minIncluded && value < maxExcluded;
+        }
+
     } // xc
 } // utils

+ 3 - 0
utils/strop.h

@@ -17,6 +17,9 @@ namespace xc {
         string fixStringTransfer(string& src);
         string& trim(string &s);
         string uppercase(string s);
+        int to_int(string s, bool &isSuccess);
+        int to_int(string s, int defaultValue);
+        bool is_in(int value, int minIncluded, int maxExcluded);
 
     } // xc
 } // utils

+ 58 - 0
view/LoginView.cpp

@@ -0,0 +1,58 @@
+//
+// Created by xcbosa on 2023/1/29.
+//
+
+#include "../processor/processor.h"
+#include "../processor/templates/framework7/Framework7Document.hpp"
+#include "../webuiconf.h"
+#include "view.h"
+
+using namespace xc;
+using namespace xc::utils;
+using namespace xc::processor::templates;
+using namespace xc::processor::templates::framework7;
+
+namespace xc::view {
+
+    View LoginView() {
+        return templates::div({
+            templates::div(conf::title).classAdd("login-screen-title"),
+                form({
+                    templates::div({
+                        ul({
+                            li({
+                                templates::div({
+                                    templates::div("用户名").classAdd("item-title item-label"),
+                                    templates::div({
+                                        input().type("text").name("username").placeholder("用户名")
+                                    }).classAdd("item-input-wrap")
+                                }).classAdd("item-inner")
+                            }).classAdd("item-content item-input"),
+                            li({
+                                templates:: div({
+                                    templates::div("密码").classAdd("item-title item-label"),
+                                    templates::div({
+                                        input().type("password").name("password").placeholder("密码")
+                                    }).classAdd("item-input-wrap")
+                                }).classAdd("item-inner")
+                            }).classAdd("item-content item-input")
+                        })
+                    }).classAdd("list"),
+                    templates::div({
+                        ul({
+                            li({
+                                Link("登陆").classAdd("list-button")
+                            })
+                        }),
+                        templates::div({
+                            p(""),
+                            p({
+                                Link("Github开源地址")
+                            })
+                        }).classAdd("block-footer")
+                    }).classAdd("list")
+                })
+            }).classAdd("page-content login-screen-content");
+    }
+
+}

+ 0 - 9
view/index.cpp

@@ -1,9 +0,0 @@
-//
-// Created by xcbosa on 2023/1/29.
-//
-
-namespace xc::view {
-
-    
-
-}

+ 20 - 0
view/view.h

@@ -0,0 +1,20 @@
+//
+// Created by xcbosa on 2023/1/31.
+//
+
+#pragma once
+
+#include "../processor/processor.h"
+#include "../processor/templates/framework7/Framework7Document.hpp"
+#include "../webuiconf.h"
+
+using namespace xc;
+using namespace xc::utils;
+using namespace xc::processor::templates;
+using namespace xc::processor::templates::framework7;
+
+namespace xc::view {
+
+    View LoginView();
+
+}

+ 1 - 0
webuiconf.h

@@ -28,6 +28,7 @@ namespace xc::conf {
     const string userJWTSecret("r)xB=P-6A4dpqXLk%03=f+*8TlXDM@%r");
     const string rootDir = "/etc/frpcwebui";
     const int userTokenExpireSeconds = 60 * 60 * 24;
+    const vector<string> supportTypes = { "tcp", "udp" };
 
     inline string getDirAndMakesureExists(string dirPath) {
         struct stat buffer;