Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 98 additions & 17 deletions airflow-core/docs/howto/customize-ui.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,7 +21,7 @@ Customizing the UI
.. _customizing-the-ui:

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.
Expand DownExpand Up@@ -61,17 +61,47 @@ After

.. image:: ../img/change-site-title/example_instance_name_configuration.png

|

Add custom alert messages on the dashboard
------------------------------------------
Adding Dashboard Alert Messages
===============================

Extra alert messages can be shown on the UI dashboard. This can be useful for warning about setup issues
or announcing changes to end users. The following example shows how to add alert messages:
Extra alert messages can be shown on the Airflow dashboard. This can be useful for warning about setup issues, announcing changes
to end users, or providing real-time status information. Dashboard alerts support both static and dynamic content.

1. Add the following contents to ``airflow_local_settings.py`` file under ``$AIRFLOW_HOME/config``.
Each alert message should specify a severity level (``info``, ``warning``, ``error``) using ``category``.
Basic Static Alerts
-------------------

.. code-block:: python
To add static alert messages that remain constant until the webserver is restarted:

1. Create an ``airflow_local_settings.py`` file and place it in ``$PYTHONPATH`` or in the ``$AIRFLOW_HOME/config`` folder.
(Airflow adds ``$AIRFLOW_HOME/config`` to ``PYTHONPATH`` when Airflow is initialized)

2. Add the following contents to ``airflow_local_settings.py``:

.. note::
See :ref:`Configuring local settings <set-config:configuring-local-settings>` for details on how to configure local settings.

.. code-block:: python

from airflow.www.utils import UIAlert

DASHBOARD_UIALERTS = [
UIAlert("Welcome to Airflow"),
]

3. Restart the Airflow webserver, and you should now see the alert message displayed on the dashboard.

Alert Categories
----------------

You can control the category of the alert message. Available categories include:

- ``"info"`` (default) - Blue informational alerts
- ``"warning"`` - Yellow warning alerts
- ``"error"`` - Red error alerts

.. code-block:: python

from airflow.api_fastapi.common.types import UIAlert

Expand All@@ -81,19 +111,70 @@ or announcing changes to end users. The following example shows how to add alert
UIAlert(text="Critical error detected!", category="error"),
]

See :ref:`Configuring local settings <set-config:configuring-local-settings>` for details on how to
configure local settings.
.. image:: ../img/ui-alert-message.png

2. Restart Airflow Webserver, and you should now see:
Markdown Content in Alerts
--------------------------

.. image:: ../img/ui-alert-message.png
Markdown can be included in alert messages for richer formatting. In the following example, we show an alert
message of heading 2 with a link included:

Alert messages also support Markdown. In the following example, we show an alert message of heading 2 with a link included.
.. code-block:: python

.. code-block:: python
from airflow.www.utils import UIAlert

DASHBOARD_UIALERTS = [
UIAlert(text="## Visit [airflow.apache.org](https://airflow.apache.org)", category="info"),
]
DASHBOARD_UIALERTS = [
UIAlert(text="## Visit [airflow.apache.org](https://airflow.apache.org)", category="info"),
]

.. image:: ../img/ui-alert-message-markdown.png

Dynamic Dashboard Alerts
------------------------

Dashboard alerts support dynamic content that updates each time the dashboard page is refreshed. This allows for real-time
status updates without requiring webserver restarts. Dynamic alerts must be defined as an instance of an iterable object.
The recommended approach is to create a class that subclasses ``list`` and implements a custom ``__iter__`` method that
yields fresh alerts each time Airflow iterates over the alerts.

.. note::
When implementing dynamic alerts it is important to keep alert generation logic lightweight to avoid
impacting dashboard load times. Consider caching results for expensive operations and handle exceptions
gracefully to prevent alert generation from breaking the UI.

Dynamic alerts are particularly useful for:

- **Real-time notifications**: Display current status updates or announcements
- **Deployment notifications**: Show current deployment status, build progress, or GitOps state
- **Temporary maintenance alerts**: Provide time-sensitive information about ongoing maintenance or issues
- **Environment-specific warnings**: Display different alerts based on current environment conditions
- **External service status**: Show the availability of dependent services or APIs

Creating Dynamic Alerts
^^^^^^^^^^^^^^^^^^^^^^^

To create dynamic alerts, define ``DASHBOARD_UIALERTS`` as an instance of a class that subclasses ``list``
and implements the ``__iter__`` method. The UI will iterate over any number ``UIAlert`` instances yielded by
this method and expose them as alerts on the dashboard page.

The example below demonstrates how logic can be applied to yield alerts dynamically. More practical use
cases might include alerts yielded from APIs, database queries or files.

.. code-block:: python

import random
from airflow.www.utils import UIAlert


class DynamicAlerts(list):
def __iter__(self):
# This method is called each time Airflow iterates over DASHBOARD_UIALERTS
# Example: Flip a coin
if random.choice([True, False]):
yield UIAlert("Heads!", category="info")
else:
yield UIAlert("Tails!", category="warning")


# Create an instance of the class
DASHBOARD_UIALERTS = DynamicAlerts()
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
from fastapi import Depends, status

