Added support for ip whitelist and added rest method to update/get the config file
This commit is contained in:
parent
66882b3b67
commit
5f7e4e2cf2
@ -82,6 +82,7 @@ void HttpServer::EventHandler(mg_connection* pNc, int ev, void* p)
|
|||||||
http_message* hpm = (http_message*)p;
|
http_message* hpm = (http_message*)p;
|
||||||
std::string requestedUri = FixUnterminatedString(hpm->uri.p, hpm->uri.len);
|
std::string requestedUri = FixUnterminatedString(hpm->uri.p, hpm->uri.len);
|
||||||
|
|
||||||
|
// Get clients ip address
|
||||||
std::string peer_addr;
|
std::string peer_addr;
|
||||||
{
|
{
|
||||||
char buf[32];
|
char buf[32];
|
||||||
@ -89,13 +90,7 @@ void HttpServer::EventHandler(mg_connection* pNc, int ev, void* p)
|
|||||||
peer_addr = buf;
|
peer_addr = buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((XGConfig::general.onlyAllowLocalhost) && (peer_addr != "127.0.0.1"))
|
if (IsConnectionAllowed(peer_addr))
|
||||||
{
|
|
||||||
Json j;
|
|
||||||
j.CloneFrom(RestResponseTemplates::GetByCode(UNAUTHORIZED, "Only localhost allowed!"));
|
|
||||||
ServeStringToConnection(pNc, j.Render(), UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -128,6 +123,12 @@ void HttpServer::EventHandler(mg_connection* pNc, int ev, void* p)
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
else // Client is not allowed, serve error json
|
||||||
|
{
|
||||||
|
Json j;
|
||||||
|
j.CloneFrom(RestResponseTemplates::GetByCode(UNAUTHORIZED, "Only localhost allowed!"));
|
||||||
|
ServeStringToConnection(pNc, j.Render(), UNAUTHORIZED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -199,6 +200,36 @@ void HttpServer::ServeDownloadeableResource(mg_connection* pNc, int ev, void* p,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HttpServer::IsConnectionAllowed(std::string peer_address)
|
||||||
|
{
|
||||||
|
// Localhost is always allowed!
|
||||||
|
if (peer_address == "127.0.0.1") return true;
|
||||||
|
|
||||||
|
// Peer is not localhost, but only localhost is allowed!
|
||||||
|
else if (XGConfig::access.only_allow_localhost) return false;
|
||||||
|
|
||||||
|
// Let's check if the whitelist is active
|
||||||
|
else if (XGConfig::access.enable_whitelist)
|
||||||
|
{
|
||||||
|
// It is. Let's check if our peer is whitelisted
|
||||||
|
for (std::size_t i = 0; i < XGConfig::access.whitelist.size(); i++)
|
||||||
|
{
|
||||||
|
// Whitelist is enabled, and peer is whitelisted
|
||||||
|
if (XGConfig::access.whitelist[i] == peer_address) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whitelist is enabled, but peer is NOT whitelisted
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else // Whitelist is NOT enabled and only_allow_localhost is FALSE!
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should never come to this point
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void HttpServer::OnExit()
|
void HttpServer::OnExit()
|
||||||
{
|
{
|
||||||
log->cout << "Shutting down http-server...";
|
log->cout << "Shutting down http-server...";
|
||||||
|
@ -29,6 +29,8 @@ namespace Rest
|
|||||||
static void ServeStringToConnection(struct mg_connection* c, std::string str, int httpStatusCode = 200);
|
static void ServeStringToConnection(struct mg_connection* c, std::string str, int httpStatusCode = 200);
|
||||||
static std::string FixUnterminatedString(const char* cstr, const std::size_t len);
|
static std::string FixUnterminatedString(const char* cstr, const std::size_t len);
|
||||||
|
|
||||||
|
static bool IsConnectionAllowed(std::string peer_address);
|
||||||
|
|
||||||
|
|
||||||
struct mg_mgr* pMgr;
|
struct mg_mgr* pMgr;
|
||||||
struct mg_connection* pNc;
|
struct mg_connection* pNc;
|
||||||
|
@ -42,6 +42,7 @@ bool RestQueryHandler::ProcessQuery(const std::string clientAdress, const Json&
|
|||||||
else if (requestName == "fetch_alltime_logs") return FetchAlltimeLogs(requestBody, responseBody, responseCode);
|
else if (requestName == "fetch_alltime_logs") return FetchAlltimeLogs(requestBody, responseBody, responseCode);
|
||||||
else if (requestName == "update_dep_youtubedl") return UpdateYoutubeDL(requestBody, responseBody, responseCode);
|
else if (requestName == "update_dep_youtubedl") return UpdateYoutubeDL(requestBody, responseBody, responseCode);
|
||||||
else if (requestName == "remove_download_entry") return RemoveDownloadEntry(requestBody, responseBody, responseCode);
|
else if (requestName == "remove_download_entry") return RemoveDownloadEntry(requestBody, responseBody, responseCode);
|
||||||
|
else if (requestName == "update_config") return UpdateConfig(requestBody, responseBody, responseCode);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -437,6 +438,44 @@ bool RestQueryHandler::RemoveDownloadEntry(const JsonBlock& request, JsonBlock&
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RestQueryHandler::UpdateConfig(const JsonBlock& request, JsonBlock& responseBody, HTTP_STATUS_CODE& responseCode)
|
||||||
|
{
|
||||||
|
if (ValidateField("config", JDType::JSON, request, responseBody))
|
||||||
|
{
|
||||||
|
bool prevStateConsole = XGConfig::general.show_console;
|
||||||
|
XGConfig::LoadFromJson(request.Get("config").AsJson);
|
||||||
|
|
||||||
|
// Update console, if necessary
|
||||||
|
if (XGConfig::general.show_console != prevStateConsole)
|
||||||
|
{
|
||||||
|
if (XGConfig::general.show_console) ConsoleManager::ShowConsole();
|
||||||
|
else ConsoleManager::HideConsole();
|
||||||
|
}
|
||||||
|
|
||||||
|
log->cout << "Updated config values...";
|
||||||
|
log->Flush();
|
||||||
|
XGConfig::Save();
|
||||||
|
|
||||||
|
responseBody.Set("message") = "Updated no settings";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
responseBody.Set("message") = "Updated no settings";
|
||||||
|
}
|
||||||
|
|
||||||
|
responseCode = OK;
|
||||||
|
responseBody.CloneFrom(RestResponseTemplates::GetByCode(OK));
|
||||||
|
responseBody.Set("settings") = XGConfig::GetSavefileBuffer();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool RestQueryHandler::ValidateField(const std::string name, const JasonPP::JDType type, const JasonPP::Json& checkThat, JasonPP::JsonBlock& putErrorResponseHere)
|
bool RestQueryHandler::ValidateField(const std::string name, const JasonPP::JDType type, const JasonPP::Json& checkThat, JasonPP::JsonBlock& putErrorResponseHere)
|
||||||
|
@ -34,6 +34,7 @@ namespace Rest
|
|||||||
static bool GetDiskUsage(const JasonPP::JsonBlock& request, JasonPP::JsonBlock& responseBody, HTTP_STATUS_CODE& responseCode);
|
static bool GetDiskUsage(const JasonPP::JsonBlock& request, JasonPP::JsonBlock& responseBody, HTTP_STATUS_CODE& responseCode);
|
||||||
static bool UpdateYoutubeDL(const JasonPP::JsonBlock& request, JasonPP::JsonBlock& responseBody, HTTP_STATUS_CODE& responseCode);
|
static bool UpdateYoutubeDL(const JasonPP::JsonBlock& request, JasonPP::JsonBlock& responseBody, HTTP_STATUS_CODE& responseCode);
|
||||||
static bool RemoveDownloadEntry(const JasonPP::JsonBlock& request, JasonPP::JsonBlock& responseBody, HTTP_STATUS_CODE& responseCode);
|
static bool RemoveDownloadEntry(const JasonPP::JsonBlock& request, JasonPP::JsonBlock& responseBody, HTTP_STATUS_CODE& responseCode);
|
||||||
|
static bool UpdateConfig(const JasonPP::JsonBlock& request, JasonPP::JsonBlock& responseBody, HTTP_STATUS_CODE& responseCode);
|
||||||
|
|
||||||
static bool ValidateField(const std::string name, const JasonPP::JDType type, const JasonPP::Json& checkThat, JasonPP::JsonBlock& putErrorResponseHere);
|
static bool ValidateField(const std::string name, const JasonPP::JDType type, const JasonPP::Json& checkThat, JasonPP::JsonBlock& putErrorResponseHere);
|
||||||
|
|
||||||
|
@ -45,13 +45,20 @@ void XGConfig::InitializeDefaultValues()
|
|||||||
downloader.num_threads = 1;
|
downloader.num_threads = 1;
|
||||||
|
|
||||||
general.show_console = true;
|
general.show_console = true;
|
||||||
general.onlyAllowLocalhost = true;
|
|
||||||
|
access.only_allow_localhost = true;
|
||||||
|
access.enable_whitelist = true;
|
||||||
|
access.whitelist = std::vector<std::string>();
|
||||||
|
access.whitelist.push_back("127.0.0.1"); // Add localhost to whitelist
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void XGConfig::LoadFromJson(const JasonPP::JsonBlock& json)
|
void XGConfig::LoadFromJson(const JasonPP::JsonBlock& json)
|
||||||
{
|
{
|
||||||
|
savefileBuffer.Clear();
|
||||||
|
savefileBuffer.CloneFrom(json);
|
||||||
|
|
||||||
if (IsJsonFieldValid(json, "logging.logfile_json", JDType::STRING))
|
if (IsJsonFieldValid(json, "logging.logfile_json", JDType::STRING))
|
||||||
{
|
{
|
||||||
logging.logfile_json = json.ShorthandGet("logging.logfile_json").AsString;
|
logging.logfile_json = json.ShorthandGet("logging.logfile_json").AsString;
|
||||||
@ -103,9 +110,28 @@ void XGConfig::LoadFromJson(const JasonPP::JsonBlock& json)
|
|||||||
{
|
{
|
||||||
general.show_console = json.ShorthandGet("general.show_console").AsBool;
|
general.show_console = json.ShorthandGet("general.show_console").AsBool;
|
||||||
}
|
}
|
||||||
if (IsJsonFieldValid(json, "general.onlyAllowLocalhost", JDType::BOOL))
|
|
||||||
|
|
||||||
|
|
||||||
|
if (IsJsonFieldValid(json, "access.only_allow_localhost", JDType::BOOL))
|
||||||
{
|
{
|
||||||
general.onlyAllowLocalhost = json.ShorthandGet("general.onlyAllowLocalhost").AsBool;
|
access.only_allow_localhost = json.ShorthandGet("access.only_allow_localhost").AsBool;
|
||||||
|
}
|
||||||
|
if (IsJsonFieldValid(json, "access.enable_whitelist", JDType::BOOL))
|
||||||
|
{
|
||||||
|
access.enable_whitelist = json.ShorthandGet("access.enable_whitelist").AsBool;
|
||||||
|
}
|
||||||
|
if (IsJsonFieldValid(json, "access.whitelist", JDType::ARRAY))
|
||||||
|
{
|
||||||
|
const JsonArray& cachedArr = json.ShorthandGet("access.whitelist").AsArray;
|
||||||
|
access.whitelist.clear();
|
||||||
|
for (std::size_t i = 0; i < cachedArr.Size(); i++)
|
||||||
|
{
|
||||||
|
if (cachedArr[i].GetDataType() == JDType::STRING)
|
||||||
|
{
|
||||||
|
access.whitelist.push_back(cachedArr[i].AsString);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -113,10 +139,13 @@ void XGConfig::LoadFromJson(const JasonPP::JsonBlock& json)
|
|||||||
|
|
||||||
JsonBlock XGConfig::CreateJson()
|
JsonBlock XGConfig::CreateJson()
|
||||||
{
|
{
|
||||||
|
JsonArray jsnArrWhitelist;
|
||||||
|
jsnArrWhitelist.Add(access.whitelist);
|
||||||
|
|
||||||
return JsonBlock({
|
return JsonBlock({
|
||||||
Ele("httpServer", JsonBlock({
|
Ele("httpServer", JsonBlock({
|
||||||
Ele("port", httpServer.port),
|
Ele("port", httpServer.port),
|
||||||
Ele("pollingRate", httpServer.polling_rate),
|
Ele("polling_rate", httpServer.polling_rate),
|
||||||
Ele("rootdir", httpServer.rootdir)
|
Ele("rootdir", httpServer.rootdir)
|
||||||
})),
|
})),
|
||||||
Ele("logging", JsonBlock({
|
Ele("logging", JsonBlock({
|
||||||
@ -131,7 +160,11 @@ JsonBlock XGConfig::CreateJson()
|
|||||||
})),
|
})),
|
||||||
Ele("general", JsonBlock({
|
Ele("general", JsonBlock({
|
||||||
Ele("show_console", general.show_console),
|
Ele("show_console", general.show_console),
|
||||||
Ele("onlyAllowLocalhost", general.onlyAllowLocalhost)
|
})),
|
||||||
|
Ele("access", JsonBlock({
|
||||||
|
Ele("only_allow_localhost", access.only_allow_localhost),
|
||||||
|
Ele("enable_whitelist", access.enable_whitelist),
|
||||||
|
Ele("whitelist", jsnArrWhitelist),
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -210,6 +243,8 @@ void XGConfig::Save()
|
|||||||
bool XGConfig::SaveToFile(std::string filename)
|
bool XGConfig::SaveToFile(std::string filename)
|
||||||
{
|
{
|
||||||
Json cfgStruct(CreateJson());
|
Json cfgStruct(CreateJson());
|
||||||
|
savefileBuffer.Clear();
|
||||||
|
savefileBuffer.CloneFrom(cfgStruct.AsJson);
|
||||||
return FileSystem::WriteFile(filename, cfgStruct.Render());
|
return FileSystem::WriteFile(filename, cfgStruct.Render());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,5 +280,7 @@ XGConfig::HttpServer XGConfig::httpServer;
|
|||||||
XGConfig::Logging XGConfig::logging;
|
XGConfig::Logging XGConfig::logging;
|
||||||
XGConfig::Downloader XGConfig::downloader;
|
XGConfig::Downloader XGConfig::downloader;
|
||||||
XGConfig::General XGConfig::general;
|
XGConfig::General XGConfig::general;
|
||||||
|
XGConfig::Access XGConfig::access;
|
||||||
|
|
||||||
|
JasonPP::JsonBlock XGConfig::savefileBuffer;
|
||||||
::Logging::Logger* XGConfig::log;
|
::Logging::Logger* XGConfig::log;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <vector>
|
||||||
#include "Filesystem.h"
|
#include "Filesystem.h"
|
||||||
#include "external_dependencies/leonetienne/JasonPP/JasonPP.hpp"
|
#include "external_dependencies/leonetienne/JasonPP/JasonPP.hpp"
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
@ -30,27 +31,37 @@ public:
|
|||||||
struct General
|
struct General
|
||||||
{
|
{
|
||||||
bool show_console;
|
bool show_console;
|
||||||
bool onlyAllowLocalhost;
|
};
|
||||||
|
struct Access
|
||||||
|
{
|
||||||
|
bool only_allow_localhost;
|
||||||
|
bool enable_whitelist;
|
||||||
|
std::vector<std::string> whitelist;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void PreInit();
|
static void PreInit();
|
||||||
static void Save();
|
static void Save();
|
||||||
static void PostExit();
|
static void PostExit();
|
||||||
|
|
||||||
|
static void LoadFromJson(const JasonPP::JsonBlock& json);
|
||||||
|
|
||||||
static XGConfig::HttpServer httpServer;
|
static XGConfig::HttpServer httpServer;
|
||||||
static XGConfig::Logging logging;
|
static XGConfig::Logging logging;
|
||||||
static XGConfig::Downloader downloader;
|
static XGConfig::Downloader downloader;
|
||||||
static XGConfig::General general;
|
static XGConfig::General general;
|
||||||
|
static XGConfig::Access access;
|
||||||
|
|
||||||
|
static const JasonPP::JsonBlock& GetSavefileBuffer() { return savefileBuffer; }
|
||||||
|
|
||||||
static ::Logging::Logger* log;
|
static ::Logging::Logger* log;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static bool SaveToFile(std::string filename);
|
static bool SaveToFile(std::string filename);
|
||||||
|
static JasonPP::JsonBlock savefileBuffer;
|
||||||
|
|
||||||
static bool IsJsonFieldValid(const JasonPP::JsonBlock& json, const std::string key, const JasonPP::JDType type);
|
static bool IsJsonFieldValid(const JasonPP::JsonBlock& json, const std::string key, const JasonPP::JDType type);
|
||||||
static void InitializeDefaultValues();
|
static void InitializeDefaultValues();
|
||||||
static JasonPP::JsonBlock CreateJson();
|
static JasonPP::JsonBlock CreateJson();
|
||||||
static void LoadFromJson(const JasonPP::JsonBlock& json);
|
|
||||||
static void Load();
|
static void Load();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
"header": [],
|
"header": [],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\r\n \"request\": \"queue_download\",\r\n \"video_url\": \"https://youtu.be/OF-thWTJcu0\",\r\n \"mode\": \"video\"\r\n}",
|
"raw": "{\r\n \"request\": \"queue_download\",\r\n \"video_url\": \"https://www.youtube.com/watch?v=BHiWygziyso\",\r\n \"mode\": \"video\"\r\n}",
|
||||||
"options": {
|
"options": {
|
||||||
"raw": {
|
"raw": {
|
||||||
"language": "json"
|
"language": "json"
|
||||||
@ -66,7 +66,7 @@
|
|||||||
"header": [],
|
"header": [],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\r\n \"request\": \"queue_download\",\r\n \"video_url\": \"https://youtu.be/wgfNsek8xkc\",\r\n \"mode\": \"audio\"\r\n}",
|
"raw": "{\r\n \"request\": \"queue_download\",\r\n \"video_url\": \"https://youtu.be/m9HT74QccbQ\",\r\n \"mode\": \"audio\"\r\n}",
|
||||||
"options": {
|
"options": {
|
||||||
"raw": {
|
"raw": {
|
||||||
"language": "json"
|
"language": "json"
|
||||||
@ -93,7 +93,7 @@
|
|||||||
"header": [],
|
"header": [],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\r\n \"request\": \"fetch_session_cache\",\r\n \"max-num\": -1\r\n}\r\n",
|
"raw": "{\r\n \"request\": \"fetch_session_cache\",\r\n \"max_age\": -1\r\n}\r\n",
|
||||||
"options": {
|
"options": {
|
||||||
"raw": {
|
"raw": {
|
||||||
"language": "json"
|
"language": "json"
|
||||||
@ -426,7 +426,61 @@
|
|||||||
"header": [],
|
"header": [],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\r\n \"request\": \"remove_download_entry\",\r\n \"id\": \"1KnnPV\"\r\n}",
|
"raw": "{\r\n \"request\": \"remove_download_entry\",\r\n \"id\": \"1KnnMe\"\r\n}",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "localhost:6969/api",
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"port": "6969",
|
||||||
|
"path": [
|
||||||
|
"api"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "UpdateConfig",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\r\n \"request\": \"update_config\",\r\n \"config\": {\r\n \"access\": {\r\n \"enable_whitelist\": false,\r\n \"only_allow_localhost\": false,\r\n \"whitelist\": [\r\n \"127.0.0.1\"\r\n ]\r\n },\r\n \"general\": {\r\n \"show_console\": true\r\n }\r\n }\r\n}",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "localhost:6969/api",
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"port": "6969",
|
||||||
|
"path": [
|
||||||
|
"api"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "GetConfig",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\r\n \"request\": \"update_config\"\r\n}",
|
||||||
"options": {
|
"options": {
|
||||||
"raw": {
|
"raw": {
|
||||||
"language": "json"
|
"language": "json"
|
||||||
|
@ -14,11 +14,6 @@
|
|||||||
<Toggle :isOn="false"/>
|
<Toggle :isOn="false"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="option toggle flex justify-between items-center">
|
|
||||||
<p>Limit speed</p>
|
|
||||||
<Toggle :isOn="false"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="option text narrow flex justify-between w-full items-center">
|
<div class="option text narrow flex justify-between w-full items-center">
|
||||||
<p class="mr-3">Max speed</p>
|
<p class="mr-3">Max speed</p>
|
||||||
<input type="text" id="max_speed" name="max_speed" placeholder="100M">
|
<input type="text" id="max_speed" name="max_speed" placeholder="100M">
|
||||||
@ -29,6 +24,11 @@
|
|||||||
<input type="text" id="max_threads" name="max_threads" placeholder="10">
|
<input type="text" id="max_threads" name="max_threads" placeholder="10">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="option toggle flex justify-between items-center">
|
||||||
|
<p>Enable whitelist</p>
|
||||||
|
<Toggle :isOn="false"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full lg:w-1/2 border-left lg:pl-3 mt-6 lg:mt-0">
|
<div class="w-full lg:w-1/2 border-left lg:pl-3 mt-6 lg:mt-0">
|
||||||
|
0
tubio-frontend-nuxt-app/store/settings.js
Normal file
0
tubio-frontend-nuxt-app/store/settings.js
Normal file
Loading…
x
Reference in New Issue
Block a user