6
0

WebServer.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. import eventlet
  2. import socketio
  3. from glob import glob
  4. from os import getcwd
  5. from os import path
  6. from os.path import exists
  7. from logging.config import dictConfig
  8. from flask import Flask
  9. from flask import send_from_directory
  10. from pycs.database.Database import Database
  11. from pycs.frontend.endpoints.ListJobs import ListJobs
  12. from pycs.frontend.endpoints.ListLabelProviders import ListLabelProviders
  13. from pycs.frontend.endpoints.ListModels import ListModels
  14. from pycs.frontend.endpoints.ListProjects import ListProjects
  15. from pycs.frontend.endpoints.additional.FolderInformation import FolderInformation
  16. from pycs.frontend.endpoints.data.GetFile import GetFile
  17. from pycs.frontend.endpoints.data.GetPreviousAndNextFile import GetPreviousAndNextFile
  18. from pycs.frontend.endpoints.data.GetResizedFile import GetResizedFile
  19. from pycs.frontend.endpoints.data.RemoveFile import RemoveFile
  20. from pycs.frontend.endpoints.data.UploadFile import UploadFile
  21. from pycs.frontend.endpoints.jobs.RemoveJob import RemoveJob
  22. from pycs.frontend.endpoints.labels.CreateLabel import CreateLabel
  23. from pycs.frontend.endpoints.labels.EditLabelName import EditLabelName
  24. from pycs.frontend.endpoints.labels.EditLabelParent import EditLabelParent
  25. from pycs.frontend.endpoints.labels.ListLabels import ListLabels
  26. from pycs.frontend.endpoints.labels.RemoveLabel import RemoveLabel
  27. from pycs.frontend.endpoints.pipelines.FitModel import FitModel
  28. from pycs.frontend.endpoints.pipelines.PredictFile import PredictFile
  29. from pycs.frontend.endpoints.pipelines.PredictModel import PredictModel
  30. from pycs.frontend.endpoints.projects.CreateProject import CreateProject
  31. from pycs.frontend.endpoints.projects.EditProjectDescription import EditProjectDescription
  32. from pycs.frontend.endpoints.projects.EditProjectName import EditProjectName
  33. from pycs.frontend.endpoints.projects.ExecuteExternalStorage import ExecuteExternalStorage
  34. from pycs.frontend.endpoints.projects.ExecuteLabelProvider import ExecuteLabelProvider
  35. from pycs.frontend.endpoints.projects.GetProjectModel import GetProjectModel
  36. from pycs.frontend.endpoints.projects.ListCollections import ListCollections
  37. from pycs.frontend.endpoints.projects.ListFiles import ListFiles
  38. from pycs.frontend.endpoints.projects.RemoveProject import RemoveProject
  39. from pycs.frontend.endpoints.results.ConfirmResult import ConfirmResult
  40. from pycs.frontend.endpoints.results.CreateResult import CreateResult
  41. from pycs.frontend.endpoints.results.EditResultData import EditResultData
  42. from pycs.frontend.endpoints.results.EditResultLabel import EditResultLabel
  43. from pycs.frontend.endpoints.results.GetProjectResults import GetProjectResults
  44. from pycs.frontend.endpoints.results.GetResults import GetResults
  45. from pycs.frontend.endpoints.results.RemoveResult import RemoveResult
  46. from pycs.frontend.endpoints.results.ResetResults import ResetResults
  47. from pycs.frontend.notifications.NotificationManager import NotificationManager
  48. from pycs.frontend.util.JSONEncoder import JSONEncoder
  49. from pycs.jobs.JobRunner import JobRunner
  50. class WebServer:
  51. """
  52. wrapper class for flask and socket.io which initializes most networking
  53. """
  54. # pylint: disable=line-too-long
  55. def __init__(self, settings: dict, database: Database, jobs: JobRunner):
  56. dictConfig(settings["logging"])
  57. # initialize web server
  58. self.__flask = Flask(__name__)
  59. init_func = self.production_init if exists('webui/index.html') else self.development_init
  60. kwargs, static_files = init_func()
  61. self.__sio = socketio.Server(**kwargs)
  62. self.__app = socketio.WSGIApp(self.__sio, self.__flask, static_files=static_files)
  63. self.__host, self.__port = settings['host'], settings['port']
  64. # set json encoder so database objects are serialized correctly
  65. self.__flask.json_encoder = JSONEncoder
  66. notifications = self.init_notifications(jobs)
  67. self.define_routes(database, jobs, notifications)
  68. @property
  69. def logger(self):
  70. return self.__flask.logger
  71. def init_notifications(self, jobs):
  72. # create notification manager
  73. notifications = NotificationManager(self.__sio)
  74. jobs.on_create(notifications.create_job)
  75. jobs.on_start(notifications.edit_job)
  76. jobs.on_progress(notifications.edit_job)
  77. jobs.on_finish(notifications.edit_job)
  78. jobs.on_remove(notifications.remove_job)
  79. return notifications
  80. def development_init(self):
  81. self.logger.info('development build')
  82. # set access control header to allow requests from Vue.js development server
  83. @self.__flask.after_request
  84. def after_request(response):
  85. # pylint: disable=unused-variable
  86. response.headers['Access-Control-Allow-Origin'] = '*'
  87. return response
  88. return dict(cors_allowed_origins='*', async_mode='eventlet'), None
  89. def production_init(self):
  90. self.logger.info('production build')
  91. kwargs = dict(async_mode='eventlet')
  92. if len(settings['allowedOrigins']) > 0:
  93. origins = settings['allowedOrigins']
  94. kwargs["cors_allowed_origins"] = origins
  95. # overwrite root path to serve index.html
  96. @self.__flask.route('/', methods=['GET'])
  97. def index():
  98. # pylint: disable=unused-variable
  99. return send_from_directory(path.join(getcwd(), 'webui'), 'index.html')
  100. return kwargs, self.static_files
  101. @property
  102. def static_files(self) -> dict:
  103. # find static files and folders
  104. static_files = {}
  105. for file_path in glob('webui/*'):
  106. file_path = file_path.replace('\\', '/')
  107. static_files[file_path[5:]] = file_path
  108. # separately add svg files and set their correct mime type
  109. for svg_path in glob('webui/img/*.svg'):
  110. svg_path = svg_path.replace('\\', '/')
  111. static_files[svg_path[5:]] = {'content_type': 'image/svg+xml', 'filename': svg_path}
  112. return static_files
  113. def define_routes(self, database, jobs, notifications):
  114. # additional
  115. self.__flask.add_url_rule(
  116. '/folder',
  117. view_func=FolderInformation.as_view('folder_information')
  118. )
  119. # jobs
  120. self.__flask.add_url_rule(
  121. '/jobs',
  122. view_func=ListJobs.as_view('list_jobs', jobs)
  123. )
  124. self.__flask.add_url_rule(
  125. '/jobs/<identifier>/remove',
  126. view_func=RemoveJob.as_view('remove_job', jobs)
  127. )
  128. # models
  129. self.__flask.add_url_rule(
  130. '/models',
  131. view_func=ListModels.as_view('list_models', database)
  132. )
  133. self.__flask.add_url_rule(
  134. '/projects/<int:identifier>/model',
  135. view_func=GetProjectModel.as_view('get_project_model', database)
  136. )
  137. # labels
  138. self.__flask.add_url_rule(
  139. '/label_providers',
  140. view_func=ListLabelProviders.as_view('label_providers', database)
  141. )
  142. self.__flask.add_url_rule(
  143. '/projects/<int:identifier>/labels',
  144. view_func=ListLabels.as_view('list_labels', database)
  145. )
  146. self.__flask.add_url_rule(
  147. '/projects/<int:identifier>/labels',
  148. view_func=CreateLabel.as_view('create_label', database, notifications)
  149. )
  150. self.__flask.add_url_rule(
  151. '/projects/<int:project_id>/labels/<int:label_id>/remove',
  152. view_func=RemoveLabel.as_view('remove_label', database, notifications)
  153. )
  154. self.__flask.add_url_rule(
  155. '/projects/<int:project_id>/labels/<int:label_id>/name',
  156. view_func=EditLabelName.as_view('edit_label_name', database, notifications)
  157. )
  158. self.__flask.add_url_rule(
  159. '/projects/<int:project_id>/labels/<int:label_id>/parent',
  160. view_func=EditLabelParent.as_view('edit_label_parent', database, notifications)
  161. )
  162. # collections
  163. self.__flask.add_url_rule(
  164. '/projects/<int:project_id>/collections',
  165. view_func=ListCollections.as_view('list_collections', database)
  166. )
  167. self.__flask.add_url_rule(
  168. '/projects/<int:project_id>/data/<int:collection_id>/<int:start>/<int:length>',
  169. view_func=ListFiles.as_view('list_collection_files', database)
  170. )
  171. # data
  172. self.__flask.add_url_rule(
  173. '/projects/<int:identifier>/data',
  174. view_func=UploadFile.as_view('upload_file', database, notifications)
  175. )
  176. self.__flask.add_url_rule(
  177. '/projects/<int:project_id>/data/<int:start>/<int:length>',
  178. view_func=ListFiles.as_view('list_files', database)
  179. )
  180. self.__flask.add_url_rule(
  181. '/data/<int:identifier>/remove',
  182. view_func=RemoveFile.as_view('remove_file', database, notifications)
  183. )
  184. self.__flask.add_url_rule(
  185. '/data/<int:file_id>',
  186. view_func=GetFile.as_view('get_file', database)
  187. )
  188. self.__flask.add_url_rule(
  189. '/data/<int:file_id>/<resolution>',
  190. view_func=GetResizedFile.as_view('get_resized_file', database)
  191. )
  192. self.__flask.add_url_rule(
  193. '/data/<int:file_id>/previous_next',
  194. view_func=GetPreviousAndNextFile.as_view('get_previous_and_next_file', database)
  195. )
  196. # results
  197. self.__flask.add_url_rule(
  198. '/projects/<int:project_id>/results',
  199. view_func=GetProjectResults.as_view('get_project_results', database)
  200. )
  201. self.__flask.add_url_rule(
  202. '/data/<int:file_id>/results',
  203. view_func=GetResults.as_view('get_results', database)
  204. )
  205. self.__flask.add_url_rule(
  206. '/data/<int:file_id>/results',
  207. view_func=CreateResult.as_view('create_result', database, notifications)
  208. )
  209. self.__flask.add_url_rule(
  210. '/data/<int:file_id>/reset',
  211. view_func=ResetResults.as_view('reset_results', database, notifications)
  212. )
  213. self.__flask.add_url_rule(
  214. '/results/<int:result_id>/remove',
  215. view_func=RemoveResult.as_view('remove_result', database, notifications)
  216. )
  217. self.__flask.add_url_rule(
  218. '/results/<int:result_id>/confirm',
  219. view_func=ConfirmResult.as_view('confirm_result', database, notifications)
  220. )
  221. self.__flask.add_url_rule(
  222. '/results/<int:result_id>/label',
  223. view_func=EditResultLabel.as_view('edit_result_label', database, notifications)
  224. )
  225. self.__flask.add_url_rule(
  226. '/results/<int:result_id>/data',
  227. view_func=EditResultData.as_view('edit_result_data', database, notifications)
  228. )
  229. # projects
  230. self.__flask.add_url_rule(
  231. '/projects',
  232. view_func=ListProjects.as_view('list_projects', database)
  233. )
  234. self.__flask.add_url_rule(
  235. '/projects',
  236. view_func=CreateProject.as_view('create_project', database, notifications, jobs)
  237. )
  238. self.__flask.add_url_rule(
  239. '/projects/<int:identifier>/label_provider',
  240. view_func=ExecuteLabelProvider.as_view('execute_label_provider', database, notifications, jobs)
  241. )
  242. self.__flask.add_url_rule(
  243. '/projects/<int:identifier>/external_storage',
  244. view_func=ExecuteExternalStorage.as_view('execute_external_storage', database, notifications, jobs)
  245. )
  246. self.__flask.add_url_rule(
  247. '/projects/<int:identifier>/remove',
  248. view_func=RemoveProject.as_view('remove_project', database, notifications)
  249. )
  250. self.__flask.add_url_rule(
  251. '/projects/<int:identifier>/name',
  252. view_func=EditProjectName.as_view('edit_project_name', database, notifications)
  253. )
  254. self.__flask.add_url_rule(
  255. '/projects/<int:identifier>/description',
  256. view_func=EditProjectDescription.as_view('edit_project_description', database, notifications)
  257. )
  258. # pipelines
  259. self.__flask.add_url_rule(
  260. '/projects/<int:project_id>/pipelines/fit',
  261. view_func=FitModel.as_view('fit_model', database, jobs)
  262. )
  263. self.__flask.add_url_rule(
  264. '/projects/<int:project_id>/pipelines/predict',
  265. view_func=PredictModel.as_view('predict_model', database, notifications, jobs)
  266. )
  267. self.__flask.add_url_rule(
  268. '/data/<int:file_id>/predict',
  269. view_func=PredictFile.as_view('predict_file', database, notifications, jobs)
  270. )
  271. def run(self):
  272. eventlet.wsgi.server(eventlet.listen((self.__host, self.__port)), self.__app)