from airflow.api_fastapi.common.router import AirflowRouter
from airflow.api_fastapi.common.types import UIAlert
from airflow.api_fastapi.core_api.datamodels.ui.config import ConfigResponse
from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc
from airflow.api_fastapi.core_api.security import requires_authenticated
Expand DownExpand Up@@ -54,7 +55,8 @@ def get_configs() -> ConfigResponse:
additional_config: dict[str, Any] = {
"instance_name": conf.get("api", "instance_name", fallback="Airflow"),
"test_connection": conf.get("core", "test_connection", fallback="Disabled"),
"dashboard_alert": DASHBOARD_UIALERTS,
# Expose "dashboard_alert" using a list comprehension so UIAlert instances can be expressed dynamically.
"dashboard_alert": [alert for alert in DASHBOARD_UIALERTS if isinstance(alert, UIAlert)],
"show_external_log_redirect": task_log_reader.supports_external_link,
"external_log_name": getattr(task_log_reader.log_handler, "log_name", None),
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 98 additions & 17 deletions airflow-core/docs/howto/customize-ui.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,7 +21,7 @@ Customizing the UI
.. _customizing-the-ui:

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.
Expand DownExpand Up@@ -61,17 +61,47 @@ After

.. image:: ../img/change-site-title/example_instance_name_configuration.png

|

Add custom alert messages on the dashboard
------------------------------------------
Adding Dashboard Alert Messages
===============================

Extra alert messages can be shown on the UI dashboard. This can be useful for warning about setup issues
or announcing changes to end users. The following example shows how to add alert messages:
Extra alert messages can be shown on the Airflow dashboard. This can be useful for warning about setup issues, announcing changes
to end users, or providing real-time status information. Dashboard alerts support both static and dynamic content.

1. Add the following contents to ``airflow_local_settings.py`` file under ``$AIRFLOW_HOME/config``.
Each alert message should specify a severity level (``info``, ``warning``, ``error``) using ``category``.
Basic Static Alerts
-------------------

.. code-block:: python
To add static alert messages that remain constant until the webserver is restarted:

1. Create an ``airflow_local_settings.py`` file and place it in ``$PYTHONPATH`` or in the ``$AIRFLOW_HOME/config`` folder.
(Airflow adds ``$AIRFLOW_HOME/config`` to ``PYTHONPATH`` when Airflow is initialized)

2. Add the following contents to ``airflow_local_settings.py``:

.. note::
See :ref:`Configuring local settings <set-config:configuring-local-settings>` for details on how to configure local settings.

.. code-block:: python

from airflow.www.utils import UIAlert

DASHBOARD_UIALERTS = [
UIAlert("Welcome to Airflow"),
]

3. Restart the Airflow webserver, and you should now see the alert message displayed on the dashboard.

Alert Categories
----------------

You can control the category of the alert message. Available categories include:

- ``"info"`` (default) - Blue informational alerts
- ``"warning"`` - Yellow warning alerts
- ``"error"`` - Red error alerts

.. code-block:: python

from airflow.api_fastapi.common.types import UIAlert

Expand All@@ -81,19 +111,70 @@ or announcing changes to end users. The following example shows how to add alert
UIAlert(text="Critical error detected!", category="error"),
]

See :ref:`Configuring local settings <set-config:configuring-local-settings>` for details on how to
configure local settings.
.. image:: ../img/ui-alert-message.png

2. Restart Airflow Webserver, and you should now see:
Markdown Content in Alerts
--------------------------

.. image:: ../img/ui-alert-message.png
Markdown can be included in alert messages for richer formatting. In the following example, we show an alert
message of heading 2 with a link included:

Alert messages also support Markdown. In the following example, we show an alert message of heading 2 with a link included.
.. code-block:: python

.. code-block:: python
from airflow.www.utils import UIAlert

DASHBOARD_UIALERTS = [
UIAlert(text="## Visit [airflow.apache.org](https://airflow.apache.org)", category="info"),
]
DASHBOARD_UIALERTS = [
UIAlert(text="## Visit [airflow.apache.org](https://airflow.apache.org)", category="info"),
]

.. image:: ../img/ui-alert-message-markdown.png

Dynamic Dashboard Alerts
------------------------

Dashboard alerts support dynamic content that updates each time the dashboard page is refreshed. This allows for real-time
status updates without requiring webserver restarts. Dynamic alerts must be defined as an instance of an iterable object.
The recommended approach is to create a class that subclasses ``list`` and implements a custom ``__iter__`` method that
yields fresh alerts each time Airflow iterates over the alerts.

.. note::
When implementing dynamic alerts it is important to keep alert generation logic lightweight to avoid
impacting dashboard load times. Consider caching results for expensive operations and handle exceptions
gracefully to prevent alert generation from breaking the UI.

Dynamic alerts are particularly useful for:

- **Real-time notifications**: Display current status updates or announcements
- **Deployment notifications**: Show current deployment status, build progress, or GitOps state
- **Temporary maintenance alerts**: Provide time-sensitive information about ongoing maintenance or issues
- **Environment-specific warnings**: Display different alerts based on current environment conditions
- **External service status**: Show the availability of dependent services or APIs

Creating Dynamic Alerts
^^^^^^^^^^^^^^^^^^^^^^^

To create dynamic alerts, define ``DASHBOARD_UIALERTS`` as an instance of a class that subclasses ``list``
and implements the ``__iter__`` method. The UI will iterate over any number ``UIAlert`` instances yielded by
this method and expose them as alerts on the dashboard page.

The example below demonstrates how logic can be applied to yield alerts dynamically. More practical use
cases might include alerts yielded from APIs, database queries or files.

.. code-block:: python

import random
from airflow.www.utils import UIAlert


class DynamicAlerts(list):
def __iter__(self):
# This method is called each time Airflow iterates over DASHBOARD_UIALERTS
# Example: Flip a coin
if random.choice([True, False]):
yield UIAlert("Heads!", category="info")
else:
yield UIAlert("Tails!", category="warning")


# Create an instance of the class
DASHBOARD_UIALERTS = DynamicAlerts()
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
from fastapi import Depends, status

