diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e4efcc..6131385 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,6 +93,8 @@ set(SOURCE_FILES user/io.h user/cgi_wifi.c user/cgi_wifi.h + user/cgi_persist.c + user/cgi_persist.h user/cgi_network.c user/cgi_network.h user/cgi_appcfg.c @@ -140,6 +142,7 @@ add_definitions( -DICACHE_FLASH_ATTR= -DICACHE_RODATA_ATTR= -DFLAG_GZIP=2 + -DADMIN_PASSWORD="asdf" -DESPFS_HEATSHRINK) add_executable(esp_vt100_firmware ${SOURCE_FILES}) diff --git a/Makefile b/Makefile index 5310111..edde199 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ LIBS += esphttpd # compiler flags using during compilation of source files CFLAGS = -Os -ggdb -std=gnu99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inline-functions \ -nostdlib -mlongcalls -mtext-section-literals -D__ets__ -DICACHE_FLASH \ - -Wno-address -Wno-unused -DHTTPD_MAX_BACKLOG_SIZE=8192 + -Wno-address -Wno-unused -DHTTPD_MAX_BACKLOG_SIZE=8192 -DADMIN_PASSWORD=$(ADMIN_PASSWORD) # linker flags used to generate the main object file LDFLAGS = -nostdlib -Wl,--no-check-sections -u call_user_start -Wl,-static diff --git a/build_web.sh b/build_web.sh index 615df48..b794ccd 100755 --- a/build_web.sh +++ b/build_web.sh @@ -14,7 +14,8 @@ cd .. cp html_orig/js/app.js html/js/ -sass --sourcemap=none html_orig/sass/app.scss html/css/app.css +sass html_orig/sass/app.scss html/css/app.css +rm html/css/app.css.map cp html_orig/img/* html/img/ - +cp html_orig/favicon.ico html/favicon.ico diff --git a/esphttpdconfig.mk b/esphttpdconfig.mk index 6f294de..edc6980 100644 --- a/esphttpdconfig.mk +++ b/esphttpdconfig.mk @@ -37,3 +37,6 @@ OUTPUT_TYPE = combined # SPI flash size, in K ESP_SPI_FLASH_SIZE_K = 1024 + +# Admin password, used to store settings to flash as defaults +ADMIN_PASSWORD = "19738426" diff --git a/html_orig/base.php b/html_orig/base.php index 0b99593..925d057 100644 --- a/html_orig/base.php +++ b/html_orig/base.php @@ -20,7 +20,7 @@ require_once __DIR__ . '/_env.php'; $prod = defined('STDIN'); define('DEBUG', !$prod); -$root = DEBUG ? json_encode(ESP_IP) : 'window.location.href'; +$root = DEBUG ? json_encode(ESP_IP) : 'location.host'; define('JS_WEB_ROOT', $root); define('LOCALE', isset($_GET['locale']) ? $_GET['locale'] : 'en'); diff --git a/html_orig/build_html.php b/html_orig/build_html.php old mode 100644 new mode 100755 index 48be82a..d123e1e --- a/html_orig/build_html.php +++ b/html_orig/build_html.php @@ -2,6 +2,18 @@ require_once __DIR__ . '/base.php'; +function process_html($s) { + $pattern = '//Uis'; + $s = preg_replace($pattern, '', $s); + + $pattern = '/(?:(?:\/\*(?:[^*]|(?:\*+[^*\/]))*\*+\/)|(?:(? $p) { if ($p->bodyclass == 'api') continue; @@ -11,6 +23,12 @@ foreach($_pages as $_k => $p) { ob_clean(); // clean up include(__DIR__ . '/index.php'); $s = ob_get_contents(); // grab the output + + // remove newlines and comments + // as tests have shown, it saves just a couple kilobytes, + // making it not a very big improvement at the expense of ugly html. +// $s = process_html($s); + ob_clean(); // clean up $of = __DIR__ . '/../html/' . $_k . '.tpl'; file_put_contents($of, $s); // write to a file diff --git a/html_orig/css/app.css b/html_orig/css/app.css index 6a5f482..7063fea 100644 --- a/html_orig/css/app.css +++ b/html_orig/css/app.css @@ -576,6 +576,10 @@ ul > * { background-color: rgba(255, 255, 255, 0.07); box-shadow: 0 0 4px black; border: 1px solid #4f4f4f; } + .Box::after { + content: ''; + display: block; + clear: both; } @media screen and (max-width: 544px) { .Box { margin-top: 0.61805rem; } } @@ -586,6 +590,8 @@ ul > * { .Box h2 { margin-top: 0; margin-bottom: 0 !important; } + .Box p:last-child { + margin-bottom: 0.5em; } .Box.wide { width: initial; max-width: initial; } @@ -967,7 +973,8 @@ form span.required { background: rgba(255, 255, 255, 0.1); border-radius: 5px; padding: 0.38198rem; - margin-bottom: 0.38198rem; } + margin-bottom: 0.38198rem; + margin-top: 0.38198rem; } #ap-noscan { font-weight: bold; } @@ -1109,10 +1116,8 @@ body.term #buttons { white-space: nowrap; } body.term #buttons button { margin: 0 3px; - padding: 10px 0; - width: 18%; - max-width: 65px; - min-width: initial; + padding: 8px 5px; + min-width: 65px; cursor: pointer; font-weight: bold; } body.term #botnav { @@ -1138,7 +1143,8 @@ body.term #botnav { float: right; height: 130px; } .page-about #logo2 { - max-width: 100%; } + max-width: 100%; + margin: 1rem; } .page-about td { white-space: normal; } @@ -1280,3 +1286,5 @@ body.term #botnav { @media screen and (max-width: 1000px) { .mq-normal-min { display: none !important; } } + +/*# sourceMappingURL=app.css.map */ diff --git a/html_orig/css/app.css.bak b/html_orig/css/app.css.bak new file mode 100644 index 0000000..5baf098 --- /dev/null +++ b/html_orig/css/app.css.bak @@ -0,0 +1,1284 @@ +@charset "UTF-8"; +/* normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS and IE text size adjust after device orientation change, + * without disabling user zoom. + */ +*, *:before, *:after { + box-sizing: border-box; } + +html { + font-family: sans-serif; + /* 1 */ + -ms-text-size-adjust: 100%; + /* 2 */ + -webkit-text-size-adjust: 100%; + /* 2 */ } + +/** + * Remove default margin. + */ +body { + margin: 0; } + +/* HTML5 display definitions + ========================================================================== */ +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ +figure, +nav + { + display: block; } + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ +canvas, +progress + { + display: inline-block; + /* 1 */ + vertical-align: baseline; + /* 2 */ } + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. + */ +[hidden] + { + display: none; } + +/* Links + ========================================================================== */ +/** + * Remove the gray background color from active links in IE 10. + */ +a { + background-color: transparent; } + +/** + * Improve readability of focused elements when they are also in an + * active/hover state. + */ +a:active, +a:hover { + outline: 0; } + +/* Text-level semantics + ========================================================================== */ +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ +b + { + font-weight: bold; } + +/** + * Address styling not present in Safari and Chrome. + */ +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ +h1 { + font-size: 2em; + margin: 0.67em 0; } + +h2 { + font-size: 2em; + margin: 0.67em 0; } + +/** + * Address styling not present in IE 8/9. + */ +/** + * Address inconsistent and variable font size in all browsers. + */ +small { + font-size: 80%; } + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; } + +sup { + top: -0.5em; } + +sub { + bottom: -0.25em; } + +/* Embedded content + ========================================================================== */ +/** + * Remove border when inside `a` element in IE 8/9/10. + */ +img { + border: 0; } + +/** + * Correct overflow not hidden in IE 9/10/11. + */ +svg:not(:root) { + overflow: hidden; } + +/* Grouping content + ========================================================================== */ +/** + * Address margin not present in IE 8/9 and Safari. + */ +/** + * Address differences between Firefox and other browsers. + */ +hr { + box-sizing: content-box; + height: 0; } + +/** + * Contain overflow in all browsers. + */ +pre { + overflow: auto; } + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ +code, +pre + { + font-family: monospace; + font-size: 1em; } + +/* Forms + ========================================================================== */ +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ +button, +input, +select, +textarea { + color: inherit; + /* 1 */ + font: inherit; + /* 2 */ + margin: 0; + /* 3 */ } + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ +button { + overflow: visible; } + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ +button, +select { + text-transform: none; } + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ +button, +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + /* 2 */ + cursor: pointer; + /* 3 */ } + +/** + * Re-set default cursor for disabled elements. + */ +button[disabled], +html input[disabled] { + cursor: default; } + +/** + * Remove inner padding and border in Firefox 4+. + */ +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; } + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ +input { + line-height: normal; } + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; + /* 1 */ + padding: 0; + /* 2 */ } + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome. + */ +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ +/** + * Define consistent border, margin, and padding. + */ +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ +legend { + border: 0; + /* 1 */ + padding: 0; + /* 2 */ } + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ +textarea { + overflow: auto; } + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ +/* Tables + ========================================================================== */ +/** + * Remove most spacing between table cells. + */ +table { + border-collapse: collapse; + border-spacing: 0; } + +td, +th { + padding: 0; } + +@font-face { + font-family: 'fontello'; + src: url("data:application/octet-stream;base64,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") format("woff"), url("data:application/octet-stream;base64,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I7AEQ2CwASuwBSVhsAUlsAJiILAAUFiwQGBZZrABY7AEJmEgsAQlYGQjsAMlYGRQWCEbIyFZIyAgsAQmI0ZhOFktsDcssAAWICAgsAUmIC5HI0cjYSM8OC2wOCywABYgsAgjQiAgIEYjR7ABKyNhOC2wOSywABawAyWwAiVHI0cjYbAAVFguIDwjIRuwAiWwAiVHI0cjYSCwBSWwBCVHI0cjYbAGJbAFJUmwAiVhuQgACABjYyMgWGIbIVljuAQAYiCwAFBYsEBgWWawAWNgIy4jICA8ijgjIVktsDossAAWILAIQyAuRyNHI2EgYLAgYGawAmIgsABQWLBAYFlmsAFjIyAgPIo4LbA7LCMgLkawAiVGUlggPFkusSsBFCstsDwsIyAuRrACJUZQWCA8WS6xKwEUKy2wPSwjIC5GsAIlRlJYIDxZIyAuRrACJUZQWCA8WS6xKwEUKy2wPiywNSsjIC5GsAIlRlJYIDxZLrErARQrLbA/LLA2K4ogIDywBCNCijgjIC5GsAIlRlJYIDxZLrErARQrsARDLrArKy2wQCywABawBCWwBCYgLkcjRyNhsAlDKyMgPCAuIzixKwEUKy2wQSyxCAQlQrAAFrAEJbAEJSAuRyNHI2EgsAQjQrAJQysgsGBQWCCwQFFYswIgAyAbswImAxpZQkIjIEewBEOwAmIgsABQWLBAYFlmsAFjYCCwASsgiophILACQ2BkI7ADQ2FkUFiwAkNhG7ADQ2BZsAMlsAJiILAAUFiwQGBZZrABY2GwAiVGYTgjIDwjOBshICBGI0ewASsjYTghWbErARQrLbBCLLA1Ky6xKwEUKy2wQyywNishIyAgPLAEI0IjOLErARQrsARDLrArKy2wRCywABUgR7AAI0KyAAEBFRQTLrAxKi2wRSywABUgR7AAI0KyAAEBFRQTLrAxKi2wRiyxAAEUE7AyKi2wRyywNCotsEgssAAWRSMgLiBGiiNhOLErARQrLbBJLLAII0KwSCstsEossgAAQSstsEsssgABQSstsEwssgEAQSstsE0ssgEBQSstsE4ssgAAQistsE8ssgABQistsFAssgEAQistsFEssgEBQistsFIssgAAPistsFMssgABPistsFQssgEAPistsFUssgEBPistsFYssgAAQCstsFcssgABQCstsFgssgEAQCstsFkssgEBQCstsFossgAAQystsFsssgABQystsFwssgEAQystsF0ssgEBQystsF4ssgAAPystsF8ssgABPystsGAssgEAPystsGEssgEBPystsGIssDcrLrErARQrLbBjLLA3K7A7Ky2wZCywNyuwPCstsGUssAAWsDcrsD0rLbBmLLA4Ky6xKwEUKy2wZyywOCuwOystsGgssDgrsDwrLbBpLLA4K7A9Ky2waiywOSsusSsBFCstsGsssDkrsDsrLbBsLLA5K7A8Ky2wbSywOSuwPSstsG4ssDorLrErARQrLbBvLLA6K7A7Ky2wcCywOiuwPCstsHEssDorsD0rLbByLLMJBAIDRVghGyMhWUIrsAhlsAMkUHiwARUwLQBLuADIUlixAQGOWbABuQgACABjcLEABUKyAAEAKrEABUKzCgIBCCqxAAVCsw4AAQgqsQAGQroCwAABAAkqsQAHQroAQAABAAkqsQMARLEkAYhRWLBAiFixA2REsSYBiFFYugiAAAEEQIhjVFixAwBEWVlZWbMMAgEMKrgB/4WwBI2xAgBEAAA=") format("truetype"); } +/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ +/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ +/* +@media screen and (-webkit-min-device-pixel-ratio:0) { + @font-face { + font-family: 'fontello'; + src: url('../font/fontello.svg?60007293#fontello') format('svg'); + } +} +*/ +[class^="icn-"]:before, [class*=" icn-"]:before { + font-family: "fontello"; + font-style: normal; + font-weight: normal; + speak: none; + display: inline-block; + text-decoration: inherit; + width: 1em; + margin-right: .2em; + text-align: center; + /* opacity: .8; */ + /* For safety - reset parent styles, that can break glyph codes*/ + font-variant: normal; + text-transform: none; + /* fix buttons height, for twitter bootstrap */ + line-height: 1em; + /* Animation center compensation - margins should be symmetric */ + /* remove if not needed */ + margin-left: .2em; + /* you can be more comfortable with increased icons size */ + /* font-size: 120%; */ + /* Uncomment for 3D effect */ + /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ } + +.icn-persist:before { + content: '\e800'; } + +/* '' */ +.icn-ok:before { + content: '\e801'; } + +/* '' */ +.icn-donate:before { + content: '\e802'; } + +/* '' */ +.icn-back:before { + content: '\e80b'; } + +/* '' */ +.icn-about:before { + content: '\e81f'; } + +/* '' */ +.icn-help:before { + content: '\e826'; } + +/* '' */ +.icn-network:before { + content: '\e82e'; } + +/* '' */ +.icn-restore:before { + content: '\e836'; } + +/* '' */ +.icn-terminal:before { + content: '\f108'; } + +/* '' */ +.icn-github:before { + content: '\f113'; } + +/* '' */ +.icn-wifi:before { + content: '\f1eb'; } + +/* '' */ +html { + box-sizing: border-box; } + +*, *::after, *::before { + box-sizing: inherit; } + +html { + font-family: Arial, sans-serif; + color: #D0D0D0; + background: #131315; } + +html, body { + border: 0 none; + margin: 0; + padding: 0; + text-decoration: none; + width: 100%; + height: 100%; + overflow: hidden; } + +a, a:visited, a:link { + cursor: pointer; + color: #5abfff; + text-decoration: none; } + +a:hover { + color: #5abfff; + text-decoration: underline; } + +.hidden { + display: none !important; } + +[onclick] { + cursor: pointer; } + +ul > * { + padding-top: .2em; + padding-bottom: .2em; } + +/* Main outer container */ +#outer { + display: flex; + position: absolute; + width: 100%; + height: 100%; + left: 0; + right: 0; + top: 0; + bottom: 0; + overflow: hidden; + flex-direction: row; } + +@media screen and (max-width: 544px) { + #outer { + display: block; + overflow-y: scroll; } } +#menu { + flex: 0 0 15rem; + background: #3983CD; } + #menu > * { + display: block; + text-decoration: none; + padding: 0.61805rem 1rem; + white-space: nowrap; + word-wrap: normal; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + #menu #brand { + color: white; + background: #2b6aa8; + font-size: 120%; + text-align: center; + position: relative; + transition: none; + font-weight: bold; + margin-bottom: 1rem; } + @media screen and (max-width: 544px) { + #menu #brand { + background: #3983CD; + cursor: pointer; + margin-bottom: 0.38198rem; } + #menu #brand::after { + position: absolute; + color: rgba(0, 0, 0, 0.4); + right: 1rem; + content: '▸'; + top: 50%; + font-size: 120%; + font-weight: bold; + transform: translate(0, -50%) rotate(90deg); } } + #menu.expanded #brand { + background: #2b6aa8; } + @media screen and (max-width: 544px) { + #menu.expanded #brand:after { + transform: translate(-25%, -50%) rotate(-90deg); } } + #menu a { + font-size: 130%; + color: white; + transition: background-color 0.2s; + text-shadow: 0 0 5px rgba(0, 0, 0, 0.4); } + #menu a:hover, #menu a.selected { + background: #5badff; + text-shadow: 0 0 5px rgba(0, 0, 0, 0.6); } + #menu a.selected { + position: relative; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); } + #menu a:focus { + outline-color: #ff0099; } + #menu a::before { + vertical-align: -2px; + margin-left: 0; + margin-right: 15px; } + @media screen and (max-width: 544px) { + #menu a { + display: none; } + #menu a::before { + margin-left: 10px; } } + #menu.expanded a { + display: block; } + @media screen and (min-width: 545px) and (max-width: 1000px) { + #menu { + flex-basis: 10rem; } + #menu #brand { + font-size: 95%; + margin-bottom: 0.61805rem; } + #menu a { + font-size: 105%; } + #menu > * { + padding: 0.38198rem 0.61805rem; } } + +#content { + flex-grow: 1; + position: relative; + padding: 1rem; + overflow-y: auto; } + @media screen and (max-width: 544px) { + #content { + padding: 0.61805rem; } } + #content > * { + margin-left: auto; + margin-right: auto; } + #content h1 { + text-align: center; + font-size: 2.2807em; + margin-top: 0; + margin-bottom: 1rem; } + #content h2 { + font-size: 1.42383em; + margin-bottom: 0.61805rem; } + @media screen and (max-width: 544px) { + #content h1 { + font-size: 1.80203em; + margin-bottom: 0.61805rem; } + #content h2 { + font-size: 1.26563em; + margin-bottom: 0.61805rem; } } + #content td, #content th { + padding: 0.38198rem; } + #content tbody th { + text-align: right; + width: 160px; + color: white; } + +#loader { + position: absolute; + right: 1.618rem; + top: 1.618rem; + transition: opacity .2s; + opacity: 0; } + @media screen and (max-width: 544px) { + #loader { + top: 1rem; + right: 1rem; } } + #loader.show { + opacity: 1; } + +.Box { + display: block; + max-width: 900px; + margin-top: 1rem; + padding: 0.61805rem 1rem; + border-radius: 3px; + background-color: rgba(255, 255, 255, 0.07); + box-shadow: 0 0 4px black; + border: 1px solid #4f4f4f; } + @media screen and (max-width: 544px) { + .Box { + margin-top: 0.61805rem; } } + .Box h1, .Box h2 { + overflow: hidden; } + h1 + .Box { + margin-top: 0; } + .Box h2 { + margin-top: 0; + margin-bottom: 0 !important; } + .Box.wide { + width: initial; + max-width: initial; } + .Box.medium { + max-width: 1200px; } + .Box.str { + position: relative; } + .Box.str .Row.buttons { + position: absolute; } + @media screen and (max-width: 544px) { + .Box.str .Row.buttons { + right: 1rem; + top: 1.8em; + margin: 1rem auto; } } + @media screen and (min-width: 545px) { + .Box.str .Row.buttons { + right: 0; + top: 0; + margin-top: 0.61805rem; } } + .Box.str.mobopen .Row.buttons { + top: 0; + margin-top: 0.61805rem; } + .Box .Row.explain { + max-width: 600px; + margin-left: 0; } + @media screen and (max-width: 544px) { + .Box .Row.explain { + margin-top: 60px; } } + .Box.mobopen .Row.explain { + margin-top: 12px; } + @media screen and (max-width: 544px) { + .Box.mobopen .Row.explain { + margin-top: 18px; } } + +@media screen and (max-width: 544px) { + .Box.mobcol h2 { + position: relative; + cursor: pointer; + padding-right: 1.3rem; } + .Box.mobcol h2::after { + position: absolute; + right: 0; + content: '▸'; + top: 50%; + font-size: 120%; + font-weight: bold; + transform: translate(0, -50%) rotate(90deg); } + .Box.mobcol.expanded h2::after { + transform: translate(-25%, -50%) rotate(-90deg); + margin-bottom: 1rem; } + .Box.mobcol .Row { + display: none; } + .Box.mobcol #ap-box { + display: none; } + .Box.mobcol.expanded .Row { + display: flex; } + .Box.mobcol.expanded #ap-box { + display: block; } } +.Modal { + position: fixed; + width: 100%; + height: 100%; + left: 0; + top: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + transition: opacity .5s; + background: rgba(0, 0, 0, 0.65); + opacity: 0; } + .Modal.visible { + opacity: 1; } + .Modal.hidden { + display: none; } + +.Dialog { + margin: 0.61805rem; + padding: 1rem; + overflow: hidden; + max-width: 100%; + max-height: 100%; + flex: 0 1 30rem; + background: #1c1c1e; + border-left: 6px solid #2972ba; + border-right: 6px solid #2972ba; + border-top: 1px solid #2972ba; + border-bottom: 1px solid #2972ba; + box-shadow: 0 0 6px 0 black; + border-radius: 6px; } + .Dialog h1, .Dialog h2 { + margin-top: 0; } + .Dialog p:last-child { + margin-bottom: 0; } + +.NotifyMsg { + position: fixed; + top: 1.618rem; + right: 2.61792rem; + padding: 0.61805rem 1rem; + -webkit-font-smoothing: subpixel-antialiased; + -webkit-transform: translateZ(0) scale(1, 1); + background: #3887d0; + color: white; + text-shadow: 0 0 2px black; + box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.6); + border-radius: 5px; + max-width: 80%; + transition: opacity .5s; + opacity: 0; } + .NotifyMsg.error { + background: #d03e42; } + @media screen and (max-width: 544px) { + .NotifyMsg { + width: calc(100% - 1rem); } } + .NotifyMsg.visible { + opacity: 1; } + .NotifyMsg.hidden { + display: none; } + +button, input[type=submit], .button { + text-align: center; + cursor: pointer; + display: inline-block; + border-radius: 2px; + padding: 0 0.6em; + border: 0 none; + line-height: 1.8em; + font-size: 1.1em; + margin-bottom: 3px; + min-width: 5em; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + text-shadow: 1.5px 1.5px 2px rgba(0, 0, 0, 0.4); + background-color: #3983cd; + box-shadow: 0 3px 0 #265f98; + text-decoration: none !important; } + button:active, input[type=submit]:active, .button:active { + position: relative; + top: 2px; } + button.narrow, input[type=submit].narrow, .button.narrow { + min-width: initial; } + button::before, input[type=submit]::before, .button::before { + vertical-align: -1px; + margin-left: 0; } + button, button:link, button:visited, input[type=submit], input[type=submit]:link, input[type=submit]:visited, .button, .button:link, .button:visited { + color: #FEFEFE; } + button:hover, button:active, button.active, button.selected, input[type=submit]:hover, input[type=submit]:active, input[type=submit].active, input[type=submit].selected, .button:hover, .button:active, .button.active, .button.selected { + background-color: #2076C6; + color: #FEFEFE; } + button:hover, button.selected, button.active, input[type=submit]:hover, input[type=submit].selected, input[type=submit].active, .button:hover, .button.selected, .button.active { + box-shadow: 0 3px 0 #154c80; } + button:active, input[type=submit]:active, .button:active { + box-shadow: 0 1px 0 #154c80; } + button:focus, input[type=submit]:focus, .button:focus { + outline-color: #ff0099; } + +button, input[type=submit], .button { + text-align: center; + cursor: pointer; + display: inline-block; + border-radius: 2px; + padding: 0 0.6em; + border: 0 none; + line-height: 1.8em; + font-size: 1.1em; + margin-bottom: 3px; + min-width: 5em; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + text-shadow: 1.5px 1.5px 2px rgba(0, 0, 0, 0.4); + background-color: #3983cd; + box-shadow: 0 3px 0 #265f98; + text-decoration: none !important; } + button:active, input[type=submit]:active, .button:active { + position: relative; + top: 2px; } + button.narrow, input[type=submit].narrow, .button.narrow { + min-width: initial; } + button::before, input[type=submit]::before, .button::before { + vertical-align: -1px; + margin-left: 0; } + button, button:link, button:visited, input[type=submit], input[type=submit]:link, input[type=submit]:visited, .button, .button:link, .button:visited { + color: #FEFEFE; } + button:hover, button:active, button.active, button.selected, input[type=submit]:hover, input[type=submit]:active, input[type=submit].active, input[type=submit].selected, .button:hover, .button:active, .button.active, .button.selected { + background-color: #2076C6; + color: #FEFEFE; } + button:hover, button.selected, button.active, input[type=submit]:hover, input[type=submit].selected, input[type=submit].active, .button:hover, .button.selected, .button.active { + box-shadow: 0 3px 0 #154c80; } + button:active, input[type=submit]:active, .button:active { + box-shadow: 0 1px 0 #154c80; } + button:focus, input[type=submit]:focus, .button:focus { + outline-color: #ff0099; } + +input[type="number"], input[type="password"], input[type="text"], textarea, select { + border: 0 none; + border-bottom: 2px solid #2972ba; + background-color: #3c3c3c; + color: white; + padding: 6px; + line-height: 1em; + font-weight: normal; } + input[type="number"]:focus, input[type="number"]:hover, input[type="password"]:focus, input[type="password"]:hover, input[type="text"]:focus, input[type="text"]:hover, textarea:focus, textarea:hover, select:focus, select:hover { + border-bottom-color: #2ea1f9; } + +.Row.checkbox { + line-height: 27px; } + .Row.checkbox .box { + overflow: hidden; + width: 27px; + height: 27px; + border: 1px solid #808080; + border-radius: 3px; + background: #3c3c3c; + display: inline-block; + position: relative; + cursor: pointer; + color: #2ea1f9; } + .Row.checkbox .box::before { + font-weight: bold; + position: absolute; + content: '×'; + left: 0; + top: 0; + right: 0; + bottom: 0; + line-height: 26px; + text-align: center; + font-size: 27px; + vertical-align: middle; + display: none; } + .Row.checkbox .box.checked::before { + display: block; } + +.Row.range .display { + margin-left: 1ex; } +.Row.range label .display { + font-weight: normal; } + +#psk-modal form > *, #wificonfbox form > * { + margin-right: 0.38198rem; } + #psk-modal form > *:last-child, #wificonfbox form > *:last-child { + margin-right: 0; } + +form { + border: 0 none; + margin: 0; + padding: 0; + text-decoration: none; } + +input[type="number"], input[type="password"], input[type="text"], textarea, select, label.select-wrap { + width: 250px; } + +input[type="number"], input.short { + width: 125px; } + +.Box.errors .list { + color: crimson; + font-weight: bold; } +.Box.errors .lead { + color: white; } + +.Row { + vertical-align: middle; + margin: 12px auto; + text-align: left; + display: flex; + flex-direction: row; + align-items: center; } + .Row:first-child { + margin-top: 0; } + .Row:last-child { + margin-bottom: 0; } + .Row .spacer { + width: 160px; } + @media screen and (max-width: 544px) { + .Row .spacer { + display: none; } } + .Row.buttons { + margin: 16px auto; } + .Row.buttons input, .Row.buttons .button { + margin-right: 0.61805rem; } + .Row.centered { + justify-content: center; } + .Row.message { + font-size: 1em; + text-shadow: 1px 1px 3px black; + text-align: center; } + .Row.message.error { + color: crimson; } + .Row.message.ok { + color: #0fe851; } + .Row.separator { + padding-top: 14px; + border-top: 2px solid rgba(255, 255, 255, 0.1); } + .Row textarea { + display: inline-block; + vertical-align: top; + min-height: 10rem; + flex-grow: 1; + resize: vertical; } + .Row label { + font-weight: bold; + color: white; + display: inline-block; + width: 160px; + text-align: right; + text-shadow: 1px 1px 3px black; + padding: 8px; + align-self: flex-start; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + white-space: nowrap; + word-wrap: normal; } + .Row label.error { + color: crimson; } + .Row input[type="range"] { + width: 200px; } + @media screen and (max-width: 544px) { + .Row { + flex-direction: column; + margin: 6px auto; } + .Row.buttons, .Row.centered, .Row.checkbox { + flex-direction: row; } + .Row.buttons { + justify-content: center; } + .Row.buttons :last-child { + margin-right: 0; } + .Row label { + padding-left: 0; + text-align: left; + width: auto; } + .Row .checkbox-wrap { + order: 1; + text-align: left; + padding-bottom: 0; + border-radius: .4px; + width: auto; } + .Row .checkbox-wrap + label { + width: auto; } + .Row input[type="number"], .Row input[type="password"], .Row input[type="text"], .Row textarea, .Row input[type="range"], .Row textarea, .Row select { + width: 100%; } } + +form span.required { + color: red; } + +.RadioGroup { + display: inline-block; + line-height: 1.5em; + vertical-align: middle; } + .RadioGroup label { + width: auto; + text-align: left; + cursor: pointer; + font-weight: normal; } + .RadioGroup input[type="radio"] { + vertical-align: middle; + margin: 0 0 0 5px; } + +#ap-list { + column-count: 3; + column-gap: 0; + margin: 0 -0.23608rem; } + @media screen and (min-width: 545px) and (max-width: 1000px) { + #ap-list { + column-count: 2; } } + @media screen and (max-width: 544px) { + #ap-list { + column-count: 1; } } + +#ap-loader, #ap-noscan, #ap-scan { + background: rgba(255, 255, 255, 0.1); + border-radius: 5px; + padding: 0.38198rem; + margin-bottom: 0.38198rem; } + +#ap-noscan { + font-weight: bold; } + +#ap-box { + padding-top: 0.38198rem; } + #ap-box label { + display: block; + color: white; + font-weight: bold; + margin-bottom: 0.23608rem; } + +#psk-modal form { + display: flex; + align-items: center; + margin: 0.38198rem; } + #psk-modal form input[type=password] { + min-width: 5rem; } + +.AP .inner, .AP-preview .wrap { + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + position: relative; + border-radius: 3px; + color: #222; + background: #afafaf; + transition: background-color 0.5s; + display: flex; } + .AP .inner:active, .AP-preview .wrap:active { + left: 0; + top: 1px; } + .AP .inner:hover, .AP-preview .wrap:hover { + background: white; } + .AP .inner .rssi, .AP-preview .wrap .rssi { + min-width: 2.5rem; + flex: 0 0 15%; + text-align: right; } + .AP .inner .rssi:after, .AP-preview .wrap .rssi:after { + padding-left: 0.09018rem; + content: '%'; + font-size: 0.88889em; } + .AP .inner .essid, .AP-preview .wrap .essid { + flex: 1 1 70%; + min-width: 0; + text-overflow: ellipsis; + overflow: hidden; + font-weight: bold; } + .AP .inner .auth, .AP-preview .wrap .auth { + flex: 0 0 15%; } + +.AP { + break-inside: avoid-column; + max-width: 500px; + padding: 0.23608rem; } + .AP.selected .inner { + background: #42a6f9 !important; + cursor: default; + top: 0 !important; } + .AP .inner > * { + padding: 0.61805rem; + white-space: nowrap; + word-wrap: normal; } + +.AP-preview-nil { + padding: 8px; + border-radius: 5px; + border: 1px dashed #ddd; + width: 250px; + height: 94px; } + +.AP-preview .wrap { + flex-direction: row; + background: #ddd !important; + cursor: default; + top: 0 !important; + overflow: hidden; } + .AP-preview .wrap .inner { + display: flex; + flex-direction: column; } + .AP-preview .wrap .inner > * { + padding: 0.61805rem; + white-space: nowrap; + word-wrap: normal; } + .AP-preview .wrap .forget { + align-self: stretch; + line-height: 100%; + padding: 0.61805rem; + border-left: 1px solid #bbb; + display: flex; + align-items: center; + font-size: 28px; } + .AP-preview .wrap .forget, .AP-preview .wrap .forget:hover { + color: black; + text-decoration: none; } + .AP-preview .wrap .forget:hover { + background: #dc4a6a; + color: white; + border-left: 1px solid #666; + border-bottom-right-radius: 3px; + border-top-right-radius: 3px; } + .AP-preview .wrap .forget:active { + position: relative; + padding-top: calc(0.61805rem + 1px); } + .AP-preview .wrap .essid, .AP-preview .wrap .passwd, .AP-preview .wrap .nopasswd { + padding-bottom: 0; } + .AP-preview .wrap .x-passwd { + font-family: monospace; } + +body.term h1 { + font-size: 1.80203em; } + @media screen and (max-width: 544px) { + body.term h1 { + font-size: 1.42383em; } } +body.term #screen { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + font-family: monospace; + font-size: 16pt; + white-space: nowrap; + background: #111213; + padding: 6px; + display: inline-block; + border: 2px solid #3983CD; } + body.term #screen span { + white-space: pre; + cursor: pointer; } + body.term #screen span:hover { + outline: 1px solid rgba(255, 255, 255, 0.4); } + @media screen and (max-width: 544px) { + body.term #screen span:hover { + outline: 0 none; } } +body.term #buttons { + margin-top: 10px; + white-space: nowrap; } + body.term #buttons button { + margin: 0 3px; + padding: 10px 0; + width: 18%; + max-width: 65px; + min-width: initial; + cursor: pointer; + font-weight: bold; } +body.term #botnav { + padding-top: 1.5em; + text-align: center; } + body.term #botnav a { + padding: 0 0.38198rem; + text-decoration: underline; } + body.term #botnav a, body.term #botnav a:visited, body.term #botnav a:link { + color: #2e4d6e; } + body.term #botnav a:hover { + color: #5abfff; } + +#termwrap { + text-align: center; } + +.page-about .Box { + padding-left: 1rem; + padding-right: 1rem; } + .page-about .Box a { + font-weight: bold; } +.page-about #logo { + float: right; + height: 130px; } +.page-about #logo2 { + max-width: 100%; } +.page-about td { + white-space: normal; } + +.colorprev { + margin-top: 0.38198rem; + margin-bottom: 0.38198rem; } + .colorprev span { + display: inline-block; + width: 2em; + padding: 0.38198rem 0; + text-align: center; } + +.ansiref, .ansiref td, .ansiref th { + border: 1px solid #666; } +.ansiref th, .ansiref td { + white-space: normal; } +.ansiref th { + background-color: rgba(255, 255, 255, 0.1); } +.ansiref td:nth-child(1) { + font-family: monospace; } +.ansiref.w100 { + width: 100%; } + .ansiref.w100 td:nth-child(1) { + width: 9em; } + .ansiref.w100 td:nth-child(2) { + width: 8em; } + +.fg0 { + color: #111213; } + +.bg0 { + background-color: #111213; } + +.fg1 { + color: #CC0000; } + +.bg1 { + background-color: #CC0000; } + +.fg2 { + color: #4E9A06; } + +.bg2 { + background-color: #4E9A06; } + +.fg3 { + color: #C4A000; } + +.bg3 { + background-color: #C4A000; } + +.fg4 { + color: #3465A4; } + +.bg4 { + background-color: #3465A4; } + +.fg5 { + color: #75507B; } + +.bg5 { + background-color: #75507B; } + +.fg6 { + color: #06989A; } + +.bg6 { + background-color: #06989A; } + +.fg7 { + color: #D3D7CF; } + +.bg7 { + background-color: #D3D7CF; } + +.fg8 { + color: #555753; } + +.bg8 { + background-color: #555753; } + +.fg9 { + color: #EF2929; } + +.bg9 { + background-color: #EF2929; } + +.fg10 { + color: #8AE234; } + +.bg10 { + background-color: #8AE234; } + +.fg11 { + color: #FCE94F; } + +.bg11 { + background-color: #FCE94F; } + +.fg12 { + color: #729FCF; } + +.bg12 { + background-color: #729FCF; } + +.fg13 { + color: #AD7FA8; } + +.bg13 { + background-color: #AD7FA8; } + +.fg14 { + color: #34E2E2; } + +.bg14 { + background-color: #34E2E2; } + +.fg15 { + color: #EEEEEC; } + +.bg15 { + background-color: #EEEEEC; } + +.fg8, .fg9, .fg10, .fg11, .fg12, .fg13, .fg14, .fg15 { + font-weight: bold; } + +.nb { + font-weight: normal !important; } + +@media screen and (min-width: 545px) { + .mq-phone { + display: none !important; } } +@media screen and (max-width: 544px) { + .mq-tablet-min, .mq-no-phone { + display: none !important; } } +@media screen and (min-width: 1001px) { + .mq-tablet-max { + display: none !important; } } +@media screen and (max-width: 1000px) { + .mq-normal-min { + display: none !important; } } + +/*# sourceMappingURL=app.css.map */ diff --git a/html_orig/dump_js_lang.php b/html_orig/dump_js_lang.php new file mode 100755 index 0000000..41cf168 --- /dev/null +++ b/html_orig/dump_js_lang.php @@ -0,0 +1,21 @@ + 0); + + $('#sta-nw .essid').html(e(name)); + var nopw = undef(password) || password.length == 0; + $('#sta-nw .x-passwd').html(e(password)); + $('#sta-nw .passwd').toggleClass('hidden', nopw); + $('#sta-nw .nopasswd').toggleClass('hidden', !nopw); + $('#sta-nw .ip').html(ip.length>0 ? tr('wifi.connected_ip_is')+ip : tr('wifi.not_conn')); + } + + function submitPskModal(e, open) { + var passwd = $('#conn-passwd').val(); + var ssid = $('#conn-ssid').val(); + + if (open || passwd.length) { + $('#sta_password').val(passwd); + $('#sta_ssid').val(ssid); + selectSta(ssid, passwd, ''); + } + + if (e) e.preventDefault(); + Modal.hide('#psk-modal'); + return false; + } + + /** Update display for received response */ + function onScan(resp, status) { + //var ap_json = { + // "result": { + // "inProgress": "0", + // "APs": [ + // {"essid": "Chlivek", "bssid": "88:f7:c7:52:b3:99", "rssi": "204", "enc": "4", "channel": "1"}, + // {"essid": "TyNikdy", "bssid": "5c:f4:ab:0d:f1:1b", "rssi": "164", "enc": "3", "channel": "1"}, + // ] + // } + //}; + + if (status != 200) { + // bad response + rescan(5000); // wait 5sm then retry + return; + } + + try { + resp = JSON.parse(resp); + } catch (e) { + console.log(e); + rescan(5000); + return; + } + + var done = !bool(resp.result.inProgress) && (resp.result.APs.length > 0); + rescan(done ? 15000 : 1000); + if (!done) return; // no redraw yet + + // clear the AP list + var $list = $('#ap-list'); + // remove old APs + $('#ap-list .AP').remove(); + + $list.toggleClass('hidden', !done); + $('#ap-loader').toggleClass('hidden', done); + + // scan done + resp.result.APs.sort(function (a, b) { + return b.rssi - a.rssi; + }).forEach(function (ap) { + ap.enc = parseInt(ap.enc); + + if (ap.enc > 4) return; // hide unsupported auths + WiFi.scan_url = '/cfg/wifi/scan'; + + var item = mk('div'); + + var $item = $(item) + .data('ssid', ap.essid) + .data('pwd', ap.enc) + .attr('tabindex', 0) + .addClass('AP'); + + // mark current SSID + if (ap.essid == curSSID) { + $item.addClass('selected'); + } + + var inner = mk('div'); + $(inner).addClass('inner') + .htmlAppend('
{0}
'.format(ap.rssi_perc)) + .htmlAppend('
{0}
'.format($.htmlEscape(ap.essid))) + .htmlAppend('
{0}
'.format(authStr[ap.enc])); + + $item.on('click', function () { + var $th = $(this); + + var ssid = $th.data('ssid'); + + $('#conn-ssid').val(ssid); + $('#conn-passwd').val(''); + + if (+$th.data('pwd')) { + // this AP needs a password + Modal.show('#psk-modal'); + $('#conn-passwd')[0].focus(); + } else { + //Modal.show('#reset-modal'); + submitPskModal(null, true); + } + }); + + + item.appendChild(inner); + $list[0].appendChild(item); + }); + } + + function startScanning() { + $('#ap-loader').removeClass('hidden'); + $('#ap-scan').addClass('hidden'); + $('#ap-loader .anim-dots').html('.'); + scanAPs(); + } + + /** Ask the CGI what APs are visible (async) */ + function scanAPs() { + $.get('http://'+_root+w.scan_url, onScan); + } + + function rescan(time) { + setTimeout(scanAPs, time); + } + + /** Set up the WiFi page */ + function wifiInit(cfg) { + // Hide what should be hidden in this mode + cfg.mode = +cfg.mode; + + $('#ap-noscan').toggleClass('hidden', cfg.mode != 2); + $('#ap-scan').toggleClass('hidden', cfg.mode == 2); + + // Update slider value displays + $('.Row.range').forEach(function(x) { + var inp = x.querySelector('input'); + var disp1 = x.querySelector('.x-disp1'); + var disp2 = x.querySelector('.x-disp2'); + var t = rangePt(inp); + $(disp1).html(t); + $(disp2).html(t); + $(inp).on('input', function() { + t = rangePt(inp); + $(disp1).html(t); + $(disp2).html(t); + }); + }); + + // Forget STA credentials + $('#forget-sta').on('click', function() { + selectSta('', '', ''); + return false; + }); + + selectSta(cfg.sta_ssid, cfg.sta_password, cfg.sta_active_ip); + curSSID = cfg.sta_active_ssid; + } + + w.init = wifiInit; + w.startScanning = startScanning; +})(window.WiFi = {}); (function() { /** * Terminal module @@ -1307,5 +1497,5 @@ $._loader = function(vis) { Term.init(obj); Conn.init(); Input.init(); - } + }; })(); diff --git a/html_orig/jssrc/appcommon.js b/html_orig/jssrc/appcommon.js index 0a97d82..0e5616b 100644 --- a/html_orig/jssrc/appcommon.js +++ b/html_orig/jssrc/appcommon.js @@ -100,7 +100,8 @@ $.ready(function () { x.removeAttribute('tabindex'); }); - qs('#brand').removeAttribute('tabindex'); + var br = qs('#brand'); + br && br.removeAttribute('tabindex'); } }); diff --git a/html_orig/jssrc/lang.js b/html_orig/jssrc/lang.js new file mode 100644 index 0000000..6ef4755 --- /dev/null +++ b/html_orig/jssrc/lang.js @@ -0,0 +1,7 @@ +// Generated from PHP locale file +var _tr = { + "wifi.connected_ip_is": "Connected, IP is ", + "wifi.not_conn": "Not connected." +}; + +function tr(key) { return _tr[key] || '?'+key+'?'; } diff --git a/html_orig/jssrc/term.js b/html_orig/jssrc/term.js index 0965105..6206c85 100644 --- a/html_orig/jssrc/term.js +++ b/html_orig/jssrc/term.js @@ -299,5 +299,5 @@ Term.init(obj); Conn.init(); Input.init(); - } + }; })(); diff --git a/html_orig/jssrc/wifi.js b/html_orig/jssrc/wifi.js index 5e442dd..ece92c7 100644 --- a/html_orig/jssrc/wifi.js +++ b/html_orig/jssrc/wifi.js @@ -1,19 +1,70 @@ -/** Wifi page */ -(function () { +(function(w) { var authStr = ['Open', 'WEP', 'WPA', 'WPA2', 'WPA/WPA2']; var curSSID; + // Get XX % for a slider input + function rangePt(inp) { + return Math.round(((inp.value / inp.max)*100)) + '%'; + } + + // Display selected STA SSID etc + function selectSta(name, password, ip) { + $('#sta_ssid').val(name); + $('#sta_password').val(password); + + $('#sta-nw').toggleClass('hidden', name.length == 0); + $('#sta-nw-nil').toggleClass('hidden', name.length > 0); + + $('#sta-nw .essid').html(e(name)); + var nopw = undef(password) || password.length == 0; + $('#sta-nw .x-passwd').html(e(password)); + $('#sta-nw .passwd').toggleClass('hidden', nopw); + $('#sta-nw .nopasswd').toggleClass('hidden', !nopw); + $('#sta-nw .ip').html(ip.length>0 ? tr('wifi.connected_ip_is')+ip : tr('wifi.not_conn')); + } + + function submitPskModal(e, open) { + var passwd = $('#conn-passwd').val(); + var ssid = $('#conn-ssid').val(); + + if (open || passwd.length) { + $('#sta_password').val(passwd); + $('#sta_ssid').val(ssid); + selectSta(ssid, passwd, ''); + } + + if (e) e.preventDefault(); + Modal.hide('#psk-modal'); + return false; + } + /** Update display for received response */ function onScan(resp, status) { + //var ap_json = { + // "result": { + // "inProgress": "0", + // "APs": [ + // {"essid": "Chlivek", "bssid": "88:f7:c7:52:b3:99", "rssi": "204", "enc": "4", "channel": "1"}, + // {"essid": "TyNikdy", "bssid": "5c:f4:ab:0d:f1:1b", "rssi": "164", "enc": "3", "channel": "1"}, + // ] + // } + //}; + if (status != 200) { // bad response rescan(5000); // wait 5sm then retry return; } - resp = jsp(resp); + try { + resp = JSON.parse(resp); + } catch (e) { + console.log(e); + rescan(5000); + return; + } - var done = resp && !bool(resp.result.inProgress) && (resp.result.APs.length > 0); + var done = !bool(resp.result.inProgress) && (resp.result.APs.length > 0); rescan(done ? 15000 : 1000); if (!done) return; // no redraw yet @@ -22,60 +73,71 @@ // remove old APs $('#ap-list .AP').remove(); - $list.toggle(done); - $('#ap-loader').toggle(!done); + $list.toggleClass('hidden', !done); + $('#ap-loader').toggleClass('hidden', done); // scan done resp.result.APs.sort(function (a, b) { - return b.rssi - a.rssi; - }).forEach(function (ap) { - ap.enc = parseInt(ap.enc); - - if (ap.enc > 4) return; // hide unsupported auths - - var item = document.createElement('div'); - - var $item = $(item) - .data('ssid', ap.essid) - .data('pwd', ap.enc != 0) - .addClass('AP'); - - // mark current SSID - if (ap.essid == curSSID) { - $item.addClass('selected'); + return b.rssi - a.rssi; + }).forEach(function (ap) { + ap.enc = parseInt(ap.enc); + + if (ap.enc > 4) return; // hide unsupported auths + WiFi.scan_url = '/cfg/wifi/scan'; + + var item = mk('div'); + + var $item = $(item) + .data('ssid', ap.essid) + .data('pwd', ap.enc) + .attr('tabindex', 0) + .addClass('AP'); + + // mark current SSID + if (ap.essid == curSSID) { + $item.addClass('selected'); + } + + var inner = mk('div'); + $(inner).addClass('inner') + .htmlAppend('
{0}
'.format(ap.rssi_perc)) + .htmlAppend('
{0}
'.format($.htmlEscape(ap.essid))) + .htmlAppend('
{0}
'.format(authStr[ap.enc])); + + $item.on('click', function () { + var $th = $(this); + + var ssid = $th.data('ssid'); + + $('#conn-ssid').val(ssid); + $('#conn-passwd').val(''); + + if (+$th.data('pwd')) { + // this AP needs a password + Modal.show('#psk-modal'); + $('#conn-passwd')[0].focus(); + } else { + //Modal.show('#reset-modal'); + submitPskModal(null, true); } + }); - var inner = document.createElement('div'); - $(inner).addClass('inner') - .htmlAppend('
{0}
'.format(ap.rssi_perc)) - .htmlAppend('
{0}
'.format($.htmlEscape(ap.essid))) - .htmlAppend('
{0}
'.format(authStr[ap.enc])); - - $item.on('click', function () { - var $th = $(this); - - // populate the form - $('#conn-essid').val($th.data('ssid')); - $('#conn-passwd').val(''); // clear - - if ($th.data('pwd')) { - // this AP needs a password - Modal.show('#psk-modal'); - } else { - Modal.show('#reset-modal'); - $('#conn-form').submit(); - } - }); + item.appendChild(inner); + $list[0].appendChild(item); + }); + } - item.appendChild(inner); - $list[0].appendChild(item); - }); + function startScanning() { + $('#ap-loader').removeClass('hidden'); + $('#ap-scan').addClass('hidden'); + $('#ap-loader .anim-dots').html('.'); + scanAPs(); } /** Ask the CGI what APs are visible (async) */ function scanAPs() { - $.get('http://'+_root+'/wifi/scan', onScan); + $.get('http://'+_root+w.scan_url, onScan); } function rescan(time) { @@ -83,46 +145,38 @@ } /** Set up the WiFi page */ - window.wifiInit = function (obj) { - //var ap_json = { - // "result": { - // "inProgress": "0", - // "APs": [ - // {"essid": "Chlivek", "bssid": "88:f7:c7:52:b3:99", "rssi": "204", "enc": "4", "channel": "1"}, - // {"essid": "TyNikdy", "bssid": "5c:f4:ab:0d:f1:1b", "rssi": "164", "enc": "3", "channel": "1"}, - // ] - // } - //}; - + function wifiInit(cfg) { // Hide what should be hidden in this mode - $('.x-hide-'+obj.mode).addClass('hidden'); - obj.mode = +obj.mode; - - // Channel writable only in AP mode - if (obj.mode != 2) $('#channel').attr('readonly', 1); - - curSSID = obj.staSSID; - - // add SSID to the opmode field - if (curSSID) { - var box = $('#opmodebox'); - box.html(box.html() + ' (' + curSSID + ')'); - } - - // hide IP if IP not received - if (!obj.staIP) $('.x-hide-noip').addClass('hidden'); - - // scan if not AP - if (obj.mode != 2) { - scanAPs(); - } + cfg.mode = +cfg.mode; + + $('#ap-noscan').toggleClass('hidden', cfg.mode != 2); + $('#ap-scan').toggleClass('hidden', cfg.mode == 2); + + // Update slider value displays + $('.Row.range').forEach(function(x) { + var inp = x.querySelector('input'); + var disp1 = x.querySelector('.x-disp1'); + var disp2 = x.querySelector('.x-disp2'); + var t = rangePt(inp); + $(disp1).html(t); + $(disp2).html(t); + $(inp).on('input', function() { + t = rangePt(inp); + $(disp1).html(t); + $(disp2).html(t); + }); + }); - $('#modeswitch').html([ - 'Client+AP AP only', - 'Client+AP', - 'Client only AP only' - ][obj.mode-1]); - }; + // Forget STA credentials + $('#forget-sta').on('click', function() { + selectSta('', '', ''); + return false; + }); + selectSta(cfg.sta_ssid, cfg.sta_password, cfg.sta_active_ip); + curSSID = cfg.sta_active_ssid; + } -})(); + w.init = wifiInit; + w.startScanning = startScanning; +})(window.WiFi = {}); diff --git a/html_orig/lang/en.php b/html_orig/lang/en.php index 269e678..c3d6b69 100644 --- a/html_orig/lang/en.php +++ b/html_orig/lang/en.php @@ -12,6 +12,8 @@ return [ 'menu.cfg_admin' => 'Reset & Restore', 'menu.cfg_wifi_conn' => 'Connecting to External Network', + 'menu.settings' => 'Settings', + 'title.term' => 'Terminal', 'net.ap' => 'DHCP Server (AP)', diff --git a/html_orig/packjs.sh b/html_orig/packjs.sh index f1652fd..6df5917 100755 --- a/html_orig/packjs.sh +++ b/html_orig/packjs.sh @@ -7,4 +7,6 @@ cat jssrc/chibi.js \ jssrc/modal.js \ jssrc/notif.js \ jssrc/appcommon.js \ + jssrc/lang.js \ + jssrc/wifi.js \ jssrc/term.js > js/app.js diff --git a/html_orig/pages/_cfg_menu.php b/html_orig/pages/_cfg_menu.php index c96af23..570c623 100644 --- a/html_orig/pages/_cfg_menu.php +++ b/html_orig/pages/_cfg_menu.php @@ -1,5 +1,6 @@ diff --git a/html_orig/pages/cfg_wifi.php b/html_orig/pages/cfg_wifi.php index 7b8a495..cd8fe1c 100644 --- a/html_orig/pages/cfg_wifi.php +++ b/html_orig/pages/cfg_wifi.php @@ -74,7 +74,7 @@
-
+
@@ -97,186 +97,12 @@
diff --git a/html_orig/pages/cfg_wifi_conn.php b/html_orig/pages/cfg_wifi_conn.php index a5cab5a..33c8b64 100755 --- a/html_orig/pages/cfg_wifi_conn.php +++ b/html_orig/pages/cfg_wifi_conn.php @@ -31,7 +31,7 @@ $("#status").html(msg); if (done) { -// $('#backbtn').removeClass('hidden'); +// $('#backbtn').removeClass('hidden'); $('.anim-dots').addClass('hidden'); } else { window.setTimeout(getStatus, 1000); @@ -42,7 +42,7 @@ abortTmeo = setTimeout(function () { xhr.abort(); $("#status").html(); -// $('#backbtn').removeClass('hidden'); +// $('#backbtn').removeClass('hidden'); $('.anim-dots').addClass('hidden'); }, 4000); diff --git a/html_orig/pages/term.php b/html_orig/pages/term.php index 1dbf074..5f96a80 100644 --- a/html_orig/pages/term.php +++ b/html_orig/pages/term.php @@ -22,7 +22,7 @@ diff --git a/html_orig/sass/layout/_box.scss b/html_orig/sass/layout/_box.scss index e449065..a61c850 100755 --- a/html_orig/sass/layout/_box.scss +++ b/html_orig/sass/layout/_box.scss @@ -5,6 +5,13 @@ margin-top: dist(0); padding: dist(-1) dist(0); + // clear floats + &::after { + content: ''; + display: block; + clear: both + } + @include media($phone) { margin-top: dist(-1); } @@ -22,6 +29,10 @@ margin-bottom: 0 !important; } + p:last-child { + margin-bottom: 0.5em; + } + border-radius: 3px; background-color: rgba(white, .07); box-shadow: 0 0 4px black; diff --git a/html_orig/sass/pages/_about.scss b/html_orig/sass/pages/_about.scss index 9eba5e8..d87129f 100644 --- a/html_orig/sass/pages/_about.scss +++ b/html_orig/sass/pages/_about.scss @@ -14,6 +14,7 @@ // mobile friendly #logo2 { max-width: 100%; + margin: 1rem; } td { diff --git a/html_orig/sass/pages/_term.scss b/html_orig/sass/pages/_term.scss index 34b332c..17634ba 100755 --- a/html_orig/sass/pages/_term.scss +++ b/html_orig/sass/pages/_term.scss @@ -34,10 +34,11 @@ body.term { button { margin: 0 3px; - padding: 10px 0; - width: 18%; - max-width: 65px; - min-width: initial; + padding: 8px 5px; + //width: 18%; + min-width: 65px; + //max-width: 65px; + //min-width: initial; cursor: pointer; font-weight: bold; } diff --git a/html_orig/sass/pages/_wifi.scss b/html_orig/sass/pages/_wifi.scss index b2d16e8..90a758d 100755 --- a/html_orig/sass/pages/_wifi.scss +++ b/html_orig/sass/pages/_wifi.scss @@ -18,6 +18,7 @@ border-radius: 5px; padding: dist(-2); margin-bottom: dist(-2); + margin-top: dist(-2); } #ap-noscan { diff --git a/include/helpers.h b/include/helpers.h index 37eeba8..39a756b 100644 --- a/include/helpers.h +++ b/include/helpers.h @@ -19,4 +19,7 @@ */ #define GET_ARG(key) (httpdFindArg(connData->getArgs, key, buff, sizeof(buff)) > 0) +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + #endif //ESP_VT100_FIRMWARE_HELPERS_H diff --git a/user/cgi_appcfg.c b/user/cgi_appcfg.c index 0c3fe50..e766139 100644 --- a/user/cgi_appcfg.c +++ b/user/cgi_appcfg.c @@ -8,13 +8,14 @@ Cgi/template routines for configuring non-wifi settings #include "screen.h" #include "helpers.h" -#define SET_REDIR_SUC "/cfg/term" +#define SET_REDIR_SUC "/cfg/app" #define SET_REDIR_ERR SET_REDIR_SUC"?err=" /** * Universal CGI endpoint to set Terminal params. */ -httpd_cgi_state ICACHE_FLASH_ATTR cgiAppCfgSet(HttpdConnData *connData) +httpd_cgi_state ICACHE_FLASH_ATTR +cgiAppCfgSetParams(HttpdConnData *connData) { char buff[50]; @@ -49,7 +50,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiAppCfgSet(HttpdConnData *connData) redir_url += sprintf(redir_url, "term_width,"); } } else { - warn("Missing height arg", buff); + warn("Missing height arg!"); // this wont happen normally when the form is used redir_url += sprintf(redir_url, "term_width,term_height,"); } @@ -115,6 +116,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiAppCfgSet(HttpdConnData *connData) // All was OK info("Set app params - success, saving..."); + terminal_apply_settings(); persist_store(); httpdRedirect(connData, SET_REDIR_SUC); @@ -127,7 +129,8 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiAppCfgSet(HttpdConnData *connData) } -httpd_cgi_state ICACHE_FLASH_ATTR tplAppCfg(HttpdConnData *connData, char *token, void **arg) +httpd_cgi_state ICACHE_FLASH_ATTR +tplAppCfg(HttpdConnData *connData, char *token, void **arg) { #define BUFLEN 100 char buff[BUFLEN]; @@ -149,7 +152,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplAppCfg(HttpdConnData *connData, char *token sprintf(buff, "%d", termconf->default_bg); } else if (streq(token, "default_fg")) { - sprintf(buff, "%d", termconf->default_bg); + sprintf(buff, "%d", termconf->default_fg); } else if (streq(token, "term_title")) { strncpy_safe(buff, termconf->title, BUFLEN); diff --git a/user/cgi_appcfg.h b/user/cgi_appcfg.h index fe4fa79..823a0f1 100644 --- a/user/cgi_appcfg.h +++ b/user/cgi_appcfg.h @@ -3,7 +3,7 @@ #include "httpd.h" -httpd_cgi_state cgiAppCfgSet(HttpdConnData *connData); +httpd_cgi_state cgiAppCfgSetParams(HttpdConnData *connData); httpd_cgi_state tplAppCfg(HttpdConnData *connData, char *token, void **arg); #endif diff --git a/user/cgi_main.c b/user/cgi_main.c index 997ebef..70a5ec7 100644 --- a/user/cgi_main.c +++ b/user/cgi_main.c @@ -5,9 +5,7 @@ #include "cgi_main.h" #include "screen.h" #include "user_main.h" - -#define STR_HELPER(x) #x -#define STR(x) STR_HELPER(x) +#include "helpers.h" /** * Main page template substitution @@ -28,7 +26,33 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplScreen(HttpdConnData *connData, char *token const int bufsiz = 512; char buff[bufsiz]; - if (streq(token, "screenData")) { + if (streq(token, "term_title")) { + httpdSend(connData, termconf->title, -1); + } + else if (streq(token, "btn1")) { + httpdSend(connData, termconf->btn1, -1); + } + else if (streq(token, "btn2")) { + httpdSend(connData, termconf->btn2, -1); + } + else if (streq(token, "btn3")) { + httpdSend(connData, termconf->btn3, -1); + } + else if (streq(token, "btn4")) { + httpdSend(connData, termconf->btn4, -1); + } + else if (streq(token, "btn5")) { + httpdSend(connData, termconf->btn5, -1); + } + else if (streq(token, "default_bg")) { + sprintf(buff, "%d", termconf->default_bg); + httpdSend(connData, buff, -1); + } + else if (streq(token, "default_fg")) { + sprintf(buff, "%d", termconf->default_fg); + httpdSend(connData, buff, -1); + } + else if (streq(token, "screenData")) { httpd_cgi_state cont = screenSerializeToBuffer(buff, bufsiz, arg); httpdSend(connData, buff, -1); return cont; diff --git a/user/cgi_network.c b/user/cgi_network.c index 5067c49..ad4ac21 100644 --- a/user/cgi_network.c +++ b/user/cgi_network.c @@ -231,10 +231,10 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplNetwork(HttpdConnData *connData, char *toke else if (streq(token, "sta_addr_ip")) { sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->sta_addr.ip.addr)); } - else if (streq(token, "ap_addr_mask")) { + else if (streq(token, "sta_addr_mask")) { sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->sta_addr.netmask.addr)); } - else if (streq(token, "ap_addr_gw")) { + else if (streq(token, "sta_addr_gw")) { sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->sta_addr.gw.addr)); } else if (streq(token, "sta_mac")) { diff --git a/user/cgi_persist.c b/user/cgi_persist.c new file mode 100644 index 0000000..7f2faac --- /dev/null +++ b/user/cgi_persist.c @@ -0,0 +1,74 @@ +/* +Cgi/template routines for configuring non-wifi settings +*/ + +#include +#include "cgi_persist.h" +#include "persist.h" +#include "helpers.h" + +#define SET_REDIR_SUC "/cfg/admin" + +static bool ICACHE_FLASH_ATTR +verify_admin_pw(const char *pw) +{ + // This is not really for security, but to prevent someone who + // shouldn't touch those settings from fucking it up. + return streq(pw, STR(ADMIN_PASSWORD)); // the PW comes from the makefile +} + +httpd_cgi_state ICACHE_FLASH_ATTR +cgiPersistWriteDefaults(HttpdConnData *connData) +{ + char buff[50]; + + if (connData->conn == NULL) { + //Connection aborted. Clean up. + return HTTPD_CGI_DONE; + } + + // width and height must always go together so we can do max size validation + if (GET_ARG("pw")) { + dbg("Entered password for admin: %s", buff); + if (verify_admin_pw(buff)) { + dbg("pw is OK"); + + persist_set_as_default(); + + httpdRedirect(connData, SET_REDIR_SUC); + return HTTPD_CGI_DONE; + } + // if pw failed, show the same error as if it's wrong + } + + httpdRedirect(connData, SET_REDIR_SUC "?err=Password"); // this will show in the "validation errors" box + return HTTPD_CGI_DONE; +} + + +httpd_cgi_state ICACHE_FLASH_ATTR +cgiPersistRestoreDefaults(HttpdConnData *connData) +{ + if (connData->conn == NULL) { + //Connection aborted. Clean up. + return HTTPD_CGI_DONE; + } + + persist_restore_default(); + httpdRedirect(connData, SET_REDIR_SUC); + return HTTPD_CGI_DONE; +} + +httpd_cgi_state ICACHE_FLASH_ATTR +cgiPersistRestoreHard(HttpdConnData *connData) +{ + if (connData->conn == NULL) { + //Connection aborted. Clean up. + return HTTPD_CGI_DONE; + } + + persist_restore_hard_default(); + + httpdRedirect(connData, SET_REDIR_SUC); + return HTTPD_CGI_DONE; +} diff --git a/user/cgi_persist.h b/user/cgi_persist.h new file mode 100644 index 0000000..4875407 --- /dev/null +++ b/user/cgi_persist.h @@ -0,0 +1,10 @@ +#ifndef CGIPERSIST_H +#define CGIPERSIST_H + +#include "httpd.h" + +httpd_cgi_state cgiPersistWriteDefaults(HttpdConnData *connData); +httpd_cgi_state cgiPersistRestoreDefaults(HttpdConnData *connData); +httpd_cgi_state cgiPersistRestoreHard(HttpdConnData *connData); + +#endif diff --git a/user/cgi_wifi.c b/user/cgi_wifi.c index 2a45e2a..653f928 100644 --- a/user/cgi_wifi.c +++ b/user/cgi_wifi.c @@ -571,10 +571,12 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token, else { struct ip_info info; wifi_get_ip_info(STATION_IF, &info); - sprintf(buff, "ip: "IPSTR", mask: "IPSTR", gw: "IPSTR, - GOOD_IP2STR(info.ip.addr), - GOOD_IP2STR(info.netmask.addr), - GOOD_IP2STR(info.gw.addr)); + sprintf(buff, IPSTR, GOOD_IP2STR(info.ip.addr)); + +// sprintf(buff, "ip: "IPSTR", mask: "IPSTR", gw: "IPSTR, +// GOOD_IP2STR(info.ip.addr), +// GOOD_IP2STR(info.netmask.addr), +// GOOD_IP2STR(info.gw.addr)); } } diff --git a/user/persist.c b/user/persist.c index c7ec05c..c1b7541 100644 --- a/user/persist.c +++ b/user/persist.c @@ -11,9 +11,6 @@ PersistBlock persist; #define PERSIST_SECTOR_ID 0x3D -// This is used to force-erase the config area (when it's changed) -#define CHECKSUM_SALT 0x02 - //region Persist and restore individual modules static void ICACHE_FLASH_ATTR @@ -81,6 +78,11 @@ persist_load(void) { info("[Persist] Loading stored settings from FLASH..."); + dbg("sizeof(AppConfigBundle) = %d bytes", sizeof(AppConfigBundle)); + dbg("sizeof(PersistBlock) = %d bytes", sizeof(PersistBlock)); + dbg("sizeof(WiFiConfigBundle) = %d bytes", sizeof(WiFiConfigBundle)); + dbg("sizeof(TerminalConfigBundle) = %d bytes", sizeof(TerminalConfigBundle)); + bool hard_reset = false; // Try to load @@ -90,15 +92,15 @@ persist_load(void) if (hard_reset || (compute_checksum(&persist.defaults) != persist.defaults.checksum) || (compute_checksum(&persist.current) != persist.current.checksum)) { - dbg("[Persist] Checksum verification: FAILED"); + error("[Persist] Checksum verification: FAILED"); hard_reset = true; } else { - dbg("[Persist] Checksum verification: PASSED"); + info("[Persist] Checksum verification: PASSED"); } if (hard_reset) { persist_restore_hard_default(); - // this also stores them to flash and applies to modues + // this also stores them to flash and applies to modules } else { apply_live_settings(); } @@ -161,8 +163,8 @@ persist_set_as_default(void) { info("[Persist] Storing live settings as defaults.."); + // current -> defaults memcpy(&persist.defaults, &persist.current, sizeof(AppConfigBundle)); - persist_store(); info("[Persist] Default settings updated."); diff --git a/user/persist.h b/user/persist.h index d9bc487..be334e4 100644 --- a/user/persist.h +++ b/user/persist.h @@ -13,15 +13,31 @@ #include "wifimgr.h" #include "screen.h" +// Changing this could be used to force-erase the config area +// after a firmware upgrade +#define CHECKSUM_SALT 0x5F5F5F5F + +/** Struct for current or default settings */ typedef struct { WiFiConfigBundle wificonf; TerminalConfigBundle termconf; - // ... - // other settings here - // ... + + // --- Space for future settings --- + // Original size: 1024 + // + // The size must be appropriately reduced each time something is added, + // and boolean flags defaulting to 0 should be used to detect unpopulated + // sections that must be restored to defaults on load. + // + // This ensures user settings are not lost each time they upgrade the firmware, + // which would lead to a checksum mismatch if the structure was changed and + // it grew to a different memory area. + uint8_t filler[1024]; + uint32_t checksum; // computed before write and tested on load. If it doesn't match, values are reset to hard defaults. } AppConfigBundle; +/** This is the entire data block stored in FLASH */ typedef struct { AppConfigBundle defaults; // defaults are stored here AppConfigBundle current; // active settings adjusted by the user diff --git a/user/routes.c b/user/routes.c index 2f3dc68..ee2fcd7 100644 --- a/user/routes.c +++ b/user/routes.c @@ -10,6 +10,9 @@ #include "cgi_ping.h" #include "cgi_main.h" #include "cgi_sockets.h" +#include "cgi_network.h" +#include "cgi_appcfg.h" +#include "cgi_persist.h" #define WIFI_PROTECT 0 #define WIFI_AUTH_NAME "wifi" @@ -26,28 +29,38 @@ HttpdBuiltInUrl routes[] = { // --- Web pages --- ROUTE_TPL_FILE("/", tplScreen, "/term.tpl"), - ROUTE_TPL_FILE("/about", tplAbout, "/about.tpl"), - ROUTE_FILE("/help", "/help.tpl"), + ROUTE_TPL_FILE("/about/?", tplAbout, "/about.tpl"), + ROUTE_FILE("/help/?", "/help.tpl"), // --- Sockets --- ROUTE_WS(URL_WS_UPDATE, updateSockConnect), // --- System control --- - ROUTE_CGI("/system/reset", cgiResetDevice), - ROUTE_CGI("/system/ping", cgiPing), + ROUTE_CGI("/system/reset/?", cgiResetDevice), + ROUTE_CGI("/system/ping/?", cgiPing), - // --- WiFi config --- + // --- WiFi config --- (TODO make this conditional and configurable) #if WIFI_PROTECT ROUTE_AUTH("/wifi*", wifiPassFn), #endif - ROUTE_REDIRECT("/wifi/", "/wifi"), - ROUTE_TPL_FILE("/wifi", tplWlan, "/wifi.tpl"), + ROUTE_REDIRECT("/cfg/?", "/cfg/wifi"), - ROUTE_CGI("/wifi/scan", cgiWiFiScan), - ROUTE_CGI("/wifi/connect", cgiWiFiConnect), - ROUTE_CGI("/wifi/connstatus", cgiWiFiConnStatus), - ROUTE_FILE("/wifi/connecting", "/wifi_conn.tpl"), - ROUTE_CGI("/wifi/set", cgiWiFiSetParams), + ROUTE_TPL_FILE("/cfg/wifi/?", tplWlan, "/cfg_wifi.tpl"), + ROUTE_FILE("/cfg/wifi/connecting/?", "/cfg_wifi_conn.tpl"), + ROUTE_CGI("/cfg/wifi/scan", cgiWiFiScan), + ROUTE_CGI("/cfg/wifi/connstatus", cgiWiFiConnStatus), + ROUTE_CGI("/cfg/wifi/set", cgiWiFiSetParams), + + ROUTE_TPL_FILE("/cfg/network/?", tplNetwork, "/cfg_network.tpl"), + ROUTE_CGI("/cfg/network/set", cgiNetworkSetParams), + + ROUTE_TPL_FILE("/cfg/app/?", tplAppCfg, "/cfg_app.tpl"), + ROUTE_CGI("/cfg/app/set", cgiAppCfgSetParams), + + ROUTE_FILE("/cfg/admin/?", "/cfg_admin.tpl"), + ROUTE_CGI("/cfg/admin/write_defaults", cgiPersistWriteDefaults), + ROUTE_CGI("/cfg/admin/restore_defaults", cgiPersistRestoreDefaults), + ROUTE_CGI("/cfg/admin/restore_hard", cgiPersistRestoreHard), ROUTE_FILESYSTEM(), ROUTE_END(), diff --git a/user/screen.c b/user/screen.c index e2e07f1..ce9075d 100644 --- a/user/screen.c +++ b/user/screen.c @@ -17,7 +17,7 @@ void terminal_restore_defaults(void) termconf->default_fg = 7; termconf->width = 26; termconf->height = 10; - sprintf(termconf->title, "ESP8266 Wireless Terminal"); + sprintf(termconf->title, "ESPTerm"); sprintf(termconf->btn1, "1"); sprintf(termconf->btn2, "2"); sprintf(termconf->btn3, "3"); diff --git a/user/wifimgr.c b/user/wifimgr.c index 3e9bc82..5a4b132 100644 --- a/user/wifimgr.c +++ b/user/wifimgr.c @@ -43,6 +43,11 @@ wifimgr_restore_defaults(void) IP4_ADDR(&wificonf->sta_addr.ip, 192, 168, 0, (mac[5] == 1 ? 2 : mac[5])); // avoid being the same as "default gw" IP4_ADDR(&wificonf->sta_addr.netmask, 255, 255, 255, 0); IP4_ADDR(&wificonf->sta_addr.gw, 192, 168, 0, 1); + + // DEBUG ONLY - TODO remove for release + wificonf->opmode = STATION_MODE; + sprintf((char*)wificonf->sta_ssid, "Chlivek"); + sprintf((char*)wificonf->sta_password, "prase chrochta"); } static void ICACHE_FLASH_ATTR