diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 04f3cc5c04..2f0d179eba 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -3,7 +3,7 @@ import esphome.config_validation as cv from esphome.components import web_server_base from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID from esphome.const import ( - CONF_CSS_URL, CONF_ID, CONF_JS_URL, CONF_PORT, + CONF_CSS_INCLUDE, CONF_CSS_URL, CONF_ID, CONF_JS_INCLUDE, CONF_JS_URL, CONF_PORT, CONF_AUTH, CONF_USERNAME, CONF_PASSWORD) from esphome.core import coroutine_with_priority @@ -16,7 +16,9 @@ CONFIG_SCHEMA = cv.Schema({ cv.GenerateID(): cv.declare_id(WebServer), cv.Optional(CONF_PORT, default=80): cv.port, cv.Optional(CONF_CSS_URL, default="https://esphome.io/_static/webserver-v1.min.css"): cv.string, + cv.Optional(CONF_CSS_INCLUDE): cv.file_, cv.Optional(CONF_JS_URL, default="https://esphome.io/_static/webserver-v1.min.js"): cv.string, + cv.Optional(CONF_JS_INCLUDE): cv.file_, cv.Optional(CONF_AUTH): cv.Schema({ cv.Required(CONF_USERNAME): cv.string_strict, cv.Required(CONF_PASSWORD): cv.string_strict, @@ -39,3 +41,11 @@ def to_code(config): if CONF_AUTH in config: cg.add(var.set_username(config[CONF_AUTH][CONF_USERNAME])) cg.add(var.set_password(config[CONF_AUTH][CONF_PASSWORD])) + if CONF_CSS_INCLUDE in config: + cg.add_define('WEBSERVER_CSS_INCLUDE') + with open(config[CONF_CSS_INCLUDE], "r") as myfile: + cg.add(var.set_css_include(myfile.read())) + if CONF_JS_INCLUDE in config: + cg.add_define('WEBSERVER_JS_INCLUDE') + with open(config[CONF_JS_INCLUDE], "r") as myfile: + cg.add(var.set_js_include(myfile.read())) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index fcd83297e2..c0708b763f 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -60,7 +60,9 @@ UrlMatch match_url(const std::string &url, bool only_domain = false) { } void WebServer::set_css_url(const char *css_url) { this->css_url_ = css_url; } +void WebServer::set_css_include(const char *css_include) { this->css_include_ = css_include; } void WebServer::set_js_url(const char *js_url) { this->js_url_ = js_url; } +void WebServer::set_js_include(const char *js_include) { this->js_include_ = js_include; } void WebServer::setup() { ESP_LOGCONFIG(TAG, "Setting up web server..."); @@ -133,9 +135,16 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { std::string title = App.get_name() + " Web Server"; stream->print(F("")); stream->print(title.c_str()); - stream->print(F("print(this->css_url_); - stream->print(F("\">

")); + stream->print(F("")); +#ifdef WEBSERVER_CSS_INCLUDE + stream->print(F("")); +#endif + if (strlen(this->css_url_) > 0) { + stream->print(F("print(this->css_url_); + stream->print(F("\">")); + } + stream->print(F("

")); stream->print(title.c_str()); stream->print(F("

States

")); // All content is controlled and created by user - so allowing all origins is fine here. @@ -175,14 +184,44 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { "REST API documentation.

" "

OTA Update

" - "

Debug Log

"
-                  ""));
+                  "

Debug Log

"));
+#ifdef WEBSERVER_JS_INCLUDE
+  if (this->js_include_ != nullptr) {
+    stream->print(F(""));
+  }
+#endif
+  if (strlen(this->js_url_) > 0) {
+    stream->print(F(""));
+  }
+  stream->print(F(""));
 
   request->send(stream);
 }
 
+#ifdef WEBSERVER_CSS_INCLUDE
+void WebServer::handle_css_request(AsyncWebServerRequest *request) {
+  AsyncResponseStream *stream = request->beginResponseStream("text/css");
+  if (this->css_include_ != nullptr) {
+    stream->print(this->css_include_);
+  }
+
+  request->send(stream);
+}
+#endif
+
+#ifdef WEBSERVER_JS_INCLUDE
+void WebServer::handle_js_request(AsyncWebServerRequest *request) {
+  AsyncResponseStream *stream = request->beginResponseStream("text/javascript");
+  if (this->js_include_ != nullptr) {
+    stream->print(this->js_include_);
+  }
+
+  request->send(stream);
+}
+#endif
+
 #ifdef USE_SENSOR
 void WebServer::on_sensor_update(sensor::Sensor *obj, float state) {
   this->events_.send(this->sensor_json(obj, state).c_str(), "state");
@@ -460,6 +499,16 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
   if (request->url() == "/")
     return true;
 
+#ifdef WEBSERVER_CSS_INCLUDE
+  if (request->url() == "/0.css")
+    return true;
+#endif
+
+#ifdef WEBSERVER_JS_INCLUDE
+  if (request->url() == "/0.js")
+    return true;
+#endif
+
   UrlMatch match = match_url(request->url().c_str(), true);
   if (!match.valid)
     return false;
@@ -505,6 +554,20 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
     return;
   }
 
+#ifdef WEBSERVER_CSS_INCLUDE
+  if (request->url() == "/0.css") {
+    this->handle_css_request(request);
+    return;
+  }
+#endif
+
+#ifdef WEBSERVER_JS_INCLUDE
+  if (request->url() == "/0.js") {
+    this->handle_js_request(request);
+    return;
+  }
+#endif
+
   UrlMatch match = match_url(request->url().c_str());
 #ifdef USE_SENSOR
   if (match.domain == "sensor") {
diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h
index 4dca8200cc..def1cac0ea 100644
--- a/esphome/components/web_server/web_server.h
+++ b/esphome/components/web_server/web_server.h
@@ -41,6 +41,12 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
    */
   void set_css_url(const char *css_url);
 
+  /** Set local path to the script that's embedded in the index page. Defaults to
+   *
+   * @param css_include Local path to web server script.
+   */
+  void set_css_include(const char *css_include);
+
   /** Set the URL to the script that's embedded in the index page. Defaults to
    * https://esphome.io/_static/webserver-v1.min.js
    *
@@ -48,6 +54,12 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
    */
   void set_js_url(const char *js_url);
 
+  /** Set local path to the script that's embedded in the index page. Defaults to
+   *
+   * @param js_include Local path to web server script.
+   */
+  void set_js_include(const char *js_include);
+
   // ========== INTERNAL METHODS ==========
   // (In most use cases you won't need these)
   /// Setup the internal web server and register handlers.
@@ -61,6 +73,16 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
   /// Handle an index request under '/'.
   void handle_index_request(AsyncWebServerRequest *request);
 
+#ifdef WEBSERVER_CSS_INCLUDE
+  /// Handle included css request under '/0.css'.
+  void handle_css_request(AsyncWebServerRequest *request);
+#endif
+
+#ifdef WEBSERVER_JS_INCLUDE
+  /// Handle included js request under '/0.js'.
+  void handle_js_request(AsyncWebServerRequest *request);
+#endif
+
   bool using_auth() { return username_ != nullptr && password_ != nullptr; }
 
 #ifdef USE_SENSOR
@@ -135,7 +157,9 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
   const char *username_{nullptr};
   const char *password_{nullptr};
   const char *css_url_{nullptr};
+  const char *css_include_{nullptr};
   const char *js_url_{nullptr};
+  const char *js_include_{nullptr};
 };
 
 }  // namespace web_server
diff --git a/esphome/const.py b/esphome/const.py
index 0c5f286f30..308dd2683f 100644
--- a/esphome/const.py
+++ b/esphome/const.py
@@ -103,6 +103,7 @@ CONF_COOL_ACTION = 'cool_action'
 CONF_COUNT_MODE = 'count_mode'
 CONF_CRON = 'cron'
 CONF_CS_PIN = 'cs_pin'
+CONF_CSS_INCLUDE = 'css_include'
 CONF_CSS_URL = 'css_url'
 CONF_CURRENT = 'current'
 CONF_CURRENT_OPERATION = 'current_operation'
@@ -210,6 +211,7 @@ CONF_INVALID_COOLDOWN = 'invalid_cooldown'
 CONF_INVERT = 'invert'
 CONF_INVERTED = 'inverted'
 CONF_IP_ADDRESS = 'ip_address'
+CONF_JS_INCLUDE = 'js_include'
 CONF_JS_URL = 'js_url'
 CONF_JVC = 'jvc'
 CONF_KEEP_ON_TIME = 'keep_on_time'
NameStateActions