from airflow.api_fastapi.common.router import AirflowRouter
from airflow.api_fastapi.common.types import UIAlert
from airflow.api_fastapi.core_api.datamodels.ui.config import ConfigResponse
from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc
from airflow.api_fastapi.core_api.security import requires_authenticated
Expand DownExpand Up@@ -54,7 +55,8 @@ def get_configs() -> ConfigResponse:
additional_config: dict[str, Any] = {
"instance_name": conf.get("api", "instance_name", fallback="Airflow"),
"test_connection": conf.get("core", "test_connection", fallback="Disabled"),
"dashboard_alert": DASHBOARD_UIALERTS,
# Expose "dashboard_alert" using a list comprehension so UIAlert instances can be expressed dynamically.
"dashboard_alert": [alert for alert in DASHBOARD_UIALERTS if isinstance(alert, UIAlert)],
"show_external_log_redirect": task_log_reader.supports_external_link,
"external_log_name": getattr(task_log_reader.log_handler, "log_name", None),
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 98 additions & 17 deletions airflow-core/docs/howto/customize-ui.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,7 +21,7 @@ Customizing the UI
.. _customizing-the-ui:

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.
Expand DownExpand Up@@ -61,17 +61,47 @@ After

.. image:: ../img/change-site-title/example_instance_name_configuration.png

|

Add custom alert messages on the dashboard
------------------------------------------
Adding Dashboard Alert Messages
===============================

Extra alert messages can be shown on the UI dashboard. This can be useful for warning about setup issues
or announcing changes to end users. The following example shows how to add alert messages:
Extra alert messages can be shown on the Airflow dashboard. This can be useful for warning about setup issues, announcing changes
to end users, or providing real-time status information. Dashboard alerts support both static and dynamic content.

1. Add the following contents to ``airflow_local_settings.py`` file under ``$AIRFLOW_HOME/config``.
Each alert message should specify a severity level (``info``, ``warning``, ``error``) using ``category``.
Basic Static Alerts
-------------------

.. code-block:: python
To add static alert messages that remain constant until the webserver is restarted:

1. Create an ``airflow_local_settings.py`` file and place it in ``$PYTHONPATH`` or in the ``$AIRFLOW_HOME/config`` folder.
(Airflow adds ``$AIRFLOW_HOME/config`` to ``PYTHONPATH`` when Airflow is initialized)

2. Add the following contents to ``airflow_local_settings.py``:

.. note::
See :ref:`Configuring local settings <set-config:configuring-local-settings>` for details on how to configure local settings.

.. code-block:: python

from airflow.www.utils import UIAlert

DASHBOARD_UIALERTS = [
UIAlert("Welcome to Airflow"),
]

3. Restart the Airflow webserver, and you should now see the alert message displayed on the dashboard.

Alert Categories
----------------

You can control the category of the alert message. Available categories include:

- ``"info"`` (default) - Blue informational alerts
- ``"warning"`` - Yellow warning alerts
- ``"error"`` - Red error alerts

.. code-block:: python

from airflow.api_fastapi.common.types import UIAlert

Expand All@@ -81,19 +111,70 @@ or announcing changes to end users. The following example shows how to add alert
UIAlert(text="Critical error detected!", category="error"),
]

See :ref:`Configuring local settings <set-config:configuring-local-settings>` for details on how to
configure local settings.
.. image:: ../img/ui-alert-message.png

2. Restart Airflow Webserver, and you should now see:
Markdown Content in Alerts
--------------------------

.. image:: ../img/ui-alert-message.png
Markdown can be included in alert messages for richer formatting. In the following example, we show an alert
message of heading 2 with a link included:

Alert messages also support Markdown. In the following example, we show an alert message of heading 2 with a link included.
.. code-block:: python

.. code-block:: python
from airflow.www.utils import UIAlert

DASHBOARD_UIALERTS = [
UIAlert(text="## Visit [airflow.apache.org](https://airflow.apache.org)", category="info"),
]
DASHBOARD_UIALERTS = [
UIAlert(text="## Visit [airflow.apache.org](https://airflow.apache.org)", category="info"),
]

.. image:: ../img/ui-alert-message-markdown.png

Dynamic Dashboard Alerts
------------------------

Dashboard alerts support dynamic content that updates each time the dashboard page is refreshed. This allows for real-time
status updates without requiring webserver restarts. Dynamic alerts must be defined as an instance of an iterable object.
The recommended approach is to create a class that subclasses ``list`` and implements a custom ``__iter__`` method that
yields fresh alerts each time Airflow iterates over the alerts.

.. note::
When implementing dynamic alerts it is important to keep alert generation logic lightweight to avoid
impacting dashboard load times. Consider caching results for expensive operations and handle exceptions
gracefully to prevent alert generation from breaking the UI.

Dynamic alerts are particularly useful for:

- **Real-time notifications**: Display current status updates or announcements
- **Deployment notifications**: Show current deployment status, build progress, or GitOps state
- **Temporary maintenance alerts**: Provide time-sensitive information about ongoing maintenance or issues
- **Environment-specific warnings**: Display different alerts based on current environment conditions
- **External service status**: Show the availability of dependent services or APIs

Creating Dynamic Alerts
^^^^^^^^^^^^^^^^^^^^^^^

To create dynamic alerts, define ``DASHBOARD_UIALERTS`` as an instance of a class that subclasses ``list``
and implements the ``__iter__`` method. The UI will iterate over any number ``UIAlert`` instances yielded by
this method and expose them as alerts on the dashboard page.

The example below demonstrates how logic can be applied to yield alerts dynamically. More practical use
cases might include alerts yielded from APIs, database queries or files.

.. code-block:: python

import random
from airflow.www.utils import UIAlert


class DynamicAlerts(list):
def __iter__(self):
# This method is called each time Airflow iterates over DASHBOARD_UIALERTS
# Example: Flip a coin
if random.choice([True, False]):
yield UIAlert("Heads!", category="info")
else:
yield UIAlert("Tails!", category="warning")


# Create an instance of the class
DASHBOARD_UIALERTS = DynamicAlerts()
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
from fastapi import Depends, status

