From 634e9c985589e06a04e1af3073054c3cdee31113 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Fri, 8 Nov 2024 22:24:07 +0800 Subject: [PATCH 01/14] v3.1.3 init commit - Fixed #378 - Added wip dark theme - Fixed in code typo - Fixed int conversion bug in some DNS challenge supplier --- src/accesslist.go | 48 +++- src/main.go | 4 +- src/mod/acme/ca.go | 4 +- src/mod/update/v308/typedef307.go | 2 +- src/mod/update/v308/typedef308.go | 2 +- src/reverseproxy.go | 2 +- src/web/components/access.html | 223 ++++++++++++------- src/web/components/redirection.html | 2 +- src/web/components/rules.html | 2 +- src/web/components/status.html | 8 +- src/web/components/vdir.html | 2 +- src/web/darktheme.css | 330 ++++++++++++++++++++++++++++ src/web/index.html | 12 +- src/web/main.css | 39 ++-- src/web/script/darktheme.js | 51 +++++ src/web/snippet/acme.html | 16 +- src/web/snippet/customHeaders.html | 2 +- src/web/tools/fs.css | 2 +- 18 files changed, 629 insertions(+), 122 deletions(-) create mode 100644 src/web/darktheme.css create mode 100644 src/web/script/darktheme.js diff --git a/src/accesslist.go b/src/accesslist.go index 46155dd..2df35d7 100644 --- a/src/accesslist.go +++ b/src/accesslist.go @@ -230,7 +230,17 @@ func handleCountryBlacklistAdd(w http.ResponseWriter, r *http.Request) { return } - rule.AddCountryCodeToBlackList(countryCode, comment) + //Check if the country code contains comma, if yes, split it + if strings.Contains(countryCode, ",") { + codes := strings.Split(countryCode, ",") + for _, code := range codes { + code = strings.TrimSpace(code) + rule.AddCountryCodeToBlackList(code, comment) + } + } else { + countryCode = strings.TrimSpace(countryCode) + rule.AddCountryCodeToBlackList(countryCode, comment) + } utils.SendOK(w) } @@ -254,7 +264,17 @@ func handleCountryBlacklistRemove(w http.ResponseWriter, r *http.Request) { return } - rule.RemoveCountryCodeFromBlackList(countryCode) + //Check if the country code contains comma, if yes, split it + if strings.Contains(countryCode, ",") { + codes := strings.Split(countryCode, ",") + for _, code := range codes { + code = strings.TrimSpace(code) + rule.RemoveCountryCodeFromBlackList(code) + } + } else { + countryCode = strings.TrimSpace(countryCode) + rule.RemoveCountryCodeFromBlackList(countryCode) + } utils.SendOK(w) } @@ -397,7 +417,17 @@ func handleCountryWhitelistAdd(w http.ResponseWriter, r *http.Request) { p := bluemonday.StrictPolicy() comment = p.Sanitize(comment) - rule.AddCountryCodeToWhitelist(countryCode, comment) + //Check if the country code contains comma, if yes, split it + if strings.Contains(countryCode, ",") { + codes := strings.Split(countryCode, ",") + for _, code := range codes { + code = strings.TrimSpace(code) + rule.AddCountryCodeToWhitelist(code, comment) + } + } else { + countryCode = strings.TrimSpace(countryCode) + rule.AddCountryCodeToWhitelist(countryCode, comment) + } utils.SendOK(w) } @@ -420,7 +450,17 @@ func handleCountryWhitelistRemove(w http.ResponseWriter, r *http.Request) { return } - rule.RemoveCountryCodeFromWhitelist(countryCode) + //Check if the country code contains comma, if yes, split it + if strings.Contains(countryCode, ",") { + codes := strings.Split(countryCode, ",") + for _, code := range codes { + code = strings.TrimSpace(code) + rule.RemoveCountryCodeFromWhitelist(code) + } + } else { + countryCode = strings.TrimSpace(countryCode) + rule.RemoveCountryCodeFromWhitelist(countryCode) + } utils.SendOK(w) } diff --git a/src/main.go b/src/main.go index 630cadf..2b54f5e 100644 --- a/src/main.go +++ b/src/main.go @@ -60,9 +60,9 @@ var enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade var ( name = "Zoraxy" - version = "3.1.2" + version = "3.1.3" nodeUUID = "generic" //System uuid, in uuidv4 format - development = false //Set this to false to use embedded web fs + development = true //Set this to false to use embedded web fs bootTime = time.Now().Unix() /* diff --git a/src/mod/acme/ca.go b/src/mod/acme/ca.go index b673170..fb62b8e 100644 --- a/src/mod/acme/ca.go +++ b/src/mod/acme/ca.go @@ -3,7 +3,7 @@ package acme /* CA.go - This script load CA defination from embedded ca.json + This script load CA definition from embedded ca.json */ import ( _ "embed" @@ -13,7 +13,7 @@ import ( "strings" ) -// CA Defination, load from embeded json when startup +// CA definition, load from embeded json when startup type CaDef struct { Production map[string]string Test map[string]string diff --git a/src/mod/update/v308/typedef307.go b/src/mod/update/v308/typedef307.go index 17c6d18..980f8e5 100644 --- a/src/mod/update/v308/typedef307.go +++ b/src/mod/update/v308/typedef307.go @@ -1,7 +1,7 @@ package v308 /* - v307 type definations + v307 type definitions This file wrap up the self-contained data structure for v3.0.7 structure and allow automatic updates diff --git a/src/mod/update/v308/typedef308.go b/src/mod/update/v308/typedef308.go index 02a592e..f5364cb 100644 --- a/src/mod/update/v308/typedef308.go +++ b/src/mod/update/v308/typedef308.go @@ -1,7 +1,7 @@ package v308 /* - v308 type definations + v308 type definition This file wrap up the self-contained data structure for v3.0.8 structure and allow automatic updates diff --git a/src/reverseproxy.go b/src/reverseproxy.go index 4ef44f9..3a1d3da 100644 --- a/src/reverseproxy.go +++ b/src/reverseproxy.go @@ -1173,7 +1173,7 @@ func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) { return } - //Create a Custom Header Defination type + //Create a Custom Header Definition type var rewriteDirection rewrite.HeaderDirection if direction == "toOrigin" { rewriteDirection = rewrite.HeaderDirection_ZoraxyToUpstream diff --git a/src/web/components/access.html b/src/web/components/access.html index 462369b..10f5437 100644 --- a/src/web/components/access.html +++ b/src/web/components/access.html @@ -841,6 +841,25 @@

Quick Ban List

