diff --git a/airflow/www/templates/airflow/dag.html b/airflow/www/templates/airflow/dag.html
index adb2d387da443..9ef4cce5c1c46 100644
--- a/airflow/www/templates/airflow/dag.html
+++ b/airflow/www/templates/airflow/dag.html
@@ -100,6 +100,13 @@
Refresh
+
+
+
+ Delete
+
+
@@ -302,6 +309,12 @@
$("#dagModal").css("margin-top","0px");
}
+ function confirmDeleteDag(dag_id){
+ return confirm("Are you sure you want to delete '"+dag_id+"' now?\n\
+ This option will delete ALL metadata, DAG runs, etc.\n\
+ This cannot be undone.");
+ }
+
$("#btn_rendered").click(function(){
url = "{{ url_for('airflow.rendered') }}" +
"?task_id=" + encodeURIComponent(task_id) +
diff --git a/airflow/www/templates/airflow/dags.html b/airflow/www/templates/airflow/dags.html
index 23978905780ea..8a869488628fd 100644
--- a/airflow/www/templates/airflow/dags.html
+++ b/airflow/www/templates/airflow/dags.html
@@ -190,6 +190,12 @@ DAGs
+
+
+
+
+
{% endfor %}
@@ -244,6 +250,12 @@ DAGs
function confirmTriggerDag(dag_id){
return confirm("Are you sure you want to run '"+dag_id+"' now?");
}
+
+ function confirmDeleteDag(dag_id){
+ return confirm("Are you sure you want to delete '"+dag_id+"' now?\n\
+ This option will delete ALL metadata, DAG runs, etc.\n\
+ This cannot be undone.");
+ }
all_dags = $("[id^=toggle]");
$.each(all_dags, function(i,v) {
$(v).change (function() {
diff --git a/airflow/www/views.py b/airflow/www/views.py
index c8baa5c06251f..5c0c9730c7387 100644
--- a/airflow/www/views.py
+++ b/airflow/www/views.py
@@ -1044,6 +1044,32 @@ def run(self):
"it should start any moment now.".format(ti))
return redirect(origin)
+ @expose('/delete')
+ @login_required
+ @wwwutils.action_logging
+ @wwwutils.notify_owner
+ def delete(self):
+ from airflow.api.common.experimental import delete_dag
+ from airflow.exceptions import DagNotFound, DagFileExists
+
+ dag_id = request.args.get('dag_id')
+ origin = request.args.get('origin') or "/admin/"
+
+ try:
+ delete_dag.delete_dag(dag_id)
+ except DagNotFound:
+ flash("DAG with id {} not found. Cannot delete".format(dag_id))
+ return redirect(request.referrer)
+ except DagFileExists:
+ flash("Dag id {} is still in DagBag. "
+ "Remove the DAG file first.".format(dag_id))
+ return redirect(request.referrer)
+
+ flash("Deleting DAG with id {}. May take a couple minutes to fully"
+ " disappear.".format(dag_id))
+ # Upon successful delete return to origin
+ return redirect(origin)
+
@expose('/trigger')
@login_required
@wwwutils.action_logging
@@ -1283,6 +1309,10 @@ def tree(self, session=None):
dag_id = request.args.get('dag_id')
blur = conf.getboolean('webserver', 'demo_mode')
dag = dagbag.get_dag(dag_id)
+ if dag_id not in dagbag.dags:
+ flash('DAG "{0}" seems to be missing.'.format(dag_id), "error")
+ return redirect('/admin/')
+
root = request.args.get('root')
if root:
dag = dag.sub_dag(
diff --git a/airflow/www_rbac/templates/airflow/dag.html b/airflow/www_rbac/templates/airflow/dag.html
index eb449085023b9..e6495fe4be9da 100644
--- a/airflow/www_rbac/templates/airflow/dag.html
+++ b/airflow/www_rbac/templates/airflow/dag.html
@@ -99,6 +99,13 @@
Refresh
+
+
+
+ Delete
+
+
@@ -300,6 +307,12 @@
$("#dagModal").css("margin-top","0px");
}
+ function confirmDeleteDag(dag_id){
+ return confirm("Are you sure you want to delete '"+dag_id+"' now?\n\
+ This option will delete ALL metadata, DAG runs, etc.\n\
+ This cannot be undone.");
+ }
+
$("#btn_rendered").click(function(){
url = "{{ url_for('Airflow.rendered') }}" +
"?task_id=" + encodeURIComponent(task_id) +
diff --git a/airflow/www_rbac/templates/airflow/dags.html b/airflow/www_rbac/templates/airflow/dags.html
index a712e5a9ee4bc..ed11a56facb3c 100644
--- a/airflow/www_rbac/templates/airflow/dags.html
+++ b/airflow/www_rbac/templates/airflow/dags.html
@@ -191,6 +191,11 @@ DAGs
+
+
+
+
{% endfor %}
@@ -242,6 +247,12 @@ DAGs
window.location = DAGS_INDEX + "?page_size=" + p_size;
});
+ function confirmDeleteDag(dag_id){
+ return confirm("Are you sure you want to delete '"+dag_id+"' now?\n\
+ This option will delete ALL metadata, DAG runs, etc.\n\
+ This cannot be undone.");
+ }
+
function confirmTriggerDag(dag_id){
return confirm("Are you sure you want to run '"+dag_id+"' now?");
}
diff --git a/airflow/www_rbac/views.py b/airflow/www_rbac/views.py
index 91bd1770a828f..78f9799f16b04 100644
--- a/airflow/www_rbac/views.py
+++ b/airflow/www_rbac/views.py
@@ -728,6 +728,32 @@ def run(self):
"it should start any moment now.".format(ti))
return redirect(origin)
+ @expose('/delete')
+ @action_logging
+ @has_access
+ def delete(self):
+ from airflow.api.common.experimental import delete_dag
+ from airflow.exceptions import DagNotFound, DagFileExists
+
+ dag_id = request.args.get('dag_id')
+ origin = request.args.get('origin') or "/"
+
+ try:
+ delete_dag.delete_dag(dag_id)
+ except DagNotFound:
+ flash("DAG with id {} not found. Cannot delete".format(dag_id))
+ return redirect(request.referrer)
+ except DagFileExists:
+ flash("Dag id {} is still in DagBag. "
+ "Remove the DAG file first.".format(dag_id))
+ return redirect(request.referrer)
+
+ flash("Deleting DAG with id {}. May take a couple minutes to fully"
+ " disappear.".format(dag_id))
+
+ # Upon success return to origin.
+ return redirect(origin)
+
@expose('/trigger')
@has_access
@action_logging
@@ -961,6 +987,10 @@ def tree(self, session=None):
dag_id = request.args.get('dag_id')
blur = conf.getboolean('webserver', 'demo_mode')
dag = dagbag.get_dag(dag_id)
+ if dag_id not in dagbag.dags:
+ flash('DAG "{0}" seems to be missing.'.format(dag_id), "error")
+ return redirect('/')
+
root = request.args.get('root')
if root:
dag = dag.sub_dag(