from airflow.api_fastapi.common.router import AirflowRouter
from airflow.api_fastapi.common.types import UIAlert
from airflow.api_fastapi.core_api.datamodels.ui.config import ConfigResponse
from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc
from airflow.api_fastapi.core_api.security import requires_authenticated
Expand DownExpand Up@@ -54,7 +55,8 @@ def get_configs() -> ConfigResponse:
additional_config: dict[str, Any] = {
"instance_name": conf.get("api", "instance_name", fallback="Airflow"),
"test_connection": conf.get("core", "test_connection", fallback="Disabled"),
"dashboard_alert": DASHBOARD_UIALERTS,
# Expose "dashboard_alert" using a list comprehension so UIAlert instances can be expressed dynamically.
"dashboard_alert": [alert for alert in DASHBOARD_UIALERTS if isinstance(alert, UIAlert)],
"show_external_log_redirect": task_log_reader.supports_external_link,
"external_log_name": getattr(task_log_reader.log_handler, "log_name", None),
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 98 additions & 17 deletions airflow-core/docs/howto/customize-ui.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,7 +21,7 @@ Customizing the UI
.. _customizing-the-ui:

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.
Expand DownExpand Up@@ -61,17 +61,47 @@ After

.. image:: ../img/change-site-title/example_instance_name_configuration.png

|

Add custom alert messages on the dashboard
------------------------------------------
Adding Dashboard Alert Messages
===============================

Extra alert messages can be shown on the UI dashboard. This can be useful for warning about setup issues
or announcing changes to end users. The following example shows how to add alert messages:
Extra alert messages can be shown on the Airflow dashboard. This can be useful for warning about setup issues, announcing changes
to end users, or providing real-time status information. Dashboard alerts support both static and dynamic content.

1. Add the following contents to ``airflow_local_settings.py`` file under ``$AIRFLOW_HOME/config``.
Each alert message should specify a severity level (``info``, ``warning``, ``error``) using ``category``.
Basic Static Alerts
-------------------

.. code-block:: python
To add static alert messages that remain constant until the webserver is restarted:

1. Create an ``airflow_local_settings.py`` file and place it in ``$PYTHONPATH`` or in the ``$AIRFLOW_HOME/config`` folder.
(Airflow adds ``$AIRFLOW_HOME/config`` to ``PYTHONPATH`` when Airflow is initialized)

2. Add the following contents to ``airflow_local_settings.py``:

.. note::
See :ref:`Configuring local settings <set-config:configuring-local-settings>` for details on how to configure local settings.

.. code-block:: python

from airflow.www.utils import UIAlert

DASHBOARD_UIALERTS = [
UIAlert("Welcome to Airflow"),
]

3. Restart the Airflow webserver, and you should now see the alert message displayed on the dashboard.

Alert Categories
----------------

You can control the category of the alert message. Available categories include:

- ``"info"`` (default) - Blue informational alerts
- ``"warning"`` - Yellow warning alerts
- ``"error"`` - Red error alerts

.. code-block:: python

from airflow.api_fastapi.common.types import UIAlert

Expand All@@ -81,19 +111,70 @@ or announcing changes to end users. The following example shows how to add alert
UIAlert(text="Critical error detected!", category="error"),
]

See :ref:`Configuring local settings <set-config:configuring-local-settings>` for details on how to
configure local settings.
.. image:: ../img/ui-alert-message.png

2. Restart Airflow Webserver, and you should now see:
Markdown Content in Alerts
--------------------------

.. image:: ../img/ui-alert-message.png
Markdown can be included in alert messages for richer formatting. In the following example, we show an alert
message of heading 2 with a link included:

Alert messages also support Markdown. In the following example, we show an alert message of heading 2 with a link included.
.. code-block:: python

.. code-block:: python
from airflow.www.utils import UIAlert

DASHBOARD_UIALERTS = [
UIAlert(text="## Visit [airflow.apache.org](https://airflow.apache.org)", category="info"),
]
DASHBOARD_UIALERTS = [
UIAlert(text="## Visit [airflow.apache.org](https://airflow.apache.org)", category="info"),
]

.. image:: ../img/ui-alert-message-markdown.png

Dynamic Dashboard Alerts
------------------------

Dashboard alerts support dynamic content that updates each time the dashboard page is refreshed. This allows for real-time
status updates without requiring webserver restarts. Dynamic alerts must be defined as an instance of an iterable object.
The recommended approach is to create a class that subclasses ``list`` and implements a custom ``__iter__`` method that
yields fresh alerts each time Airflow iterates over the alerts.

.. note::
When implementing dynamic alerts it is important to keep alert generation logic lightweight to avoid
impacting dashboard load times. Consider caching results for expensive operations and handle exceptions
gracefully to prevent alert generation from breaking the UI.

Dynamic alerts are particularly useful for:

- **Real-time notifications**: Display current status updates or announcements
- **Deployment notifications**: Show current deployment status, build progress, or GitOps state
- **Temporary maintenance alerts**: Provide time-sensitive information about ongoing maintenance or issues
- **Environment-specific warnings**: Display different alerts based on current environment conditions
- **External service status**: Show the availability of dependent services or APIs

Creating Dynamic Alerts
^^^^^^^^^^^^^^^^^^^^^^^

To create dynamic alerts, define ``DASHBOARD_UIALERTS`` as an instance of a class that subclasses ``list``
and implements the ``__iter__`` method. The UI will iterate over any number ``UIAlert`` instances yielded by
this method and expose them as alerts on the dashboard page.

The example below demonstrates how logic can be applied to yield alerts dynamically. More practical use
cases might include alerts yielded from APIs, database queries or files.

.. code-block:: python

import random
from airflow.www.utils import UIAlert


class DynamicAlerts(list):
def __iter__(self):
# This method is called each time Airflow iterates over DASHBOARD_UIALERTS
# Example: Flip a coin
if random.choice([True, False]):
yield UIAlert("Heads!", category="info")
else:
yield UIAlert("Tails!", category="warning")


# Create an instance of the class
DASHBOARD_UIALERTS = DynamicAlerts()
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
from fastapi import Depends, status

