#include "mergerfs_webui.hpp" #include "fs_exists.hpp" #include "fs_mounts.hpp" #include "mergerfs_api.hpp" #include "str.hpp" #include "CLI11.hpp" #include "fmt/core.h" #include "httplib.h" #include "json.hpp" #include using json = nlohmann::json; static void _get_root(const httplib::Request &req_, httplib::Response &res_) { std::string html; if(fs::exists("index.html")) { res_.set_file_content("index.html"); return; } html = R"html( mergerfs ui
)html"; res_.set_content(html, "text/html"); } #define IERT(S) if(type_ == (S)) return true; static bool _valid_fs_type(const std::string &type_) { IERT("ext2"); IERT("ext3"); IERT("ext4"); IERT("xfs"); IERT("jfs"); IERT("btrfs"); IERT("zfs"); IERT("reiserfs"); IERT("f2fs"); IERT("ntfs"); IERT("vfat"); IERT("exfat"); if(str::startswith(type_,"fuse.")) return true; return false; } static void _get_mounts(const httplib::Request &req_, httplib::Response &res_) { json json_array; std::string type; fs::MountVec mounts; fs::mounts(mounts); json_array = json::array(); for(const auto &mount : mounts) { json obj; if(not ::_valid_fs_type(mount.type)) continue; obj["path"] = mount.dir; obj["type"] = mount.type; json_array.push_back(obj); } res_.set_content(json_array.dump(), "application/json"); } static void _get_mounts_mergerfs(const httplib::Request &req_, httplib::Response &res_) { json j; std::string type; fs::MountVec mounts; fs::mounts(mounts); j = json::array(); for(const auto &mount : mounts) { if(mount.type != "fuse.mergerfs") continue; j.push_back(mount.dir); } res_.set_content(j.dump(), "application/json"); } static void _get_kvs(const httplib::Request &req_, httplib::Response &res_) { if(not req_.has_param("mount")) { res_.status = 400; res_.set_content("mount param not set", "text/plain"); return; } json j; std::string mount; std::map kvs; mount = req_.get_param_value("mount"); mergerfs::api::get_kvs(mount,&kvs); j = kvs; res_.set_content(j.dump(), "application/json"); } static void _get_kvs_key(const httplib::Request &req_, httplib::Response &res_) { if(not req_.has_param("mount")) { res_.status = 400; res_.set_content("mount param not set", "text/plain"); return; } json j; fs::path mount; std::string key; std::string val; key = req_.path_params.at("key"); mount = req_.get_param_value("mount"); mergerfs::api::get_kv(mount,key,&val); j = val; res_.set_content(j.dump(), "application/json"); } static json _generate_error(const fs::path &mount_, const std::string &key_, const std::string &val_, const int err_) { json rv; rv = json::object(); rv["mount"] = mount_.string(); rv["key"] = key_; rv["value"] = val_; switch(err_) { case -EROFS: rv["msg"] = fmt::format("'{}' is read only",key_); break; case -EINVAL: rv["msg"] = fmt::format("value '{}' is not valid for '{}'", val_, key_); break; case -EACCES: rv["msg"] = fmt::format("mergerfs.webui (pid {}) is running as uid {}" " which appears not to have access to modify the" " mount's config", ::getpid(), ::getuid()); break; case -ENOTCONN: rv["msg"] = fmt::format("Appears the mergerfs mount '{}' is broken. " "mergerfs may have crashed.", mount_.string()); break; default: rv["msg"] = strerror(-err_); break; } return rv; } static void _post_kvs_key(const httplib::Request &req_, httplib::Response &res_) { if(not req_.has_param("mount")) { res_.status = 400; res_.set_content("mount param not set", "text/plain"); return; } try { int rv; json j; fs::path mount; std::string key; std::string val; j = json::parse(req_.body); key = req_.path_params.at("key"); val = j; mount = req_.get_param_value("mount"); rv = mergerfs::api::set_kv(mount,key,val); j = json::object(); if(rv >= 0) { res_.status = 200; j["result"] = "success"; } else { res_.status = 400; j["result"] = "error"; j["error"] = ::_generate_error(mount,key,val,rv); } res_.set_content(j.dump(), "application/json"); } catch (const std::exception& e) { fmt::print("{}\n",e.what()); res_.status = 400; res_.set_content("Invalid JSON","text/plain"); } } int mergerfs::webui::main(const int argc_, char **argv_) { CLI::App app; std::string host; int port; app.description("mergerfs.webui:" " A simple webui to configure mergerfs instances"); app.name("USAGE: mergerfs.webui"); app.add_option("--host",host); app.add_option("--port",port); try { app.parse(argc_,argv_); } catch(const CLI::ParseError &e) { return app.exit(e); } httplib::Server http_server; host = "0.0.0.0"; port = 8000; http_server.Get("/",::_get_root); http_server.Get("/mounts",::_get_mounts); http_server.Get("/mounts/mergerfs",::_get_mounts_mergerfs); http_server.Get("/kvs",::_get_kvs); http_server.Get("/kvs/:key",::_get_kvs_key); http_server.Post("/kvs/:key",::_post_kvs_key); http_server.listen(host,port); return 0; }