function initBannedCountryList(){ $.get("/api/blacklist/list?type=country&id=" + currentEditingAccessRule, function(data) { let bannedListHtml = ''; + + //Check if the country code list contains all eu countries. If yes, replace it with "EU" + let allEu = true; + let euCountries = getEUCCs(); + for (var i = 0; i < euCountries.length; i++){ + if (!data.includes(euCountries[i])){ + allEu = false; + break; + } + } + + if (allEu){ + //Remove EU countries from the list and replace it with EU + data = data.filter(function(value, index, arr){ + return !euCountries.includes(value); + }); + data.push("eu"); + } + data.forEach((countryCode) => { bannedListHtml += ` @@ -919,18 +938,48 @@

Quick Ban List

//Whitelist country table function initWhitelistCountryList(){ $.get("/api/whitelist/list?type=country&id=" + currentEditingAccessRule, function(data) { - let bannedListHtml = ''; + let whiteListHTML = ''; + + //Check if the country code list contains all eu countries. If yes, replace it with "EU" + let allEu = true; + let euCountries = getEUCCs(); + let countryCodesIndata = data.map(function(item){ + //data[n].CC is the country code + return item.CC; + }); + for (var i = 0; i < euCountries.length; i++){ + if (!countryCodesIndata.includes(euCountries[i])){ + allEu = false; + break; + } + } + + if (allEu){ + //Remove EU countries from the list and replace it with EU + data = data.filter(function(value, index, arr){ + return !euCountries.includes(value.CC); + }); + data.push({ + CC: "eu" + }); + } + data.forEach((countryWhitelistEntry) => { let countryCode = countryWhitelistEntry.CC; - bannedListHtml += ` + whiteListHTML += ` ${getCountryName(countryCode)} (${countryCode.toUpperCase()}) `; }); - $('#whitelistCountryList').html(bannedListHtml); - filterCountries(data, "#countrySelectorWhitelist .menu .item"); + $('#whitelistCountryList').html(whiteListHTML); + + //Map the data.CC to the country code + let countryCodes = data.map(function(item){ + return item.CC; + }); + filterCountries(countryCodes, "#countrySelectorWhitelist .menu .item"); if (data.length === 0) { $('#whitelistCountryList').append(` @@ -1016,6 +1065,10 @@

Quick Ban List

}); } + function getEUCCs(){ + return ["at","be","bg","cy","cz","de","dk","ee","es","fi","fr","gr","hr","hu","ie","it","lt","lu","lv","mt","nl","pl","pt","se","si","sk"]; + } + function addCountryToBlacklist() { var countryCode = $("#countrySelector").dropdown("get value").toLowerCase(); let ccs = [countryCode]; @@ -1025,48 +1078,50 @@

Quick Ban List

ccs = countryCode.split(","); } - let counter = 0; - for(var i = 0; i < ccs.length; i++){ - let thisCountryCode = ccs[i]; - $.cjax({ - type: "POST", - url: "/api/blacklist/country/add", - method: "POST", - data: { cc: thisCountryCode, id: currentEditingAccessRule}, - success: function(response) { - if (response.error != undefined){ - msgbox(response.error, false); - } - - if (counter == (ccs.length - 1)){ - //Last item - setTimeout(function(){ - initBannedCountryList(); - if (ccs.length == 1){ - //Single country - msgbox(`Added ${getCountryName(ccs[0])} to blacklist`); - }else{ - msgbox(ccs.length + " countries added to blacklist"); - } - - }, (ccs.length==1)?0:100); - } - counter++; - }, - error: function(xhr, status, error) { - // handle error response - } + //If the ccs includes "eu", remove the "eu" and add all eu country code to the list + if (ccs.includes("eu")){ + ccs = ccs.concat(getEUCCs()); + ccs = ccs.filter(function(item){ + return item != "eu"; }); } - - + + let counter = ccs.length; + $.cjax({ + type: "POST", + url: "/api/blacklist/country/add", + method: "POST", + data: { cc: ccs.join(","), id: currentEditingAccessRule}, + success: function(response) { + if (response.error != undefined){ + msgbox(response.error, false); + } + initBannedCountryList(); + if (ccs.length == 1){ + //Single country + msgbox(`Added ${getCountryName(ccs[0])} to blacklist`); + }else{ + msgbox(ccs.length + " countries added to blacklist"); + } + }, + error: function(xhr, status, error) { + // handle error response + } + }); $('#countrySelector').dropdown('clear'); - } function removeFromBannedList(countryCode){ - countryCode = countryCode.toLowerCase(); let countryName = getCountryName(countryCode); + if (countryCode == "eu"){ + let euCountries = getEUCCs(); + countryCode = euCountries.join(","); + countryName = "European Union"; + }else{ + countryCode = countryCode.toLowerCase(); + } + + $.cjax({ url: "/api/blacklist/country/remove", method: "POST", @@ -1162,44 +1217,53 @@

Quick Ban List

//Usually just a few countries a for loop will get the job done ccs = countryCode.split(","); } - - let counter = 0; - for(var i = 0; i < ccs.length; i++){ - let thisCountryCode = ccs[i]; - $.cjax({ - type: "POST", - url: "/api/whitelist/country/add", - data: { cc: thisCountryCode , id: currentEditingAccessRule}, - success: function(response) { - if (response.error != undefined){ - msgbox(response.error, false); - } - if (counter == (ccs.length - 1)){ - setTimeout(function(){ - initWhitelistCountryList(); - if (ccs.length == 1){ - //Single country - msgbox(`Added ${getCountryName(ccs[0])} to whitelist`); - }else{ - msgbox(ccs.length + " countries added to whitelist"); - } - }, (ccs.length==1)?0:100); - } - counter++; - }, - error: function(xhr, status, error) { - // handle error response - } + //If the ccs includes "eu", remove the "eu" and add all eu country code to the list + if (ccs.includes("eu")){ + ccs = ccs.filter(function(item){ + return item != "eu"; }); + ccs = ccs.concat(getEUCCs()); } + let counter = ccs.length; + $.cjax({ + type: "POST", + url: "/api/whitelist/country/add", + data: { cc: ccs.join(",") , id: currentEditingAccessRule}, + success: function(response) { + if (response.error != undefined){ + msgbox(response.error, false); + } + + initWhitelistCountryList(); + if (ccs.length == 1){ + //Single country + msgbox(`Added ${getCountryName(ccs[0])} to whitelist`); + }else{ + msgbox(ccs.length + " countries added to whitelist"); + } + }, + error: function(xhr, status, error) { + // handle error response + } + }); + + $('#countrySelectorWhitelist').dropdown('clear'); } - function removeFromWhiteList(countryCode){ - if (confirm("Confirm removing " + getCountryName(countryCode) + " from whitelist?")){ + //Remove from whitelist, accepts a country code or "eu" for all EU countries + function removeFromWhiteList(countryCode, skipConfirm = true){ + let countryName = getCountryName(countryCode); + if (countryCode == "eu"){ + let euCountries = getEUCCs(); + countryCode = euCountries.join(","); + countryName = "European Union"; + }else{ countryCode = countryCode.toLowerCase(); + } + if (skipConfirm || confirm("Confirm removing " + getCountryName(countryCode) + " from whitelist?")){ $.cjax({ url: "/api/whitelist/country/remove", method: "POST", @@ -1208,6 +1272,7 @@

Quick Ban List

if (response.error != undefined){ msgbox(response.error, false); } + msgbox(countryName + " removed from whitelist"); initWhitelistCountryList(); }, error: function(xhr, status, error) { @@ -1276,19 +1341,27 @@

Quick Ban List

/* Common Utilities */ - function filterCountries(codesToShow, selector="#countrySelector .menu .item") { + function filterCountries(alreadySelectedCCs, selector="#countrySelector .menu .item") { // get all items in the dropdown const items = document.querySelectorAll(selector); + const euCountries = getEUCCs(); + //Replce "eu" in alreadySelectedCCs with all EU countries + if (alreadySelectedCCs.includes("eu")){ + alreadySelectedCCs = alreadySelectedCCs.filter(function(item){ + return item != "eu"; + }); + alreadySelectedCCs = alreadySelectedCCs.concat(euCountries); + } + // loop through all items items.forEach(item => { // get the value of the item (i.e. the country code) const code = item.dataset.value; - // if the code is in the array of codes to show, show the item - if (codesToShow.includes(code)) { + if (alreadySelectedCCs.includes(code)) { + //This country code already selected. Hide it item.style.display = 'none'; - } - // otherwise, hide the item - else { + } else { + // otherwise, show the item item.style.display = 'block'; } }); diff --git a/src/web/components/redirection.html b/src/web/components/redirection.html index 40bbeb4..4ef40f2 100644 --- a/src/web/components/redirection.html +++ b/src/web/components/redirection.html @@ -30,7 +30,7 @@

Redirection Rules

Redirection Rule Deleted -
+
diff --git a/src/web/components/rules.html b/src/web/components/rules.html index fa4b078..6ce8ead 100644 --- a/src/web/components/rules.html +++ b/src/web/components/rules.html @@ -50,7 +50,7 @@

New Proxy Rule

-
+
diff --git a/src/web/components/status.html b/src/web/components/status.html index 5ab73c5..abef39e 100644 --- a/src/web/components/status.html +++ b/src/web/components/status.html @@ -94,7 +94,7 @@

Global Settings

-
+
@@ -579,7 +579,7 @@

Statistic Overview

let timestamps = []; for(var i = 0; i < dataCount; i++){ - timestamps.push(parseInt(Date.now() / 1000) + i); + timestamps.push(new Date(Date.now() + i * 1000).toLocaleString().replace(',', '')); } function fetchData() { @@ -600,10 +600,8 @@

Statistic Overview

txValues.shift(); } - - timestamps.push(parseInt(Date.now() / 1000)); + timestamps.push(new Date(Date.now()).toLocaleString().replace(',', '')); timestamps.shift(); - updateChart(); } }) diff --git a/src/web/components/vdir.html b/src/web/components/vdir.html index 8d8d7b2..2553fd3 100644 --- a/src/web/components/vdir.html +++ b/src/web/components/vdir.html @@ -69,7 +69,7 @@

New Virtual Directory Rule

-
+
diff --git a/src/web/darktheme.css b/src/web/darktheme.css new file mode 100644 index 0000000..6018b63 --- /dev/null +++ b/src/web/darktheme.css @@ -0,0 +1,330 @@ +/* + Darktheme CSS + + This file contains the CSS for the dark theme. + This will override the default CSS (white theme) for semantic UI +*/ + +body.darkTheme { + background-color: var(--theme_bg); + color: var(--text_color); +} + +body.darkTheme h1, +body.darkTheme h2, +body.darkTheme h3, +body.darkTheme h4, +body.darkTheme h5, +body.darkTheme h6, +body.darkTheme a { + color: var(--text_color); +} + +body.darkTheme .ui.header { + color: var(--text_color) !important; +} + +body.darkTheme p, +body.darkTheme span{ + color: var(--text_color_secondary); +} + +body.darkTheme .ui.secondary.menu .dropdown.item:hover, +body.darkTheme .ui.secondary.menu .link.item:hover, +body.darkTheme .ui.secondary.menu a.item:hover { + color: var(--text_color) !important; +} + +body.darkTheme .ui.basic.white.icon.button { + background-color: transparent !important; + border: none !important; +} + +body.darkTheme .ui.basic.white.icon.button:hover { + border: none !important; + opacity: 0.8; +} + +body.darkTheme .ui.basic.white.icon.button:disabled { + border: none !important; + opacity: 0.5; +} + +body.darkTheme .ui.basic.buttons .button i.icon { + color: #ffffff !important; +} + +body.darkTheme .ui.basic.button:not(.red) { + color: #ffffff !important; + border: 1px solid var(--button_border_color) !important; +} + +body.darkTheme .ui.basic.button:not(.red):hover { + border: 1px solid var(--button_border_color) !important; + background-color: var(--theme_bg) !important; + opacity: 0.8; +} + +body.darkTheme .ui.basic.button.red:hover { + background-color: #380a0a !important; + opacity: 0.8; +} + +body.darkTheme .ui.basic.button:disabled { + border: none !important; + background-color: transparent !important; + opacity: 0.5; +} + +body.darkTheme .ui.basic.button:focus, +body.darkTheme .ui.basic.buttons .button:focus { + background: transparent !important; + background-color: transparent !important; + border: none !important; +} + + +body.darkTheme .ui.table thead th, +body.darkTheme .ui.table tbody td, +body.darkTheme .ui.table tfoot td { + color: #ffffff !important; +} + +body.darkTheme .ui.input input, +body.darkTheme .ui.input input::placeholder, +body.darkTheme .ui.input input:focus, +body.darkTheme .ui.input input:active { + color: #ffffff !important; + border-color: #ffffff !important; +} + +body.darkTheme .ui.input input { + background-color: var(--theme_bg_active) !important; + border: 1px solid transparent !important; +} + +body.darkTheme .ui.input input:focus, +body.darkTheme .ui.input input:active { + border-color: var(--theme_highlight) !important; +} + +body.darkTheme .ui.input input::placeholder { + opacity: 0.7; +} + +body.darkTheme .ui.label, +body.darkTheme .ui.label .detail, +body.darkTheme .ui.label .icon { + color: #ffffff !important; +} + +body.darkTheme .advanceoptions .title { + color: var(--text_color_secondary) !important; +} + +body.darkTheme .ui.toggle.checkbox input ~ .box, +body.darkTheme .ui.toggle.checkbox input ~ label, +body.darkTheme .ui.toggle.checkbox input ~ label:focus { + color: var(--text_color_secondary) !important; +} + + +body.darkTheme .ui.toggle.checkbox input ~ label::before{ + background-color: var(--theme_bg_secondary) !important; +} +body.darkTheme .ui.toggle.checkbox input:checked ~ label::before{ + background-color: var(--theme_highlight) !important; +} + + + +body.darkTheme .ui.segment:not(.basic) { + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; + border: 1px solid transparent !important; +} + +body.darkTheme .sub.header { + color: var(--text_color) !important; +} + +body.darkTheme .ui.radio.defaultsite.checkbox label { + color: var(--text_color) !important; +} + +body.darkTheme .ui.radio.defaultsite.checkbox label small { + color: var(--text_color_secondary) !important; +} + +body.darkTheme .ui.form .field input, +body.darkTheme .ui.form .field input::placeholder, +body.darkTheme .ui.form .field input:focus, +body.darkTheme .ui.form .field input:active { + color: var(--text_color) !important; + border-color: 1px solid transparent !important; + background-color: var(--theme_bg_active) !important; +} + +body.darkTheme .ui.form .field input::placeholder { + opacity: 0.7; +} + +body.darkTheme .ui.form .field label, +body.darkTheme .ui.form .field .ui.checkbox input:checked ~ label { + color: var(--text_color) !important; +} + +body.darkTheme .ui.basic.label { + background-color: var(--theme_bg_secondary) !important; + color: var(--text_color) !important; +} + +/* + HTTP Proxy Table +*/ +body.darkTheme .ui.table{ + background-color: transparent !important; +} +body.darkTheme .ui.celled.sortable.unstackable.compact.table thead th, +body.darkTheme .ui.celled.sortable.unstackable.compact.table tbody td, +body.darkTheme .ui.celled.sortable.unstackable.compact.table tfoot td { + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; + border-color: var(--divider_color) !important; +} + +body.darkTheme .ui.celled.sortable.unstackable.compact.table thead th { + background-color: var(--theme_bg_secondary) !important; +} + +body.darkTheme .ui.celled.sortable.unstackable.compact.table tbody tr:hover { + background-color: var(--theme_bg_hover) !important; +} + +body.darkTheme .ui.celled.sortable.unstackable.compact.table tbody td a { + color: var(--link_color) !important; +} + +body.darkTheme .ui.celled.sortable.unstackable.compact.table tbody td a:hover { + color: var(--link_hover_color) !important; +} + +body.darkTheme .ui.celled.sortable.unstackable.compact.table tbody td small { + color: var(--text_color_secondary) !important; +} + +body.darkTheme .ui.celled.sortable.unstackable.compact.table tbody td .ui.toggle.checkbox input ~ .box, +body.darkTheme .ui.celled.sortable.unstackable.compact.table tbody td .ui.toggle.checkbox input ~ label, +body.darkTheme .ui.celled.sortable.unstackable.compact.table tbody td .ui.toggle.checkbox input ~ label:focus { + color: var(--text_color_secondary) !important; +} + +body.darkTheme .ui.celled.sortable.unstackable.compact.table tbody td .ui.toggle.checkbox input ~ label::before { + background-color: var(--theme_bg_secondary) !important; +} + +body.darkTheme .ui.celled.sortable.unstackable.compact.table tbody td .ui.toggle.checkbox input:checked ~ label::before { + background-color: var(--theme_highlight) !important; +} + +body.darkTheme .ui.celled.sortable.unstackable.compact.table tbody td .ui.circular.mini.basic.icon.button { + color: var(--button_color) !important; + border: 1px solid var(--button_border_color) !important; +} + +body.darkTheme .ui.celled.sortable.unstackable.compact.table tbody td .ui.circular.mini.basic.icon.button:hover { + background-color: var(--button_hover_bg) !important; + color: var(--button_hover_color) !important; + border: 1px solid var(--button_border_color) !important; +} + +body.darkTheme .ui.celled.sortable.unstackable.compact.table tbody td .ui.circular.mini.red.basic.icon.button { + color: var(--button_red_color) !important; + border: 1px solid var(--button_red_border_color) !important; +} + +body.darkTheme .ui.celled.sortable.unstackable.compact.table tbody td .ui.circular.mini.red.basic.icon.button:hover { + background-color: #380a0a !important; + color: var(--button_red_hover_color) !important; + +} + +body.darkTheme .ui.basic.small.icon.circular.button { + color: var(--button_color) !important; + border: 1px solid var(--button_border_color) !important; +} + +body.darkTheme .ui.basic.small.icon.circular.button:hover { + background-color: var(--button_hover_bg) !important; + color: var(--button_hover_color) !important; + border: 1px solid var(--button_border_color) !important; + opacity: 0.8; +} + +body.darkTheme .ui.checkbox input ~ .box, +body.darkTheme .ui.checkbox input ~ label, +body.darkTheme .ui.checkbox input ~ label:focus { + color: var(--text_color_secondary) !important; +} + +body.darkTheme .ui.basic.advance.segment { + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; + border: 1px solid var(--divider_color) !important; +} + +body.darkTheme .ui.endpointAdvanceConfig.accordion .title { + color: var(--text_color) !important; +} + +/* + Virtual Directorie Table + +*/ + +body.darkTheme .ui.fluid.search.selection.dropdown { + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; + border-color: transparent !important; +} + +body.darkTheme .ui.fluid.search.selection.dropdown .menu { + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; +} + +body.darkTheme .ui.fluid.search.selection.dropdown .menu .item { + color: var(--text_color) !important; +} + +body.darkTheme .ui.selection.dropdown .menu > .item { + border-top: 1px solid var(--divider_color) !important; +} + +body.darkTheme .ui.selection.active.dropdown .menu { + border-color: var(--divider_color) !important; +} + +body.darkTheme .ui.fluid.search.selection.dropdown .menu .item:hover { + background-color: var(--theme_bg_hover) !important; + color: var(--text_color) !important; +} + +body.darkTheme .ui.fluid.search.selection.dropdown .menu .item.active.selected { + background-color: var(--theme_highlight) !important; + color: var(--text_color) !important; +} + +body.darkTheme .ui.fluid.search.selection.dropdown .search { + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; + border-color: transparent !important; +} + +body.darkTheme .ui.fluid.search.selection.dropdown .text { + color: var(--text_color) !important; +} + +body.darkTheme .ui.fluid.search.selection.dropdown .dropdown.icon { + color: var(--text_color) !important; +} \ No newline at end of file diff --git a/src/web/index.html b/src/web/index.html index a767e32..c025184 100644 --- a/src/web/index.html +++ b/src/web/index.html @@ -16,8 +16,10 @@ + +
@@ -269,11 +271,9 @@

Welcome to Zoraxy Tour

function toggleTheme(){ if ($("body").hasClass("darkTheme")){ - $("body").removeClass("darkTheme") - $("#themeColorButton").html(``); + setDarkTheme(false); }else{ - $("body").addClass("darkTheme"); - $("#themeColorButton").html(``); + setDarkTheme(true); } } diff --git a/src/web/main.css b/src/web/main.css index f7e5861..1ce8364 100644 --- a/src/web/main.css +++ b/src/web/main.css @@ -2,13 +2,10 @@ index.html style overwrite */ :root{ - --theme_background: linear-gradient(60deg, rgb(84, 58, 183) 0%, rgb(0, 172, 193) 100%); - --theme_background_inverted: linear-gradient(215deg, rgba(38,60,71,1) 13%, rgba(2,3,42,1) 84%); - --theme_green: linear-gradient(270deg, #27e7ff, #00ca52); - --theme_red: linear-gradient(203deg, rgba(250,172,38,1) 17%, rgba(202,0,37,1) 78%); + } -/* Theme Color Definations */ +/* Theme Color Definition */ body:not(.darkTheme){ --theme_bg: #f6f6f6; --theme_bg_primary: #ffffff; @@ -16,7 +13,7 @@ body:not(.darkTheme){ --theme_bg_active: #ececec; --theme_highlight: #a9d1f3; --theme_bg_inverted: #27292d; - --theme_advance: #f8f8f9; + --theme_advance: #f7f7f7; --item_color: #5e5d5d; --item_color_select: rgba(0,0,0,.87); --text_color: #414141; @@ -25,25 +22,35 @@ body:not(.darkTheme){ --text_color_inverted: #fcfcfc; --button_text_color: #878787; --button_border_color: #dedede; + + --theme_background: linear-gradient(60deg, rgb(84, 58, 183) 0%, rgb(0, 172, 193) 100%); + --theme_background_inverted: linear-gradient(215deg, rgba(38,60,71,1) 13%, rgba(2,3,42,1) 84%); + --theme_green: linear-gradient(270deg, #27e7ff, #00ca52); + --theme_red: linear-gradient(203deg, rgba(250,172,38,1) 17%, rgba(202,0,37,1) 78%); } body.darkTheme{ - --theme_bg: #27292d; - --theme_bg_primary: #3d3f47; - --theme_bg_secondary: #373a42; - --theme_highlight: #6682c4; - --theme_bg_active: #292929; + --theme_bg: #000000; + --theme_bg_primary: #141414; + --theme_bg_secondary:#230046; + --theme_highlight: #320064; + --theme_bg_active: #020101; --theme_bg_inverted: #f8f8f9; - --theme_advance: #333333; + --theme_advance: #000000; --item_color: #cacaca; - --text_color: #fcfcfc; - --text_color_secondary: #dfdfdf; + --text_color: #eef1f3; + --text_color_secondary: #b5c0c7; --input_color: black; - --divider_color: #3b3b3b; + --divider_color: #282828; --item_color_select: rgba(255, 255, 255, 0.87); --text_color_inverted: #414141; --button_text_color: #e9e9e9; --button_border_color: #646464; + + --theme_background: linear-gradient(214deg, rgba(3,1,70,1) 17%, rgba(60,1,80,1) 78%); + --theme_background_inverted: linear-gradient(215deg, rgba(38,60,71,1) 13%, rgba(2,3,42,1) 84%); + --theme_green: linear-gradient(214deg, rgba(25,128,94,1) 17%, rgba(62,76,111,1) 78%); + --theme_red: linear-gradient(203deg, rgba(250,172,38,1) 17%, rgba(202,0,37,1) 78%); } /* Theme Toggle CSS */ @@ -368,7 +375,7 @@ body{ } .basic.segment.advanceoptions{ - background-color: #f7f7f7; + background-color: var(--theme_advance); border-radius: 1em; } diff --git a/src/web/script/darktheme.js b/src/web/script/darktheme.js new file mode 100644 index 0000000..8a01b82 --- /dev/null +++ b/src/web/script/darktheme.js @@ -0,0 +1,51 @@ +/* + Dark Theme Toggle Manager + + This script is used to manage the dark theme toggle button in the header of the website. + It will change the theme of the website to dark mode when the toggle is clicked and back to light mode when clicked again. + + Must be included just after the start of body tag in the HTML file. +*/ + +function _whiteThemeHandleApplyChange(){ + $(".menubar .logo").attr("src", "img/logo.svg"); +} + +function _darkThemeHandleApplyChange(){ + $(".menubar .logo").attr("src", "img/logo_white.svg"); +} + + + //Check if the theme is dark, must be done before the body is loaded to prevent flickering + function setDarkTheme(isDarkTheme = false){ + if (isDarkTheme){ + $("body").addClass("darkTheme"); + $("#themeColorButton").html(``); + localStorage.setItem("theme", "dark"); + + //Check if the page is still loading, if not change the logo + if (document.readyState == "complete"){ + _darkThemeHandleApplyChange(); + }else{ + //Wait for the page to load and then change the logo + $(document).ready(function(){ + _darkThemeHandleApplyChange(); + }); + } + }else{ + $("body").removeClass("darkTheme") + $("#themeColorButton").html(``); + localStorage.setItem("theme", "light"); + //By default the page is white theme. So no need to change the logo if page is still loading + if (document.readyState == "complete"){ + //Switching back to light theme + _whiteThemeHandleApplyChange(); + } + } +} + +if (localStorage.getItem("theme") == "dark"){ + setDarkTheme(true); +}else{ + setDarkTheme(false); +} \ No newline at end of file diff --git a/src/web/snippet/acme.html b/src/web/snippet/acme.html index 6dd80b7..e4a5b24 100644 --- a/src/web/snippet/acme.html +++ b/src/web/snippet/acme.html @@ -50,7 +50,7 @@

ACME Email

If you don't want to share your private email address, you can also fill in an email address that point to a mailbox not exists on your domain.
-
+
@@ -437,11 +437,15 @@

Notes & FAQ

let optionalFieldsHTML = ""; for (const [key, datatype] of Object.entries(data)) { if (datatype == "int"){ - $("#dnsProviderAPIFields").append(`
+ let defaultValue = 10; + if (key == "HTTPTimeout"){ + defaultValue = 300; + } + $("#dnsProviderAPIFields").append(`
${key}
- +
`); }else if (datatype == "bool"){ booleanFieldsHTML += (`
@@ -600,8 +604,12 @@

Notes & FAQ

//Boolean option let checked = $(this).find("input")[0].checked; dnsCredentials[thisKey] = checked; + }else if ($(this).hasClass("typeint")){ + //Int options + let value = $(this).find("input").val(); + dnsCredentials[thisKey] = parseInt(value); }else{ - //String or int options + //String options let value = $(this).find("input").val().trim(); dnsCredentials[thisKey] = value; } diff --git a/src/web/snippet/customHeaders.html b/src/web/snippet/customHeaders.html index 3167e64..36ee37b 100644 --- a/src/web/snippet/customHeaders.html +++ b/src/web/snippet/customHeaders.html @@ -86,7 +86,7 @@

Edit Custom Header

-
+
diff --git a/src/web/tools/fs.css b/src/web/tools/fs.css index 2e785b1..5345604 100644 --- a/src/web/tools/fs.css +++ b/src/web/tools/fs.css @@ -6,7 +6,7 @@ */ /* - Color definations + Color definition */ :root{ --dark_theme_toggle: #333333; From 4cf5d29692d1b1c5ff42f246f3d18d23349746f1 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Sat, 9 Nov 2024 16:12:41 +0800 Subject: [PATCH 02/14] Added more dark theme --- src/web/components/redirection.html | 2 +- src/web/darktheme.css | 394 +++++++++++++++++++++++++++- src/web/index.html | 2 +- src/web/main.css | 4 +- 4 files changed, 390 insertions(+), 12 deletions(-) diff --git a/src/web/components/redirection.html b/src/web/components/redirection.html index 4ef40f2..98088e0 100644 --- a/src/web/components/redirection.html +++ b/src/web/components/redirection.html @@ -173,7 +173,7 @@

Add Redirection Rule

}); if (data.length == 0){ - $("#redirectionRuleList").append(` No redirection rule`); + $("#redirectionRuleList").append(` No redirection rule`); } }); diff --git a/src/web/darktheme.css b/src/web/darktheme.css index 6018b63..8260122 100644 --- a/src/web/darktheme.css +++ b/src/web/darktheme.css @@ -100,7 +100,7 @@ body.darkTheme .ui.input input:active { body.darkTheme .ui.input input { background-color: var(--theme_bg_active) !important; - border: 1px solid transparent !important; + border: 1px solid var(--button_border_color) !important; } body.darkTheme .ui.input input:focus, @@ -136,7 +136,19 @@ body.darkTheme .ui.toggle.checkbox input:checked ~ label::before{ background-color: var(--theme_highlight) !important; } +#sidemenuBtn{ + border: 1px solid var(--button_border_color) !important; +} +.toolbar{ + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; + border: 1px solid var(--divider_color) !important; +} + +.toobar #mainmenu a.item:hover{ + background-color: var(--theme_highlight) !important; +} body.darkTheme .ui.segment:not(.basic) { background-color: var(--theme_bg) !important; @@ -156,13 +168,16 @@ body.darkTheme .ui.radio.defaultsite.checkbox label small { color: var(--text_color_secondary) !important; } -body.darkTheme .ui.form .field input, +body.darkTheme .ui.form .field input{ + color: var(--text_color) !important; + background-color: var(--theme_bg_active) !important; + border-color: var(--button_border_color) !important; +} + body.darkTheme .ui.form .field input::placeholder, body.darkTheme .ui.form .field input:focus, body.darkTheme .ui.form .field input:active { - color: var(--text_color) !important; - border-color: 1px solid transparent !important; - background-color: var(--theme_bg_active) !important; + border-color: var(--theme_highlight) !important; } body.darkTheme .ui.form .field input::placeholder { @@ -179,6 +194,10 @@ body.darkTheme .ui.basic.label { color: var(--text_color) !important; } +body.darkTheme .ui.form .grouped.fields label { + color: var(--text_color) !important; +} + /* HTTP Proxy Table */ @@ -277,15 +296,18 @@ body.darkTheme .ui.endpointAdvanceConfig.accordion .title { color: var(--text_color) !important; } +body.darkTheme .RateLimit input { + border-color: var(--theme_highlight) !important; +} + /* Virtual Directorie Table - */ body.darkTheme .ui.fluid.search.selection.dropdown { background-color: var(--theme_bg) !important; color: var(--text_color) !important; - border-color: transparent !important; + border-color: var(--divider_color) !important; } body.darkTheme .ui.fluid.search.selection.dropdown .menu { @@ -327,4 +349,360 @@ body.darkTheme .ui.fluid.search.selection.dropdown .text { body.darkTheme .ui.fluid.search.selection.dropdown .dropdown.icon { color: var(--text_color) !important; -} \ No newline at end of file +} + + +/* + + New Proxy Rule + +*/ + +body.darkTheme .ui.horizontal.divider.transition.visible { + color: var(--text_color) !important; +} + +body.darkTheme #basicAuthCredPassword, body.darkTheme #basicAuthCredUsername { + color: var(--text_color) !important; + background-color: var(--theme_bg_active) !important; + border: 1px solid var(--button_border_color) !important; +} + +body.darkTheme #rules .field label { + color: var(--text_color) !important; +} + +body.darkTheme #rules .field .ui.selection.dropdown { + background-color: var(--theme_bg_primary) !important; + color: var(--text_color) !important; + border-color: transparent !important; +} + +body.darkTheme #rules .field .ui.selection.dropdown .menu { + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; +} + +body.darkTheme #rules .field .ui.selection.dropdown .menu .item { + color: var(--text_color) !important; +} + +body.darkTheme #rules .field .ui.selection.dropdown .menu .item:hover { + background-color: var(--theme_bg_hover) !important; + color: var(--text_color) !important; +} + +body.darkTheme #rules .field .ui.selection.dropdown .menu .item.active.selected { + background-color: var(--theme_highlight) !important; + color: var(--text_color) !important; +} + +body.darkTheme #rules .field .ui.selection.dropdown .text { + color: var(--text_color) !important; +} + +body.darkTheme #rules .field small { + color: var(--text_color_secondary) !important; +} + +/* + + Stream Proxy + +*/ + +body.darkTheme #streamproxy { + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; + border: 1px solid var(--divider_color) !important; +} + +body.darkTheme #proxyTable { + background-color: transparent !important; + color: var(--text_color) !important; + border: 1px solid var(--divider_color) !important; +} + +body.darkTheme #proxyTable thead th { + background-color: var(--theme_bg_secondary) !important; + color: var(--text_color) !important; + border-color: var(--divider_color) !important; +} + +body.darkTheme #proxyTable tbody tr td:not(:first-child) { + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; + border-color: var(--divider_color) !important; +} + +body.darkTheme #proxyTable tbody tr:hover { + background-color: var(--theme_bg_hover) !important; +} + +body.darkTheme #proxyTable tbody td .statusText { + color: var(--text_color_secondary) !important; +} + +body.darkTheme #proxyTable tbody td .ui.basic.mini.circular.icon.button { + color: var(--button_color) !important; + border: 1px solid var(--button_border_color) !important; +} + +body.darkTheme #proxyTable tbody td .ui.basic.mini.circular.icon.button:hover { + background-color: var(--button_hover_bg) !important; + color: var(--button_hover_color) !important; + border: 1px solid var(--button_border_color) !important; +} + +body.darkTheme #proxyTable tbody td .ui.circular.red.basic.mini.icon.button { + color: var(--button_red_color) !important; + border: 1px solid var(--button_red_border_color) !important; +} + +body.darkTheme #proxyTable tbody td .ui.circular.red.basic.mini.icon.button:hover { + background-color: #380a0a !important; + color: var(--button_red_hover_color) !important; +} + +/* + + Redirect + +*/ + +body.darkTheme #redirectset .ui.sortable.unstackable.celled.table thead th { + background-color: var(--theme_bg_secondary) !important; + color: var(--text_color) !important; + border-color: var(--divider_color) !important; +} + +body.darkTheme #redirectset .ui.sortable.unstackable.celled.table tbody tr td { + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; + border-color: var(--divider_color) !important; +} + +body.darkTheme #redirectset .ui.sortable.unstackable.celled.table tbody tr:hover { + background-color: var(--theme_bg_hover) !important; +} + +body.darkTheme #redirectset .ui.sortable.unstackable.celled.table tbody td .icon { + color: var(--icon_color) !important; +} + +body.darkTheme #redirectset .ui.sortable.unstackable.celled.table tbody td .ui.button { + color: var(--button_color) !important; + border: 1px solid var(--button_border_color) !important; +} + +body.darkTheme #redirectset .ui.sortable.unstackable.celled.table tbody td .ui.button:hover { + background-color: var(--button_hover_bg) !important; + color: var(--button_hover_color) !important; + border: 1px solid var(--button_border_color) !important; +} + +body.darkTheme #redirectset .ui.sortable.unstackable.celled.table tbody td .ui.red.button { + color: var(--button_red_color) !important; + border: 1px solid var(--button_red_border_color) !important; +} + +body.darkTheme #redirectset .ui.sortable.unstackable.celled.table tbody td .ui.red.button:hover { + background-color: #380a0a !important; + color: var(--button_red_hover_color) !important; +} + +body.darkTheme #redirectset .ui.checkbox input:checked ~ label, +body.darkTheme #redirectset .ui.checkbox input:checked ~ label small, +body.darkTheme #redirectset .ui.checkbox input:checked ~ label a { + color: var(--text_color_secondary) !important; +} + +body.darkTheme .ui.message { + color: var(--text_color) !important; + background-color: var(--theme_bg_active) !important; + border: 1px solid var(--message_border_color) !important; +} + +/* + Access Rules +*/ + +/* Access Rule Selector */ + +body.darkTheme .ui.selection.fluid.dropdown#accessRuleSelector { + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; + border-color: transparent !important; +} + +body.darkTheme .ui.selection.fluid.dropdown#accessRuleSelector .menu { + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; +} + +body.darkTheme .ui.selection.fluid.dropdown#accessRuleSelector .menu .item { + color: var(--text_color) !important; +} + +body.darkTheme .ui.selection.fluid.dropdown#accessRuleSelector .menu .item:hover { + background-color: var(--theme_bg_hover) !important; + color: var(--text_color) !important; +} + +body.darkTheme .ui.selection.fluid.dropdown#accessRuleSelector .menu .item.active.selected { + background-color: var(--theme_highlight) !important; + color: var(--text_color) !important; +} + +body.darkTheme .ui.selection.fluid.dropdown#accessRuleSelector .text { + color: var(--text_color) !important; +} + +body.darkTheme .ui.selection.fluid.dropdown#accessRuleSelector .dropdown.icon { + color: var(--text_color) !important; +} + +/* Tab Menu in access control */ + +body.darkTheme .ui.top.attached.tabular.menu { + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; +} + +body.darkTheme .ui.top.attached.tabular.menu .item { + color: var(--text_color) !important; +} + +body.darkTheme .ui.top.attached.tabular.menu .item:hover { + background-color: var(--theme_bg_hover) !important; + color: var(--text_color) !important; +} + +body.darkTheme .ui.top.attached.tabular.menu .item.active { + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; +} + +/* Tables in access control */ + +body.darkTheme #access .ui.unstackable.basic.celled.table{ + border: 1px solid var(--divider_color) !important; +} + +body.darkTheme #access .ui.unstackable.basic.celled.table thead th { + background-color: var(--theme_bg_secondary) !important; + color: var(--text_color) !important; + border-color: var(--divider_color) !important; +} + +body.darkTheme #access .ui.unstackable.basic.celled.table tbody tr td { + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; + border-color: var(--divider_color) !important; +} + +body.darkTheme #access .ui.unstackable.basic.celled.table tbody tr:hover { + background-color: var(--theme_bg_hover) !important; +} + +body.darkTheme #access .ui.unstackable.basic.celled.table tbody td .icon { + color: var(--icon_color) !important; +} + +body.darkTheme #access .ui.unstackable.basic.celled.table tbody td .ui.button { + color: var(--button_color) !important; + border: 1px solid var(--button_border_color) !important; +} + +body.darkTheme #access .ui.unstackable.basic.celled.table tbody td .ui.button:hover { + background-color: var(--button_hover_bg) !important; + color: var(--button_hover_color) !important; + border: 1px solid var(--button_border_color) !important; +} + +body.darkTheme #access .ui.unstackable.basic.celled.table tbody td .ui.red.button { + color: var(--button_red_color) !important; + border: 1px solid var(--button_red_border_color) !important; +} + +body.darkTheme #access .ui.unstackable.basic.celled.table tbody td .ui.red.button:hover { + background-color: #380a0a !important; + color: var(--button_red_hover_color) !important; +} + +/* Fixing the color of the sel;ector label in country selector */ +body.darkTheme .ui.search.multiple.selection.dropdown .ui.label { + background-color: var(--theme_bg_secondary) !important; + color: var(--text_color) !important; +} + +/* Quick band IP table *(ipTable) */ +body.darkTheme #ipTable { + background-color: transparent !important; + color: var(--text_color) !important; + border: 1px solid var(--divider_color) !important; +} + +body.darkTheme #ipTable thead th { + background-color: var(--theme_bg_secondary) !important; + color: var(--text_color) !important; + border-color: var(--divider_color) !important; +} + +body.darkTheme #ipTable tbody tr td { + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; + border-color: var(--divider_color) !important; +} + +body.darkTheme #ipTable tbody tr:hover { + background-color: var(--theme_bg_hover) !important; +} + +body.darkTheme #ipTable tbody td .ui.basic.red.tiny.icon.button { + color: var(--button_red_color) !important; + border: 1px solid var(--button_red_border_color) !important; +} + +body.darkTheme #ipTable tbody td .ui.basic.red.tiny.icon.button:hover { + background-color: #380a0a !important; + color: var(--button_red_hover_color) !important; +} + + +/* + TLS / SSL Certificates +*/ +body.darkTheme .ui.selection.dropdown#defaultCA { + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; + border-color: transparent !important; +} + +body.darkTheme .ui.selection.dropdown#defaultCA .menu { + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; +} + +body.darkTheme .ui.selection.dropdown#defaultCA .menu .item { + color: var(--text_color) !important; +} + +body.darkTheme .ui.selection.dropdown#defaultCA .menu .item:hover { + background-color: var(--theme_bg_hover) !important; + color: var(--text_color) !important; +} + +body.darkTheme .ui.selection.dropdown#defaultCA .menu .item.active.selected { + background-color: var(--theme_highlight) !important; + color: var(--text_color) !important; +} + +body.darkTheme .ui.selection.dropdown#defaultCA .default.text { + color: var(--text_color) !important; +} + +body.darkTheme .ui.selection.dropdown#defaultCA .dropdown.icon { + color: var(--text_color) !important; +} + diff --git a/src/web/index.html b/src/web/index.html index c025184..e4785fc 100644 --- a/src/web/index.html +++ b/src/web/index.html @@ -26,7 +26,7 @@
diff --git a/src/web/main.css b/src/web/main.css index 1ce8364..50063d5 100644 --- a/src/web/main.css +++ b/src/web/main.css @@ -33,12 +33,12 @@ body.darkTheme{ --theme_bg: #000000; --theme_bg_primary: #141414; --theme_bg_secondary:#230046; - --theme_highlight: #320064; + --theme_highlight: #743788; --theme_bg_active: #020101; --theme_bg_inverted: #f8f8f9; --theme_advance: #000000; --item_color: #cacaca; - --text_color: #eef1f3; + --text_color: #dee1e4; --text_color_secondary: #b5c0c7; --input_color: black; --divider_color: #282828; From ec5c24b9b847549c6ba450270835b71e0127b307 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Thu, 14 Nov 2024 21:18:05 +0800 Subject: [PATCH 03/14] Added more darktheme - Added more dark theme css - Merged main branch fixes and new features - Added todo tag for custom timeout --- src/mod/acme/autorenew.go | 1 - src/mod/dynamicproxy/dpcore/dpcore.go | 2 + src/mod/sshprox/sshprox.go | 26 ++--- src/mod/sshprox/sshprox_test.go | 66 +++++++++++ src/mod/sshprox/utils.go | 63 +++++++++- src/web/components/cert.html | 2 +- src/web/darktheme.css | 61 ++++++++-- src/web/snippet/dockerContainersList.html | 135 ++++++++++++++-------- src/webssh.go | 4 +- 9 files changed, 277 insertions(+), 83 deletions(-) create mode 100644 src/mod/sshprox/sshprox_test.go diff --git a/src/mod/acme/autorenew.go b/src/mod/acme/autorenew.go index 33a5fc1..77ccae3 100644 --- a/src/mod/acme/autorenew.go +++ b/src/mod/acme/autorenew.go @@ -308,7 +308,6 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) { } if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) { //This cert is expired - DNSName, err := ExtractDomains(certBytes) if err != nil { //Maybe self signed. Ignore this diff --git a/src/mod/dynamicproxy/dpcore/dpcore.go b/src/mod/dynamicproxy/dpcore/dpcore.go index 862f3da..5ae55bc 100644 --- a/src/mod/dynamicproxy/dpcore/dpcore.go +++ b/src/mod/dynamicproxy/dpcore/dpcore.go @@ -109,6 +109,8 @@ func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOp thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2 thisTransporter.(*http.Transport).DisableCompression = true + //TODO: Add user adjustable timeout option here + if dpcOptions.IgnoreTLSVerification { //Ignore TLS certificate validation error thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true diff --git a/src/mod/sshprox/sshprox.go b/src/mod/sshprox/sshprox.go index ed1b92c..113cb45 100644 --- a/src/mod/sshprox/sshprox.go +++ b/src/mod/sshprox/sshprox.go @@ -50,21 +50,6 @@ func NewSSHProxyManager() *Manager { } } -// Get the next free port in the list -func (m *Manager) GetNextPort() int { - nextPort := m.StartingPort - occupiedPort := make(map[int]bool) - for _, instance := range m.Instances { - occupiedPort[instance.AssignedPort] = true - } - for { - if !occupiedPort[nextPort] { - return nextPort - } - nextPort++ - } -} - func (m *Manager) HandleHttpByInstanceId(instanceId string, w http.ResponseWriter, r *http.Request) { targetInstance, err := m.GetInstanceById(instanceId) if err != nil { @@ -168,6 +153,17 @@ func (i *Instance) CreateNewConnection(listenPort int, username string, remoteIp if username != "" { connAddr = username + "@" + remoteIpAddr } + + //Trim the space in the username and remote address + username = strings.TrimSpace(username) + remoteIpAddr = strings.TrimSpace(remoteIpAddr) + + //Validate the username and remote address + err := ValidateUsernameAndRemoteAddr(username, remoteIpAddr) + if err != nil { + return err + } + configPath := filepath.Join(filepath.Dir(i.ExecPath), ".gotty") title := username + "@" + remoteIpAddr if remotePort != 22 { diff --git a/src/mod/sshprox/sshprox_test.go b/src/mod/sshprox/sshprox_test.go new file mode 100644 index 0000000..36a9ab5 --- /dev/null +++ b/src/mod/sshprox/sshprox_test.go @@ -0,0 +1,66 @@ +package sshprox + +import ( + "testing" +) + +func TestInstance_Destroy(t *testing.T) { + manager := NewSSHProxyManager() + instance, err := manager.NewSSHProxy("/tmp") + if err != nil { + t.Fatalf("Failed to create new SSH proxy: %v", err) + } + + instance.Destroy() + + if len(manager.Instances) != 0 { + t.Errorf("Expected Instances to be empty, got %d", len(manager.Instances)) + } +} + +func TestInstance_ValidateUsernameAndRemoteAddr(t *testing.T) { + tests := []struct { + username string + remoteAddr string + expectError bool + }{ + {"validuser", "127.0.0.1", false}, + {"valid.user", "example.com", false}, + {"; bash ;", "example.com", true}, + {"valid-user", "example.com", false}, + {"invalid user", "127.0.0.1", true}, + {"validuser", "invalid address", true}, + {"invalid@user", "127.0.0.1", true}, + {"validuser", "invalid@address", true}, + {"injection; rm -rf /", "127.0.0.1", true}, + {"validuser", "127.0.0.1; rm -rf /", true}, + {"$(reboot)", "127.0.0.1", true}, + {"validuser", "$(reboot)", true}, + {"validuser", "127.0.0.1; $(reboot)", true}, + {"validuser", "127.0.0.1 | ls", true}, + {"validuser", "127.0.0.1 & ls", true}, + {"validuser", "127.0.0.1 && ls", true}, + {"validuser", "127.0.0.1 |& ls", true}, + {"validuser", "127.0.0.1 ; ls", true}, + {"validuser", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", false}, + {"validuser", "2001:db8::ff00:42:8329", false}, + {"validuser", "2001:db8:0:1234:0:567:8:1", false}, + {"validuser", "2001:db8::1234:0:567:8:1", false}, + {"validuser", "2001:db8:0:0:0:0:2:1", false}, + {"validuser", "2001:db8::2:1", false}, + {"validuser", "2001:db8:0:0:8:800:200c:417a", false}, + {"validuser", "2001:db8::8:800:200c:417a", false}, + {"validuser", "2001:db8:0:0:8:800:200c:417a; rm -rf /", true}, + {"validuser", "2001:db8::8:800:200c:417a; rm -rf /", true}, + } + + for _, test := range tests { + err := ValidateUsernameAndRemoteAddr(test.username, test.remoteAddr) + if test.expectError && err == nil { + t.Errorf("Expected error for username %s and remoteAddr %s, but got none", test.username, test.remoteAddr) + } + if !test.expectError && err != nil { + t.Errorf("Did not expect error for username %s and remoteAddr %s, but got %v", test.username, test.remoteAddr, err) + } + } +} diff --git a/src/mod/sshprox/utils.go b/src/mod/sshprox/utils.go index 082c8d9..0e4d271 100644 --- a/src/mod/sshprox/utils.go +++ b/src/mod/sshprox/utils.go @@ -1,9 +1,11 @@ package sshprox import ( + "errors" "fmt" "net" "net/url" + "regexp" "runtime" "strings" "time" @@ -34,6 +36,21 @@ func IsWebSSHSupported() bool { return true } +// Get the next free port in the list +func (m *Manager) GetNextPort() int { + nextPort := m.StartingPort + occupiedPort := make(map[int]bool) + for _, instance := range m.Instances { + occupiedPort[instance.AssignedPort] = true + } + for { + if !occupiedPort[nextPort] { + return nextPort + } + nextPort++ + } +} + // Check if a given domain and port is a valid ssh server func IsSSHConnectable(ipOrDomain string, port int) bool { timeout := time.Second * 3 @@ -60,13 +77,47 @@ func IsSSHConnectable(ipOrDomain string, port int) bool { return string(buf[:7]) == "SSH-2.0" } -// Check if the port is used by other process or application -func isPortInUse(port int) bool { - address := fmt.Sprintf(":%d", port) - listener, err := net.Listen("tcp", address) - if err != nil { +// Validate the username and remote address to prevent injection +func ValidateUsernameAndRemoteAddr(username string, remoteIpAddr string) error { + // Validate and sanitize the username to prevent ssh injection + validUsername := regexp.MustCompile(`^[a-zA-Z0-9._-]+$`) + if !validUsername.MatchString(username) { + return errors.New("invalid username, only alphanumeric characters, dots, underscores and dashes are allowed") + } + + //Check if the remoteIpAddr is a valid ipv4 or ipv6 address + if net.ParseIP(remoteIpAddr) != nil { + //A valid IP address do not need further validation + return nil + } + + // Validate and sanitize the remote domain to prevent injection + validRemoteAddr := regexp.MustCompile(`^[a-zA-Z0-9._-]+$`) + if !validRemoteAddr.MatchString(remoteIpAddr) { + return errors.New("invalid remote address, only alphanumeric characters, dots, underscores and dashes are allowed") + } + + return nil +} + +// Check if the given ip or domain is a loopback address +// or resolves to a loopback address +func IsLoopbackIPOrDomain(ipOrDomain string) bool { + if strings.EqualFold(strings.TrimSpace(ipOrDomain), "localhost") || strings.TrimSpace(ipOrDomain) == "127.0.0.1" { return true } - listener.Close() + + //Check if the ipOrDomain resolves to a loopback address + ips, err := net.LookupIP(ipOrDomain) + if err != nil { + return false + } + + for _, ip := range ips { + if ip.IsLoopback() { + return true + } + } + return false } diff --git a/src/web/components/cert.html b/src/web/components/cert.html index 64c2ac5..bb35c34 100644 --- a/src/web/components/cert.html +++ b/src/web/components/cert.html @@ -101,7 +101,7 @@

Fallback Certificate

Upload Default Keypairs

- +
diff --git a/src/web/darktheme.css b/src/web/darktheme.css index 8260122..eac711b 100644 --- a/src/web/darktheme.css +++ b/src/web/darktheme.css @@ -144,11 +144,7 @@ body.darkTheme .ui.toggle.checkbox input:checked ~ label::before{ background-color: var(--theme_bg) !important; color: var(--text_color) !important; border: 1px solid var(--divider_color) !important; -} - -.toobar #mainmenu a.item:hover{ - background-color: var(--theme_highlight) !important; -} +}s body.darkTheme .ui.segment:not(.basic) { background-color: var(--theme_bg) !important; @@ -156,6 +152,12 @@ body.darkTheme .ui.segment:not(.basic) { border: 1px solid transparent !important; } +body.darkTheme .ui.segment{ + background-color: transparent !important; + color: var(--text_color) !important; + border: 1px solid transparent !important; +} + body.darkTheme .sub.header { color: var(--text_color) !important; } @@ -565,7 +567,7 @@ body.darkTheme .ui.selection.fluid.dropdown#accessRuleSelector .dropdown.icon { /* Tab Menu in access control */ body.darkTheme .ui.top.attached.tabular.menu { - background-color: var(--theme_bg) !important; + background-color: transparent !important; color: var(--text_color) !important; } @@ -579,7 +581,7 @@ body.darkTheme .ui.top.attached.tabular.menu .item:hover { } body.darkTheme .ui.top.attached.tabular.menu .item.active { - background-color: var(--theme_bg) !important; + background-color: var(--theme_bg_primary) !important; color: var(--text_color) !important; } @@ -706,3 +708,48 @@ body.darkTheme .ui.selection.dropdown#defaultCA .dropdown.icon { color: var(--text_color) !important; } +/* + +ZeroTier + +*/ + +body.darkTheme #gan .ui.list .item .icon { + color: var(--icon_color) !important; +} + +body.darkTheme #gan .ui.list .item .content .header { + color: var(--text_color) !important; +} + +body.darkTheme #gan .ui.list .item .content .description { + color: var(--text_color_secondary) !important; +} + +body.darkTheme #gan .clickable.iprange.active { + background-color: var(--theme_highlight) !important; +} + +body.darkTheme #gan thead th { + background-color: var(--theme_bg_secondary) !important; + color: var(--text_color) !important; + border-color: var(--divider_color) !important; +} + + +/* + + Uptime Monitor + +*/ + +body.darkTheme #utm .standardContainer { + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; + border: 1px solid var(--divider_color) !important; +} + +body.darkTheme #utm .standardContainer .padding.statusDot { + background-color: var(--theme_bg) !important; + border: 0.2px solid var(--theme_bg_primary) !important; +} \ No newline at end of file diff --git a/src/web/snippet/dockerContainersList.html b/src/web/snippet/dockerContainersList.html index c71c09a..d032968 100644 --- a/src/web/snippet/dockerContainersList.html +++ b/src/web/snippet/dockerContainersList.html @@ -2,7 +2,7 @@ - + @@ -10,6 +10,19 @@
+
+
+ + +
+
List of Docker Containers @@ -33,56 +46,70 @@

-
-
-

Select an Access Rule to edit

- -