from airflow.api_fastapi.common.router import AirflowRouter
from airflow.api_fastapi.common.types import UIAlert
from airflow.api_fastapi.core_api.datamodels.ui.config import ConfigResponse
from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc
from airflow.api_fastapi.core_api.security import requires_authenticated
Expand DownExpand Up@@ -54,7 +55,8 @@ def get_configs() -> ConfigResponse:
additional_config: dict[str, Any] = {
"instance_name": conf.get("api", "instance_name", fallback="Airflow"),
"test_connection": conf.get("core", "test_connection", fallback="Disabled"),
"dashboard_alert": DASHBOARD_UIALERTS,
# Expose "dashboard_alert" using a list comprehension so UIAlert instances can be expressed dynamically.
"dashboard_alert": [alert for alert in DASHBOARD_UIALERTS if isinstance(alert, UIAlert)],
"show_external_log_redirect": task_log_reader.supports_external_link,
"external_log_name": getattr(task_log_reader.log_handler, "log_name", None),
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 98 additions & 17 deletions airflow-core/docs/howto/customize-ui.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,7 +21,7 @@ Customizing the UI
.. _customizing-the-ui:

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.
Expand DownExpand Up@@ -61,17 +61,47 @@ After

.. image:: ../img/change-site-title/example_instance_name_configuration.png

|

Add custom alert messages on the dashboard
------------------------------------------
Adding Dashboard Alert Messages
===============================

Extra alert messages can be shown on the UI dashboard. This can be useful for warning about setup issues
or announcing changes to end users. The following example shows how to add alert messages:
Extra alert messages can be shown on the Airflow dashboard. This can be useful for warning about setup issues, announcing changes
to end users, or providing real-time status information. Dashboard alerts support both static and dynamic content.

1. Add the following contents to ``airflow_local_settings.py`` file under ``$AIRFLOW_HOME/config``.
Each alert message should specify a severity level (``info``, ``warning``, ``error``) using ``category``.
Basic Static Alerts
-------------------

.. code-block:: python
To add static alert messages that remain constant until the webserver is restarted:

1. Create an ``airflow_local_settings.py`` file and place it in ``$PYTHONPATH`` or in the ``$AIRFLOW_HOME/config`` folder.
(Airflow adds ``$AIRFLOW_HOME/config`` to ``PYTHONPATH`` when Airflow is initialized)

2. Add the following contents to ``airflow_local_settings.py``:

.. note::
See :ref:`Configuring local settings <set-config:configuring-local-settings>` for details on how to configure local settings.

.. code-block:: python

from airflow.www.utils import UIAlert

DASHBOARD_UIALERTS = [
UIAlert("Welcome to Airflow"),
]

3. Restart the Airflow webserver, and you should now see the alert message displayed on the dashboard.

Alert Categories
----------------

You can control the category of the alert message. Available categories include:

- ``"info"`` (default) - Blue informational alerts
- ``"warning"`` - Yellow warning alerts
- ``"error"`` - Red error alerts

.. code-block:: python

from airflow.api_fastapi.common.types import UIAlert

Expand All@@ -81,19 +111,70 @@ or announcing changes to end users. The following example shows how to add alert
UIAlert(text="Critical error detected!", category="error"),
]

See :ref:`Configuring local settings <set-config:configuring-local-settings>` for details on how to
configure local settings.
.. image:: ../img/ui-alert-message.png

2. Restart Airflow Webserver, and you should now see:
Markdown Content in Alerts
--------------------------

.. image:: ../img/ui-alert-message.png
Markdown can be included in alert messages for richer formatting. In the following example, we show an alert
message of heading 2 with a link included:

Alert messages also support Markdown. In the following example, we show an alert message of heading 2 with a link included.
.. code-block:: python

.. code-block:: python
from airflow.www.utils import UIAlert

DASHBOARD_UIALERTS = [
UIAlert(text="## Visit [airflow.apache.org](https://airflow.apache.org)", category="info"),
]
DASHBOARD_UIALERTS = [
UIAlert(text="## Visit [airflow.apache.org](https://airflow.apache.org)", category="info"),
]

.. image:: ../img/ui-alert-message-markdown.png

Dynamic Dashboard Alerts
------------------------

Dashboard alerts support dynamic content that updates each time the dashboard page is refreshed. This allows for real-time
status updates without requiring webserver restarts. Dynamic alerts must be defined as an instance of an iterable object.
The recommended approach is to create a class that subclasses ``list`` and implements a custom ``__iter__`` method that
yields fresh alerts each time Airflow iterates over the alerts.

.. note::
When implementing dynamic alerts it is important to keep alert generation logic lightweight to avoid
impacting dashboard load times. Consider caching results for expensive operations and handle exceptions
gracefully to prevent alert generation from breaking the UI.

Dynamic alerts are particularly useful for:

- **Real-time notifications**: Display current status updates or announcements
- **Deployment notifications**: Show current deployment status, build progress, or GitOps state
- **Temporary maintenance alerts**: Provide time-sensitive information about ongoing maintenance or issues
- **Environment-specific warnings**: Display different alerts based on current environment conditions
- **External service status**: Show the availability of dependent services or APIs

Creating Dynamic Alerts
^^^^^^^^^^^^^^^^^^^^^^^

To create dynamic alerts, define ``DASHBOARD_UIALERTS`` as an instance of a class that subclasses ``list``
and implements the ``__iter__`` method. The UI will iterate over any number ``UIAlert`` instances yielded by
this method and expose them as alerts on the dashboard page.

The example below demonstrates how logic can be applied to yield alerts dynamically. More practical use
cases might include alerts yielded from APIs, database queries or files.

.. code-block:: python

import random
from airflow.www.utils import UIAlert


class DynamicAlerts(list):
def __iter__(self):
# This method is called each time Airflow iterates over DASHBOARD_UIALERTS
# Example: Flip a coin
if random.choice([True, False]):
yield UIAlert("Heads!", category="info")
else:
yield UIAlert("Tails!", category="warning")


# Create an instance of the class
DASHBOARD_UIALERTS = DynamicAlerts()
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
from fastapi import Depends, status

