diff --git a/data/en.json b/data/en.json index 0bfe12d4..248e4adc 100644 --- a/data/en.json +++ b/data/en.json @@ -1 +1 @@ -{"en":{"language":"English","home":{"title":"Main Menu","btn":"Main Menu","nav":"Home"},"save":"Save Settings","user":"Username","pass":"Password","hasp":{"title":"HASP Design","btn":"HASP Design","theme":"UI Theme","color1":"Primary color","color2":"Secondary color","pages":"Start Layout","font":"Default Font","startpage":"Startup Page","startdim":"Startup Dim"},"screenshot":{"title":"Screenshot","btn":"Screenshot","nav":"Screenshot","prev":"Prev Page","next":"Next Page","refresh":"Refresh"},"info":{"title":"Information","btn":"Information","nav":"Information"},"config":{"title":"Configuration","btn":"Configuration","nav":"Settings"},"ota":{"title":"Firmware Update","btn":"Firmware Update","nav":"Firmware","submit":"Update Firmware","file":"Firmware File","url":"Firmware URL","redirect":"Follow Redirects","never":"Never","strict":"Strict","always":"Always"},"editor":{"title":"File Editor","btn":"File Editor","nav":"File Editor"},"reset":{"title":"Factory Reset","btn":"Factory Reset","warning":"Warning","message":"This process will reset all settings to the default values. The internal flash will be erased and the device is restarted. You may need to connect to the WiFi AP displayed on the panel to reconfigure the device before accessing it again.","fileloss":"ALL FILES WILL BE LOST!"},"reboot":{"title":"Rebooting...","btn":"Restart","nav":"Reboot","message":"The device is rebooting."},"about":{"credits":"Based on the previous work of the following open source developers:","copyright":"Copyright ","rights":"All rights reserved.","clause1":"Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:","clause2":"The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.","clause3":"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.","mit":"MIT License","bsd":"BSD License","freebsd":"FreeBSD License","apache2":"Apache2 License"},"wifi":{"title":"Wifi Settings","btn":"Wifi Settings","ssid":"SSID"},"mqtt":{"title":"MQTT Settings","btn":"MQTT Settings","name":"Hostname","group":"Groupname","host":"Broker","port":"Port","node_t":"Node Topic","group_t":"Group Topic","broadcast_t":"Broadcast Topic","hass_t":"HA LWT Topic"},"http":{"title":"HTTP Settings","btn":"HTTP Settings"},"ftp":{"title":"FTP Settings","btn":"FTP Settings","port":"FTP Port","pasv":"Passive Port"},"gui":{"title":"Display Settings","btn":"Display Settings","antiburn":"Antiburn","calibrate":"Calibrate"},"gpio":"GPIO Settings","debug":{"title":"Debug Settings","btn":"Debug Settings","baud":"Baudrate","tele":"Tele Period","ansi":"Use ANSI codes","host":"Syslog Server","port":"Syslog Port","ietf":"IETF (RFC 5424)","bsd":"BSD (RFC 3164)","log":"Facility"},"time":{"title":"Time Settings","btn":"Time Settings","region":"Region","zone":"Timezone","tz":"Timezone","ntp":"NTP Servers"},"region":{"etc":"Etcetera ","continents":"Continents ","af":"Africa ","as":"Asia ","au":"Australia ","aq":"Antarctica ","eu":"Europe ","na":"North America ","sa":"South America ","islands":"Islands ","at":"Atlantic Ocean ","in":"Indian Ocean ","pa":"Pacific Ocean "}}} \ No newline at end of file +{"en":{"language":"English","home":{"title":"Main Menu","btn":"Main Menu","nav":"Home"},"save":"Save Settings","user":"Username","pass":"Password","hasp":{"title":"HASP Design","btn":"HASP Design","theme":"UI Theme","color1":"Primary color","color2":"Secondary color","pages":"Start Layout","font":"Default Font","startpage":"Startup Page","startdim":"Startup Dim"},"screenshot":{"title":"Screenshot","btn":"Screenshot","nav":"Screenshot","prev":"Prev Page","next":"Next Page","refresh":"Refresh"},"info":{"title":"Information","btn":"Information","nav":"Information"},"config":{"title":"Configuration","btn":"Configuration","nav":"Settings"},"ota":{"title":"Firmware Update","btn":"Firmware Update","nav":"Firmware","submit":"Update Firmware","file":"Firmware File","url":"Firmware URL","redirect":"Follow Redirects","never":"Never","strict":"Strict","always":"Always"},"editor":{"title":"File Editor","btn":"File Editor","nav":"File Editor"},"reset":{"title":"Factory Reset","btn":"Factory Reset","warning":"Warning","message":"This process will reset all settings to the default values. The internal flash will be erased and the device is restarted. You may need to connect to the WiFi AP displayed on the panel to reconfigure the device before accessing it again.","fileloss":"ALL FILES WILL BE LOST!"},"reboot":{"title":"Rebooting...","btn":"Restart","nav":"Reboot","message":"The device is rebooting."},"about":{"credits":"Based on the previous work of the following open source developers:","copyright":"Copyright ","rights":"All rights reserved.","clause1":"Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:","clause2":"The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.","clause3":"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.","mit":"MIT License","bsd":"BSD License","freebsd":"FreeBSD License","apache2":"Apache2 License"},"wifi":{"title":"Wifi Settings","btn":"Wifi Settings","ssid":"SSID"},"wg":{"title":"WireGuard Settings","btn":"WireGuard Settings","vpnip":"VPN IP","privkey":"Private Key","host":"Remote IP","port":"Remote Port","pubkey":"Remote Public Key"},"mqtt":{"title":"MQTT Settings","btn":"MQTT Settings","name":"Hostname","group":"Groupname","host":"Broker","port":"Port","node_t":"Node Topic","group_t":"Group Topic","broadcast_t":"Broadcast Topic","hass_t":"HA LWT Topic"},"http":{"title":"HTTP Settings","btn":"HTTP Settings"},"ftp":{"title":"FTP Settings","btn":"FTP Settings","port":"FTP Port","pasv":"Passive Port"},"gui":{"title":"Display Settings","btn":"Display Settings","antiburn":"Antiburn","calibrate":"Calibrate"},"gpio":"GPIO Settings","debug":{"title":"Debug Settings","btn":"Debug Settings","baud":"Baudrate","tele":"Tele Period","ansi":"Use ANSI codes","host":"Syslog Server","port":"Syslog Port","ietf":"IETF (RFC 5424)","bsd":"BSD (RFC 3164)","log":"Facility"},"time":{"title":"Time Settings","btn":"Time Settings","region":"Region","zone":"Timezone","tz":"Timezone","ntp":"NTP Servers"},"region":{"etc":"Etcetera ","continents":"Continents ","af":"Africa ","as":"Asia ","au":"Australia ","aq":"Antarctica ","eu":"Europe ","na":"North America ","sa":"South America ","islands":"Islands ","at":"Atlantic Ocean ","in":"Indian Ocean ","pa":"Pacific Ocean "}}} \ No newline at end of file diff --git a/data/main.js b/data/main.js index a74ef0c9..989d9086 100644 --- a/data/main.js +++ b/data/main.js @@ -1 +1 @@ -import{createApp,reactive,createI18n}from"/static/petite-vue.hasp.js?COMMIT_HASH";const languages=[{code:"en",name:"English"},{code:"nl",name:"Nederlands"},{code:"fr",name:"Français"}];var locations={af:["Abidjan","Algiers","Bissau","Cairo","Casablanca","El_Aaiun","Johannesburg","Juba","Khartoum","Lagos","Maputo","Monrovia","Nairobi","Ndjamena","Sao_Tome","Tripoli","Tunis","Windhoek","Cape_Verde","Mauritius"],eu:["Ceuta","Danmarkshavn","Nuuk","Scoresbysund","Thule","Anadyr","Barnaul","Chita","Irkutsk","Kamchatka","Khandyga","Krasnoyarsk","Magadan","Novokuznetsk","Novosibirsk","Omsk","Sakhalin","Srednekolymsk","Tomsk","Ust-Nera","Vladivostok","Yakutsk","Yekaterinburg","Azores","Canary","Faroe","Madeira","Andorra","Astrakhan","Athens","Belgrade","Berlin","Brussels","Bucharest","Budapest","Chisinau","Dublin","Gibraltar","Helsinki","Istanbul","Kaliningrad","Kirov","Kyiv","Lisbon","London","Madrid","Malta","Minsk","Moscow","Paris","Prague","Riga","Rome","Samara","Saratov","Sofia","Tallinn","Tirane","Ulyanovsk","Vienna","Vilnius","Volgograd","Warsaw","Zurich"],as:["Almaty","Amman","Aqtau","Aqtobe","Ashgabat","Atyrau","Baghdad","Baku","Bangkok","Beirut","Bishkek","Choibalsan","Colombo","Damascus","Dhaka","Dili","Dubai","Dushanbe","Famagusta","Gaza","Hebron","Ho_Chi_Minh","Hong_Kong","Hovd","Jakarta","Jayapura","Jerusalem","Kabul","Karachi","Kathmandu","Kolkata","Kuching","Macau","Makassar","Manila","Nicosia","Oral","Pontianak","Pyongyang","Qatar","Qostanay","Qyzylorda","Riyadh","Samarkand","Seoul","Shanghai","Singapore","Taipei","Tashkent","Tbilisi","Tehran","Thimphu","Tokyo","Ulaanbaatar","Urumqi","Yangon","Yerevan","Chagos","Maldives"],au:["Perth","Eucla","Adelaide","Broken_Hill","Darwin","Brisbane","Hobart","Lindeman","Melbourne","Sydney","Lord_Howe"],na:["Adak","Anchorage","Bahia_Banderas","Barbados","Belize","Boise","Cambridge_Bay","Cancun","Chicago","Chihuahua","Ciudad_Juarez","Costa_Rica","Dawson","Dawson_Creek","Denver","Detroit","Edmonton","El_Salvador","Fort_Nelson","Glace_Bay","Goose_Bay","Grand_Turk","Guatemala","Halifax","Havana","Hermosillo","Indiana/Indianapolis","Indiana/Knox","Indiana/Marengo","Indiana/Petersburg","Indiana/Tell_City","Indiana/Vevay","Indiana/Vincennes","Indiana/Winamac","Inuvik","Iqaluit","Jamaica","Juneau","Kentucky/Louisville","Kentucky/Monticello","Los_Angeles","Managua","Martinique","Matamoros","Mazatlan","Menominee","Merida","Metlakatla","Mexico_City","Miquelon","Moncton","Monterrey","New_York","Nome","North_Dakota/Beulah","North_Dakota/Center","North_Dakota/New_Salem","Ojinaga","Panama","Phoenix","Port-au-Prince","Puerto_Rico","Rankin_Inlet","Regina","Resolute","Santo_Domingo","Sitka","St_Johns","Swift_Current","Tegucigalpa","Tijuana","Toronto","Vancouver","Whitehorse","Winnipeg","Yakutat","Yellowknife","Bermuda","Honolulu"],sa:["Araguaina","Argentina/Buenos_Aires","Argentina/Catamarca","Argentina/Cordoba","Argentina/Jujuy","Argentina/La_Rioja","Argentina/Mendoza","Argentina/Rio_Gallegos","Argentina/Salta","Argentina/San_Juan","Argentina/San_Luis","Argentina/Tucuman","Argentina/Ushuaia","Asuncion","Bahia","Belem","Boa_Vista","Bogota","Campo_Grande","Caracas","Cayenne","Cuiaba","Eirunepe","Fortaleza","Guayaquil","Guyana","La_Paz","Lima","Maceio","Manaus","Montevideo","Noronha","Paramaribo","Porto_Velho","Punta_Arenas","Recife","Rio_Branco","Santarem","Santiago","Sao_Paulo","Palmer","South_Georgia","Stanley","Easter","Galapagos"],at:["Cape_Verde","Canary","Faroe","Madeira","Azores","Bermuda","South_Georgia","Stanley"],in:["Mauritius","Maldives","Chagos"],pa:["Palau","Guam","Port_Moresby","Bougainville","Efate","Guadalcanal","Kosrae","Norfolk","Noumea","Auckland","Fiji","Kwajalein","Nauru","Tarawa","Chatham","Apia","Fakaofo","Kanton","Tongatapu","Kiritimati","Pitcairn","Gambier","Marquesas","Rarotonga","Tahiti","Niue","Pago_Pago","Honolulu","Easter","Galapagos"],aq:["Troll","Mawson","Davis","Casey","Rothera","Macquarie","Palmer"],etc:["Greenwich","Universal","Zulu","GMT-14","GMT-13","GMT-12","GMT-11","GMT-10","GMT-9","GMT-8","GMT-7","GMT-6","GMT-5","GMT-4","GMT-3","GMT-2","GMT-1","GMT","GMT+1","GMT+2","GMT+3","GMT+4","GMT+5","GMT+6","GMT+7","GMT+8","GMT+9","GMT+10","GMT+11","GMT+12","UCT","UTC"]};const regions={etc:"Etc",af:"Africa",as:"Asia",au:"Australia",aq:"Antarctica",eu:"Europe",na:"America",sa:"America",at:"Atlantic",in:"Indian",pa:"Pacific"},licenseData=[],licenseApp=[{t:"Petite Vue",y:2021,a:"Yuxi (Evan) You",l:"mit"},{t:"Petite Vue I18n Lite",y:2021,a:"Front Labs",l:"mit"},{t:"Ace Editor",y:2010,a:"Ajax.org B.V.",r:1,l:"bsd"},{t:"MaterialDesign Icons",y:2022,a:"Google",l:"apache2"}];function Credits(a){return{$template:"#credit-template",model:a}}function RegionItem(a,o,e){return{$template:"#region-template",model:a,region:o,i18n:e,list(e){if(a[e]&&o[e]){for(var n="etc"===e?a[e]:a[e].sort(),t=[],i=0;ie.t(a).toString().replace(/_/g," ")}}fetch("/static/en.json?COMMIT_HASH").then((a=>a.json())).then((a=>{const o=reactive(createI18n({locale:"en",fallbackLocale:"en",messages:{en:a.en}}));createApp({i18n:o,languages:languages,RegionItem:RegionItem,regions:regions,locations:locations,licenseData:licenseData,licenseApp:licenseApp,Credits:Credits,hostname:null,title:null,config:{hasp:null,wifi:null,mqtt:null,http:null,gui:null,gpio:null,debug:null,time:null,ota:null},info:null,files:null,show:null,t(a){return this.i18n.t(a)},fetchConfig(a){fetch("/api/config/"+a+"/").then((a=>a.json())).then((o=>{this.config[a]=o,this.show=a,document.title=a}))},submitConfig(){let a=this.show;fetch("/api/config/"+a+"/",{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(this.config[a])}).then((a=>a.json())).then((o=>{this.config[a]=o,window.history.pushState({},"","/config/"),window.dispatchEvent(new Event("popstate"))}))},submitOldConfig(a){fetch("/api/config/"+a+"/",{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(this.config[a])}).then((a=>a.json())).then((a=>{window.location.href="/config"}))},fetchLang(a){fetch("/static/"+a+".json?COMMIT_HASH").then((a=>a.json())).then((o=>{let e=o[a]?o[a]:{};this.i18n.setLocaleMessage(a,e),this.i18n.changeLocale(a),console.log(a)}))},fetchInfo(){fetch("/api/info/").then((a=>a.json())).then((a=>{this.info=a,this.show="info",document.title="Info"}))},fetchAbout(){fetch("/api/credits/").then((a=>a.json())).then((a=>{this.licenseData=a,this.show="about",document.title="About"}))},showPage(a){console.log("showPage "+a),this.show=a,document.title=a,""!=a&&(a+="/")},showInfo(){console.log("showInfo"),this.fetchInfo(),document.title="Info"},showConfig(a){console.log("showConfig "+a),this.fetchConfig(a),document.title=a},showEditor(){console.log("showEditor"),fetch("/api/files/").then((a=>a.json())).then((a=>{this.files=a,this.show="edit";var o=document.getElementsByClassName("container__editor")[0];o&&(o.style.display="flex"),document.title="Editor"}))},handleLocation(a,o){const e={"/":()=>{this.showPage("")},"/hasp.htm":()=>{this.showPage("")},"/config/":()=>{this.showPage("config")},"/config/hasp/":()=>{this.showConfig("hasp")},"/config/wifi/":()=>{this.showConfig("wifi")},"/config/http/":()=>{this.showConfig("http")},"/config/mqtt/":()=>{this.showConfig("mqtt")},"/config/gui/":()=>{this.showConfig("gui")},"/config/ftp/":()=>{this.showConfig("ftp")},"/config/time/":()=>{this.showConfig("time")},"/config/debug/":()=>{this.showConfig("debug")},"/config/reset/":()=>{this.showPage("reset")},"/firmware/":()=>{this.showConfig("ota")},"/info/":()=>{this.showInfo()},"/screenshot/":()=>{this.showPage("screenshot")},"/about/":()=>{this.fetchAbout()},"/edit/":()=>{this.showEditor()},"/edit":()=>{},"/static/editor.htm":()=>{},"/reboot/":()=>{this.showPage("reboot")}};"function"==typeof e[a]?(console.log("Location: "+a),e[a]()):"/"!==a.slice(-1)&&"function"==typeof e[a+"/"]?(console.log("Location: "+a),e[a+"/"]()):(console.log("Not found: "+a),e["/"]);const n=document.getElementsByClassName("container__editor")[0];n&&(n.style.display=a.includes("/edit")?"flex":"none"),window.scrollTo({top:o})},mounted(){let a=decodeURIComponent(document.cookie).split(";");for(let o=0;o{const o=window.location.pathname;console.log("Popstate: "+o),console.log(a);var e=a.state,n=0;e&&(n=e.scrollTop),this.handleLocation(o,n)};const o=window.location.pathname;this.handleLocation(o,0),console.log("App Mounted")},route(a){console.log("Routing..."),a=a||window.event,console.log(a.target),a.preventDefault();const o=a.currentTarget.href||a.target.parentNode.href,e=new URL(o).pathname;if(window.location.pathname!=e){console.log("Push Route: "+e);var n={path:window.location.href||a.target.href,scrollTop:document.body.scrollTop};window.history.replaceState(n,"",document.location.pathname),n={path:window.location.href,scrollTop:0},window.history.pushState(n,"",e),window.dispatchEvent(new Event("popstate"))}},goto(a){if(console.log("Goto..."),window.location.pathname!=a){console.log("Push Route: "+a);var o={path:window.location.href,scrollTop:document.body.scrollTop};window.history.replaceState(o,"",document.location.pathname),o={path:window.location.href,scrollTop:0},window.history.pushState(o,"",a),window.dispatchEvent(new Event("popstate"))}},ref(a){},aref(a){setTimeout((function(){}),1e3*a)},upd(a){var o=(new Date).getTime();document.getElementById("bmp").src="/screenshot?a="+a+"&q="+o}}).directive("t",(({el:a,get:e,effect:n})=>n((()=>a.textContent=o.t(e()))))).directive("ts",(({el:a,get:e,effect:n})=>n((()=>a.textContent=o.t(e()).replace(/_/g," "))))).mount(),console.log("JS Loaded...")})); \ No newline at end of file +import{createApp,reactive,createI18n}from"/static/petite-vue.hasp.js?COMMIT_HASH";const languages=[{code:"en",name:"English"},{code:"nl",name:"Nederlands"},{code:"fr",name:"Français"}];var locations={af:["Abidjan","Algiers","Bissau","Cairo","Casablanca","El_Aaiun","Johannesburg","Juba","Khartoum","Lagos","Maputo","Monrovia","Nairobi","Ndjamena","Sao_Tome","Tripoli","Tunis","Windhoek","Cape_Verde","Mauritius"],eu:["Ceuta","Danmarkshavn","Nuuk","Scoresbysund","Thule","Anadyr","Barnaul","Chita","Irkutsk","Kamchatka","Khandyga","Krasnoyarsk","Magadan","Novokuznetsk","Novosibirsk","Omsk","Sakhalin","Srednekolymsk","Tomsk","Ust-Nera","Vladivostok","Yakutsk","Yekaterinburg","Azores","Canary","Faroe","Madeira","Andorra","Astrakhan","Athens","Belgrade","Berlin","Brussels","Bucharest","Budapest","Chisinau","Dublin","Gibraltar","Helsinki","Istanbul","Kaliningrad","Kirov","Kyiv","Lisbon","London","Madrid","Malta","Minsk","Moscow","Paris","Prague","Riga","Rome","Samara","Saratov","Sofia","Tallinn","Tirane","Ulyanovsk","Vienna","Vilnius","Volgograd","Warsaw","Zurich"],as:["Almaty","Amman","Aqtau","Aqtobe","Ashgabat","Atyrau","Baghdad","Baku","Bangkok","Beirut","Bishkek","Choibalsan","Colombo","Damascus","Dhaka","Dili","Dubai","Dushanbe","Famagusta","Gaza","Hebron","Ho_Chi_Minh","Hong_Kong","Hovd","Jakarta","Jayapura","Jerusalem","Kabul","Karachi","Kathmandu","Kolkata","Kuching","Macau","Makassar","Manila","Nicosia","Oral","Pontianak","Pyongyang","Qatar","Qostanay","Qyzylorda","Riyadh","Samarkand","Seoul","Shanghai","Singapore","Taipei","Tashkent","Tbilisi","Tehran","Thimphu","Tokyo","Ulaanbaatar","Urumqi","Yangon","Yerevan","Chagos","Maldives"],au:["Perth","Eucla","Adelaide","Broken_Hill","Darwin","Brisbane","Hobart","Lindeman","Melbourne","Sydney","Lord_Howe"],na:["Adak","Anchorage","Bahia_Banderas","Barbados","Belize","Boise","Cambridge_Bay","Cancun","Chicago","Chihuahua","Ciudad_Juarez","Costa_Rica","Dawson","Dawson_Creek","Denver","Detroit","Edmonton","El_Salvador","Fort_Nelson","Glace_Bay","Goose_Bay","Grand_Turk","Guatemala","Halifax","Havana","Hermosillo","Indiana/Indianapolis","Indiana/Knox","Indiana/Marengo","Indiana/Petersburg","Indiana/Tell_City","Indiana/Vevay","Indiana/Vincennes","Indiana/Winamac","Inuvik","Iqaluit","Jamaica","Juneau","Kentucky/Louisville","Kentucky/Monticello","Los_Angeles","Managua","Martinique","Matamoros","Mazatlan","Menominee","Merida","Metlakatla","Mexico_City","Miquelon","Moncton","Monterrey","New_York","Nome","North_Dakota/Beulah","North_Dakota/Center","North_Dakota/New_Salem","Ojinaga","Panama","Phoenix","Port-au-Prince","Puerto_Rico","Rankin_Inlet","Regina","Resolute","Santo_Domingo","Sitka","St_Johns","Swift_Current","Tegucigalpa","Tijuana","Toronto","Vancouver","Whitehorse","Winnipeg","Yakutat","Yellowknife","Bermuda","Honolulu"],sa:["Araguaina","Argentina/Buenos_Aires","Argentina/Catamarca","Argentina/Cordoba","Argentina/Jujuy","Argentina/La_Rioja","Argentina/Mendoza","Argentina/Rio_Gallegos","Argentina/Salta","Argentina/San_Juan","Argentina/San_Luis","Argentina/Tucuman","Argentina/Ushuaia","Asuncion","Bahia","Belem","Boa_Vista","Bogota","Campo_Grande","Caracas","Cayenne","Cuiaba","Eirunepe","Fortaleza","Guayaquil","Guyana","La_Paz","Lima","Maceio","Manaus","Montevideo","Noronha","Paramaribo","Porto_Velho","Punta_Arenas","Recife","Rio_Branco","Santarem","Santiago","Sao_Paulo","Palmer","South_Georgia","Stanley","Easter","Galapagos"],at:["Cape_Verde","Canary","Faroe","Madeira","Azores","Bermuda","South_Georgia","Stanley"],in:["Mauritius","Maldives","Chagos"],pa:["Palau","Guam","Port_Moresby","Bougainville","Efate","Guadalcanal","Kosrae","Norfolk","Noumea","Auckland","Fiji","Kwajalein","Nauru","Tarawa","Chatham","Apia","Fakaofo","Kanton","Tongatapu","Kiritimati","Pitcairn","Gambier","Marquesas","Rarotonga","Tahiti","Niue","Pago_Pago","Honolulu","Easter","Galapagos"],aq:["Troll","Mawson","Davis","Casey","Rothera","Macquarie","Palmer"],etc:["Greenwich","Universal","Zulu","GMT-14","GMT-13","GMT-12","GMT-11","GMT-10","GMT-9","GMT-8","GMT-7","GMT-6","GMT-5","GMT-4","GMT-3","GMT-2","GMT-1","GMT","GMT+1","GMT+2","GMT+3","GMT+4","GMT+5","GMT+6","GMT+7","GMT+8","GMT+9","GMT+10","GMT+11","GMT+12","UCT","UTC"]};const regions={etc:"Etc",af:"Africa",as:"Asia",au:"Australia",aq:"Antarctica",eu:"Europe",na:"America",sa:"America",at:"Atlantic",in:"Indian",pa:"Pacific"},licenseData=[],licenseApp=[{t:"Petite Vue",y:2021,a:"Yuxi (Evan) You",l:"mit"},{t:"Petite Vue I18n Lite",y:2021,a:"Front Labs",l:"mit"},{t:"Ace Editor",y:2010,a:"Ajax.org B.V.",r:1,l:"bsd"},{t:"MaterialDesign Icons",y:2022,a:"Google",l:"apache2"}];function Credits(a){return{$template:"#credit-template",model:a}}function RegionItem(a,o,e){return{$template:"#region-template",model:a,region:o,i18n:e,list(e){if(a[e]&&o[e]){for(var n="etc"===e?a[e]:a[e].sort(),t=[],i=0;ie.t(a).toString().replace(/_/g," ")}}fetch("/static/en.json?COMMIT_HASH").then((a=>a.json())).then((a=>{const o=reactive(createI18n({locale:"en",fallbackLocale:"en",messages:{en:a.en}}));createApp({i18n:o,languages:languages,RegionItem:RegionItem,regions:regions,locations:locations,licenseData:licenseData,licenseApp:licenseApp,Credits:Credits,hostname:null,title:null,config:{hasp:null,wifi:null,wg:null,mqtt:null,http:null,gui:null,gpio:null,debug:null,time:null,ota:null},info:null,files:null,show:null,t(a){return this.i18n.t(a)},fetchConfig(a){fetch("/api/config/"+a+"/").then((a=>a.json())).then((o=>{this.config[a]=o,this.show=a,document.title=a}))},submitConfig(){let a=this.show;fetch("/api/config/"+a+"/",{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(this.config[a])}).then((a=>a.json())).then((o=>{this.config[a]=o,window.history.pushState({},"","/config/"),window.dispatchEvent(new Event("popstate"))}))},submitOldConfig(a){fetch("/api/config/"+a+"/",{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(this.config[a])}).then((a=>a.json())).then((a=>{window.location.href="/config"}))},fetchLang(a){fetch("/static/"+a+".json?COMMIT_HASH").then((a=>a.json())).then((o=>{let e=o[a]?o[a]:{};this.i18n.setLocaleMessage(a,e),this.i18n.changeLocale(a),console.log(a)}))},fetchInfo(){fetch("/api/info/").then((a=>a.json())).then((a=>{this.info=a,this.show="info",document.title="Info"}))},fetchAbout(){fetch("/api/credits/").then((a=>a.json())).then((a=>{this.licenseData=a,this.show="about",document.title="About"}))},showPage(a){console.log("showPage "+a),this.show=a,document.title=a,""!=a&&(a+="/")},showInfo(){console.log("showInfo"),this.fetchInfo(),document.title="Info"},showConfig(a){console.log("showConfig "+a),this.fetchConfig(a),document.title=a},showEditor(){console.log("showEditor"),fetch("/api/files/").then((a=>a.json())).then((a=>{this.files=a,this.show="edit";var o=document.getElementsByClassName("container__editor")[0];o&&(o.style.display="flex"),document.title="Editor"}))},handleLocation(a,o){const e={"/":()=>{this.showPage("")},"/hasp.htm":()=>{this.showPage("")},"/config/":()=>{this.showPage("config")},"/config/hasp/":()=>{this.showConfig("hasp")},"/config/wifi/":()=>{this.showConfig("wifi")},"/config/wg/":()=>{this.showConfig("wg")},"/config/http/":()=>{this.showConfig("http")},"/config/mqtt/":()=>{this.showConfig("mqtt")},"/config/gui/":()=>{this.showConfig("gui")},"/config/ftp/":()=>{this.showConfig("ftp")},"/config/time/":()=>{this.showConfig("time")},"/config/debug/":()=>{this.showConfig("debug")},"/config/reset/":()=>{this.showPage("reset")},"/firmware/":()=>{this.showConfig("ota")},"/info/":()=>{this.showInfo()},"/screenshot/":()=>{this.showPage("screenshot")},"/about/":()=>{this.fetchAbout()},"/edit/":()=>{this.showEditor()},"/edit":()=>{},"/static/editor.htm":()=>{},"/reboot/":()=>{this.showPage("reboot")}};"function"==typeof e[a]?(console.log("Location: "+a),e[a]()):"/"!==a.slice(-1)&&"function"==typeof e[a+"/"]?(console.log("Location: "+a),e[a+"/"]()):(console.log("Not found: "+a),e["/"]);const n=document.getElementsByClassName("container__editor")[0];n&&(n.style.display=a.includes("/edit")?"flex":"none"),window.scrollTo({top:o})},mounted(){let a=decodeURIComponent(document.cookie).split(";");for(let o=0;o{const o=window.location.pathname;console.log("Popstate: "+o),console.log(a);var e=a.state,n=0;e&&(n=e.scrollTop),this.handleLocation(o,n)};const o=window.location.pathname;this.handleLocation(o,0),console.log("App Mounted")},route(a){console.log("Routing..."),a=a||window.event,console.log(a.target),a.preventDefault();const o=a.currentTarget.href||a.target.parentNode.href,e=new URL(o).pathname;if(window.location.pathname!=e){console.log("Push Route: "+e);var n={path:window.location.href||a.target.href,scrollTop:document.body.scrollTop};window.history.replaceState(n,"",document.location.pathname),n={path:window.location.href,scrollTop:0},window.history.pushState(n,"",e),window.dispatchEvent(new Event("popstate"))}},goto(a){if(console.log("Goto..."),window.location.pathname!=a){console.log("Push Route: "+a);var o={path:window.location.href,scrollTop:document.body.scrollTop};window.history.replaceState(o,"",document.location.pathname),o={path:window.location.href,scrollTop:0},window.history.pushState(o,"",a),window.dispatchEvent(new Event("popstate"))}},ref(a){},aref(a){setTimeout((function(){}),1e3*a)},upd(a){var o=(new Date).getTime();document.getElementById("bmp").src="/screenshot?a="+a+"&q="+o}}).directive("t",(({el:a,get:e,effect:n})=>n((()=>a.textContent=o.t(e()))))).directive("ts",(({el:a,get:e,effect:n})=>n((()=>a.textContent=o.t(e()).replace(/_/g," "))))).mount(),console.log("JS Loaded...")})); \ No newline at end of file diff --git a/include/hasp_conf.h b/include/hasp_conf.h index 16a4ab23..448b66a3 100644 --- a/include/hasp_conf.h +++ b/include/hasp_conf.h @@ -65,6 +65,10 @@ #define HASP_USE_MQTT (HASP_HAS_NETWORK) #endif +#ifndef HASP_USE_WIREGUARD +#define HASP_USE_WIREGUARD (HASP_HAS_NETWORK) +#endif + #ifndef HASP_USE_BROADCAST #define HASP_USE_BROADCAST 1 #endif @@ -232,6 +236,10 @@ static WiFiSpiClass WiFi; #endif #endif // HASP_USE_WIFI +#if HASP_USE_WIREGUARD > 0 +#include "sys/net/hasp_wireguard.h" +#endif + #if HASP_USE_ETHERNET > 0 #if defined(ARDUINO_ARCH_ESP32) #include "sys/net/hasp_ethernet_esp32.h" diff --git a/src/hasp/hasp_dispatch.cpp b/src/hasp/hasp_dispatch.cpp index acbc9734..38548264 100644 --- a/src/hasp/hasp_dispatch.cpp +++ b/src/hasp/hasp_dispatch.cpp @@ -503,6 +503,14 @@ void dispatch_config(const char* topic, const char* payload, uint8_t source) else timeGetConfig(settings); } +#if HASP_USE_WIREGUARD > 0 + else if(strcasecmp_P(topic, PSTR(FP_WG)) == 0) { + if(update) + wgSetConfig(settings); + else + wgGetConfig(settings); + } +#endif #if HASP_USE_MQTT > 0 else if(strcasecmp_P(topic, PSTR(FP_MQTT)) == 0) { if(update) diff --git a/src/hasp/hasp_nvs.cpp b/src/hasp/hasp_nvs.cpp index 439a1bea..a2fd0b23 100644 --- a/src/hasp/hasp_nvs.cpp +++ b/src/hasp/hasp_nvs.cpp @@ -6,6 +6,8 @@ #include "hasplib.h" #include "hasp_nvs.h" +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) + bool nvs_user_begin(Preferences& preferences, const char* key, bool readonly) { if(preferences.begin(key, false, "config")) { @@ -22,16 +24,16 @@ bool nvs_user_begin(Preferences& preferences, const char* key, bool readonly) bool nvs_clear_user_config() { - const char* name[] = {FP_TIME, FP_OTA, FP_HTTP, FP_FTP, FP_MQTT, FP_WIFI}; + const char* name[] = {FP_TIME, FP_OTA, FP_HTTP, FP_FTP, FP_MQTT, FP_WIFI, FP_WG}; Preferences preferences; bool state = true; - for(int i = 0; i < sizeof(name) / sizeof(name[0]); i++) { + for(int i = 0; i < ARRAY_SIZE(name); i++) { if(preferences.begin(name[i], false) && !preferences.clear()) state = false; preferences.end(); } - for(int i = 0; i < sizeof(name) / sizeof(name[0]); i++) { + for(int i = 0; i < ARRAY_SIZE(name); i++) { if(preferences.begin(name[i], false, "config") && !preferences.clear()) state = false; preferences.end(); } @@ -226,19 +228,30 @@ void nvs_setup() nvs_stats.free_entries, nvs_stats.total_entries); { // TODO: remove migratrion of keys from default NVS partition to CONFIG partition - const char* name[8] = {FP_TIME, FP_OTA, FP_HTTP, FP_FTP, FP_MQTT, FP_WIFI}; + const struct { + const char* name; + const char* config; + } sec[] = { + { .name = FP_TIME, .config = FP_CONFIG_PASS }, + { .name = FP_OTA, .config = FP_CONFIG_PASS }, + { .name = FP_HTTP, .config = FP_CONFIG_PASS }, + { .name = FP_FTP, .config = FP_CONFIG_PASS }, + { .name = FP_MQTT, .config = FP_CONFIG_PASS }, + { .name = FP_WIFI, .config = FP_CONFIG_PASS }, + { .name = FP_WG, .config = FP_CONFIG_PRIVATE_KEY } + }; Preferences oldPrefs, newPrefs; - for(int i = 0; i < 6; i++) { - if(oldPrefs.begin(name[i], false) && newPrefs.begin(name[i], false, "config")) { - LOG_INFO(TAG_NVS, "opened %s", name[i]); - String password = oldPrefs.getString(FP_CONFIG_PASS, D_PASSWORD_MASK); + for(int i = 0; i < ARRAY_SIZE(sec); i++) { + if(oldPrefs.begin(sec[i].name, false) && newPrefs.begin(sec[i].name, false, "config")) { + LOG_INFO(TAG_NVS, "opened %s", sec[i].name); + String password = oldPrefs.getString(sec[i].config, D_PASSWORD_MASK); if(password != D_PASSWORD_MASK) { - LOG_INFO(TAG_NVS, "found %s %s => %s", name[i], D_PASSWORD_MASK, password.c_str()); - size_t len = newPrefs.putString(FP_CONFIG_PASS, password); + LOG_INFO(TAG_NVS, "found %s %s => %s", sec[i].name, D_PASSWORD_MASK, password.c_str()); + size_t len = newPrefs.putString(sec[i].config, password); if(len == password.length()) { - oldPrefs.remove(FP_CONFIG_PASS); - LOG_INFO(TAG_NVS, "Moved %s key %s to new NVS partition", name[i], FP_CONFIG_PASS); + oldPrefs.remove(sec[i].config); + LOG_INFO(TAG_NVS, "Moved %s key %s to new NVS partition", sec[i].name, sec[i].config); } } } diff --git a/src/hasp_config.cpp b/src/hasp_config.cpp index 9e3f5ce5..09ceea05 100644 --- a/src/hasp_config.cpp +++ b/src/hasp_config.cpp @@ -117,6 +117,19 @@ bool configSet(lv_color_t& value, const JsonVariant& setting, const __FlashStrin } return false; } +bool configSet(char *value, size_t size, const JsonVariant& setting, const __FlashStringHelper* fstr_name) +{ + if(!setting.isNull()) { + const char *val = setting; + if(strcmp(value, val) != 0) { + confDebugSet(fstr_name); + strncpy(value, val, size - 1); + value[size - 1] = '\0'; + return true; + } + } + return false; +} bool configSet(bool& value, const JsonVariant& setting, const char* fstr_name) { @@ -198,28 +211,30 @@ void configSetupDebug(JsonDocument& settings) debugStart(); // Debug started, now we can use it; HASP header sent } -void configStorePasswords(JsonDocument& settings, String& wifiPass, String& mqttPass, String& httpPass) +void configStorePasswords(JsonDocument& settings, String& wifiPass, String& mqttPass, String& httpPass, String &wgPrivKey) { const char* pass = ("pass"); wifiPass = settings[FPSTR(FP_WIFI)][pass].as(); mqttPass = settings[FPSTR(FP_MQTT)][pass].as(); httpPass = settings[FPSTR(FP_HTTP)][pass].as(); + wgPrivKey = settings[FPSTR(FP_WG)][FPSTR(FP_CONFIG_PRIVATE_KEY)].as(); } -void configRestorePasswords(JsonDocument& settings, String& wifiPass, String& mqttPass, String& httpPass) +void configRestorePasswords(JsonDocument& settings, String& wifiPass, String& mqttPass, String& httpPass, String& wgPrivKey) { const char* pass = ("pass"); if(!settings[FPSTR(FP_WIFI)][pass].isNull()) settings[FPSTR(FP_WIFI)][pass] = wifiPass; if(!settings[FPSTR(FP_MQTT)][pass].isNull()) settings[FPSTR(FP_MQTT)][pass] = mqttPass; if(!settings[FPSTR(FP_HTTP)][pass].isNull()) settings[FPSTR(FP_HTTP)][pass] = httpPass; + if(!settings[FPSTR(FP_WG)][FPSTR(FP_CONFIG_PRIVATE_KEY)].isNull()) settings[FPSTR(FP_WG)][FPSTR(FP_CONFIG_PRIVATE_KEY)] = wgPrivKey; } void configMaskPasswords(JsonDocument& settings) { String passmask = F(D_PASSWORD_MASK); - configRestorePasswords(settings, passmask, passmask, passmask); + configRestorePasswords(settings, passmask, passmask, passmask, passmask); } DeserializationError configParseFile(String& configFile, JsonDocument& settings) @@ -254,7 +269,7 @@ DeserializationError configRead(JsonDocument& settings, bool setupdebug) #if HASP_USE_SPIFFS > 0 || HASP_USE_LITTLEFS > 0 error = configParseFile(configFile, settings); if(!error) { - String output, wifiPass, mqttPass, httpPass; + String output, wifiPass, mqttPass, httpPass, wgPrivKey; /* Load Debug params */ if(setupdebug) { @@ -266,14 +281,14 @@ DeserializationError configRead(JsonDocument& settings, bool setupdebug) } LOG_TRACE(TAG_CONF, F(D_FILE_LOADING), configFile.c_str()); - configStorePasswords(settings, wifiPass, mqttPass, httpPass); + configStorePasswords(settings, wifiPass, mqttPass, httpPass, wgPrivKey); // Output settings in log with masked passwords configMaskPasswords(settings); serializeJson(settings, output); LOG_VERBOSE(TAG_CONF, output.c_str()); - configRestorePasswords(settings, wifiPass, mqttPass, httpPass); + configRestorePasswords(settings, wifiPass, mqttPass, httpPass, wgPrivKey); LOG_INFO(TAG_CONF, F(D_FILE_LOADED), configFile.c_str()); // if(setupdebug) debugSetup(); @@ -382,6 +397,17 @@ void configWrite() } #endif +#if HASP_USE_WIREGUARD > 0 + module = FPSTR(FP_WG); + if(settings[module].as().isNull()) settings.createNestedObject(module); + changed = wgGetConfig(settings[module]); + if(changed) { + LOG_VERBOSE(TAG_WG, settingsChanged.c_str()); + configOutput(settings[module], TAG_WG); + writefile = true; + } +#endif + #if HASP_USE_MQTT > 0 module = FPSTR(FP_MQTT); if(settings[module].as().isNull()) settings.createNestedObject(module); @@ -549,6 +575,11 @@ void configSetup() wifiSetConfig(settings[FPSTR(FP_WIFI)]); #endif +#if HASP_USE_WIREGUARD > 0 + LOG_INFO(TAG_WG, F("Loading WireGuard settings")); + wgSetConfig(settings[FPSTR(FP_WG)]); +#endif + #if HASP_USE_MQTT > 0 LOG_INFO(TAG_MQTT, F("Loading MQTT settings")); mqttSetConfig(settings[FPSTR(FP_MQTT)]); @@ -624,6 +655,14 @@ void configOutput(const JsonObject& settings, uint8_t tag) output.replace(password, passmask); } + if(!settings[FPSTR(FP_WG)][FPSTR(FP_CONFIG_PRIVATE_KEY)].isNull()) { + password = F("\"privkey\":\""); + password += settings[FPSTR(FP_WG)][FPSTR(FP_CONFIG_PRIVATE_KEY)].as(); + password += F("\""); + passmask = F("\"privkey\":\"" D_PASSWORD_MASK "\""); + output.replace(password, passmask); + } + LOG_VERBOSE(tag, output.c_str()); } diff --git a/src/hasp_config.h b/src/hasp_config.h index df547408..7effe4cd 100644 --- a/src/hasp_config.h +++ b/src/hasp_config.h @@ -31,6 +31,7 @@ bool configSet(uint8_t& value, const JsonVariant& setting, const __FlashStringHe bool configSet(uint16_t& value, const JsonVariant& setting, const __FlashStringHelper* fstr_name); bool configSet(int32_t& value, const JsonVariant& setting, const __FlashStringHelper* fstr_name); bool configSet(lv_color_t& value, const JsonVariant& setting, const __FlashStringHelper* fstr_name); +bool configSet(char *value, size_t size, const JsonVariant& setting, const __FlashStringHelper* fstr_name); bool configSet(bool& value, const JsonVariant& setting, const char* fstr_name); bool configSet(int8_t& value, const JsonVariant& setting, const char* fstr_name); bool configSet(uint8_t& value, const JsonVariant& setting, const char* fstr_name); @@ -71,6 +72,9 @@ const char FP_CONFIG_BROADCAST_TOPIC[] PROGMEM = "broadcast_t"; const char FP_CONFIG_BAUD[] PROGMEM = "baud"; const char FP_CONFIG_LOG[] PROGMEM = "log"; const char FP_CONFIG_PROTOCOL[] PROGMEM = "proto"; +const char FP_CONFIG_VPN_IP[] PROGMEM = "vpnip"; +const char FP_CONFIG_PRIVATE_KEY[] PROGMEM = "privkey"; +const char FP_CONFIG_PUBLIC_KEY[] PROGMEM = "pubkey"; const char FP_GUI_ROTATION[] PROGMEM = "rotate"; const char FP_GUI_INVERT[] PROGMEM = "invert"; const char FP_GUI_TICKPERIOD[] PROGMEM = "tick"; @@ -89,6 +93,7 @@ const char FP_GPIO_CONFIG[] PROGMEM = "config"; const char FP_HASP_CONFIG_FILE[] PROGMEM = "/config.json"; const char FP_WIFI[] PROGMEM = "wifi"; +const char FP_WG[] PROGMEM = "wg"; const char FP_MQTT[] PROGMEM = "mqtt"; const char FP_HTTP[] PROGMEM = "http"; const char FP_FTP[] PROGMEM = "ftp"; diff --git a/src/hasp_debug.cpp b/src/hasp_debug.cpp index df523c19..52977625 100644 --- a/src/hasp_debug.cpp +++ b/src/hasp_debug.cpp @@ -321,6 +321,10 @@ void debug_get_tag(uint8_t tag, char* buffer) memcpy_P(buffer, PSTR("CUST"), 5); break; + case TAG_WG: + memcpy_P(buffer, PSTR("WG "), 5); + break; + default: memcpy_P(buffer, PSTR("----"), 5); break; diff --git a/src/hasp_debug.h b/src/hasp_debug.h index b805004e..425a7d97 100644 --- a/src/hasp_debug.h +++ b/src/hasp_debug.h @@ -194,6 +194,7 @@ enum { TAG_FTP = 68, TAG_TIME = 69, TAG_NETW = 70, + TAG_WG = 71, TAG_LVGL = 90, TAG_LVFS = 91, diff --git a/src/lang/en_US.h b/src/lang/en_US.h index b24d806d..13df8dbc 100644 --- a/src/lang/en_US.h +++ b/src/lang/en_US.h @@ -127,6 +127,7 @@ #define D_HTTP_HTTP_SETTINGS "HTTP Settings" #define D_HTTP_FTP_SETTINGS "FTP Settings" #define D_HTTP_WIFI_SETTINGS "Wifi Settings" +#define D_HTTP_WIREGUARD_SETTINGS "WireGuard Settings" #define D_HTTP_MQTT_SETTINGS "MQTT Settings" #define D_HTTP_GPIO_SETTINGS "GPIO Settings" #define D_HTTP_MDNS_SETTINGS "mDNS Settings" @@ -191,6 +192,7 @@ #define D_INFO_FAILED "Failed" #define D_INFO_ETHERNET "Ethernet" #define D_INFO_WIFI "Wifi" +#define D_INFO_WIREGUARD "WireGuard" #define D_INFO_LINK_SPEED "Link Speed" #define D_INFO_FULL_DUPLEX "Full Duplex" #define D_INFO_BSSID "BSSID" @@ -200,6 +202,8 @@ #define D_INFO_MAC_ADDRESS "MAC Address" #define D_INFO_GATEWAY "Gateway" #define D_INFO_DNS_SERVER "DNS Server" +#define D_INFO_ENDPOINT_IP "Endpoint IP" +#define D_INFO_ENDPOINT_PORT "Endpoint Port" #define D_OOBE_MSG "Tap the screen to setup WiFi or connect to this Access Point:" #define D_OOBE_SCAN_TO_CONNECT "Scan to connect" @@ -212,6 +216,9 @@ #define D_WIFI_RSSI_WEAK "Weak" #define D_WIFI_RSSI_BAD "Very bad" +#define D_WG_INITIALIZED "Initialized" +#define D_WG_BAD_CONFIG "Missing or bad configuration" + #define D_GPIO_SWITCH "Switch" #define D_GPIO_BUTTON "Push Button" #define D_GPIO_TOUCH "Capacitive Touch" diff --git a/src/sys/net/hasp_network.cpp b/src/sys/net/hasp_network.cpp index 5b20ad4a..395f3a2f 100644 --- a/src/sys/net/hasp_network.cpp +++ b/src/sys/net/hasp_network.cpp @@ -14,6 +14,11 @@ uint16_t network_reconnect_counter = 0; #if HASP_USE_ETHERNET > 0 || HASP_USE_WIFI > 0 +bool network_is_connected() +{ + return current_network_state; +} + void network_disconnected() { @@ -23,6 +28,9 @@ void network_disconnected() // if(!current_network_state) return; // we were not connected current_network_state = false; // now we are disconnected +#if HASP_USE_WIREGUARD + wg_network_disconnected(); +#endif network_reconnect_counter++; // LOG_VERBOSE(TAG_NETW, F("Connected = %s"), // WiFi.status() == WL_CONNECTED ? PSTR(D_NETWORK_ONLINE) : PSTR(D_NETWORK_OFFLINE)); @@ -32,10 +40,15 @@ void network_connected() { if(current_network_state) return; // already connected +#if HASP_USE_WIREGUARD + wg_network_connected(); +#endif + current_network_state = true; // now we are connected network_reconnect_counter = 0; LOG_VERBOSE(TAG_NETW, F("Connected = %s"), WiFi.status() == WL_CONNECTED ? PSTR(D_NETWORK_ONLINE) : PSTR(D_NETWORK_OFFLINE)); + } void network_run_scripts() @@ -101,6 +114,10 @@ void networkSetup() #if HASP_USE_WIFI > 0 wifiSetup(); #endif + +#if HASP_USE_WIREGUARD > 0 + wg_setup(); +#endif } IRAM_ATTR void networkLoop(void) @@ -178,10 +195,20 @@ void network_get_statusupdate(char* buffer, size_t len) #if HASP_USE_WIFI > 0 wifi_get_statusupdate(buffer, len); #endif + +#if HASP_USE_WIREGUARD > 0 + size_t l = strlen(buffer); + wg_get_statusupdate(buffer + l, len - l); +#endif } void network_get_ipaddress(char* buffer, size_t len) { +#if HASP_USE_WIREGUARD > 0 + if (wg_get_ipaddress(buffer, len)) + return; +#endif + #if HASP_USE_ETHERNET > 0 #if defined(ARDUINO_ARCH_ESP32) #if HASP_USE_ETHSPI > 0 @@ -222,6 +249,10 @@ void network_get_info(JsonDocument& doc) #if HASP_USE_WIFI > 0 wifi_get_info(doc); #endif + +#if HASP_USE_WIREGUARD > 0 + wg_get_info(doc); +#endif } #endif diff --git a/src/sys/net/hasp_network.h b/src/sys/net/hasp_network.h index 83f9b7b2..47ec456a 100644 --- a/src/sys/net/hasp_network.h +++ b/src/sys/net/hasp_network.h @@ -11,6 +11,7 @@ bool networkEvery5Seconds(void); // bool networkEverySecond(void); void networkStart(void); void networkStop(void); +bool network_is_connected(); /* ===== Special Event Processors ===== */ void network_connected(); diff --git a/src/sys/net/hasp_wireguard.cpp b/src/sys/net/hasp_wireguard.cpp new file mode 100644 index 00000000..36b05515 --- /dev/null +++ b/src/sys/net/hasp_wireguard.cpp @@ -0,0 +1,131 @@ +/* MIT License - Copyright (c) 2023 Jaroslav Kysela + For full license information read the LICENSE file in the project folder */ + +#include "hasplib.h" + +#if HASP_USE_WIREGUARD > 0 + +#include "hal/hasp_hal.h" +#include "hasp_debug.h" +#include "hasp_network.h" +#include "WireGuard-ESP32.h" + +char wg_ip[16] = WIREGUARD_IP; +char wg_private_key[45] = WIREGUARD_PRIVATE_KEY; +char wg_ep_ip[16] = WIREGUARD_EP_IP; +uint16_t wg_ep_port = WIREGUARD_EP_PORT; +char wg_ep_public_key[45] = WIREGUARD_EP_PUBLIC_KEY; +static WireGuard wg; + +void wg_setup() +{ + Preferences preferences; + nvs_user_begin(preferences, FP_WG, true); + String privkey = preferences.getString(FP_CONFIG_PRIVATE_KEY, String(wg_private_key)); // Update from NVS if it exists + strncpy(wg_private_key, privkey.c_str(), sizeof(wg_private_key)-1); + wg_private_key[sizeof(wg_private_key)-1] = '\0'; +} + +int wg_config_valid() +{ + return strlen(wg_ip) > 7 && strlen(wg_ep_ip) > 7 && + strlen(wg_private_key) == 44 && strlen(wg_ep_public_key) == 44 && + wg_ep_port > 0; +} + +void wg_network_disconnected() +{ + wg.end(); +} + +void wg_network_connected() +{ + IPAddress local_ip; + LOG_VERBOSE(TAG_WG, F("WireGuard connected")); + if (local_ip.fromString(wg_ip) && wg_config_valid()) { + LOG_INFO(TAG_WG, F("WireGuard begin (%s -> %s:%u)"), wg_ip, wg_ep_ip, wg_ep_port); + wg.begin(local_ip, wg_private_key, wg_ep_ip, wg_ep_public_key, wg_ep_port); + } +} + +void wg_get_statusupdate(char* buffer, size_t len) +{ + snprintf_P(buffer, len, PSTR("\"wg\":\"%s\","), wg.is_initialized() ? "on" : "off"); +} + +int wg_get_ipaddress(char* buffer, size_t len) +{ + if (wg.is_initialized()) { + snprintf(buffer, len, "%s", wg_ip); + return 1; + } + return 0; +} + +void wg_get_info(JsonDocument& doc) +{ + JsonObject info = doc.createNestedObject(F(D_INFO_WIREGUARD)); + info[F(D_INFO_STATUS)] = wg.is_initialized() ? F(D_WG_INITIALIZED) : F(D_WG_BAD_CONFIG); + info[F(D_INFO_IP_ADDRESS)] = String(wg_ip); + info[F(D_INFO_ENDPOINT_IP)] = String(wg_ep_ip); + info[F(D_INFO_ENDPOINT_PORT)] = String(wg_ep_port); +} + +#if HASP_USE_CONFIG > 0 +bool wgGetConfig(const JsonObject& settings) +{ + bool changed = false; + + if(strcmp(wg_ip, settings[FPSTR(FP_CONFIG_VPN_IP)].as().c_str()) != 0) changed = true; + settings[FPSTR(FP_CONFIG_VPN_IP)] = wg_ip; + + if(strcmp(wg_private_key, settings[FPSTR(FP_CONFIG_PRIVATE_KEY)].as().c_str()) != 0) changed = true; + //settings[FPSTR(FP_CONFIG_PRIVATE_KEY)] = wg_private_key; + settings[FPSTR(FP_CONFIG_PRIVATE_KEY)] = D_PASSWORD_MASK; + + if(strcmp(wg_ep_ip, settings[FPSTR(FP_CONFIG_HOST)].as().c_str()) != 0) changed = true; + settings[FPSTR(FP_CONFIG_HOST)] = wg_ep_ip; + + if(wg_ep_port != settings[FPSTR(FP_CONFIG_PORT)].as()) changed = true; + settings[FPSTR(FP_CONFIG_PORT)] = wg_ep_port; + + if(strcmp(wg_ep_public_key, settings[FPSTR(FP_CONFIG_PUBLIC_KEY)].as().c_str()) != 0) changed = true; + settings[FPSTR(FP_CONFIG_PUBLIC_KEY)] = wg_ep_public_key; + + if(changed) configOutput(settings, TAG_WG); + return changed; +} + +bool wgSetConfig(const JsonObject& settings) +{ + Preferences preferences; + nvs_user_begin(preferences, "wg", false); + + configOutput(settings, TAG_WG); + bool changed = false; + bool changed_privkey = false; + + changed |= configSet((char *)wg_ip, sizeof(wg_ip), settings[FPSTR(FP_CONFIG_VPN_IP)], F("wgIp")); + if(!settings[FPSTR(FP_CONFIG_PRIVATE_KEY)].isNull() && + settings[FPSTR(FP_CONFIG_PRIVATE_KEY)].as() != String(FPSTR(D_PASSWORD_MASK))) { + changed |= strcmp(wg_private_key, settings[FPSTR(FP_CONFIG_PRIVATE_KEY)]) != 0; + strncpy(wg_private_key, settings[FPSTR(FP_CONFIG_PRIVATE_KEY)], sizeof(wg_private_key)-1); + wg_private_key[sizeof(wg_private_key)-1] = '\0'; + nvsUpdateString(preferences, FP_CONFIG_PRIVATE_KEY, settings[FPSTR(FP_CONFIG_PRIVATE_KEY)]); + } + changed |= changed_privkey; + changed |= configSet((char *)wg_ep_ip, sizeof(wg_ep_ip), settings[FPSTR(FP_CONFIG_HOST)], F("wgEpIp")); + changed |= configSet(wg_ep_port, settings[FPSTR(FP_CONFIG_PORT)], F("wgEpPort")); + changed |= configSet((char *)wg_ep_public_key, sizeof(wg_ep_public_key), settings[FPSTR(FP_CONFIG_PUBLIC_KEY)], F("wgEpPubKey")); + + if (changed && network_is_connected()) { + wg.end(); + wg_network_connected(); + } + + return changed; +} +#endif + + +#endif /* WIREGUARD */ diff --git a/src/sys/net/hasp_wireguard.h b/src/sys/net/hasp_wireguard.h new file mode 100644 index 00000000..486c711e --- /dev/null +++ b/src/sys/net/hasp_wireguard.h @@ -0,0 +1,40 @@ +/* MIT License - Copyright (c) 2023 Jaroslav Kysela + For full license information read the LICENSE file in the project folder */ + +#ifndef HASP_WIREGUARD_H +#define HASP_WIREGUARD_H + +void wg_setup(); +int wg_config_valid(); +void wg_network_disconnected(); +void wg_network_connected(); +void wg_get_statusupdate(char* buffer, size_t len); +int wg_get_ipaddress(char* buffer, size_t len); +void wg_get_info(JsonDocument& doc); + +#if HASP_USE_CONFIG > 0 +bool wgGetConfig(const JsonObject& settings); +bool wgSetConfig(const JsonObject& settings); +#endif + +#ifndef WIREGUARD_IP +#define WIREGUARD_IP "" +#endif + +#ifndef WIREGUARD_PRIVATE_KEY +#define WIREGUARD_PRIVATE_KEY "" +#endif + +#ifndef WIREGUARD_EP_IP +#define WIREGUARD_EP_IP "" +#endif + +#ifndef WIREGUARD_EP_PORT +#define WIREGUARD_EP_PORT 51820 +#endif + +#ifndef WIREGUARD_EP_PUBLIC_KEY +#define WIREGUARD_EP_PUBLIC_KEY "" +#endif + +#endif diff --git a/src/sys/svc/hasp_http.cpp b/src/sys/svc/hasp_http.cpp index d9b12158..6139b177 100644 --- a/src/sys/svc/hasp_http.cpp +++ b/src/sys/svc/hasp_http.cpp @@ -408,6 +408,10 @@ bool http_save_config() #if HASP_USE_WIFI > 0 } else if(save == FP_WIFI) { updated = wifiSetConfig(settings.as()); +#endif +#if HASP_USE_WIREGUARD > 0 + } else if(save == FP_WG) { + updated = wgSetConfig(settings.as()); #endif } } @@ -635,6 +639,10 @@ static void webHandleApi() add_license(obj, "AceButton", "2018", "Brian T. Park", "mit"); obj = doc.createNestedObject(); add_license(obj, "QR Code generator", "", "Project Nayuki", "mit"); +#if HASP_USE_WIREGUARD > 0 + obj = doc.createNestedObject(); + add_license(obj, "WireGuard", "2021", "Kenta Ida fugafuga.org, Daniel Hope www.floorsense.nz", "bsd", 1); +#endif } { char output[HTTP_PAGE_SIZE]; @@ -688,6 +696,11 @@ static void webHandleApi() settings.createNestedObject(module); timeGetConfig(settings[module]); #endif +#if HASP_USE_WIREGUARD > 0 + module = FPSTR(FP_WG); + settings.createNestedObject(module); + wgGetConfig(settings[module]); +#endif #if HASP_USE_MQTT > 0 module = FPSTR(FP_MQTT); settings.createNestedObject(module); @@ -790,6 +803,11 @@ static void webHandleApiConfig() timeSetConfig(settings); } else #endif +#if HASP_USE_WIREGUARD > 0 + if(!strcasecmp(endpoint_key, FP_WG)) { + wgSetConfig(settings); + } else +#endif #if HASP_USE_MQTT > 0 if(!strcasecmp(endpoint_key, FP_MQTT)) { mqttSetConfig(settings); @@ -831,6 +849,11 @@ static void webHandleApiConfig() timeGetConfig(settings); } else #endif +#if HASP_USE_WIREGUARD > 0 + if(!strcasecmp(endpoint_key, FP_WG)) { + wgGetConfig(settings); + } else +#endif #if HASP_USE_MQTT > 0 if(!strcasecmp(endpoint_key, FP_MQTT)) { mqttGetConfig(settings); @@ -937,6 +960,8 @@ static void http_handle_info() {{ item }} Wifi {{ item }} +WireGuard +{{ item }} Module {{ item }} )"; @@ -1415,6 +1440,9 @@ static void http_handle_config() #if HASP_USE_WIFI > 0 html[min(i++, len)] = R"()"; #endif +#if HASP_USE_WIREGUARD > 0 + html[min(i++, len)] = R"()"; +#endif #if HASP_USE_MQTT > 0 html[min(i++, len)] = R"()"; #endif @@ -2302,6 +2330,50 @@ static void http_handle_wifi() #endif // HASP_USE_WIFI +//////////////////////////////////////////////////////////////////////////////////////////////////// +#if HASP_USE_WIREGUARD > 0 +static void http_handle_wireguard() +{ // http://plate01/config/wireguard + if(!http_is_authenticated(F("config/wireguard"))) return; + + const char* html[20]; + int i = 0; + int len = (sizeof(html) / sizeof(html[0])) - 1; + + html[min(i++, len)] = "

"; + html[min(i++, len)] = haspDevice.get_hostname(); + html[min(i++, len)] = "


"; + html[min(i++, len)] = R"( +

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
)"; + html[min(i++, len)] = R"(
)"; + html[min(i++, len)] = R"()"; + http_send_content(html, min(i, len)); +} + +#endif // HASP_USE_WIREGUARD + static inline int handleFirmwareFile(String path) { String contentType((char*)0); @@ -2715,6 +2787,9 @@ void httpSetup() #if HASP_USE_WIFI > 0 webServer.on("/config/wifi", http_handle_wifi); #endif +#if HASP_USE_WIREGUARD > 0 + webServer.on("/config/wireguard", http_handle_wireguard); +#endif #if HASP_USE_GPIO > 0 webServer.on("/config/gpio", webHandleGpioConfig); webServer.on("/config/gpio/options", webHandleGpioOutput); diff --git a/src/sys/svc/hasp_http_async.cpp b/src/sys/svc/hasp_http_async.cpp index 9232cf0d..d73f112f 100644 --- a/src/sys/svc/hasp_http_async.cpp +++ b/src/sys/svc/hasp_http_async.cpp @@ -1259,7 +1259,7 @@ void webHandleMqttConfig(AsyncWebServerRequest* request) //////////////////////////////////////////////////////////////////////////////////////////////////// void webHandleGuiConfig(AsyncWebServerRequest* request) -{ // http://plate01/config/wifi +{ // http://plate01/config/gui if(!httpIsAuthenticated(request, F("config/gui"))) return; {