You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by as...@apache.org on 2021/02/17 09:46:37 UTC

[airflow] branch master updated: Add Airflow UI instance_name configuration option (#10162)

This is an automated email from the ASF dual-hosted git repository.

ash pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/master by this push:
     new 7d60bbf  Add Airflow UI instance_name configuration option (#10162)
7d60bbf is described below

commit 7d60bbfd81f48214d8a09cc8edf10f4266c57892
Author: Patrick Cando <32...@users.noreply.github.com>
AuthorDate: Wed Feb 17 09:46:23 2021 +0000

    Add Airflow UI instance_name configuration option (#10162)
    
    Co-authored-by: Ash Berlin-Taylor <as...@apache.org>
---
 airflow/config_templates/config.yml                |   7 +++
 airflow/config_templates/default_airflow.cfg       |   3 ++
 airflow/www/app.py                                 |   4 +-
 airflow/www/templates/airflow/chart.html           |   2 +-
 airflow/www/templates/airflow/code.html            |   2 +-
 airflow/www/templates/airflow/dag.html             |   2 +-
 airflow/www/templates/airflow/dag_code.html        |   2 +-
 airflow/www/templates/airflow/dag_details.html     |   2 +-
 airflow/www/templates/airflow/dags.html            |   8 +--
 airflow/www/templates/airflow/duration_chart.html  |   2 +-
 airflow/www/templates/airflow/gantt.html           |   2 +-
 airflow/www/templates/airflow/graph.html           |   2 +-
 airflow/www/templates/airflow/main.html            |   8 +++
 airflow/www/templates/airflow/task.html            |   2 +-
 airflow/www/templates/airflow/ti_code.html         |   2 +-
 airflow/www/templates/airflow/ti_log.html          |   2 +-
 airflow/www/templates/airflow/tree.html            |   2 +-
 airflow/www/templates/airflow/xcom.html            |   2 +-
 airflow/www/views.py                               |   3 ++
 .../howto/customize-dag-ui-page-instance-name.rst  |  55 +++++++++++++++++++++
 docs/apache-airflow/howto/index.rst                |   1 +
 .../default_instance_name_configuration.png        | Bin 0 -> 154888 bytes
 .../example_instance_name_configuration.png        | Bin 0 -> 154915 bytes
 tests/www/test_views.py                            |  15 ++++++
 24 files changed, 109 insertions(+), 21 deletions(-)

diff --git a/airflow/config_templates/config.yml b/airflow/config_templates/config.yml
index 64b852c..c3b2173 100644
--- a/airflow/config_templates/config.yml
+++ b/airflow/config_templates/config.yml
@@ -1236,6 +1236,13 @@
       type: integer
       example: ~
       default: "43200"
+    - name: instance_name
+      description: |
+        Sets a custom page title for the DAGs overview page and site title for all pages
+      version_added: 2.1.0
+      type: string
+      example: ~
+      default:
 
 - name: email
   description: |
diff --git a/airflow/config_templates/default_airflow.cfg b/airflow/config_templates/default_airflow.cfg
index 48d4111..88c1111 100644
--- a/airflow/config_templates/default_airflow.cfg
+++ b/airflow/config_templates/default_airflow.cfg
@@ -613,6 +613,9 @@ update_fab_perms = True
 # ``session_lifetime_minutes`` of non-activity
 session_lifetime_minutes = 43200
 
+# Sets a custom page title for the DAGs overview page and site title for all pages
+# instance_name =
+
 [email]
 
 # Configuration email backend and whether to
diff --git a/airflow/www/app.py b/airflow/www/app.py
index aa9b5ed..f33edc5 100644
--- a/airflow/www/app.py
+++ b/airflow/www/app.py
@@ -66,14 +66,14 @@ def sync_appbuilder_roles(flask_app):
         security_manager.sync_resource_permissions()
 
 
-def create_app(config=None, testing=False, app_name="Airflow"):
+def create_app(config=None, testing=False):
     """Create a new instance of Airflow WWW app"""
     flask_app = Flask(__name__)
     flask_app.secret_key = conf.get('webserver', 'SECRET_KEY')
 
     flask_app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(minutes=settings.get_session_lifetime_config())
     flask_app.config.from_pyfile(settings.WEBSERVER_CONFIG, silent=True)
-    flask_app.config['APP_NAME'] = app_name
+    flask_app.config['APP_NAME'] = conf.get(section="webserver", key="instance_name", fallback="Airflow")
     flask_app.config['TESTING'] = testing
     flask_app.config['SQLALCHEMY_DATABASE_URI'] = conf.get('core', 'SQL_ALCHEMY_CONN')
     flask_app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
diff --git a/airflow/www/templates/airflow/chart.html b/airflow/www/templates/airflow/chart.html
index 0853dcf..3f34023 100644
--- a/airflow/www/templates/airflow/chart.html
+++ b/airflow/www/templates/airflow/chart.html
@@ -19,7 +19,7 @@
 
 {% extends "airflow/dag.html" %}
 
-{% block page_title %}{{ dag.dag_id }} - {{ tab_title }} - Airflow{% endblock %}
+{% block page_title %}{{ dag.dag_id }} - {{ tab_title }} - {{ appbuilder.app_name }}{% endblock %}
 
 {% block head_css %}
   {{ super() }}
diff --git a/airflow/www/templates/airflow/code.html b/airflow/www/templates/airflow/code.html
index 8ea8d4a..b1eaa93 100644
--- a/airflow/www/templates/airflow/code.html
+++ b/airflow/www/templates/airflow/code.html
@@ -19,7 +19,7 @@
 
 {% extends base_template %}
 
-{% block page_title %}{{ dag.dag_id }} - Code - Airflow{% endblock %}
+{% block page_title %}{{ dag.dag_id }} - Code - {{ appbuilder.app_name }}{% endblock %}
 
 {% block content %}
   {{ super() }}
diff --git a/airflow/www/templates/airflow/dag.html b/airflow/www/templates/airflow/dag.html
index 72c4378..0f0cbfd 100644
--- a/airflow/www/templates/airflow/dag.html
+++ b/airflow/www/templates/airflow/dag.html
@@ -20,7 +20,7 @@
 {% extends base_template %}
 {% from 'appbuilder/dag_docs.html' import dag_docs %}
 
-{% block page_title %}{{ dag.dag_id }} - Airflow{% endblock %}
+{% block page_title %}{{ dag.dag_id }} - {{ appbuilder.app_name }}{% endblock %}
 
 {% block head_css %}
   {{ super() }}
diff --git a/airflow/www/templates/airflow/dag_code.html b/airflow/www/templates/airflow/dag_code.html
index 1a5afc2..8e3f5ee 100644
--- a/airflow/www/templates/airflow/dag_code.html
+++ b/airflow/www/templates/airflow/dag_code.html
@@ -19,7 +19,7 @@
 
 {% extends "airflow/dag.html" %}
 
-{% block page_title %}{{ dag.dag_id }} - Code - Airflow{% endblock %}
+{% block page_title %}{{ dag.dag_id }} - Code - {{ appbuilder.app_name }}{% endblock %}
 
 {% block head_meta %}
   {{ super() }}
diff --git a/airflow/www/templates/airflow/dag_details.html b/airflow/www/templates/airflow/dag_details.html
index e428670..096379b 100644
--- a/airflow/www/templates/airflow/dag_details.html
+++ b/airflow/www/templates/airflow/dag_details.html
@@ -19,7 +19,7 @@
 
 {% extends "airflow/dag.html" %}
 
-{% block page_title %}{{ dag.dag_id }} - {{ title }} - Airflow{% endblock %}
+{% block page_title %}{{ dag.dag_id }} - DAG Details - {{ appbuilder.app_name }}{% endblock %}
 
 {% block content %}
   {{ super() }}
diff --git a/airflow/www/templates/airflow/dags.html b/airflow/www/templates/airflow/dags.html
index 1f67972..cbdb2d7 100644
--- a/airflow/www/templates/airflow/dags.html
+++ b/airflow/www/templates/airflow/dags.html
@@ -21,11 +21,7 @@
 {% from 'appbuilder/loading_dots.html' import loading_dots %}
 
 {% block page_title %}
-  {% if search_query %}
-    "{{ search_query }}" - DAGs - Airflow
-  {% else %}
-    DAGs - Airflow
-  {% endif %}
+  {% if search_query %}"{{ search_query }}" - {% endif %}DAGs - {{ appbuilder.app_name }}
 {% endblock %}
 
 {% block head_css %}
@@ -35,7 +31,7 @@
 {% endblock %}
 
 {% block content %}
-  <h2>DAGs</h2>
+  <h2>{{ page_title }}</h2>
   <div id="main_content">
     <div class="dags-table-wrap">
       <div class="row dags-table-header">
diff --git a/airflow/www/templates/airflow/duration_chart.html b/airflow/www/templates/airflow/duration_chart.html
index c41642b..7fdbb21 100644
--- a/airflow/www/templates/airflow/duration_chart.html
+++ b/airflow/www/templates/airflow/duration_chart.html
@@ -18,7 +18,7 @@
 #}
 
 {% extends "airflow/dag.html" %}
-{% block page_title %}{{ dag.dag_id }} - Duration chart - Airflow{% endblock %}
+{% block page_title %}{{ dag.dag_id }} - Duration chart - {{ appbuilder.app_name }}{% endblock %}
 
 {% block head_css %}
   {{ super() }}
diff --git a/airflow/www/templates/airflow/gantt.html b/airflow/www/templates/airflow/gantt.html
index abc27f3..1f6cce6 100644
--- a/airflow/www/templates/airflow/gantt.html
+++ b/airflow/www/templates/airflow/gantt.html
@@ -19,7 +19,7 @@
 
 {% extends "airflow/dag.html" %}
 
-{% block page_title %}{{ dag.dag_id }} - Gantt - Airflow{% endblock %}
+{% block page_title %}{{ dag.dag_id }} - Gantt - {{ appbuilder.app_name }}{% endblock %}
 
 {% block head_css %}
   {{ super() }}
diff --git a/airflow/www/templates/airflow/graph.html b/airflow/www/templates/airflow/graph.html
index 138c0c2..3e18600 100644
--- a/airflow/www/templates/airflow/graph.html
+++ b/airflow/www/templates/airflow/graph.html
@@ -20,7 +20,7 @@
 {% extends "airflow/dag.html" %}
 {% from 'appbuilder/loading_dots.html' import loading_dots %}
 
-{% block page_title %}{{ dag.dag_id }} - Graph - Airflow{% endblock %}
+{% block page_title %}{{ dag.dag_id }} - Graph - {{ appbuilder.app_name }}{% endblock %}
 
 {% block head_css %}
   {{ super() }}
diff --git a/airflow/www/templates/airflow/main.html b/airflow/www/templates/airflow/main.html
index 223ebe1..d1331d0 100644
--- a/airflow/www/templates/airflow/main.html
+++ b/airflow/www/templates/airflow/main.html
@@ -19,6 +19,14 @@
 
 {% extends 'appbuilder/baselayout.html' %}
 
+{% block page_title -%}
+  {% if title is defined -%}
+    {{ title }} - {{ appbuilder.app_name }}
+  {% else -%}
+    {{ appbuilder.app_name }}
+  {% endif%}
+{% endblock %}
+
 {% block head_css %}
   {{ super() }}
 
diff --git a/airflow/www/templates/airflow/task.html b/airflow/www/templates/airflow/task.html
index 547559a..a870208 100644
--- a/airflow/www/templates/airflow/task.html
+++ b/airflow/www/templates/airflow/task.html
@@ -18,7 +18,7 @@
 #}
 
 {% extends "airflow/task_instance.html" %}
-{% block title %}Airflow - DAGs{% endblock %}
+{% block title %}DAGs - {{ appbuilder.app_name }}{% endblock %}
 
 {% block content %}
   {{ super() }}
diff --git a/airflow/www/templates/airflow/ti_code.html b/airflow/www/templates/airflow/ti_code.html
index d7157a2..ef5a28d 100644
--- a/airflow/www/templates/airflow/ti_code.html
+++ b/airflow/www/templates/airflow/ti_code.html
@@ -18,7 +18,7 @@
 #}
 
 {% extends "airflow/task_instance.html" %}
-{% block title %}Airflow - DAGs{% endblock %}
+{% block title %}DAGs - {{ appbuilder.app_name }}{% endblock %}
 
 {% block content %}
   {{ super() }}
diff --git a/airflow/www/templates/airflow/ti_log.html b/airflow/www/templates/airflow/ti_log.html
index 297108c..dd61df3 100644
--- a/airflow/www/templates/airflow/ti_log.html
+++ b/airflow/www/templates/airflow/ti_log.html
@@ -18,7 +18,7 @@
 #}
 
 {% extends "airflow/task_instance.html" %}
-{% block title %}Airflow - DAGs{% endblock %}
+{% block title %}DAGs - {{ appbuilder.app_name }}{% endblock %}
 
 {% block content %}
   {{ super() }}
diff --git a/airflow/www/templates/airflow/tree.html b/airflow/www/templates/airflow/tree.html
index 6820266..bd8a3a7 100644
--- a/airflow/www/templates/airflow/tree.html
+++ b/airflow/www/templates/airflow/tree.html
@@ -18,7 +18,7 @@
 #}
 
 {% extends "airflow/dag.html" %}
-{% block page_title %}{{ dag.dag_id }} - Tree - Airflow{% endblock %}
+{% block page_title %}{{ dag.dag_id }} - Tree - {{ appbuilder.app_name }}{% endblock %}
 
 {% block head_css %}
   {{ super() }}
diff --git a/airflow/www/templates/airflow/xcom.html b/airflow/www/templates/airflow/xcom.html
index 8b32629..68bbd72 100644
--- a/airflow/www/templates/airflow/xcom.html
+++ b/airflow/www/templates/airflow/xcom.html
@@ -18,7 +18,7 @@
 #}
 
 {% extends "airflow/task_instance.html" %}
-{% block title %}Airflow - DAGs{% endblock %}
+{% block title %}DAGs - {{ appbuilder.app_name }}{% endblock %}
 
 {% block content %}
   {{ super() }}
diff --git a/airflow/www/views.py b/airflow/www/views.py
index 51144ad..44f03c4 100644
--- a/airflow/www/views.py
+++ b/airflow/www/views.py
@@ -574,11 +574,14 @@ class Airflow(AirflowBaseView):  # noqa: D101  pylint: disable=too-many-public-m
         state_color_mapping = State.state_color.copy()
         state_color_mapping["null"] = state_color_mapping.pop(None)
 
+        page_title = conf.get(section="webserver", key="instance_name", fallback="DAGs")
+
         return self.render_template(
             'airflow/dags.html',
             dags=dags,
             current_page=current_page,
             search_query=arg_search_query if arg_search_query else '',
+            page_title=page_title,
             page_size=dags_per_page,
             num_of_pages=num_of_pages,
             num_dag_from=min(start + 1, num_of_all_dags),
diff --git a/docs/apache-airflow/howto/customize-dag-ui-page-instance-name.rst b/docs/apache-airflow/howto/customize-dag-ui-page-instance-name.rst
new file mode 100644
index 0000000..e54a066
--- /dev/null
+++ b/docs/apache-airflow/howto/customize-dag-ui-page-instance-name.rst
@@ -0,0 +1,55 @@
+ .. Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+ ..   http://www.apache.org/licenses/LICENSE-2.0
+
+ .. Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+Customizing DAG UI Header and Airflow Page Titles
+=================================================
+
+Airflow now allows you to customize the DAG home page header and page title. This will help
+distinguish between various installations of Airflow or simply amend the page text.
+
+Note: the custom title will be applied to both the page header and the page title.
+
+To make this change, simply:
+
+1.  Add the configuration option of ``instance_name`` under ``webserver`` inside ``airflow.cfg``:
+
+.. code-block::
+
+  [webserver]
+
+  instance_name = "DevEnv"
+
+
+2.  Alternatively, you can set a custom title using the environment variable:
+
+.. code-block::
+
+  AIRFLOW__WEBSERVER__SITE_TITLE = "DevEnv"
+
+
+Screenshots
+-----------
+
+Before
+^^^^^^
+
+.. image:: ../img/change-site-title/default_instance_name_configuration.png
+
+After
+^^^^^
+
+.. image:: ../img/change-site-title/example_instance_name_configuration.png
diff --git a/docs/apache-airflow/howto/index.rst b/docs/apache-airflow/howto/index.rst
index b8c2912..9a7e68b 100644
--- a/docs/apache-airflow/howto/index.rst
+++ b/docs/apache-airflow/howto/index.rst
@@ -34,6 +34,7 @@ configuring an Airflow environment.
     set-up-database
     operator/index
     customize-state-colors-ui
+    customize-dag-ui-page-instance-name
     custom-operator
     connection
     variable
diff --git a/docs/apache-airflow/img/change-site-title/default_instance_name_configuration.png b/docs/apache-airflow/img/change-site-title/default_instance_name_configuration.png
new file mode 100644
index 0000000..283f1d3
Binary files /dev/null and b/docs/apache-airflow/img/change-site-title/default_instance_name_configuration.png differ
diff --git a/docs/apache-airflow/img/change-site-title/example_instance_name_configuration.png b/docs/apache-airflow/img/change-site-title/example_instance_name_configuration.png
new file mode 100644
index 0000000..04ea95a
Binary files /dev/null and b/docs/apache-airflow/img/change-site-title/example_instance_name_configuration.png differ
diff --git a/tests/www/test_views.py b/tests/www/test_views.py
index ca5c3e8..98aef0c 100644
--- a/tests/www/test_views.py
+++ b/tests/www/test_views.py
@@ -1137,6 +1137,21 @@ class TestAirflowBaseViews(TestBase):
             assert ctx['show_external_log_redirect']
             assert ctx['external_log_name'] == ExternalHandler.LOG_NAME
 
+    def test_page_instance_name(self):
+        with conf_vars({('webserver', 'instance_name'): 'Site Title Test'}):
+            resp = self.client.get('home', follow_redirects=True)
+            self.check_content_in_response('Site Title Test', resp)
+
+    def test_page_instance_name_xss_prevention(self):
+        xss_string = "<script>alert('Give me your credit card number')</script>"
+        with conf_vars({('webserver', 'instance_name'): xss_string}):
+            resp = self.client.get('home', follow_redirects=True)
+            escaped_xss_string = (
+                "&lt;script&gt;alert(&#39;Give me your credit card number&#39;)&lt;/script&gt;"
+            )
+            self.check_content_in_response(escaped_xss_string, resp)
+            self.check_content_not_in_response(xss_string, resp)
+
 
 class TestConfigurationView(TestBase):
     def test_configuration_do_not_expose_config(self):