from airflow.api_fastapi.common.router import AirflowRouter
from airflow.api_fastapi.common.types import UIAlert
from airflow.api_fastapi.core_api.datamodels.ui.config import ConfigResponse
from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc
from airflow.api_fastapi.core_api.security import requires_authenticated
Expand DownExpand Up@@ -54,7 +55,8 @@ def get_configs() -> ConfigResponse:
additional_config: dict[str, Any] = {
"instance_name": conf.get("api", "instance_name", fallback="Airflow"),
"test_connection": conf.get("core", "test_connection", fallback="Disabled"),
"dashboard_alert": DASHBOARD_UIALERTS,
# Expose "dashboard_alert" using a list comprehension so UIAlert instances can be expressed dynamically.
"dashboard_alert": [alert for alert in DASHBOARD_UIALERTS if isinstance(alert, UIAlert)],
"show_external_log_redirect": task_log_reader.supports_external_link,
"external_log_name": getattr(task_log_reader.log_handler, "log_name", None),
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 98 additions & 17 deletions airflow-core/docs/howto/customize-ui.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,7 +21,7 @@ Customizing the UI
.. _customizing-the-ui:

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.
Expand DownExpand Up@@ -61,17 +61,47 @@ After

.. image:: ../img/change-site-title/example_instance_name_configuration.png

|

Add custom alert messages on the dashboard
------------------------------------------
Adding Dashboard Alert Messages
===============================

Extra alert messages can be shown on the UI dashboard. This can be useful for warning about setup issues
or announcing changes to end users. The following example shows how to add alert messages:
Extra alert messages can be shown on the Airflow dashboard. This can be useful for warning about setup issues, announcing changes
to end users, or providing real-time status information. Dashboard alerts support both static and dynamic content.

1. Add the following contents to ``airflow_local_settings.py`` file under ``$AIRFLOW_HOME/config``.
Each alert message should specify a severity level (``info``, ``warning``, ``error``) using ``category``.
Basic Static Alerts
-------------------

.. code-block:: python
To add static alert messages that remain constant until the webserver is restarted:

1. Create an ``airflow_local_settings.py`` file and place it in ``$PYTHONPATH`` or in the ``$AIRFLOW_HOME/config`` folder.
(Airflow adds ``$AIRFLOW_HOME/config`` to ``PYTHONPATH`` when Airflow is initialized)

2. Add the following contents to ``airflow_local_settings.py``:

.. note::
See :ref:`Configuring local settings <set-config:configuring-local-settings>` for details on how to configure local settings.

.. code-block:: python

from airflow.www.utils import UIAlert

DASHBOARD_UIALERTS = [
UIAlert("Welcome to Airflow"),
]

3. Restart the Airflow webserver, and you should now see the alert message displayed on the dashboard.

Alert Categories
----------------

You can control the category of the alert message. Available categories include:

- ``"info"`` (default) - Blue informational alerts
- ``"warning"`` - Yellow warning alerts
- ``"error"`` - Red error alerts

.. code-block:: python

from airflow.api_fastapi.common.types import UIAlert

Expand All@@ -81,19 +111,70 @@ or announcing changes to end users. The following example shows how to add alert
UIAlert(text="Critical error detected!", category="error"),
]

See :ref:`Configuring local settings <set-config:configuring-local-settings>` for details on how to
configure local settings.
.. image:: ../img/ui-alert-message.png

2. Restart Airflow Webserver, and you should now see:
Markdown Content in Alerts
--------------------------

.. image:: ../img/ui-alert-message.png
Markdown can be included in alert messages for richer formatting. In the following example, we show an alert
message of heading 2 with a link included:

Alert messages also support Markdown. In the following example, we show an alert message of heading 2 with a link included.
.. code-block:: python

.. code-block:: python
from airflow.www.utils import UIAlert

DASHBOARD_UIALERTS = [
UIAlert(text="## Visit [airflow.apache.org](https://airflow.apache.org)", category="info"),
]
DASHBOARD_UIALERTS = [
UIAlert(text="## Visit [airflow.apache.org](https://airflow.apache.org)", category="info"),
]

.. image:: ../img/ui-alert-message-markdown.png

Dynamic Dashboard Alerts
------------------------

Dashboard alerts support dynamic content that updates each time the dashboard page is refreshed. This allows for real-time
status updates without requiring webserver restarts. Dynamic alerts must be defined as an instance of an iterable object.
The recommended approach is to create a class that subclasses ``list`` and implements a custom ``__iter__`` method that
yields fresh alerts each time Airflow iterates over the alerts.

.. note::
When implementing dynamic alerts it is important to keep alert generation logic lightweight to avoid
impacting dashboard load times. Consider caching results for expensive operations and handle exceptions
gracefully to prevent alert generation from breaking the UI.

Dynamic alerts are particularly useful for:

- **Real-time notifications**: Display current status updates or announcements
- **Deployment notifications**: Show current deployment status, build progress, or GitOps state
- **Temporary maintenance alerts**: Provide time-sensitive information about ongoing maintenance or issues
- **Environment-specific warnings**: Display different alerts based on current environment conditions
- **External service status**: Show the availability of dependent services or APIs

Creating Dynamic Alerts
^^^^^^^^^^^^^^^^^^^^^^^

To create dynamic alerts, define ``DASHBOARD_UIALERTS`` as an instance of a class that subclasses ``list``
and implements the ``__iter__`` method. The UI will iterate over any number ``UIAlert`` instances yielded by
this method and expose them as alerts on the dashboard page.

The example below demonstrates how logic can be applied to yield alerts dynamically. More practical use
cases might include alerts yielded from APIs, database queries or files.

.. code-block:: python

import random
from airflow.www.utils import UIAlert


class DynamicAlerts(list):
def __iter__(self):
# This method is called each time Airflow iterates over DASHBOARD_UIALERTS
# Example: Flip a coin
if random.choice([True, False]):
yield UIAlert("Heads!", category="info")
else:
yield UIAlert("Tails!", category="warning")


# Create an instance of the class
DASHBOARD_UIALERTS = DynamicAlerts()
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
from fastapi import Depends, status

