1 # Copyright (C) 2013-2016 Martin Vejmelka, UC Denver
3 # Permission is hereby granted, free of charge, to any person obtaining a copy
4 # of this software and associated documentation files (the "Software"), to deal
5 # in the Software without restriction, including without limitation the rights
6 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 # of the Software, and to permit persons to whom the Software is furnished to do
8 # so, subject to the following conditions:
10 # The above copyright notice and this permission notice shall be included in all
11 # copies or substantial portions of the Software.
13 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14 # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
15 # A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
16 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
18 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 from __future__
import absolute_import
22 from __future__
import print_function
23 from cluster
import Cluster
24 from simulation
import create_simulation
, get_simulation_state
, cancel_simulation
, delete_simulation
, load_simulations
25 from utils
import Dict
, to_esmf
, to_utc
, load_profiles
, load_sys_cfg
, parse_kml
26 from flask
import Flask
, render_template
, request
, redirect
, make_response
, url_for
28 from datetime
import datetime
, timedelta
32 from functools
import wraps
, update_wrapper
34 from cleanup
import cleanup_delete
, cleanup_cancel
36 # global objects tracking state
41 # conf params and state
43 sims_path
= conf
['sims_path']
44 simulations
= load_simulations(sims_path
)
47 root
= osp
.join('/',conf
['root'])
49 root
= osp
.join('/',''.join(random
.choice(string
.ascii_lowercase
) for i
in range(5)))
51 debug
= conf
['debug'] in ['T', 'True', 't', 'true']
54 'submit': osp
.join(root
, 'submit'),
55 'welcome': osp
.join(root
, 'start'),
56 'overview': osp
.join(root
, 'overview'),
58 print('Welcome page is http://%s:%s%s' % (host
, port
, urls
['welcome']) )
63 # lifted from: http://arusahni.net/blog/2014/03/flask-nocache.html
66 def no_cache(*args
, **kwargs
):
67 response
= make_response(view(*args
, **kwargs
))
68 response
.headers
['Last-Modified'] = datetime
.now()
69 response
.headers
['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0'
70 response
.headers
['Pragma'] = 'no-cache'
71 response
.headers
['Expires'] = '-1'
74 return update_wrapper(no_cache
, view
)
77 @app.route(urls
['welcome'])
80 return render_template('welcome.html', cluster
=cluster
, urls
=urls
)
83 @app.route(urls
['submit'], methods
=['GET', 'POST'])
85 if request
.method
== 'GET':
86 # it's a get so let's build a fire simulation
87 return render_template('build.html', profiles
=list(profiles
.values()), urls
=urls
)
88 elif request
.method
== 'POST':
89 # it's a POST so initiate a simulation
90 # dictionary values set in the html <select name="KEY" class="ui dropdown" id="KEY">
91 sim_cfg
= request
.form
.copy()
92 print('values returned by build page:')
93 print(json
.dumps(sim_cfg
, indent
=4, separators
=(',', ': ')))
94 sim_cfg
['profile'] = profiles
[sim_cfg
['profile']]
95 sim_info
= create_simulation(sim_cfg
, conf
, cluster
)
96 sim_id
= sim_info
['id']
97 simulations
[sim_id
] = sim_info
99 print(json
.dumps(sim_info
, indent
=4, separators
=(',', ': ')))
100 f
= osp
.join(sims_path
, sim_id
+ '.json')
101 json
.dump(sim_info
, open(f
, 'w'), indent
=4, separators
=(',', ': '))
102 return redirect("/monitor/%s" % sim_id
)
104 @app.route(osp
.join(urls
['submit'],'sat_data'), methods
=['GET'])
106 if request
.method
== 'GET':
107 with
open('./sat_data/filteredSatData.json', 'r') as read_file
:
108 # with open('./sat_data/filtered_hotspots_2021-03-03_1838.json', 'r') as read_file:
109 sat_data
= json
.load(read_file
)
113 @app.route("/monitor/<sim_id>")
115 def monitor(sim_id
=None):
116 print('monitor {}'.format(sim_id
))
117 return render_template('monitor.html', sim
=simulations
.get(sim_id
, None), urls
=urls
)
120 @app.route(urls
['overview'], methods
=['GET', 'POST'])
123 if request
.method
== 'GET':
124 # reload, cleanup might delete jsons while webserver is running
125 simulations
= load_simulations(sims_path
)
126 deadline
= to_esmf(datetime
.now() - timedelta(seconds
=5))
127 # only update stale & running simulations in overview
128 kk
= list(simulations
.keys())
130 sim
= simulations
[sim_id
]
131 if sim
['state']['wrf'] != 'complete':
132 last_upd
= sim
.get('last_updated', '2000-01-01_00:00:00')
133 if last_upd
< deadline
:
134 sim
['state'] = get_simulation_state(sim
['log_file'])
135 sim
['last_updated'] = to_esmf(datetime
.now())
136 f
= osp
.join(sims_path
, sim_id
+ '.json')
138 json
.dump(sim
, open(f
,'w'), indent
=4, separators
=(',', ': '))
139 simulations
[sim_id
] = sim
141 print('File %s no longer exists, deleting simulation' % f
)
142 del simulations
[sim_id
]
143 return render_template('overview.html', simulations
=simulations
, urls
=urls
)
144 elif request
.method
== 'POST':
145 print('Values returned by overview page:')
146 sims_checked
= request
.form
.getlist('sim_chk')
148 for sim_id
in sims_checked
: # Only the simulation(s) checked in checkbox.
149 if 'RemoveB' in request
.form
:
150 print('Remove Sim: box checked= %s' % (sim_id
))
151 cleanup_delete(sim_id
)
153 if 'CancelB' in request
.form
:
154 print('Cancel Sim: box checked= %s' % (sim_id
))
155 cleanup_cancel(sim_id
)
157 print('Error-No button push detected: box checked= %s' % (sim_id
))
158 simulations
= load_simulations(sims_path
)
159 return render_template('overview.html', simulations
= simulations
, urls
=urls
)
162 # JSON access to state
163 @app.route("/retrieve_log/<sim_id>")
164 def retrieve_log(sim_id
=None):
165 sim_info
= simulations
.get(sim_id
, None)
169 return open(sim_info
['log_file']).read()
172 @app.route("/sim_info/<sim_id>")
173 def retrieve_sim_info(sim_id
=None):
174 sim_info
= simulations
.get(sim_id
, {}).copy()
175 return json
.dumps(sim_info
, indent
=4, separators
=(',', ': '))
178 @app.route("/get_state/<sim_id>")
179 def get_state(sim_id
=None):
180 sim_info
= simulations
.get(sim_id
, None)
185 # always update during get_state()
186 if sim_info
['state']['wrf'] != 'completed':
187 sim_state
= get_simulation_state(sim_info
['log_file'])
188 sim_info
['state'] = sim_state
189 sim_info
['last_updated'] = to_esmf(datetime
.now())
190 f
= osp
.join('simulations', sim_id
+ '.json')
191 json
.dump(sim_info
, open(f
, 'w'), indent
=4, separators
=(',', ': '))
192 return json
.dumps(sim_state
)
195 @app.route("/remove_sim/<sim_id>")
196 def remove_sim(sim_id
=None):
197 if sim_id
is not None:
198 delete_simulation(simulations(sim_id
, conf
))
199 del simulations
[sim_id
]
202 @app.route("/cancel_sim/<sim_id>")
203 def cancel_sim(sim_id
=None):
204 if sim_id
is not None:
205 cancel_simulation(simulations(sim_id
, conf
))
208 @app.route("/all_sims")
210 print(json
.dumps(simulations
, indent
=4, separators
=(',', ': ')))
211 return json
.dumps(simulations
, indent
=4, separators
=(',', ': '))
213 @app.route("/upload_perimeter", methods
=['POST'])
214 def upload_perimeter_file():
215 kml_content
= request
.files
['file'].read()
216 data
= parse_kml(kml_content
, 'Polygon')
217 result
= { 'data': data
}
218 return json
.dumps(result
, indent
=4, separators
=(',', ':'))
220 @app.route("/upload_line", methods
=['POST'])
221 def upload_line_file():
222 kml_content
= request
.files
['file'].read()
223 data
= parse_kml(kml_content
, 'LineString')
224 result
= { 'data': data
}
225 return json
.dumps(result
, indent
=4, separators
=(',', ':'))
227 if __name__
== '__main__':
228 profiles
= load_profiles()
229 cluster
= Cluster(json
.load(open('etc/cluster.json')))
231 app
.run(host
=host
, port
=port
, debug
=debug
)