from airflow.api_fastapi.common.router import AirflowRouter
from airflow.api_fastapi.common.types import UIAlert
from airflow.api_fastapi.core_api.datamodels.ui.config import ConfigResponse
from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc
from airflow.api_fastapi.core_api.security import requires_authenticated
Expand DownExpand Up@@ -54,7 +55,8 @@ def get_configs() -> ConfigResponse:
additional_config: dict[str, Any] = {
"instance_name": conf.get("api", "instance_name", fallback="Airflow"),
"test_connection": conf.get("core", "test_connection", fallback="Disabled"),
"dashboard_alert": DASHBOARD_UIALERTS,
# Expose "dashboard_alert" using a list comprehension so UIAlert instances can be expressed dynamically.
"dashboard_alert": [alert for alert in DASHBOARD_UIALERTS if isinstance(alert, UIAlert)],
"show_external_log_redirect": task_log_reader.supports_external_link,
"external_log_name": getattr(task_log_reader.log_handler, "log_name", None),
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 98 additions & 17 deletions airflow-core/docs/howto/customize-ui.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,7 +21,7 @@ Customizing the UI
.. _customizing-the-ui:

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.
Expand DownExpand Up@@ -61,17 +61,47 @@ After

.. image:: ../img/change-site-title/example_instance_name_configuration.png

|

Add custom alert messages on the dashboard
------------------------------------------
Adding Dashboard Alert Messages
===============================

Extra alert messages can be shown on the UI dashboard. This can be useful for warning about setup issues
or announcing changes to end users. The following example shows how to add alert messages:
Extra alert messages can be shown on the Airflow dashboard. This can be useful for warning about setup issues, announcing changes
to end users, or providing real-time status information. Dashboard alerts support both static and dynamic content.

1. Add the following contents to ``airflow_local_settings.py`` file under ``$AIRFLOW_HOME/config``.
Each alert message should specify a severity level (``info``, ``warning``, ``error``) using ``category``.
Basic Static Alerts
-------------------

.. code-block:: python
To add static alert messages that remain constant until the webserver is restarted:

1. Create an ``airflow_local_settings.py`` file and place it in ``$PYTHONPATH`` or in the ``$AIRFLOW_HOME/config`` folder.
(Airflow adds ``$AIRFLOW_HOME/config`` to ``PYTHONPATH`` when Airflow is initialized)

2. Add the following contents to ``airflow_local_settings.py``:

.. note::
See :ref:`Configuring local settings <set-config:configuring-local-settings>` for details on how to configure local settings.

.. code-block:: python

from airflow.www.utils import UIAlert

DASHBOARD_UIALERTS = [
UIAlert("Welcome to Airflow"),
]

3. Restart the Airflow webserver, and you should now see the alert message displayed on the dashboard.

Alert Categories
----------------

You can control the category of the alert message. Available categories include:

- ``"info"`` (default) - Blue informational alerts
- ``"warning"`` - Yellow warning alerts
- ``"error"`` - Red error alerts

.. code-block:: python

from airflow.api_fastapi.common.types import UIAlert

Expand All@@ -81,19 +111,70 @@ or announcing changes to end users. The following example shows how to add alert
UIAlert(text="Critical error detected!", category="error"),
]

See :ref:`Configuring local settings <set-config:configuring-local-settings>` for details on how to
configure local settings.
.. image:: ../img/ui-alert-message.png

2. Restart Airflow Webserver, and you should now see:
Markdown Content in Alerts
--------------------------

.. image:: ../img/ui-alert-message.png
Markdown can be included in alert messages for richer formatting. In the following example, we show an alert
message of heading 2 with a link included:

Alert messages also support Markdown. In the following example, we show an alert message of heading 2 with a link included.
.. code-block:: python

.. code-block:: python
from airflow.www.utils import UIAlert

DASHBOARD_UIALERTS = [
UIAlert(text="## Visit [airflow.apache.org](https://airflow.apache.org)", category="info"),
]
DASHBOARD_UIALERTS = [
UIAlert(text="## Visit [airflow.apache.org](https://airflow.apache.org)", category="info"),
]

.. image:: ../img/ui-alert-message-markdown.png

Dynamic Dashboard Alerts
------------------------

Dashboard alerts support dynamic content that updates each time the dashboard page is refreshed. This allows for real-time
status updates without requiring webserver restarts. Dynamic alerts must be defined as an instance of an iterable object.
The recommended approach is to create a class that subclasses ``list`` and implements a custom ``__iter__`` method that
yields fresh alerts each time Airflow iterates over the alerts.

.. note::
When implementing dynamic alerts it is important to keep alert generation logic lightweight to avoid
impacting dashboard load times. Consider caching results for expensive operations and handle exceptions
gracefully to prevent alert generation from breaking the UI.

Dynamic alerts are particularly useful for:

- **Real-time notifications**: Display current status updates or announcements
- **Deployment notifications**: Show current deployment status, build progress, or GitOps state
- **Temporary maintenance alerts**: Provide time-sensitive information about ongoing maintenance or issues
- **Environment-specific warnings**: Display different alerts based on current environment conditions
- **External service status**: Show the availability of dependent services or APIs

Creating Dynamic Alerts
^^^^^^^^^^^^^^^^^^^^^^^

To create dynamic alerts, define ``DASHBOARD_UIALERTS`` as an instance of a class that subclasses ``list``
and implements the ``__iter__`` method. The UI will iterate over any number ``UIAlert`` instances yielded by
this method and expose them as alerts on the dashboard page.

The example below demonstrates how logic can be applied to yield alerts dynamically. More practical use
cases might include alerts yielded from APIs, database queries or files.

.. code-block:: python

import random
from airflow.www.utils import UIAlert


class DynamicAlerts(list):
def __iter__(self):
# This method is called each time Airflow iterates over DASHBOARD_UIALERTS
# Example: Flip a coin
if random.choice([True, False]):
yield UIAlert("Heads!", category="info")
else:
yield UIAlert("Tails!", category="warning")


# Create an instance of the class
DASHBOARD_UIALERTS = DynamicAlerts()
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
from fastapi import Depends, status

from airflow.api_fastapi.common.router import AirflowRouter
from airflow.api_fastapi.common.types import UIAlert
from airflow.api_fastapi.core_api.datamodels.ui.config import ConfigResponse
from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc
from airflow.api_fastapi.core_api.security import requires_authenticated
Expand DownExpand Up@@ -54,7 +55,8 @@ def get_configs() -> ConfigResponse:
additional_config: dict[str, Any] = {
"instance_name": conf.get("api", "instance_name", fallback="Airflow"),
"test_connection": conf.get("core", "test_connection", fallback="Disabled"),
"dashboard_alert": DASHBOARD_UIALERTS,
# Expose "dashboard_alert" using a list comprehension so UIAlert instances can be expressed dynamically.
"dashboard_alert": [alert for alert in DASHBOARD_UIALERTS if isinstance(alert, UIAlert)],
"show_external_log_redirect": task_log_reader.supports_external_link,
"external_log_name": getattr(task_log_reader.log_handler, "log_name", None),
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 98 additions & 17 deletions airflow-core/docs/howto/customize-ui.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,7 +21,7 @@ Customizing the UI
.. _customizing-the-ui:

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.
Expand DownExpand Up@@ -61,17 +61,47 @@ After

.. image:: ../img/change-site-title/example_instance_name_configuration.png

|

Add custom alert messages on the dashboard
------------------------------------------
Adding Dashboard Alert Messages
===============================

Extra alert messages can be shown on the UI dashboard. This can be useful for warning about setup issues
or announcing changes to end users. The following example shows how to add alert messages:
Extra alert messages can be shown on the Airflow dashboard. This can be useful for warning about setup issues, announcing changes
to end users, or providing real-time status information. Dashboard alerts support both static and dynamic content.

1. Add the following contents to ``airflow_local_settings.py`` file under ``$AIRFLOW_HOME/config``.
Each alert message should specify a severity level (``info``, ``warning``, ``error``) using ``category``.
Basic Static Alerts
-------------------

.. code-block:: python
To add static alert messages that remain constant until the webserver is restarted:

1. Create an ``airflow_local_settings.py`` file and place it in ``$PYTHONPATH`` or in the ``$AIRFLOW_HOME/config`` folder.
(Airflow adds ``$AIRFLOW_HOME/config`` to ``PYTHONPATH`` when Airflow is initialized)

2. Add the following contents to ``airflow_local_settings.py``:

.. note::
See :ref:`Configuring local settings <set-config:configuring-local-settings>` for details on how to configure local settings.

.. code-block:: python

from airflow.www.utils import UIAlert

DASHBOARD_UIALERTS = [
UIAlert("Welcome to Airflow"),
]

3. Restart the Airflow webserver, and you should now see the alert message displayed on the dashboard.

Alert Categories
----------------

You can control the category of the alert message. Available categories include:

- ``"info"`` (default) - Blue informational alerts
- ``"warning"`` - Yellow warning alerts
- ``"error"`` - Red error alerts

.. code-block:: python

from airflow.api_fastapi.common.types import UIAlert

Expand All@@ -81,19 +111,70 @@ or announcing changes to end users. The following example shows how to add alert
UIAlert(text="Critical error detected!", category="error"),
]

See :ref:`Configuring local settings <set-config:configuring-local-settings>` for details on how to
configure local settings.
.. image:: ../img/ui-alert-message.png

2. Restart Airflow Webserver, and you should now see:
Markdown Content in Alerts
--------------------------

.. image:: ../img/ui-alert-message.png
Markdown can be included in alert messages for richer formatting. In the following example, we show an alert
message of heading 2 with a link included:

Alert messages also support Markdown. In the following example, we show an alert message of heading 2 with a link included.
.. code-block:: python

.. code-block:: python
from airflow.www.utils import UIAlert

DASHBOARD_UIALERTS = [
UIAlert(text="## Visit [airflow.apache.org](https://airflow.apache.org)", category="info"),
]
DASHBOARD_UIALERTS = [
UIAlert(text="## Visit [airflow.apache.org](https://airflow.apache.org)", category="info"),
]

.. image:: ../img/ui-alert-message-markdown.png

Dynamic Dashboard Alerts
------------------------

Dashboard alerts support dynamic content that updates each time the dashboard page is refreshed. This allows for real-time
status updates without requiring webserver restarts. Dynamic alerts must be defined as an instance of an iterable object.
The recommended approach is to create a class that subclasses ``list`` and implements a custom ``__iter__`` method that
yields fresh alerts each time Airflow iterates over the alerts.

.. note::
When implementing dynamic alerts it is important to keep alert generation logic lightweight to avoid
impacting dashboard load times. Consider caching results for expensive operations and handle exceptions
gracefully to prevent alert generation from breaking the UI.

Dynamic alerts are particularly useful for:

- **Real-time notifications**: Display current status updates or announcements
- **Deployment notifications**: Show current deployment status, build progress, or GitOps state
- **Temporary maintenance alerts**: Provide time-sensitive information about ongoing maintenance or issues
- **Environment-specific warnings**: Display different alerts based on current environment conditions
- **External service status**: Show the availability of dependent services or APIs

Creating Dynamic Alerts
^^^^^^^^^^^^^^^^^^^^^^^

To create dynamic alerts, define ``DASHBOARD_UIALERTS`` as an instance of a class that subclasses ``list``
and implements the ``__iter__`` method. The UI will iterate over any number ``UIAlert`` instances yielded by
this method and expose them as alerts on the dashboard page.

The example below demonstrates how logic can be applied to yield alerts dynamically. More practical use
cases might include alerts yielded from APIs, database queries or files.

.. code-block:: python

import random
from airflow.www.utils import UIAlert


class DynamicAlerts(list):
def __iter__(self):
# This method is called each time Airflow iterates over DASHBOARD_UIALERTS
# Example: Flip a coin
if random.choice([True, False]):
yield UIAlert("Heads!", category="info")
else:
yield UIAlert("Tails!", category="warning")


# Create an instance of the class
DASHBOARD_UIALERTS = DynamicAlerts()
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
from fastapi import Depends, status

from airflow.api_fastapi.common.router import AirflowRouter
from airflow.api_fastapi.common.types import UIAlert
from airflow.api_fastapi.core_api.datamodels.ui.config import ConfigResponse
from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc
from airflow.api_fastapi.core_api.security import requires_authenticated
Expand DownExpand Up@@ -54,7 +55,8 @@ def get_configs() -> ConfigResponse:
additional_config: dict[str, Any] = {
"instance_name": conf.get("api", "instance_name", fallback="Airflow"),
"test_connection": conf.get("core", "test_connection", fallback="Disabled"),
"dashboard_alert": DASHBOARD_UIALERTS,
# Expose "dashboard_alert" using a list comprehension so UIAlert instances can be expressed dynamically.
"dashboard_alert": [alert for alert in DASHBOARD_UIALERTS if isinstance(alert, UIAlert)],
"show_external_log_redirect": task_log_reader.supports_external_link,
"external_log_name": getattr(task_log_reader.log_handler, "log_name", None),
}
Expand Down
Loading